Statistics
| Revision:

root / src / addr_compl.c @ 3318

History | View | Annotate | Download (30.9 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
 * Copyright (C) 1999-2012 Hiroyuki Yamamoto
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
 */
21

    
22
#ifdef HAVE_CONFIG_H
23
#  include "config.h"
24
#endif
25
#include "defs.h"
26

    
27
#include <glib.h>
28
#include <glib/gi18n.h>
29
#include <gdk/gdkkeysyms.h>
30
#include <gtk/gtk.h>
31

    
32
#include <string.h>
33
#include <ctype.h>
34

    
35
#include "xml.h"
36
#include "addr_compl.h"
37
#include "utils.h"
38
#include "addressbook.h"
39
#include "main.h"
40
#include "prefs_common.h"
41

    
42
/* How it works:
43
 *
44
 * The address book is read into memory. We set up an address list
45
 * containing all address book entries. Next we make the completion
46
 * list, which contains all the completable strings, and store a
47
 * reference to the address entry it belongs to.
48
 * After calling the g_completion_complete(), we get a reference
49
 * to a valid email address.  
50
 *
51
 * Completion is very simplified. We never complete on another prefix,
52
 * i.e. we neglect the next smallest possible prefix for the current
53
 * completion cache. This is simply done so we might break up the
54
 * addresses a little more (e.g. break up alfons@proteus.demon.nl into
55
 * something like alfons, proteus, demon, nl; and then completing on
56
 * any of those words).
57
 */ 
58
        
59
/* address_entry - structure which refers to the original address entry in the
60
 * address book 
61
 */
62
typedef struct
63
{
64
        gchar *name;
65
        gchar *address;
66
} address_entry;
67

    
68
/* completion_entry - structure used to complete addresses, with a reference
69
 * the the real address information.
70
 */
71
typedef struct
72
{
73
        gchar                *string; /* string to complete */
74
        address_entry        *ref;         /* address the string belongs to  */
75
} completion_entry;
76

    
77
/*******************************************************************************/
78

    
79
static gint            ref_count;                /* list ref count */
80
static GList            *completion_list;        /* list of strings to be checked */
81
static GList            *address_list;        /* address storage */
82
static GCompletion *completion;                /* completion object */
83

    
84
/* To allow for continuing completion we have to keep track of the state
85
 * using the following variables. No need to create a context object. */
86

    
87
static gint            completion_count;                /* nr of addresses incl. the prefix */
88
static gint            completion_next;                /* next prev address */
89
static GSList           *completion_addresses;        /* unique addresses found in the
90
                                                   completion cache. */
91
static gchar           *completion_prefix;                /* last prefix. (this is cached here
92
                                                 * because the prefix passed to g_completion
93
                                                 * is g_strdown()'ed */
94

    
95
/*******************************************************************************/
96

    
97

    
98
static void address_completion_entry_changed                (GtkEditable *editable,
99
                                                         gpointer     data);
100

    
101

    
102
/* completion_func() - used by GTK to find the string data to be used for 
103
 * completion 
104
 */
105
static gchar *completion_func(gpointer data)
106
{
107
        g_return_val_if_fail(data != NULL, NULL);
108

    
109
        return ((completion_entry *)data)->string;
110
} 
111

    
112
static void init_all(void)
113
{
114
        completion = g_completion_new(completion_func);
115
        g_return_if_fail(completion != NULL);
116
}
117

    
118
static void free_all(void)
119
{
120
        GList *walk;
121

    
122
        walk = g_list_first(completion_list);
123
        for (; walk != NULL; walk = g_list_next(walk)) {
124
                completion_entry *ce = (completion_entry *) walk->data;
125
                g_free(ce->string);
126
                g_free(ce);
127
        }
128
        g_list_free(completion_list);
129
        completion_list = NULL;
130

    
131
        walk = address_list;
132
        for (; walk != NULL; walk = g_list_next(walk)) {
133
                address_entry *ae = (address_entry *) walk->data;
134
                g_free(ae->name);
135
                g_free(ae->address);
136
                g_free(ae);
137
        }
138
        g_list_free(address_list);
139
        address_list = NULL;
140

    
141
        g_completion_free(completion);
142
        completion = NULL;
143
}
144

    
145
static gint address_entry_find_func(gconstpointer a, gconstpointer b)
146
{
147
        const address_entry *ae1 = a;
148
        const address_entry *ae2 = b;
149
        gint val;
150

    
151
        if (!a || !b)
152
                return -1;
153

    
154
        val = strcmp(ae1->name, ae2->name);
155
        if (val != 0)
156
                return val;
157
        val = strcmp(ae1->address, ae2->address);
158
        if (val != 0)
159
                return val;
160

    
161
        return 0;
162
}
163

    
164
static void add_completion_entry(const gchar *str, address_entry *ae)
165
{
166
        completion_entry *ce;
167

    
168
        if (!str || *str == '\0')
169
                return;
170
        if (!ae)
171
                return;
172

    
173
        ce = g_new0(completion_entry, 1);
174
        /* GCompletion list is case sensitive */
175
        ce->string = g_utf8_strdown(str, -1);
176
        ce->ref = ae;
177
        completion_list = g_list_append(completion_list, ce);
178
}
179

    
180
/* add_address() - adds address to the completion list. this function looks
181
 * complicated, but it's only allocation checks.
182
 */
183
static gint add_address(const gchar *name, const gchar *firstname, const gchar *lastname, const gchar *nickname, const gchar *address)
184
{
185
        address_entry *ae;
186
        GList         *found;
187

    
188
        if (!address || *address == '\0')
189
                return -1;
190

    
191
        /* debugg_print("add_address: [%s] [%s] [%s] [%s] [%s]\n", name, firstname, lastname, nickname, address); */
192

    
193
        ae = g_new0(address_entry, 1);
194
        ae->name    = g_strdup(name ? name : "");
195
        ae->address = g_strdup(address);                
196
        if ((found = g_list_find_custom(address_list, ae,
197
                                        address_entry_find_func))) {
198
                g_free(ae->name);
199
                g_free(ae->address);
200
                g_free(ae);
201
                ae = (address_entry *)found->data;
202
        } else
203
                address_list = g_list_append(address_list, ae);
204
 
205
        if (name) {
206
                const gchar *p = name;
207

    
208
                while (*p != '\0') {
209
                        add_completion_entry(p, ae);
210
                        while (*p && *p != '-' && *p != '.' && !g_ascii_isspace(*p))
211
                                p++;
212
                        while (*p == '-' || *p == '.' || g_ascii_isspace(*p))
213
                                p++;
214
                }
215
        }
216
        add_completion_entry(firstname, ae);
217
        add_completion_entry(lastname, ae);
218
        add_completion_entry(nickname, ae);
219
        add_completion_entry(address, ae);
220

    
221
        return 0;
222
}
223

    
224
/* read_address_book()
225
 */ 
226
static void read_address_book(void) {        
227
        addressbook_load_completion_full( add_address );
228
}
229

    
230
/* start_address_completion() - returns the number of addresses 
231
 * that should be matched for completion.
232
 */
233
gint start_address_completion(void)
234
{
235
        clear_completion_cache();
236
        if (!ref_count) {
237
                init_all();
238
                /* open the address book */
239
                read_address_book();
240
                /* merge the completion entry list into g_completion */
241
                if (completion_list)
242
                        g_completion_add_items(completion, completion_list);
243
        }
244
        ref_count++;
245
        debug_print("start_address_completion ref count %d\n", ref_count);
246

    
247
        return g_list_length(completion_list);
248
}
249

    
250
/* get_address_from_edit() - returns a possible address (or a part)
251
 * from an entry box. To make life easier, we only look at the last valid address 
252
 * component; address completion only works at the last string component in
253
 * the entry box. 
254
 */ 
255
gchar *get_address_from_edit(GtkEntry *entry, gint *start_pos)
256
{
257
        const gchar *edit_text, *p;
258
        gint cur_pos;
259
        gboolean in_quote = FALSE;
260
        gboolean in_bracket = FALSE;
261
        gchar *str;
262

    
263
        edit_text = gtk_entry_get_text(entry);
264
        if (edit_text == NULL) return NULL;
265

    
266
        cur_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
267

    
268
        /* scan for a separator. doesn't matter if walk points at null byte. */
269
        for (p = g_utf8_offset_to_pointer(edit_text, cur_pos);
270
             p > edit_text;
271
             p = g_utf8_prev_char(p)) {
272
                if (*p == '"')
273
                        in_quote ^= TRUE;
274
                else if (!in_quote) {
275
                        if (!in_bracket && *p == ',')
276
                                break;
277
                        else if (*p == '>')
278
                                in_bracket = TRUE;
279
                        else if (*p == '<')
280
                                in_bracket = FALSE;
281
                }
282
        }
283

    
284
        /* have something valid */
285
        if (g_utf8_strlen(p, -1) == 0)
286
                return NULL;
287

    
288
        /* now scan back until we hit a valid character */
289
        for (; *p && (*p == ',' || g_ascii_isspace(*p));
290
             p = g_utf8_next_char(p))
291
                ;
292

    
293
        if (g_utf8_strlen(p, -1) == 0)
294
                return NULL;
295

    
296
        if (start_pos) *start_pos = g_utf8_pointer_to_offset(edit_text, p);
297

    
298
        str = g_strdup(p);
299

    
300
        return str;
301
} 
302

    
303
/* replace_address_in_edit() - replaces an incompleted address with a completed one.
304
 */
305
void replace_address_in_edit(GtkEntry *entry, const gchar *newtext,
306
                             gint start_pos)
307
{
308
        gchar *origtext;
309

    
310
        if (!newtext) return;
311

    
312
        debug_print("replace_address_in_edit: %s\n", newtext);
313

    
314
        origtext = gtk_editable_get_chars(GTK_EDITABLE(entry), start_pos, -1);
315
        if (!strcmp(origtext, newtext)) {
316
                g_free(origtext);
317
                return;
318
        }
319
        g_free(origtext);
320

    
321
        g_signal_handlers_block_by_func
322
                (entry, address_completion_entry_changed, NULL);
323
        gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, -1);
324
        gtk_editable_insert_text(GTK_EDITABLE(entry), newtext, strlen(newtext),
325
                                 &start_pos);
326
        gtk_editable_set_position(GTK_EDITABLE(entry), -1);
327
        g_signal_handlers_unblock_by_func
328
                (entry, address_completion_entry_changed, NULL);
329
}
330

    
331
#if 0
332
static gint insert_address_func(gconstpointer a, gconstpointer b)
333
{
334
        const address_entry *ae1 = a;
335
        const address_entry *ae2 = b;
336
        gchar *s1, *s2;
337
        gint val;
338

339
        if (!a || !b)
340
                return -1;
341

342
        s1 = g_utf8_casefold(ae1->address, -1);
343
        s2 = g_utf8_casefold(ae2->address, -1);
344
        val = g_utf8_collate(s1, s2);
345
        g_free(s2);
346
        g_free(s1);
347
        if (val != 0)
348
                return val;
349
        s1 = g_utf8_casefold(ae1->name, -1);
350
        s2 = g_utf8_casefold(ae2->name, -1);
351
        val = g_utf8_collate(s1, s2);
352
        g_free(s2);
353
        g_free(s1);
354
        if (val != 0)
355
                return val;
356

357
        return 0;
358
}
359
#endif
360

    
361
/* complete_address() - tries to complete an addres, and returns the
362
 * number of addresses found. use get_complete_address() to get one.
363
 * returns zero if no match was found, otherwise the number of addresses,
364
 * with the original prefix at index 0. 
365
 */
366
guint complete_address(const gchar *str)
367
{
368
        GList *result;
369
        gchar *d;
370
        guint  count, cpl;
371
        completion_entry *ce;
372

    
373
        g_return_val_if_fail(str != NULL, 0);
374

    
375
        clear_completion_cache();
376
        completion_prefix = g_strdup(str);
377

    
378
        /* g_completion is case sensitive */
379
        d = g_utf8_strdown(str, -1);
380
        result = g_completion_complete(completion, d, NULL);
381

    
382
        count = g_list_length(result);
383
        if (count) {
384
                /* create list with unique addresses  */
385
                for (cpl = 0, result = g_list_first(result);
386
                     result != NULL;
387
                     result = g_list_next(result)) {
388
                        ce = (completion_entry *)(result->data);
389
                        if (NULL == g_slist_find(completion_addresses,
390
                                                 ce->ref)) {
391
                                cpl++;
392
                                completion_addresses =
393
                                        g_slist_append(completion_addresses,
394
                                                       ce->ref);
395
#if 0
396
                                        g_slist_insert_sorted
397
                                                (completion_addresses, ce->ref,
398
                                                 insert_address_func);
399
#endif
400
                        }
401
                }
402
                count = cpl + 1;        /* index 0 is the original prefix */
403
                completion_next = 1;        /* we start at the first completed one */
404
        } else {
405
                g_free(completion_prefix);
406
                completion_prefix = NULL;
407
        }
408

    
409
        completion_count = count;
410

    
411
        g_free(d);
412

    
413
        return count;
414
}
415

    
416
/* get_complete_address() - returns a complete address. the returned
417
 * string should be freed 
418
 */
419
gchar *get_complete_address(gint index)
420
{
421
        const address_entry *p;
422
        gchar *address = NULL;
423

    
424
        if (index < completion_count) {
425
                if (index == 0)
426
                        address = g_strdup(completion_prefix);
427
                else {
428
                        /* get something from the unique addresses */
429
                        p = (address_entry *)g_slist_nth_data
430
                                (completion_addresses, index - 1);
431
                        if (p != NULL) {
432
                                if (!p->name || p->name[0] == '\0')
433
                                        address = g_strdup(p->address);
434
                                else if (p->name[0] != '"' &&
435
                                         strpbrk(p->name, "(),.:;<>@[]") != NULL)
436
                                        address = g_strdup_printf
437
                                                ("\"%s\" <%s>", p->name, p->address);
438
                                else
439
                                        address = g_strdup_printf
440
                                                ("%s <%s>", p->name, p->address);
441
                        }
442
                }
443
        }
444

    
445
        return address;
446
}
447

    
448
gchar *get_next_complete_address(void)
449
{
450
        if (is_completion_pending()) {
451
                gchar *res;
452

    
453
                res = get_complete_address(completion_next);
454
                completion_next += 1;
455
                if (completion_next >= completion_count)
456
                        completion_next = 0;
457

    
458
                return res;
459
        } else
460
                return NULL;
461
}
462

    
463
gchar *get_prev_complete_address(void)
464
{
465
        if (is_completion_pending()) {
466
                int n = completion_next - 2;
467

    
468
                /* real previous */
469
                n = (n + (completion_count * 5)) % completion_count;
470

    
471
                /* real next */
472
                completion_next = n + 1;
473
                if (completion_next >=  completion_count)
474
                        completion_next = 0;
475
                return get_complete_address(n);
476
        } else
477
                return NULL;
478
}
479

    
480
guint get_completion_count(void)
481
{
482
        if (is_completion_pending())
483
                return completion_count;
484
        else
485
                return 0;
486
}
487

    
488
/* should clear up anything after complete_address() */
489
void clear_completion_cache(void)
490
{
491
        if (is_completion_pending()) {
492
                if (completion_prefix)
493
                        g_free(completion_prefix);
494

    
495
                if (completion_addresses) {
496
                        g_slist_free(completion_addresses);
497
                        completion_addresses = NULL;
498
                }
499

    
500
                completion_count = completion_next = 0;
501
        }
502
}
503

    
504
gboolean is_completion_pending(void)
505
{
506
        /* check if completion pending, i.e. we might satisfy a request for the next
507
         * or previous address */
508
         return completion_count;
509
}
510

    
511
/* invalidate_address_completion() - should be called if address book
512
 * changed; 
513
 */
514
gint invalidate_address_completion(void)
515
{
516
        if (ref_count) {
517
                /* simply the same as start_address_completion() */
518
                debug_print("Invalidation request for address completion\n");
519
                free_all();
520
                init_all();
521
                read_address_book();
522
                if (completion_list)
523
                        g_completion_add_items(completion, completion_list);
524
                clear_completion_cache();
525
        }
526

    
527
        return g_list_length(completion_list);
528
}
529

    
530
gint end_address_completion(void)
531
{
532
        clear_completion_cache();
533

    
534
        if (0 == --ref_count)
535
                free_all();
536

    
537
        debug_print("end_address_completion ref count %d\n", ref_count);
538

    
539
        return ref_count; 
540
}
541

    
542

    
543
/* address completion entry ui. the ui (completion list was inspired by galeon's
544
 * auto completion list). remaining things powered by sylpheed's completion engine.
545
 */
546

    
547
#define ENTRY_DATA_TAB_HOOK        "tab_hook"                        /* used to lookup entry */
548
#define WINDOW_DATA_COMPL_ENTRY        "compl_entry"        /* used to store entry for compl. window */
549
#define WINDOW_DATA_COMPL_CLIST "compl_clist"        /* used to store clist for compl. window */
550

    
551
static void address_completion_mainwindow_set_focus        (GtkWindow   *window,
552
                                                         GtkWidget   *widget,
553
                                                         gpointer     data);
554
static gboolean address_completion_entry_key_pressed        (GtkEntry    *entry,
555
                                                         GdkEventKey *ev,
556
                                                         gpointer     data);
557
static gboolean address_completion_complete_address_in_entry
558
                                                        (GtkEntry    *entry,
559
                                                         gboolean     next);
560
static void address_completion_create_completion_window        (GtkEntry    *entry,
561
                                                         gboolean     select_next);
562

    
563
static void completion_window_select_row(GtkCList         *clist,
564
                                         gint                  row,
565
                                         gint                  col,
566
                                         GdkEvent         *event,
567
                                         GtkWidget        **window);
568
static gboolean completion_window_button_press
569
                                        (GtkWidget         *widget,
570
                                         GdkEventButton  *event,
571
                                         GtkWidget        **window);
572
static gboolean completion_window_key_press
573
                                        (GtkWidget         *widget,
574
                                         GdkEventKey         *event,
575
                                         GtkWidget        **window);
576

    
577

    
578
static void completion_window_advance_to_row(GtkCList *clist, gint row)
579
{
580
        g_return_if_fail(row < completion_count);
581
        gtk_clist_select_row(clist, row, 0);
582
}
583

    
584
static void completion_window_advance_selection(GtkCList *clist, gboolean forward)
585
{
586
        int row;
587

    
588
        g_return_if_fail(clist != NULL);
589
        g_return_if_fail(clist->selection != NULL);
590

    
591
        row = GPOINTER_TO_INT(clist->selection->data);
592

    
593
        row = forward ? (row + 1) % completion_count :
594
                        (row - 1) < 0 ? completion_count - 1 : row - 1;
595

    
596
        gtk_clist_freeze(clist);
597
        completion_window_advance_to_row(clist, row);                                        
598
        gtk_clist_thaw(clist);
599
}
600

    
601
#if 0
602
/* completion_window_accept_selection() - accepts the current selection in the
603
 * clist, and destroys the window */
604
static void completion_window_accept_selection(GtkWidget **window,
605
                                               GtkCList *clist,
606
                                               GtkEntry *entry)
607
{
608
        gchar *address = NULL, *text = NULL;
609
        gint   cursor_pos, row;
610

611
        g_return_if_fail(window != NULL);
612
        g_return_if_fail(*window != NULL);
613
        g_return_if_fail(clist != NULL);
614
        g_return_if_fail(entry != NULL);
615
        g_return_if_fail(clist->selection != NULL);
616

617
        /* FIXME: I believe it's acceptable to access the selection member directly  */
618
        row = GPOINTER_TO_INT(clist->selection->data);
619

620
        /* we just need the cursor position */
621
        address = get_address_from_edit(entry, &cursor_pos);
622
        g_free(address);
623
        gtk_clist_get_text(clist, row, 0, &text);
624
        replace_address_in_edit(entry, text, cursor_pos);
625

626
        clear_completion_cache();
627
        gtk_widget_destroy(*window);
628
        *window = NULL;
629
}
630
#endif
631

    
632
/* completion_window_apply_selection() - apply the current selection in the
633
 * clist */
634
static void completion_window_apply_selection(GtkCList *clist, GtkEntry *entry)
635
{
636
        gchar *address = NULL, *text = NULL;
637
        gint   cursor_pos, row;
638

    
639
        g_return_if_fail(clist != NULL);
640
        g_return_if_fail(entry != NULL);
641
        g_return_if_fail(clist->selection != NULL);
642

    
643
        row = GPOINTER_TO_INT(clist->selection->data);
644

    
645
        address = get_address_from_edit(entry, &cursor_pos);
646
        g_free(address);
647
        gtk_clist_get_text(clist, row, 0, &text);
648
        replace_address_in_edit(entry, text, cursor_pos);
649
}
650

    
651
static void completion_window_apply_selection_address_only(GtkCList *clist, GtkEntry *entry)
652
{
653
        gchar *address = NULL;
654
        address_entry *ae;
655
        gint   cursor_pos, row;
656

    
657
        g_return_if_fail(clist != NULL);
658
        g_return_if_fail(entry != NULL);
659
        g_return_if_fail(clist->selection != NULL);
660

    
661
        row = GPOINTER_TO_INT(clist->selection->data);
662

    
663
        ae = (address_entry *)g_slist_nth_data(completion_addresses, row - 1);
664
        if (ae && ae->address) {
665
                address = get_address_from_edit(entry, &cursor_pos);
666
                g_free(address);
667
                replace_address_in_edit(entry, ae->address, cursor_pos);
668
        }
669
}
670

    
671
/* should be called when creating the main window containing address
672
 * completion entries */
673
void address_completion_start(GtkWidget *mainwindow)
674
{
675
        start_address_completion();
676

    
677
        /* register focus change hook */
678
        g_signal_connect(G_OBJECT(mainwindow), "set_focus",
679
                         G_CALLBACK(address_completion_mainwindow_set_focus),
680
                         mainwindow);
681
}
682

    
683
/* Need unique data to make unregistering signal handler possible for the auto
684
 * completed entry */
685
#define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
686

    
687
void address_completion_register_entry(GtkEntry *entry)
688
{
689
        g_return_if_fail(entry != NULL);
690
        g_return_if_fail(GTK_IS_ENTRY(entry));
691

    
692
        /* add hooked property */
693
        g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, entry);
694

    
695
        /* add keypress event */
696
        g_signal_connect_closure
697
                (G_OBJECT(entry), "key_press_event",
698
                 g_cclosure_new
699
                        (G_CALLBACK(address_completion_entry_key_pressed),
700
                         COMPLETION_UNIQUE_DATA, NULL),
701
                 FALSE);
702
        g_signal_connect(G_OBJECT(entry), "changed",
703
                         G_CALLBACK(address_completion_entry_changed),
704
                         NULL);
705
}
706

    
707
void address_completion_unregister_entry(GtkEntry *entry)
708
{
709
        GObject *entry_obj;
710

    
711
        g_return_if_fail(entry != NULL);
712
        g_return_if_fail(GTK_IS_ENTRY(entry));
713

    
714
        entry_obj = g_object_get_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK);
715
        g_return_if_fail(entry_obj);
716
        g_return_if_fail(entry_obj == G_OBJECT(entry));
717

    
718
        /* has the hooked property? */
719
        g_object_set_data(G_OBJECT(entry), ENTRY_DATA_TAB_HOOK, NULL);
720

    
721
        /* remove the hook */
722
        g_signal_handlers_disconnect_by_func
723
                (G_OBJECT(entry), 
724
                 G_CALLBACK(address_completion_entry_key_pressed),
725
                 COMPLETION_UNIQUE_DATA);
726
}
727

    
728
/* should be called when main window with address completion entries
729
 * terminates.
730
 * NOTE: this function assumes that it is called upon destruction of
731
 * the window */
732
void address_completion_end(GtkWidget *mainwindow)
733
{
734
        /* if address_completion_end() is really called on closing the window,
735
         * we don't need to unregister the set_focus_cb */
736
        end_address_completion();
737
}
738

    
739
/* if focus changes to another entry, then clear completion cache */
740
static void address_completion_mainwindow_set_focus(GtkWindow *window,
741
                                                    GtkWidget *widget,
742
                                                    gpointer   data)
743
{
744
        if (widget && GTK_IS_ENTRY(widget) &&
745
            g_object_get_data(G_OBJECT(widget), ENTRY_DATA_TAB_HOOK)) {
746
                clear_completion_cache();
747
        }
748
}
749

    
750
static GtkWidget *completion_window;
751

    
752
/* watch for tabs in one of the address entries. if no tab then clear the
753
 * completion cache */
754
static gboolean address_completion_entry_key_pressed(GtkEntry    *entry,
755
                                                     GdkEventKey *ev,
756
                                                     gpointer     data)
757
{
758
        if (!prefs_common.fullauto_completion_mode && ev->keyval == GDK_Tab &&
759
            !completion_window) {
760
                if (address_completion_complete_address_in_entry(entry, TRUE)) {
761
                        address_completion_create_completion_window(entry, TRUE);
762
                        /* route a void character to the default handler */
763
                        /* this is a dirty hack; we're actually changing a key
764
                         * reported by the system. */
765
                        ev->keyval = GDK_AudibleBell_Enable;
766
                        ev->state &= ~GDK_SHIFT_MASK;
767
                        return TRUE;
768
                }
769
        }
770

    
771
        if (!completion_window)
772
                return FALSE;
773

    
774
        if (       ev->keyval == GDK_Up
775
                || ev->keyval == GDK_Down
776
                || ev->keyval == GDK_Page_Up
777
                || ev->keyval == GDK_Page_Down
778
                || ev->keyval == GDK_Return
779
                || ev->keyval == GDK_Escape
780
                || ev->keyval == GDK_Tab
781
                || ev->keyval == GDK_ISO_Left_Tab) {
782
                completion_window_key_press(completion_window, ev, &completion_window);
783
                return TRUE;
784
        } else if (ev->keyval == GDK_Shift_L
785
                || ev->keyval == GDK_Shift_R
786
                || ev->keyval == GDK_Control_L
787
                || ev->keyval == GDK_Control_R
788
                || ev->keyval == GDK_Caps_Lock
789
                || ev->keyval == GDK_Shift_Lock
790
                || ev->keyval == GDK_Meta_L
791
                || ev->keyval == GDK_Meta_R
792
                || ev->keyval == GDK_Alt_L
793
                || ev->keyval == GDK_Alt_R) {
794
                /* these buttons should not clear the cache... */
795
        } else {
796
                clear_completion_cache();
797
                gtk_widget_destroy(completion_window);
798
                completion_window = NULL;
799
        }
800

    
801
        return FALSE;
802
}
803

    
804
static void address_completion_entry_changed(GtkEditable *editable,
805
                                             gpointer data)
806
{
807
        GtkEntry *entry = GTK_ENTRY(editable);
808

    
809
        if (!prefs_common.fullauto_completion_mode)
810
                return;
811

    
812
        g_signal_handlers_block_by_func
813
                (editable, address_completion_entry_changed, data);
814
        if (address_completion_complete_address_in_entry(entry, TRUE)) {
815
                address_completion_create_completion_window(entry, FALSE);
816
        } else {
817
                clear_completion_cache();
818
                if (completion_window) {
819
                        gtk_widget_destroy(completion_window);
820
                        completion_window = NULL;
821
                }
822
        }
823
        g_signal_handlers_unblock_by_func
824
                (editable, address_completion_entry_changed, data);
825
}
826

    
827
/* initialize the completion cache and put first completed string
828
 * in entry. this function used to do back cycling but this is not
829
 * currently used. since the address completion behaviour has been
830
 * changed regularly, we keep the feature in case someone changes
831
 * his / her mind again. :) */
832
static gboolean address_completion_complete_address_in_entry(GtkEntry *entry,
833
                                                             gboolean  next)
834
{
835
        gint ncount = 0, cursor_pos;
836
        gchar *address, *new = NULL;
837
        gboolean completed = FALSE;
838

    
839
        g_return_val_if_fail(entry != NULL, FALSE);
840

    
841
        if (!GTK_WIDGET_HAS_FOCUS(entry)) return FALSE;
842

    
843
        /* get an address component from the cursor */
844
        address = get_address_from_edit(entry, &cursor_pos);
845
        if (!address) return FALSE;
846

    
847
        /* still something in the cache */
848
        if (is_completion_pending()) {
849
                new = next ? get_next_complete_address() :
850
                        get_prev_complete_address();
851
        } else {
852
                if (0 < (ncount = complete_address(address)))
853
                        new = get_next_complete_address();
854
        }
855

    
856
        if (new) {
857
                /* prevent "change" signal */
858
                /* replace_address_in_edit(entry, new, cursor_pos); */
859

    
860
                /* don't complete if entry equals to the completed address */
861
                if (ncount == 2 && !strcmp(address, new))
862
                        completed = FALSE;
863
                else
864
                        completed = TRUE;
865
                g_free(new);
866
        }
867

    
868
        g_free(address);
869

    
870
        return completed;
871
}
872

    
873
static void address_completion_create_completion_window(GtkEntry *entry_,
874
                                                        gboolean select_next)
875
{
876
        gint x, y, width;
877
        GtkWidget *scroll, *clist;
878
        GtkRequisition r;
879
        guint count = 0;
880
        GtkWidget *entry = GTK_WIDGET(entry_);
881

    
882
        debug_print("address_completion_create_completion_window\n");
883

    
884
        if (completion_window) {
885
                gtk_widget_destroy(completion_window);
886
                completion_window = NULL;
887
        }
888

    
889
        scroll = gtk_scrolled_window_new(NULL, NULL);
890
        clist  = gtk_clist_new(1);
891
        gtk_clist_set_selection_mode(GTK_CLIST(clist), GTK_SELECTION_SINGLE);
892
        
893
        completion_window = gtk_window_new(GTK_WINDOW_POPUP);
894

    
895
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
896
                                       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
897
        gtk_container_add(GTK_CONTAINER(completion_window), scroll);
898
        gtk_container_add(GTK_CONTAINER(scroll), clist);
899

    
900
        /* set the unique data so we can always get back the entry and
901
         * clist window to which this completion window has been attached */
902
        g_object_set_data(G_OBJECT(completion_window),
903
                          WINDOW_DATA_COMPL_ENTRY, entry_);
904
        g_object_set_data(G_OBJECT(completion_window),
905
                          WINDOW_DATA_COMPL_CLIST, clist);
906

    
907
        g_signal_connect(G_OBJECT(clist), "select_row",
908
                         G_CALLBACK(completion_window_select_row),
909
                         &completion_window);
910

    
911
        for (count = 0; count < get_completion_count(); count++) {
912
                gchar *text[] = {NULL, NULL};
913

    
914
                text[0] = get_complete_address(count);
915
                gtk_clist_append(GTK_CLIST(clist), text);
916
                g_free(text[0]);
917
        }
918

    
919
        gdk_window_get_origin(entry->window, &x, &y);
920
        gtk_widget_size_request(entry, &r);
921
        width = entry->allocation.width;
922
        y += r.height;
923
        gtk_window_move(GTK_WINDOW(completion_window), x, y);
924

    
925
        gtk_widget_size_request(clist, &r);
926
        gtk_widget_set_size_request(completion_window, width, r.height);
927
        gtk_widget_show_all(completion_window);
928
        gtk_widget_size_request(clist, &r);
929

    
930
        if ((y + r.height) > gdk_screen_height()) {
931
                gtk_window_set_policy(GTK_WINDOW(completion_window),
932
                                      TRUE, FALSE, FALSE);
933
                gtk_widget_set_size_request(completion_window, width,
934
                                            gdk_screen_height () - y);
935
        }
936

    
937
        g_signal_connect(G_OBJECT(completion_window),
938
                         "button-press-event",
939
                         G_CALLBACK(completion_window_button_press),
940
                         &completion_window);
941
        g_signal_connect(G_OBJECT(completion_window),
942
                         "key-press-event",
943
                         G_CALLBACK(completion_window_key_press),
944
                         &completion_window);
945
        gdk_pointer_grab(completion_window->window, TRUE,
946
                         GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
947
                         GDK_BUTTON_RELEASE_MASK,
948
                         NULL, NULL, GDK_CURRENT_TIME);
949
        gtk_grab_add(completion_window);
950

    
951
        /* this gets rid of the irritating focus rectangle that doesn't
952
         * follow the selection */
953
        GTK_WIDGET_UNSET_FLAGS(clist, GTK_CAN_FOCUS);
954
        gtk_clist_select_row(GTK_CLIST(clist), select_next ? 1 : 0, 0);
955

    
956
        debug_print("address_completion_create_completion_window done\n");
957
}
958

    
959

    
960
/* row selection sends completed address to entry.
961
 * note: event is NULL if selected by anything else than a mouse button. */
962
static void completion_window_select_row(GtkCList *clist, gint row, gint col,
963
                                         GdkEvent *event,
964
                                         GtkWidget **window)
965
{
966
        GtkEntry *entry;
967

    
968
        g_return_if_fail(window != NULL);
969
        g_return_if_fail(*window != NULL);
970

    
971
        entry = GTK_ENTRY(g_object_get_data(G_OBJECT(*window),
972
                                            WINDOW_DATA_COMPL_ENTRY));
973
        g_return_if_fail(entry != NULL);
974

    
975
        completion_window_apply_selection(clist, entry);
976

    
977
        if (!event || event->type != GDK_BUTTON_RELEASE)
978
                return;
979

    
980
        clear_completion_cache();
981
        gtk_widget_destroy(*window);
982
        *window = NULL;
983
}
984

    
985
/* completion_window_button_press() - check is mouse click is anywhere
986
 * else (not in the completion window). in that case the completion
987
 * window is destroyed, and the original prefix is restored */
988
static gboolean completion_window_button_press(GtkWidget *widget,
989
                                               GdkEventButton *event,
990
                                               GtkWidget **window)
991
{
992
        GtkWidget *event_widget, *entry;
993
        gchar *prefix;
994
        gint cursor_pos;
995
        gboolean restore = TRUE;
996

    
997
        g_return_val_if_fail(window != NULL, FALSE);
998
        g_return_val_if_fail(*window != NULL, FALSE);
999

    
1000
        entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1001
                                             WINDOW_DATA_COMPL_ENTRY));
1002
        g_return_val_if_fail(entry != NULL, FALSE);
1003

    
1004
        event_widget = gtk_get_event_widget((GdkEvent *)event);
1005
        if (event_widget != widget) {
1006
                while (event_widget) {
1007
                        if (event_widget == widget)
1008
                                return FALSE;
1009
                        else if (event_widget == entry) {
1010
                                restore = FALSE;
1011
                                break;
1012
                        }
1013
                    event_widget = event_widget->parent;
1014
                }
1015
        }
1016

    
1017
        if (restore) {
1018
                prefix = get_complete_address(0);
1019
                g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1020
                replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
1021
                g_free(prefix);
1022
        }
1023

    
1024
        clear_completion_cache();
1025
        gtk_widget_destroy(*window);
1026
        *window = NULL;
1027

    
1028
        return TRUE;
1029
}
1030

    
1031
static gboolean completion_window_key_press(GtkWidget *widget,
1032
                                            GdkEventKey *event,
1033
                                            GtkWidget **window)
1034
{
1035
        GtkWidget *entry;
1036
        gchar *prefix;
1037
        gint cursor_pos;
1038
        GtkWidget *clist;
1039

    
1040
        g_return_val_if_fail(window != NULL, FALSE);
1041
        g_return_val_if_fail(*window != NULL, FALSE);
1042

    
1043
        if (!is_completion_pending())
1044
                g_warning("completion is not pending!\n");
1045

    
1046
        entry = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1047
                                             WINDOW_DATA_COMPL_ENTRY));
1048
        clist = GTK_WIDGET(g_object_get_data(G_OBJECT(*window),
1049
                                             WINDOW_DATA_COMPL_CLIST));
1050
        g_return_val_if_fail(entry != NULL, FALSE);
1051

    
1052
        /* allow keyboard navigation in the alternatives clist */
1053
        if (event->keyval == GDK_Up || event->keyval == GDK_Down ||
1054
            event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down) {
1055
                completion_window_advance_selection
1056
                        (GTK_CLIST(clist),
1057
                         event->keyval == GDK_Down ||
1058
                         event->keyval == GDK_Page_Down ? TRUE : FALSE);
1059
                return FALSE;
1060
        }                
1061

    
1062
        /* also make tab / shift tab go to next previous completion entry. we're
1063
         * changing the key value */
1064
        if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
1065
                event->keyval = (event->state & GDK_SHIFT_MASK)
1066
                        ? GDK_Up : GDK_Down;
1067
                /* need to reset shift state if going up */
1068
                if (event->state & GDK_SHIFT_MASK)
1069
                        event->state &= ~GDK_SHIFT_MASK;
1070
                completion_window_advance_selection(GTK_CLIST(clist), 
1071
                        event->keyval == GDK_Down ? TRUE : FALSE);
1072
                return FALSE;
1073
        }
1074

    
1075
        /* look for presses that accept the selection */
1076
        if (event->keyval == GDK_Return ||
1077
            (!prefs_common.fullauto_completion_mode &&
1078
             event->keyval == GDK_space)) {
1079
                /* insert address only if shift or control is pressed */
1080
                if (event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK) ||
1081
                    prefs_common.always_add_address_only) {
1082
                        completion_window_apply_selection_address_only
1083
                                (GTK_CLIST(clist), GTK_ENTRY(entry));
1084
                }
1085
                clear_completion_cache();
1086
                gtk_widget_destroy(*window);
1087
                *window = NULL;
1088
                return FALSE;
1089
        }
1090

    
1091
        /* key state keys should never be handled */
1092
        if (event->keyval == GDK_Shift_L
1093
                 || event->keyval == GDK_Shift_R
1094
                 || event->keyval == GDK_Control_L
1095
                 || event->keyval == GDK_Control_R
1096
                 || event->keyval == GDK_Caps_Lock
1097
                 || event->keyval == GDK_Shift_Lock
1098
                 || event->keyval == GDK_Meta_L
1099
                 || event->keyval == GDK_Meta_R
1100
                 || event->keyval == GDK_Alt_L
1101
                 || event->keyval == GDK_Alt_R) {
1102
                return FALSE;
1103
        }
1104

    
1105
        /* other key, let's restore the prefix (orignal text) */
1106
        if (!prefs_common.fullauto_completion_mode ||
1107
            event->keyval == GDK_Escape) {
1108
                prefix = get_complete_address(0);
1109
                g_free(get_address_from_edit(GTK_ENTRY(entry), &cursor_pos));
1110
                replace_address_in_edit(GTK_ENTRY(entry), prefix, cursor_pos);
1111
                g_free(prefix);
1112
        }
1113

    
1114
        /* make sure anything we typed comes in the edit box */
1115
        if ((!prefs_common.fullauto_completion_mode && event->length > 0 &&
1116
             event->keyval != GDK_Escape) ||
1117
            (prefs_common.fullauto_completion_mode &&
1118
             event->keyval != GDK_Escape)) {
1119
                GtkWidget *pwin = entry;
1120

    
1121
                while ((pwin = gtk_widget_get_parent(pwin)) != NULL) {
1122
                        if (GTK_WIDGET_TOPLEVEL(pwin)) {
1123
                                gtk_window_propagate_key_event
1124
                                        (GTK_WINDOW(pwin), event);
1125
                                if (prefs_common.fullauto_completion_mode)
1126
                                        return TRUE;
1127
                        }
1128
                }
1129
        }
1130

    
1131
        /* and close the completion window */
1132
        clear_completion_cache();
1133
        gtk_widget_destroy(*window);
1134
        *window = NULL;
1135

    
1136
        return TRUE;
1137
}