1161 lines
29 KiB
C++
1161 lines
29 KiB
C++
/*****************************************************************************
|
|
* $Id: gtkcompletionline.cc,v 1.33 2003/11/16 10:55:07 andreas99 Exp $
|
|
* Copyright (C) 2000, Mishoo
|
|
* Author: Mihai Bazon Email: mishoo@fenrir.infoiasi.ro
|
|
*
|
|
* Distributed under the terms of the GNU General Public License. You are
|
|
* free to use/modify/distribute this program as long as you comply to the
|
|
* terms of the GNU General Public License, version 2 or above, at your
|
|
* option, and provided that this copyright notice remains intact.
|
|
*****************************************************************************/
|
|
|
|
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <iostream>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
using namespace std;
|
|
|
|
#include "gtkcompletionline.h"
|
|
|
|
static int on_row_selected_handler = 0;
|
|
static int on_key_press_handler = 0;
|
|
|
|
/* GLOBALS */
|
|
|
|
/* signals */
|
|
enum {
|
|
UNIQUE,
|
|
NOTUNIQUE,
|
|
INCOMPLETE,
|
|
RUNWITHTERM,
|
|
SEARCH_MODE,
|
|
SEARCH_LETTER,
|
|
SEARCH_NOT_FOUND,
|
|
EXT_HANDLER,
|
|
CANCEL,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
#define GEN_COMPLETION_OK 1
|
|
#define GEN_CANT_COMPLETE 2
|
|
#define GEN_NOT_UNIQUE 3
|
|
|
|
static guint gtk_completion_line_signals[LAST_SIGNAL];
|
|
|
|
typedef set<string> StrSet;
|
|
typedef vector<string> StrList;
|
|
|
|
static StrSet path;
|
|
static StrSet execs;
|
|
static StrSet dirlist;
|
|
static string prefix;
|
|
static int g_show_dot_files;
|
|
|
|
/* callbacks */
|
|
static void gtk_completion_line_class_init(GtkCompletionLineClass *klass);
|
|
static void gtk_completion_line_init(GtkCompletionLine *object);
|
|
|
|
static gboolean
|
|
on_key_press(GtkCompletionLine *cl, GdkEventKey *event, gpointer data);
|
|
|
|
/* get_type */
|
|
GType gtk_completion_line_get_type(void)
|
|
{
|
|
static GType type = 0;
|
|
if (type == 0)
|
|
{
|
|
static const GTypeInfo type_info =
|
|
{
|
|
sizeof(GtkCompletionLineClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc)gtk_completion_line_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof(GtkCompletionLine),
|
|
0,
|
|
(GInstanceInitFunc)gtk_completion_line_init,
|
|
NULL
|
|
};
|
|
type = g_type_register_static(GTK_TYPE_ENTRY, "GtkCompletionLine",
|
|
&type_info, (GTypeFlags)0);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
/* class_init */
|
|
static void
|
|
gtk_completion_line_class_init(GtkCompletionLineClass *klass)
|
|
{
|
|
GtkWidgetClass *object_class;
|
|
|
|
object_class = (GtkWidgetClass*)klass;
|
|
|
|
gtk_completion_line_signals[UNIQUE] =
|
|
g_signal_new("unique",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, unique),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[NOTUNIQUE] =
|
|
g_signal_new("notunique",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, notunique),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[INCOMPLETE] =
|
|
g_signal_new("incomplete",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, incomplete),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[RUNWITHTERM] =
|
|
g_signal_new("runwithterm",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, runwithterm),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[SEARCH_MODE] =
|
|
g_signal_new("search_mode",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, search_mode),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[SEARCH_NOT_FOUND] =
|
|
g_signal_new("search_not_found",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, search_not_found),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[SEARCH_LETTER] =
|
|
g_signal_new("search_letter",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, search_letter),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_completion_line_signals[EXT_HANDLER] =
|
|
g_signal_new("ext_handler",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, ext_handler),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER);
|
|
|
|
gtk_completion_line_signals[CANCEL] =
|
|
g_signal_new("cancel",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET(GtkCompletionLineClass, ext_handler),
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__POINTER,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER);
|
|
|
|
//GTK_WIDGET_class_add_signals(object_class,
|
|
// gtk_completion_line_signals, LAST_SIGNAL);
|
|
|
|
klass->unique = NULL;
|
|
klass->notunique = NULL;
|
|
klass->incomplete = NULL;
|
|
klass->runwithterm = NULL;
|
|
klass->search_mode = NULL;
|
|
klass->search_letter = NULL;
|
|
klass->search_not_found = NULL;
|
|
klass->ext_handler = NULL;
|
|
klass->cancel = NULL;
|
|
}
|
|
|
|
/* init */
|
|
static void
|
|
gtk_completion_line_init(GtkCompletionLine *object)
|
|
{
|
|
/* Add object initialization / creation stuff here */
|
|
|
|
object->where = NULL;
|
|
object->cmpl = NULL;
|
|
object->win_compl = NULL;
|
|
object->list_compl = NULL;
|
|
object->hist_search_mode = GCL_SEARCH_OFF;
|
|
object->hist_word = new string;
|
|
object->tabtimeout = 0;
|
|
object->show_dot_files = 0;
|
|
|
|
on_key_press_handler =
|
|
g_signal_connect(GTK_WIDGET(object), "key_press_event",
|
|
G_CALLBACK(on_key_press), NULL);
|
|
g_signal_connect(GTK_WIDGET(object), "key_release_event",
|
|
G_CALLBACK(on_key_press), NULL);
|
|
|
|
object->hist = new HistoryFile();
|
|
|
|
object->first_key = 1;
|
|
}
|
|
|
|
void gtk_completion_line_last_history_item(GtkCompletionLine* object) {
|
|
const char *last_item = object->hist->last_item();
|
|
if (last_item) {
|
|
object->hist->set_default("");
|
|
const char* txt = object->hist->prev();
|
|
gtk_entry_set_text(GTK_ENTRY(object),
|
|
g_locale_to_utf8 (txt, -1, NULL, NULL, NULL));
|
|
}
|
|
}
|
|
|
|
string quote_string(const string& str)
|
|
{
|
|
string res;
|
|
const char* i = str.c_str();
|
|
while (*i) {
|
|
char c = *i++;
|
|
switch (c) {
|
|
case ' ':
|
|
res += '\\';
|
|
default:
|
|
res += c;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
get_token(istream& is, string& s)
|
|
{
|
|
s.clear();
|
|
bool escaped = false;
|
|
while (!is.eof()) {
|
|
char c = is.get();
|
|
if (is.eof())
|
|
break;
|
|
if (escaped) {
|
|
s += c;
|
|
escaped = false;
|
|
} else if (c == '\\') {
|
|
// s += c;
|
|
escaped = true;
|
|
} else if (::isspace(c)) {
|
|
while (::isspace(c) && !is.eof()) c = is.get();
|
|
if (!is.eof())
|
|
is.unget();
|
|
break;
|
|
} else {
|
|
s += c;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
get_words(GtkCompletionLine *object, vector<string>& words)
|
|
{
|
|
string content(gtk_entry_get_text(GTK_ENTRY(object)));
|
|
int pos_in_text = gtk_editable_get_position(GTK_EDITABLE(object));
|
|
int pos = 0;
|
|
{
|
|
string::iterator i = content.begin() + pos_in_text;
|
|
if (i != content.end())
|
|
content.insert(i, ' ');
|
|
}
|
|
istringstream ss(content);
|
|
|
|
while (!ss.eof()) {
|
|
string s;
|
|
// ss >> s;
|
|
get_token(ss, s);
|
|
words.push_back(s);
|
|
if (ss.eof()) break;
|
|
if (ss.tellg() < pos_in_text && ss.tellg() >= 0)
|
|
++pos;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
int
|
|
set_words(GtkCompletionLine *object, const vector<string>& words, int pos = -1)
|
|
{
|
|
ostringstream ss;
|
|
if (pos == -1)
|
|
pos = words.size() - 1;
|
|
int where = 0;
|
|
|
|
vector<string>::const_iterator
|
|
i = words.begin(),
|
|
i_end = words.end();
|
|
|
|
while (i != i_end) {
|
|
ss << quote_string(*i++);
|
|
if (i != i_end)
|
|
ss << ' ';
|
|
if (!pos && !where)
|
|
where = ss.tellp();
|
|
else
|
|
--pos;
|
|
}
|
|
ss << ends;
|
|
|
|
if (words.size() == 1) {
|
|
const string& s = words.back();
|
|
size_t pos = s.rfind('.');
|
|
if (pos != string::npos)
|
|
g_signal_emit_by_name(
|
|
GTK_WIDGET(object), "ext_handler", s.substr(pos + 1).c_str());
|
|
else
|
|
g_signal_emit_by_name(GTK_WIDGET(object), "ext_handler", NULL);
|
|
}
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(object),
|
|
g_locale_to_utf8 (ss.str().c_str(), -1, NULL, NULL, NULL));
|
|
gtk_editable_set_position(GTK_EDITABLE(object), where);
|
|
return where;
|
|
}
|
|
|
|
static void
|
|
generate_path()
|
|
{
|
|
char *path_cstr = (char*)getenv("PATH");
|
|
|
|
istringstream path_ss(path_cstr);
|
|
string tmp;
|
|
|
|
path.clear();
|
|
while (!path_ss.eof()) {
|
|
tmp = "";
|
|
do {
|
|
char c;
|
|
c = path_ss.get();
|
|
if (c == ':' || path_ss.eof()) break;
|
|
else tmp += c;
|
|
} while (true);
|
|
if (tmp.length() != 0)
|
|
path.insert(tmp);
|
|
}
|
|
}
|
|
|
|
static int
|
|
select_executables_only(const struct dirent* dent)
|
|
{
|
|
int len = strlen(dent->d_name);
|
|
int lenp = prefix.length();
|
|
|
|
if (dent->d_name[0] == '.') {
|
|
if (!g_show_dot_files)
|
|
return 0;
|
|
if (dent->d_name[1] == '\0')
|
|
return 0;
|
|
if ((dent->d_name[1] == '.') && (dent->d_name[2] == '\0'))
|
|
return 0;
|
|
}
|
|
if (dent->d_name[len - 1] == '~')
|
|
return 0;
|
|
if (lenp == 0)
|
|
return 1;
|
|
if (lenp > len)
|
|
return 0;
|
|
|
|
if (strncmp(dent->d_name, prefix.c_str(), lenp) == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int my_alphasort(const struct dirent **a, const struct dirent **b) {
|
|
const char* s1 = (*a)->d_name;
|
|
const char* s2 = (*b)->d_name;
|
|
|
|
int l1 = strlen(s1);
|
|
int l2 = strlen(s2);
|
|
int result = strcmp(s1, s2);
|
|
|
|
if (result == 0) return 0;
|
|
|
|
if (l1 < l2) {
|
|
int res2 = strncmp(s1, s2, l1);
|
|
if (res2 == 0) return -1;
|
|
} else {
|
|
int res2 = strncmp(s1, s2, l2);
|
|
if (res2 == 0) return 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
generate_execs()
|
|
{
|
|
execs.clear();
|
|
|
|
for (StrSet::iterator i = path.begin(); i != path.end(); i++) {
|
|
struct dirent **eps;
|
|
int n = scandir(i->c_str(), &eps, select_executables_only, my_alphasort);
|
|
if (n >= 0) {
|
|
for (int j = 0; j < n; j++) {
|
|
execs.insert(eps[j]->d_name);
|
|
free(eps[j]);
|
|
}
|
|
free(eps);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
generate_completion_from_execs(GtkCompletionLine *object)
|
|
{
|
|
g_list_foreach(object->cmpl, (GFunc)g_string_free, NULL);
|
|
g_list_free(object->cmpl);
|
|
object->cmpl = NULL;
|
|
|
|
for (StrSet::const_iterator i = execs.begin(); i != execs.end(); i++) {
|
|
GString *the_fucking_gstring = g_string_new(i->c_str());
|
|
object->cmpl = g_list_append(object->cmpl, the_fucking_gstring);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static string
|
|
get_common_part(const char *p1, const char *p2)
|
|
{
|
|
string ret;
|
|
|
|
while (*p1 == *p2 && *p1 != '\0' && *p2 != '\0') {
|
|
ret += *p1;
|
|
p1++;
|
|
p2++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
complete_common(GtkCompletionLine *object)
|
|
{
|
|
GList *l;
|
|
GList *ls = object->cmpl;
|
|
vector<string> words;
|
|
int pos = get_words(object, words);
|
|
words[pos] = ((GString*)ls->data)->str;
|
|
|
|
ls = g_list_next(ls);
|
|
while (ls != NULL) {
|
|
words[pos] = get_common_part(words[pos].c_str(),
|
|
((GString*)ls->data)->str);
|
|
ls = g_list_next(ls);
|
|
}
|
|
|
|
set_words(object, words, pos);
|
|
|
|
ls = object->cmpl;
|
|
l = ls;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
generate_dirlist(const char *what)
|
|
{
|
|
char *str = strdup(what);
|
|
char *p = str + 1;
|
|
char *filename = str;
|
|
string dest("/");
|
|
int n;
|
|
|
|
while (*p != '\0') {
|
|
dest += *p;
|
|
if (*p == '/') {
|
|
DIR* dir = opendir(dest.c_str());
|
|
if (!dir)
|
|
goto dirty;
|
|
closedir(dir);
|
|
filename = p;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
*filename = '\0';
|
|
filename++;
|
|
dest = str;
|
|
dest += '/';
|
|
|
|
dirlist.clear();
|
|
struct dirent **eps;
|
|
prefix = filename;
|
|
n = scandir(dest.c_str(), &eps, select_executables_only, my_alphasort);
|
|
if (n >= 0) {
|
|
for (int j = 0; j < n; j++) {
|
|
{
|
|
string foo(dest);
|
|
foo += eps[j]->d_name;
|
|
struct stat filestatus;
|
|
stat(foo.c_str(), &filestatus);
|
|
if (S_ISDIR(filestatus.st_mode)) foo += '/';
|
|
dirlist.insert(foo);
|
|
}
|
|
free(eps[j]);
|
|
}
|
|
free(eps);
|
|
}
|
|
|
|
free(str);
|
|
return GEN_COMPLETION_OK;
|
|
|
|
dirty:
|
|
free(str);
|
|
return GEN_CANT_COMPLETE;
|
|
}
|
|
|
|
static int
|
|
generate_completion_from_dirlist(GtkCompletionLine *object)
|
|
{
|
|
g_list_foreach(object->cmpl, (GFunc)g_string_free, NULL);
|
|
g_list_free(object->cmpl);
|
|
object->cmpl = NULL;
|
|
|
|
for (StrSet::const_iterator i = dirlist.begin(); i != dirlist.end(); i++) {
|
|
GString *the_fucking_gstring = g_string_new(i->c_str());
|
|
object->cmpl = g_list_append(object->cmpl, the_fucking_gstring);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
parse_tilda(GtkCompletionLine *object)
|
|
{
|
|
string text = gtk_entry_get_text(GTK_ENTRY(object));
|
|
gint where = (gint)text.find("~");
|
|
if (where != string::npos) {
|
|
if ((where > 0) && (text[where - 1] != ' '))
|
|
return 0;
|
|
if (where < text.size() - 1 && text[where + 1] != '/') {
|
|
// FIXME: Parse another user's home
|
|
} else {
|
|
string home = g_get_home_dir();
|
|
size_t i = home.length() - 1;
|
|
while ((i >= 0) && (home[i] == '/'))
|
|
home.erase(i--);
|
|
gtk_editable_insert_text(GTK_EDITABLE(object), home.c_str(), home.length(), &where);
|
|
gtk_editable_delete_text(GTK_EDITABLE(object), where, where + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
complete_from_list(GtkCompletionLine *object)
|
|
{
|
|
parse_tilda(object);
|
|
vector<string> words;
|
|
int pos = get_words(object, words);
|
|
|
|
prefix = words[pos];
|
|
|
|
if (object->win_compl != NULL) {
|
|
object->where = (GList*)gtk_clist_get_row_data(
|
|
GTK_CLIST(object->list_compl), object->list_compl_items_where);
|
|
words[pos] = ((GString*)object->where->data)->str;
|
|
int current_pos = set_words(object, words, pos);
|
|
int &item = object->list_compl_items_where;
|
|
gtk_clist_select_row(GTK_CLIST(object->list_compl), item, 0);
|
|
gtk_clist_moveto(GTK_CLIST(object->list_compl), item, 0, 0.5, 0.0);
|
|
} else {
|
|
words[pos] = ((GString*)object->where->data)->str;
|
|
object->pos_in_text = gtk_editable_get_position(GTK_EDITABLE(object));
|
|
int current_pos = set_words(object, words, pos);
|
|
object->where = g_list_next(object->where);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_row_selected(GtkWidget *ls, gint row, gint col, GdkEvent *ev, gpointer data)
|
|
{
|
|
GtkCompletionLine *cl = GTK_COMPLETION_LINE(data);
|
|
|
|
cl->list_compl_items_where = row;
|
|
g_signal_handler_block(GTK_WIDGET(cl->list_compl),
|
|
on_row_selected_handler);
|
|
complete_from_list(cl);
|
|
g_signal_handler_block(GTK_WIDGET(cl->list_compl),
|
|
on_row_selected_handler);
|
|
}
|
|
|
|
static void
|
|
get_prefix(GtkCompletionLine *object)
|
|
{
|
|
parse_tilda(object);
|
|
vector<string> words;
|
|
int pos = get_words(object, words);
|
|
prefix = words[pos];
|
|
}
|
|
|
|
static int
|
|
complete_line(GtkCompletionLine *object)
|
|
{
|
|
parse_tilda(object);
|
|
vector<string> words;
|
|
int pos = get_words(object, words);
|
|
prefix = words[pos];
|
|
|
|
g_show_dot_files = object->show_dot_files;
|
|
if (prefix[0] != '/') {
|
|
if (object->where == NULL) {
|
|
generate_path();
|
|
generate_execs();
|
|
generate_completion_from_execs(object);
|
|
object->where = NULL;
|
|
}
|
|
} else if (object->where == NULL) {
|
|
generate_dirlist(prefix.c_str());
|
|
generate_completion_from_dirlist(object);
|
|
object->where = NULL;
|
|
}
|
|
|
|
if (object->cmpl != NULL) {
|
|
complete_common(object);
|
|
object->where = object->cmpl;
|
|
}
|
|
|
|
// FUCK C! C++ Rules!
|
|
if (object->where != NULL) {
|
|
if (object->win_compl != NULL) {
|
|
int &item = object->list_compl_items_where;
|
|
++item;
|
|
if (item >= object->list_compl_nr_rows)
|
|
item = object->list_compl_nr_rows - 1;
|
|
}
|
|
complete_from_list(object);
|
|
} else if (object->cmpl != NULL) {
|
|
complete_common(object);
|
|
object->where = object->cmpl;
|
|
}
|
|
|
|
GList *ls = object->cmpl;
|
|
|
|
if (g_list_length(ls) == 1) {
|
|
g_signal_emit_by_name(GTK_WIDGET(object), "unique");
|
|
return GEN_COMPLETION_OK;
|
|
} else if (g_list_length(ls) == 0 || ls == NULL) {
|
|
g_signal_emit_by_name(GTK_WIDGET(object), "incomplete");
|
|
return GEN_CANT_COMPLETE;
|
|
} else if (g_list_length(ls) > 1) {
|
|
g_signal_emit_by_name(GTK_WIDGET(object), "notunique");
|
|
|
|
vector<string> words;
|
|
int pos = get_words(object, words);
|
|
|
|
if (words[pos] == ((GString*)ls->data)->str) {
|
|
|
|
if (object->win_compl == NULL) {
|
|
object->win_compl = gtk_window_new(GTK_WINDOW_POPUP);
|
|
gtk_widget_set_name(object->win_compl, "Msh_Run_Window");
|
|
|
|
/*gtk_window_set_position(GTK_WINDOW(object->win_compl),
|
|
GTK_WIN_POS_MOUSE);*/
|
|
|
|
gtk_window_set_policy(GTK_WINDOW(object->win_compl),
|
|
FALSE, FALSE, TRUE);
|
|
|
|
object->list_compl = gtk_clist_new(1);
|
|
|
|
on_row_selected_handler =
|
|
g_signal_connect(GTK_WIDGET(object->list_compl), "select_row",
|
|
G_CALLBACK(on_row_selected), object);
|
|
|
|
g_signal_handler_block(GTK_WIDGET(object->list_compl),
|
|
on_row_selected_handler);
|
|
|
|
GList *p = ls;
|
|
object->list_compl_nr_rows = 0;
|
|
while (p) {
|
|
char *tmp[2];
|
|
tmp[0] = ((GString*)p->data)->str;
|
|
tmp[1] = NULL;
|
|
int row = gtk_clist_append(GTK_CLIST(object->list_compl), tmp);
|
|
gtk_clist_set_row_data(GTK_CLIST(object->list_compl), row, p);
|
|
object->list_compl_nr_rows++;
|
|
p = g_list_next(p);
|
|
}
|
|
|
|
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_OUT);
|
|
gtk_widget_show(scroll);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scroll),
|
|
GTK_POLICY_NEVER,
|
|
GTK_POLICY_AUTOMATIC);
|
|
|
|
gtk_container_set_border_width(GTK_CONTAINER(object->list_compl), 2);
|
|
gtk_container_add(GTK_CONTAINER (scroll), object->list_compl);
|
|
|
|
object->list_compl_items_where = 0;
|
|
|
|
gtk_widget_show(object->list_compl);
|
|
int w = gtk_clist_optimal_column_width(GTK_CLIST(object->list_compl), 0);
|
|
gtk_widget_set_usize(scroll, w + 40, 150);
|
|
|
|
gtk_container_add(GTK_CONTAINER(object->win_compl), scroll);
|
|
|
|
GdkWindow *top = gtk_widget_get_parent_window(GTK_WIDGET(object));
|
|
int x, y;
|
|
gdk_window_get_position(top, &x, &y);
|
|
x += GTK_WIDGET(object)->allocation.x;
|
|
y += GTK_WIDGET(object)->allocation.y +
|
|
GTK_WIDGET(object)->allocation.height;
|
|
|
|
// gtk_widget_popup(object->win_compl, x, y);
|
|
gtk_window_move(GTK_WINDOW(object->win_compl), x, y);
|
|
gtk_widget_show(object->win_compl);
|
|
|
|
gtk_clist_select_row(GTK_CLIST(object->list_compl),
|
|
object->list_compl_items_where, 0);
|
|
|
|
g_signal_handler_block(GTK_WIDGET(object->list_compl),
|
|
on_row_selected_handler);
|
|
}
|
|
|
|
return GEN_COMPLETION_OK;
|
|
}
|
|
return GEN_NOT_UNIQUE;
|
|
}
|
|
|
|
return GEN_COMPLETION_OK;
|
|
}
|
|
|
|
GtkWidget *
|
|
gtk_completion_line_new()
|
|
{
|
|
return GTK_WIDGET(g_object_new(gtk_completion_line_get_type(), NULL));
|
|
}
|
|
|
|
static void
|
|
up_history(GtkCompletionLine* cl)
|
|
{
|
|
cl->hist->set_default(gtk_entry_get_text(GTK_ENTRY(cl)));
|
|
gtk_entry_set_text(GTK_ENTRY(cl),
|
|
g_locale_to_utf8 (cl->hist->prev(), -1, NULL, NULL, NULL));
|
|
}
|
|
|
|
static void
|
|
down_history(GtkCompletionLine* cl)
|
|
{
|
|
cl->hist->set_default(gtk_entry_get_text(GTK_ENTRY(cl)));
|
|
gtk_entry_set_text(GTK_ENTRY(cl),
|
|
g_locale_to_utf8 (cl->hist->next(), -1, NULL, NULL, NULL));
|
|
}
|
|
|
|
static int
|
|
search_back_history(GtkCompletionLine* cl, bool avance, bool begin)
|
|
{
|
|
if (!cl->hist_word->empty()) {
|
|
const char * histext;
|
|
if (avance) {
|
|
histext = cl->hist->prev_to_first();
|
|
if (histext == NULL) {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_not_found");
|
|
return 0;
|
|
}
|
|
} else {
|
|
histext = gtk_entry_get_text(GTK_ENTRY(cl));
|
|
}
|
|
while (true) {
|
|
string s = histext;
|
|
string::size_type i;
|
|
i = s.find(*cl->hist_word);
|
|
if (i != string::npos && !(begin && i != 0)) {
|
|
const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
|
|
if (!(avance && strcmp(tmp, histext) == 0)) {
|
|
gtk_entry_set_text(GTK_ENTRY(cl),
|
|
g_locale_to_utf8 (histext, -1, NULL, NULL, NULL));
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_letter");
|
|
return 1;
|
|
}
|
|
}
|
|
histext = cl->hist->prev_to_first();
|
|
if (histext == NULL) {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_not_found");
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_letter");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
search_forward_history(GtkCompletionLine* cl, bool avance, bool begin)
|
|
{
|
|
if (!cl->hist_word->empty()) {
|
|
const char * histext;
|
|
if (avance) {
|
|
histext = cl->hist->next_to_last();
|
|
if (histext == NULL) {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_not_found");
|
|
return 0;
|
|
}
|
|
} else {
|
|
histext = gtk_entry_get_text(GTK_ENTRY(cl));
|
|
}
|
|
while (true) {
|
|
string s = histext;
|
|
string::size_type i;
|
|
i = s.find(*cl->hist_word);
|
|
if (i != string::npos && !(begin && i != 0)) {
|
|
const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
|
|
if (!(avance && strcmp(tmp, histext) == 0)) {
|
|
gtk_entry_set_text(GTK_ENTRY(cl),
|
|
g_locale_to_utf8 (histext, -1, NULL, NULL, NULL));
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_letter");
|
|
return 1;
|
|
}
|
|
}
|
|
histext = cl->hist->next_to_last();
|
|
if (histext == NULL) {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_not_found");
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_letter");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
search_history(GtkCompletionLine* cl, bool avance, bool begin)
|
|
{
|
|
switch (cl->hist_search_mode) {
|
|
case GCL_SEARCH_REW:
|
|
case GCL_SEARCH_BEG:
|
|
return search_back_history(cl, avance, begin);
|
|
|
|
case GCL_SEARCH_FWD:
|
|
return search_forward_history(cl, avance, begin);
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
search_off(GtkCompletionLine* cl)
|
|
{
|
|
int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
|
|
cl->hist_search_mode = GCL_SEARCH_OFF;
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_mode");
|
|
cl->hist->reset_position();
|
|
}
|
|
|
|
#define STOP_PRESS \
|
|
(g_signal_stop_emission_by_name(GTK_WIDGET(cl), "key_press_event"))
|
|
#define MODE_BEG \
|
|
(cl->hist_search_mode == GCL_SEARCH_BEG)
|
|
#define MODE_REW \
|
|
(cl->hist_search_mode == GCL_SEARCH_REW)
|
|
#define MODE_FWD \
|
|
(cl->hist_search_mode == GCL_SEARCH_FWD)
|
|
#define MODE_SRC \
|
|
(cl->hist_search_mode != GCL_SEARCH_OFF)
|
|
|
|
static gint tab_pressed(GtkCompletionLine* cl)
|
|
{
|
|
if (MODE_SRC)
|
|
search_off(cl);
|
|
complete_line(cl);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
clear_selection(GtkCompletionLine* cl)
|
|
{
|
|
int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
|
|
gtk_editable_select_region(GTK_EDITABLE(cl), pos, pos);
|
|
}
|
|
|
|
static gboolean
|
|
on_key_press(GtkCompletionLine *cl, GdkEventKey *event, gpointer data)
|
|
{
|
|
static gint tt_id = -1;
|
|
|
|
switch (event->type) {
|
|
|
|
case GDK_KEY_PRESS:
|
|
|
|
|
|
switch (event->keyval) {
|
|
|
|
case GDK_KEY_Control_R:
|
|
case GDK_KEY_Control_L:
|
|
case GDK_KEY_Shift_R:
|
|
case GDK_KEY_Shift_L:
|
|
case GDK_KEY_Alt_R:
|
|
case GDK_KEY_Alt_L:
|
|
break;
|
|
|
|
case GDK_KEY_Tab:
|
|
if (tt_id != -1) {
|
|
gtk_timeout_remove(tt_id);
|
|
tt_id = -1;
|
|
}
|
|
tab_pressed(cl);
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
|
|
case GDK_KEY_Up:
|
|
if (cl->win_compl != NULL) {
|
|
int &item = cl->list_compl_items_where;
|
|
item--;
|
|
if (item < 0) {
|
|
item = 0;
|
|
} else {
|
|
complete_from_list(cl);
|
|
}
|
|
} else {
|
|
up_history(cl);
|
|
}
|
|
if (MODE_SRC) {
|
|
search_off(cl);
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
|
|
case GDK_KEY_space:
|
|
{
|
|
cl->first_key = 0;
|
|
bool search = MODE_SRC;
|
|
if (search)
|
|
search_off(cl);
|
|
if (cl->win_compl != NULL) {
|
|
gtk_widget_destroy(cl->win_compl);
|
|
cl->win_compl = NULL;
|
|
if (!search) {
|
|
int pos = gtk_editable_get_position(GTK_EDITABLE(cl));
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
|
|
case GDK_KEY_Down:
|
|
if (cl->win_compl != NULL) {
|
|
int &item = cl->list_compl_items_where;
|
|
item++;
|
|
if (item >= cl->list_compl_nr_rows) {
|
|
item = cl->list_compl_nr_rows - 1;
|
|
} else {
|
|
complete_from_list(cl);
|
|
}
|
|
} else {
|
|
down_history(cl);
|
|
}
|
|
if (MODE_SRC) {
|
|
search_off(cl);
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
|
|
case GDK_KEY_Return:
|
|
if (cl->win_compl != NULL) {
|
|
gtk_widget_destroy(cl->win_compl);
|
|
cl->win_compl = NULL;
|
|
}
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "runwithterm");
|
|
} else {
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "activate");
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
|
|
case GDK_KEY_exclam:
|
|
if (!MODE_BEG) {
|
|
if (!MODE_SRC)
|
|
gtk_editable_delete_selection(GTK_EDITABLE(cl));
|
|
const char *tmp = gtk_entry_get_text(GTK_ENTRY(cl));
|
|
if (!(*tmp == '\0' || cl->first_key))
|
|
goto ordinary;
|
|
cl->hist_search_mode = GCL_SEARCH_BEG;
|
|
cl->hist_word->clear();
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_mode");
|
|
STOP_PRESS;
|
|
return true;
|
|
} else goto ordinary;
|
|
|
|
case GDK_KEY_R:
|
|
case GDK_KEY_r:
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
if (MODE_SRC) {
|
|
search_back_history(cl, true, MODE_BEG);
|
|
} else {
|
|
cl->hist_search_mode = GCL_SEARCH_REW;
|
|
cl->hist_word->clear();
|
|
cl->hist->reset_position();
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_mode");
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
} else goto ordinary;
|
|
|
|
case GDK_KEY_S:
|
|
case GDK_KEY_s:
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
if (MODE_SRC) {
|
|
search_forward_history(cl, true, MODE_BEG);
|
|
} else {
|
|
cl->hist_search_mode = GCL_SEARCH_FWD;
|
|
cl->hist_word->clear();
|
|
cl->hist->reset_position();
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_mode");
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
} else goto ordinary;
|
|
|
|
case GDK_KEY_BackSpace:
|
|
if (MODE_SRC) {
|
|
if (!cl->hist_word->empty()) {
|
|
cl->hist_word->erase(cl->hist_word->length() - 1);
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "search_letter");
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
|
|
case GDK_KEY_Home:
|
|
case GDK_KEY_End:
|
|
clear_selection(cl);
|
|
goto ordinary;
|
|
|
|
case GDK_KEY_Escape:
|
|
if (MODE_SRC) {
|
|
search_off(cl);
|
|
} else if (cl->win_compl != NULL) {
|
|
gtk_widget_destroy(cl->win_compl);
|
|
cl->win_compl = NULL;
|
|
} else {
|
|
// user cancelled
|
|
g_signal_emit_by_name(GTK_WIDGET(cl), "cancel");
|
|
}
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
|
|
case GDK_KEY_G:
|
|
case GDK_KEY_g:
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
search_off(cl);
|
|
if (MODE_SRC)
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
} else goto ordinary;
|
|
|
|
case GDK_KEY_E:
|
|
case GDK_KEY_e:
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
search_off(cl);
|
|
if (MODE_SRC)
|
|
clear_selection(cl);
|
|
}
|
|
goto ordinary;
|
|
|
|
ordinary:
|
|
default:
|
|
cl->first_key = 0;
|
|
if (cl->win_compl != NULL) {
|
|
gtk_widget_destroy(cl->win_compl);
|
|
cl->win_compl = NULL;
|
|
}
|
|
cl->where = NULL;
|
|
if (MODE_SRC) {
|
|
if (event->length > 0) {
|
|
*cl->hist_word += event->string;
|
|
if (search_history(cl, false, MODE_BEG) <= 0)
|
|
cl->hist_word->erase(cl->hist_word->length() - 1);
|
|
STOP_PRESS;
|
|
return TRUE;
|
|
} else
|
|
search_off(cl);
|
|
}
|
|
if (cl->tabtimeout != 0) {
|
|
if (tt_id != -1) {
|
|
gtk_timeout_remove(tt_id);
|
|
tt_id = -1;
|
|
}
|
|
if (::isprint(*event->string))
|
|
tt_id = gtk_timeout_add(cl->tabtimeout,
|
|
GtkFunction(tab_pressed), cl);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#undef STOP_PRESS
|
|
#undef MODE_BEG
|
|
#undef MODE_REW
|
|
#undef MODE_FWD
|
|
#undef MODE_SRC
|
|
|
|
// Local Variables: ***
|
|
// mode: c++ ***
|
|
// c-basic-offset: 2 ***
|
|
// End: ***
|