1 /*****************************************************************************
2 * $Id: gtkcompletionline.cc,v 1.33 2003/11/16 10:55:07 andreas99 Exp $
3 * Copyright (C) 2000, Mishoo
4 * Author: Mihai Bazon Email: mishoo@fenrir.infoiasi.ro
5 *
6 * Distributed under the terms of the GNU General Public License. You are
7 * free to use/modify/distribute this program as long as you comply to the
8 * terms of the GNU General Public License, version 2 or above, at your
9 * option, and provided that this copyright notice remains intact.
10 *****************************************************************************/
11
12
13 #include <gdk/gdkkeysyms.h>
14 #include <gtk/gtksignal.h>
15 #include <gtk/gtkclist.h>
16 #include <gtk/gtkwindow.h>
17 #include <gtk/gtkscrolledwindow.h>
18 #include <gtk/gtkmain.h>
19
20 #include <stddef.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <dirent.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <string.h>
28
29 #include <iostream>
30 #include <set>
31 #include <sstream>
32 #include <string>
33 #include <vector>
34 using namespace std;
35
36 #include "gtkcompletionline.h"
37
38 static int on_row_selected_handler = 0;
39 static int on_key_press_handler = 0;
40
41 /* GLOBALS */
42
43 GtkType type = 0;
44
45 /* signals */
46 enum {
47 UNIQUE,
48 NOTUNIQUE,
49 INCOMPLETE,
50 RUNWITHTERM,
51 SEARCH_MODE,
52 SEARCH_LETTER,
53 SEARCH_NOT_FOUND,
54 EXT_HANDLER,
55 CANCEL,
56 LAST_SIGNAL
57 };
58
59 #define GEN_COMPLETION_OK 1
60 #define GEN_CANT_COMPLETE 2
61 #define GEN_NOT_UNIQUE 3
62
63 static guint gtk_completion_line_signals[LAST_SIGNAL];
64
65 typedef set<string> StrSet;
66 typedef vector<string> StrList;
67
68 static StrSet path;
69 static StrSet execs;
70 static StrSet dirlist;
71 static string prefix;
72 static int g_show_dot_files;
73
74 /* callbacks */
75 static void gtk_completion_line_class_init(GtkCompletionLineClass *klass);
76 static void gtk_completion_line_init(GtkCompletionLine *object);
77
78 static gboolean
79 on_key_press(GtkCompletionLine *cl, GdkEventKey *event, gpointer data);
80
81 /* get_type */
gtk_completion_line_get_type(void)82 GtkType gtk_completion_line_get_type(void)
83 {
84 if (type == 0)
85 {
86 GtkTypeInfo type_info =
87 {
88 (gchar *)"GtkCompletionLine",
89 sizeof(GtkCompletionLine),
90 sizeof(GtkCompletionLineClass),
91 (GtkClassInitFunc)gtk_completion_line_class_init,
92 (GtkObjectInitFunc)gtk_completion_line_init,
93 /*(GtkArgSetFunc)*/NULL /* reserved */,
94 /*(GtkArgGetFunc)*/NULL /* reserved */
95 };
96 type = gtk_type_unique(gtk_entry_get_type(), &type_info);
97 }
98 return type;
99 }
100
101 /* class_init */
102 static void
gtk_completion_line_class_init(GtkCompletionLineClass * klass)103 gtk_completion_line_class_init(GtkCompletionLineClass *klass)
104 {
105 GtkObjectClass *object_class;
106
107 object_class = (GtkObjectClass*)klass;
108
109 gtk_completion_line_signals[UNIQUE] =
110 gtk_signal_new("unique",
111 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
112 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
113 unique),
114 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
115
116 gtk_completion_line_signals[NOTUNIQUE] =
117 gtk_signal_new("notunique",
118 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
119 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
120 notunique),
121 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
122
123 gtk_completion_line_signals[INCOMPLETE] =
124 gtk_signal_new("incomplete",
125 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
126 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
127 incomplete),
128 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
129
130 gtk_completion_line_signals[RUNWITHTERM] =
131 gtk_signal_new("runwithterm",
132 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
133 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
134 runwithterm),
135 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
136
137 gtk_completion_line_signals[SEARCH_MODE] =
138 gtk_signal_new("search_mode",
139 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
140 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
141 search_mode),
142 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
143
144 gtk_completion_line_signals[SEARCH_NOT_FOUND] =
145 gtk_signal_new("search_not_found",
146 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
147 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
148 search_not_found),
149 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
150
151 gtk_completion_line_signals[SEARCH_LETTER] =
152 gtk_signal_new("search_letter",
153 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
154 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
155 search_letter),
156 gtk_signal_default_marshaller, GTK_TYPE_NONE, 0);
157
158 gtk_completion_line_signals[EXT_HANDLER] =
159 gtk_signal_new("ext_handler",
160 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
161 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
162 ext_handler),
163 gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1,
164 GTK_TYPE_POINTER);
165
166 gtk_completion_line_signals[CANCEL] =
167 gtk_signal_new("cancel",
168 GTK_RUN_FIRST, G_TYPE_FROM_CLASS(object_class),
169 GTK_SIGNAL_OFFSET(GtkCompletionLineClass,
170 ext_handler),
171 gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1,
172 GTK_TYPE_POINTER);
173
174 //gtk_object_class_add_signals(object_class,
175 // gtk_completion_line_signals, LAST_SIGNAL);
176
177 klass->unique = NULL;
178 klass->notunique = NULL;
179 klass->incomplete = NULL;
180 klass->runwithterm = NULL;
181 klass->search_mode = NULL;
182 klass->search_letter = NULL;
183 klass->search_not_found = NULL;
184 klass->ext_handler = NULL;
185 klass->cancel = NULL;
186 }
187
188 /* init */
189 static void
gtk_completion_line_init(GtkCompletionLine * object)190 gtk_completion_line_init(GtkCompletionLine *object)
191 {
192 /* Add object initialization / creation stuff here */
193
194 object->where = NULL;
195 object->cmpl = NULL;
196 object->win_compl = NULL;
197 object->list_compl = NULL;
198 object->hist_search_mode = GCL_SEARCH_OFF;
199 object->hist_word = new string;
200 object->tabtimeout = 0;
201 object->show_dot_files = 0;
202
203 on_key_press_handler =
204 gtk_signal_connect(GTK_OBJECT(object), "key_press_event",
205 GTK_SIGNAL_FUNC(on_key_press), NULL);
206 gtk_signal_connect(GTK_OBJECT(object), "key_release_event",
207 GTK_SIGNAL_FUNC(on_key_press), NULL);
208
209 object->hist = new HistoryFile();
210
211 object->first_key = 1;
212 }
213
gtk_completion_line_last_history_item(GtkCompletionLine * object)214 void gtk_completion_line_last_history_item(GtkCompletionLine* object) {
215 const char *last_item = object->hist->last_item();
216 if (last_item) {
217 object->hist->set_default("");
218 const char* txt = object->hist->prev();
219 gtk_entry_set_text(GTK_ENTRY(object),
220 g_locale_to_utf8 (txt, -1, NULL, NULL, NULL));
221 gtk_entry_select_region(GTK_ENTRY(object), 0, strlen(txt));
222 }
223 }
224
quote_string(const string & str)225 string quote_string(const string& str)
226 {
227 string res;
228 const char* i = str.c_str();
229 while (*i) {
230 char c = *i++;
231 switch (c) {
232 case ' ':
233 res += '\\';
234 default:
235 res += c;
236 }
237 }
238 return res;
239 }
240
241 static void
get_token(istream & is,string & s)242 get_token(istream& is, string& s)
243 {
244 s.clear();
245 bool escaped = false;
246 while (!is.eof()) {
247 char c = is.get();
248 if (is.eof())
249 break;
250 if (escaped) {
251 s += c;
252 escaped = false;
253 } else if (c == '\\') {
254 // s += c;
255 escaped = true;
256 } else if (::isspace(c)) {
257 while (::isspace(c) && !is.eof()) c = is.get();
258 if (!is.eof())
259 is.unget();
260 break;
261 } else {
262 s += c;
263 }
264 }
265 }
266
267 int
get_words(GtkCompletionLine * object,vector<string> & words)268 get_words(GtkCompletionLine *object, vector<string>& words)
269 {
270 string content(gtk_entry_get_text(GTK_ENTRY(object)));
271 int pos_in_text = gtk_editable_get_position(GTK_EDITABLE(object));
272 int pos = 0;
273 {
274 string::iterator i = content.begin() + pos_in_text;
275 if (i != content.end())
276 content.insert(i, ' ');
277 }
278 istringstream ss(content);
279
280 while (!ss.eof()) {
281 string s;
282 // ss >> s;
283 get_token(ss, s);
284 words.push_back(s);
285 if (ss.eof()) break;
286 if (ss.tellg() < pos_in_text && ss.tellg() >= 0)
287 ++pos;
288 }
289
290 return pos;
291 }
292
293 int
set_words(GtkCompletionLine * object,const vector<string> & words,int pos=-1)294 set_words(GtkCompletionLine *object, const vector<string>& words, int pos = -1)
295 {
296 ostringstream ss;
297 if (pos == -1)
298 pos = words.size() - 1;
299 int where = 0;
300
301 vector<string>::const_iterator
302 i = words.begin(),
303 i_end = words.end();
304
305 while (i != i_end) {
306 ss << quote_string(*i++);
307 if (i != i_end)
308 ss << ' ';
309 if (!pos && !where)
310 where = ss.tellp();
311 else
312 --pos;
313 }
314 ss << ends;
315
316 if (words.size() == 1) {
317 const string& s = words.back();
318 size_t pos = s.rfind('.');
319 if (pos != string::npos)
320 gtk_signal_emit_by_name(
321 GTK_OBJECT(object), "ext_handler", s.substr(pos + 1).c_str());
322 else
323 gtk_signal_emit_by_name(GTK_OBJECT(object), "ext_handler", NULL);
324 }
325
326 gtk_entry_set_text(GTK_ENTRY(object),
327 g_locale_to_utf8 (ss.str().c_str(), -1, NULL, NULL, NULL));
328 gtk_editable_set_position(GTK_EDITABLE(object), where);
329 return where;
330 }
331
332 static void
generate_path()333 generate_path()
334 {
335 char *path_cstr = (char*)getenv("PATH");
336
337 istringstream path_ss(path_cstr);
338 string tmp;
339
340 path.clear();
341 while (!path_ss.eof()) {
342 tmp = "";
343 do {
344 char c;
345 c = path_ss.get();
346 if (c == ':' || path_ss.eof()) break;
347 else tmp += c;
348 } while (true);
349 if (tmp.length() != 0)
350 path.insert(tmp);
351 }
352 }
353
354 static int
select_executables_only(const struct dirent * dent)355 select_executables_only(const struct dirent* dent)
356 {
357 int len = strlen(dent->d_name);
358 int lenp = prefix.length();
359
360 if (dent->d_name[0] == '.') {
361 if (!g_show_dot_files)
362 return 0;
363 if (dent->d_name[1] == '\0')
364 return 0;
365 if ((dent->d_name[1] == '.') && (dent->d_name[2] == '\0'))
366 return 0;
367 }
368 if (dent->d_name[len - 1] == '~')
369 return 0;
370 if (lenp == 0)
371 return 1;
372 if (lenp > len)
373 return 0;
374
375 if (strncmp(dent->d_name, prefix.c_str(), lenp) == 0)
376 return 1;
377
378 return 0;
379 }
380
my_alphasort(const struct dirent ** va,const struct dirent ** vb)381 int my_alphasort(const struct dirent** va, const struct dirent** vb) {
382 const struct dirent** a = (const struct dirent**)va;
383 const struct dirent** b = (const struct dirent**)vb;
384
385 const char* s1 = (*a)->d_name;
386 const char* s2 = (*b)->d_name;
387
388 int l1 = strlen(s1);
389 int l2 = strlen(s2);
390 int result = strcmp(s1, s2);
391
392 if (result == 0) return 0;
393
394 if (l1 < l2) {
395 int res2 = strncmp(s1, s2, l1);
396 if (res2 == 0) return -1;
397 } else {
398 int res2 = strncmp(s1, s2, l2);
399 if (res2 == 0) return 1;
400 }
401
402 return result;
403 }
404
405 static void
generate_execs()406 generate_execs()
407 {
408 execs.clear();
409
410 for (StrSet::iterator i = path.begin(); i != path.end(); i++) {
411 struct dirent **eps;
412 int n = scandir(i->c_str(), &eps, select_executables_only, my_alphasort);
413 if (n >= 0) {
414 for (int j = 0; j < n; j++) {
415 execs.insert(eps[j]->d_name);
416 free(eps[j]);
417 }
418 free(eps);
419 }
420 }
421 }
422
423 static int
generate_completion_from_execs(GtkCompletionLine * object)424 generate_completion_from_execs(GtkCompletionLine *object)
425 {
426 g_list_foreach(object->cmpl, (GFunc)g_string_free, NULL);
427 g_list_free(object->cmpl);
428 object->cmpl = NULL;
429
430 for (StrSet::const_iterator i = execs.begin(); i != execs.end(); i++) {
431 GString *the_fucking_gstring = g_string_new(i->c_str());
432 object->cmpl = g_list_append(object->cmpl, the_fucking_gstring);
433 }
434
435 return 0;
436 }
437
438 static string
get_common_part(const char * p1,const char * p2)439 get_common_part(const char *p1, const char *p2)
440 {
441 string ret;
442
443 while (*p1 == *p2 && *p1 != '\0' && *p2 != '\0') {
444 ret += *p1;
445 p1++;
446 p2++;
447 }
448
449 return ret;
450 }
451
452 static int
complete_common(GtkCompletionLine * object)453 complete_common(GtkCompletionLine *object)
454 {
455 GList *l;
456 GList *ls = object->cmpl;
457 vector<string> words;
458 int pos = get_words(object, words);
459 words[pos] = ((GString*)ls->data)->str;
460
461 ls = g_list_next(ls);
462 while (ls != NULL) {
463 words[pos] = get_common_part(words[pos].c_str(),
464 ((GString*)ls->data)->str);
465 ls = g_list_next(ls);
466 }
467
468 set_words(object, words, pos);
469
470 ls = object->cmpl;
471 l = ls;
472 /*
473 if (words[pos] == ((GString*)(l->data))->str) {
474 ls = g_list_remove_link(ls, l);
475 ls = g_list_append(ls, l->data);
476 g_list_free_1(l);
477 object->cmpl = ls;
478 }
479 */
480 return 0;
481 }
482
483 static int
generate_dirlist(const char * what)484 generate_dirlist(const char *what)
485 {
486 char *str = strdup(what);
487 char *p = str + 1;
488 char *filename = str;
489 string dest("/");
490 int n;
491
492 while (*p != '\0') {
493 dest += *p;
494 if (*p == '/') {
495 DIR* dir = opendir(dest.c_str());
496 if (!dir)
497 goto dirty;
498 closedir(dir);
499 filename = p;
500 }
501 ++p;
502 }
503
504 *filename = '\0';
505 filename++;
506 dest = str;
507 dest += '/';
508
509 dirlist.clear();
510 struct dirent **eps;
511 prefix = filename;
512 n = scandir(dest.c_str(), &eps, select_executables_only, my_alphasort);
513 if (n >= 0) {
514 for (int j = 0; j < n; j++) {
515 {
516 string foo(dest);
517 foo += eps[j]->d_name;
518 struct stat filestatus;
519 stat(foo.c_str(), &filestatus);
520 if (S_ISDIR(filestatus.st_mode)) foo += '/';
521 dirlist.insert(foo);
522 }
523 free(eps[j]);
524 }
525 free(eps);
526 }
527
528 free(str);
529 return GEN_COMPLETION_OK;
530
531 dirty:
532 free(str);
533 return GEN_CANT_COMPLETE;
534 }
535
536 static int
generate_completion_from_dirlist(GtkCompletionLine * object)537 generate_completion_from_dirlist(GtkCompletionLine *object)
538 {
539 g_list_foreach(object->cmpl, (GFunc)g_string_free, NULL);
540 g_list_free(object->cmpl);
541 object->cmpl = NULL;
542
543 for (StrSet::const_iterator i = dirlist.begin(); i != dirlist.end(); i++) {
544 GString *the_fucking_gstring = g_string_new(i->c_str());
545 object->cmpl = g_list_append(object->cmpl, the_fucking_gstring);
546 }
547
548 return 0;
549 }
550
551 static int
parse_tilda(GtkCompletionLine * object)552 parse_tilda(GtkCompletionLine *object)
553 {
554 string text = gtk_entry_get_text(GTK_ENTRY(object));
555 gint where = (gint)text.find("~");
556 if (where != (gint)string::npos) {
557 if ((where > 0) && (text[where - 1] != ' '))
558 return 0;
559 if (where < (gint)text.size() - 1 && text[where + 1] != '/') {
560 // FIXME: Parse another user's home
561 } else {
562 string home = g_get_home_dir();
563 size_t i = home.length() - 1;
564 while ((i >= 0) && (home[i] == '/'))
565 home.erase(i--);
566 gtk_editable_insert_text(GTK_EDITABLE(object), home.c_str(), home.length(), &where);
567 gtk_editable_delete_text(GTK_EDITABLE(object), where, where + 1);
568 }
569 }
570
571 return 0;
572 }
573
574 static void
complete_from_list(GtkCompletionLine * object)575 complete_from_list(GtkCompletionLine *object)
576 {
577 parse_tilda(object);
578 vector<string> words;
579 int pos = get_words(object, words);
580
581 prefix = words[pos];
582
583 if (object->win_compl != NULL) {
584 object->where = (GList*)gtk_clist_get_row_data(
585 GTK_CLIST(object->list_compl), object->list_compl_items_where);
586 words[pos] = ((GString*)object->where->data)->str;
587 int current_pos = set_words(object, words, pos);
588 gtk_entry_select_region(GTK_ENTRY(object),
589 object->pos_in_text, current_pos);
590 int &item = object->list_compl_items_where;
591 gtk_clist_select_row(GTK_CLIST(object->list_compl), item, 0);
592 gtk_clist_moveto(GTK_CLIST(object->list_compl), item, 0, 0.5, 0.0);
593 } else {
594 words[pos] = ((GString*)object->where->data)->str;
595 object->pos_in_text = gtk_editable_get_position(GTK_EDITABLE(object));
596 int current_pos = set_words(object, words, pos);
597 gtk_entry_select_region(GTK_ENTRY(object),
598 object->pos_in_text, current_pos);
599 object->where = g_list_next(object->where);
600 }
601 }
602
603 static void
on_row_selected(GtkWidget * ls,gint row,gint col,GdkEvent * ev,gpointer data)604 on_row_selected(GtkWidget *ls, gint row, gint col, GdkEvent *ev, gpointer data)
605 {
606 GtkCompletionLine *cl = GTK_COMPLETION_LINE(data);
607
608 cl->list_compl_items_where = row;
609 gtk_signal_handler_block(GTK_OBJECT(cl->list_compl),
610 on_row_selected_handler);
611 complete_from_list(cl);
612 gtk_signal_handler_unblock(GTK_OBJECT(cl->list_compl),
613 on_row_selected_handler);
614 }
615
616 /*
617
618 static void
619 select_appropiate(GtkCompletionLine *object)
620 {
621 for (int i = 0; i < object->list_compl_nr_rows; ++i) {
622 char *text;
623 gtk_clist_get_text(GTK_CLIST(object->list_compl), i, 0, &text);
624 if (strncmp(prefix.c_str(), text, prefix.length())) {
625 gtk_signal_handler_block(GTK_OBJECT(object->list_compl),
626 on_row_selected_handler);
627 gtk_clist_select_row(GTK_CLIST(object->list_compl), i, 0);
628 object->list_compl_items_where = i;
629 gtk_signal_handler_unblock(GTK_OBJECT(object->list_compl),
630 on_row_selected_handler);
631 break;
632 }
633 }
634 }
635
636 static void
637 get_prefix(GtkCompletionLine *object)
638 {
639 parse_tilda(object);
640 vector<string> words;
641 int pos = get_words(object, words);
642 prefix = words[pos];
643 }
644
645 */
646
647 static int
complete_line(GtkCompletionLine * object)648 complete_line(GtkCompletionLine *object)
649 {
650 parse_tilda(object);
651 vector<string> words;
652 int pos = get_words(object, words);
653 prefix = words[pos];
654
655 g_show_dot_files = object->show_dot_files;
656 if (prefix[0] != '/') {
657 if (object->where == NULL) {
658 generate_path();
659 generate_execs();
660 generate_completion_from_execs(object);
661 object->where = NULL;
662 }
663 } else if (object->where == NULL) {
664 generate_dirlist(prefix.c_str());
665 generate_completion_from_dirlist(object);
666 object->where = NULL;
667 }
668
669 if (object->cmpl != NULL) {
670 complete_common(object);
671 object->where = object->cmpl;
672 }
673
674 // FUCK C! C++ Rules!
675 if (object->where != NULL) {
676 if (object->win_compl != NULL) {
677 int &item = object->list_compl_items_where;
678 ++item;
679 if (item >= object->list_compl_nr_rows)
680 item = object->list_compl_nr_rows - 1;
681 }
682 complete_from_list(object);
683 } else if (object->cmpl != NULL) {
684 complete_common(object);
685 object->where = object->cmpl;
686 }
687
688 GList *ls = object->cmpl;
689
690 if (g_list_length(ls) == 1) {
691 gtk_signal_emit_by_name(GTK_OBJECT(object), "unique");
692 return GEN_COMPLETION_OK;
693 } else if (g_list_length(ls) == 0 || ls == NULL) {
694 gtk_signal_emit_by_name(GTK_OBJECT(object), "incomplete");
695 return GEN_CANT_COMPLETE;
696 } else if (g_list_length(ls) > 1) {
697 gtk_signal_emit_by_name(GTK_OBJECT(object), "notunique");
698
699 vector<string> words;
700 int pos = get_words(object, words);
701
702 if (words[pos] == ((GString*)ls->data)->str) {
703
704 if (object->win_compl == NULL) {
705 object->win_compl = gtk_window_new(GTK_WINDOW_POPUP);
706 gtk_widget_set_name(object->win_compl, "Msh_Run_Window");
707
708 /*gtk_window_set_position(GTK_WINDOW(object->win_compl),
709 GTK_WIN_POS_MOUSE);*/
710
711 gtk_window_set_policy(GTK_WINDOW(object->win_compl),
712 FALSE, FALSE, TRUE);
713
714 object->list_compl = gtk_clist_new(1);
715
716 on_row_selected_handler =
717 gtk_signal_connect(GTK_OBJECT(object->list_compl), "select_row",
718 GTK_SIGNAL_FUNC(on_row_selected), object);
719
720 gtk_signal_handler_block(GTK_OBJECT(object->list_compl),
721 on_row_selected_handler);
722
723 GList *p = ls;
724 object->list_compl_nr_rows = 0;
725 while (p) {
726 char *tmp[2];
727 tmp[0] = ((GString*)p->data)->str;
728 tmp[1] = NULL;
729 int row = gtk_clist_append(GTK_CLIST(object->list_compl), tmp);
730 gtk_clist_set_row_data(GTK_CLIST(object->list_compl), row, p);
731 object->list_compl_nr_rows++;
732 p = g_list_next(p);
733 }
734
735 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
736 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_OUT);
737 gtk_widget_show(scroll);
738 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scroll),
739 GTK_POLICY_NEVER,
740 GTK_POLICY_AUTOMATIC);
741
742 gtk_container_set_border_width(GTK_CONTAINER(object->list_compl), 2);
743 gtk_container_add(GTK_CONTAINER (scroll), object->list_compl);
744
745 object->list_compl_items_where = 0;
746
747 gtk_widget_show(object->list_compl);
748 int w = gtk_clist_optimal_column_width(GTK_CLIST(object->list_compl), 0);
749 gtk_widget_set_usize(scroll, w + 40, 150);
750
751 gtk_container_add(GTK_CONTAINER(object->win_compl), scroll);
752
753 GdkWindow *top = gtk_widget_get_parent_window(GTK_WIDGET(object));
754 int x, y;
755 gdk_window_get_position(top, &x, &y);
756 x += GTK_WIDGET(object)->allocation.x;
757 y += GTK_WIDGET(object)->allocation.y +
758 GTK_WIDGET(object)->allocation.height;
759
760 // gtk_widget_popup(object->win_compl, x, y);
761 gtk_window_move(GTK_WINDOW(object->win_compl), x, y);
762 gtk_widget_show(object->win_compl);
763
764 gtk_clist_select_row(GTK_CLIST(object->list_compl),
765 object->list_compl_items_where, 0);
766
767 gtk_signal_handler_unblock(GTK_OBJECT(object->list_compl),
768 on_row_selected_handler);
769 }
770
771 return GEN_COMPLETION_OK;
772 }
773 return GEN_NOT_UNIQUE;
774 }
775
776 return GEN_COMPLETION_OK;
777 }
778
779 GtkWidget *
gtk_completion_line_new()780 gtk_completion_line_new()
781 {
782 return GTK_WIDGET(gtk_type_new(gtk_completion_line_get_type()));
783 }
784
785 static void
up_history(GtkCompletionLine * cl)786 up_history(GtkCompletionLine* cl)
787 {
788 cl->hist->set_default(gtk_entry_get_text(GTK_ENTRY(cl)));
789 gtk_entry_set_text(GTK_ENTRY(cl),
790 g_locale_to_utf8 (cl->hist->prev(), -1, NULL, NULL, NULL));
791 }
792
793 static void
down_history(GtkCompletionLine * cl)794 down_history(GtkCompletionLine* cl)
795 {
796 cl->hist->set_default(gtk_entry_get_text(GTK_ENTRY(cl)));
797 gtk_entry_set_text(GTK_ENTRY(cl),
798 g_locale_to_utf8 (cl->hist->next(), -1, NULL, NULL, NULL));
799 }
800
801 static int
search_back_history(GtkCompletionLine * cl,bool avance,bool begin)802 search_back_history(GtkCompletionLine* cl, bool avance, bool begin)
803 {
804 if (!cl->hist_word->empty()) {
805 const char * histext;
806 if (avance) {
807 histext = cl->hist->prev_to_first();
808 if (histext == NULL) {
809 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_not_found");
810 return 0;
811 }
812 } else {
813 histext = gtk_entry_get_text(GTK_ENTRY(cl));
814 }
815 while (true) {
816 string s = histext;
817 string::size_type i;
818 i = s.find(*cl->hist_word);
819 if (i != string::npos && !(begin && i != 0)) {
820 const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
821 if (!(avance && strcmp(tmp, histext) == 0)) {
822 gtk_entry_set_text(GTK_ENTRY(cl),
823 g_locale_to_utf8 (histext, -1, NULL, NULL, NULL));
824 gtk_entry_select_region(GTK_ENTRY(cl),
825 i, i + cl->hist_word->length());
826 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_letter");
827 return 1;
828 }
829 }
830 histext = cl->hist->prev_to_first();
831 if (histext == NULL) {
832 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_not_found");
833 break;
834 }
835 }
836 } else {
837 gtk_entry_select_region(GTK_ENTRY(cl), 0, 0);
838 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_letter");
839 }
840
841 return 0;
842 }
843
844 static int
search_forward_history(GtkCompletionLine * cl,bool avance,bool begin)845 search_forward_history(GtkCompletionLine* cl, bool avance, bool begin)
846 {
847 if (!cl->hist_word->empty()) {
848 const char * histext;
849 if (avance) {
850 histext = cl->hist->next_to_last();
851 if (histext == NULL) {
852 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_not_found");
853 return 0;
854 }
855 } else {
856 histext = gtk_entry_get_text(GTK_ENTRY(cl));
857 }
858 while (true) {
859 string s = histext;
860 string::size_type i;
861 i = s.find(*cl->hist_word);
862 if (i != string::npos && !(begin && i != 0)) {
863 const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
864 if (!(avance && strcmp(tmp, histext) == 0)) {
865 gtk_entry_set_text(GTK_ENTRY(cl),
866 g_locale_to_utf8 (histext, -1, NULL, NULL, NULL));
867 gtk_entry_select_region(GTK_ENTRY(cl),
868 i, i + cl->hist_word->length());
869 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_letter");
870 return 1;
871 }
872 }
873 histext = cl->hist->next_to_last();
874 if (histext == NULL) {
875 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_not_found");
876 break;
877 }
878 }
879 } else {
880 gtk_entry_select_region(GTK_ENTRY(cl), 0, 0);
881 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_letter");
882 }
883
884 return 0;
885 }
886
887 static int
search_history(GtkCompletionLine * cl,bool avance,bool begin)888 search_history(GtkCompletionLine* cl, bool avance, bool begin)
889 {
890 switch (cl->hist_search_mode) {
891 case GCL_SEARCH_REW:
892 case GCL_SEARCH_BEG:
893 return search_back_history(cl, avance, begin);
894
895 case GCL_SEARCH_FWD:
896 return search_forward_history(cl, avance, begin);
897
898 default:
899 return -1;
900 }
901 }
902
903 /*
904 static int
905 inverse_search_history(GtkCompletionLine* cl, bool avance, bool begin)
906 {
907 switch (cl->hist_search_mode) {
908 case GCL_SEARCH_FWD:
909 return search_back_history(cl, avance, begin);
910
911 case GCL_SEARCH_REW:
912 case GCL_SEARCH_BEG:
913 return search_forward_history(cl, avance, begin);
914
915 default:
916 return -1;
917 }
918 }
919 */
920
921 static void
search_off(GtkCompletionLine * cl)922 search_off(GtkCompletionLine* cl)
923 {
924 int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
925 gtk_entry_select_region(GTK_ENTRY(cl), pos, pos);
926 cl->hist_search_mode = GCL_SEARCH_OFF;
927 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_mode");
928 cl->hist->reset_position();
929 }
930
931 #define STOP_PRESS \
932 (gtk_signal_emit_stop_by_name(GTK_OBJECT(cl), "key_press_event"))
933 #define MODE_BEG \
934 (cl->hist_search_mode == GCL_SEARCH_BEG)
935 #define MODE_REW \
936 (cl->hist_search_mode == GCL_SEARCH_REW)
937 #define MODE_FWD \
938 (cl->hist_search_mode == GCL_SEARCH_FWD)
939 #define MODE_SRC \
940 (cl->hist_search_mode != GCL_SEARCH_OFF)
941
tab_pressed(GtkCompletionLine * cl)942 static gint tab_pressed(GtkCompletionLine* cl)
943 {
944 if (MODE_SRC)
945 search_off(cl);
946 complete_line(cl);
947 return FALSE;
948 }
949
950 static void
clear_selection(GtkCompletionLine * cl)951 clear_selection(GtkCompletionLine* cl)
952 {
953 int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
954 gtk_editable_select_region(GTK_EDITABLE(cl), pos, pos);
955 }
956
957 static gboolean
on_key_press(GtkCompletionLine * cl,GdkEventKey * event,gpointer data)958 on_key_press(GtkCompletionLine *cl, GdkEventKey *event, gpointer data)
959 {
960 static gint tt_id = -1;
961
962 switch (event->type) {
963
964 case GDK_KEY_PRESS:
965
966
967 switch (event->keyval) {
968
969 case GDK_Control_R:
970 case GDK_Control_L:
971 case GDK_Shift_R:
972 case GDK_Shift_L:
973 case GDK_Alt_R:
974 case GDK_Alt_L:
975 break;
976
977 case GDK_Tab:
978 if (tt_id != -1) {
979 gtk_timeout_remove(tt_id);
980 tt_id = -1;
981 }
982 tab_pressed(cl);
983 STOP_PRESS;
984 return TRUE;
985
986 case GDK_Up:
987 if (cl->win_compl != NULL) {
988 int &item = cl->list_compl_items_where;
989 item--;
990 if (item < 0) {
991 item = 0;
992 } else {
993 complete_from_list(cl);
994 }
995 } else {
996 up_history(cl);
997 }
998 if (MODE_SRC) {
999 search_off(cl);
1000 }
1001 STOP_PRESS;
1002 return TRUE;
1003
1004 case GDK_space:
1005 {
1006 cl->first_key = 0;
1007 bool search = MODE_SRC;
1008 if (search)
1009 search_off(cl);
1010 if (cl->win_compl != NULL) {
1011 gtk_widget_destroy(cl->win_compl);
1012 cl->win_compl = NULL;
1013 if (!search) {
1014 int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
1015 gtk_entry_select_region(GTK_ENTRY(cl), pos, pos);
1016 }
1017 }
1018 }
1019 return FALSE;
1020
1021 case GDK_Down:
1022 if (cl->win_compl != NULL) {
1023 int &item = cl->list_compl_items_where;
1024 item++;
1025 if (item >= cl->list_compl_nr_rows) {
1026 item = cl->list_compl_nr_rows - 1;
1027 } else {
1028 complete_from_list(cl);
1029 }
1030 } else {
1031 down_history(cl);
1032 }
1033 if (MODE_SRC) {
1034 search_off(cl);
1035 }
1036 STOP_PRESS;
1037 return TRUE;
1038
1039 case GDK_Return:
1040 if (cl->win_compl != NULL) {
1041 gtk_widget_destroy(cl->win_compl);
1042 cl->win_compl = NULL;
1043 }
1044 if (event->state & GDK_CONTROL_MASK) {
1045 gtk_signal_emit_by_name(GTK_OBJECT(cl), "runwithterm");
1046 } else {
1047 gtk_signal_emit_by_name(GTK_OBJECT(cl), "activate");
1048 }
1049 STOP_PRESS;
1050 return TRUE;
1051
1052 case GDK_exclam:
1053 if (!MODE_BEG) {
1054 if (!MODE_SRC)
1055 gtk_editable_delete_selection(GTK_EDITABLE(cl));
1056 const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
1057 if (!(*tmp == '\0' || cl->first_key))
1058 goto ordinary;
1059 cl->hist_search_mode = GCL_SEARCH_BEG;
1060 cl->hist_word->clear();
1061 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_mode");
1062 STOP_PRESS;
1063 return true;
1064 } else goto ordinary;
1065
1066 case GDK_R:
1067 case GDK_r:
1068 if (event->state & GDK_CONTROL_MASK) {
1069 if (MODE_SRC) {
1070 search_back_history(cl, true, MODE_BEG);
1071 } else {
1072 cl->hist_search_mode = GCL_SEARCH_REW;
1073 cl->hist_word->clear();
1074 cl->hist->reset_position();
1075 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_mode");
1076 }
1077 STOP_PRESS;
1078 return TRUE;
1079 } else goto ordinary;
1080
1081 case GDK_S:
1082 case GDK_s:
1083 if (event->state & GDK_CONTROL_MASK) {
1084 if (MODE_SRC) {
1085 search_forward_history(cl, true, MODE_BEG);
1086 } else {
1087 cl->hist_search_mode = GCL_SEARCH_FWD;
1088 cl->hist_word->clear();
1089 cl->hist->reset_position();
1090 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_mode");
1091 }
1092 STOP_PRESS;
1093 return TRUE;
1094 } else goto ordinary;
1095
1096 case GDK_BackSpace:
1097 if (MODE_SRC) {
1098 if (!cl->hist_word->empty()) {
1099 cl->hist_word->erase(cl->hist_word->length() - 1);
1100 gtk_signal_emit_by_name(GTK_OBJECT(cl), "search_letter");
1101 }
1102 STOP_PRESS;
1103 return TRUE;
1104 }
1105 return FALSE;
1106
1107 case GDK_Home:
1108 case GDK_End:
1109 clear_selection(cl);
1110 goto ordinary;
1111
1112 case GDK_Escape:
1113 if (MODE_SRC) {
1114 search_off(cl);
1115 } else if (cl->win_compl != NULL) {
1116 gtk_widget_destroy(cl->win_compl);
1117 cl->win_compl = NULL;
1118 } else {
1119 // user cancelled
1120 gtk_signal_emit_by_name(GTK_OBJECT(cl), "cancel");
1121 }
1122 STOP_PRESS;
1123 return TRUE;
1124
1125 case GDK_G:
1126 case GDK_g:
1127 if (event->state & GDK_CONTROL_MASK) {
1128 search_off(cl);
1129 if (MODE_SRC)
1130 STOP_PRESS;
1131 return TRUE;
1132 } else goto ordinary;
1133
1134 case GDK_E:
1135 case GDK_e:
1136 if (event->state & GDK_CONTROL_MASK) {
1137 search_off(cl);
1138 if (MODE_SRC)
1139 clear_selection(cl);
1140 }
1141 goto ordinary;
1142
1143 ordinary:
1144 default:
1145 cl->first_key = 0;
1146 if (cl->win_compl != NULL) {
1147 gtk_widget_destroy(cl->win_compl);
1148 cl->win_compl = NULL;
1149 }
1150 cl->where = NULL;
1151 if (MODE_SRC) {
1152 if (event->length > 0) {
1153 *cl->hist_word += event->string;
1154 if (search_history(cl, false, MODE_BEG) <= 0)
1155 cl->hist_word->erase(cl->hist_word->length() - 1);
1156 STOP_PRESS;
1157 return TRUE;
1158 } else
1159 search_off(cl);
1160 }
1161 if (cl->tabtimeout != 0) {
1162 if (tt_id != -1) {
1163 gtk_timeout_remove(tt_id);
1164 tt_id = -1;
1165 }
1166 if (::isprint(*event->string))
1167 tt_id = gtk_timeout_add(cl->tabtimeout,
1168 GtkFunction(tab_pressed), cl);
1169 }
1170 break;
1171 }
1172 break;
1173
1174 default:
1175 break;
1176 }
1177 return FALSE;
1178 }
1179
1180 #undef STOP_PRESS
1181 #undef MODE_BEG
1182 #undef MODE_REW
1183 #undef MODE_FWD
1184 #undef MODE_SRC
1185
1186 // Local Variables: ***
1187 // mode: c++ ***
1188 // c-basic-offset: 2 ***
1189 // End: ***
1190