view console/libgnt/gntmain.c @ 14833:2b369cd04576

[gaim-migrate @ 17600] I think "not loadable" is a vastly superior way to report plugins that cannot be loaded. "unloadable" could easily be misinterpreted to mean it was loaded and can be now removed (we actually use this meaning too!). committer: Tailor Script <tailor@pidgin.im>
author Stu Tomlinson <stu@nosnilmot.com>
date Sat, 28 Oct 2006 14:24:10 +0000
parents 3d1be4577fa9
children 70623f0d5cdc
line wrap: on
line source

#define _GNU_SOURCE
#if defined(__APPLE__)
#define _XOPEN_SOURCE_EXTENDED
#endif

#include "config.h"

#include <gmodule.h>

#include "gnt.h"
#include "gntbox.h"
#include "gntcolors.h"
#include "gntkeys.h"
#include "gntmenu.h"
#include "gntstyle.h"
#include "gnttree.h"
#include "gntutils.h"
#include "gntwm.h"

#include <panel.h>

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/wait.h>

/**
 * Notes: Interesting functions to look at:
 * 	scr_dump, scr_init, scr_restore: for workspaces
 *
 * 	Need to wattrset for colors to use with PDCurses.
 */

/**
 * There can be at most one menu at a time on the screen.
 * If there is a menu being displayed, then all the keystrokes will be sent to
 * the menu until it is closed, either when the user activates a menuitem, or
 * presses Escape to cancel the menu.
 */
static GntMenu *menu;

static int lock_focus_list;
static GList *focus_list;
static GList *ordered;

static int X_MIN;
static int X_MAX;
static int Y_MIN;
static int Y_MAX;

static gboolean ascii_only;
static gboolean mouse_enabled;

/**
 * 'event_stack' will be set to TRUE when a user-event, ie. a mouse-click
 * or a key-press is being processed. This variable will be used to determine
 * whether to give focus to a new window.
 */
static gboolean event_stack;

static GMainLoop *loop;

static struct
{
	GntWidget *window;
	GntWidget *tree;
} _list, *window_list, *action_list;

typedef struct
{
	GntWidget *me;

	PANEL *panel;
} GntNode;

typedef enum
{
	GNT_KP_MODE_NORMAL,
	GNT_KP_MODE_RESIZE,
	GNT_KP_MODE_MOVE,
	GNT_KP_MODE_MENU,
	GNT_KP_MODE_WINDOW_LIST
} GntKeyPressMode;

static GHashTable *nodes;

static void free_node(gpointer data);
static void draw_taskbar(gboolean reposition);
static void bring_on_top(GntWidget *widget);

static gboolean refresh_screen();
static const GList *list_all_windows();

static void show_actions_list();

static GntWM wm = 
{
	NULL,   /* new_window */
	NULL,   /* close_window */
	NULL,   /* window_resize_confirm */
	NULL,   /* window_resized */
	NULL,   /* window_move_confirm */
	NULL,   /* window_moved */
	NULL,   /* window_update */
	NULL,   /* key_pressed */
	NULL,   /* mouse clicked */
	bring_on_top, /* give_focus */
	NULL,   /* uninit */
	list_all_windows,  /* window_list */
};

static const GList *list_all_windows()
{
	return focus_list;
}

static GList *
g_list_bring_to_front(GList *list, gpointer data)
{
	list = g_list_remove(list, data);
	list = g_list_prepend(list, data);
	return list;
}

static gboolean
update_screen(gpointer null)
{
	if (menu) {
		GntMenu *top = menu;
		while (top) {
			GntNode *node = g_hash_table_lookup(nodes, top);
			if (node)
				top_panel(node->panel);
			top = top->submenu;
		}
	}
	update_panels();
	doupdate();
	return TRUE;
}

void gnt_screen_take_focus(GntWidget *widget)
{
	GntWidget *w = NULL;

	if (lock_focus_list)
		return;
	if (g_list_find(focus_list, widget))
		return;

	if (ordered)
		w = ordered->data;

	focus_list = g_list_append(focus_list, widget);

	if (event_stack) {
		ordered = g_list_prepend(ordered, widget);
		g_object_set_data(G_OBJECT(widget), "give_focus", GINT_TO_POINTER(event_stack));
	} else
		ordered = g_list_append(ordered, widget);

	gnt_widget_set_focus(widget, TRUE);
	if (w)
		gnt_widget_set_focus(w, FALSE);
	draw_taskbar(FALSE);
}

void gnt_screen_remove_widget(GntWidget *widget)
{
	int pos = g_list_index(focus_list, widget);

	if (lock_focus_list)
		return;

	if (pos == -1)
		return;

	focus_list = g_list_remove(focus_list, widget);
	ordered = g_list_remove(ordered, widget);

	if (ordered)
	{
		wm.give_focus(ordered->data);
	}
	draw_taskbar(FALSE);
}

static void
bring_on_top(GntWidget *widget)
{
	GntNode *node = g_hash_table_lookup(nodes, widget);

	if (!node)
		return;
	
	if (ordered->data != widget) {
		GntWidget *w = ordered->data;
		ordered = g_list_bring_to_front(ordered, widget);
		gnt_widget_set_focus(w, FALSE);
	}

	gnt_widget_set_focus(widget, TRUE);
	gnt_widget_draw(widget);
	top_panel(node->panel);

	if (_list.window)
	{
		GntNode *nd = g_hash_table_lookup(nodes, _list.window);
		top_panel(nd->panel);
	}
	update_screen(NULL);
	draw_taskbar(FALSE);
}

static void
update_window_in_list(GntWidget *wid)
{
	GntTextFormatFlags flag = 0;

	if (window_list == NULL)
		return;

	if (wid == ordered->data)
		flag |= GNT_TEXT_FLAG_DIM;
	else if (GNT_WIDGET_IS_FLAG_SET(wid, GNT_WIDGET_URGENT))
		flag |= GNT_TEXT_FLAG_BOLD;

	gnt_tree_set_row_flags(GNT_TREE(window_list->tree), wid, flag);
}

static void
draw_taskbar(gboolean reposition)
{
	static WINDOW *taskbar = NULL;
	GList *iter;
	int n, width = 0;
	int i;

	if (taskbar == NULL)
	{
		taskbar = newwin(1, getmaxx(stdscr), getmaxy(stdscr) - 1, 0);
	}
	else if (reposition)
	{
		mvwin(taskbar, Y_MAX, 0);
	}

	wbkgdset(taskbar, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
	werase(taskbar);

	n = g_list_length(focus_list);
	if (n)
		width = getmaxx(stdscr) / n;

	for (i = 0, iter = focus_list; iter; iter = iter->next, i++)
	{
		GntWidget *w = iter->data;
		int color;
		const char *title;

		if (w == ordered->data) {
			/* This is the current window in focus */
			color = GNT_COLOR_TITLE;
			GNT_WIDGET_UNSET_FLAGS(w, GNT_WIDGET_URGENT);
			if (wm.window_update) {
				GntNode *node = g_hash_table_lookup(nodes, w);
				wm.window_update(node ? node->panel : NULL, w);
			}
		} else if (GNT_WIDGET_IS_FLAG_SET(w, GNT_WIDGET_URGENT)) {
			/* This is a window with the URGENT hint set */
			color = GNT_COLOR_URGENT;
		} else {
			color = GNT_COLOR_NORMAL;
		}
		wbkgdset(taskbar, '\0' | COLOR_PAIR(color));
		mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), width);
		title = GNT_BOX(w)->title;
		mvwprintw(taskbar, 0, width * i, "%s", title ? title : "<gnt>");
		if (i)
			mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | COLOR_PAIR(GNT_COLOR_NORMAL));

		update_window_in_list(w);
	}

	wrefresh(taskbar);
}

static void
switch_window(int direction)
{
	GntWidget *w = NULL, *wid = NULL;
	int pos;

	if (!ordered || !ordered->next)
		return;

	w = ordered->data;
	pos = g_list_index(focus_list, w);
	pos += direction;

	if (pos < 0)
		wid = g_list_last(focus_list)->data;
	else if (pos >= g_list_length(focus_list))
		wid = focus_list->data;
	else if (pos >= 0)
		wid = g_list_nth_data(focus_list, pos);

	ordered = g_list_bring_to_front(ordered, wid);

	wm.give_focus(ordered->data);

	if (w != wid)
	{
		gnt_widget_set_focus(w, FALSE);
	}
}

static void
switch_window_n(int n)
{
	GntWidget *w = NULL;
	GList *l;

	if (!ordered)
		return;
	
	w = ordered->data;

	if ((l = g_list_nth(focus_list, n)) != NULL)
	{
		ordered = g_list_bring_to_front(ordered, l->data);
		wm.give_focus(ordered->data);
	}

	if (l && w != l->data)
	{
		gnt_widget_set_focus(w, FALSE);
	}
}

static void
window_list_activate(GntTree *tree, gpointer null)
{
	GntWidget *widget = gnt_tree_get_selection_data(GNT_TREE(tree));
	GntWidget *old = NULL;

	if (!ordered || !widget)
		return;

	old = ordered->data;
	ordered = g_list_bring_to_front(ordered, widget);
	wm.give_focus(widget);

	if (old != widget)
	{
		gnt_widget_set_focus(old, FALSE);
	}
}

static void
setup__list()
{
	GntWidget *tree, *win;
	win = _list.window = gnt_box_new(FALSE, FALSE);
	gnt_box_set_toplevel(GNT_BOX(win), TRUE);
	gnt_box_set_pad(GNT_BOX(win), 0);

	tree = _list.tree = gnt_tree_new();
	gnt_box_add_widget(GNT_BOX(win), tree);
}

static void
show_window_list()
{
	GntWidget *tree, *win;
	GList *iter;

	if (window_list)
		return;
	
	setup__list();

	window_list = &_list;

	win = window_list->window;
	tree = window_list->tree;

	gnt_box_set_title(GNT_BOX(win), "Window List");

	for (iter = focus_list; iter; iter = iter->next)
	{
		GntBox *box = GNT_BOX(iter->data);

		gnt_tree_add_row_last(GNT_TREE(tree), box,
				gnt_tree_create_row(GNT_TREE(tree), box->title), NULL);
		update_window_in_list(GNT_WIDGET(box));
	}

	gnt_tree_set_selected(GNT_TREE(tree), ordered->data);
	g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(window_list_activate), NULL);

	gnt_tree_set_col_width(GNT_TREE(tree), 0, getmaxx(stdscr) / 3);
	gnt_widget_set_size(tree, 0, getmaxy(stdscr) / 2);
	gnt_widget_set_position(win, getmaxx(stdscr) / 3, getmaxy(stdscr) / 4);

	lock_focus_list = 1;
	gnt_widget_show(win);
	lock_focus_list = 0;
}

static void
shift_window(GntWidget *widget, int dir)
{
	GList *all = focus_list;
	GList *list = g_list_find(all, widget);
	int length, pos;
	if (!list)
		return;

	length = g_list_length(all);
	pos = g_list_position(all, list);

	pos += dir;
	if (dir > 0)
		pos++;

	if (pos < 0)
		pos = length;
	else if (pos > length)
		pos = 0;

	all = g_list_insert(all, widget, pos);
	all = g_list_delete_link(all, list);
	focus_list = all;
	draw_taskbar(FALSE);
}

static void
dump_screen()
{
	int x, y;
	chtype old = 0, now = 0;
	FILE *file = fopen("dump.html", "w");

	fprintf(file, "<pre>");
	for (y = 0; y < getmaxy(stdscr); y++)
	{
		for (x = 0; x < getmaxx(stdscr); x++)
		{
			char ch;
			now = mvwinch(curscr, y, x);
			ch = now & A_CHARTEXT;
			now ^= ch;

#define CHECK(attr, start, end) \
			do \
			{  \
				if (now & attr)  \
				{  \
					if (!(old & attr))  \
						fprintf(file, start);  \
				}  \
				else if (old & attr)  \
				{  \
					fprintf(file, end);  \
				}  \
			} while (0) 

			CHECK(A_BOLD, "<b>", "</b>");
			CHECK(A_UNDERLINE, "<u>", "</u>");
			CHECK(A_BLINK, "<blink>", "</blink>");

			if ((now & A_COLOR) != (old & A_COLOR) ||
				(now & A_REVERSE) != (old & A_REVERSE))
			{
				int ret;
				short fgp, bgp, r, g, b;
				struct
				{
					int r, g, b;
				} fg, bg;

				ret = pair_content(PAIR_NUMBER(now & A_COLOR), &fgp, &bgp);
				if (fgp == -1)
					fgp = COLOR_BLACK;
				if (bgp == -1)
					bgp = COLOR_WHITE;
				if (now & A_REVERSE)
					fgp ^= bgp ^= fgp ^= bgp;  /* *wink* */
				ret = color_content(fgp, &r, &g, &b);
				fg.r = r; fg.b = b; fg.g = g;
				ret = color_content(bgp, &r, &g, &b);
				bg.r = r; bg.b = b; bg.g = g;
#define ADJUST(x) (x = x * 255 / 1000)
				ADJUST(fg.r);
				ADJUST(fg.g);
				ADJUST(fg.b);
				ADJUST(bg.r);
				ADJUST(bg.b);
				ADJUST(bg.g);
				
				if (x) fprintf(file, "</span>");
				fprintf(file, "<span style=\"background:#%02x%02x%02x;color:#%02x%02x%02x\">",
						bg.r, bg.g, bg.b, fg.r, fg.g, fg.b);
			}
			if (now & A_ALTCHARSET)
			{
				switch (ch)
				{
					case 'q':
						ch = '-'; break;
					case 't':
					case 'u':
					case 'x':
						ch = '|'; break;
					case 'v':
					case 'w':
					case 'l':
					case 'm':
					case 'k':
					case 'j':
					case 'n':
						ch = '+'; break;
					case '-':
						ch = '^'; break;
					case '.':
						ch = 'v'; break;
					case 'a':
						ch = '#'; break;
					default:
						ch = ' '; break;
				}
			}
			if (ch == '&')
				fprintf(file, "&amp;");
			else if (ch == '<')
				fprintf(file, "&lt;");
			else if (ch == '>')
				fprintf(file, "&gt;");
			else
				fprintf(file, "%c", ch);
			old = now;
		}
		fprintf(file, "</span>\n");
		old = 0;
	}
	fprintf(file, "</pre>");
	fclose(file);
}

static void
refresh_node(GntWidget *widget, GntNode *node, gpointer null)
{
	int x, y, w, h;
	int nw, nh;

	gnt_widget_get_position(widget, &x, &y);
	gnt_widget_get_size(widget, &w, &h);

	if (x + w >= X_MAX)
		x = MAX(0, X_MAX - w);
	if (y + h >= Y_MAX)
		y = MAX(0, Y_MAX - h);
	gnt_screen_move_widget(widget, x, y);

	nw = MIN(w, X_MAX);
	nh = MIN(h, Y_MAX);
	if (nw != w || nh != h)
		gnt_screen_resize_widget(widget, nw, nh);
}

/**
 * Mouse support:
 *  - bring a window on top if you click on its taskbar
 *  - click on the top-bar of the active window and drag+drop to move a window
 *  - click on a window to bring it to focus
 *   - allow scrolling in tree/textview on wheel-scroll event
 *   - click to activate button or select a row in tree
 *  wishlist:
 *   - have a little [X] on the windows, and clicking it will close that window.
 */
static gboolean
detect_mouse_action(const char *buffer)
{
	int x, y;
	static enum {
		MOUSE_NONE,
		MOUSE_LEFT,
		MOUSE_RIGHT,
		MOUSE_MIDDLE
	} button = MOUSE_NONE;
	static GntWidget *remember = NULL;
	static int offset = 0;
	GntMouseEvent event;
	GntWidget *widget = NULL;
	PANEL *p = NULL;

	if (!ordered || buffer[0] != 27)
		return FALSE;
	
	buffer++;
	if (strlen(buffer) < 5)
		return FALSE;

	x = buffer[3];
	y = buffer[4];
	if (x < 0)	x += 256;
	if (y < 0)	y += 256;
	x -= 33;
	y -= 33;

	while ((p = panel_below(p)) != NULL) {
		const GntNode *node = panel_userptr(p);
		GntWidget *wid;
		if (!node)
			continue;
		wid = node->me;
		if (x >= wid->priv.x && x < wid->priv.x + wid->priv.width) {
			if (y >= wid->priv.y && y < wid->priv.y + wid->priv.height) {
				widget = wid;
				break;
			}
		}
	}

	if (strncmp(buffer, "[M ", 3) == 0) {
		/* left button down */
		/* Bring the window you clicked on to front */
		/* If you click on the topbar, then you can drag to move the window */
		event = GNT_LEFT_MOUSE_DOWN;
	} else if (strncmp(buffer, "[M\"", 3) == 0) {
		/* right button down */
		event = GNT_RIGHT_MOUSE_DOWN;
	} else if (strncmp(buffer, "[M!", 3) == 0) {
		/* middle button down */
		event = GNT_MIDDLE_MOUSE_DOWN;
	} else if (strncmp(buffer, "[M`", 3) == 0) {
		/* wheel up*/
		event = GNT_MOUSE_SCROLL_UP;
	} else if (strncmp(buffer, "[Ma", 3) == 0) {
		/* wheel down */
		event = GNT_MOUSE_SCROLL_DOWN;
	} else if (strncmp(buffer, "[M#", 3) == 0) {
		/* button up */
		event = GNT_MOUSE_UP;
	} else
		return FALSE;
	
	if (wm.mouse_clicked && wm.mouse_clicked(event, x, y, widget))
		return TRUE;
	
	if (event == GNT_LEFT_MOUSE_DOWN && widget && widget != _list.window &&
			!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) {
		if (widget != ordered->data) {
			GntWidget *w = ordered->data;
			ordered = g_list_bring_to_front(ordered, widget);
			wm.give_focus(ordered->data);
			gnt_widget_set_focus(w, FALSE);
		}
		if (y == widget->priv.y) {
			offset = x - widget->priv.x;
			remember = widget;
			button = MOUSE_LEFT;
		}
	} else if (event == GNT_MOUSE_UP) {
		if (button == MOUSE_NONE && y == getmaxy(stdscr) - 1) {
			/* Clicked on the taskbar */
			int n = g_list_length(focus_list);
			if (n) {
				int width = getmaxx(stdscr) / n;
				switch_window_n(x / width);
			}
		} else if (button == MOUSE_LEFT && remember) {
			x -= offset;
			if (x < 0)	x = 0;
			if (y < 0)	y = 0;
			gnt_screen_move_widget(remember, x, y);
			refresh_node(remember, NULL, NULL);
		}
		button = MOUSE_NONE;
		remember = NULL;
		offset = 0;
	}

	gnt_widget_clicked(widget, event, x, y);
	return TRUE; /* XXX: this should be TRUE */
}

#ifndef NO_WIDECHAR
static int
widestringwidth(wchar_t *wide)
{
	int len, ret;
	char *string;

	len = wcstombs(NULL, wide, 0) + 1;
	string = g_new0(char, len);
	wcstombs(string, wide, len);
	ret = gnt_util_onscreen_width(string, NULL);
	g_free(string);
	return ret;
}
#endif

/* Returns the onscreen width of the character at the position */
static int
reverse_char(WINDOW *d, int y, int x, gboolean set)
{
#define DECIDE(ch) (set ? ((ch) | A_REVERSE) : ((ch) & ~A_REVERSE))

#ifdef NO_WIDECHAR
	chtype ch;
	ch = mvwinch(d, y, x);
	mvwaddch(d, y, x, DECIDE(ch));
	return 1;
#else
	cchar_t ch;
	int wc = 1;
	if (mvwin_wch(d, y, x, &ch) == OK) {
		wc = widestringwidth(ch.chars);
		ch.attr = DECIDE(ch.attr);
		ch.attr &= WA_ATTRIBUTES;   /* XXX: This is a workaround for a bug */
		mvwadd_wch(d, y, x, &ch);
	}

	return wc;
#endif
}

static void
window_reverse(GntWidget *win, gboolean set)
{
	int i;
	int w, h;
	WINDOW *d;

	if (GNT_WIDGET_IS_FLAG_SET(win, GNT_WIDGET_NO_BORDER))
		return;
	
	d = win->window;
	gnt_widget_get_size(win, &w, &h);

	if (gnt_widget_has_shadow(win)) {
		--w;
		--h;
	}

	/* the top and bottom */
	for (i = 0; i < w; i += reverse_char(d, 0, i, set));
	for (i = 0; i < w; i += reverse_char(d, h-1, i, set));

	/* the left and right */
	for (i = 0; i < h; i += reverse_char(d, i, 0, set));
	for (i = 0; i < h; i += reverse_char(d, i, w-1, set));

	wrefresh(win->window);
}

static gboolean
io_invoke(GIOChannel *source, GIOCondition cond, gpointer null)
{
	char keys[256];
	gboolean ret = FALSE;
	static GntKeyPressMode mode = GNT_KP_MODE_NORMAL;
	const char *buffer;

	int rd = read(STDIN_FILENO, keys, sizeof(keys) - 1);
	if (rd < 0)
	{
		endwin();
		printf("ERROR: %s\n", strerror(errno));
		printf("File descriptor is: %d\n\nGIOChannel is: %p", STDIN_FILENO, source);
		exit(1);
	}
	else if (rd == 0)
	{
		endwin();
		printf("EOF\n");
		exit(1);
	}

	event_stack = TRUE;
	keys[rd] = 0;

	if (keys[0] == 27 && keys[1] == 'd' && keys[2] == 0)
	{
		/* This dumps the screen contents in an html file */
		dump_screen();
	}

	gnt_keys_refine(keys);

	if (mouse_enabled && detect_mouse_action(keys))
		return TRUE;
	
	if (wm.key_pressed) {
		buffer = wm.key_pressed(keys);
		if (buffer == NULL) {
			event_stack = FALSE;
			return TRUE;
		}
	} else
		buffer = keys;

	if (mode == GNT_KP_MODE_NORMAL)
	{
		if (menu) {
			ret = gnt_widget_key_pressed(GNT_WIDGET(menu), buffer);
		} else if (ordered) {
			ret = gnt_widget_key_pressed(ordered->data, buffer);
		}

		if (!ret)
		{
			if (buffer[0] == 27)
			{
				/* Some special key has been pressed */
				if (strcmp(buffer+1, GNT_KEY_POPUP) == 0)
				{}
				else if (strcmp(buffer + 1, "c") == 0)
				{
					/* Alt + c was pressed. I am going to use it to close a window. */
					if (ordered)
					{
						gnt_widget_destroy(ordered->data);
					}
				}
				else if (strcmp(buffer + 1, "q") == 0)
				{
					/* I am going to use Alt + q to quit. */
					g_main_loop_quit(loop);
				}
				else if (strcmp(buffer + 1, "n") == 0)
				{
					/* Alt + n to go to the next window */
					switch_window(1);
				}
				else if (strcmp(buffer + 1, "p") == 0)
				{
					/* Alt + p to go to the previous window */
					switch_window(-1);
				}
				else if (strcmp(buffer + 1, "m") == 0 && focus_list)
				{
					/* Move a window */
					mode = GNT_KP_MODE_MOVE;
					window_reverse(ordered->data, TRUE);
				}
				else if (strcmp(buffer + 1, "w") == 0 && focus_list)
				{
					/* Window list */
					mode = GNT_KP_MODE_WINDOW_LIST;
					show_window_list();
				}
				else if (strcmp(buffer + 1, "a") == 0)
				{
					mode = GNT_KP_MODE_WINDOW_LIST;
					show_actions_list();
				}
				else if (strcmp(buffer + 1, "r") == 0 && focus_list)
				{
					/* Resize window */
					mode = GNT_KP_MODE_RESIZE;
					window_reverse(ordered->data, TRUE);
				}
				else if (strcmp(buffer + 1, ",") == 0 && focus_list)
				{
					/* Re-order the list of windows */
					shift_window(ordered->data, -1);
				}
				else if (strcmp(buffer + 1, ".") == 0 && focus_list)
				{
					shift_window(ordered->data, 1);
				}
				else if (strcmp(buffer + 1, "l") == 0)
				{
					refresh_screen();
				}
				else if (strlen(buffer) == 2 && isdigit(*(buffer + 1)))
				{
					int n = *(buffer + 1) - '0';

					if (n == 0)
						n = 10;

					switch_window_n(n - 1);
				}
			}
		}
	}
	else if (mode == GNT_KP_MODE_MOVE && focus_list)
	{
		if (buffer[0] == 27)
		{
			gboolean changed = FALSE;
			int x, y, w, h;
			GntWidget *widget = GNT_WIDGET(ordered->data);

			gnt_widget_get_position(widget, &x, &y);
			gnt_widget_get_size(widget, &w, &h);

			if (strcmp(buffer + 1, GNT_KEY_LEFT) == 0)
			{
				if (x > X_MIN)
				{
					x--;
					changed = TRUE;
				}
			}
			else if (strcmp(buffer + 1, GNT_KEY_RIGHT) == 0)
			{
				if (x + w < X_MAX)
				{
					x++;
					changed = TRUE;
				}
			}
			else if (strcmp(buffer + 1, GNT_KEY_UP) == 0)
			{
				if (y > Y_MIN)
				{
					y--;
					changed = TRUE;
				}						
			}
			else if (strcmp(buffer + 1, GNT_KEY_DOWN) == 0)
			{
				if (y + h < Y_MAX)
				{
					y++;
					changed = TRUE;
				}
			}
			else if (buffer[1] == 0)
			{
				mode = GNT_KP_MODE_NORMAL;
				window_reverse(widget, FALSE);
			}

			if (changed)
			{
				gnt_screen_move_widget(widget, x, y);
			}
		}
		else if (*buffer == '\r')
		{
			mode = GNT_KP_MODE_NORMAL;
			window_reverse(ordered->data, FALSE);
		}
	}
	else if (mode == GNT_KP_MODE_WINDOW_LIST && _list.window)
	{
		gnt_widget_key_pressed(_list.window, buffer);

		if (buffer[0] == '\r' || (buffer[0] == 27 && buffer[1] == 0))
		{
			mode = GNT_KP_MODE_NORMAL;
			lock_focus_list = 1;
			gnt_widget_destroy(_list.window);
			_list.window = NULL;
			_list.tree = NULL;
			lock_focus_list = 0;
			window_list = NULL;
			action_list = NULL;
		}
	}
	else if (mode == GNT_KP_MODE_RESIZE)
	{
		if (buffer[0] == '\r' || (buffer[0] == 27 && buffer[1] == 0)) {
			mode = GNT_KP_MODE_NORMAL;
			window_reverse(ordered->data, FALSE);
		} else if (buffer[0] == 27) {
			GntWidget *widget = ordered->data;
			gboolean changed = FALSE;
			int width, height;

			gnt_widget_get_size(widget, &width, &height);

			if (strcmp(buffer + 1, GNT_KEY_DOWN) == 0)
			{
				if (widget->priv.y + height < Y_MAX)
				{
					height++;
					changed = TRUE;
				}
			}
			else if (strcmp(buffer + 1, GNT_KEY_UP) == 0)
			{
				height--;
				changed = TRUE;
			}
			else if (strcmp(buffer + 1, GNT_KEY_LEFT) == 0)
			{
				width--;
				changed = TRUE;
			}
			else if (strcmp(buffer + 1, GNT_KEY_RIGHT) == 0)
			{
				if (widget->priv.x + width < X_MAX)
				{
					width++;
					changed = TRUE;
				}
			}

			if (changed)
			{
				gnt_screen_resize_widget(widget, width, height);
				window_reverse(widget, TRUE);
			}
		}
	}

	event_stack = FALSE;
	return TRUE;
}

static gboolean
refresh_screen()
{
	endwin();
	refresh();

	X_MAX = getmaxx(stdscr);
	Y_MAX = getmaxy(stdscr) - 1;

	g_hash_table_foreach(nodes, (GHFunc)refresh_node, NULL);
	update_screen(NULL);
	draw_taskbar(TRUE);

	return FALSE;
}

/* Xerox */
static void
clean_pid(void)
{
	int status;
	pid_t pid;

	do {
		pid = waitpid(-1, &status, WNOHANG);
	} while (pid != 0 && pid != (pid_t)-1);

	if ((pid == (pid_t) - 1) && (errno != ECHILD)) {
		char errmsg[BUFSIZ];
		g_snprintf(errmsg, BUFSIZ, "Warning: waitpid() returned %d", pid);
		perror(errmsg);
	}
}

static void
sighandler(int sig)
{
	switch (sig) {
#ifdef SIGWINCH
	case SIGWINCH:
		werase(stdscr);
		wrefresh(stdscr);
		g_idle_add(refresh_screen, NULL);
		signal(SIGWINCH, sighandler);
		break;
#endif
	case SIGCHLD:
		clean_pid();
		signal(SIGCHLD, sighandler);
		break;
	}
}

static void
init_wm()
{
	const char *name = gnt_style_get(GNT_STYLE_WM);
	gpointer handle;
	
	if (!name || !*name)
		return;
	
	handle = g_module_open(name, G_MODULE_BIND_LAZY);
	if (handle) {
		gboolean (*init)(GntWM *);
		if (g_module_symbol(handle, "gntwm_init", (gpointer)&init)) {
			init(&wm);
		}
	}
}

void gnt_init()
{
	static GIOChannel *channel = NULL;
	char *filename;
	int result;
	const char *locale;

	if (channel)
		return;
	
	channel = g_io_channel_unix_new(STDIN_FILENO);

	g_io_channel_set_encoding(channel, NULL, NULL);
	g_io_channel_set_buffered(channel, FALSE);
#if 0
	g_io_channel_set_flags(channel, G_IO_FLAG_NONBLOCK, NULL );
#endif

	result = g_io_add_watch_full(channel,  G_PRIORITY_HIGH,
					(G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_PRI | G_IO_NVAL),
					io_invoke, NULL, NULL);
	
	locale = setlocale(LC_ALL, "");

#if 0
	g_io_channel_unref(channel);  /* Apparently this causes crash for some people */
#endif

	if (locale && (strstr(locale, "UTF") || strstr(locale, "utf")))
		ascii_only = FALSE;
	else
		ascii_only = TRUE;

	initscr();
	typeahead(-1);
	noecho();
	curs_set(0);

	gnt_init_styles();

	filename = g_build_filename(g_get_home_dir(), ".gntrc", NULL);
	gnt_style_read_configure_file(filename);
	g_free(filename);

	gnt_init_colors();
	X_MIN = 0;
	Y_MIN = 0;
	X_MAX = getmaxx(stdscr);
	Y_MAX = getmaxy(stdscr) - 1;

	nodes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_node);

	wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
	refresh();

#ifdef ALL_MOUSE_EVENTS
	if ((mouse_enabled = gnt_style_get_bool(GNT_STYLE_MOUSE, FALSE)))
		mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
#endif

	wbkgdset(stdscr, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
	werase(stdscr);
	wrefresh(stdscr);

#ifdef SIGWINCH
	signal(SIGWINCH, sighandler);
#endif
	signal(SIGCHLD, sighandler);
	signal(SIGPIPE, SIG_IGN);

	g_type_init();

	init_wm();
}

void gnt_main()
{
	loop = g_main_loop_new(NULL, FALSE);
	g_main_loop_run(loop);
}

/*********************************
 * Stuff for 'window management' *
 *********************************/

static void
free_node(gpointer data)
{
	GntNode *node = data;
	hide_panel(node->panel);
	del_panel(node->panel);
	g_free(node);
}

void gnt_screen_occupy(GntWidget *widget)
{
	GntNode *node;

	while (widget->parent)
		widget = widget->parent;
	
	if (g_hash_table_lookup(nodes, widget))
		return;		/* XXX: perhaps _update instead? */

	node = g_new0(GntNode, 1);
	node->me = widget;

	g_hash_table_replace(nodes, widget, node);

	refresh_node(widget, node, NULL);

	if (window_list)
	{
		if ((GNT_IS_BOX(widget) && GNT_BOX(widget)->title) && window_list->window != widget
				&& GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_CAN_TAKE_FOCUS))
		{
			gnt_tree_add_row_last(GNT_TREE(window_list->tree), widget,
					gnt_tree_create_row(GNT_TREE(window_list->tree), GNT_BOX(widget)->title),
					NULL);
			update_window_in_list(widget);
		}
	}

	update_screen(NULL);
}

void gnt_screen_release(GntWidget *widget)
{
	GntNode *node;

	gnt_screen_remove_widget(widget);
	node = g_hash_table_lookup(nodes, widget);

	if (node == NULL)	/* Yay! Nothing to do. */
		return;

	if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_DESTROYING) && wm.close_window)
		wm.close_window(widget);

	g_hash_table_remove(nodes, widget);

	if (window_list)
	{
		gnt_tree_remove(GNT_TREE(window_list->tree), widget);
	}

	update_screen(NULL);
}

void gnt_screen_update(GntWidget *widget)
{
	GntNode *node;
	
	while (widget->parent)
		widget = widget->parent;
	if (!GNT_IS_MENU(widget))
		gnt_box_sync_children(GNT_BOX(widget));
	node = g_hash_table_lookup(nodes, widget);
	if (node && !node->panel)
	{
		if (wm.new_window && node->me != _list.window)
			node->panel = wm.new_window(node->me);
		else
			node->panel = new_panel(node->me->window);
		set_panel_userptr(node->panel, node);
		if (!GNT_WIDGET_IS_FLAG_SET(node->me, GNT_WIDGET_TRANSIENT)) {
			if (!g_object_get_data(G_OBJECT(node->me), "give_focus")) {
				bottom_panel(node->panel);     /* New windows should not grab focus */
				gnt_widget_set_urgent(node->me);
			}
			else {
				bring_on_top(node->me);
			}
		}
	}

	if (_list.window)
	{
		GntNode *nd = g_hash_table_lookup(nodes, _list.window);
		top_panel(nd->panel);
	}

	update_screen(NULL);
}

gboolean gnt_widget_has_focus(GntWidget *widget)
{
	GntWidget *w;
	if (!widget)
		return FALSE;
	
	if (GNT_IS_MENU(widget))
		return TRUE;

	w = widget;

	while (widget->parent)
		widget = widget->parent;

	if (widget == _list.window)
		return TRUE;

	if (ordered && ordered->data == widget)
	{
		if (GNT_IS_BOX(widget) &&
				(GNT_BOX(widget)->active == w || widget == w))
			return TRUE;
	}

	return FALSE;
}

void gnt_widget_set_urgent(GntWidget *widget)
{
	while (widget->parent)
		widget = widget->parent;

	if (ordered && ordered->data == widget)
		return;

	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_URGENT);

	if (wm.window_update) {
		GntNode *node = g_hash_table_lookup(nodes, widget);
		wm.window_update(node ? node->panel : NULL, widget);
	}

	draw_taskbar(FALSE);
}

void gnt_quit()
{
	gnt_uninit_colors();
	gnt_uninit_styles();
	endwin();
}

gboolean gnt_ascii_only()
{
	return ascii_only;
}

void gnt_screen_resize_widget(GntWidget *widget, int width, int height)
{
	if (widget->parent == NULL)
	{
		GntNode *node = g_hash_table_lookup(nodes, widget);
		if (!node)
			return;

		if (wm.window_resize_confirm && !wm.window_resize_confirm(widget, &width, &height))
			return;

		hide_panel(node->panel);
		gnt_widget_set_size(widget, width, height);
		gnt_widget_draw(widget);
		if (wm.window_resized)
			node->panel = wm.window_resized(node->panel, widget);
		else
			replace_panel(node->panel, widget->window);
		show_panel(node->panel);
		update_screen(NULL);
	}
}

void gnt_screen_move_widget(GntWidget *widget, int x, int y)
{
	GntNode *node = g_hash_table_lookup(nodes, widget);

	if (wm.window_move_confirm && !wm.window_move_confirm(widget, &x, &y))
		return;

	gnt_widget_set_position(widget, x, y);
	move_panel(node->panel, y, x);

	if (wm.window_moved)
		wm.window_moved(node->panel, widget);

	update_screen(NULL);
}

void gnt_screen_rename_widget(GntWidget *widget, const char *text)
{
	gnt_box_set_title(GNT_BOX(widget), text);
	gnt_widget_draw(widget);

	if (wm.window_update) {
		GntNode *node = g_hash_table_lookup(nodes, widget);
		wm.window_update(node ? node->panel : NULL, widget);
	}

	draw_taskbar(FALSE);
}

/**
 * An application can register actions which will show up in a 'start-menu' like popup
 */
typedef struct _GnAction
{
	const char *label;
	void (*callback)();
} GntAction;

static GList *actions;

void gnt_register_action(const char *label, void (*callback)())
{
	GntAction *action = g_new0(GntAction, 1);
	action->label = g_strdup(label);
	action->callback = callback;

	actions = g_list_append(actions, action);
}

static void
action_list_activate(GntTree *tree, gpointer null)
{
	GntAction *action = gnt_tree_get_selection_data(tree);
	action->callback();
}

static int
compare_action(gconstpointer p1, gconstpointer p2)
{
	const GntAction *a1 = p1;
	const GntAction *a2 = p2;

	return g_utf8_collate(a1->label, a2->label);
}

static void
show_actions_list()
{
	GntWidget *tree, *win;
	GList *iter;
	int h;

	if (action_list)
		return;
	
	setup__list();
	action_list = &_list;
	win = action_list->window;
	tree = action_list->tree;

	gnt_box_set_title(GNT_BOX(win), "Actions");
	GNT_WIDGET_SET_FLAGS(tree, GNT_WIDGET_NO_BORDER);
	/* XXX: Do we really want this? */
	gnt_tree_set_compare_func(GNT_TREE(tree), compare_action);

	for (iter = actions; iter; iter = iter->next) {
		GntAction *action = iter->data;
		gnt_tree_add_row_last(GNT_TREE(tree), action,
				gnt_tree_create_row(GNT_TREE(tree), action->label), NULL);
	}
	g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(action_list_activate), NULL);
	gnt_widget_set_size(tree, 0, g_list_length(actions));
	gnt_widget_get_size(win, NULL, &h);
	gnt_widget_set_position(win, 0, getmaxy(stdscr) - 1 - h);

	lock_focus_list = 1;
	gnt_widget_show(win);
	lock_focus_list = 0;
}

static void
reset_menu(GntWidget *widget, gpointer null)
{
	menu = NULL;
}

gboolean gnt_screen_menu_show(gpointer newmenu)
{
	if (menu) {
		/* For now, if a menu is being displayed, then another menu
		 * can NOT take over. */
		return FALSE;
	}

	menu = newmenu;
	gnt_widget_draw(GNT_WIDGET(menu));

	g_signal_connect(G_OBJECT(menu), "hide", G_CALLBACK(reset_menu), NULL);

	return TRUE;
}