2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
4 * Copyright (c) 2000-2001 by Alfons Hoogervorst <alfons@proteus.demon.nl>
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.
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.
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.
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>
38 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
44 #include "addr_compl.h"
46 #include "addressbook.h"
50 debug_mode == 0 ? (debug_mode == debug_mode) : debug_print
54 * The address book is read into memory. We set up an address list
55 * containing all address book entries. Next we make the completion
56 * list, which contains all the completable strings, and store a
57 * reference to the address entry it belongs to.
58 * After calling the g_completion_complete(), we get a reference
59 * to a valid email address.
61 * Completion is very simplified. We never complete on another prefix,
62 * i.e. we neglect the next smallest possible prefix for the current
63 * completion cache. This is simply done so we might break up the
64 * addresses a little more (e.g. break up alfons@proteus.demon.nl into
65 * something like alfons, proteus, demon, nl; and then completing on
66 * any of those words).
69 /* address_entry - structure which refers to the original address entry in the
78 /* completion_entry - structure used to complete addresses, with a reference
79 * the the real address information.
83 gchar
*string
; /* string to complete */
84 address_entry
*ref
; /* address the string belongs to */
87 /*******************************************************************************/
89 static gint g_ref_count
; /* list ref count */
90 static GList
*g_completion_list
; /* list of strings to be checked */
91 static GList
*g_address_list
; /* address storage */
92 static GCompletion
*g_completion
; /* completion object */
94 /* To allow for continuing completion we have to keep track of the state
95 * using the following variables. No need to create a context object. */
97 static gint g_completion_count
; /* nr of addresses incl. the prefix */
98 static gint g_completion_next
; /* next prev address */
99 static GSList
*g_completion_addresses
; /* unique addresses found in the
101 static gchar
*g_completion_prefix
; /* last prefix. (this is cached here
102 * because the prefix passed to g_completion
103 * is g_strdown()'ed */
105 /*******************************************************************************/
107 /* completion_func() - used by GTK to find the string data to be used for
110 static gchar
*completion_func(gpointer data
)
112 g_return_val_if_fail(data
!= NULL
, NULL
);
114 return ((completion_entry
*)data
)->string
;
117 static void init_all(void)
119 g_completion
= g_completion_new(completion_func
);
120 g_return_if_fail(g_completion
!= NULL
);
123 static void free_all(void)
127 walk
= g_list_first(g_completion_list
);
128 for (; walk
!= NULL
; walk
= g_list_next(walk
)) {
129 completion_entry
*ce
= (completion_entry
*) walk
->data
;
133 g_list_free(g_completion_list
);
134 g_completion_list
= NULL
;
136 walk
= g_address_list
;
137 for (; walk
!= NULL
; walk
= g_list_next(walk
)) {
138 address_entry
*ae
= (address_entry
*) walk
->data
;
143 g_list_free(g_address_list
);
144 g_address_list
= NULL
;
146 g_completion_free(g_completion
);
150 /* add_address() - adds address to the completion list. this function looks
151 * complicated, but it's only allocation checks.
153 static gint
add_address(const gchar
*name
, const gchar
*address
)
156 completion_entry
*ce1
;
157 completion_entry
*ce2
;
159 if (!name
|| !address
) return -1;
161 ae
= g_new0(address_entry
, 1);
162 ce1
= g_new0(completion_entry
, 1),
163 ce2
= g_new0(completion_entry
, 1);
165 g_return_val_if_fail(ae
!= NULL
, -1);
166 g_return_val_if_fail(ce1
!= NULL
&& ce2
!= NULL
, -1);
168 ae
->name
= g_strdup(name
);
169 ae
->address
= g_strdup(address
);
170 ce1
->string
= g_strdup(name
);
171 ce2
->string
= g_strdup(address
);
173 /* GCompletion list is case sensitive */
174 g_strdown(ce2
->string
);
175 g_strdown(ce1
->string
);
176 ce1
->ref
= ce2
->ref
= ae
;
178 g_completion_list
= g_list_append(g_completion_list
, ce1
);
179 g_completion_list
= g_list_append(g_completion_list
, ce2
);
180 g_address_list
= g_list_append(g_address_list
, ae
);
185 static gboolean
get_all_addresses(GNode
*node
, gpointer ae
)
187 XMLNode
*xmlnode
= (XMLNode
*)node
->data
;
188 address_entry
*addr
= (address_entry
*)ae
;
190 /* this simply checks if tag is "item". in that case, it
191 * verifies it has already seen an item. if it did, an
192 * address retrieval was complete */
193 if (!strcmp(xmlnode
->tag
->tag
, "item")) {
194 /* see if a previous item was complete */
195 /* TODO: does sylpheed address book allow empty names to be entered?
196 * if so, addr->name *AND* addr->address should be checked. */
197 /* add address to our database */
198 add_address(addr
->name
, addr
->address
);
200 g_free(addr
->address
);
202 addr
->address
= NULL
;
203 } else if (!strcmp(xmlnode
->tag
->tag
, "name"))
204 addr
->name
= g_strdup(xmlnode
->element
);
205 else if (!strcmp(xmlnode
->tag
->tag
, "address"))
206 addr
->address
= g_strdup(xmlnode
->element
);
211 /* read_address_book()
213 static void read_address_book(void)
217 address_entry ad
= {NULL
, NULL
};
219 LOG_MESSAGE( _("%s%d entering read_address_book\n"), __FILE__
, __LINE__
);
220 path
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, ADDRESS_BOOK
, NULL
);
221 tree
= xml_parse_file(path
);
224 LOG_MESSAGE( _("%s(%d) no addressbook\n"), __FILE__
, __LINE__
);
228 /* retrieve all addresses */
229 g_node_traverse(tree
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1,
230 (GNodeTraverseFunc
) get_all_addresses
, &ad
);
231 /* still one pending? */
233 add_address(ad
.name
, ad
.address
);
242 LOG_MESSAGE(_("%s(%d) leaving read_address_book - OK\n"), __FILE__
, __LINE__
);
245 /* start_address_completion() - returns the number of addresses
246 * that should be matched for completion.
248 gint
start_address_completion(void)
250 clear_completion_cache();
253 /* open the address book */
255 /* merge the completion entry list into g_completion */
256 if (g_completion_list
)
257 g_completion_add_items(g_completion
, g_completion_list
);
260 LOG_MESSAGE("start_address_completion ref count %d\n", g_ref_count
);
262 return g_list_length(g_completion_list
);
265 /* get_address_from_edit() - returns a possible address (or a part)
266 * from an entry box. To make life easier, we only look at the last valid address
267 * component; address completion only works at the last string component in
270 gchar
*get_address_from_edit(GtkEntry
*entry
, gint
*start_pos
)
272 const gchar
*edit_text
;
276 wchar_t rfc_mail_sep
;
279 edit_text
= gtk_entry_get_text(entry
);
280 if (edit_text
== NULL
) return NULL
;
282 wtext
= strdup_mbstowcs(edit_text
);
283 g_return_val_if_fail(wtext
!= NULL
, NULL
);
285 cur_pos
= gtk_editable_get_position(GTK_EDITABLE(entry
));
287 if (mbtowc(&rfc_mail_sep
, ",", 1) < 0) {
292 /* scan for a separator. doesn't matter if walk points at null byte. */
293 for (wp
= wtext
+ cur_pos
; wp
> wtext
&& *wp
!= rfc_mail_sep
; wp
--)
296 /* have something valid */
297 if (wcslen(wp
) == 0) {
302 #define IS_VALID_CHAR(x) (iswalnum(x) || ((x) > 0x7f))
304 /* now scan back until we hit a valid character */
305 for (; *wp
&& !IS_VALID_CHAR(*wp
); wp
++)
310 if (wcslen(wp
) == 0) {
315 if (start_pos
) *start_pos
= wp
- wtext
;
317 str
= strdup_wcstombs(wp
);
323 /* replace_address_in_edit() - replaces an incompleted address with a completed one.
325 void replace_address_in_edit(GtkEntry
*entry
, const gchar
*newtext
,
328 gtk_editable_delete_text(GTK_EDITABLE(entry
), start_pos
, -1);
329 gtk_editable_insert_text(GTK_EDITABLE(entry
), newtext
, strlen(newtext
),
331 gtk_editable_set_position(GTK_EDITABLE(entry
), -1);
334 /* complete_address() - tries to complete an addres, and returns the
335 * number of addresses found. use get_complete_address() to get one.
336 * returns zero if no match was found, otherwise the number of addresses,
337 * with the original prefix at index 0.
339 guint
complete_address(const gchar
*str
)
344 completion_entry
*ce
;
346 g_return_val_if_fail(str
!= NULL
, 0);
348 Xstrdup_a(d
, str
, return 0);
350 clear_completion_cache();
351 g_completion_prefix
= g_strdup(str
);
353 /* g_completion is case sensitive */
355 result
= g_completion_complete(g_completion
, d
, NULL
);
357 count
= g_list_length(result
);
359 /* create list with unique addresses */
360 for (cpl
= 0, result
= g_list_first(result
);
362 result
= g_list_next(result
)) {
363 ce
= (completion_entry
*)(result
->data
);
364 if (NULL
== g_slist_find(g_completion_addresses
,
367 g_completion_addresses
=
368 g_slist_append(g_completion_addresses
,
372 count
= cpl
+ 1; /* index 0 is the original prefix */
373 g_completion_next
= 1; /* we start at the first completed one */
375 g_free(g_completion_prefix
);
376 g_completion_prefix
= NULL
;
379 g_completion_count
= count
;
383 /* get_complete_address() - returns a complete address. the returned
384 * string should be freed
386 gchar
*get_complete_address(gint index
)
388 const address_entry
*p
;
390 if (index
< g_completion_count
) {
392 return g_strdup(g_completion_prefix
);
394 /* get something from the unique addresses */
395 p
= (address_entry
*)g_slist_nth_data
396 (g_completion_addresses
, index
- 1);
400 return g_strdup_printf
401 ("%s <%s>", p
->name
, p
->address
);
407 gchar
*get_next_complete_address(void)
409 if (is_completion_pending()) {
412 res
= get_complete_address(g_completion_next
);
413 g_completion_next
+= 1;
414 if (g_completion_next
>= g_completion_count
)
415 g_completion_next
= 0;
422 gchar
*get_prev_complete_address(void)
424 if (is_completion_pending()) {
425 int n
= g_completion_next
- 2;
428 n
= (n
+ (g_completion_count
* 5)) % g_completion_count
;
431 g_completion_next
= n
+ 1;
432 if (g_completion_next
>= g_completion_count
)
433 g_completion_next
= 0;
434 return get_complete_address(n
);
439 guint
get_completion_count(void)
441 if (is_completion_pending())
442 return g_completion_count
;
447 /* should clear up anything after complete_address() */
448 void clear_completion_cache(void)
450 if (is_completion_pending()) {
451 if (g_completion_prefix
)
452 g_free(g_completion_prefix
);
454 if (g_completion_addresses
) {
455 g_slist_free(g_completion_addresses
);
456 g_completion_addresses
= NULL
;
459 g_completion_count
= g_completion_next
= 0;
463 gboolean
is_completion_pending(void)
465 /* check if completion pending, i.e. we might satisfy a request for the next
466 * or previous address */
467 return g_completion_count
;
470 /* invalidate_address_completion() - should be called if address book
473 gint
invalidate_address_completion(void)
476 /* simply the same as start_address_completion() */
477 LOG_MESSAGE("Invalidation request for address completion\n");
481 g_completion_add_items(g_completion
, g_completion_list
);
482 clear_completion_cache();
485 return g_list_length(g_completion_list
);
488 gint
end_address_completion(void)
490 clear_completion_cache();
492 if (0 == --g_ref_count
)
495 LOG_MESSAGE("end_address_completion ref count %d\n", g_ref_count
);
501 /* address completion entry ui. the ui (completion list was inspired by galeon's
502 * auto completion list). remaining things powered by sylpheed's completion engine.
505 #define ENTRY_DATA_TAB_HOOK "tab_hook" /* used to lookup entry */
506 #define WINDOW_DATA_COMPL_ENTRY "compl_entry" /* used to store entry for compl. window */
507 #define WINDOW_DATA_COMPL_CLIST "compl_clist" /* used to store clist for compl. window */
509 static void address_completion_mainwindow_set_focus (GtkWindow
*window
,
512 static gboolean
address_completion_entry_key_pressed (GtkEntry
*entry
,
515 static gboolean address_completion_complete_address_in_entry
518 static void address_completion_create_completion_window (GtkEntry
*entry
);
520 static void completion_window_select_row(GtkCList
*clist
,
524 GtkWidget
**completion_window
);
525 static gboolean completion_window_button_press
527 GdkEventButton
*event
,
528 GtkWidget
**completion_window
);
529 static gboolean completion_window_key_press
532 GtkWidget
**completion_window
);
535 static void completion_window_advance_to_row(GtkCList
*clist
, gint row
)
537 g_return_if_fail(row
< g_completion_count
);
538 gtk_clist_select_row(clist
, row
, 0);
541 static void completion_window_advance_selection(GtkCList
*clist
, gboolean forward
)
545 g_return_if_fail(clist
!= NULL
);
546 g_return_if_fail(clist
->selection
!= NULL
);
548 row
= GPOINTER_TO_INT(clist
->selection
->data
);
550 row
= forward
? (row
+ 1) % g_completion_count
:
551 (row
- 1) < 0 ? g_completion_count
- 1 : row
- 1;
553 gtk_clist_freeze(clist
);
554 completion_window_advance_to_row(clist
, row
);
555 gtk_clist_thaw(clist
);
558 /* completion_window_accept_selection() - accepts the current selection in the
559 * clist, and destroys the window */
560 static void completion_window_accept_selection(GtkWidget
**window
,
564 gchar
*address
= NULL
, *text
= NULL
;
565 gint cursor_pos
, row
, col
;
567 g_return_if_fail(window
!= NULL
);
568 g_return_if_fail(*window
!= NULL
);
569 g_return_if_fail(clist
!= NULL
);
570 g_return_if_fail(entry
!= NULL
);
571 g_return_if_fail(clist
->selection
!= NULL
);
575 /* FIXME: I believe it's acceptable to access the selection member directly */
576 row
= GPOINTER_TO_INT(clist
->selection
->data
);
578 /* we just need the cursor position */
579 address
= get_address_from_edit(entry
, &cursor_pos
);
580 gtk_clist_get_text(clist
, row
, col
, &text
);
581 replace_address_in_edit(entry
, text
, cursor_pos
);
584 clear_completion_cache();
585 gtk_widget_destroy(*window
);
589 /* should be called when creating the main window containing address
590 * completion entries */
591 void address_completion_start(GtkWidget
*mainwindow
)
593 start_address_completion();
595 /* register focus change hook */
596 gtk_signal_connect(GTK_OBJECT(mainwindow
), "set_focus",
597 GTK_SIGNAL_FUNC(address_completion_mainwindow_set_focus
),
601 /* Need unique data to make unregistering signal handler possible for the auto
603 #define COMPLETION_UNIQUE_DATA (GINT_TO_POINTER(0xfeefaa))
605 void address_completion_register_entry(GtkEntry
*entry
)
607 g_return_if_fail(entry
!= NULL
);
608 g_return_if_fail(GTK_IS_ENTRY(entry
));
610 /* add hooked property */
611 gtk_object_set_data(GTK_OBJECT(entry
), ENTRY_DATA_TAB_HOOK
, entry
);
613 /* add keypress event */
614 gtk_signal_connect_full(GTK_OBJECT(entry
), "key_press_event",
615 GTK_SIGNAL_FUNC(address_completion_entry_key_pressed
),
617 COMPLETION_UNIQUE_DATA
,
623 void address_completion_unregister_entry(GtkEntry
*entry
)
625 GtkObject
*entry_obj
;
627 g_return_if_fail(entry
!= NULL
);
628 g_return_if_fail(GTK_IS_ENTRY(entry
));
630 entry_obj
= gtk_object_get_data(GTK_OBJECT(entry
), ENTRY_DATA_TAB_HOOK
);
631 g_return_if_fail(entry_obj
);
632 g_return_if_fail(entry_obj
== GTK_OBJECT(entry
));
634 /* has the hooked property? */
635 gtk_object_set_data(GTK_OBJECT(entry
), ENTRY_DATA_TAB_HOOK
, NULL
);
637 /* remove the hook */
638 gtk_signal_disconnect_by_func(GTK_OBJECT(entry
),
639 GTK_SIGNAL_FUNC(address_completion_entry_key_pressed
),
640 COMPLETION_UNIQUE_DATA
);
643 /* should be called when main window with address completion entries
645 * NOTE: this function assumes that it is called upon destruction of
647 void address_completion_end(GtkWidget
*mainwindow
)
649 /* if address_completion_end() is really called on closing the window,
650 * we don't need to unregister the set_focus_cb */
651 end_address_completion();
654 /* if focus changes to another entry, then clear completion cache */
655 static void address_completion_mainwindow_set_focus(GtkWindow
*window
,
660 clear_completion_cache();
663 /* watch for tabs in one of the address entries. if no tab then clear the
664 * completion cache */
665 static gboolean
address_completion_entry_key_pressed(GtkEntry
*entry
,
669 if (ev
->keyval
== GDK_Tab
) {
670 if (address_completion_complete_address_in_entry(entry
, TRUE
)) {
671 address_completion_create_completion_window(entry
);
672 /* route a void character to the default handler */
673 /* this is a dirty hack; we're actually changing a key
674 * reported by the system. */
675 ev
->keyval
= GDK_AudibleBell_Enable
;
676 ev
->state
&= ~GDK_SHIFT_MASK
;
677 gtk_signal_emit_stop_by_name(GTK_OBJECT(entry
),
682 } else if (ev
->keyval
== GDK_Shift_L
683 || ev
->keyval
== GDK_Shift_R
684 || ev
->keyval
== GDK_Control_L
685 || ev
->keyval
== GDK_Control_R
686 || ev
->keyval
== GDK_Caps_Lock
687 || ev
->keyval
== GDK_Shift_Lock
688 || ev
->keyval
== GDK_Meta_L
689 || ev
->keyval
== GDK_Meta_R
690 || ev
->keyval
== GDK_Alt_L
691 || ev
->keyval
== GDK_Alt_R
) {
692 /* these buttons should not clear the cache... */
694 clear_completion_cache();
699 /* initialize the completion cache and put first completed string
700 * in entry. this function used to do back cycling but this is not
701 * currently used. since the address completion behaviour has been
702 * changed regularly, we keep the feature in case someone changes
703 * his / her mind again. :) */
704 static gboolean
address_completion_complete_address_in_entry(GtkEntry
*entry
,
707 gint ncount
, cursor_pos
;
708 gchar
*address
, *new = NULL
;
709 gboolean completed
= FALSE
;
711 g_return_val_if_fail(entry
!= NULL
, FALSE
);
713 if (!GTK_WIDGET_HAS_FOCUS(entry
)) return FALSE
;
715 /* get an address component from the cursor */
716 if (0 != (address
= get_address_from_edit(entry
, &cursor_pos
))) {
717 /* still something in the cache */
718 if (is_completion_pending()) {
719 new = next
? get_next_complete_address() :
720 get_prev_complete_address();
722 if (0 < (ncount
= complete_address(address
)))
723 new = get_next_complete_address();
727 /* prevent "change" signal */
728 replace_address_in_edit(entry
, new, cursor_pos
);
739 static void address_completion_create_completion_window(GtkEntry
*entry_
)
741 static GtkWidget
*completion_window
;
742 gint x
, y
, height
, width
, depth
;
743 GtkWidget
*scroll
, *clist
;
746 GtkWidget
*entry
= GTK_WIDGET(entry_
);
748 if (completion_window
) {
749 gtk_widget_destroy(completion_window
);
750 completion_window
= NULL
;
753 scroll
= gtk_scrolled_window_new(NULL
, NULL
);
754 clist
= gtk_clist_new(1);
755 gtk_clist_set_selection_mode(GTK_CLIST(clist
), GTK_SELECTION_SINGLE
);
757 completion_window
= gtk_window_new(GTK_WINDOW_POPUP
);
759 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll
),
760 GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
761 gtk_container_add(GTK_CONTAINER(completion_window
), scroll
);
762 gtk_container_add(GTK_CONTAINER(scroll
), clist
);
764 /* set the unique data so we can always get back the entry and
765 * clist window to which this completion window has been attached */
766 gtk_object_set_data(GTK_OBJECT(completion_window
),
767 WINDOW_DATA_COMPL_ENTRY
, entry_
);
768 gtk_object_set_data(GTK_OBJECT(completion_window
),
769 WINDOW_DATA_COMPL_CLIST
, clist
);
771 gtk_signal_connect(GTK_OBJECT(clist
), "select_row",
772 GTK_SIGNAL_FUNC(completion_window_select_row
),
775 for (count
= 0; count
< get_completion_count(); count
++) {
776 gchar
*text
[] = {NULL
, NULL
};
778 text
[0] = get_complete_address(count
);
779 gtk_clist_append(GTK_CLIST(clist
), text
);
783 gdk_window_get_geometry(entry
->window
, &x
, &y
, &width
, &height
, &depth
);
784 gdk_window_get_deskrelative_origin (entry
->window
, &x
, &y
);
786 gtk_widget_set_uposition(completion_window
, x
, y
);
788 gtk_widget_size_request(clist
, &r
);
789 gtk_widget_set_usize(completion_window
, width
, r
.height
);
790 gtk_widget_show_all(completion_window
);
791 gtk_widget_size_request(clist
, &r
);
793 if ((y
+ r
.height
) > gdk_screen_height()) {
794 gtk_window_set_policy(GTK_WINDOW(completion_window
),
796 gtk_widget_set_usize(completion_window
, width
,
797 gdk_screen_height () - y
);
800 gtk_signal_connect(GTK_OBJECT(completion_window
),
801 "button-press-event",
802 GTK_SIGNAL_FUNC(completion_window_button_press
),
804 gtk_signal_connect(GTK_OBJECT(completion_window
),
806 GTK_SIGNAL_FUNC(completion_window_key_press
),
808 gdk_pointer_grab(completion_window
->window
, TRUE
,
809 GDK_POINTER_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
|
810 GDK_BUTTON_RELEASE_MASK
,
811 NULL
, NULL
, GDK_CURRENT_TIME
);
812 gtk_grab_add(completion_window
);
814 /* this gets rid of the irritating focus rectangle that doesn't
815 * follow the selection */
816 GTK_WIDGET_UNSET_FLAGS(clist
, GTK_CAN_FOCUS
);
817 gtk_clist_select_row(GTK_CLIST(clist
), 1, 0);
821 /* row selection sends completed address to entry.
822 * note: event is NULL if selected by anything else than a mouse button. */
823 static void completion_window_select_row(GtkCList
*clist
, gint row
, gint col
,
825 GtkWidget
**completion_window
)
829 /* first check if it's anything but a mouse event. Mouse events
830 * accept the completion. Anything else is accepted by the
831 * completion_window_key_press() */
833 /* event == NULL if key press did the selection or just
834 * event emitted with signal_emit_XXX(). This seems to
835 * be the case for the gtk versions I have seen */
839 /* however, a future version of GTK might pass the event type
840 * that triggered the select_row. since this event handler
841 * only wants mouse clicks, we check for that. */
842 if (event
->type
!= GDK_BUTTON_RELEASE
) {
846 g_return_if_fail(completion_window
!= NULL
);
847 g_return_if_fail(*completion_window
!= NULL
);
849 entry
= GTK_ENTRY(gtk_object_get_data(GTK_OBJECT(*completion_window
),
850 WINDOW_DATA_COMPL_ENTRY
));
851 g_return_if_fail(entry
!= NULL
);
853 completion_window_accept_selection(completion_window
, clist
, entry
);
856 /* completion_window_button_press() - check is mouse click is anywhere
857 * else (not in the completion window). in that case the completion
858 * window is destroyed, and the original prefix is restored */
859 static gboolean
completion_window_button_press(GtkWidget
*widget
,
860 GdkEventButton
*event
,
861 GtkWidget
**completion_window
)
863 GtkWidget
*event_widget
, *entry
;
866 gboolean restore
= TRUE
;
868 g_return_val_if_fail(completion_window
!= NULL
, FALSE
);
869 g_return_val_if_fail(*completion_window
!= NULL
, FALSE
);
871 entry
= GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window
),
872 WINDOW_DATA_COMPL_ENTRY
));
873 g_return_val_if_fail(entry
!= NULL
, FALSE
);
875 event_widget
= gtk_get_event_widget((GdkEvent
*)event
);
876 if (event_widget
!= widget
) {
877 while (event_widget
) {
878 if (event_widget
== widget
)
880 else if (event_widget
== entry
) {
884 event_widget
= event_widget
->parent
;
889 prefix
= get_complete_address(0);
890 g_free(get_address_from_edit(GTK_ENTRY(entry
), &cursor_pos
));
891 replace_address_in_edit(GTK_ENTRY(entry
), prefix
, cursor_pos
);
894 gtk_widget_destroy(*completion_window
);
895 *completion_window
= NULL
;
897 clear_completion_cache();
901 static gboolean
completion_window_key_press(GtkWidget
*widget
,
903 GtkWidget
**completion_window
)
905 GdkEventKey tmp_event
;
911 g_return_val_if_fail(completion_window
!= NULL
, FALSE
);
912 g_return_val_if_fail(*completion_window
!= NULL
, FALSE
);
914 entry
= GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window
),
915 WINDOW_DATA_COMPL_ENTRY
));
916 clist
= GTK_WIDGET(gtk_object_get_data(GTK_OBJECT(*completion_window
),
917 WINDOW_DATA_COMPL_CLIST
));
918 g_return_val_if_fail(entry
!= NULL
, FALSE
);
920 /* allow keyboard navigation in the alternatives clist */
921 if (event
->keyval
== GDK_Up
|| event
->keyval
== GDK_Down
||
922 event
->keyval
== GDK_Page_Up
|| event
->keyval
== GDK_Page_Down
) {
923 completion_window_advance_selection
925 event
->keyval
== GDK_Down
||
926 event
->keyval
== GDK_Page_Down
? TRUE
: FALSE
);
930 /* also make tab / shift tab go to next previous completion entry. we're
931 * changing the key value */
932 if (event
->keyval
== GDK_Tab
|| event
->keyval
== GDK_ISO_Left_Tab
) {
933 event
->keyval
= (event
->state
& GDK_SHIFT_MASK
)
935 /* need to reset shift state if going up */
936 if (event
->state
& GDK_SHIFT_MASK
)
937 event
->state
&= ~GDK_SHIFT_MASK
;
938 completion_window_advance_selection(GTK_CLIST(clist
),
939 event
->keyval
== GDK_Down
? TRUE
: FALSE
);
943 /* look for presses that accept the selection */
944 if (event
->keyval
== GDK_Return
|| event
->keyval
== GDK_space
) {
945 completion_window_accept_selection(completion_window
,
951 /* key state keys should never be handled */
952 if (event
->keyval
== GDK_Shift_L
953 || event
->keyval
== GDK_Shift_R
954 || event
->keyval
== GDK_Control_L
955 || event
->keyval
== GDK_Control_R
956 || event
->keyval
== GDK_Caps_Lock
957 || event
->keyval
== GDK_Shift_Lock
958 || event
->keyval
== GDK_Meta_L
959 || event
->keyval
== GDK_Meta_R
960 || event
->keyval
== GDK_Alt_L
961 || event
->keyval
== GDK_Alt_R
) {
965 /* other key, let's restore the prefix (orignal text) */
966 prefix
= get_complete_address(0);
967 g_free(get_address_from_edit(GTK_ENTRY(entry
), &cursor_pos
));
968 replace_address_in_edit(GTK_ENTRY(entry
), prefix
, cursor_pos
);
970 clear_completion_cache();
972 /* make sure anything we typed comes in the edit box */
973 tmp_event
.type
= event
->type
;
974 tmp_event
.window
= entry
->window
;
975 tmp_event
.send_event
= TRUE
;
976 tmp_event
.time
= event
->time
;
977 tmp_event
.state
= event
->state
;
978 tmp_event
.keyval
= event
->keyval
;
979 tmp_event
.length
= event
->length
;
980 tmp_event
.string
= event
->string
;
981 gtk_widget_event(entry
, (GdkEvent
*)&tmp_event
);
983 /* and close the completion window */
984 gtk_widget_destroy(*completion_window
);
985 *completion_window
= NULL
;