/* vi:set ts=8 sts=4 sw=4:
 *
 * VIM - Vi IMproved			by Bram Moolenaar
 *			    BeBox GUI support Copyright 1998 by Olaf Seibert.
 *			    All Rights Reserved.
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 *
 * BeOS GUI.
 *
 * GUI support for the Buzzword Enhanced Operating System.
 *
 * Ported to R4 by Richard Offer <richard@whitequeen.com> Jul 99
 *
 */

/*
 * Structure of the BeOS GUI code:
 *
 * There are 3 threads.
 * 1. The initial thread. In gui_mch_prepare() this gets to run the
 *    BApplication message loop. But before it starts doing that,
 *    it creates thread 2:
 * 2. The main() thread. This thread is created in gui_mch_prepare()
 *    and its purpose in life is to call main(argc, argv) again.
 *    This thread is doing the bulk of the work.
 * 3. Sooner or later, a window is opened by the main() thread. This
 *    causes a second message loop to be created: the window thread.
 *
 * == alternatively ===
 *
 * #if RUN_BAPPLICATION_IN_NEW_THREAD...
 *
 * 1. The initial thread. In gui_mch_prepare() this gets to spawn
 *    thread 2. After doing that, it returns to main() to do the
 *    bulk of the work, being the main() thread.
 * 2. Runs the BApplication.
 * 3. The window thread, just like in the first case.
 *
 * This second alternative is cleaner from Vim's viewpoint. However,
 * the BeBook seems to assume everywhere that the BApplication *must*
 * run in the initial thread. So perhaps doing otherwise is very wrong.
 *
 * However, from a B_SINGLE_LAUNCH viewpoint, the first is better.
 * If Vim is marked "Single Launch" in its application resources,
 * and a file is dropped on the Vim icon, and another Vim is already
 * running, the file is passed on to the earlier Vim. This happens
 * in BApplication::Run(). So we want Vim to terminate if
 * BApplication::Run() terminates. (See the BeBook, on BApplication.
 * However, it seems that the second copy of Vim isn't even started
 * in this case... which is for the better since I wouldn't know how
 * to detect this case.)
 *
 * Communication between these threads occurs mostly by translating
 * BMessages that come in and posting an appropriate translation on
 * the VDCMP (Vim Direct Communication Message Port). Therefore the
 * actions required for keypresses and window resizes, etc, are mostly
 * performed in the main() thread.
 *
 * A notable exception to this is the Draw() event. The redrawing of
 * the window contents is performed asynchronously from the window
 * thread. To make this work correctly, a locking protocol is used when
 * any thread is accessing the essential variables that are used by
 * the window thread.
 *
 * This locking protocol consists of locking Vim's window. This is both
 * convenient and necessary.
 */
extern "C" {

#define new		xxx_new_xxx

#include <float.h>
#include <assert.h>
#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

#undef new

}	/* extern "C" */

/* ---------------- start of header part ---------------- */

#include <be/app/MessageQueue.h>
#include <be/app/Clipboard.h>
#include <be/kernel/OS.h>
#include <be/support/Beep.h>
#include <be/interface/View.h>
#include <be/interface/Window.h>
#include <be/interface/MenuBar.h>
#include <be/interface/MenuItem.h>
#include <be/interface/ScrollBar.h>
#include <be/interface/Region.h>
#include <be/interface/Screen.h>
#include <be/storage/Path.h>
#include <be/storage/Directory.h>
#include <be/storage/Entry.h>
#include <be/app/Application.h>
#include <be/support/Debug.h>

/*
 * The macro B_PIXEL_ALIGNMENT shows us which version
 * of the header files we're using.
 */
#if defined(B_PIXEL_ALIGNMENT)
#define HAVE_R3_OR_LATER    1
#else
#define HAVE_R3_OR_LATER    0
#endif

class VimApp;
class VimFormView;
class VimTextAreaView;
class VimWindow;

extern key_map *keyMap;
extern char *keyMapChars;

extern int main(int argc, char **argv);

#ifndef B_MAX_PORT_COUNT
#define B_MAX_PORT_COUNT    100
#endif

/*
 * VimApp seems comparable to the X "vimShell"
 */
class VimApp: public BApplication
{
    typedef BApplication Inherited;
public:
    VimApp(const char *appsig);
    ~VimApp();

    // callbacks:
#if 0
    virtual void DispatchMessage(BMessage *m, BHandler *h)
    {
	m->PrintToStream();
	Inherited::DispatchMessage(m, h);
    }
#endif
    virtual void ReadyToRun();
    virtual void ArgvReceived(int32 argc, char **argv);
    virtual void RefsReceived(BMessage *m);
    virtual bool QuitRequested();

    static void SendRefs(BMessage *m, bool changedir);
private:
};

class VimWindow: public BWindow
{
    typedef BWindow Inherited;
public:
    VimWindow();
    ~VimWindow();

    virtual void DispatchMessage(BMessage *m, BHandler *h);
    virtual void WindowActivated(bool active);
    virtual bool QuitRequested();

    VimFormView		*formView;

private:
    void init();

};

class VimFormView: public BView
{
    typedef BView Inherited;
public:
    VimFormView(BRect frame);
    ~VimFormView();

    // callbacks:
    virtual void AllAttached();
    virtual void FrameResized(float new_width, float new_height);

#define MENUBAR_MARGIN	1
    float MenuHeight() const
	{ return menuBar ? menuBar->Frame().Height() + MENUBAR_MARGIN: 0; }
    BMenuBar *MenuBar() const
	{ return menuBar; }

private:
    void init(BRect);

    BMenuBar		*menuBar;
    VimTextAreaView	*textArea;
};

class VimTextAreaView: public BView
{
    typedef BView Inherited;
public:
    VimTextAreaView(BRect frame);
    ~VimTextAreaView();

    // callbacks:
    virtual void Draw(BRect updateRect);
    virtual void KeyDown(const char *bytes, int32 numBytes);
    virtual void MouseDown(BPoint point);
    virtual void MouseUp(BPoint point);
    virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message);
    virtual void MessageReceived(BMessage *m);

    // own functions:
    int mchInitFont(char_u *name);
    void mchDrawString(int row, int col, char_u *s, int len, int flags);
    void mchClearBlock(int row1, int col1, int row2, int col2);
    void mchClearAll();
    void mchDeleteLines(int row, int num_lines);
    void mchInsertLines(int row, int num_lines);

    static void guiSendMouseEvent(int button, int x, int y, int repeated_click, int_u modifiers);
    static void guiBlankMouse(bool should_hide);
    static int_u mouseModifiersToVim(int32 beModifiers);

    int32 mouseDragEventCount;

private:
    void init(BRect);

    int_u	    vimMouseButton;
    int_u	    vimMouseModifiers;
};

class VimScrollBar: public BScrollBar
{
    typedef BScrollBar Inherited;
public:
    VimScrollBar(scrollbar_T *gsb, orientation posture);
    ~VimScrollBar();

    virtual void ValueChanged(float newValue);
    virtual void MouseUp(BPoint where);
    void SetValue(float newval);
    scrollbar_T *getGsb()
	{ return gsb; }

    int32	    scrollEventCount;

private:
    scrollbar_T *gsb;
    float	ignoreValue;
};


/*
 * For caching the fonts that are used;
 * Vim seems rather sloppy in this regard.
 */
class VimFont: public BFont
{
    typedef BFont Inherited;
public:
    VimFont();
    VimFont(const VimFont *rhs);
    VimFont(const BFont *rhs);
    VimFont(const VimFont &rhs);
    ~VimFont();

    VimFont *next;
    int refcount;
    char_u *name;

private:
    void init();
};

/* ---------------- end of GUI classes ---------------- */

struct MainArgs {
    int		 argc;
    char	**argv;
};

/*
 * These messages are copied through the VDCMP.
 * Therefore they ought not to have anything fancy.
 * They must be of POD type (Plain Old Data)
 * as the C++ standard calls them.
 */

#define	KEY_MSG_BUFSIZ	7
#if KEY_MSG_BUFSIZ < MAX_KEY_CODE_LEN
#error Increase KEY_MSG_BUFSIZ!
#endif

struct VimKeyMsg {
    char_u	length;
    char_u	chars[KEY_MSG_BUFSIZ];	/* contains Vim encoding */
};

struct VimResizeMsg {
    int		width;
    int		height;
};

struct VimScrollBarMsg {
    VimScrollBar *sb;
    long	value;
    int		stillDragging;
};

struct VimMenuMsg {
    vimmenu_T	*guiMenu;
};

struct VimMouseMsg {
    int		button;
    int		x;
    int		y;
    int		repeated_click;
    int_u	modifiers;
};

struct VimFocusMsg {
    bool	active;
};

struct VimRefsMsg {
    BMessage   *message;
    bool	changedir;
};

struct VimMsg {
    enum VimMsgType {
	Key, Resize, ScrollBar, Menu, Mouse, Focus, Refs
    };

    union {
	struct VimKeyMsg	Key;
	struct VimResizeMsg	NewSize;
	struct VimScrollBarMsg	Scroll;
	struct VimMenuMsg	Menu;
	struct VimMouseMsg	Mouse;
	struct VimFocusMsg	Focus;
	struct VimRefsMsg	Refs;
    } u;
};

#define RGB(r, g, b)	((char_u)(r) << 16 | (char_u)(g) << 8 | (char_u)(b) << 0)
#define GUI_TO_RGB(g)	{ (g) >> 16, (g) >> 8, (g) >> 0, 255 }

/* ---------------- end of header part ---------------- */

static struct specialkey
{
    uint16  BeKeys;
#define KEY(a,b)	((a)<<8|(b))
#define K(a)		KEY(0,a)		    // for ASCII codes
#define F(b)		KEY(1,b)		    // for scancodes
    char_u  vim_code0;
    char_u  vim_code1;
} special_keys[] =
{
    {K(B_UP_ARROW),	    'k', 'u'},
    {K(B_DOWN_ARROW),	    'k', 'd'},
    {K(B_LEFT_ARROW),	    'k', 'l'},
    {K(B_RIGHT_ARROW),	    'k', 'r'},
    {K(B_BACKSPACE),	    'k', 'b'},
    {K(B_INSERT),	    'k', 'I'},
    {K(B_DELETE),	    'k', 'D'},
    {K(B_HOME),		    'k', 'h'},
    {K(B_END),		    '@', '7'},
    {K(B_PAGE_UP),	    'k', 'P'},	    /* XK_Prior */
    {K(B_PAGE_DOWN),	    'k', 'N'},	    /* XK_Next, */

#define FIRST_FUNCTION_KEY  11
    {F(B_F1_KEY),	    'k', '1'},
    {F(B_F2_KEY),	    'k', '2'},
    {F(B_F3_KEY),	    'k', '3'},
    {F(B_F4_KEY),	    'k', '4'},
    {F(B_F5_KEY),	    'k', '5'},
    {F(B_F6_KEY),	    'k', '6'},
    {F(B_F7_KEY),	    'k', '7'},
    {F(B_F8_KEY),	    'k', '8'},
    {F(B_F9_KEY),	    'k', '9'},
    {F(B_F10_KEY),	    'k', ';'},

    {F(B_F11_KEY),	    'F', '1'},
    {F(B_F12_KEY),	    'F', '2'},
//  {XK_F13,		    'F', '3'},		/* would be print screen/ */
						/* sysreq */
    {F(0x0F),		    'F', '4'},		/* scroll lock */
    {F(0x10),		    'F', '5'},		/* pause/break */
//  {XK_F16,	    'F', '6'},
//  {XK_F17,	    'F', '7'},
//  {XK_F18,	    'F', '8'},
//  {XK_F19,	    'F', '9'},
//  {XK_F20,	    'F', 'A'},
//
//  {XK_F21,	    'F', 'B'},
//  {XK_F22,	    'F', 'C'},
//  {XK_F23,	    'F', 'D'},
//  {XK_F24,	    'F', 'E'},
//  {XK_F25,	    'F', 'F'},
//  {XK_F26,	    'F', 'G'},
//  {XK_F27,	    'F', 'H'},
//  {XK_F28,	    'F', 'I'},
//  {XK_F29,	    'F', 'J'},
//  {XK_F30,	    'F', 'K'},
//
//  {XK_F31,	    'F', 'L'},
//  {XK_F32,	    'F', 'M'},
//  {XK_F33,	    'F', 'N'},
//  {XK_F34,	    'F', 'O'},
//  {XK_F35,	    'F', 'P'},	    /* keysymdef.h defines up to F35 */

//  {XK_Help,	    '%', '1'},	    /* XK_Help */
    {F(B_PRINT_KEY),	    '%', '9'},

#if 0
    /* Keypad keys: */
    {F(0x48),	    'k', 'l'},	    /* XK_KP_Left */
    {F(0x4A),	    'k', 'r'},	    /* XK_KP_Right */
    {F(0x38),	    'k', 'u'},	    /* XK_KP_Up */
    {F(0x59),	    'k', 'd'},	    /* XK_KP_Down */
    {F(0x64),	    'k', 'I'},	    /* XK_KP_Insert */
    {F(0x65),	    'k', 'D'},	    /* XK_KP_Delete */
    {F(0x37),	    'k', 'h'},	    /* XK_KP_Home */
    {F(0x58),	    '@', '7'},	    /* XK_KP_End */
    {F(0x39),	    'k', 'P'},	    /* XK_KP_Prior */
    {F(0x60),	    'k', 'N'},	    /* XK_KP_Next */
    {F(0x49),	    '&', '8'},	    /* XK_Undo, keypad 5 */
#endif

    /* End of list marker: */
    {0,			    0, 0}
};

#define NUM_SPECIAL_KEYS    (sizeof(special_keys)/sizeof(special_keys[0]))

/* ---------------- VimApp ---------------- */

    static void
docd(BPath &path)
{
    mch_chdir(path.Path());
    /* Do this to get the side effects of a :cd command */
    do_cmdline_cmd((char_u *)"cd .");
}

/*
 * Really handle dropped files and folders.
 */
    static void
RefsReceived(BMessage *m, bool changedir)
{
    uint32 type;
    int32 count;

    //m->PrintToStream();
    switch (m->what) {
    case B_REFS_RECEIVED:
    case B_SIMPLE_DATA:
	m->GetInfo("refs", &type, &count);
	if (type != B_REF_TYPE)
	    goto bad;
	break;
    case B_ARGV_RECEIVED:
	m->GetInfo("argv", &type, &count);
	if (type != B_STRING_TYPE)
	    goto bad;
	if (changedir) {
	    char *dirname;
	    if (m->FindString("cwd", (const char **) &dirname) == B_OK) {
		chdir(dirname);
		do_cmdline_cmd((char_u *)"cd .");
	    }
	}
	break;
    default:
    bad:
	//fprintf(stderr, "bad!\n");
	delete m;
	return;
    }

#ifdef FEAT_VISUAL
    reset_VIsual();
#endif

    char_u  **fnames;
    fnames = (char_u **) alloc(count * sizeof(char_u *));
    int fname_index = 0;

    switch (m->what) {
    case B_REFS_RECEIVED:
    case B_SIMPLE_DATA:
	//fprintf(stderr, "case B_REFS_RECEIVED\n");
	for (int i = 0; i < count; ++i)
	{
	    entry_ref ref;
	    if (m->FindRef("refs", i, &ref) == B_OK) {
		BEntry entry(&ref, false);
		BPath path;
		entry.GetPath(&path);

		/* Change to parent directory? */
		if (changedir) {
		    BPath parentpath;
		    path.GetParent(&parentpath);
		    docd(parentpath);
		}

		/* Is it a directory? If so, cd into it. */
		BDirectory bdir(&ref);
		if (bdir.InitCheck() == B_OK) {
		    /* don't cd if we already did it */
		    if (!changedir)
			docd(path);
		} else {
		    mch_dirname(IObuff, IOSIZE);
		    char_u *fname = shorten_fname((char_u *)path.Path(), IObuff);
		    if (fname == NULL)
			fname = (char_u *)path.Path();
		    fnames[fname_index++] = vim_strsave(fname);
		    //fprintf(stderr, "%s\n", fname);
		}

		/* Only do it for the first file/dir */
		changedir = false;
	    }
	}
	break;
    case B_ARGV_RECEIVED:
	//fprintf(stderr, "case B_ARGV_RECEIVED\n");
	for (int i = 1; i < count; ++i)
	{
	    char *fname;

	    if (m->FindString("argv", i, (const char **) &fname) == B_OK) {
		fnames[fname_index++] = vim_strsave((char_u *)fname);
	    }
	}
	break;
    default:
	//fprintf(stderr, "case default\n");
	break;
    }

    delete m;

    /* Handle the drop, :edit to get to the file */
    if (fname_index > 0) {
	handle_drop(fname_index, fnames, FALSE);

	/* Update the screen display */
	update_screen(NOT_VALID);
	setcursor();
	out_flush();
    } else {
	vim_free(fnames);
    }
}

VimApp::VimApp(const char *appsig):
    BApplication(appsig)
{
}

VimApp::~VimApp()
{
}

    void
VimApp::ReadyToRun()
{
    /*
     * Apparently signals are inherited by the created thread -
     * disable the most annoying ones.
     */
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
}

    void
VimApp::ArgvReceived(int32 arg_argc, char **arg_argv)
{
    if (!IsLaunching()) {
	/*
	 * This can happen if we are set to Single or Exclusive
	 * Launch. Be nice and open the file(s).
	 */
	if (gui.vimWindow)
	    gui.vimWindow->Minimize(false);
	BMessage *m = CurrentMessage();
	DetachCurrentMessage();
	SendRefs(m, true);
    }
}

    void
VimApp::RefsReceived(BMessage *m)
{
    /* Horrible hack!!! XXX XXX XXX
     * The real problem is that b_start_ffc is set too late for
     * the initial empty buffer. As a result the window will be
     * split instead of abandoned.
     */
    int limit = 15;
    while (--limit >= 0 && (curbuf == NULL || curbuf->b_start_ffc == 0))
	snooze(100000);    // 0.1 s
    if (gui.vimWindow)
	gui.vimWindow->Minimize(false);
    DetachCurrentMessage();
    SendRefs(m, true);
}

/*
 * Pass a BMessage on to the main() thread.
 * Caller must have detached the message.
 */
    void
VimApp::SendRefs(BMessage *m, bool changedir)
{
    VimRefsMsg rm;
    rm.message = m;
    rm.changedir = changedir;

    write_port(gui.vdcmp, VimMsg::Refs, &rm, sizeof(rm));
    // calls ::RefsReceived
}

    bool
VimApp::QuitRequested()
{
    (void)Inherited::QuitRequested();
    return false;
}

/* ---------------- VimWindow ---------------- */

VimWindow::VimWindow():
    BWindow(BRect(40, 40, 150, 150),
	    "Vim",
	    B_TITLED_WINDOW,
	    0,
	    B_CURRENT_WORKSPACE)

{
    init();
}

VimWindow::~VimWindow()
{
    if (formView) {
	RemoveChild(formView);
	delete formView;
    }
    gui.vimWindow = NULL;
}

    void
VimWindow::init()
{
    /* Attach the VimFormView */
    formView = new VimFormView(Bounds());
    if (formView != NULL) {
	AddChild(formView);
    }
}

    void
VimWindow::DispatchMessage(BMessage *m, BHandler *h)
{
    /*
     * Route B_MOUSE_UP messages to MouseUp(), in
     * a manner that should be compatible with the
     * intended future system behaviour.
     */
    switch (m->what) {
    case B_MOUSE_UP:
	// if (!h) h = PreferredHandler();
// gcc isn't happy without this extra set of braces, complains about
// jump to case label crosses init of 'class BView * v'
// richard@whitequeen.com jul 99
	{
	BView *v = dynamic_cast<BView *>(h);
	if (v) {
	    //m->PrintToStream();
	    BPoint where;
	    m->FindPoint("where", &where);
	    v->MouseUp(where);
	} else {
	    Inherited::DispatchMessage(m, h);
	}
	}
	break;
    default:
	Inherited::DispatchMessage(m, h);
    }
}

    void
VimWindow::WindowActivated(bool active)
{
    Inherited::WindowActivated(active);
    /* the textArea gets the keyboard action */
    if (active && gui.vimTextArea)
	gui.vimTextArea->MakeFocus(true);

    struct VimFocusMsg fm;
    fm.active = active;

    write_port(gui.vdcmp, VimMsg::Focus, &fm, sizeof(fm));
}

    bool
VimWindow::QuitRequested()
{
    struct VimKeyMsg km;
    km.length = 5;
    memcpy((char *)km.chars, "\033:qa\r", km.length);

    write_port(gui.vdcmp, VimMsg::Key, &km, sizeof(km));

    return false;
}

/* ---------------- VimFormView ---------------- */

VimFormView::VimFormView(BRect frame):
    BView(frame, "VimFormView", B_FOLLOW_ALL_SIDES,
	    B_WILL_DRAW | B_FRAME_EVENTS),
    menuBar(NULL),
    textArea(NULL)
{
    init(frame);
}

VimFormView::~VimFormView()
{
    if (menuBar) {
	RemoveChild(menuBar);
#ifdef never
	// deleting the menuBar leads to SEGV on exit
	// richard@whitequeen.com Jul 99
	delete menuBar;
#endif
    }
    if (textArea) {
	RemoveChild(textArea);
	delete textArea;
    }
    gui.vimForm = NULL;
}

    void
VimFormView::init(BRect frame)
{
    menuBar = new BMenuBar(BRect(0,0,-MENUBAR_MARGIN,-MENUBAR_MARGIN),
	    "VimMenuBar");

    AddChild(menuBar);

    BRect remaining = frame;
    textArea = new VimTextAreaView(remaining);
    AddChild(textArea);
    /* The textArea will be resized later when menus are added */

    gui.vimForm = this;
}

    void
VimFormView::AllAttached()
{
    /*
     * Apparently signals are inherited by the created thread -
     * disable the most annoying ones.
     */
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);

    if (menuBar && textArea) {
	/*
	 * Resize the textArea to fill the space left over by the menu.
	 * This is somewhat futile since it will be done again once
	 * menus are added to the menu bar.
	 */
	BRect remaining = Bounds();
	remaining.top = MenuHeight();
	textArea->ResizeTo(remaining.Width(), remaining.Height());
	textArea->MoveTo(remaining.left, remaining.top);

#ifdef FEAT_MENU
	menuBar->ResizeTo(remaining.right, remaining.top);
	gui.menu_height = (int) remaining.top;
#endif
    }
    Inherited::AllAttached();
}

    void
VimFormView::FrameResized(float new_width, float new_height)
{
    BWindow *w = Window();
#if 1
    /*
     * Look if there are more resize messages in the queue.
     * If so, ignore this one. The later one will be handled
     * eventually.
     */
    BMessageQueue *q = w->MessageQueue();
    if (q->FindMessage(B_VIEW_RESIZED, 0) != NULL) {
	return;
    }
#endif
    new_width += 1;	    // adjust from width to number of pixels occupied
    new_height += 1;

#if !HAVE_R3_OR_LATER
    int adjust_h, adjust_w;

    adjust_w = ((int)new_width - gui_get_base_width()) % gui.char_width;
    adjust_h = ((int)new_height - gui_get_base_height()) % gui.char_height;

    if (adjust_w > 0 || adjust_h > 0) {
	/*
	 * This will generate a new FrameResized() message.
	 * If we're running R3 or later, SetWindowAlignment() should make
	 * sure that this does not happen.
	 */
	w->ResizeBy(-adjust_w, -adjust_h);

	return;
    }
#endif

    struct VimResizeMsg sm;
    sm.width = (int) new_width;
    sm.height = (int) new_height;

    write_port(gui.vdcmp, VimMsg::Resize, &sm, sizeof(sm));
    // calls gui_resize_shell(new_width, new_height);

    return;

    /*
     * The area below the vertical scrollbar is erased to the colour
     * set with SetViewColor() automatically, because we had set
     * B_WILL_DRAW. Resizing the window tight around the vertical
     * scroll bar also helps to avoid debris.
     */
}

/* ---------------- VimTextAreaView ---------------- */

VimTextAreaView::VimTextAreaView(BRect frame):
    BView(frame, "VimTextAreaView", B_FOLLOW_ALL_SIDES,
	    B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
    mouseDragEventCount(0)
{
    init(frame);
}

VimTextAreaView::~VimTextAreaView()
{
    gui.vimTextArea = NULL;
}

    void
VimTextAreaView::init(BRect frame)
{
    /* set up global var for fast access */
    gui.vimTextArea = this;

    /*
     * Tell the app server not to erase the view: we will
     * fill it in completely by ourselves.
     * (Does this really work? Even if not, it won't harm either.)
     */
    SetViewColor(B_TRANSPARENT_32_BIT);
#define PEN_WIDTH   1
    SetPenSize(PEN_WIDTH);
}

    void
VimTextAreaView::Draw(BRect updateRect)
{
    /*
     * XXX Other ports call here:
     * out_flush();	     * make sure all output has been processed *
     * but we can't do that, since it involves too much information
     * that is owned by other threads...
     */

    /*
     *  No need to use gui.vimWindow->Lock(): we are locked already.
     *  However, it would not hurt.
     */
    gui_redraw((int) updateRect.left, (int) updateRect.top,
	    (int) (updateRect.Width() + PEN_WIDTH), (int) (updateRect.Height() + PEN_WIDTH));

    /* Clear the border areas if needed */
    rgb_color rgb = GUI_TO_RGB(gui.back_pixel);
    SetLowColor(rgb);

    if (updateRect.left < FILL_X(0))	// left border
	FillRect(BRect(updateRect.left, updateRect.top,
		       FILL_X(0)-PEN_WIDTH, updateRect.bottom), B_SOLID_LOW);
    if (updateRect.top < FILL_Y(0))	// top border
	FillRect(BRect(updateRect.left, updateRect.top,
		       updateRect.right, FILL_Y(0)-PEN_WIDTH), B_SOLID_LOW);
    if (updateRect.right >= FILL_X(Columns)) // right border
	FillRect(BRect(FILL_X((int)Columns), updateRect.top,
		       updateRect.right, updateRect.bottom), B_SOLID_LOW);
    if (updateRect.bottom >= FILL_Y(Rows))   // bottom border
	FillRect(BRect(updateRect.left, FILL_Y((int)Rows),
		       updateRect.right, updateRect.bottom), B_SOLID_LOW);
}

    void
VimTextAreaView::KeyDown(const char *bytes, int32 numBytes)
{
    struct VimKeyMsg km;
    char_u *dest = km.chars;

    BMessage *msg = Window()->CurrentMessage();
    assert(msg);
    //msg->PrintToStream();

    /*
     * Convert special keys to Vim codes.
     * I think it is better to do it in the window thread
     * so we use at least a little bit of the potential
     * of our 2 CPUs. Besides, due to the fantastic mapping
     * of special keys to UTF-8, we have quite some work to
     * do...
     * TODO: I'm not quite happy with detection of special
     * keys. Perhaps I should use scan codes after all...
     */
    if (numBytes > 1) {
	/* This cannot be a special key */
	if (numBytes > KEY_MSG_BUFSIZ)
	    numBytes = KEY_MSG_BUFSIZ;	    // should never happen... ???
	km.length = numBytes;
	memcpy((char *)dest, bytes, numBytes);
    } else {
	int32 scancode = 0;
	msg->FindInt32("key", &scancode);

	int32 beModifiers = 0;
	msg->FindInt32("modifiers", &beModifiers);

	char_u string[3];
	int len = 0;
	km.length = 0;

	bool canHaveVimModifiers = false;

	/*
	 * For normal, printable ASCII characters, don't look them up
	 * to check if they might be a special key. They aren't.
	 */
	assert(B_BACKSPACE <= 0x20);
	assert(B_DELETE == 0x7F);
	if (((char_u)bytes[0] <= 0x20 || (char_u)bytes[0] == 0x7F) &&
		numBytes == 1) {
	    /*
	     * Due to the great nature of Be's mapping of special keys,
	     * viz. into the range of the control characters,
	     * we can only be sure it is *really* a special key if
	     * if it is special without using ctrl. So, only if ctrl is
	     * used, we need to check it unmodified.
	     */
	    if (beModifiers & B_CONTROL_KEY) {
		int index = keyMap->normal_map[scancode];
		int newNumBytes = keyMapChars[index];
		char_u *newBytes = (char_u *)&keyMapChars[index + 1];

		/*
		 * Check if still special without the control key.
		 * This is needed for BACKSPACE: that key does produce
		 * different values with modifiers (DEL).
		 * Otherwise we could simply have checked for equality.
		 */
		if (newNumBytes != 1 || (*newBytes > 0x20 &&
					 *newBytes != 0x7F )) {
		    goto notspecial;
		}
		bytes = (char *)newBytes;
	    }
	    canHaveVimModifiers = true;

	    uint16 beoskey;
	    int first, last;

	    /*
	     * If numBytes == 0 that probably always indicates a special key.
	     * (does not happen yet)
	     */
	    if (numBytes == 0 || bytes[0] == B_FUNCTION_KEY) {
		beoskey = F(scancode);
		first = FIRST_FUNCTION_KEY;
		last = NUM_SPECIAL_KEYS;
	    } else if (*bytes == '\n' && scancode == 0x47) {
		 /* remap the (non-keypad) ENTER key from \n to \r. */
		string[0] = '\r';
		len = 1;
		first = last = 0;
	    } else {
		beoskey = K(bytes[0]);
		first = 0;
		last = FIRST_FUNCTION_KEY;
	    }

	    for (int i = first; i < last; i++) {
		if (special_keys[i].BeKeys == beoskey) {
		    string[0] = CSI;
		    string[1] = special_keys[i].vim_code0;
		    string[2] = special_keys[i].vim_code1;
		    len = 3;
		}
	    }
	}
    notspecial:
	if (len == 0) {
	    string[0] = bytes[0];
	    len = 1;
	}

	/* Special keys (and a few others) may have modifiers */
#if 0
	if (len == 3 ||
		bytes[0] == B_SPACE || bytes[0] == B_TAB ||
		bytes[0] == B_RETURN || bytes[0] == '\r' ||
		bytes[0] == B_ESCAPE)
#else
	if (canHaveVimModifiers)
#endif
	{
	    int modifiers;
	    modifiers = 0;
	    if (beModifiers & B_SHIFT_KEY)
		modifiers |= MOD_MASK_SHIFT;
	    if (beModifiers & B_CONTROL_KEY)
		modifiers |= MOD_MASK_CTRL;
	    if (beModifiers & B_OPTION_KEY)
		modifiers |= MOD_MASK_ALT;

	    /*
	     * For some keys a shift modifier is translated into another key
	     * code.  Do we need to handle the case where len != 1 and
	     * string[0] != CSI? (Not for BeOS, since len == 3 implies
	     * string[0] == CSI...)
	     */
	    int key;
	    if (string[0] == CSI && len == 3)
		key = TO_SPECIAL(string[1], string[2]);
	    else
		key = string[0];
	    key = simplify_key(key, &modifiers);
	    if (IS_SPECIAL(key))
	    {
		string[0] = CSI;
		string[1] = K_SECOND(key);
		string[2] = K_THIRD(key);
		len = 3;
	    }
	    else
	    {
		string[0] = key;
		len = 1;
	    }

	    if (modifiers)
	    {
		*dest++ = CSI;
		*dest++ = KS_MODIFIER;
		*dest++ = modifiers;
		km.length = 3;
	    }
	}
	memcpy((char *)dest, string, len);
	km.length += len;
    }

    write_port(gui.vdcmp, VimMsg::Key, &km, sizeof(km));

    /*
     * blank out the pointer if necessary
     */
    if (p_mh && !gui.pointer_hidden)
    {
	guiBlankMouse(true);
	gui.pointer_hidden = TRUE;
    }
}
    void
VimTextAreaView::guiSendMouseEvent(
    int	    button,
    int	    x,
    int	    y,
    int	    repeated_click,
    int_u   modifiers)
{
    VimMouseMsg mm;

    mm.button = button;
    mm.x = x;
    mm.y = y;
    mm.repeated_click = repeated_click;
    mm.modifiers = modifiers;

    write_port(gui.vdcmp, VimMsg::Mouse, &mm, sizeof(mm));
    // calls gui_send_mouse_event()

    /*
     * if our pointer is currently hidden, then we should show it.
     */
    if (gui.pointer_hidden)
    {
	guiBlankMouse(false);
	gui.pointer_hidden = FALSE;
    }
}

    void
VimTextAreaView::guiBlankMouse(bool should_hide)
{
    if (should_hide) {
	//gui.vimApp->HideCursor();
	gui.vimApp->ObscureCursor();
	/*
	 * ObscureCursor() would even be easier, but then
	 * Vim's idea of mouse visibility does not necessarily
	 * correspond to reality.
	 */
    } else {
	//gui.vimApp->ShowCursor();
    }
}

    int_u
VimTextAreaView::mouseModifiersToVim(int32 beModifiers)
{
    int_u vim_modifiers = 0x0;

    if (beModifiers & B_SHIFT_KEY)
	vim_modifiers |= MOUSE_SHIFT;
    if (beModifiers & B_CONTROL_KEY)
	vim_modifiers |= MOUSE_CTRL;
    if (beModifiers & B_OPTION_KEY)	    /* Alt or Meta key */
	vim_modifiers |= MOUSE_ALT;

    return vim_modifiers;
}

    void
VimTextAreaView::MouseDown(BPoint point)
{
    BMessage *m = Window()->CurrentMessage();
    assert(m);

    int32 buttons = 0;
    m->FindInt32("buttons", &buttons);

    int vimButton;

    if (buttons & B_PRIMARY_MOUSE_BUTTON)
	vimButton = MOUSE_LEFT;
    else if (buttons & B_SECONDARY_MOUSE_BUTTON)
	vimButton = MOUSE_RIGHT;
    else if (buttons & B_TERTIARY_MOUSE_BUTTON)
	vimButton = MOUSE_MIDDLE;
    else
	return;			/* Unknown button */

    vimMouseButton = 1;		/* don't care which one */

    /* Handle multiple clicks */
    int32 clicks = 0;
    m->FindInt32("clicks", &clicks);

    int32 modifiers = 0;
    m->FindInt32("modifiers", &modifiers);

    vimMouseModifiers = mouseModifiersToVim(modifiers);

    guiSendMouseEvent(vimButton, point.x, point.y,
	    clicks > 1 /* = repeated_click*/, vimMouseModifiers);
}

    void
VimTextAreaView::MouseUp(BPoint point)
{
    vimMouseButton = 0;

    BMessage *m = Window()->CurrentMessage();
    assert(m);
    //m->PrintToStream();

    int32 modifiers = 0;
    m->FindInt32("modifiers", &modifiers);

    vimMouseModifiers = mouseModifiersToVim(modifiers);

    guiSendMouseEvent(MOUSE_RELEASE, point.x, point.y,
	    0 /* = repeated_click*/, vimMouseModifiers);

    Inherited::MouseUp(point);
}

    void
VimTextAreaView::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
{
    /*
     * if our pointer is currently hidden, then we should show it.
     */
    if (gui.pointer_hidden)
    {
	guiBlankMouse(false);
	gui.pointer_hidden = FALSE;
    }

    if (!vimMouseButton)    /* could also check m->"buttons" */
	return;

    atomic_add(&mouseDragEventCount, 1);

    /* Don't care much about "transit" */
    guiSendMouseEvent(MOUSE_DRAG, point.x, point.y, 0, vimMouseModifiers);
}

    void
VimTextAreaView::MessageReceived(BMessage *m)
{
    switch (m->what) {
    case 'menu':
	{
	    VimMenuMsg mm;
	    mm.guiMenu = NULL;	/* in case no pointer in msg */
	    m->FindPointer("VimMenu", (void **)&mm.guiMenu);

	    write_port(gui.vdcmp, VimMsg::Menu, &mm, sizeof(mm));
	}
	break;
    default:
	if (m->WasDropped()) {
	    BWindow *w = Window();
	    w->DetachCurrentMessage();
	    w->Minimize(false);
	    VimApp::SendRefs(m, (modifiers() & B_SHIFT_KEY) != 0);
	} else {
	    Inherited::MessageReceived(m);
	}
	break;
    }
}

    int
VimTextAreaView::mchInitFont(char_u *name)
{
    VimFont *newFont = (VimFont *)gui_mch_get_font(name, 0);

    gui.norm_font = (GuiFont)newFont;
    gui_mch_set_font((GuiFont)newFont);
    if (name)
	hl_set_font_name(name);

    SetDrawingMode(B_OP_COPY);

    /*
     * Try to load other fonts for bold, italic, and bold-italic.
     * We should also try to work out what font to use for these when they are
     * not specified by X resources, but we don't yet.
     */

    return OK;
}

    void
VimTextAreaView::mchDrawString(int row, int col, char_u *s, int len, int flags)
{
    /*
     * First we must erase the area, because DrawString won't do
     * that for us. XXX Most of the time this is a waste of effort
     * since the bachground has been erased already... DRAW_TRANSP
     * should be set when appropriate!!!
     * (Rectangles include the bottom and right edge)
     */
    if (!(flags & DRAW_TRANSP)) {
	BRect r(FILL_X(col), FILL_Y(row),
		FILL_X(col + len) - PEN_WIDTH, FILL_Y(row + 1) - PEN_WIDTH);
	FillRect(r, B_SOLID_LOW);
    }
    BPoint where(TEXT_X(col), TEXT_Y(row));
    DrawString((char *)s, len, where);

    if (flags & DRAW_BOLD) {
	where.x += 1.0;
	SetDrawingMode(B_OP_BLEND);
	DrawString((char *)s, len, where);
	SetDrawingMode(B_OP_COPY);
    }
    if (flags & DRAW_UNDERL) {
	BPoint start(FILL_X(col), FILL_Y(row + 1) - PEN_WIDTH);
	BPoint end(FILL_X(col + len) - PEN_WIDTH, start.y);

	StrokeLine(start, end);
    }
}

    void
VimTextAreaView::mchClearBlock(
    int		row1,
    int		col1,
    int		row2,
    int		col2)
{
    BRect r(FILL_X(col1), FILL_Y(row1),
	    FILL_X(col2 + 1) - PEN_WIDTH, FILL_Y(row2 + 1) - PEN_WIDTH);
    gui_mch_set_bg_color(gui.back_pixel);
    FillRect(r, B_SOLID_LOW);
}

    void
VimTextAreaView::mchClearAll()
{
    gui_mch_set_bg_color(gui.back_pixel);
    FillRect(Bounds(), B_SOLID_LOW);
}

/*
 * mchDeleteLines() Lock()s the window by itself.
 */
    void
VimTextAreaView::mchDeleteLines(int row, int num_lines)
{
    if (row + num_lines > gui.scroll_region_bot)
    {
	/* Scrolled out of region, just blank the lines out */
	gui_clear_block(row, gui.scroll_region_left,
		gui.scroll_region_bot, gui.scroll_region_right);
    }
    else
    {
	/* copy one extra pixel, for when bold has spilled over */
	int width = gui.char_width * (gui.scroll_region_right
				- gui.scroll_region_left + 1) + 1 - PEN_WIDTH;
	int height = gui.char_height *
		     (gui.scroll_region_bot - row - num_lines + 1) - PEN_WIDTH;

	BRect source, dest;

	source.left = FILL_X(gui.scroll_region_left);
	source.top = FILL_Y(row + num_lines);
	source.right = source.left + width;
	source.bottom = source.top + height;

	dest.left = FILL_X(gui.scroll_region_left);
	dest.top = FILL_Y(row);
	dest.right = dest.left + width;
	dest.bottom = dest.top + height;

	/* XXX Attempt at a hack: */
	gui.vimWindow->UpdateIfNeeded();
#if 0
	/* XXX Attempt at a hack: */
	if (gui.vimWindow->NeedsUpdate()) {
	    fprintf(stderr, "mchDeleteLines: NeedsUpdate!\n");
	    gui.vimWindow->UpdateIfNeeded();
	    while (gui.vimWindow->NeedsUpdate()) {
		if (false && gui.vimWindow->Lock()) {
		    Sync();
		    gui.vimWindow->Unlock();
		}
		snooze(2);
	    }
	}
#endif

	if (gui.vimWindow->Lock()) {
	    Sync();
	    CopyBits(source, dest);
	    //Sync();

	    /* Update gui.cursor_row if the cursor scrolled or copied over */
	    if (gui.cursor_row >= row
		&& gui.cursor_col >= gui.scroll_region_left
		&& gui.cursor_col <= gui.scroll_region_right)
	    {
		if (gui.cursor_row < row + num_lines)
		    gui.cursor_is_valid = FALSE;
		else if (gui.cursor_row <= gui.scroll_region_bot)
		    gui.cursor_row -= num_lines;
	    }

	    /* Clear one column more for when bold has spilled over */
	    gui_clear_block(gui.scroll_region_bot - num_lines + 1,
						       gui.scroll_region_left,
		gui.scroll_region_bot, gui.scroll_region_right);

	    gui.vimWindow->Unlock();
	    /*
	     * The Draw() callback will be called now if some of the source
	     * bits were not in the visible region.
	     */

	    //gui_x11_check_copy_area();
	}
    }
}

/*
 * mchInsertLines() Lock()s the window by itself.
 */
    void
VimTextAreaView::mchInsertLines(int row, int num_lines)
{
    if (row + num_lines > gui.scroll_region_bot)
    {
	/* Scrolled out of region, just blank the lines out */
	gui_clear_block(row, gui.scroll_region_left,
		gui.scroll_region_bot, gui.scroll_region_right);
    }
    else
    {
	/* copy one extra pixel, for when bold has spilled over */
	int width = gui.char_width * (gui.scroll_region_right
				- gui.scroll_region_left + 1) + 1 - PEN_WIDTH;
	int height = gui.char_height *
		     (gui.scroll_region_bot - row - num_lines + 1) - PEN_WIDTH;

	BRect source, dest;

	source.left = FILL_X(gui.scroll_region_left);
	source.top = FILL_Y(row);
	source.right = source.left + width;
	source.bottom = source.top + height;

	dest.left = FILL_X(gui.scroll_region_left);
	dest.top = FILL_Y(row + num_lines);
	dest.right = dest.left + width;
	dest.bottom = dest.top + height;

	/* XXX Attempt at a hack: */
	gui.vimWindow->UpdateIfNeeded();
#if 0
	/* XXX Attempt at a hack: */
	if (gui.vimWindow->NeedsUpdate())
	    fprintf(stderr, "mchInsertLines: NeedsUpdate!\n");
	gui.vimWindow->UpdateIfNeeded();
	while (gui.vimWindow->NeedsUpdate())
	    snooze(2);
#endif

	if (gui.vimWindow->Lock()) {
	    Sync();
	    CopyBits(source, dest);
	    //Sync();

	    /* Update gui.cursor_row if the cursor scrolled or copied over */
	    if (gui.cursor_row >= gui.row
		&& gui.cursor_col >= gui.scroll_region_left
		&& gui.cursor_col <= gui.scroll_region_right)
	    {
		if (gui.cursor_row <= gui.scroll_region_bot - num_lines)
		    gui.cursor_row += num_lines;
		else if (gui.cursor_row <= gui.scroll_region_bot)
		    gui.cursor_is_valid = FALSE;
	    }
	    /* Clear one column more for when bold has spilled over */
	    gui_clear_block(row, gui.scroll_region_left,
		    row + num_lines - 1, gui.scroll_region_right);

	    gui.vimWindow->Unlock();
	    /*
	     * The Draw() callback will be called now if some of the source
	     * bits were not in the visible region.
	     * However, if we scroll too fast it can't keep up and the
	     * update region gets messed up. This seems to be because copying
	     * un-Draw()n bits does not generate Draw() calls for the copy...
	     * I moved the hack to before the CopyBits() to reduce the
	     * amount of additional waiting needed.
	     */

	    //gui_x11_check_copy_area();
	}
    }

}

/* ---------------- VimScrollBar ---------------- */

/* BUG: XXX
 * It seems that BScrollBar determine their direction not from
 * "posture" but from if they are "tall" or "wide" in shape...
 *
 * Also, place them out of sight, because Vim enables them before
 * they are positioned.
 */
VimScrollBar::VimScrollBar(scrollbar_T *g, orientation posture):
    BScrollBar(posture == B_HORIZONTAL ?  BRect(-100,-100,-10,-90) :
					  BRect(-100,-100,-90,-10),
		"vim scrollbar", (BView *)NULL,
	    0.0, 10.0, posture),
    ignoreValue(-1),
    scrollEventCount(0)
{
    gsb = g;
    SetResizingMode(B_FOLLOW_NONE);
}

VimScrollBar::~VimScrollBar()
{
}

    void
VimScrollBar::ValueChanged(float newValue)
{
    if (ignoreValue >= 0.0 && newValue == ignoreValue) {
	ignoreValue = -1;
	return;
    }
    ignoreValue = -1;
    /*
     * We want to throttle the amount of scroll messages generated.
     * Normally I presume you won't get a new message before we've
     * handled the previous one, but because we're passing them on this
     * happens very quickly. So instead we keep a counter of how many
     * scroll events there are (or will be) in the VDCMP, and the
     * throttling happens at the receiving end.
     */
    atomic_add(&scrollEventCount, 1);

    struct VimScrollBarMsg sm;

    sm.sb = this;
    sm.value = (long) newValue;
    sm.stillDragging = TRUE;

    write_port(gui.vdcmp, VimMsg::ScrollBar, &sm, sizeof(sm));

    // calls gui_drag_scrollbar(sb, newValue, TRUE);
}

/*
 * When the mouse goes up, report that scrolling has stopped.
 * MouseUp() is NOT called when the mouse-up occurs outside
 * the window, even though the thumb does move while the mouse
 * is outside... This has some funny effects... XXX
 * So we do special processing when the window de/activates.
 */
    void
VimScrollBar::MouseUp(BPoint where)
{
    //BMessage *m = Window()->CurrentMessage();
    //m->PrintToStream();

    atomic_add(&scrollEventCount, 1);

    struct VimScrollBarMsg sm;

    sm.sb = this;
    sm.value = (long) Value();
    sm.stillDragging = FALSE;

    write_port(gui.vdcmp, VimMsg::ScrollBar, &sm, sizeof(sm));

    // calls gui_drag_scrollbar(sb, newValue, FALSE);

    Inherited::MouseUp(where);
}

    void
VimScrollBar::SetValue(float newValue)
{
    if (newValue == Value())
	return;

    ignoreValue = newValue;
    Inherited::SetValue(newValue);
}

/* ---------------- VimFont ---------------- */

VimFont::VimFont(): BFont()
{
    init();
}

VimFont::VimFont(const VimFont *rhs): BFont(rhs)
{
    init();
}

VimFont::VimFont(const BFont *rhs): BFont(rhs)
{
    init();
}

VimFont::VimFont(const VimFont &rhs): BFont(rhs)
{
    init();
}

VimFont::~VimFont()
{
}

    void
VimFont::init()
{
    next = NULL;
    refcount = 1;
    name = NULL;
}

/* ---------------- ---------------- */

// some global variables
static char appsig[] = "application/x-vnd.Rhialto-Vim-5";
key_map *keyMap;
char *keyMapChars;
int main_exitcode = 127;

    status_t
gui_beos_process_event(bigtime_t timeout)
{
    struct VimMsg vm;
    long what;
    ssize_t size;

    size = read_port_etc(gui.vdcmp, &what, &vm, sizeof(vm),
	    B_TIMEOUT, timeout);

    if (size >= 0) {
	switch (what) {
	case VimMsg::Key:
	    {
		char_u *string = vm.u.Key.chars;
		int len = vm.u.Key.length;
		if (len == 1 && string[0] == Ctrl_chr('C')) {
		    trash_input_buf();
		    got_int = TRUE;
		}
		add_to_input_buf(string, len);
	    }
	    break;
	case VimMsg::Resize:
	    gui_resize_shell(vm.u.NewSize.width, vm.u.NewSize.height);
	    break;
	case VimMsg::ScrollBar:
	    {
		/*
		 * If loads of scroll messages queue up, use only the last
		 * one. Always report when the scrollbar stops dragging.
		 * This is not perfect yet anyway: these events are queued
		 * yet again, this time in the keyboard input buffer.
		 */
		int32 oldCount =
		    atomic_add(&vm.u.Scroll.sb->scrollEventCount, -1);
		if (oldCount <= 1 || !vm.u.Scroll.stillDragging)
		    gui_drag_scrollbar(vm.u.Scroll.sb->getGsb(),
			    vm.u.Scroll.value, vm.u.Scroll.stillDragging);
	    }
	    break;
	case VimMsg::Menu:
	    gui_menu_cb(vm.u.Menu.guiMenu);
	    break;
	case VimMsg::Mouse:
	    {
		int32 oldCount;
		if (vm.u.Mouse.button == MOUSE_DRAG)
		    oldCount =
			atomic_add(&gui.vimTextArea->mouseDragEventCount, -1);
		else
		    oldCount = 0;
		if (oldCount <= 1)
		    gui_send_mouse_event(vm.u.Mouse.button, vm.u.Mouse.x,
			    vm.u.Mouse.y, vm.u.Mouse.repeated_click,
			    vm.u.Mouse.modifiers);
	    }
	    break;
	case VimMsg::Focus:
	    gui.in_focus = vm.u.Focus.active;
	    /* XXX Signal that scrollbar dragging has stopped?
	     * This is needed because we don't get a MouseUp if
	     * that happens while outside the window... :-(
	     */
	    if (gui.dragged_sb) {
		gui.dragged_sb = SBAR_NONE;
	    }
	    gui_update_cursor(TRUE, FALSE);
	    break;
	case VimMsg::Refs:
	    ::RefsReceived(vm.u.Refs.message, vm.u.Refs.changedir);
	    break;
	default:
	    // unrecognised message, ignore it
	    break;
	}
    }

    /*
     * If size < B_OK, it is an error code.
     */
    return size;
}

/*
 * Here are some functions to protect access to ScreenLines[] and
 * LineOffset[]. These are used from the window thread to respond
 * to a Draw() callback. When that occurs, the window is already
 * locked by the system.
 *
 * Other code that needs to lock is any code that changes these
 * variables. Other read-only access, or access merely to the
 * contents of the screen buffer, need not be locked.
 *
 * If there is no window, don't call Lock() but do succeed.
 */

    int
vim_lock_screen()
{
    return !gui.vimWindow || gui.vimWindow->Lock();
}

    void
vim_unlock_screen()
{
    if (gui.vimWindow)
	gui.vimWindow->Unlock();
}

#define RUN_BAPPLICATION_IN_NEW_THREAD	0

#if RUN_BAPPLICATION_IN_NEW_THREAD

    int32
run_vimapp(void *args)
{
    VimApp app(appsig);

    gui.vimApp = &app;
    app.Run();			    /* Run until Quit() called */

    return 0;
}

#else

    int32
call_main(void *args)
{
    struct MainArgs *ma = (MainArgs *)args;

    return main(ma->argc, ma->argv);
}
#endif

extern "C" {

/*
 * Parse the GUI related command-line arguments.  Any arguments used are
 * deleted from argv, and *argc is decremented accordingly.  This is called
 * when vim is started, whether or not the GUI has been started.
 */
    void
gui_mch_prepare(
    int		*argc,
    char	**argv)
{
    /*
     * We don't have any command line arguments for the BeOS GUI yet,
     * but this is an excellent place to create our Application object.
     */
    if (!gui.vimApp) {
	thread_info tinfo;
	get_thread_info(find_thread(NULL), &tinfo);

	/* May need the port very early on to process RefsReceived() */
	gui.vdcmp = create_port(B_MAX_PORT_COUNT, "vim VDCMP");

#if RUN_BAPPLICATION_IN_NEW_THREAD
	thread_id tid = spawn_thread(run_vimapp, "vim VimApp",
						tinfo.priority, NULL);
	if (tid >= B_OK) {
	    resume_thread(tid);
	} else {
	    getout(1);
	}
#else
	MainArgs ma = { *argc, argv };
	thread_id tid = spawn_thread(call_main, "vim main()",
						tinfo.priority, &ma);
	if (tid >= B_OK) {
	    VimApp app(appsig);

	    gui.vimApp = &app;
	    resume_thread(tid);
	    /*
	     * This is rather horrible.
	     * call_main will call main() again...
	     * There will be no infinite recursion since
	     * gui.vimApp is set now.
	     */
	    app.Run();			    /* Run until Quit() called */
	    //fprintf(stderr, "app.Run() returned...\n");
	    status_t dummy_exitcode;
	    (void)wait_for_thread(tid, &dummy_exitcode);

	    /*
	     * This path should be the normal one taken to exit Vim.
	     * The main() thread calls mch_exit() which calls
	     * gui_mch_exit() which terminates its thread.
	     */
	    exit(main_exitcode);
	}
#endif
    }
    /* Don't fork() when starting the GUI. Spawned threads are not
     * duplicated with a fork(). The result is a mess.
     */
    gui.dofork = FALSE;
    /*
     * XXX Try to determine whether we were started from
     * the Tracker or the terminal.
     * It would be nice to have this work, because the Tracker
     * follows symlinks, so even if you double-click on gvim,
     * when it is a link to vim it will still pass a command name
     * of vim...
     * We try here to see if stdin comes from /dev/null. If so,
     * (or if there is an error, which should never happen) start the GUI.
     * This does the wrong thing for vim - </dev/null, and we're
     * too early to see the command line parsing. Tough.
     * On the other hand, it starts the gui for vim file & which is nice.
     */
    if (!isatty(0)) {
	struct stat stat_stdin, stat_dev_null;

	if (fstat(0, &stat_stdin) == -1 ||
	    stat("/dev/null", &stat_dev_null) == -1 ||
	    (stat_stdin.st_dev == stat_dev_null.st_dev &&
	     stat_stdin.st_ino == stat_dev_null.st_ino))
	    gui.starting = TRUE;
    }
}

/*
 * Check if the GUI can be started.  Called before gvimrc is sourced.
 * Return OK or FAIL.
 */
    int
gui_mch_init_check(void)
{
    return OK;		/* TODO: GUI can always be started? */
}

/*
 * Initialise the GUI.  Create all the windows, set up all the call-backs
 * etc.
 */
    int
gui_mch_init()
{
    gui.def_norm_pixel = RGB(0x00, 0x00, 0x00);	// black
    gui.def_back_pixel = RGB(0xFF, 0xFF, 0xFF);	// white
    gui.norm_pixel = gui.def_norm_pixel;
    gui.back_pixel = gui.def_back_pixel;

    gui.scrollbar_width = (int) B_V_SCROLL_BAR_WIDTH;
    gui.scrollbar_height = (int) B_H_SCROLL_BAR_HEIGHT;
#ifdef FEAT_MENU
    gui.menu_height = 19;	// initial guess -
				// correct for my default settings
#endif
    gui.border_offset = 3;	// coordinates are inside window borders

    if (gui.vdcmp < B_OK)
	return FAIL;
    get_key_map(&keyMap, &keyMapChars);

    gui.vimWindow = new VimWindow();	/* hidden and locked */
    if (!gui.vimWindow)
	return FAIL;

    gui.vimWindow->Run();		/* Run() unlocks but does not show */

    /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc
     * file) */
    set_normal_colors();

    /*
     * Check that none of the colors are the same as the background color
     */
    gui_check_colors();

    /* Get the colors for the highlight groups (gui_check_colors() might have
     * changed them) */
    highlight_gui_started();		/* re-init colors and fonts */

    gui_mch_new_colors();		/* window must exist for this */

    return OK;
}

/*
 * Called when the foreground or background color has been changed.
 */
    void
gui_mch_new_colors()
{
    rgb_color rgb = GUI_TO_RGB(gui.back_pixel);

    if (gui.vimWindow->Lock()) {
	gui.vimForm->SetViewColor(rgb);
	// Does this not have too much effect for those small rectangles?
	gui.vimForm->Invalidate();
	gui.vimWindow->Unlock();
    }
}

/*
 * Open the GUI window which was created by a call to gui_mch_init().
 */
    int
gui_mch_open()
{
    if (gui_win_x != -1 && gui_win_y != -1)
	gui_mch_set_winpos(gui_win_x, gui_win_y);

    /* Actually open the window */
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->Show();
	gui.vimWindow->Unlock();

#if USE_THREAD_FOR_INPUT_WITH_TIMEOUT
	/* Kill the thread that may have been created for the Terminal */
	beos_cleanup_read_thread();
#endif

	return OK;
    }

    return FAIL;
}

    void
gui_mch_exit(int vim_exitcode)
{
    if (gui.vimWindow) {
	thread_id tid = gui.vimWindow->Thread();
	gui.vimWindow->Lock();
	gui.vimWindow->Quit();
	/* Wait until it is truely gone */
	int32 exitcode;
	wait_for_thread(tid, &exitcode);
    }
    delete_port(gui.vdcmp);
#if !RUN_BAPPLICATION_IN_NEW_THREAD
    /*
     * We are in the main() thread - quit the App thread and
     * quit ourselves (passing on the exitcode). Use a global since the
     * value from exit_thread() is only used if wait_for_thread() is
     * called in time (race condition).
     */
#endif
    if (gui.vimApp) {
	VimTextAreaView::guiBlankMouse(false);

	main_exitcode = vim_exitcode;
#if RUN_BAPPLICATION_IN_NEW_THREAD
	thread_id tid = gui.vimApp->Thread();
	int32 exitcode;
	gui.vimApp->Lock();
	gui.vimApp->Quit();
	gui.vimApp->Unlock();
	wait_for_thread(tid, &exitcode);
#else
	gui.vimApp->Lock();
	gui.vimApp->Quit();
	gui.vimApp->Unlock();
	/* suicide */
	exit_thread(vim_exitcode);
#endif
    }
    /* If we are somehow still here, let mch_exit() handle things. */
}

/*
 * Get the position of the top left corner of the window.
 */
    int
gui_mch_get_winpos(int *x, int *y)
{
    /* TODO */
    return FAIL;
}

/*
 * Set the position of the top left corner of the window to the given
 * coordinates.
 */
    void
gui_mch_set_winpos(int x, int y)
{
    /* TODO */
}

/*
 * Set the size of the window to the given width and height in pixels.
 */
    void
gui_mch_set_shellsize(
    int		width,
    int		height,
    int		min_width,
    int		min_height,
    int		base_width,
    int		base_height)
{
    /*
     * We are basically given the size of the VimForm, if I understand
     * correctly. Since it fills the window completely, this will also
     * be the size of the window.
     */
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->ResizeTo(width - PEN_WIDTH, height - PEN_WIDTH);

	/* set size limits */
	float minWidth, maxWidth, minHeight, maxHeight;

	gui.vimWindow->GetSizeLimits(&minWidth, &maxWidth,
				     &minHeight, &maxHeight);
	gui.vimWindow->SetSizeLimits(min_width, maxWidth,
				     min_height, maxHeight);

#if HAVE_R3_OR_LATER
	/*
	 * Set the resizing alignment depending on font size.
	 * XXX This is untested, since I don't have R3 yet.
	 */
	SetWindowAlignment(
	    B_PIXEL_ALIGNMENT,		// window_alignment mode,
	    1,				// int32 h,
	    0,				// int32 hOffset = 0,
	    gui.char_width,		// int32 width = 0,
	    base_width,			// int32 widthOffset = 0,
	    1,				// int32 v = 0,
	    0,				// int32 vOffset = 0,
	    gui.char_height,		// int32 height = 0,
	    base_height			// int32 heightOffset = 0
	);
#else
	/* don't know what to do with base_{width,height}. */
#endif

	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_get_screen_dimensions(
    int		*screen_w,
    int		*screen_h)
{
    BRect frame;

    {
	BScreen screen(gui.vimWindow);

	if (screen.IsValid()) {
	    frame = screen.Frame();
	} else {
	    frame.right = 640;
	    frame.bottom = 480;
	}
    }

    /* XXX approximations... */
    *screen_w = (int) frame.right - 2 * gui.scrollbar_width - 20;
    *screen_h = (int) frame.bottom - gui.scrollbar_height
#ifdef FEAT_MENU
	- gui.menu_height
#endif
	- 30;
}

    void
gui_mch_set_text_area_pos(
    int		x,
    int		y,
    int		w,
    int		h)
{
    if (!gui.vimTextArea)
	return;

    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->MoveTo(x, y);
	gui.vimTextArea->ResizeTo(w - PEN_WIDTH, h - PEN_WIDTH);
	gui.vimWindow->Unlock();
    }
}


/*
 * Scrollbar stuff:
 */

    void
gui_mch_enable_scrollbar(
    scrollbar_T	*sb,
    int		flag)
{
    VimScrollBar *vsb = sb->id;
    if (gui.vimWindow->Lock()) {
	/*
	 * This function is supposed to be idempotent, but Show()/Hide()
	 * is not. Therefore we test if they are needed.
	 */
	if (flag) {
	    if (vsb->IsHidden()) {
		vsb->Show();
	    }
	} else {
	    if (!vsb->IsHidden()) {
		vsb->Hide();
	    }
	}
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_set_scrollbar_thumb(
    scrollbar_T *sb,
    int		val,
    int		size,
    int		max)
{
    if (gui.vimWindow->Lock()) {
	VimScrollBar *s = sb->id;
	if (max == 0) {
	    s->SetValue(0);
	    s->SetRange(0.0, 0.0);
	} else {
	    s->SetProportion((float)size / (max + 1.0));
	    s->SetSteps(1.0, size > 5 ? size - 2 : size);
#ifndef SCROLL_PAST_END		// really only defined in gui.c...
	    max = max + 1 - size;
#endif
	    if (max < s->Value()) {
		/*
		 * If the new maximum is lower than the current value,
		 * setting it would cause the value to be clipped and
		 * therefore a ValueChanged() call.
		 * We avoid this by setting the value first, because
		 * it presumably is <= max.
		 */
		s->SetValue(val);
		s->SetRange(0.0, max);
	    } else {
		/*
		 * In the other case, set the range first, since the
		 * new value might be higher than the current max.
		 */
		s->SetRange(0.0, max);
		s->SetValue(val);
	    }
	}
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_set_scrollbar_pos(
    scrollbar_T *sb,
    int		x,
    int		y,
    int		w,
    int		h)
{
    if (gui.vimWindow->Lock()) {
	VimScrollBar *vsb = sb->id;
	vsb->MoveTo(x, y);
	vsb->ResizeTo(w - PEN_WIDTH, h - PEN_WIDTH);
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_create_scrollbar(
    scrollbar_T *sb,
    int		orient)		/* SBAR_VERT or SBAR_HORIZ */
{
    orientation posture =
	(orient == SBAR_HORIZ) ? B_HORIZONTAL : B_VERTICAL;

    VimScrollBar *vsb = sb->id = new VimScrollBar(sb, posture);
    if (gui.vimWindow->Lock()) {
	vsb->SetTarget(gui.vimTextArea);
	vsb->Hide();
	gui.vimForm->AddChild(vsb);
	gui.vimWindow->Unlock();
    }
}

#if defined(FEAT_WINDOWS) || defined(PROTO)
    void
gui_mch_destroy_scrollbar(
    scrollbar_T	*sb)
{
    if (gui.vimWindow->Lock()) {
	sb->id->RemoveSelf();
	delete sb->id;
	gui.vimWindow->Unlock();
    }
}
#endif

/*
 * Cursor blink functions.
 *
 * This is a simple state machine:
 * BLINK_NONE	not blinking at all
 * BLINK_OFF	blinking, cursor is not shown
 * BLINK_ON	blinking, cursor is shown
 */

#define BLINK_NONE  0
#define BLINK_OFF   1
#define BLINK_ON    2

static int		blink_state = BLINK_NONE;
static long_u		blink_waittime = 700;
static long_u		blink_ontime = 400;
static long_u		blink_offtime = 250;
static int	blink_timer = 0;

    void
gui_mch_set_blinking(
    long    waittime,
    long    on,
    long    off)
{
	/* TODO */
    blink_waittime = waittime;
    blink_ontime = on;
    blink_offtime = off;
}

/*
 * Stop the cursor blinking.  Show the cursor if it wasn't shown.
 */
    void
gui_mch_stop_blink()
{
	/* TODO */
    if (blink_timer != 0)
    {
	//XtRemoveTimeOut(blink_timer);
	blink_timer = 0;
    }
    if (blink_state == BLINK_OFF)
	gui_update_cursor(TRUE, FALSE);
    blink_state = BLINK_NONE;
}

/*
 * Start the cursor blinking.  If it was already blinking, this restarts the
 * waiting time and shows the cursor.
 */
    void
gui_mch_start_blink()
{
	/* TODO */
    if (blink_timer != 0)
	;//XtRemoveTimeOut(blink_timer);
    /* Only switch blinking on if none of the times is zero */
    if (blink_waittime && blink_ontime && blink_offtime && gui.in_focus)
    {
	blink_timer = 1; //XtAppAddTimeOut(app_context, blink_waittime,
	blink_state = BLINK_ON;
	gui_update_cursor(TRUE, FALSE);
    }
}

/*
 * Initialise vim to use the font with the given name.	Return FAIL if the font
 * could not be loaded, OK otherwise.
 */
    int
gui_mch_init_font(
    char_u		*font_name,
    int			fontset)
{
    if (gui.vimWindow->Lock())
    {
	int rc = gui.vimTextArea->mchInitFont(font_name);
	gui.vimWindow->Unlock();

	return rc;
    }

    return FAIL;
}

    int
gui_mch_adjust_charsize()
{
    return FAIL;
}

    GuiFont
gui_mch_get_font(
    char_u		*name,
    int			giveErrorIfMissing)
{
    VimFont		*font = 0;
    static VimFont *fontList = NULL;

    if (!gui.in_use)		    /* can't do this when GUI not running */
	return NOFONT;

    if (!name)
	name = (char_u *)"be_fixed_font";

    VimFont *flp;
    for (flp = fontList; flp; flp = flp->next) {
	if (STRCMP(name, flp->name) == 0) {
	    flp->refcount++;
	    return (GuiFont)flp;
	}
    }

    font = new VimFont(be_fixed_font);

    /* Set some universal features: */
    font->SetSpacing(B_FIXED_SPACING);
    font->SetEncoding(B_ISO_8859_1);

    /* Remember font for later use */
    font->name = vim_strsave(name);
    font->next = fontList;
    fontList = font;

    font_family family;
    font_style style;
    int size;
    int len;
    char_u *end;

#ifdef never
    // This leads to SEGV/BUS on R4+
    // Replace underscores with spaces, and I can't see why ?
    // richard@whitequeen.com jul 99
    while (end = (char_u *)strchr((char *)name, '_'))
	*end = ' ';
#endif
    /*
     *  Parse font names as Family/Style/Size.
     *  On errors, just keep the be_fixed_font.
     */
    end = (char_u *)strchr((char *)name, '/');
    if (!end)
	goto error;
    strncpy(family, (char *)name, len = end - name);
    family[len] = '\0';

    name = end + 1;
    end = (char_u *)strchr((char *)name, '/');
    if (!end)
	goto error;
    strncpy(style, (char *)name, len = end - name);
    style[len] = '\0';

    name = end + 1;
    size = atoi((char *)name);
    if (size <= 0)
	goto error;

    font->SetFamilyAndStyle(family, style);
    font->SetSize(size);
    font->SetSpacing(B_FIXED_SPACING);
    font->SetEncoding(B_ISO_8859_1);
    //font->PrintToStream();

    return (GuiFont)font;

error:
    if (giveErrorIfMissing)
	EMSG2("(fe0) Unknown font: %s", name);

    return (GuiFont)font;
}

/*
 * Set the current text font.
 */
    void
gui_mch_set_font(
    GuiFont	font)
{
    if (gui.vimWindow->Lock()) {
	VimFont *vf = (VimFont *)font;

	gui.vimTextArea->SetFont(vf);

	gui.char_width = (int) vf->StringWidth("n");
	font_height fh;
	vf->GetHeight(&fh);
	gui.char_height = (int)(fh.ascent + 0.9999)
		    + (int)(fh.descent + 0.9999) + (int)(fh.leading + 0.9999);
        gui.char_ascent = (int)(fh.ascent + 0.9999);

	gui.vimWindow->Unlock();
    }
}

#if 0 /* not used */
/*
 * Return TRUE if the two fonts given are equivalent.
 */
    int
gui_mch_same_font(
    GuiFont	f1,
    GuiFont	f2)
{
    VimFont *vf1 = (VimFont *)f1;
    VimFont *vf2 = (VimFont *)f2;

    return f1 == f2 ||
	    (vf1->FamilyAndStyle() == vf2->FamilyAndStyle() &&
	     vf1->Size() == vf2->Size());
}
#endif

/* XXX TODO This is apparently never called... */
    void
gui_mch_free_font(
    GuiFont	font)
{
    VimFont *f = (VimFont *)font;
    if (--f->refcount <= 0) {
	if (f->refcount < 0)
	    fprintf(stderr, "VimFont: refcount < 0\n");
	delete f;
    }
}

    static int
hex_digit(int c)
{
    if (isdigit(c))
	return c - '0';
    c = TOLOWER_ASC(c);
    if (c >= 'a' && c <= 'f')
	return c - 'a' + 10;
    return -1000;
}

/*
 * This function has been lifted from gui_w32.c and extended a bit.
 *
 * Return the Pixel value (color) for the given color name.
 * Return INVALCOLOR for error.
 */
    guicolor_T
gui_mch_get_color(
    char_u	*name)
{
    typedef struct GuiColourTable
    {
	char	    *name;
	guicolor_T     colour;
    } GuiColourTable;

#define NSTATIC_COLOURS		32
#define NDYNAMIC_COLOURS	33
#define NCOLOURS		(NSTATIC_COLOURS + NDYNAMIC_COLOURS)

    static GuiColourTable table[NCOLOURS] =
    {
	{"Black",	    RGB(0x00, 0x00, 0x00)},
	{"DarkGray",	    RGB(0x80, 0x80, 0x80)},
	{"DarkGrey",	    RGB(0x80, 0x80, 0x80)},
	{"Gray",	    RGB(0xC0, 0xC0, 0xC0)},
	{"Grey",	    RGB(0xC0, 0xC0, 0xC0)},
	{"LightGray",	    RGB(0xD3, 0xD3, 0xD3)},
	{"LightGrey",	    RGB(0xD3, 0xD3, 0xD3)},
	{"White",	    RGB(0xFF, 0xFF, 0xFF)},
	{"DarkRed",	    RGB(0x80, 0x00, 0x00)},
	{"Red",		    RGB(0xFF, 0x00, 0x00)},
	{"LightRed",	    RGB(0xFF, 0xA0, 0xA0)},
	{"DarkBlue",	    RGB(0x00, 0x00, 0x80)},
	{"Blue",	    RGB(0x00, 0x00, 0xFF)},
	{"LightBlue",	    RGB(0xA0, 0xA0, 0xFF)},
	{"DarkGreen",	    RGB(0x00, 0x80, 0x00)},
	{"Green",	    RGB(0x00, 0xFF, 0x00)},
	{"LightGreen",	    RGB(0xA0, 0xFF, 0xA0)},
	{"DarkCyan",	    RGB(0x00, 0x80, 0x80)},
	{"Cyan",	    RGB(0x00, 0xFF, 0xFF)},
	{"LightCyan",	    RGB(0xA0, 0xFF, 0xFF)},
	{"DarkMagenta",	    RGB(0x80, 0x00, 0x80)},
	{"Magenta",	    RGB(0xFF, 0x00, 0xFF)},
	{"LightMagenta",    RGB(0xFF, 0xA0, 0xFF)},
	{"Brown",	    RGB(0x80, 0x40, 0x40)},
	{"Yellow",	    RGB(0xFF, 0xFF, 0x00)},
	{"LightYellow",	    RGB(0xFF, 0xFF, 0xA0)},
	{"DarkYellow",	    RGB(0xBB, 0xBB, 0x00)},
	{"SeaGreen",	    RGB(0x2E, 0x8B, 0x57)},
	{"Orange",	    RGB(0xFF, 0xA5, 0x00)},
	{"Purple",	    RGB(0xA0, 0x20, 0xF0)},
	{"SlateBlue",	    RGB(0x6A, 0x5A, 0xCD)},
	{"Violet",          RGB(0xEE, 0x82, 0xEE)},
    };

    static int endColour = NSTATIC_COLOURS;
    static int newColour = NSTATIC_COLOURS;

    int		    r, g, b;
    int		    i;

    if (name[0] == '#' && STRLEN(name) == 7)
    {
	/* Name is in "#rrggbb" format */
	r = hex_digit(name[1]) * 16 + hex_digit(name[2]);
	g = hex_digit(name[3]) * 16 + hex_digit(name[4]);
	b = hex_digit(name[5]) * 16 + hex_digit(name[6]);
	if (r < 0 || g < 0 || b < 0)
	    return INVALCOLOR;
	return RGB(r, g, b);
    }
    else
    {
	/* Check if the name is one of the colours we know */
	for (i = 0; i < endColour; i++)
	    if (STRICMP(name, table[i].name) == 0)
		return table[i].colour;
    }

    /*
     * Last attempt. Look in the file "$VIM/rgb.txt".
     */
    {
#define LINE_LEN 100
	FILE	*fd;
	char	line[LINE_LEN];
	char_u	*fname;

	fname = expand_env_save((char_u *)"$VIM/rgb.txt");
	if (fname == NULL)
	    return INVALCOLOR;

	fd = fopen((char *)fname, "rt");
	vim_free(fname);
	if (fd == NULL)
	    return INVALCOLOR;

	while (!feof(fd))
	{
	    int	    len;
	    int	    pos;
	    char    *colour;

	    fgets(line, LINE_LEN, fd);
	    len = strlen(line);

	    if (len <= 1 || line[len-1] != '\n')
		continue;

	    line[len-1] = '\0';

	    i = sscanf(line, "%d %d %d %n", &r, &g, &b, &pos);
	    if (i != 3)
		continue;

	    colour = line + pos;

	    if (STRICMP(colour, name) == 0)
	    {
		fclose(fd);
		/*
		 * Now remember this colour in the table.
		 * A LRU scheme might be better but this is simpler.
		 * Or could use a growing array.
		 */
		guicolor_T gcolour = RGB(r,g,b);

		vim_free(table[newColour].name);
		table[newColour].name = (char *)vim_strsave((char_u *)colour);
		table[newColour].colour = gcolour;

		newColour++;
		if (newColour >= NCOLOURS)
		    newColour = NSTATIC_COLOURS;
		if (endColour < NCOLOURS)
		    endColour = newColour;

		return gcolour;
	    }
	}

	fclose(fd);
    }

    return INVALCOLOR;
}

/*
 * Set the current text foreground color.
 */
    void
gui_mch_set_fg_color(
    guicolor_T	color)
{
    rgb_color rgb = GUI_TO_RGB(color);
    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->SetHighColor(rgb);
	gui.vimWindow->Unlock();
    }
}

/*
 * Set the current text background color.
 */
    void
gui_mch_set_bg_color(
    guicolor_T	color)
{
    rgb_color rgb = GUI_TO_RGB(color);
    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->SetLowColor(rgb);
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_draw_string(
    int		row,
    int		col,
    char_u	*s,
    int		len,
    int		flags)
{
    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->mchDrawString(row, col, s, len, flags);
	gui.vimWindow->Unlock();
    }
}

/*
 * Return OK if the key with the termcap name "name" is supported.
 */
    int
gui_mch_haskey(
    char_u	*name)
{
    int i;

    for (i = 0; special_keys[i].BeKeys != 0; i++)
	if (name[0] == special_keys[i].vim_code0 &&
					 name[1] == special_keys[i].vim_code1)
	    return OK;
    return FAIL;
}

    void
gui_mch_beep()
{
    ::beep();
}

    void
gui_mch_flash(int msec)
{
    /* Do a visual beep by reversing the foreground and background colors */

    if (gui.vimWindow->Lock()) {
	BRect rect = gui.vimTextArea->Bounds();

	gui.vimTextArea->SetDrawingMode(B_OP_INVERT);
	gui.vimTextArea->FillRect(rect);
	gui.vimTextArea->Sync();
	snooze(msec * 1000);	 /* wait for a few msec */
	gui.vimTextArea->FillRect(rect);
	gui.vimTextArea->SetDrawingMode(B_OP_COPY);
	gui.vimTextArea->Flush();
	gui.vimWindow->Unlock();
    }
}

/*
 * Invert a rectangle from row r, column c, for nr rows and nc columns.
 */
    void
gui_mch_invert_rectangle(
    int		r,
    int		c,
    int		nr,
    int		nc)
{
    BRect rect;
    rect.left = FILL_X(c);
    rect.top = FILL_Y(r);
    rect.right = rect.left + nc * gui.char_width - PEN_WIDTH;
    rect.bottom = rect.top + nr * gui.char_height - PEN_WIDTH;

    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->SetDrawingMode(B_OP_INVERT);
	gui.vimTextArea->FillRect(rect);
	gui.vimTextArea->SetDrawingMode(B_OP_COPY);
	gui.vimWindow->Unlock();
    }
}

/*
 * Iconify the GUI window.
 */
    void
gui_mch_iconify()
{
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->Minimize(true);
	gui.vimWindow->Unlock();
    }
}

#if defined(FEAT_EVAL) || defined(PROTO)
/*
 * Bring the Vim window to the foreground.
 */
    void
gui_mch_set_foreground()
{
    /* TODO */
}
#endif

/*
 * Set the window title
 */
    void
gui_mch_settitle(
    char_u	*title,
    char_u	*icon)
{
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->SetTitle((char *)title);
	gui.vimWindow->Unlock();
    }
}

/*
 * Draw a cursor without focus.
 */
    void
gui_mch_draw_hollow_cursor(guicolor_T color)
{
    gui_mch_set_fg_color(color);

    BRect r;
    r.left = FILL_X(gui.col);
    r.top = FILL_Y(gui.row);
    r.right = r.left + gui.char_width - PEN_WIDTH;
    r.bottom = r.top + gui.char_height - PEN_WIDTH;

    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->StrokeRect(r);
	gui.vimWindow->Unlock();
	//gui_mch_flush();
    }
}

/*
 * Draw part of a cursor, only w pixels wide, and h pixels high.
 */
    void
gui_mch_draw_part_cursor(
    int		w,
    int		h,
    guicolor_T	color)
{
    gui_mch_set_fg_color(color);

    BRect r;
    r.left =
#ifdef FEAT_RIGHTLEFT
	/* vertical line should be on the right of current point */
	!(State & CMDLINE) && curwin->w_p_rl ? FILL_X(gui.col + 1) - w :
#endif
	    FILL_X(gui.col);
    r.right = r.left + w - PEN_WIDTH;
    r.bottom = FILL_Y(gui.row + 1) - PEN_WIDTH;
    r.top = r.bottom - h + PEN_WIDTH;

    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->FillRect(r);
	gui.vimWindow->Unlock();
	//gui_mch_flush();
    }
}

/*
 * Catch up with any queued events.  This may put keyboard input into the
 * input buffer, call resize call-backs, trigger timers etc.  If there is
 * nothing in the event queue (& no timers pending), then we return
 * immediately.
 */
    void
gui_mch_update()
{
    gui_mch_flush();
    while (port_count(gui.vdcmp) > 0 &&
	    !vim_is_input_buf_full() &&
	    gui_beos_process_event(0) >= B_OK)
	/* nothing */ ;
}

/*
 * GUI input routine called by gui_wait_for_chars().  Waits for a character
 * from the keyboard.
 *	wtime == -1		Wait forever.
 *	wtime == 0		This should never happen.
 *	wtime > 0		Wait wtime milliseconds for a character.
 * Returns OK if a character was found to be available within the given time,
 * or FAIL otherwise.
 */
    int
gui_mch_wait_for_chars(
    int		wtime)
{
    int		    focus;
    bigtime_t	    until, timeout;
    status_t	    st;

    if (wtime >= 0) {
	timeout = wtime * 1000;
	until = system_time() + timeout;
    } else {
	timeout = B_INFINITE_TIMEOUT;
    }

    focus = gui.in_focus;
    for (;;)
    {
	/* Stop or start blinking when focus changes */
	if (gui.in_focus != focus)
	{
	    if (gui.in_focus)
		gui_mch_start_blink();
	    else
		gui_mch_stop_blink();
	    focus = gui.in_focus;
	}

	gui_mch_flush();
	/*
	 * Don't use gui_mch_update() because then we will spin-lock until a
	 * char arrives, instead we use gui_beos_process_event() to hang until
	 * an event arrives.  No need to check for input_buf_full because we
	 * are returning as soon as it contains a single char.
	 */
	st = gui_beos_process_event(timeout);

	if (!vim_is_input_buf_empty())
	    return OK;
	if (st < B_OK)		    /* includes B_TIMED_OUT */
	    return FAIL;

	/*
	 * Calculate how much longer we're willing to wait for the
	 * next event.
	 */
	if (wtime >= 0) {
	    timeout = until - system_time();
	    if (timeout < 0)
		break;
	}
    }
    return FAIL;

}

/*
 * Output routines.
 */

/*
 * Flush any output to the screen. This is typically called before
 * the app goes to sleep.
 */
    void
gui_mch_flush()
{
    // does this need to lock the window? Apparently not but be safe.
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->Flush();
	gui.vimWindow->Unlock();
    }
    return;
}

/*
 * Clear a rectangular region of the screen from text pos (row1, col1) to
 * (row2, col2) inclusive.
 */
    void
gui_mch_clear_block(
    int		row1,
    int		col1,
    int		row2,
    int		col2)
{
    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->mchClearBlock(row1, col1, row2, col2);
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_clear_all()
{
    if (gui.vimWindow->Lock()) {
	gui.vimTextArea->mchClearAll();
	gui.vimWindow->Unlock();
    }
}

/*
 * Delete the given number of lines from the given row, scrolling up any
 * text further down within the scroll region.
 */
    void
gui_mch_delete_lines(
    int		row,
    int		num_lines)
{
    gui.vimTextArea->mchDeleteLines(row, num_lines);
}

/*
 * Insert the given number of lines before the given row, scrolling down any
 * following text within the scroll region.
 */
    void
gui_mch_insert_lines(
    int		row,
    int		num_lines)
{
    gui.vimTextArea->mchInsertLines(row, num_lines);
}

#if defined(FEAT_MENU) || defined(PROTO)
/*
 * Menu stuff.
 */

    void
gui_mch_enable_menu(
    int		flag)
{
    if (gui.vimWindow->Lock())
    {
	BMenuBar *menubar = gui.vimForm->MenuBar();
	menubar->SetEnabled(flag);
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_set_menu_pos(
    int		x,
    int		y,
    int		w,
    int		h)
{
    /* It will be in the right place anyway */
}

/*
 * Add a sub menu to the menu bar.
 */
    void
gui_mch_add_menu(
    vimmenu_T	*menu,
    int		idx)
{
    vimmenu_T	*parent = menu->parent;

    if (!menu_is_menubar(menu->name)
	    || (parent != NULL && parent->submenu_id == NULL))
	return;

    if (gui.vimWindow->Lock())
    {
/* Major re-write of the menu code, it was failing with memory corruption when
 * we started loading multiple files (the Buffer menu)
 *
 * Note we don't use the preference values yet, all are inserted into the
 * menubar on a first come-first served basis...
 *
 * richard@whitequeen.com jul 99
 */

	BMenu *tmp;

	if ( parent )
	    tmp = parent->submenu_id;
	else
	    tmp = gui.vimForm->MenuBar();
// make sure we don't try and add the same menu twice. The Buffers menu tries to
// do this and Be starts to crash...

	if ( ! tmp->FindItem((const char *) menu->dname)) {

	    BMenu *bmenu = new BMenu((char *)menu->dname);

	    menu->submenu_id = bmenu;

// when we add a BMenu to another Menu, it creates the interconnecting BMenuItem
	    tmp->AddItem(bmenu);

// Now its safe to query the menu for the associated MenuItem....
	    menu->id = tmp->FindItem((const char *) menu->dname);

	}
	gui.vimWindow->Unlock();
    }
}

    void
gui_mch_toggle_tearoffs(int enable)
{
    /* no tearoff menus */
}

    static BMessage *
MenuMessage(vimmenu_T *menu)
{
    BMessage *m = new BMessage('menu');
    m->AddPointer("VimMenu", (void *)menu);

    return m;
}

/*
 * Add a menu item to a menu
 */
    void
gui_mch_add_menu_item(
    vimmenu_T	*menu,
    int		idx)
{
    int		mnemonic = 0;
    vimmenu_T	*parent = menu->parent;

    if (parent->submenu_id == NULL)
	return;

#ifdef never
    /* why not add separators ?
     * richard
     */
    /* Don't add menu separator */
    if (menu_is_separator(menu->name))
	return;
#endif

    /* TODO: use menu->actext */
    /* This is difficult, since on Be, an accelerator must be a single char
     * and a lot of Vim ones are the standard VI commands.
     *
     * Punt for Now...
     * richard@whiequeen.com jul 99
     */
    if (gui.vimWindow->Lock())
    {
	if ( menu_is_separator(menu->name)) {
	    BSeparatorItem *item = new BSeparatorItem();
	    parent->submenu_id->AddItem(item);
	    menu->id = item;
	    menu->submenu_id = NULL;
	}
	else {
	    BMenuItem *item = new BMenuItem((char *)menu->dname,
		    MenuMessage(menu));
	    item->SetTarget(gui.vimTextArea);
	    item->SetTrigger((char) menu->mnemonic);
	    parent->submenu_id->AddItem(item);
	    menu->id = item;
	    menu->submenu_id = NULL;
	}
	gui.vimWindow->Unlock();
    }
}

/*
 * Destroy the machine specific menu widget.
 */
    void
gui_mch_destroy_menu(
    vimmenu_T	*menu)
{
    if (gui.vimWindow->Lock())
    {
	assert(menu->submenu_id == NULL || menu->submenu_id->CountItems() == 0);
	/*
	 * Detach this menu from its parent, so that it is not deleted
	 * twice once we get to delete that parent.
	 * Deleting a BMenuItem also deletes the associated BMenu, if any
	 * (which does not have any items anymore since they were
	 * removed and deleted before).
	 */
	BMenu *bmenu = menu->id->Menu();
	if (bmenu)
	{
	    bmenu->RemoveItem(menu->id);
	    /*
	     * If we removed the last item from the menu bar,
	     * resize it out of sight.
	     */
	    if (bmenu == gui.vimForm->MenuBar() && bmenu->CountItems() == 0)
	    {
		bmenu->ResizeTo(-MENUBAR_MARGIN, -MENUBAR_MARGIN);
	    }
	}
	delete menu->id;
	menu->id = NULL;
	menu->submenu_id = NULL;

	gui.menu_height = (int) gui.vimForm->MenuHeight();
	gui.vimWindow->Unlock();
    }
}

/*
 * Make a menu either grey or not grey.
 */
    void
gui_mch_menu_grey(
    vimmenu_T	*menu,
    int		grey)
{
    if (menu->id != NULL)
	menu->id->SetEnabled(!grey);
}

/*
 * Make menu item hidden or not hidden
 */
    void
gui_mch_menu_hidden(
    vimmenu_T	*menu,
    int		hidden)
{
    if (menu->id != NULL)
	menu->id->SetEnabled(!hidden);
}

/*
 * This is called after setting all the menus to grey/hidden or not.
 */
    void
gui_mch_draw_menubar()
{
    /* Nothing to do in BeOS */
}

#endif /* FEAT_MENU */

/* Mouse stuff */

#ifdef FEAT_CLIPBOARD
/*
 * Clipboard stuff, for cutting and pasting text to other windows.
 */
char textplain[] = "text/plain";
char vimselectiontype[] = "application/x-vnd.Rhialto-Vim-selectiontype";

/*
 * Get the current selection and put it in the clipboard register.
 */
    void
clip_mch_request_selection(VimClipboard *cbd)
{
    if (be_clipboard->Lock())
    {
	BMessage *m = be_clipboard->Data();
	//m->PrintToStream();

	char_u *string = NULL;
	ssize_t stringlen = -1;

	if (m->FindData(textplain, B_MIME_TYPE,
				   (const void **)&string, &stringlen) == B_OK
		|| m->FindString("text", (const char **)&string) == B_OK)
	{
	    if (stringlen == -1)
		stringlen = STRLEN(string);

	    int type;
	    char *seltype;
	    ssize_t seltypelen;

	    /*
	     * Try to get the special vim selection type first
	     */
	    if (m->FindData(vimselectiontype, B_MIME_TYPE,
		    (const void **)&seltype, &seltypelen) == B_OK)
	    {
		switch (*seltype)
		{
		    default:
		    case 'L':	type = MLINE;	break;
		    case 'C':	type = MCHAR;	break;
#ifdef FEAT_VISUAL
		    case 'B':	type = MBLOCK;	break;
#endif
		}
	    }
	    else
	    {
		/* Otherwise use heuristic as documented */
		type = memchr(string, stringlen, '\n') ? MLINE : MCHAR;
	    }
	    clip_yank_selection(type, string, (long)stringlen, cbd);
	}
	be_clipboard->Unlock();
    }
}
/*
 * Make vim the owner of the current selection.
 */
    void
clip_mch_lose_selection(VimClipboard *cbd)
{
    /* Nothing needs to be done here */
}

/*
 * Make vim the owner of the current selection.  Return OK upon success.
 */
    int
clip_mch_own_selection(VimClipboard *cbd)
{
    /*
     * Never actually own the clipboard.  If another application sets the
     * clipboard, we don't want to think that we still own it.
     */
    return FAIL;
}

/*
 * Send the current selection to the clipboard.
 */
    void
clip_mch_set_selection(VimClipboard *cbd)
{
    if (be_clipboard->Lock())
    {
	be_clipboard->Clear();
	BMessage *m = be_clipboard->Data();
	assert(m);

	/* If the '*' register isn't already filled in, fill it in now */
	cbd->owned = TRUE;
	clip_get_selection(cbd);
	cbd->owned = FALSE;

	char_u  *str = NULL;
	long_u  count;
	int	type;

	type = clip_convert_selection(&str, &count, cbd);

	if (type < 0)
	    return;

	m->AddData(textplain, B_MIME_TYPE, (void *)str, count);

	/* Add type of selection */
	char    vtype;
	switch (type)
	{
	    default:
	    case MLINE:    vtype = 'L';    break;
	    case MCHAR:    vtype = 'C';    break;
#ifdef FEAT_VISUAL
	    case MBLOCK:   vtype = 'B';    break;
#endif
	}
	m->AddData(vimselectiontype, B_MIME_TYPE, (void *)&vtype, 1);

	vim_free(str);

	be_clipboard->Commit();
	be_clipboard->Unlock();
    }
}

#endif	/* FEAT_CLIPBOARD */

/*
 * Return the RGB value of a pixel as long.
 */
    long_u
gui_mch_get_rgb(guicolor_T pixel)
{
    rgb_color rgb = GUI_TO_RGB(pixel);

    return ((rgb.red & 0xff) << 16) + ((rgb.green & 0xff) << 8)
							  + (rgb.blue & 0xff);
}

    void
gui_mch_setmouse(int x, int y)
{
    TRACE();
    /* TODO */
}

    void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
    TRACE();
    /* TODO */
}

int
gui_mch_get_mouse_x()
{
    TRACE();
    return 0;
}


int
gui_mch_get_mouse_y()
{
    TRACE();
    return 0;
}

} /* extern "C" */
