view src/gui_haiku.cc @ 34074:1629cc65d78d v9.1.0006

patch 9.1.0006: is*() and to*() function may be unsafe Commit: https://github.com/vim/vim/commit/184f71cc6868a240dc872ed2852542bbc1d43e28 Author: Keith Thompson <Keith.S.Thompson@gmail.com> Date: Thu Jan 4 21:19:04 2024 +0100 patch 9.1.0006: is*() and to*() function may be unsafe Problem: is*() and to*() function may be unsafe Solution: Add SAFE_* macros and start using those instead (Keith Thompson) Use SAFE_() macros for is*() and to*() functions The standard is*() and to*() functions declared in <ctype.h> have undefined behavior for negative arguments other than EOF. If plain char is signed, passing an unchecked value from argv for from user input to one of these functions has undefined behavior. Solution: Add SAFE_*() macros that cast the argument to unsigned char. Most implementations behave sanely for negative arguments, and most character values in practice are non-negative, but it's still best to avoid undefined behavior. The change from #13347 has been omitted, as this has already been separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a (v9.0.2054) fixes: #13332 closes: #13347 Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 04 Jan 2024 21:30:04 +0100
parents 695b50472e85
children
line wrap: on
line source

/* 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.
 *
 * Based on "GUI support for the Buzzword Enhanced Operating System."
 *
 * Ported to R4 by Richard Offer <richard@whitequeen.com> Jul 99
 *
 * Haiku support by Siarzhuk Zharski <imker@gmx.li> Apr-Mai 2009
 *
 */

/*
 * Structure of the Haiku 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" {

#include <assert.h>
#include <float.h>
#include <syslog.h>

#include "vim.h"
#include "version.h"

}   // extern "C"

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

//#include <Alert.h>
#include <Application.h>
#include <Beep.h>
#include <Bitmap.h>
#include <Box.h>
#include <Button.h>
#include <Clipboard.h>
#include <Debug.h>
//#include <Directory.h>
//#include <Entry.h>
#include <File.h>
#include <FilePanel.h>
#include <FindDirectory.h>
//#include <Font.h>
#include <IconUtils.h>
#include <Input.h>
#include <ListView.h>
#include <MenuBar.h>
#include <MenuItem.h>
//#include <MessageQueue.h>
//#include <OS.h>
#include <Path.h>
#include <PictureButton.h>
#include <PopUpMenu.h>
//#include <Region.h>
#include <Resources.h>
//#include <Roster.h>
#include <Screen.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <String.h>
#include <StringView.h>
//#include <SupportDefs.h>
#include <TabView.h>
#include <TextControl.h>
#include <TextView.h>
#include <TranslationUtils.h>
#include <TranslatorFormats.h>
#include <View.h>
#include <Window.h>

class VimApp;
class VimFormView;
class VimTextAreaView;
class VimWindow;
class VimToolbar;
class VimTabLine;

extern key_map *keyMap;
extern char *keyMapChars;

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

#ifndef B_MAX_PORT_COUNT
#define B_MAX_PORT_COUNT    255
#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();
    virtual void MessageReceived(BMessage *m);

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

    sem_id	fFilePanelSem;
    BFilePanel*	fFilePanel;
    BPath	fBrowsedPath;
    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;

#ifdef FEAT_TOOLBAR
    public:
    float ToolbarHeight() const;
    VimToolbar *ToolBar() const
    { return toolBar; }
    private:
    VimToolbar	    *toolBar;
#endif

#ifdef FEAT_GUI_TABLINE
    public:
    VimTabLine *TabLine() const	{ return tabLine; }
    bool IsShowingTabLine() const { return showingTabLine; }
    void SetShowingTabLine(bool showing) { showingTabLine = showing;	}
    float TablineHeight() const;
    private:
    VimTabLine	*tabLine;
    int	showingTabLine;
#endif
};

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 guiMouseMoved(int x, int y);
    static void guiBlankMouse(bool should_hide);
    static int_u mouseModifiersToVim(int32 beModifiers);

    int32 mouseDragEventCount;

#ifdef FEAT_MBYTE_IME
    void DrawIMString(void);
#endif

    private:
    void init(BRect);

    int_u	vimMouseButton;
    int_u	vimMouseModifiers;

#ifdef FEAT_MBYTE_IME
    struct {
	BMessenger* messenger;
	BMessage* message;
	BPoint location;
	int row;
	int col;
	int count;
    } IMData;
#endif
};

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;
};


#ifdef FEAT_TOOLBAR

class VimToolbar : public BBox
{
    static BBitmap *normalButtonsBitmap;
    static BBitmap *grayedButtonsBitmap;

    BBitmap *LoadVimBitmap(const char* fileName);
    bool GetPictureFromBitmap(BPicture *pictureTo, int32 index, BBitmap *bitmapFrom, bool pressed);
    bool ModifyBitmapToGrayed(BBitmap *bitmap);

    BList fButtonsList;
    void InvalidateLayout();

    public:
    VimToolbar(BRect frame, const char * name);
    ~VimToolbar();

    bool PrepareButtonBitmaps();

    bool AddButton(int32 index, vimmenu_T *menu);
    bool RemoveButton(vimmenu_T *menu);
    bool GrayButton(vimmenu_T *menu, int grey);

    float ToolbarHeight() const;
    virtual void AttachedToWindow();
};

BBitmap *VimToolbar::normalButtonsBitmap  = NULL;
BBitmap *VimToolbar::grayedButtonsBitmap  = NULL;

const float ToolbarMargin = 3.;
const float ButtonMargin  = 3.;

#endif //FEAT_TOOLBAR

#ifdef FEAT_GUI_TABLINE

class VimTabLine : public BTabView
{
    public:
	class VimTab : public BTab {
	    public:
		VimTab() : BTab(new BView(BRect(), "-Empty-", 0, 0)) {}

	    virtual void Select(BView* owner);
	};

	VimTabLine(BRect r) : BTabView(r, "vimTabLine", B_WIDTH_FROM_LABEL,
	       B_FOLLOW_LEFT | B_FOLLOW_TOP | B_FOLLOW_RIGHT, B_WILL_DRAW | B_FRAME_EVENTS) {}

    float TablineHeight() const;
    virtual void MouseDown(BPoint point);
};

#endif //FEAT_GUI_TABLINE


// 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();
};

#if defined(FEAT_GUI_DIALOG)

class VimDialog : public BWindow
{
    typedef BWindow Inherited;

    BButton* _CreateButton(int32 which, const char* label);

    public:

    class View : public BView {
	typedef BView Inherited;

	public:
	View(BRect frame);
	~View();

	virtual void Draw(BRect updateRect);
	void InitIcon(int32 type);

	private:
	BBitmap*    fIconBitmap;
    };

    VimDialog(int type, const char *title, const char *message,
	    const char *buttons, int dfltbutton, const char *textfield,
	    int ex_cmd);
    ~VimDialog();

    int Go();

    virtual void MessageReceived(BMessage *msg);

    private:
    sem_id	    fDialogSem;
    int		    fDialogValue;
    BList	    fButtonsList;
    BTextView*	    fMessageView;
    BTextControl*   fInputControl;
    const char*	    fInputValue;
};

class VimSelectFontDialog : public BWindow
{
    typedef BWindow Inherited;

    void _CleanList(BListView* list);
    void _UpdateFontStyles();
    void _UpdateSizeInputPreview();
    void _UpdateFontPreview();
    bool _UpdateFromListItem(BListView* list, char* text, int textSize);
    public:

    VimSelectFontDialog(font_family* family, font_style* style, float* size);
    ~VimSelectFontDialog();

    bool Go();

    virtual void MessageReceived(BMessage *msg);

    private:
    status_t	    fStatus;
    sem_id	    fDialogSem;
    bool	    fDialogValue;
    font_family*    fFamily;
    font_style*	    fStyle;
    float*	    fSize;
    font_family	    fFontFamily;
    font_style	    fFontStyle;
    float	    fFontSize;
    BStringView*    fPreview;
    BListView*	    fFamiliesList;
    BListView*	    fStylesList;
    BListView*	    fSizesList;
    BTextControl*   fSizesInput;
};

#endif // FEAT_GUI_DIALOG

// ---------------- 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
    bool    csi_escape;
};

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 VimMouseMovedMsg {
    int	    x;
    int	    y;
};

struct VimFocusMsg {
    bool    active;
};

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

struct VimTablineMsg {
    int	    index;
};

struct VimTablineMenuMsg {
    int	    index;
    int	    event;
};

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

    union {
	struct VimKeyMsg    Key;
	struct VimResizeMsg NewSize;
	struct VimScrollBarMsg	Scroll;
	struct VimMenuMsg   Menu;
	struct VimMouseMsg  Mouse;
	struct VimMouseMovedMsg	MouseMoved;
	struct VimFocusMsg  Focus;
	struct VimRefsMsg   Refs;
	struct VimTablineMsg	Tabline;
	struct VimTablineMenuMsg    TablineMenu;
    } u;
};

#define RGB(r, g, b)	((char_u)(r) << 16 | (char_u)(g) << 8 | (char_u)(b) << 0)
#define GUI_TO_RGB(g)	{ (char_u)((g) >> 16), (char_u)((g) >> 8), (char_u)((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    ARRAY_LENGTH(special_keys)

// ---------------- VimApp ----------------

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

	static void
drop_callback(void *cookie)
{
    // TODO here we could handle going to a specific position in the dropped
    // file (see src/gui_mac.c, deleted in 8.2.1422)
    // Update the screen display
    update_screen(UPD_NOT_VALID);
}

    // 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, drop_callback, NULL);

	setcursor();
	out_flush();
    } else {
	vim_free(fnames);
    }
}

VimApp::VimApp(const char *appsig):
    BApplication(appsig),
    fFilePanelSem(-1),
    fFilePanel(NULL)
{
}

VimApp::~VimApp()
{
}

    void
VimApp::ReadyToRun()
{
    /*
     * Apparently signals are inherited by the created thread -
     * disable the most annoying ones.
     */
    mch_signal(SIGINT, SIG_IGN);
    mch_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
}

    void
VimApp::MessageReceived(BMessage *m)
{
    switch (m->what) {
	case 'save':
	    {
		entry_ref refDirectory;
		m->FindRef("directory", &refDirectory);
		fBrowsedPath.SetTo(&refDirectory);
		BString strName;
		m->FindString("name", &strName);
		fBrowsedPath.Append(strName.String());
	    }
	    break;
	case 'open':
	    {
		entry_ref ref;
		m->FindRef("refs", &ref);
		fBrowsedPath.SetTo(&ref);
	    }
	    break;
	case B_CANCEL:
	    {
		BFilePanel *panel;
		m->FindPointer("source", (void**)&panel);
		if (fFilePanelSem != -1 && panel == fFilePanel)
		{
		    delete_sem(fFilePanelSem);
		    fFilePanelSem = -1;
		}

	    }
	    break;
	default:
	    Inherited::MessageReceived(m);
	    break;
    }
}

    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);
    }
}

#if 0  //  disabled in zeta patch
    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);
    }
}
#endif

    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);
    km.csi_escape = false;
    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),
#ifdef FEAT_TOOLBAR
    toolBar(NULL),
#endif
#ifdef FEAT_GUI_TABLINE
//  showingTabLine(false),
    tabLine(NULL),
#endif
    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
    }

#ifdef FEAT_TOOLBAR
    delete toolBar;
#endif

#ifdef FEAT_GUI_TABLINE
    delete tabLine;
#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);

#ifdef FEAT_TOOLBAR
    toolBar = new VimToolbar(BRect(0,0,0,0), "VimToolBar");
    toolBar->PrepareButtonBitmaps();
    AddChild(toolBar);
#endif

#ifdef FEAT_GUI_TABLINE
    tabLine = new VimTabLine(BRect(0,0,0,0));
//  tabLine->PrepareButtonBitmaps();
    AddChild(tabLine);
#endif

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

    gui.vimForm = this;
}

#ifdef FEAT_TOOLBAR
    float
VimFormView::ToolbarHeight() const
{
    return toolBar ? toolBar->ToolbarHeight() : 0.;
}
#endif

#ifdef FEAT_GUI_TABLINE
    float
VimFormView::TablineHeight() const
{
    return (tabLine && IsShowingTabLine()) ? tabLine->TablineHeight() : 0.;
}
#endif

    void
VimFormView::AllAttached()
{
    /*
     * Apparently signals are inherited by the created thread -
     * disable the most annoying ones.
     */
    mch_signal(SIGINT, SIG_IGN);
    mch_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();

#ifdef FEAT_MENU
	remaining.top += MenuHeight();
	menuBar->ResizeTo(remaining.right, remaining.top);
	gui.menu_height = (int) MenuHeight();
#endif

#ifdef FEAT_TOOLBAR
	toolBar->MoveTo(remaining.left, remaining.top);
	toolBar->ResizeTo(remaining.right, ToolbarHeight());
	remaining.top += ToolbarHeight();
	gui.toolbar_height = ToolbarHeight();
#endif

#ifdef FEAT_GUI_TABLINE
	tabLine->MoveTo(remaining.left, remaining.top);
	tabLine->ResizeTo(remaining.right + 1, TablineHeight());
	remaining.top += TablineHeight();
	gui.tabline_height = TablineHeight();
#endif

	textArea->ResizeTo(remaining.Width(), remaining.Height());
	textArea->MoveTo(remaining.left, remaining.top);
    }


    Inherited::AllAttached();
}

    void
VimFormView::FrameResized(float new_width, float new_height)
{
    struct VimResizeMsg sm;
    int adjust_h, adjust_w;

    new_width += 1;	//  adjust from width to number of pixels occupied
    new_height += 1;

    sm.width = (int) new_width;
    sm.height = (int) new_height;
    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) {
	sm.width  -= adjust_w;
	sm.height -= adjust_h;
    }

    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,
#ifdef FEAT_MBYTE_IME
	B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_INPUT_METHOD_AWARE
#else
	B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
#endif
	),
    mouseDragEventCount(0)
{
#ifdef FEAT_MBYTE_IME
    IMData.messenger = NULL;
    IMData.message = NULL;
#endif
    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);
#define W_WIDTH(curwin)   0
}

    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.
     */
    rgb_color rgb = GUI_TO_RGB(gui.back_pixel);
    SetLowColor(rgb);
    FillRect(updateRect, B_SOLID_LOW);
    gui_redraw((int) updateRect.left, (int) updateRect.top,
	    (int) (updateRect.Width() + PEN_WIDTH), (int) (updateRect.Height() + PEN_WIDTH));

    // Clear the border areas if needed
    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);

#ifdef FEAT_MBYTE_IME
    DrawIMString();
#endif
}

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

    bool canHaveVimModifiers = false;

    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);
	km.csi_escape = true;
    } 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;

	/*
	 * 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;
	km.csi_escape = false;
    }

    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::guiMouseMoved(
	int	x,
	int	y)
{
    VimMouseMovedMsg mm;

    mm.x = x;
    mm.y = y;

    write_port(gui.vdcmp, VimMsg::MouseMoved, &mm, sizeof(mm));

    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"
	guiMouseMoved(point.x, point.y);
	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;
	case B_MOUSE_WHEEL_CHANGED:
	    {
		VimScrollBar* scb = curwin->w_scrollbars[1].id;
		float small=0, big=0, dy=0;
		m->FindFloat("be:wheel_delta_y", &dy);
		scb->GetSteps(&small, &big);
		scb->SetValue(scb->Value()+small*dy*3);
		scb->ValueChanged(scb->Value());
#if 0
		scb = curwin->w_scrollbars[0].id;
		scb->GetSteps(&small, &big);
		scb->SetValue(scb->Value()+small*dy);
		scb->ValueChanged(scb->Value());
#endif
	    }
	    break;
#ifdef FEAT_MBYTE_IME
	case B_INPUT_METHOD_EVENT:
	    {
		int32 opcode;
		m->FindInt32("be:opcode", &opcode);
		switch(opcode)
		{
		    case B_INPUT_METHOD_STARTED:
			if (!IMData.messenger) delete IMData.messenger;
			IMData.messenger = new BMessenger();
			m->FindMessenger("be:reply_to", IMData.messenger);
			break;
		    case B_INPUT_METHOD_CHANGED:
			{
			    BString str;
			    bool confirmed;
			    if (IMData.message) *(IMData.message) = *m;
			    else	       IMData.message = new BMessage(*m);
			    DrawIMString();
			    m->FindBool("be:confirmed", &confirmed);
			    if (confirmed)
			    {
				m->FindString("be:string", &str);
				char_u *chars = (char_u*)str.String();
				struct VimKeyMsg km;
				km.csi_escape = true;
				int clen;
				int i = 0;
				while (i < str.Length())
				{
				    clen = utf_ptr2len(chars+i);
				    memcpy(km.chars, chars+i, clen);
				    km.length = clen;
				    write_port(gui.vdcmp, VimMsg::Key, &km, sizeof(km));
				    i += clen;
				}
			    }
			}
			break;
		    case B_INPUT_METHOD_LOCATION_REQUEST:
			{
			    BMessage msg(B_INPUT_METHOD_EVENT);
			    msg.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
			    msg.AddPoint("be:location_reply", IMData.location);
			    msg.AddFloat("be:height_reply", FILL_Y(1));
			    IMData.messenger->SendMessage(&msg);
			}
			break;
		    case B_INPUT_METHOD_STOPPED:
			delete IMData.messenger;
			delete IMData.message;
			IMData.messenger = NULL;
			IMData.message = NULL;
			break;
		}
	    }
	    // TODO: sz: break here???
#endif
	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, 1);
    if (newFont != NOFONT) {
	gui.norm_font = (GuiFont)newFont;
	gui_mch_set_font((GuiFont)newFont);
	if (name && STRCMP(name, "*") != 0)
	    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;
    }
    return FAIL;
}

    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)) {
	int cells;
	cells = 0;
	for (int i=0; i<len; i++) {
	    int cn = utf_ptr2cells((char_u *)(s+i));
	    if (cn<4) cells += cn;
	}

	BRect r(FILL_X(col), FILL_Y(row),
		FILL_X(col + cells) - PEN_WIDTH, FILL_Y(row + 1) - PEN_WIDTH);
	FillRect(r, B_SOLID_LOW);
    }

    BFont font;
    this->GetFont(&font);
    if (!font.IsFixed())
    {
	char* p = (char*)s;
	int32 clen, lastpos = 0;
	BPoint where;
	int cells;
	while ((p - (char*)s) < len) {
	    clen = utf_ptr2len((u_char*)p);
	    where.Set(TEXT_X(col+lastpos), TEXT_Y(row));
	    DrawString(p, clen, where);
	    if (flags & DRAW_BOLD) {
		where.x += 1.0;
		SetDrawingMode(B_OP_BLEND);
		DrawString(p, clen, where);
		SetDrawingMode(B_OP_COPY);
	    }
	    cells = utf_ptr2cells((char_u *)p);
	    if (cells<4) lastpos += cells;
	    else	lastpos++;
	    p += clen;
	}
    }
    else
    {
	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) {
	int cells;
	cells = 0;
	for (int i=0; i<len; i++) {
	    int cn = utf_ptr2cells((char_u *)(s+i));
	    if (cn<4) cells += cn;
	}

	BPoint start(FILL_X(col), FILL_Y(row + 1) - PEN_WIDTH);
	BPoint end(FILL_X(col + cells) - 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)
{
    BRect source, dest;
    source.left = FILL_X(gui.scroll_region_left);
    source.top = FILL_Y(row + num_lines);
    source.right = FILL_X(gui.scroll_region_right + 1) - PEN_WIDTH;
    source.bottom = FILL_Y(gui.scroll_region_bot + 1) - PEN_WIDTH;

    dest.left = FILL_X(gui.scroll_region_left);
    dest.top = FILL_Y(row);
    dest.right = FILL_X(gui.scroll_region_right + 1) - PEN_WIDTH;
    dest.bottom = FILL_Y(gui.scroll_region_bot - num_lines + 1) - PEN_WIDTH;

    if (gui.vimWindow->Lock()) {
	// Clear one column more for when bold has spilled over
	CopyBits(source, dest);
	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)
{
    BRect source, dest;

    // XXX Attempt at a hack:
    gui.vimWindow->UpdateIfNeeded();
    source.left = FILL_X(gui.scroll_region_left);
    source.top = FILL_Y(row);
    source.right = FILL_X(gui.scroll_region_right + 1) - PEN_WIDTH;
    source.bottom = FILL_Y(gui.scroll_region_bot - num_lines + 1) - PEN_WIDTH;

    dest.left = FILL_X(gui.scroll_region_left);
    dest.top = FILL_Y(row + num_lines);
    dest.right = FILL_X(gui.scroll_region_right + 1) - PEN_WIDTH;
    dest.bottom = FILL_Y(gui.scroll_region_bot + 1) - PEN_WIDTH;

    if (gui.vimWindow->Lock()) {
	// Clear one column more for when bold has spilled over
	CopyBits(source, dest);
	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();

    }
}

#ifdef FEAT_MBYTE_IME
/*
 * DrawIMString draws string with IMData.message.
 */
void VimTextAreaView::DrawIMString(void)
{
    static const rgb_color r_highlight = {255, 152, 152, 255},
		 b_highlight = {152, 203, 255, 255};
    BString str;
    const char* s;
    int len;
    BMessage* msg = IMData.message;
    if (!msg)
	return;
    gui_redraw_block(IMData.row, 0,
	    IMData.row + IMData.count, W_WIDTH(curwin), GUI_MON_NOCLEAR);
    bool confirmed = false;
    msg->FindBool("be:confirmed", &confirmed);
    if (confirmed)
	return;
    rgb_color hcolor = HighColor(), lcolor = LowColor();
    msg->FindString("be:string", &str);
    s = str.String();
    len = str.Length();
    SetHighColor(0, 0, 0);
    IMData.row = gui.row;
    IMData.col = gui.col;
    int32 sel_start = 0, sel_end = 0;
    msg->FindInt32("be:selection", 0, &sel_start);
    msg->FindInt32("be:selection", 1, &sel_end);
    int clen, cn;
    BPoint pos(IMData.col, 0);
    BRect r;
    BPoint where;
    IMData.location = ConvertToScreen(
	    BPoint(FILL_X(pos.x), FILL_Y(IMData.row + pos.y)));
    for (int i=0; i<len; i+=clen)
    {
	cn = utf_ptr2cells((char_u *)(s+i));
	clen = utf_ptr2len((char_u *)(s+i));
	if (pos.x + cn > W_WIDTH(curwin))
	{
	    pos.y++;
	    pos.x = 0;
	}
	if (sel_start<=i && i<sel_end)
	{
	    SetLowColor(r_highlight);
	    IMData.location = ConvertToScreen(
		    BPoint(FILL_X(pos.x), FILL_Y(IMData.row + pos.y)));
	}
	else
	{
	    SetLowColor(b_highlight);
	}
	r.Set(FILL_X(pos.x), FILL_Y(IMData.row + pos.y),
		FILL_X(pos.x + cn) - PEN_WIDTH,
		FILL_Y(IMData.row + pos.y + 1) - PEN_WIDTH);
	FillRect(r, B_SOLID_LOW);
	where.Set(TEXT_X(pos.x), TEXT_Y(IMData.row + pos.y));
	DrawString((s+i), clen, where);
	pos.x += cn;
    }
    IMData.count = (int)pos.y;

    SetHighColor(hcolor);
    SetLowColor(lcolor);
}
#endif
// ---------------- 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;
}

// ---------------- VimDialog ----------------

#if defined(FEAT_GUI_DIALOG)

const unsigned int  kVimDialogButtonMsg = 'VMDB';
const unsigned int  kVimDialogIconStripeWidth = 30;
const unsigned int  kVimDialogButtonsSpacingX = 9;
const unsigned int  kVimDialogButtonsSpacingY = 4;
const unsigned int  kVimDialogSpacingX = 6;
const unsigned int  kVimDialogSpacingY = 10;
const unsigned int  kVimDialogMinimalWidth  = 310;
const unsigned int  kVimDialogMinimalHeight = 75;
const BRect	    kDefaultRect =
BRect(0, 0, kVimDialogMinimalWidth, kVimDialogMinimalHeight);

VimDialog::VimDialog(int type, const char *title, const char *message,
	const char *buttons, int dfltbutton, const char *textfield, int ex_cmd)
: BWindow(kDefaultRect, title, B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL,
	B_NOT_CLOSABLE | B_NOT_RESIZABLE |
	B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_ASYNCHRONOUS_CONTROLS)
    , fDialogSem(-1)
    , fDialogValue(dfltbutton)
    , fMessageView(NULL)
    , fInputControl(NULL)
    , fInputValue(textfield)
{
    //	master view
    VimDialog::View* view = new VimDialog::View(Bounds());
    if (view == NULL)
	return;

    if (title == NULL)
	SetTitle("Vim " VIM_VERSION_MEDIUM);

    AddChild(view);

    //	icon
    view->InitIcon(type);

    //	buttons
    int32 which = 1;
    float maxButtonWidth  = 0;
    float maxButtonHeight = 0;
    float buttonsWidth	  = 0;
    float buttonsHeight   = 0;
    BString strButtons(buttons);
    strButtons.RemoveAll("&");
    do {
	int32 end = strButtons.FindFirst('\n');
	if (end != B_ERROR)
	    strButtons.SetByteAt(end, '\0');

	BButton *button = _CreateButton(which++, strButtons.String());
	view->AddChild(button);
	fButtonsList.AddItem(button);

	maxButtonWidth	= max_c(maxButtonWidth,  button->Bounds().Width());
	maxButtonHeight = max_c(maxButtonHeight, button->Bounds().Height());
	buttonsWidth   += button->Bounds().Width();
	buttonsHeight  += button->Bounds().Height();

	if (end == B_ERROR)
	    break;

	strButtons.Remove(0, end + 1);
    } while (true);

    int32 buttonsCount = fButtonsList.CountItems();
    buttonsWidth      += kVimDialogButtonsSpacingX * (buttonsCount - 1);
    buttonsHeight     += kVimDialogButtonsSpacingY * (buttonsCount - 1);
    float dialogWidth  = buttonsWidth + kVimDialogIconStripeWidth +
	kVimDialogSpacingX * 2;
    float dialogHeight = maxButtonHeight + kVimDialogSpacingY * 3;

    // Check 'v' flag in 'guioptions': vertical button placement.
    bool vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL) ||
	dialogWidth >= gui.vimWindow->Bounds().Width();
    if (vertical) {
	dialogWidth  -= buttonsWidth;
	dialogWidth  += maxButtonWidth;
	dialogHeight -= maxButtonHeight;
	dialogHeight += buttonsHeight;
    }

    dialogWidth  = max_c(dialogWidth,  kVimDialogMinimalWidth);

    //	message view
    BRect rect(0, 0, dialogWidth, 0);
    rect.left  += kVimDialogIconStripeWidth + 16 + kVimDialogSpacingX;
    rect.top   += kVimDialogSpacingY;
    rect.right -= kVimDialogSpacingX;
    rect.bottom = rect.top;
    fMessageView = new BTextView(rect, "_tv_", rect.OffsetByCopy(B_ORIGIN),
	    B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW);

    fMessageView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
    rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
    fMessageView->SetFontAndColor(be_plain_font, B_FONT_ALL, &textColor);
    fMessageView->SetText(message);
    fMessageView->MakeEditable(false);
    fMessageView->MakeSelectable(false);
    fMessageView->SetWordWrap(true);
    AddChild(fMessageView);

    float messageHeight = fMessageView->TextHeight(0, fMessageView->CountLines());
    fMessageView->ResizeBy(0, messageHeight);
    fMessageView->SetTextRect(BRect(0, 0, rect.Width(), messageHeight));

    dialogHeight += messageHeight;

    //	input view
    if (fInputValue != NULL) {
	rect.top     =
	    rect.bottom += messageHeight + kVimDialogSpacingY;
	fInputControl = new BTextControl(rect, "_iv_", NULL, fInputValue, NULL,
		B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE |  B_PULSE_NEEDED);
	fInputControl->TextView()->SetText(fInputValue);
	fInputControl->TextView()->SetWordWrap(false);
	AddChild(fInputControl);

	float width = 0.f, height = 0.f;
	fInputControl->GetPreferredSize(&width, &height);
	fInputControl->MakeFocus(true);

	dialogHeight += height + kVimDialogSpacingY * 1.5;
    }

    dialogHeight = max_c(dialogHeight, kVimDialogMinimalHeight);

    ResizeTo(dialogWidth, dialogHeight);
    MoveTo((gui.vimWindow->Bounds().Width() - dialogWidth) / 2,
	    (gui.vimWindow->Bounds().Height() - dialogHeight) / 2);

    //	adjust layout of buttons
    float buttonWidth = max_c(maxButtonWidth, rect.Width() * 0.66);
    BPoint origin(dialogWidth, dialogHeight);
    origin.x -= kVimDialogSpacingX + (vertical ? buttonWidth : buttonsWidth);
    origin.y -= kVimDialogSpacingY + (vertical ? buttonsHeight	: maxButtonHeight);

    for (int32 i = 0 ; i < buttonsCount; i++) {
	BButton *button = (BButton*)fButtonsList.ItemAt(i);
	button->MoveTo(origin);
	if (vertical) {
	    origin.y += button->Frame().Height() + kVimDialogButtonsSpacingY;
	    button->ResizeTo(buttonWidth, button->Frame().Height());
	} else
	    origin.x += button->Frame().Width() + kVimDialogButtonsSpacingX;

	if (dfltbutton == i + 1) {
	    button->MakeDefault(true);
	    button->MakeFocus(fInputControl == NULL);
	}
    }
}

VimDialog::~VimDialog()
{
    if (fDialogSem > B_OK)
	delete_sem(fDialogSem);
}

    int
VimDialog::Go()
{
    fDialogSem = create_sem(0, "VimDialogSem");
    if (fDialogSem < B_OK) {
	Quit();
	return fDialogValue;
    }

    Show();

    while (acquire_sem(fDialogSem) == B_INTERRUPTED);

    int retValue = fDialogValue;
    if (fInputValue != NULL)
	vim_strncpy((char_u*)fInputValue, (char_u*)fInputControl->Text(), IOSIZE - 1);

    if (Lock())
	Quit();

    return retValue;
}

void VimDialog::MessageReceived(BMessage *msg)
{
    int32 which = 0;
    if (msg->what != kVimDialogButtonMsg ||
	    msg->FindInt32("which", &which) != B_OK)
	return BWindow::MessageReceived(msg);

    fDialogValue = which;
    delete_sem(fDialogSem);
    fDialogSem = -1;
}

BButton* VimDialog::_CreateButton(int32 which, const char* label)
{
    BMessage *message = new BMessage(kVimDialogButtonMsg);
    message->AddInt32("which", which);

    BRect rect(0, 0, 0, 0);
    BString name;
    name << "_b" << which << "_";

    BButton* button = new BButton(rect, name.String(), label, message,
	    B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);

    float width = 0.f, height = 0.f;
    button->GetPreferredSize(&width, &height);
    button->ResizeTo(width, height);

    return button;
}

VimDialog::View::View(BRect frame)
    :	BView(frame, "VimDialogView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
    fIconBitmap(NULL)
{
    SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}

VimDialog::View::~View()
{
    delete fIconBitmap;
}

void VimDialog::View::Draw(BRect updateRect)
{
    BRect stripeRect = Bounds();
    stripeRect.right = kVimDialogIconStripeWidth;
    SetHighColor(tint_color(ViewColor(), B_DARKEN_1_TINT));
    FillRect(stripeRect);

    if (fIconBitmap == NULL)
	return;

    SetDrawingMode(B_OP_ALPHA);
    SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
    DrawBitmapAsync(fIconBitmap, BPoint(18, 6));
}

void VimDialog::View::InitIcon(int32 type)
{
    if (type == VIM_GENERIC)
	return;

    BPath path;
    status_t status = find_directory(B_BEOS_SERVERS_DIRECTORY, &path);
    if (status != B_OK) {
	fprintf(stderr, "Cannot retrieve app info:%s\n", strerror(status));
	return;
    }

    path.Append("app_server");

    BFile file(path.Path(), O_RDONLY);
    if (file.InitCheck() != B_OK) {
	fprintf(stderr, "App file assignment failed:%s\n",
		strerror(file.InitCheck()));
	return;
    }

    BResources resources(&file);
    if (resources.InitCheck() != B_OK) {
	fprintf(stderr, "App server resources assignment failed:%s\n",
		strerror(resources.InitCheck()));
	return;
    }

    const char *name = "";
    switch(type) {
	case VIM_ERROR:	    name = "stop"; break;
	case VIM_WARNING:   name = "warn"; break;
	case VIM_INFO:	    name = "info"; break;
	case VIM_QUESTION:  name = "idea"; break;
	default: return;
    }

    int32 iconSize = 32;
    fIconBitmap = new BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32);
    if (fIconBitmap == NULL || fIconBitmap->InitCheck() != B_OK) {
	fprintf(stderr, "Icon bitmap allocation failed:%s\n",
		(fIconBitmap == NULL) ? "null" : strerror(fIconBitmap->InitCheck()));
	return;
    }

    size_t size = 0;
    const uint8* iconData = NULL;
    //	try vector icon first?
    iconData = (const uint8*)resources.LoadResource(B_VECTOR_ICON_TYPE, name, &size);
    if (iconData != NULL && BIconUtils::GetVectorIcon(iconData, size, fIconBitmap) == B_OK)
	return;

    //	try bitmap icon now
    iconData = (const uint8*)resources.LoadResource(B_LARGE_ICON_TYPE, name, &size);
    if (iconData == NULL) {
	fprintf(stderr, "Bitmap icon resource not found\n");
	delete fIconBitmap;
	fIconBitmap = NULL;
	return;
    }

    if (fIconBitmap->ColorSpace() != B_CMAP8)
	BIconUtils::ConvertFromCMAP8(iconData, iconSize, iconSize, iconSize, fIconBitmap);
}

const unsigned int  kVimDialogOKButtonMsg = 'FDOK';
const unsigned int  kVimDialogCancelButtonMsg = 'FDCN';
const unsigned int  kVimDialogSizeInputMsg = 'SICH';
const unsigned int  kVimDialogFamilySelectMsg = 'MSFM';
const unsigned int  kVimDialogStyleSelectMsg = 'MSST';
const unsigned int  kVimDialogSizeSelectMsg = 'MSSZ';

VimSelectFontDialog::VimSelectFontDialog(font_family* family, font_style* style, float* size)
: BWindow(kDefaultRect, "Font Selection", B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL,
	B_NOT_CLOSABLE | B_NOT_RESIZABLE |
	B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_ASYNCHRONOUS_CONTROLS)
    , fStatus(B_NO_INIT)
    , fDialogSem(-1)
    , fDialogValue(false)
    , fFamily(family)
    , fStyle(style)
    , fSize(size)
    , fFontSize(*size)
    , fPreview(0)
    , fFamiliesList(0)
    , fStylesList(0)
    , fSizesList(0)
    , fSizesInput(0)
{
    strncpy(fFontFamily, *family, B_FONT_FAMILY_LENGTH);
    strncpy(fFontStyle, *style, B_FONT_STYLE_LENGTH);

    //	"client" area view
    BBox *clientBox = new BBox(Bounds(), B_EMPTY_STRING, B_FOLLOW_ALL_SIDES,
		    B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP | B_PULSE_NEEDED,
		    B_PLAIN_BORDER);
    AddChild(clientBox);

    //	client view
    BRect RC = clientBox->Bounds();
    RC.InsetBy(kVimDialogSpacingX, kVimDialogSpacingY);
    BRect rc(RC.LeftTop(), RC.LeftTop());

    //	at first create all controls
    fPreview = new BStringView(rc, "preview", "DejaVu Sans Mono");
    clientBox->AddChild(fPreview);

    BBox* boxDivider = new BBox(rc, B_EMPTY_STRING,
	    B_FOLLOW_NONE, B_WILL_DRAW, B_FANCY_BORDER);
    clientBox->AddChild(boxDivider);

    BStringView *labelFamily = new BStringView(rc, "labelFamily", "Family:");
    clientBox->AddChild(labelFamily);
    labelFamily->ResizeToPreferred();

    BStringView *labelStyle = new BStringView(rc, "labelStyle", "Style:");
    clientBox->AddChild(labelStyle);
    labelStyle->ResizeToPreferred();

    BStringView *labelSize = new BStringView(rc, "labelSize", "Size:");
    clientBox->AddChild(labelSize);
    labelSize->ResizeToPreferred();

    fFamiliesList = new BListView(rc, "listFamily",
	    B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL_SIDES);
    BScrollView *scrollFamilies = new BScrollView("scrollFamily",
	    fFamiliesList, B_FOLLOW_LEFT_RIGHT, 0, false, true);
    clientBox->AddChild(scrollFamilies);

    fStylesList= new BListView(rc, "listStyles",
	    B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL_SIDES);
    BScrollView *scrollStyles = new BScrollView("scrollStyle",
	    fStylesList, B_FOLLOW_LEFT_RIGHT, 0, false, true);
    clientBox->AddChild(scrollStyles);

    fSizesInput = new BTextControl(rc, "inputSize", NULL, "???",
	    new BMessage(kVimDialogSizeInputMsg));
    clientBox->AddChild(fSizesInput);
    fSizesInput->ResizeToPreferred();

    fSizesList = new BListView(rc, "listSizes",
	    B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL_SIDES);
    BScrollView *scrollSizes = new BScrollView("scrollSize",
	    fSizesList, B_FOLLOW_LEFT_RIGHT, 0, false, true);
    clientBox->AddChild(scrollSizes);

    BButton *buttonOK = new BButton(rc, "buttonOK", "OK",
			new BMessage(kVimDialogOKButtonMsg));
    clientBox->AddChild(buttonOK);
    buttonOK->ResizeToPreferred();

    BButton *buttonCancel = new BButton(rc, "buttonCancel", "Cancel",
			new BMessage(kVimDialogCancelButtonMsg));
    clientBox->AddChild(buttonCancel);
    buttonCancel->ResizeToPreferred();

    //	layout controls
    float lineHeight = labelFamily->Bounds().Height();
    float previewHeight = lineHeight * 3;
    float offsetYLabels = previewHeight + kVimDialogSpacingY;
    float offsetYLists = offsetYLabels + lineHeight + kVimDialogSpacingY / 2;
    float offsetYSizes = offsetYLists + fSizesInput->Bounds().Height() + kVimDialogSpacingY / 2;
    float listsHeight = lineHeight * 9;
    float offsetYButtons = offsetYLists + listsHeight +  kVimDialogSpacingY;
    float maxControlsHeight = offsetYButtons + buttonOK->Bounds().Height();
    float familiesWidth = labelFamily->Bounds().Width() * 5;
    float offsetXStyles = familiesWidth + kVimDialogSpacingX;
    float stylesWidth = labelStyle->Bounds().Width() * 4;
    float offsetXSizes = offsetXStyles + stylesWidth + kVimDialogSpacingX;
    float sizesWidth = labelSize->Bounds().Width() * 2;
    float maxControlsWidth = offsetXSizes + sizesWidth;

    ResizeTo(maxControlsWidth + kVimDialogSpacingX * 2,
	maxControlsHeight + kVimDialogSpacingY * 2);

    BRect rcVim = gui.vimWindow->Frame();
    MoveTo(rcVim.left + (rcVim.Width() - Frame().Width()) / 2,
	    rcVim.top + (rcVim.Height() - Frame().Height()) / 2);

    fPreview->ResizeTo(maxControlsWidth, previewHeight);
    fPreview->SetAlignment(B_ALIGN_CENTER);

    boxDivider->MoveBy(0.f, previewHeight + kVimDialogSpacingY / 2);
    boxDivider->ResizeTo(maxControlsWidth, 1.f);

    labelFamily->MoveBy(0.f, offsetYLabels);
    labelStyle->MoveBy(offsetXStyles, offsetYLabels);
    labelSize->MoveBy(offsetXSizes, offsetYLabels);

    //	text control alignment issues
    float insetX = fSizesInput->TextView()->Bounds().Width() - fSizesInput->Bounds().Width();
    float insetY = fSizesInput->TextView()->Bounds().Width() - fSizesInput->Bounds().Width();

    scrollFamilies->MoveBy(0.f, offsetYLists);
    scrollStyles->MoveBy(offsetXStyles, offsetYLists);
    fSizesInput->MoveBy(offsetXSizes + insetX / 2, offsetYLists + insetY / 2);
    scrollSizes->MoveBy(offsetXSizes, offsetYSizes);

    fSizesInput->SetAlignment(B_ALIGN_CENTER, B_ALIGN_CENTER);

    scrollFamilies->ResizeTo(familiesWidth, listsHeight);
    scrollStyles->ResizeTo(stylesWidth, listsHeight);
    fSizesInput->ResizeTo(sizesWidth, fSizesInput->Bounds().Height());
    scrollSizes->ResizeTo(sizesWidth,
	    listsHeight - (offsetYSizes - offsetYLists));

    buttonOK->MoveBy(maxControlsWidth - buttonOK->Bounds().Width(), offsetYButtons);
    buttonCancel->MoveBy(maxControlsWidth - buttonOK->Bounds().Width()
	    - buttonCancel->Bounds().Width() - kVimDialogSpacingX, offsetYButtons);

    //	fill lists
    int selIndex = -1;
    int count = count_font_families();
    for (int i = 0; i < count; i++) {
	font_family family;
	if (get_font_family(i, &family ) == B_OK) {
	    fFamiliesList->AddItem(new BStringItem((const char*)family));
	    if (strncmp(family, fFontFamily, B_FONT_FAMILY_LENGTH) == 0)
		selIndex = i;
	}
    }

    if (selIndex >= 0) {
	fFamiliesList->Select(selIndex);
	fFamiliesList->ScrollToSelection();
    }

    _UpdateFontStyles();

    selIndex = -1;
    for (int size = 8, index = 0; size <= 18; size++, index++) {
	BString str;
	str << size;
	fSizesList->AddItem(new BStringItem(str));
	if (size == fFontSize)
	    selIndex = index;

    }

    if (selIndex >= 0) {
	fSizesList->Select(selIndex);
	fSizesList->ScrollToSelection();
    }

    fFamiliesList->SetSelectionMessage(new BMessage(kVimDialogFamilySelectMsg));
    fStylesList->SetSelectionMessage(new BMessage(kVimDialogStyleSelectMsg));
    fSizesList->SetSelectionMessage(new BMessage(kVimDialogSizeSelectMsg));
    fSizesInput->SetModificationMessage(new BMessage(kVimDialogSizeInputMsg));

    _UpdateSizeInputPreview();
    _UpdateFontPreview();

    fStatus = B_OK;
}

VimSelectFontDialog::~VimSelectFontDialog()
{
    _CleanList(fFamiliesList);
    _CleanList(fStylesList);
    _CleanList(fSizesList);

    if (fDialogSem > B_OK)
	delete_sem(fDialogSem);
}

    void
VimSelectFontDialog::_CleanList(BListView* list)
{
    while (0 < list->CountItems())
	delete (dynamic_cast<BStringItem*>(list->RemoveItem((int32)0)));
}

    bool
VimSelectFontDialog::Go()
{
    if (fStatus != B_OK) {
	Quit();
	return NOFONT;
    }

    fDialogSem = create_sem(0, "VimFontSelectDialogSem");
    if (fDialogSem < B_OK) {
	Quit();
	return fDialogValue;
    }

    Show();

    while (acquire_sem(fDialogSem) == B_INTERRUPTED);

    bool retValue = fDialogValue;

    if (Lock())
	Quit();

    return retValue;
}


void VimSelectFontDialog::_UpdateFontStyles()
{
    _CleanList(fStylesList);

    int32 selIndex = -1;
    int32 count = count_font_styles(fFontFamily);
    for (int32 i = 0; i < count; i++) {
	font_style style;
	uint32 flags = 0;
	if (get_font_style(fFontFamily, i, &style, &flags) == B_OK) {
	    fStylesList->AddItem(new BStringItem((const char*)style));
	    if (strncmp(style, fFontStyle, B_FONT_STYLE_LENGTH) == 0)
		selIndex = i;
	}
    }

    if (selIndex >= 0) {
	fStylesList->Select(selIndex);
	fStylesList->ScrollToSelection();
    } else
	fStylesList->Select(0);
}


void VimSelectFontDialog::_UpdateSizeInputPreview()
{
    char buf[10] = {0};
    vim_snprintf(buf, sizeof(buf), (char*)"%.0f", fFontSize);
    fSizesInput->SetText(buf);
}


void VimSelectFontDialog::_UpdateFontPreview()
{
    BFont font;
    fPreview->GetFont(&font);
    font.SetSize(fFontSize);
    font.SetFamilyAndStyle(fFontFamily, fFontStyle);
    fPreview->SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);

    BString str;
    str << fFontFamily << " " << fFontStyle << ", " << (int)fFontSize << " pt.";
    fPreview->SetText(str);
}


    bool
VimSelectFontDialog::_UpdateFromListItem(BListView* list, char* text, int textSize)
{
    int32 index = list->CurrentSelection();
    if (index < 0)
	return false;
    BStringItem* item = (BStringItem*)list->ItemAt(index);
    if (item == NULL)
	return false;
    strncpy(text, item->Text(), textSize);
    return true;
}


void VimSelectFontDialog::MessageReceived(BMessage *msg)
{
    switch (msg->what) {
	case kVimDialogOKButtonMsg:
	    strncpy(*fFamily, fFontFamily, B_FONT_FAMILY_LENGTH);
	    strncpy(*fStyle, fFontStyle, B_FONT_STYLE_LENGTH);
	    *fSize = fFontSize;
	    fDialogValue = true;
	case kVimDialogCancelButtonMsg:
	    delete_sem(fDialogSem);
	    fDialogSem = -1;
	    return;
	case B_KEY_UP:
	    {
		int32 key = 0;
		if (msg->FindInt32("raw_char", &key) == B_OK
			&& key == B_ESCAPE) {
		    delete_sem(fDialogSem);
		    fDialogSem = -1;
		}
	    }
	    break;

	case kVimDialogFamilySelectMsg:
	    if (_UpdateFromListItem(fFamiliesList,
		    fFontFamily, B_FONT_FAMILY_LENGTH)) {
		_UpdateFontStyles();
		_UpdateFontPreview();
	    }
	    break;
	case kVimDialogStyleSelectMsg:
	    if (_UpdateFromListItem(fStylesList,
		    fFontStyle, B_FONT_STYLE_LENGTH))
		_UpdateFontPreview();
	    break;
	case kVimDialogSizeSelectMsg:
	    {
		char buf[10] = {0};
		if (_UpdateFromListItem(fSizesList, buf, sizeof(buf))) {
		    float size = atof(buf);
		    if (size > 0.f) {
			fFontSize = size;
			_UpdateSizeInputPreview();
			_UpdateFontPreview();
		    }
		}
	    }
	    break;
	case kVimDialogSizeInputMsg:
	    {
		float size = atof(fSizesInput->Text());
		if (size > 0.f) {
		    fFontSize = size;
		    _UpdateFontPreview();
		}
	    }
	    break;
	default:
	    break;
    }
    return BWindow::MessageReceived(msg);
}

#endif // FEAT_GUI_DIALOG

#ifdef FEAT_TOOLBAR

//  some forward declaration required by toolbar functions...
static BMessage * MenuMessage(vimmenu_T *menu);

VimToolbar::VimToolbar(BRect frame, const char *name) :
    BBox(frame, name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS, B_PLAIN_BORDER)
{
}

VimToolbar::~VimToolbar()
{
    int32 count = fButtonsList.CountItems();
    for (int32 i = 0; i < count; i++)
	delete (BPictureButton*)fButtonsList.ItemAt(i);
    fButtonsList.MakeEmpty();

    delete normalButtonsBitmap;
    delete grayedButtonsBitmap;
    normalButtonsBitmap    = NULL;
    grayedButtonsBitmap  = NULL;
}

    void
VimToolbar::AttachedToWindow()
{
    BBox::AttachedToWindow();

    SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}

    float
VimToolbar::ToolbarHeight() const
{
    float size = NULL == normalButtonsBitmap ? 18. : normalButtonsBitmap->Bounds().Height();
    return size + ToolbarMargin * 2 + ButtonMargin * 2 + 1;
}

    bool
VimToolbar::ModifyBitmapToGrayed(BBitmap *bitmap)
{
    float height = bitmap->Bounds().Height();
    float width  = bitmap->Bounds().Width();

    rgb_color *bits = (rgb_color*)bitmap->Bits();
    int32 pixels = bitmap->BitsLength() / 4;
    for (int32 i = 0; i < pixels; i++) {
	bits[i].red = bits[i].green =
	bits[i].blue = ((uint32)bits[i].red + bits[i].green + bits[i].blue) / 3;
	bits[i].alpha /= 4;
    }

    return true;
}

    bool
VimToolbar::PrepareButtonBitmaps()
{
    //	first try to load potentially customized $VIRUNTIME/bitmaps/builtin-tools.png
    normalButtonsBitmap = LoadVimBitmap("builtin-tools.png");
    if (normalButtonsBitmap == NULL)
	//  customized not found? dig application resources for "builtin-tools" one
	normalButtonsBitmap = BTranslationUtils::GetBitmap(B_PNG_FORMAT, "builtin-tools");

    if (normalButtonsBitmap == NULL)
	return false;

    BMessage archive;
    normalButtonsBitmap->Archive(&archive);

    grayedButtonsBitmap = new BBitmap(&archive);
    if (grayedButtonsBitmap == NULL)
	return false;

    //	modify grayed bitmap
    ModifyBitmapToGrayed(grayedButtonsBitmap);

    return true;
}

BBitmap *VimToolbar::LoadVimBitmap(const char* fileName)
{
    BBitmap *bitmap = NULL;

    int mustfree = 0;
    char_u* runtimePath = vim_getenv((char_u*)"VIMRUNTIME", &mustfree);
    if (runtimePath != NULL && fileName != NULL) {
	BString strPath((char*)runtimePath);
	strPath << "/bitmaps/" << fileName;
	bitmap = BTranslationUtils::GetBitmap(strPath.String());
    }

    if (mustfree)
	vim_free(runtimePath);

    return bitmap;
}

    bool
VimToolbar::GetPictureFromBitmap(BPicture *pictureTo, int32 index, BBitmap *bitmapFrom, bool pressed)
{
    float size = bitmapFrom->Bounds().Height() + 1.;

    BView view(BRect(0, 0, size, size), "", 0, 0);

    AddChild(&view);
    view.BeginPicture(pictureTo);

    view.SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
    view.FillRect(view.Bounds());
    view.SetDrawingMode(B_OP_OVER);

    BRect source(0, 0, size - 1, size - 1);
    BRect destination(source);

    source.OffsetBy(size * index, 0);
    destination.OffsetBy(ButtonMargin, ButtonMargin);

    view.DrawBitmap(bitmapFrom, source, destination);

    if (pressed)	{
	rgb_color shineColor  = ui_color(B_SHINE_COLOR);
	rgb_color shadowColor = ui_color(B_SHADOW_COLOR);
	size += ButtonMargin * 2 - 1;
	view.BeginLineArray(4);
	view.AddLine(BPoint(0, 0),	 BPoint(size, 0),    shadowColor);
	view.AddLine(BPoint(size, 0),	 BPoint(size, size), shineColor);
	view.AddLine(BPoint(size, size), BPoint(0, size),    shineColor);
	view.AddLine(BPoint(0, size),	 BPoint(0, 0),	     shadowColor);
	view.EndLineArray();
    }

    view.EndPicture();
    RemoveChild(&view);

    return true;
}

    bool
VimToolbar::AddButton(int32 index, vimmenu_T *menu)
{
    BPictureButton *button = NULL;
    if (!menu_is_separator(menu->name)) {
	float size = normalButtonsBitmap ?
	    normalButtonsBitmap->Bounds().Height() + 1. + ButtonMargin * 2 : 18.;
	BRect frame(0, 0, size, size);
	BPicture pictureOn;
	BPicture pictureOff;
	BPicture pictureGray;

	if (menu->iconfile == NULL && menu->iconidx >= 0 && normalButtonsBitmap) {
	    GetPictureFromBitmap(&pictureOn,  menu->iconidx, normalButtonsBitmap, true);
	    GetPictureFromBitmap(&pictureOff, menu->iconidx, normalButtonsBitmap, false);
	    GetPictureFromBitmap(&pictureGray, menu->iconidx, grayedButtonsBitmap, false);
	} else {

	    char_u buffer[MAXPATHL] = {0};
	    BBitmap *bitmap = NULL;

	    if (menu->iconfile) {
		gui_find_iconfile(menu->iconfile, buffer, (char*)"png");
		bitmap = BTranslationUtils::GetBitmap((char*)buffer);
	    }

	    if (bitmap == NULL && gui_find_bitmap(menu->name, buffer, (char*)"png") == OK)
		bitmap = BTranslationUtils::GetBitmap((char*)buffer);

	    if (bitmap == NULL)
		bitmap = new BBitmap(BRect(0, 0, size, size), B_RGB32);

	    GetPictureFromBitmap(&pictureOn,   0, bitmap, true);
	    GetPictureFromBitmap(&pictureOff,  0, bitmap, false);
	    ModifyBitmapToGrayed(bitmap);
	    GetPictureFromBitmap(&pictureGray, 0, bitmap, false);

	    delete bitmap;
	}

	button = new BPictureButton(frame, (char*)menu->name,
		    &pictureOff, &pictureOn, MenuMessage(menu));

	button->SetDisabledOn(&pictureGray);
	button->SetDisabledOff(&pictureGray);

	button->SetTarget(gui.vimTextArea);

	AddChild(button);

	menu->button = button;
    }

    bool result = fButtonsList.AddItem(button, index);
    InvalidateLayout();
    return result;
}

    bool
VimToolbar::RemoveButton(vimmenu_T *menu)
{
    if (menu->button) {
	if (fButtonsList.RemoveItem(menu->button)) {
	    delete menu->button;
	    menu->button = NULL;
	}
    }
    return true;
}

    bool
VimToolbar::GrayButton(vimmenu_T *menu, int grey)
{
    if (menu->button) {
	int32 index = fButtonsList.IndexOf(menu->button);
	if (index >= 0)
	    menu->button->SetEnabled(grey ? false : true);
    }
    return true;
}

    void
VimToolbar::InvalidateLayout()
{
    int32 offset = ToolbarMargin;
    int32 count = fButtonsList.CountItems();
    for (int32 i = 0; i < count; i++) {
	BPictureButton *button = (BPictureButton *)fButtonsList.ItemAt(i);
	if (button) {
	    button->MoveTo(offset, ToolbarMargin);
	    offset += button->Bounds().Width() + ToolbarMargin;
	} else
	    offset += ToolbarMargin * 3;
    }
}

#endif /*FEAT_TOOLBAR*/

#if defined(FEAT_GUI_TABLINE)

    float
VimTabLine::TablineHeight() const
{
//  float size = NULL == normalButtonsBitmap ? 18. : normalButtonsBitmap->Bounds().Height();
//  return size + ToolbarMargin * 2 + ButtonMargin * 2 + 1;
    return TabHeight(); //  + ToolbarMargin;
}

void
VimTabLine::MouseDown(BPoint point)
{
    if (!gui_mch_showing_tabline())
	return;

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

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

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

    int index = 0; //  0 means here - no tab found
    for (int i = 0; i < CountTabs(); i++) {
	if (TabFrame(i).Contains(point)) {
	    index = i + 1; //  indexes are 1-based
	    break;
	}
    }

    int event = -1;

    if ((buttons & B_PRIMARY_MOUSE_BUTTON) && clicks > 1)
	//  left button double click on - create new tab
	event = TABLINE_MENU_NEW;

    else if (buttons & B_TERTIARY_MOUSE_BUTTON)
	//  middle button click - close the pointed tab
	//  or create new one in case empty space
	event = index > 0 ? TABLINE_MENU_CLOSE : TABLINE_MENU_NEW;

    else if (buttons & B_SECONDARY_MOUSE_BUTTON) {
	//  right button click - show context menu
	BPopUpMenu* popUpMenu = new BPopUpMenu("tabLineContextMenu", false, false);
	popUpMenu->AddItem(new BMenuItem(_("Close tabi R"), new BMessage(TABLINE_MENU_CLOSE)));
	popUpMenu->AddItem(new BMenuItem(_("New tab    T"), new BMessage(TABLINE_MENU_NEW)));
	popUpMenu->AddItem(new BMenuItem(_("Open tab..."), new BMessage(TABLINE_MENU_OPEN)));

	ConvertToScreen(&point);
	BMenuItem* item = popUpMenu->Go(point);
	if (item != NULL) {
	    event = item->Command();
	}

	delete popUpMenu;

    } else {
	//  default processing
	BTabView::MouseDown(point);
	return;
    }

    if (event < 0)
	return;

    VimTablineMenuMsg tmm;
    tmm.index = index;
    tmm.event = event;
    write_port(gui.vdcmp, VimMsg::TablineMenu, &tmm, sizeof(tmm));
}

void
VimTabLine::VimTab::Select(BView* owner)
{
    BTab::Select(owner);

    VimTabLine *tabLine = gui.vimForm->TabLine();
    if (tabLine != NULL) {

	int32 i = 0;
	for (; i < tabLine->CountTabs(); i++)
	    if (this == tabLine->TabAt(i))
		break;

//	printf("%d:%d:%s\n", i, tabLine->CountTabs(), tabLine->TabAt(i)->Label());
	if (i < tabLine->CountTabs()) {
	    VimTablineMsg tm;
	    tm.index = i + 1;
	    write_port(gui.vdcmp, VimMsg::Tabline, &tm, sizeof(tm));
	}
    }
}

#endif //  defined(FEAT_GUI_TABLINE)

// ---------------- ----------------

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

    status_t
gui_haiku_process_event(bigtime_t timeout)
{
    struct VimMsg vm;
    int32 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;
		    }

		    if (vm.u.Key.csi_escape)
#ifndef FEAT_MBYTE_IME
		    {
			int	i;
			char_u	buf[2];

			for (i = 0; i < len; ++i)
			{
			    add_to_input_buf(string + i, 1);
			    if (string[i] == CSI)
			    {
				// Turn CSI into K_CSI.
				buf[0] = KS_EXTRA;
				buf[1] = (int)KE_CSI;
				add_to_input_buf(buf, 2);
			    }
			}
		    }
#else
			add_to_input_buf_csi(string, len);
#endif
		    else
			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;
#if defined(FEAT_MENU)
	    case VimMsg::Menu:
		gui_menu_cb(vm.u.Menu.guiMenu);
		break;
#endif
	    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::MouseMoved:
		{
		    gui_mouse_moved(vm.u.MouseMoved.x, vm.u.MouseMoved.y);
		}
		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;
	    case VimMsg::Tabline:
		send_tabline_event(vm.u.Tabline.index);
		break;
	    case VimMsg::TablineMenu:
		send_tabline_menu_event(vm.u.TablineMenu.index, vm.u.TablineMenu.event);
		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

/*
 * 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()
{
    display_errors();
    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();
	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 truly 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)
{
    if (gui.vimWindow->Lock()) {
	BRect r;
	r = gui.vimWindow->Frame();
	gui.vimWindow->Unlock();
	*x = (int)r.left;
	*y = (int)r.top;
	return OK;
    }
    else
	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)
{
    if (gui.vimWindow->Lock()) {
	gui.vimWindow->MoveTo(x, y);
	gui.vimWindow->Unlock();
    }
}

/*
 * 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,
	int	direction) // TODO: utilize?
{
    /*
     * 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);

	/*
	 * Set the resizing alignment depending on font size.
	 */
	gui.vimWindow->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
		);

	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);

#ifdef FEAT_GUI_TABLINE
	if (gui.vimForm->TabLine() != NULL) {
	    gui.vimForm->TabLine()->ResizeTo(w, gui.vimForm->TablineHeight());
	}
#endif // FEAT_GUI_TABLINE

	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()) {
	BRect winb = gui.vimWindow->Bounds();
	float vsbx = x, vsby = y;
	VimScrollBar *vsb = sb->id;
	vsb->ResizeTo(w - PEN_WIDTH, h - PEN_WIDTH);
	if (winb.right-(x+w)<w) vsbx = winb.right - (w - PEN_WIDTH);
	vsb->MoveTo(vsbx, vsby);
	gui.vimWindow->Unlock();
    }
}

    int
gui_mch_get_scrollbar_xpadding(void)
{
    // TODO: Calculate the padding for adjust scrollbar position when the
    // Window is maximized.
    return 0;
}

    int
gui_mch_get_scrollbar_ypadding(void)
{
    // TODO: Calculate the padding for adjust scrollbar position when the
    // Window is maximized.
    return 0;
}

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(FEAT_GUI_HAIKU) || 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 does not flash
 */
    int
gui_mch_is_blink_off(void)
{
    return FALSE;
}

/*
 * 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(int may_call_gui_update_cursor)
{
    // 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;
}


    int
gui_mch_font_dialog(font_family* family, font_style* style, float* size)
{
#if defined(FEAT_GUI_DIALOG)
	// gui.vimWindow->Unlock();
    VimSelectFontDialog *dialog = new VimSelectFontDialog(family, style, size);
    return dialog->Go();
#else
    return NOFONT;
#endif // FEAT_GUI_DIALOG
}


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

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

    //	storage for locally modified name;
    const int buff_size = B_FONT_FAMILY_LENGTH + B_FONT_STYLE_LENGTH + 20;
    static char font_name[buff_size] = {0};
    font_family family = {0};
    font_style	style  = {0};
    float size = 0.f;

    if (name == 0 && be_fixed_font == 0) {
	if (giveErrorIfMissing)
	    semsg(_(e_unknown_font_str), name);
	return NOFONT;
    }

    bool useSelectGUI = false;
    if (name != NULL)
	if (STRCMP(name, "*") == 0) {
	    useSelectGUI = true;
	    STRNCPY(font_name, hl_get_font_name(), buff_size);
	} else
	    STRNCPY(font_name, name, buff_size);

    if (font_name[0] == 0) {
	be_fixed_font->GetFamilyAndStyle(&family, &style);
	size = be_fixed_font->Size();
	vim_snprintf(font_name, buff_size,
	    (char*)"%s/%s/%.0f", family, style, size);
    }

    //	replace underscores with spaces
    char* end = 0;
    while (end = strchr((char *)font_name, '_'))
	*end = ' ';

    //	store the name before strtok corrupt the buffer ;-)
    static char buff[buff_size] = {0};
    STRNCPY(buff, font_name, buff_size);
    STRNCPY(family, strtok(buff, "/\0"), B_FONT_FAMILY_LENGTH);
    char* style_s = strtok(0, "/\0");
    if (style_s != 0)
	STRNCPY(style, style_s, B_FONT_STYLE_LENGTH);
    size = atof((style_s != 0) ? strtok(0, "/\0") : "0");

    if (useSelectGUI) {
	if (gui_mch_font_dialog(&family, &style, &size) == NOFONT)
	    return FAIL;
	//  compose for further processing
	vim_snprintf(font_name, buff_size,
		(char*)"%s/%s/%.0f", family, style, size);
	hl_set_font_name((char_u*)font_name);

	//  Set guifont to the name of the selected font.
	char_u* new_p_guifont = (char_u*)alloc(STRLEN(font_name) + 1);
	if (new_p_guifont != NULL) {
	    STRCPY(new_p_guifont, font_name);
	    vim_free(p_guifont);
	    p_guifont = new_p_guifont;
	    //	Replace spaces in the font name with underscores.
	    for ( ; *new_p_guifont; ++new_p_guifont)
		if (*new_p_guifont == ' ')
		    *new_p_guifont = '_';
	}
    }

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

    VimFont *font = new VimFont();
    font->name = vim_strsave((char_u*)font_name);

    if (count_font_styles(family) <= 0) {
	if (giveErrorIfMissing)
	    semsg(_(e_unknown_font_str), font->name);
	delete font;
	return NOFONT;
    }

    //	Remember font in the static list for later use
    font->next = fontList;
    fontList = font;

    font->SetFamilyAndStyle(family, style);
    if (size > 0.f)
	font->SetSize(size);

    font->SetSpacing(B_FIXED_SPACING);
    font->SetEncoding(B_UNICODE_UTF8);

    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();
    }
}

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

    char_u *
gui_mch_get_fontname(GuiFont font, char_u *name)
{
    if (name == NULL)
	return NULL;
    return vim_strsave(name);
}

/*
 * Adjust gui.char_height (after 'linespace' was changed).
 */
    int
gui_mch_adjust_charheight()
{

    // TODO: linespace support?

// #ifdef FEAT_XFONTSET
//  if (gui.fontset != NOFONTSET)
//  {
//  gui.char_height = fontset_height((XFontSet)gui.fontset) + p_linespace;
//  gui.char_ascent = fontset_ascent((XFontSet)gui.fontset)
//  + p_linespace / 2;
//  }
//  else
// #endif
    {
	VimFont *font = (VimFont *)gui.norm_font;
	font_height fh = {0};
	font->GetHeight(&fh);
	gui.char_height = (int)(fh.ascent + fh.descent + 0.5) + p_linespace;
	gui.char_ascent = (int)(fh.ascent + 0.5) + p_linespace / 2;
    }
    return OK;
}

    void
gui_mch_getmouse(int *x, int *y)
{
    fprintf(stderr, "gui_mch_getmouse");

    /*int	rootx, rooty, winx, winy;
      Window	root, child;
      unsigned int mask;

      if (gui.wid && XQueryPointer(gui.dpy, gui.wid, &root, &child,
      &rootx, &rooty, &winx, &winy, &mask)) {
     *x = winx;
     *y = winy;
     } else*/ {
	 *x = -1;
	 *y = -1;
     }
}

    void
gui_mch_mousehide(int hide)
{
    fprintf(stderr, "gui_mch_getmouse");
    //	TODO
}

/*
 * 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)
{
    return gui_get_color_cmn(name);
}

/*
 * 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();
    }
}

/*
 * Set the current text special color.
 */
    void
gui_mch_set_sp_color(guicolor_T	color)
{
    // prev_sp_color = color;
}

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();
    }
}

	guicolor_T
gui_mch_get_rgb_color(int r, int g, int b)
{
    return gui_get_rgb_color_cmn(r, g, b);
}


// 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(void)
{
    // 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);
    int cells = utf_off2cells(LineOffset[gui.row] + gui.col, 100); // TODO-TODO
    if (cells>=4) cells = 1;
    r.right = r.left + cells*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
	CURSOR_BAR_RIGHT ? 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_haiku_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(TRUE);
	    focus = gui.in_focus;
	}

	gui_mch_flush();

#ifdef MESSAGE_QUEUE
# ifdef FEAT_TIMERS
	did_add_timer = FALSE;
# endif
	parse_queued_messages();
# ifdef FEAT_TIMERS
	if (did_add_timer)
	    // Need to recompute the waiting time.
	    break;
# endif
# ifdef FEAT_JOB_CHANNEL
	if (has_any_channel())
	{
	    if (wtime < 0 || timeout > 20000)
		timeout = 20000;
	}
	else if (wtime < 0)
	    timeout = B_INFINITE_TIMEOUT;
# endif
#endif

	/*
	 * Don't use gui_mch_update() because then we will spin-lock until a
	 * char arrives, instead we use gui_haiku_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_haiku_process_event(timeout);

	if (input_available())
	    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;

    //	popup menu - just create it unattached
    if (menu_is_popup(menu->name) && parent == NULL) {
	BPopUpMenu* popUpMenu = new BPopUpMenu((const char*)menu->name, false, false);
	menu->submenu_id = popUpMenu;
	menu->id = NULL;
	return;
    }

    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 it's 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;

    // 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())
    {
#ifdef FEAT_TOOLBAR
	if (menu_is_toolbar(parent->name)) {
	    VimToolbar *toolbar = gui.vimForm->ToolBar();
	    if (toolbar != NULL) {
		toolbar->AddButton(idx, menu);
	    }
	} else
#endif

	if (parent->submenu_id != NULL || menu_is_popup(parent->name)) {
	    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())
    {
#ifdef FEAT_TOOLBAR
	if (menu->parent && menu_is_toolbar(menu->parent->name)) {
	    VimToolbar *toolbar = gui.vimForm->ToolBar();
	    if (toolbar != NULL) {
		toolbar->RemoveButton(menu);
	    }
	} else
#endif
	{
	    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)
{
#ifdef FEAT_TOOLBAR
    if (menu->parent && menu_is_toolbar(menu->parent->name)) {
	if (gui.vimWindow->Lock()) {
	    VimToolbar *toolbar = gui.vimForm->ToolBar();
	    if (toolbar != NULL) {
		toolbar->GrayButton(menu, grey);
	    }
	    gui.vimWindow->Unlock();
	}
    } else
#endif
    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
}

    void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
    if (!menu_is_popup(menu->name) || menu->submenu_id == NULL)
	return;

    BPopUpMenu* popupMenu = dynamic_cast<BPopUpMenu*>(menu->submenu_id);
    if (popupMenu == NULL)
	return;

    BPoint point;
    if (gui.vimWindow->Lock()) {
	uint32 buttons = 0;
	gui.vimTextArea->GetMouse(&point, &buttons);
	gui.vimTextArea->ConvertToScreen(&point);
	gui.vimWindow->Unlock();
    }
    popupMenu->Go(point, true);
}

#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(Clipboard_T *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(Clipboard_T *cbd)
{
    // Nothing needs to be done here
}

/*
 * Make vim the owner of the current selection.  Return OK upon success.
 */
    int
clip_mch_own_selection(Clipboard_T *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(Clipboard_T *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

#ifdef FEAT_BROWSE
/*
 * Pop open a file browser and return the file selected, in allocated memory,
 * or NULL if Cancel is hit.
 *  saving  - TRUE if the file will be saved to, FALSE if it will be opened.
 *  title   - Title message for the file browser dialog.
 *  dflt    - Default name of file.
 *  ext     - Default extension to be added to files without extensions.
 *  initdir - directory in which to open the browser (NULL = current dir)
 *  filter  - Filter for matched files to choose from.
 *  Has a format like this:
 *  "C Files (*.c)\0*.c\0"
 *  "All Files\0*.*\0\0"
 *  If these two strings were concatenated, then a choice of two file
 *  filters will be selectable to the user.  Then only matching files will
 *  be shown in the browser.  If NULL, the default allows all files.
 *
 *  *NOTE* - the filter string must be terminated with TWO nulls.
 */
char_u *
gui_mch_browse(
	int saving,
	char_u *title,
	char_u *dflt,
	char_u *ext,
	char_u *initdir,
	char_u *filter)
{
    gui.vimApp->fFilePanel = new BFilePanel((saving == TRUE) ? B_SAVE_PANEL : B_OPEN_PANEL,
	    NULL, NULL, 0, false,
	    new BMessage((saving == TRUE) ? 'save' : 'open'), NULL, true);

    gui.vimApp->fBrowsedPath.Unset();

    gui.vimApp->fFilePanel->Window()->SetTitle((char*)title);
    gui.vimApp->fFilePanel->SetPanelDirectory((const char*)initdir);

    gui.vimApp->fFilePanel->Show();

    gui.vimApp->fFilePanelSem = create_sem(0, "FilePanelSem");

    while (acquire_sem(gui.vimApp->fFilePanelSem) == B_INTERRUPTED);

    char_u *fileName = NULL;
    status_t result = gui.vimApp->fBrowsedPath.InitCheck();
    if (result == B_OK) {
	fileName = vim_strsave((char_u*)gui.vimApp->fBrowsedPath.Path());
    } else
	if (result != B_NO_INIT) {
	    fprintf(stderr, "gui_mch_browse: BPath error: %#08x (%s)\n",
		    result, strerror(result));
	}

    delete gui.vimApp->fFilePanel;
    gui.vimApp->fFilePanel = NULL;

    return fileName;
}
#endif // FEAT_BROWSE


#if defined(FEAT_GUI_DIALOG)

/*
 * Create a dialog dynamically from the parameter strings.
 * type	    = type of dialog (question, alert, etc.)
 * title    = dialog title. may be NULL for default title.
 * message  = text to display. Dialog sizes to accommodate it.
 * buttons  = '\n' separated list of button captions, default first.
 * dfltbutton	= number of default button.
 *
 * This routine returns 1 if the first button is pressed,
 *	    2 for the second, etc.
 *
 *	    0 indicates Esc was pressed.
 *	    -1 for unexpected error
 *
 * If stubbing out this fn, return 1.
 */

int
gui_mch_dialog(
	int	 type,
	char_u	*title,
	char_u	*message,
	char_u	*buttons,
	int	 dfltbutton,
	char_u	*textfield,
	int ex_cmd)
{
    VimDialog *dialog = new VimDialog(type, (char*)title, (char*)message,
	    (char*)buttons, dfltbutton, (char*)textfield, ex_cmd);
    return dialog->Go();
}

#endif // FEAT_GUI_DIALOG


/*
 * Return the RGB value of a pixel as long.
 */
    guicolor_T
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
}

#ifdef FEAT_MBYTE_IME
    void
im_set_position(int row, int col)
{
    if (gui.vimWindow->Lock())
    {
	gui.vimTextArea->DrawIMString();
	gui.vimWindow->Unlock();
    }
    return;
}
#endif

    void
gui_mch_show_toolbar(int showit)
{
    VimToolbar *toolbar = gui.vimForm->ToolBar();
    gui.toolbar_height = (toolbar && showit) ? toolbar->ToolbarHeight() : 0.;
}

    void
gui_mch_set_toolbar_pos(int x, int y, int w, int h)
{
    VimToolbar *toolbar = gui.vimForm->ToolBar();
    if (toolbar != NULL) {
	if (gui.vimWindow->Lock()) {
	    toolbar->MoveTo(x, y);
	    toolbar->ResizeTo(w - 1, h - 1);
	    gui.vimWindow->Unlock();
	}
    }
}

#if defined(FEAT_GUI_TABLINE) || defined(PROTO)

/*
 * Show or hide the tabline.
 */
    void
gui_mch_show_tabline(int showit)
{
    VimTabLine *tabLine = gui.vimForm->TabLine();

    if (tabLine == NULL)
	return;

    if (!showit != !gui.vimForm->IsShowingTabLine()) {
	gui.vimForm->SetShowingTabLine(showit != 0);
	gui.tabline_height = gui.vimForm->TablineHeight();
    }
}

    void
gui_mch_set_tabline_pos(int x, int y, int w, int h)
{
    VimTabLine *tabLine = gui.vimForm->TabLine();
    if (tabLine != NULL) {
	if (gui.vimWindow->Lock()) {
	    tabLine->MoveTo(x, y);
	    tabLine->ResizeTo(w - 1, h - 1);
	    gui.vimWindow->Unlock();
	}
    }
}

/*
 * Return TRUE when tabline is displayed.
 */
    int
gui_mch_showing_tabline()
{
    VimTabLine *tabLine = gui.vimForm->TabLine();
    return tabLine != NULL && gui.vimForm->IsShowingTabLine();
}

/*
 * Update the labels of the tabline.
 */
    void
gui_mch_update_tabline()
{
    tabpage_T	*tp;
    int	    nr = 0;
    int	    curtabidx = 0;

    VimTabLine *tabLine = gui.vimForm->TabLine();

    if (tabLine == NULL)
	return;

    gui.vimWindow->Lock();

    // Add a label for each tab page.  They all contain the same text area.
    for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr) {
	if (tp == curtab)
	    curtabidx = nr;

	BTab* tab = tabLine->TabAt(nr);

	if (tab == NULL) {
	    tab = new VimTabLine::VimTab();
	    tabLine->AddTab(NULL, tab);
	}

	get_tabline_label(tp, FALSE);
	tab->SetLabel((const char*)NameBuff);
	tabLine->Invalidate();
    }

    // Remove any old labels.
    while (nr < tabLine->CountTabs())
	tabLine->RemoveTab(nr);

    if (tabLine->Selection() != curtabidx)
	tabLine->Select(curtabidx);

    gui.vimWindow->Unlock();
}

/*
 * Set the current tab to "nr".  First tab is 1.
 */
    void
gui_mch_set_curtab(int nr)
{
    VimTabLine *tabLine = gui.vimForm->TabLine();
    if (tabLine == NULL)
	return;

    gui.vimWindow->Lock();

    if (tabLine->Selection() != nr -1)
	tabLine->Select(nr -1);

    gui.vimWindow->Unlock();
}

#endif // FEAT_GUI_TABLINE