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