/*
 * Copyright 2008 François Revol <mmu_man@users.sourceforge.net>
 * Copyright 2006 Daniel Silverstone <dsilvers@digital-scurf.org>
 * Copyright 2006 Rob Kendrick <rjek@rjek.com>
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * NetSurf is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * NetSurf is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define __STDBOOL_H__	1
#include <assert.h>
extern "C" {
#include "content/urldb.h"
#include "css/utils.h"
#include "desktop/browser.h"
#include "desktop/mouse.h"
#include "desktop/options.h"
#include "desktop/selection.h"
#include "desktop/textinput.h"
#include "render/font.h"
#include "utils/log.h"
#include "utils/types.h"
#include "utils/utf8.h"
#include "utils/utils.h"
}
#include "beos/beos_about.h"
#include "beos/beos_window.h"
#include "beos/beos_font.h"
#include "beos/beos_gui.h"
#include "beos/beos_scaffolding.h"
#include "beos/beos_plotters.h"
//#include "beos/beos_schedule.h"

#include <AppDefs.h>
#include <BeBuild.h>
#include <Clipboard.h>
#include <Cursor.h>
#include <InterfaceDefs.h>
#include <Message.h>
#include <ScrollBar.h>
#include <String.h>
#include <String.h>
#include <TextView.h>
#include <View.h>
#include <Window.h>

class NSBrowserFrameView;

struct gui_window {
	/* All gui_window objects have an ultimate scaffold */
	nsbeos_scaffolding	*scaffold;
	bool	toplevel;
	/* A gui_window is the rendering of a browser_window */
	struct browser_window	*bw;

	struct {
		int pressed_x;
		int pressed_y;
		int state; /* browser_mouse_state */
	} mouse;

	/* These are the storage for the rendering */
	int			caretx, carety, careth;
	gui_pointer_shape	current_pointer;
	int			last_x, last_y;

	NSBrowserFrameView	*view;

	// some cached events to speed up things
	// those are the last queued event of their kind,
	// we can safely drop others and avoid wasting cpu.
	// number of pending resizes
	vint32				pending_resizes;
	// accumulated rects of pending redraws
	//volatile BMessage	*lastRedraw;
	// UNUSED YET
	BRect				pendingRedraw;
#if 0 /* GTK */
	/* Within GTK, a gui_window is a scrolled window
	 * with a viewport inside
	 * with a gtkfixed in that
	 * with a drawing area in that
	 * The scrolled window is optional and only chosen
	 * for frames which need it. Otherwise we just use
	 * a viewport.
	 */
	GtkScrolledWindow	*scrolledwindow;
	GtkViewport		*viewport;
	GtkFixed		*fixed;
	GtkDrawingArea		*drawing_area;
#endif

	/* Keep gui_windows in a list for cleanup later */
	struct gui_window	*next, *prev;
};



static const rgb_color kWhiteColor = {255, 255, 255, 255};

static struct gui_window *window_list = 0;	/**< first entry in win list*/

static BString current_selection;
static BList current_selection_textruns;

/* Methods which apply only to a gui_window */
static void nsbeos_window_expose_event(BView *view, gui_window *g, BMessage *message);
static void nsbeos_window_keypress_event(BView *view, gui_window *g, BMessage *event);
static void nsbeos_window_resize_event(BView *view, gui_window *g, BMessage *event);
static void nsbeos_window_moved_event(BView *view, gui_window *g, BMessage *event);
/* Other useful bits */
static void nsbeos_redraw_caret(struct gui_window *g);

#if 0 /* GTK */
static GdkCursor *nsbeos_create_menu_cursor(void);
#endif

// #pragma mark - class NSBrowserFrameView


NSBrowserFrameView::NSBrowserFrameView(BRect frame, struct gui_window *gui)
	: BView(frame, "NSBrowserFrameView", B_FOLLOW_ALL_SIDES, 
		B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS /*| B_SUBPIXEL_PRECISE*/),
	fGuiWindow(gui)
{
}


NSBrowserFrameView::~NSBrowserFrameView()
{
}


void
NSBrowserFrameView::MessageReceived(BMessage *message)
{
	switch (message->what) {
		case B_SIMPLE_DATA:
		case B_ARGV_RECEIVED:
		case B_REFS_RECEIVED:
		case B_COPY:
		case B_CUT:
		case B_PASTE:
		case B_SELECT_ALL:
		//case B_MOUSE_WHEEL_CHANGED:
		case B_UI_SETTINGS_CHANGED:
		// NetPositive messages
		case B_NETPOSITIVE_OPEN_URL:
		case B_NETPOSITIVE_BACK:
		case B_NETPOSITIVE_FORWARD:
		case B_NETPOSITIVE_HOME:
		case B_NETPOSITIVE_RELOAD:
		case B_NETPOSITIVE_STOP:
		case B_NETPOSITIVE_DOWN:
		case B_NETPOSITIVE_UP:
		// messages for top-level
		case 'back':
		case 'forw':
		case 'stop':
		case 'relo':
		case 'home':
		case 'urlc':
		case 'urle':
		case 'menu':
		case NO_ACTION:
		case HELP_OPEN_CONTENTS:
		case HELP_OPEN_GUIDE:
		case HELP_OPEN_INFORMATION:
		case HELP_OPEN_ABOUT:
		case HELP_LAUNCH_INTERACTIVE:
		case HISTORY_SHOW_LOCAL:
		case HISTORY_SHOW_GLOBAL:
		case HOTLIST_ADD_URL:
		case HOTLIST_SHOW:
		case COOKIES_SHOW:
		case COOKIES_DELETE:
		case BROWSER_PAGE:
		case BROWSER_PAGE_INFO:
		case BROWSER_PRINT:
		case BROWSER_NEW_WINDOW:
		case BROWSER_VIEW_SOURCE:
		case BROWSER_OBJECT:
		case BROWSER_OBJECT_INFO:
		case BROWSER_OBJECT_RELOAD:
		case BROWSER_OBJECT_SAVE:
		case BROWSER_OBJECT_EXPORT_SPRITE:
		case BROWSER_OBJECT_SAVE_URL_URI:
		case BROWSER_OBJECT_SAVE_URL_URL:
		case BROWSER_OBJECT_SAVE_URL_TEXT:
		case BROWSER_SAVE:
		case BROWSER_SAVE_COMPLETE:
		case BROWSER_EXPORT_DRAW:
		case BROWSER_EXPORT_TEXT:
		case BROWSER_SAVE_URL_URI:
		case BROWSER_SAVE_URL_URL:
		case BROWSER_SAVE_URL_TEXT:
		case HOTLIST_EXPORT:
		case HISTORY_EXPORT:
		case BROWSER_NAVIGATE_HOME:
		case BROWSER_NAVIGATE_BACK:
		case BROWSER_NAVIGATE_FORWARD:
		case BROWSER_NAVIGATE_UP:
		case BROWSER_NAVIGATE_RELOAD:
		case BROWSER_NAVIGATE_RELOAD_ALL:
		case BROWSER_NAVIGATE_STOP:
		case BROWSER_NAVIGATE_URL:
		case BROWSER_SCALE_VIEW:
		case BROWSER_FIND_TEXT:
		case BROWSER_IMAGES_FOREGROUND:
		case BROWSER_IMAGES_BACKGROUND:
		case BROWSER_BUFFER_ANIMS:
		case BROWSER_BUFFER_ALL:
		case BROWSER_SAVE_VIEW:
		case BROWSER_WINDOW_DEFAULT:
		case BROWSER_WINDOW_STAGGER:
		case BROWSER_WINDOW_COPY:
		case BROWSER_WINDOW_RESET:
		case TREE_NEW_FOLDER:
		case TREE_NEW_LINK:
		case TREE_EXPAND_ALL:
		case TREE_EXPAND_FOLDERS:
		case TREE_EXPAND_LINKS:
		case TREE_COLLAPSE_ALL:
		case TREE_COLLAPSE_FOLDERS:
		case TREE_COLLAPSE_LINKS:
		case TREE_SELECTION:
		case TREE_SELECTION_EDIT:
		case TREE_SELECTION_LAUNCH:
		case TREE_SELECTION_DELETE:
		case TREE_SELECT_ALL:
		case TREE_CLEAR_SELECTION:
		case TOOLBAR_BUTTONS:
		case TOOLBAR_ADDRESS_BAR:
		case TOOLBAR_THROBBER:
		case TOOLBAR_EDIT:
		case CHOICES_SHOW:
		case APPLICATION_QUIT:
			Window()->DetachCurrentMessage();
			nsbeos_pipe_message_top(message, NULL, fGuiWindow->scaffold);
			break;
		default:
		message->PrintToStream();
			BView::MessageReceived(message);
	}
}


void
NSBrowserFrameView::Draw(BRect updateRect)
{
	BMessage *message = NULL;
	//message = Window()->DetachCurrentMessage();
	// might be called directly...
	if (message == NULL)
		message = new BMessage(_UPDATE_);
	message->AddRect("rect", updateRect);
	nsbeos_pipe_message(message, this, fGuiWindow);
}


#if 0
void
NSBrowserFrameView::FrameMoved(BPoint new_location)
{
	BMessage *message = Window()->DetachCurrentMessage();
	// discard any other pending resize, 
	// so we don't end up processing them all, the last one matters.
	//atomic_add(&fGuiWindow->pending_resizes, 1);
	nsbeos_pipe_message(message, this, fGuiWindow);
	BView::FrameMoved(new_location);
}
#endif

void
NSBrowserFrameView::FrameResized(float new_width, float new_height)
{
	BMessage *message = Window()->DetachCurrentMessage();
	// discard any other pending resize, 
	// so we don't end up processing them all, the last one matters.
	atomic_add(&fGuiWindow->pending_resizes, 1);
	nsbeos_pipe_message(message, this, fGuiWindow);
	BView::FrameResized(new_width, new_height);
}


void
NSBrowserFrameView::KeyDown(const char *bytes, int32 numBytes)
{
	BMessage *message = Window()->DetachCurrentMessage();
	nsbeos_pipe_message(message, this, fGuiWindow);
}


void
NSBrowserFrameView::MouseDown(BPoint where)
{
	BMessage *message = Window()->DetachCurrentMessage();
	BPoint screenWhere;
	if (message->FindPoint("screen_where", &screenWhere) < B_OK) {
		screenWhere = ConvertToScreen(where);
		message->AddPoint("screen_where", screenWhere);
	}
	nsbeos_pipe_message(message, this, fGuiWindow);
}


void
NSBrowserFrameView::MouseUp(BPoint where)
{
	//BMessage *message = Window()->DetachCurrentMessage();
	//nsbeos_pipe_message(message, this, fGuiWindow);
	BMessage *message = Window()->DetachCurrentMessage();
	BPoint screenWhere;
	if (message->FindPoint("screen_where", &screenWhere) < B_OK) {
		screenWhere = ConvertToScreen(where);
		message->AddPoint("screen_where", screenWhere);
	}
	nsbeos_pipe_message(message, this, fGuiWindow);
}


void
NSBrowserFrameView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg)
{
	if (transit != B_INSIDE_VIEW) {
		BView::MouseMoved(where, transit, msg);
		return;
	}
	BMessage *message = Window()->DetachCurrentMessage();
	nsbeos_pipe_message(message, this, fGuiWindow);
}


// #pragma mark - gui_window

struct browser_window *nsbeos_get_browser_window(struct gui_window *g)
{
	return g->bw;
}

nsbeos_scaffolding *nsbeos_get_scaffold(struct gui_window *g)
{
	return g->scaffold;
}

struct browser_window *nsbeos_get_browser_for_gui(struct gui_window *g)
{
	return g->bw;
}

float nsbeos_get_scale_for_gui(struct gui_window *g)
{
	return g->bw->scale;
}

/* Create a gui_window */
struct gui_window *gui_create_browser_window(struct browser_window *bw,
					     struct browser_window *clone, bool new_tab)
{
	struct gui_window *g;		/**< what we're creating to return */

	g = (struct gui_window *)malloc(sizeof(*g));
	if (!g) {
		warn_user("NoMemory", 0);
		return 0;
	}

	LOG(("Creating gui window %p for browser window %p", g, bw));

	g->bw = bw;
	g->mouse.state = 0;
	g->current_pointer = GUI_POINTER_DEFAULT;
	if (clone != NULL)
		bw->scale = clone->scale;
	else
		bw->scale = (float) option_scale / 100;

	g->careth = 0;
	g->pending_resizes = 0;

	/* Attach ourselves to the list (push_top) */
	if (window_list)
		window_list->prev = g;
	g->next = window_list;
	g->prev = NULL;
	window_list = g;

	/* Now construct and attach a scaffold */
	g->scaffold = nsbeos_new_scaffolding(g);
	if (!g->scaffold)
		return NULL;

	/* Construct our primary elements */
	BRect frame(0,0,-1,-1); // will be resized later
	g->view = new NSBrowserFrameView(frame, g);
	/* set the default background colour of the drawing area to white. */
	//g->view->SetViewColor(kWhiteColor);
	/* NOOO! Since we defer drawing (DetachCurrent()), the white flickers,
	 * besides sometimes text was drawn twice, making it ugly.
	 * Instead we set to transparent here, and implement plot_clg() to 
	 * do it just before the rest. This almost removes the flicker. */
	g->view->SetViewColor(B_TRANSPARENT_COLOR);
	g->view->SetLowColor(kWhiteColor);

#ifdef B_BEOS_VERSION_DANO
	/* enable double-buffering on the content view */
/*
	XXX: doesn't really work
	g->view->SetDoubleBuffering(B_UPDATE_INVALIDATED
		| B_UPDATE_SCROLLED
		//| B_UPDATE_RESIZED
		| B_UPDATE_EXPOSED);
*/
#endif


	g->toplevel = true;

	/* Attach our viewport into the scaffold */
	nsbeos_attach_toplevel_view(g->scaffold, g->view);

#warning WRITEME
#if 0 /* GTK */
	GtkPolicyType scrollpolicy;

	/* Construct our primary elements */
	g->fixed = GTK_FIXED(gtk_fixed_new());
	g->drawing_area = GTK_DRAWING_AREA(gtk_drawing_area_new());
	gtk_fixed_put(g->fixed, GTK_WIDGET(g->drawing_area), 0, 0);
	gtk_container_set_border_width(GTK_CONTAINER(g->fixed), 0);

	g->scrolledwindow = 0;
	g->viewport = GTK_VIEWPORT(gtk_viewport_new(NULL, NULL)); /* Need to attach adjustments */
	gtk_container_add(GTK_CONTAINER(g->viewport), GTK_WIDGET(g->fixed));

	/* Attach our viewport into the scaffold */
	nsbeos_attach_toplevel_viewport(g->scaffold, g->viewport);

	gtk_container_set_border_width(GTK_CONTAINER(g->viewport), 0);
	gtk_viewport_set_shadow_type(g->viewport, GTK_SHADOW_NONE);
	if (g->scrolledwindow)
		gtk_widget_show(GTK_WIDGET(g->scrolledwindow));
	/* And enable visibility from our viewport down */
	gtk_widget_show(GTK_WIDGET(g->viewport));
	gtk_widget_show(GTK_WIDGET(g->fixed));
	gtk_widget_show(GTK_WIDGET(g->drawing_area));

	switch(bw->scrolling) {
	case SCROLLING_NO:
		scrollpolicy = GTK_POLICY_NEVER;
		break;
	case SCROLLING_YES:
		scrollpolicy = GTK_POLICY_ALWAYS;
		break;
	case SCROLLING_AUTO:
	default:
		scrollpolicy = GTK_POLICY_AUTOMATIC;
		break;
	};


	if (g->scrolledwindow)
		gtk_scrolled_window_set_policy(g->scrolledwindow,
				scrollpolicy, scrollpolicy);

	/* set the events we're interested in receiving from the browser's
	 * drawing area.
	 */
	gtk_widget_add_events(GTK_WIDGET(g->drawing_area),
				GDK_EXPOSURE_MASK |
				GDK_LEAVE_NOTIFY_MASK |
				GDK_BUTTON_PRESS_MASK |
				GDK_POINTER_MOTION_MASK |
				GDK_KEY_PRESS_MASK |
				GDK_KEY_RELEASE_MASK);
	GTK_WIDGET_SET_FLAGS(GTK_WIDGET(g->drawing_area), GTK_CAN_FOCUS);

	/* set the default background colour of the drawing area to white. */
	gtk_widget_modify_bg(GTK_WIDGET(g->drawing_area), GTK_STATE_NORMAL,
				&((GdkColor) { 0, 0xffff, 0xffff, 0xffff } ));

#define CONNECT(obj, sig, callback, ptr) \
	g_signal_connect(G_OBJECT(obj), (sig), G_CALLBACK(callback), (ptr))
	CONNECT(g->drawing_area, "expose_event", nsgtk_window_expose_event, g);
	CONNECT(g->drawing_area, "motion_notify_event",
		nsgtk_window_motion_notify_event, g);
	CONNECT(g->drawing_area, "button_press_event",
	    	nsgtk_window_button_press_event, g);
	CONNECT(g->drawing_area, "key_press_event",
		nsgtk_window_keypress_event, g);
	CONNECT(g->viewport, "size_allocate",
		nsgtk_window_size_allocate_event, g);
#endif

	return g;
}


void gui_window_scroll_visible(struct gui_window *g, int x0, int y0,
		int x1, int y1)
{
	gui_window_set_scroll(g, x0, y0);
}

void nsbeos_dispatch_event(BMessage *message)
{
	struct gui_window *gui = NULL;
	NSBrowserFrameView *view = NULL;
	struct beos_scaffolding *scaffold = NULL;
	NSBrowserWindow *window = NULL;

	//message->PrintToStream();
	if (message->FindPointer("View", (void **)&view) < B_OK)
		view = NULL;
	if (message->FindPointer("gui_window", (void **)&gui) < B_OK)
		gui = NULL;
	if (message->FindPointer("Window", (void **)&window) < B_OK)
		window = NULL;
	if (message->FindPointer("scaffolding", (void **)&scaffold) < B_OK)
		scaffold = NULL;

	struct gui_window *z;
	for (z = window_list; z && gui && z != gui; z = z->next)
		continue;

	struct gui_window *y;
	for (y = window_list; y && scaffold && y->scaffold != scaffold; y = y->next)
		continue;

	if (gui && gui != z) {
		LOG(("discarding event for destroyed gui_window"));
		delete message;
		return;
	}
	if (scaffold && (!y || scaffold != y->scaffold)) {
		LOG(("discarding event for destroyed scaffolding"));
		delete message;
		return;
	}

	// messages for top-level
	if (scaffold) {
		LOG(("dispatching to top-level"));
		nsbeos_scaffolding_dispatch_event(scaffold, message);
		delete message;
		return;
	}

	//LOG(("processing message"));
	switch (message->what) {
		case B_QUIT_REQUESTED:
			// from the BApplication
			netsurf_quit = true;
			break;
		case B_ABOUT_REQUESTED:
		{
			nsbeos_about(gui);
			/* XXX: doesn't work yet! bug in rsrc:/
			BString url("rsrc:/about.en.html,text/html");
			browser_window_create(url.String(), NULL, NULL, true, false);
			*/
			break;
		}
		case _UPDATE_:
			if (gui && view)
				nsbeos_window_expose_event(view, gui, message);
			break;
		case B_MOUSE_MOVED:
		{
			if (gui == NULL)
				break;

			BPoint where;
			int32 mods;
			// where refers to Window coords !?
			// check be:view_where first
			if (message->FindPoint("be:view_where", &where) < B_OK) {
				if (message->FindPoint("where", &where) < B_OK)
					break;
			}
			if (message->FindInt32("modifiers", &mods) < B_OK)
				mods = 0;


			if (gui->mouse.state & BROWSER_MOUSE_PRESS_1) {
				/* Start button 1 drag */
				browser_window_mouse_click(gui->bw, BROWSER_MOUSE_DRAG_1,
					gui->mouse.pressed_x, gui->mouse.pressed_y);
				/* Replace PRESS with HOLDING and declare drag in progress */
				gui->mouse.state ^= (BROWSER_MOUSE_PRESS_1 |
					BROWSER_MOUSE_HOLDING_1);
				gui->mouse.state |= BROWSER_MOUSE_DRAG_ON;
			} else if (gui->mouse.state & BROWSER_MOUSE_PRESS_2) {
				/* Start button 2 drag */
				browser_window_mouse_click(gui->bw, BROWSER_MOUSE_DRAG_2,
					gui->mouse.pressed_x, gui->mouse.pressed_y);
				/* Replace PRESS with HOLDING and declare drag in progress */
				gui->mouse.state ^= (BROWSER_MOUSE_PRESS_2 |
					BROWSER_MOUSE_HOLDING_2);
				gui->mouse.state |= BROWSER_MOUSE_DRAG_ON;
			}

			bool shift = mods & B_SHIFT_KEY;
			bool ctrl = mods & B_CONTROL_KEY;

			/* Handle modifiers being removed */
			if (gui->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
				gui->mouse.state ^= BROWSER_MOUSE_MOD_1;
			if (gui->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
				gui->mouse.state ^= BROWSER_MOUSE_MOD_2;

			browser_window_mouse_track(gui->bw, (browser_mouse_state)gui->mouse.state, 
					(int)(where.x / gui->bw->scale),
					(int)(where.y / gui->bw->scale));

			gui->last_x = (int)where.x;
			gui->last_y = (int)where.y;
			break;
		}
		case B_MOUSE_DOWN:
		{
			if (gui == NULL)
				break;

			BPoint where;
			int32 buttons;
			int32 mods;
			BPoint screenWhere;
			if (message->FindPoint("be:view_where", &where) < B_OK) {
				if (message->FindPoint("where", &where) < B_OK)
					break;
			}
			if (message->FindInt32("buttons", &buttons) < B_OK)
				break;
			if (message->FindPoint("screen_where", &screenWhere) < B_OK)
				break;
			if (message->FindInt32("modifiers", &mods) < B_OK)
				mods = 0;

			if (buttons & B_SECONDARY_MOUSE_BUTTON) {
				/* 2 == right button on BeOS */
				
				nsbeos_scaffolding_popup_menu(gui->scaffold, screenWhere);
				break;
			}

			gui->mouse.state = BROWSER_MOUSE_PRESS_1;

			if (buttons & B_TERTIARY_MOUSE_BUTTON) /* 3 == middle button on BeOS */
				gui->mouse.state = BROWSER_MOUSE_PRESS_2;

			if (mods & B_SHIFT_KEY)
				gui->mouse.state |= BROWSER_MOUSE_MOD_1;
			if (mods & B_CONTROL_KEY)
				gui->mouse.state |= BROWSER_MOUSE_MOD_2;

			gui->mouse.pressed_x = where.x / gui->bw->scale;
			gui->mouse.pressed_y = where.y / gui->bw->scale;

			// make sure the view is in focus
			if (view && view->LockLooper()) {
				if (!view->IsFocus())
					view->MakeFocus();
				view->UnlockLooper();
			}

			browser_window_mouse_click(gui->bw, 
				(browser_mouse_state)gui->mouse.state,
				gui->mouse.pressed_x, gui->mouse.pressed_y);

			break;
		}
		case B_MOUSE_UP:
		{
			if (gui == NULL)
				break;

			BPoint where;
			int32 buttons;
			int32 mods;
			BPoint screenWhere;
			if (message->FindPoint("be:view_where", &where) < B_OK) {
				if (message->FindPoint("where", &where) < B_OK)
					break;
			}
			if (message->FindInt32("buttons", &buttons) < B_OK)
				break;
			if (message->FindPoint("screen_where", &screenWhere) < B_OK)
				break;
			if (message->FindInt32("modifiers", &mods) < B_OK)
				mods = 0;

			/* If the mouse state is PRESS then we are waiting for a release to emit
			 * a click event, otherwise just reset the state to nothing*/
			if (gui->mouse.state & BROWSER_MOUSE_PRESS_1) 
				gui->mouse.state ^= (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1);
			else if (gui->mouse.state & BROWSER_MOUSE_PRESS_2)
				gui->mouse.state ^= (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_CLICK_2);

			bool shift = mods & B_SHIFT_KEY;
			bool ctrl = mods & B_CONTROL_KEY;

			/* Handle modifiers being removed */
			if (gui->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
				gui->mouse.state ^= BROWSER_MOUSE_MOD_1;
			if (gui->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
				gui->mouse.state ^= BROWSER_MOUSE_MOD_2;

			/*
			if (view && view->LockLooper()) {
				view->MakeFocus();
				view->UnlockLooper();
			}
			*/

			if (gui->mouse.state & (BROWSER_MOUSE_CLICK_1|BROWSER_MOUSE_CLICK_2))
				browser_window_mouse_click(gui->bw, 
					(browser_mouse_state)gui->mouse.state, 
					where.x / gui->bw->scale,
					where.y / gui->bw->scale);
			else 
				browser_window_mouse_track(gui->bw, (browser_mouse_state)0, 
					where.x, where.y);

			gui->mouse.state = 0;

			break;
		}
		case B_KEY_DOWN:
			if (gui && view)
				nsbeos_window_keypress_event(view, gui, message);
			break;
		case B_VIEW_RESIZED:
			if (gui && view)
				nsbeos_window_resize_event(view, gui, message);
			break;
		case B_VIEW_MOVED:
			if (gui && view)
				nsbeos_window_moved_event(view, gui, message);
			break;
		case B_MOUSE_WHEEL_CHANGED:
			break;
		case B_UI_SETTINGS_CHANGED:
			nsbeos_update_system_ui_colors();
			break;
		case 'nsLO': // login
		{
			BString url;
			BString realm;
			BString auth;
			if (message->FindString("URL", &url) < B_OK)
				break;
			if (message->FindString("Realm", &realm) < B_OK)
				break;
			if (message->FindString("Auth", &auth) < B_OK)
				break;
			//printf("login to '%s' with '%s'\n", url.String(), auth.String());
			urldb_set_auth_details(url.String(), realm.String(), auth.String());
			browser_window_go(gui->bw, url.String(), 0, true);
			break;
		}
		default:
			break;
	}
	delete message;
}

void nsbeos_window_expose_event(BView *view, gui_window *g, BMessage *message)
{
	BRect updateRect;
	hlcache_handle *c;
	float scale = g->bw->scale;
	struct rect clip;

	struct redraw_context ctx = { true, true, &nsbeos_plotters };

	assert(g);
	assert(g->bw);

	struct gui_window *z;
	for (z = window_list; z && z != g; z = z->next)
		continue;
	assert(z);
	assert(g->view == view);

	// we'll be resizing = reflowing = redrawing everything anyway...
	if (g->pending_resizes > 1)
		return;

	if (message->FindRect("rect", &updateRect) < B_OK)
		return;

	c = g->bw->current_content;
	if (c == NULL)
		return;

	if (!view->LockLooper())
		return;
	nsbeos_current_gc_set(view);

	if (view->Window())
		view->Window()->BeginViewTransaction();

	clip.x0 = (int)updateRect.left;
	clip.y0 = (int)updateRect.top;
	clip.x1 = (int)updateRect.right + 1;
	clip.y1 = (int)updateRect.bottom + 1;

	browser_window_redraw(g->bw, 0, 0, &clip, &ctx);

	if (g->careth != 0)
		nsbeos_plot_caret(g->caretx, g->carety, g->careth);

	if (view->Window())
		view->Window()->EndViewTransaction();

	// reset clipping just in case
	view->ConstrainClippingRegion(NULL);
	nsbeos_current_gc_set(NULL);
	view->UnlockLooper();
}

void nsbeos_window_keypress_event(BView *view, gui_window *g, BMessage *event)
{
	const char *bytes;
	char buff[6];
	int numbytes = 0;
	uint32 mods;
	uint32 key;
	uint32 raw_char;
	uint32_t nskey;
	int i;

	if (event->FindInt32("modifiers", (int32 *)&mods) < B_OK)
		mods = modifiers();
	if (event->FindInt32("key", (int32 *)&key) < B_OK)
		key = 0;
	if (event->FindInt32("raw_char", (int32 *)&raw_char) < B_OK)
		raw_char = 0;
	/* check for byte[] first, because C-space gives bytes="" (and byte[0] = '\0') */
	for (i = 0; i < 5; i++) {
		buff[i] = '\0';
		if (event->FindInt8("byte", i, (int8 *)&buff[i]) < B_OK)
			break;
	}

	if (i) {
		bytes = buff;
		numbytes = i;
	} else if (event->FindString("bytes", &bytes) < B_OK)
		bytes = "";

	if (!numbytes)
		numbytes = strlen(bytes);

	LOG(("mods 0x%08lx key %ld raw %ld byte[0] %d", mods, key, raw_char, buff[0]));

	char byte;
	if (numbytes == 1) {
		byte = bytes[0];
		if (mods & B_CONTROL_KEY)
			byte = (char)raw_char;
		if (byte >= '!' && byte <= '~')
			nskey = (uint32_t)byte;
		else {
			switch (byte) {
				case B_BACKSPACE:	nskey = KEY_DELETE_LEFT; break;
				case B_TAB:	nskey = KEY_TAB; break;
				/*case XK_Linefeed:	return QKlinefeed;*/
				case B_ENTER:	nskey = (uint32_t)10; break;
				case B_ESCAPE:	nskey = (uint32_t)'\033'; break;
				case B_SPACE:	nskey = (uint32_t)' '; break;
				case B_DELETE:	nskey = KEY_DELETE_RIGHT; break;
				/*
				case B_INSERT:	nskey = KEYSYM("insert"); break;
				*/
				case B_HOME:	nskey = KEY_LINE_START; break; // XXX ?
				case B_END:	nskey = KEY_LINE_END; break; // XXX ?
				case B_PAGE_UP:	nskey = KEY_PAGE_UP; break;
				case B_PAGE_DOWN:	nskey = KEY_PAGE_DOWN; break;
				case B_LEFT_ARROW:	nskey = KEY_LEFT; break;
				case B_RIGHT_ARROW:	nskey = KEY_RIGHT; break;
				case B_UP_ARROW:	nskey = KEY_UP; break;
				case B_DOWN_ARROW:	nskey = KEY_DOWN; break;
				/*
				case B_FUNCTION_KEY:
					switch (scancode) {
						case B_F1_KEY: nskey = KEYSYM("f1"); break;
						case B_F2_KEY: nskey = KEYSYM("f2"); break;
						case B_F3_KEY: nskey = KEYSYM("f3"); break;
						case B_F4_KEY: nskey = KEYSYM("f4"); break;
						case B_F5_KEY: nskey = KEYSYM("f5"); break;
						case B_F6_KEY: nskey = KEYSYM("f6"); break;
						case B_F7_KEY: nskey = KEYSYM("f7"); break;
						case B_F8_KEY: nskey = KEYSYM("f8"); break;
						case B_F9_KEY: nskey = KEYSYM("f9"); break;
						case B_F10_KEY: nskey = KEYSYM("f10"); break;
						case B_F11_KEY: nskey = KEYSYM("f11"); break;
						case B_F12_KEY: nskey = KEYSYM("f12"); break;
						case B_PRINT_KEY: nskey = KEYSYM("print"); break;
						case B_SCROLL_KEY: nskey = KEYSYM("scroll-lock"); break;
						case B_PAUSE_KEY: nskey = KEYSYM("pause"); break;
					}
				*/
				case 0:
					nskey = (uint32_t)0;
				default:
					nskey = (uint32_t)raw_char;
					/*if (simple_p)
						nskey = (uint32_t)0;*/
					break;
			}
		}
	} else {
		nskey = utf8_to_ucs4(bytes, numbytes);
	}

	bool done = browser_window_key_press(g->bw, nskey);
	LOG(("nskey %d %d", nskey, done));
	//if (browser_window_key_press(g->bw, nskey))
		return;
	
}

#warning WRITEME
#if 0 /* GTK */
gboolean nsbeos_window_keypress_event(GtkWidget *widget, GdkEventKey *event,
					gpointer data)
{
	struct gui_window *g = data;
	uint32_t nskey = gdkkey_to_nskey(event);

	if (browser_window_key_press(g->bw, nskey))
		return TRUE;

	if (event->state == 0) {
		double value;
		GtkAdjustment *vscroll = gtk_viewport_get_vadjustment(g->viewport);

		GtkAdjustment *hscroll = gtk_viewport_get_hadjustment(g->viewport);

		GtkAdjustment *scroll;

		const GtkAllocation *const alloc =
			&GTK_WIDGET(g->viewport)->allocation;

		switch (event->keyval) {
		default:
			return TRUE;

		case GDK_Home:
		case GDK_KP_Home:
			scroll = vscroll;
			value = scroll->lower;
			break;

		case GDK_End:
		case GDK_KP_End:
			scroll = vscroll;
			value = scroll->upper - alloc->height;
			if (value < scroll->lower)
				value = scroll->lower;
			break;

		case GDK_Left:
		case GDK_KP_Left:
			scroll = hscroll;
			value = gtk_adjustment_get_value(scroll) -
						scroll->step_increment;
			if (value < scroll->lower)
				value = scroll->lower;
			break;

		case GDK_Up:
		case GDK_KP_Up:
			scroll = vscroll;
			value = gtk_adjustment_get_value(scroll) -
						scroll->step_increment;
			if (value < scroll->lower)
				value = scroll->lower;
			break;

		case GDK_Right:
		case GDK_KP_Right:
			scroll = hscroll;
			value = gtk_adjustment_get_value(scroll) +
						scroll->step_increment;
			if (value > scroll->upper - alloc->width)
				value = scroll->upper - alloc->width;
			break;

		case GDK_Down:
		case GDK_KP_Down:
			scroll = vscroll;
			value = gtk_adjustment_get_value(scroll) +
						scroll->step_increment;
			if (value > scroll->upper - alloc->height)
				value = scroll->upper - alloc->height;
			break;

		case GDK_Page_Up:
		case GDK_KP_Page_Up:
			scroll = vscroll;
			value = gtk_adjustment_get_value(scroll) -
						scroll->page_increment;
			if (value < scroll->lower)
				value = scroll->lower;
			break;

		case GDK_Page_Down:
		case GDK_KP_Page_Down:
			scroll = vscroll;
			value = gtk_adjustment_get_value(scroll) +
						scroll->page_increment;
			if (value > scroll->upper - alloc->height)
				value = scroll->upper - alloc->height;
			break;
		}

		gtk_adjustment_set_value(scroll, value);
	}

	return TRUE;
}

#endif

void nsbeos_window_resize_event(BView *view, gui_window *g, BMessage *event)
{
	CALLED();
	int32 width;
	int32 height;

	// drop this event if we have at least 2 resize pending
#if 1
	if (atomic_add(&g->pending_resizes, -1) > 1)
		return;
#endif

	if (event->FindInt32("width", &width) < B_OK)
		width = -1;
	if (event->FindInt32("height", &height) < B_OK)
		height = -1;
	width++;
	height++;


#if 0
	hlcache_handle *content;

	content = g->bw->current_content;

	/* reformat or change extent if necessary */
	if ((content) && (g->old_width != width || g->old_height != height)) {
	  	/* Ctrl-resize of a top-level window scales the content size */
#if 0
		if ((g->old_width > 0) && (g->old_width != width) && (!g->bw->parent) &&
				(ro_gui_ctrl_pressed()))
			new_scale = (g->bw->scale * width) / g->old_width;
#endif
		g->bw->reformat_pending = true;
		browser_reformat_pending = true;
	}
	if (g->update_extent || g->old_width != width || g->old_height != height) {
		g->old_width = width;
		g->old_height = height;
		g->update_extent = false;
		gui_window_set_extent(g, width, height);
	}
#endif
		g->bw->reformat_pending = true;
		browser_reformat_pending = true;

	return;
}


void nsbeos_window_moved_event(BView *view, gui_window *g, BMessage *event)
{
	CALLED();

#warning XXX: Invalidate ? 
	if (!view || !view->LockLooper())
		return;
	//view->Invalidate(view->Bounds());
	view->UnlockLooper();

	//g->bw->reformat_pending = true;
	//browser_reformat_pending = true;
	

	return;
}


void nsbeos_reflow_all_windows(void)
{
	for (struct gui_window *g = window_list; g; g = g->next)
		g->bw->reformat_pending = true;

	browser_reformat_pending = true;
}


/**
 * Process pending reformats
 */

void nsbeos_window_process_reformats(void)
{
	struct gui_window *g;

	browser_reformat_pending = false;
	for (g = window_list; g; g = g->next) {
		NSBrowserFrameView *view = g->view;
		if (!g->bw->reformat_pending)
			continue;
		if (!view || !view->LockLooper())
			continue;
		g->bw->reformat_pending = false;
		BRect bounds = view->Bounds();
		view->UnlockLooper();
#warning XXX why - 1 & - 2 !???
		browser_window_reformat(g->bw,
				false,
				bounds.Width() + 1 /* - 2*/,
				bounds.Height() + 1);
	}

#warning WRITEME
#if 0 /* GTK */
	for (g = window_list; g; g = g->next) {
		GtkWidget *widget = GTK_WIDGET(g->viewport);
		if (!g->bw->reformat_pending)
			continue;
		g->bw->reformat_pending = false;
		browser_window_reformat(g->bw,
				false,
				widget->allocation.width - 2,
				widget->allocation.height);
	}
#endif
}


void nsbeos_window_destroy_browser(struct gui_window *g)
{
	browser_window_destroy(g->bw);
}

void gui_window_destroy(struct gui_window *g)
{
	if (!g)
		return;

	if (g->prev)
		g->prev->next = g->next;
	else
		window_list = g->next;

	if (g->next)
		g->next->prev = g->prev;


	LOG(("Destroying gui_window %p", g));
	assert(g != NULL);
	assert(g->bw != NULL);
	LOG(("     Scaffolding: %p", g->scaffold));
	LOG(("     Window name: %s", g->bw->name));

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	BLooper *looper = g->view->Looper();
	/* If we're a top-level gui_window, destroy our scaffold */
	if (g->toplevel) {
		g->view->RemoveSelf();
		delete g->view;
		nsbeos_scaffolding_destroy(g->scaffold);
	} else {
		g->view->RemoveSelf();
		delete g->view;
		looper->Unlock();
	}
	//XXX 
	//looper->Unlock();

#warning FIXME

#if 0 /* GTK */
	/* If we're a top-level gui_window, destroy our scaffold */
	if (g->scrolledwindow == NULL) {
	  	gtk_widget_destroy(GTK_WIDGET(g->viewport));
		nsgtk_scaffolding_destroy(g->scaffold);
	} else {
	  	gtk_widget_destroy(GTK_WIDGET(g->scrolledwindow));
	}
#endif

	free(g);

}

void nsbeos_redraw_caret(struct gui_window *g)
{
	if (g->careth == 0)
		return;

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	nsbeos_current_gc_set(g->view);

	g->view->Invalidate(BRect(g->caretx, g->carety,
				g->caretx, g->carety + g->careth));

	nsbeos_current_gc_set(NULL);
	g->view->UnlockLooper();
}

void gui_window_redraw_window(struct gui_window *g)
{
	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	nsbeos_current_gc_set(g->view);

	g->view->Invalidate();

	nsbeos_current_gc_set(NULL);
	g->view->UnlockLooper();
}

void gui_window_update_box(struct gui_window *g, const struct rect *rect)
{
	hlcache_handle *c = g->bw->current_content;

	if (c == NULL)
		return;

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	nsbeos_current_gc_set(g->view);

//XXX +1 ??
	g->view->Invalidate(BRect(rect->x0, rect->y0,
				   rect->x1 - 1, rect->y1 - 1));

	nsbeos_current_gc_set(NULL);
	g->view->UnlockLooper();
}

bool gui_window_get_scroll(struct gui_window *g, int *sx, int *sy)
{
	CALLED();
	if (g->view == NULL)
		return false;
	if (!g->view->LockLooper())
		return false;

#warning XXX: report to view frame ?
	if (g->view->ScrollBar(B_HORIZONTAL))
		*sx = (int)g->view->ScrollBar(B_HORIZONTAL)->Value();
	if (g->view->ScrollBar(B_VERTICAL))
		*sy = (int)g->view->ScrollBar(B_VERTICAL)->Value();
		
	g->view->UnlockLooper();
#warning WRITEME
#if 0 /* GTK */
	GtkAdjustment *vadj = gtk_viewport_get_vadjustment(g->viewport);
	GtkAdjustment *hadj = gtk_viewport_get_hadjustment(g->viewport);

	assert(vadj);
	assert(hadj);

	*sy = (int)(gtk_adjustment_get_value(vadj));
	*sx = (int)(gtk_adjustment_get_value(hadj));

#endif
	return true;
}

void gui_window_set_scroll(struct gui_window *g, int sx, int sy)
{
	CALLED();
	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

#warning XXX: report to view frame ?
	if (g->view->ScrollBar(B_HORIZONTAL))
		g->view->ScrollBar(B_HORIZONTAL)->SetValue(sx);
	if (g->view->ScrollBar(B_VERTICAL))
		g->view->ScrollBar(B_VERTICAL)->SetValue(sy);
		
	g->view->UnlockLooper();
#warning WRITEME
#if 0 /* GTK */
	GtkAdjustment *vadj = gtk_viewport_get_vadjustment(g->viewport);
	GtkAdjustment *hadj = gtk_viewport_get_hadjustment(g->viewport);
	gdouble vlower, vpage, vupper, hlower, hpage, hupper, x = (double)sx, y = (double)sy;
	
	assert(vadj);
	assert(hadj);
	
	g_object_get(vadj, "page-size", &vpage, "lower", &vlower, "upper", &vupper, NULL);
	g_object_get(hadj, "page-size", &hpage, "lower", &hlower, "upper", &hupper, NULL);
	
	if (x < hlower)
		x = hlower;
	if (x > (hupper - hpage))
		x = hupper - hpage;
	if (y < vlower)
		y = vlower;
	if (y > (vupper - vpage))
		y = vupper - vpage;
	
	gtk_adjustment_set_value(vadj, y);
	gtk_adjustment_set_value(hadj, x);
#endif
}


void gui_window_update_extent(struct gui_window *g)
{
	CALLED();
	if (!g->bw->current_content)
		return;

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	float x_max = content_get_width(g->bw->current_content) * g->bw->scale /* - 1*/;
	float y_max = content_get_height(g->bw->current_content) * g->bw->scale /* - 1*/;
	float x_prop = g->view->Bounds().Width() / x_max;
	float y_prop = g->view->Bounds().Height() / y_max;
	x_max -= g->view->Bounds().Width() + 1;
	y_max -= g->view->Bounds().Height() + 1;
printf("x_max = %f y_max = %f x_prop = %f y_prop = %f\n", x_max, y_max, x_prop, y_prop);
	if (g->view->ScrollBar(B_HORIZONTAL)) {
		g->view->ScrollBar(B_HORIZONTAL)->SetRange(0, x_max);
		g->view->ScrollBar(B_HORIZONTAL)->SetProportion(x_prop);
		g->view->ScrollBar(B_HORIZONTAL)->SetSteps(10, 50);
	}
	if (g->view->ScrollBar(B_VERTICAL)) {
		g->view->ScrollBar(B_VERTICAL)->SetRange(0, y_max);
		g->view->ScrollBar(B_VERTICAL)->SetProportion(y_prop);
		g->view->ScrollBar(B_VERTICAL)->SetSteps(10, 50);
	}

#if 0
	g->view->ResizeTo(
		g->bw->current_content->width * g->bw->scale /* - 1*/,
		g->bw->current_content->height * g->bw->scale /* - 1*/);
#endif

	g->view->UnlockLooper();

#warning WRITEME
#if 0 /* GTK */
	gtk_widget_set_size_request(GTK_WIDGET(g->drawing_area),
			g->bw->current_content->width * g->bw->scale,
			g->bw->current_content->height * g->bw->scale);

	gtk_widget_set_size_request(GTK_WIDGET(g->viewport), 0, 0);
#endif
}

/* some cursors like those in Firefox */
// XXX: move to separate file or resource ?

const uint8 kLinkCursorBits[] = {
	16,		/* cursor size */
	1,		/* bits per pixel */
	2,		/* vertical hot spot */
	2,		/* horizontal hot spot */

	/* data */
	0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x24, 0x00, 0x24, 0x00, 0x13, 0xe0, 0x12, 0x5c, 0x09, 0x2a, 
	0x08, 0x01, 0x3c, 0x21, 0x4c, 0x71, 0x42, 0x71, 0x30, 0xf9, 0x0c, 0xf9, 0x02, 0x02, 0x01, 0x00,

	/* mask */
	0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x1f, 0xe0, 0x1f, 0xfc, 0x0f, 0xfe, 
	0x0f, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x3f, 0xff, 0x0f, 0xff, 0x03, 0xfc, 0x01, 0xe0
};

const uint8 kWatchCursorBits[] = {
	16,		/* cursor size */
	1,		/* bits per pixel */
	0,		/* vertical hot spot */
	1,		/* horizontal hot spot */

	/* data */
	0x70, 0x00, 0x48, 0x00, 0x48, 0x00, 0x27, 0xc0, 0x24, 0xb8, 0x12, 0x54, 0x10, 0x02, 0x78, 0x02, 
	0x98, 0x02, 0x84, 0x02, 0x60, 0x3a, 0x18, 0x46, 0x04, 0x8a, 0x02, 0x92, 0x01, 0x82, 0x00, 0x45,

	/* mask */
	0x70, 0x00, 0x78, 0x00, 0x78, 0x00, 0x3f, 0xc0, 0x3f, 0xf8, 0x1f, 0xfc, 0x1f, 0xfe, 0x7f, 0xfe, 
	0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe, 0x1f, 0xfe, 0x07, 0xfe, 0x03, 0xfe, 0x01, 0xfe, 0x00, 0x7f
};

const uint8 kWatch2CursorBits[] = {
	16,		/* cursor size */
	1,		/* bits per pixel */
	0,		/* vertical hot spot */
	1,		/* horizontal hot spot */

	/* data */
	0x70, 0x00, 0x48, 0x00, 0x48, 0x00, 0x27, 0xc0, 0x24, 0xb8, 0x12, 0x54, 0x10, 0x02, 0x78, 0x02, 
	0x98, 0x02, 0x84, 0x02, 0x60, 0x3a, 0x18, 0x46, 0x04, 0xa2, 0x02, 0x92, 0x01, 0xa2, 0x00, 0x45,

	/* mask */
	0x70, 0x00, 0x78, 0x00, 0x78, 0x00, 0x3f, 0xc0, 0x3f, 0xf8, 0x1f, 0xfc, 0x1f, 0xfe, 0x7f, 0xfe, 
	0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfe, 0x1f, 0xfe, 0x07, 0xfe, 0x03, 0xfe, 0x01, 0xfe, 0x00, 0x7f
};


void gui_window_set_pointer(struct gui_window *g, gui_pointer_shape shape)
{
	BCursor *cursor = NULL;
	bool allocated = false;

	if (g->current_pointer == shape)
		return;

	g->current_pointer = shape;

	switch (shape) {
	case GUI_POINTER_POINT:
		cursor = new BCursor(kLinkCursorBits);
		allocated = true;
#if 0 // it's ugly anyway
#ifdef B_ZETA_VERSION
		cursor = (BCursor *)B_CURSOR_LINK;
#endif
#endif
		break;
	case GUI_POINTER_CARET:
		cursor = (BCursor *)B_CURSOR_I_BEAM;
		break;
	case GUI_POINTER_WAIT:
		cursor = new BCursor(kWatchCursorBits);
		allocated = true;
		break;
	case GUI_POINTER_PROGRESS:
		cursor = new BCursor(kWatch2CursorBits);
		allocated = true;
		break;
#if 0 /* GTK */
	case GUI_POINTER_UP:
		cursortype = GDK_TOP_SIDE;
		break;
	case GUI_POINTER_DOWN:
		cursortype = GDK_BOTTOM_SIDE;
		break;
	case GUI_POINTER_LEFT:
		cursortype = GDK_LEFT_SIDE;
		break;
	case GUI_POINTER_RIGHT:
		cursortype = GDK_RIGHT_SIDE;
		break;
	case GUI_POINTER_LD:
		cursortype = GDK_BOTTOM_LEFT_CORNER;
		break;
	case GUI_POINTER_RD:
		cursortype = GDK_BOTTOM_RIGHT_CORNER;
		break;
	case GUI_POINTER_LU:
		cursortype = GDK_TOP_LEFT_CORNER;
		break;
	case GUI_POINTER_RU:
		cursortype = GDK_TOP_RIGHT_CORNER;
		break;
	case GUI_POINTER_CROSS:
		cursortype = GDK_CROSS;
		break;
	case GUI_POINTER_MOVE:
		cursortype = GDK_FLEUR;
		break;
	case GUI_POINTER_WAIT:
		cursortype = GDK_WATCH;
		break;
	case GUI_POINTER_HELP:
		cursortype = GDK_QUESTION_ARROW;
		break;
	case GUI_POINTER_MENU:
		cursor = nsbeos_create_menu_cursor();
		nullcursor = true;
		break;
	case GUI_POINTER_PROGRESS:
		/* In reality, this needs to be the funky left_ptr_watch
		 * which we can't do easily yet.
		 */
		cursortype = GDK_WATCH;
		break;
	/* The following we're not sure about */
	case GUI_POINTER_NO_DROP:
	case GUI_POINTER_NOT_ALLOWED:
	case GUI_POINTER_DEFAULT:
#endif
	default:
		cursor = (BCursor *)B_CURSOR_SYSTEM_DEFAULT;
		allocated = false;
	}

	if (g->view && g->view->LockLooper()) {
		g->view->SetViewCursor(cursor);
		g->view->UnlockLooper();
	}

	if (allocated)
		delete cursor;
}

void gui_window_hide_pointer(struct gui_window *g)
{
	//XXX no BView::HideCursor... use empty one
}

void gui_window_place_caret(struct gui_window *g, int x, int y, int height)
{
	CALLED();
	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	nsbeos_redraw_caret(g);

	g->caretx = x;
	g->carety = y + 1;
	g->careth = height - 2;

	nsbeos_redraw_caret(g);
	g->view->MakeFocus();

	g->view->UnlockLooper();
}

void gui_window_remove_caret(struct gui_window *g)
{
	int oh = g->careth;

	if (oh == 0)
		return;

	g->careth = 0;

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	nsbeos_current_gc_set(g->view);

	g->view->Invalidate(BRect(g->caretx, g->carety, g->caretx, g->carety + oh));

	nsbeos_current_gc_set(NULL);
	g->view->UnlockLooper();
}

void gui_window_new_content(struct gui_window *g)
{
	if (!g->toplevel)
		return;

	if (g->view == NULL)
		return;
	if (!g->view->LockLooper())
		return;

	// scroll back to top
	g->view->ScrollTo(0,0);

	g->view->UnlockLooper();
}

bool gui_window_scroll_start(struct gui_window *g)
{
	return true;
}

bool gui_window_drag_start(struct gui_window *g, gui_drag_type type,
		const struct rect *rect)
{
	return true;
}

void gui_drag_save_object(gui_save_type type, hlcache_handle *c,
			  struct gui_window *g)
{

}

void gui_drag_save_selection(struct selection *s, struct gui_window *g)
{

}

void gui_start_selection(struct gui_window *g)
{
	current_selection.Truncate(0);
	while (current_selection_textruns.ItemAt(0)) {
		text_run *run = (text_run *)current_selection_textruns.RemoveItem(0L);
		delete run;
	}

	if (!g->view->LockLooper())
		return;

	g->view->MakeFocus();

	g->view->UnlockLooper();
}

void gui_clear_selection(struct gui_window *g)
{
}

void gui_paste_from_clipboard(struct gui_window *g, int x, int y)
{
	BMessage *clip;
	if (be_clipboard->Lock()) {
		clip = be_clipboard->Data();
		if (clip) {
			const char *text;
			int32 textlen;
			if (clip->FindData("text/plain", B_MIME_TYPE, 
				(const void **)&text, &textlen) >= B_OK) {
				browser_window_paste_text(g->bw,text,textlen,true);
			}
		}
		be_clipboard->Unlock();
	}
}

bool gui_empty_clipboard(void)
{
	current_selection.Truncate(0);
	while (current_selection_textruns.ItemAt(0)) {
		text_run *run = (text_run *)current_selection_textruns.RemoveItem(0L);
		delete run;
	}
	return true;
}

bool gui_add_to_clipboard(const char *text, size_t length, bool space)
{
	BString s;
	s.SetTo(text, length);
	current_selection << s;
	if (space)
		current_selection << " ";
  	return true;
}

bool gui_commit_clipboard(void)
{
	BMessage *clip;

	if (current_selection.Length() == 0)
		return true;

	if (be_clipboard->Lock()) {
		be_clipboard->Clear();
		clip = be_clipboard->Data();
		if (clip) {
			clip->AddData("text/plain", B_MIME_TYPE, 
				current_selection.String(), 
				current_selection.Length());
			int arraySize = sizeof(text_run_array) + 
				current_selection_textruns.CountItems() * sizeof(text_run);
			text_run_array *array = (text_run_array *)malloc(arraySize);
			array->count = current_selection_textruns.CountItems();
			for (int i = 0; i < array->count; i++)
				memcpy(&array->runs[i], current_selection_textruns.ItemAt(i), 
					sizeof(text_run));
			clip->AddData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 
				array, arraySize);
			free(array);
			
			gui_empty_clipboard();
			be_clipboard->Commit();
		}
		be_clipboard->Unlock();
	}
	return true;
}

static bool copy_handler(const char *text, size_t length, struct box *box,
		void *handle, const char *whitespace_text,
		size_t whitespace_length)
{
	bool space = false;
	//XXX: handle box->style to StyledEdit / RTF ?
	/* add any whitespace which precedes the text from this box */
	if (whitespace_text) {
		if (!gui_add_to_clipboard(whitespace_text,
				whitespace_length, false)) {
			return false;
		}
	}

	// add a text_run for StyledEdit-like text formating
	if (box && box->style) {
		text_run *run = new text_run;
		BFont font;
		plot_font_style_t style;
		font_plot_style_from_css(box->style, &style);
		nsbeos_style_to_font(font, &style);
		run->offset = current_selection.Length();
		run->font = font;
		css_color csscolor;
		if (css_computed_color(box->style, &csscolor) == CSS_COLOR_COLOR) {
			run->color = nsbeos_rgb_colour(nscss_color_to_ns(csscolor));
		}
		current_selection_textruns.AddItem(run);
		space = box->space != 0;
	}

	/* add the text from this box */
	if (!gui_add_to_clipboard(text, length, space))
		return false;

	return true;
}

bool gui_copy_to_clipboard(struct selection *s)
{
	if (s->defined && selection_traverse(s, copy_handler, NULL))
		gui_commit_clipboard();
	return true;
}


void gui_window_get_dimensions(struct gui_window *g, int *width, int *height,
			       bool scaled)
{
	if (g->view && g->view->LockLooper()) {
		*width = g->view->Bounds().Width() + 1;
		*height = g->view->Bounds().Height() + 1;
		g->view->UnlockLooper();
	}

	if (scaled) {
		*width /= g->bw->scale;
		*height /= g->bw->scale;
	}
}

