view src/tabcomp.c @ 8:e0d0593d519e

Sync to GQview 1.5.9 release. ######## DO NOT BASE ENHANCEMENTS OR TRANSLATION UPDATES ON CODE IN THIS CVS! This CVS is never up to date with current development and is provided solely for reference purposes, please use the latest official release package when making any changes or translation updates. ########
author gqview
date Sat, 26 Feb 2005 00:07:07 +0000
parents c0e337a01cb7
children
line wrap: on
line source

/*
 * GQview image viewer
 * (C)2000 John Ellis
 *
 * Author: John Ellis
 *
 */

#include "gqview.h"
#include <gdk/gdkkeysyms.h> /* for key values */

#include "tabcomp.xpm"

/* ----------------------------------------------------------------
   Tab completion routines, can be connected to any gtkentry widget
   using the tab_completion_add_to_entry() function.
   Use remove_trailing_slash() to strip the trailing '/'.
   ----------------------------------------------------------------*/

typedef struct _TabCompData TabCompData;
struct _TabCompData
{
	GtkWidget *entry;
	gchar *dir_path;
	GList *file_list;
	void (*enter_func)(gchar *, gpointer);
	void (*tab_func)(gchar *, gpointer);
	gpointer enter_data;
	gpointer tab_data;

	GtkWidget *combo;
	gint has_history;
	gchar *history_key;
	gint history_levels;
};

typedef struct _HistoryData HistoryData;
struct _HistoryData
{
	gchar *key;
	GList *list;
};

static GList *history_list = NULL;

static void tab_completion_free_list(TabCompData *td);
static void tab_completion_read_dir(TabCompData *td, gchar *path);
static void tab_completion_destroy(GtkWidget *widget, gpointer data);
static void tab_completion_emit_enter_signal(TabCompData *td);
static void tab_completion_emit_tab_signal(TabCompData *td);
static gint tab_completion_do(TabCompData *td);
static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data);
static void tab_completion_button_pressed(GtkWidget *widget, gpointer data);
static GtkWidget *tab_completion_create_complete_button(GtkWidget *window, GtkWidget *entry);

static void history_list_free(HistoryData *hd);
static HistoryData *history_list_find_by_key(const gchar* key);
static gchar *history_list_find_last_path_by_key(const gchar* key);
static void history_list_free_key(const gchar *key);
static void history_list_add_to_key(const gchar *key, const gchar *path, gint max);

static void history_list_free(HistoryData *hd)
{
	GList *work;

	if (!hd) return;

	work = hd->list;
	while(work)
		{
		g_free(work->data);
		work = work->next;
		}

	g_free(hd->key);
	g_free(hd);
}

static HistoryData *history_list_find_by_key(const gchar* key)
{
	GList *work = history_list;
	while(work)
		{
		HistoryData *hd = work->data;
		if (strcmp(hd->key, key) == 0) return hd;
		work = work->next;
		}
	return NULL;
}

static gchar *history_list_find_last_path_by_key(const gchar* key)
{
	HistoryData *hd;
	hd = history_list_find_by_key(key);
	if (!hd || !hd->list) return NULL;

	return hd->list->data;
}

static void history_list_free_key(const gchar *key)
{
	HistoryData *hd;
	hd = history_list_find_by_key(key);
	if (!hd) return;

	history_list = g_list_remove(history_list, hd);
	history_list_free(hd);
}

static void history_list_add_to_key(const gchar *key, const gchar *path, gint max)
{
	HistoryData *hd;
	GList *work;

	hd = history_list_find_by_key(key);
	if (!hd)
		{
		hd = g_new(HistoryData, 1);
		hd->key = g_strdup(key);
		hd->list = NULL;
		history_list = g_list_prepend(history_list, hd);
		}

	/* if already in the list, simply move it to the top */
	work = hd->list;
	while(work)
		{
		gchar *buf = work->data;
		work = work->next;
		if (strcmp(buf, path) == 0)
			{
			hd->list = g_list_remove(hd->list, buf);
			hd->list = g_list_prepend(hd->list, buf);
			return;
			}
		}

	hd->list = g_list_prepend(hd->list, g_strdup(path));

	if (max > 0)
		{
		while(hd->list && g_list_length(hd->list) > max)
			{
			GList *work = g_list_last(hd->list);
			gchar *buf = work->data;
			hd->list = g_list_remove(hd->list, buf);
			g_free(buf);
			}
		}
}

static void tab_completion_free_list(TabCompData *td)
{
	GList *list;

	g_free(td->dir_path);
	td->dir_path = NULL;

	list = td->file_list;

	while(list)
		{
		g_free(list->data);
		list = list->next;
		}

	g_list_free(td->file_list);
	td->file_list = NULL;
}

static void tab_completion_read_dir(TabCompData *td, gchar *path)
{
        DIR *dp;
        struct dirent *dir;
        GList *list = NULL;

	tab_completion_free_list(td);

        if((dp = opendir(path))==NULL)
                {
                /* dir not found */
                return;
                }
        while ((dir = readdir(dp)) != NULL)
                {
                /* skips removed files */
                if (dir->d_ino > 0)
                        {
			gchar *name = dir->d_name;
			if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
				{
				list = g_list_prepend(list, g_strdup(name));
				}
                        }
		}
        closedir(dp);

	td->dir_path = g_strdup(path);
	td->file_list = list;
}

static void tab_completion_destroy(GtkWidget *widget, gpointer data)
{
	TabCompData *td = data;
	tab_completion_free_list(td);
	g_free(td->history_key);
	g_free(td);
}

static void tab_completion_emit_enter_signal(TabCompData *td)
{
	gchar *text;
	if (!td->enter_func) return;

	text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));

	if (text[0] == '~')
		{
		gchar *t = text;
		text = g_strconcat(homedir(), t + 1, NULL);
		g_free(t);
		}

	td->enter_func(text, td->enter_data);
	g_free(text);
}

static void tab_completion_emit_tab_signal(TabCompData *td)
{
	gchar *text;
	if (!td->tab_func) return;

	text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));

	if (text[0] == '~')
		{
		gchar *t = text;
		text = g_strconcat(homedir(), t + 1, NULL);
		g_free(t);
		}

	td->tab_func(text, td->tab_data);
	g_free(text);
}

static gint tab_completion_do(TabCompData *td)
{
	gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry));
	gchar *entry_file;
	gchar *entry_dir;
	gchar *ptr;
	gint home_exp = FALSE;

	/* home dir expansion */
	if (entry_text[0] == '~')
		{
		entry_dir = g_strconcat(homedir(), entry_text + 1, NULL);
		home_exp = TRUE;
		}
	else
		{
		entry_dir = g_strdup(entry_text);
		}

	entry_file = filename_from_path(entry_text);

	if (isfile(entry_dir))
		{
		if (home_exp)
			{
			gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
			gtk_entry_set_position(GTK_ENTRY(td->entry), strlen(entry_dir));
			}
		g_free(entry_dir);
		return home_exp;
		}
	if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
		{
		ptr = entry_dir + strlen(entry_dir) - 1;
		if (ptr[0] == '/')
			{
			if (home_exp)
				{
				gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
				gtk_entry_set_position(GTK_ENTRY(td->entry), strlen(entry_dir));
				}
			g_free(entry_dir);
			return home_exp;
			}
		else
			{
			gchar *buf = g_strconcat(entry_dir, "/", NULL);
			gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
			gtk_entry_set_position(GTK_ENTRY(td->entry), strlen(buf));
			g_free(buf);
			g_free(entry_dir);
			return TRUE;
			}
		}

	ptr = filename_from_path(entry_dir);
	if (ptr > entry_dir) ptr--;
	ptr[0] = '\0';

	if (strlen(entry_dir) == 0)
		{
		g_free(entry_dir);
		entry_dir = g_strdup("/");
		}

	if (isdir(entry_dir))
		{
		GList *list;
		GList *poss = NULL;
		gint l = strlen(entry_file);

		if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0)
			{
			tab_completion_read_dir(td, entry_dir);
			}

		if (strcmp(entry_dir, "/") == 0) entry_dir[0] = '\0';

		list = td->file_list;
		while(list)
			{
			gchar *file = list->data;
			if (strncmp(entry_file, file, l) == 0)
				{
				poss = g_list_prepend(poss, file);
				}
			list = list->next;
			}

		if (poss)
			{
			if (!poss->next)
				{
				gchar *file = poss->data;
				gchar *buf;

				buf = g_strconcat(entry_dir, "/", file, NULL);

				if (isdir(buf))
					{
					g_free(buf);
					buf = g_strconcat(entry_dir, "/", file, "/", NULL);
					}
				gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
				gtk_entry_set_position(GTK_ENTRY(td->entry), strlen(buf));
				g_free(buf);
				g_list_free(poss);
				g_free(entry_dir);
				return TRUE;
				}
			else
				{
				gint c = strlen(entry_file);
				gint done = FALSE;
				gchar *test_file = poss->data;

				while (!done)
					{
					list = poss;
					if (!list) done = TRUE;
					while(list && !done)
						{
						gchar *file = list->data;
						if (strlen(file) < c || strncmp(test_file, file, c) != 0)
							{
							done = TRUE;
							}
						list = list->next;
						}
					c++;
					}
				c -= 2;
				if (c > 0)
					{
					gchar *file;
					gchar *buf;
					file = g_strdup(test_file);
					file[c] = '\0';
					buf = g_strconcat(entry_dir, "/", file, NULL);
					gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
					gtk_entry_set_position(GTK_ENTRY(td->entry), strlen(buf));
					g_free(file);
					g_free(buf);
					g_list_free(poss);
					g_free(entry_dir);
					return TRUE;
					}
				}
			g_list_free(poss);
			}
		}

	g_free(entry_dir);

	return FALSE;
}

static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	TabCompData *td = data;
	gint stop_signal = FALSE;

	switch (event->keyval)
		{
                case GDK_Tab:
			if (tab_completion_do(td))
				{
				tab_completion_emit_tab_signal(td);
				}
			stop_signal = TRUE;
			break;
		case GDK_Return:
			tab_completion_emit_enter_signal(td);
			stop_signal = TRUE;
			break;
		default:
			break;
		}

	if (stop_signal)
		{
		if (stop_signal) gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
		return TRUE;
		}

	return FALSE;
}

static void tab_completion_button_pressed(GtkWidget *widget, gpointer data)
{
	TabCompData *td;
	GtkWidget *entry = data;

	td = gtk_object_get_data(GTK_OBJECT(entry), "tab_completion_data");

	if (!td) return;

	if (!GTK_WIDGET_HAS_FOCUS(entry))
		{
		gtk_widget_grab_focus(entry);
		}

	if (tab_completion_do(td))
		{
		tab_completion_emit_tab_signal(td);
		}
}

static GtkWidget *tab_completion_create_complete_button(GtkWidget *window, GtkWidget *entry)
{
	GtkWidget *button;
	GtkWidget *icon;
	GdkPixmap *pixmap = NULL;
	GdkBitmap *mask = NULL;
	GtkStyle *style;

	style = gtk_widget_get_style(window);
	pixmap = gdk_pixmap_create_from_xpm_d(window->window, &mask,
		&style->bg[GTK_STATE_NORMAL], (gchar **)tabcomp_xpm);

	button = gtk_button_new();
	GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
	gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc) tab_completion_button_pressed, entry);
	
	icon = gtk_pixmap_new(pixmap, mask);
	gtk_container_add(GTK_CONTAINER(button), icon);
	gtk_widget_show(icon);

	return button;
}

/*
 *----------------------------------------------------------------------------
 * public interface
 *----------------------------------------------------------------------------
 */

GtkWidget *tab_completion_new_with_history(GtkWidget **entry, GtkWidget *window, gchar *text,
					   const gchar *history_key, gint max_levels,
					   void (*enter_func)(gchar *, gpointer), gpointer data)
{
	GtkWidget *combo;
	GtkWidget *button;
	HistoryData *hd;
	TabCompData *td;

	combo = gtk_combo_new();
	gtk_combo_set_use_arrows(GTK_COMBO(combo), FALSE);

	button = tab_completion_create_complete_button(window, GTK_COMBO(combo)->entry);
	gtk_box_pack_start(GTK_BOX(combo), button, FALSE, FALSE, 0);
	gtk_box_reorder_child(GTK_BOX(combo), button, 1);
	gtk_widget_show(button);
	
	tab_completion_add_to_entry(GTK_COMBO(combo)->entry, enter_func, data);

	td = gtk_object_get_data(GTK_OBJECT(GTK_COMBO(combo)->entry), "tab_completion_data");
	if (!td) return; /* this should never happen! */

	td->combo = combo;
	td->has_history = TRUE;
	td->history_key = g_strdup(history_key);
	td->history_levels = max_levels;

	hd = history_list_find_by_key(td->history_key);
	if (hd && hd->list)
		{
		gtk_combo_set_popdown_strings(GTK_COMBO(combo), hd->list);
		}

	if (text) gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(combo)->entry), text);

	if (entry) *entry = GTK_COMBO(combo)->entry;
	return combo;
}

gchar *tab_completion_set_to_last_history(GtkWidget *entry)
{
	TabCompData *td = gtk_object_get_data(GTK_OBJECT(entry), "tab_completion_data");
	gchar *buf;

	if (!td || !td->has_history) return NULL;

	buf = history_list_find_last_path_by_key(td->history_key);
	if (buf)
		{
		gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
		}

	return buf;
}

void tab_completion_append_to_history(GtkWidget *entry, gchar *path)
{
	TabCompData *td = gtk_object_get_data(GTK_OBJECT(entry), "tab_completion_data");
	HistoryData *hd;

	if (!td || !td->has_history) return;

	history_list_add_to_key(td->history_key, path, td->history_levels);
	hd = history_list_find_by_key(td->history_key);
	if (hd && hd->list)
		{
		gtk_combo_set_popdown_strings(GTK_COMBO(td->combo), hd->list);
		}
}

GtkWidget *tab_completion_new(GtkWidget **entry, GtkWidget *window, gchar *text,
			      void (*enter_func)(gchar *, gpointer), gpointer data)
{
	GtkWidget *hbox;
	GtkWidget *button;
	GtkWidget *newentry;

	hbox = gtk_hbox_new(FALSE, 0);

	newentry = gtk_entry_new();
	if (text) gtk_entry_set_text(GTK_ENTRY(newentry), text);
	gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0);
	gtk_widget_show(newentry);

	button = tab_completion_create_complete_button(window, newentry);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);

	tab_completion_add_to_entry(newentry, enter_func, data);

	if (entry) *entry = newentry;
	return hbox;
}

void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(gchar *, gpointer), gpointer data)
{
	TabCompData *td;
	if (!entry)
		{
		printf("Tab completion error: entry != NULL\n");
		return;
		}

	td = g_new0(TabCompData, 1);
	td->entry = entry;
	td->dir_path = NULL;
	td->file_list = NULL;
	td->enter_func = enter_func;
	td->enter_data = data;
	td->tab_func = NULL;
	td->tab_data = NULL;

	td->has_history = FALSE;
	td->history_key = NULL;
	td->history_levels = 0;

	gtk_object_set_data(GTK_OBJECT(td->entry), "tab_completion_data", td);

	gtk_signal_connect(GTK_OBJECT(entry), "key_press_event",
			(GtkSignalFunc) tab_completion_key_pressed, td);
	gtk_signal_connect(GTK_OBJECT(entry), "destroy",
			(GtkSignalFunc) tab_completion_destroy, td);
}

void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(gchar *, gpointer), gpointer data)
{
	TabCompData *td = gtk_object_get_data(GTK_OBJECT(entry), "tab_completion_data");

	if (!td) return;

	td->tab_func = tab_func;
	td->tab_data = data;
}

gchar *remove_trailing_slash(gchar *path)
{
	gchar *ret;
	gint l;
	if (!path) return NULL;

	ret = g_strdup(path);
	l = strlen(ret);
	if (l > 1 && ret[l - 1] == '/') ret[l - 1] = '\0';

	return ret;
}