diff options
Diffstat (limited to 'src/gui_beos.cc')
-rw-r--r-- | src/gui_beos.cc | 3347 |
1 files changed, 3347 insertions, 0 deletions
diff --git a/src/gui_beos.cc b/src/gui_beos.cc new file mode 100644 index 000000000..e2c659848 --- /dev/null +++ b/src/gui_beos.cc @@ -0,0 +1,3347 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * BeBox GUI support Copyright 1998 by Olaf Seibert. + * All Rights Reserved. + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * + * BeOS GUI. + * + * GUI support for the Buzzword Enhanced Operating System. + * + * Ported to R4 by Richard Offer <richard@whitequeen.com> Jul 99 + * + */ + +/* + * Structure of the BeOS GUI code: + * + * There are 3 threads. + * 1. The initial thread. In gui_mch_prepare() this gets to run the + * BApplication message loop. But before it starts doing that, + * it creates thread 2: + * 2. The main() thread. This thread is created in gui_mch_prepare() + * and its purpose in life is to call main(argc, argv) again. + * This thread is doing the bulk of the work. + * 3. Sooner or later, a window is opened by the main() thread. This + * causes a second message loop to be created: the window thread. + * + * == alternatively === + * + * #if RUN_BAPPLICATION_IN_NEW_THREAD... + * + * 1. The initial thread. In gui_mch_prepare() this gets to spawn + * thread 2. After doing that, it returns to main() to do the + * bulk of the work, being the main() thread. + * 2. Runs the BApplication. + * 3. The window thread, just like in the first case. + * + * This second alternative is cleaner from Vim's viewpoint. However, + * the BeBook seems to assume everywhere that the BApplication *must* + * run in the initial thread. So perhaps doing otherwise is very wrong. + * + * However, from a B_SINGLE_LAUNCH viewpoint, the first is better. + * If Vim is marked "Single Launch" in its application resources, + * and a file is dropped on the Vim icon, and another Vim is already + * running, the file is passed on to the earlier Vim. This happens + * in BApplication::Run(). So we want Vim to terminate if + * BApplication::Run() terminates. (See the BeBook, on BApplication. + * However, it seems that the second copy of Vim isn't even started + * in this case... which is for the better since I wouldn't know how + * to detect this case.) + * + * Communication between these threads occurs mostly by translating + * BMessages that come in and posting an appropriate translation on + * the VDCMP (Vim Direct Communication Message Port). Therefore the + * actions required for keypresses and window resizes, etc, are mostly + * performed in the main() thread. + * + * A notable exception to this is the Draw() event. The redrawing of + * the window contents is performed asynchronously from the window + * thread. To make this work correctly, a locking protocol is used when + * any thread is accessing the essential variables that are used by + * the window thread. + * + * This locking protocol consists of locking Vim's window. This is both + * convenient and necessary. + */ +extern "C" { + +#define new xxx_new_xxx + +#include <float.h> +#include <assert.h> +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" + +#undef new + +} /* extern "C" */ + +/* ---------------- start of header part ---------------- */ + +#include <be/app/MessageQueue.h> +#include <be/app/Clipboard.h> +#include <be/kernel/OS.h> +#include <be/support/Beep.h> +#include <be/interface/View.h> +#include <be/interface/Window.h> +#include <be/interface/MenuBar.h> +#include <be/interface/MenuItem.h> +#include <be/interface/ScrollBar.h> +#include <be/interface/Region.h> +#include <be/interface/Screen.h> +#include <be/storage/Path.h> +#include <be/storage/Directory.h> +#include <be/storage/Entry.h> +#include <be/app/Application.h> +#include <be/support/Debug.h> + +/* + * The macro B_PIXEL_ALIGNMENT shows us which version + * of the header files we're using. + */ +#if defined(B_PIXEL_ALIGNMENT) +#define HAVE_R3_OR_LATER 1 +#else +#define HAVE_R3_OR_LATER 0 +#endif + +class VimApp; +class VimFormView; +class VimTextAreaView; +class VimWindow; + +extern key_map *keyMap; +extern char *keyMapChars; + +extern int main(int argc, char **argv); + +#ifndef B_MAX_PORT_COUNT +#define B_MAX_PORT_COUNT 100 +#endif + +/* + * VimApp seems comparable to the X "vimShell" + */ +class VimApp: public BApplication +{ + typedef BApplication Inherited; +public: + VimApp(const char *appsig); + ~VimApp(); + + // callbacks: +#if 0 + virtual void DispatchMessage(BMessage *m, BHandler *h) + { + m->PrintToStream(); + Inherited::DispatchMessage(m, h); + } +#endif + virtual void ReadyToRun(); + virtual void ArgvReceived(int32 argc, char **argv); + virtual void RefsReceived(BMessage *m); + virtual bool QuitRequested(); + + static void SendRefs(BMessage *m, bool changedir); +private: +}; + +class VimWindow: public BWindow +{ + typedef BWindow Inherited; +public: + VimWindow(); + ~VimWindow(); + + virtual void DispatchMessage(BMessage *m, BHandler *h); + virtual void WindowActivated(bool active); + virtual bool QuitRequested(); + + VimFormView *formView; + +private: + void init(); + +}; + +class VimFormView: public BView +{ + typedef BView Inherited; +public: + VimFormView(BRect frame); + ~VimFormView(); + + // callbacks: + virtual void AllAttached(); + virtual void FrameResized(float new_width, float new_height); + +#define MENUBAR_MARGIN 1 + float MenuHeight() const + { return menuBar ? menuBar->Frame().Height() + MENUBAR_MARGIN: 0; } + BMenuBar *MenuBar() const + { return menuBar; } + +private: + void init(BRect); + + BMenuBar *menuBar; + VimTextAreaView *textArea; +}; + +class VimTextAreaView: public BView +{ + typedef BView Inherited; +public: + VimTextAreaView(BRect frame); + ~VimTextAreaView(); + + // callbacks: + virtual void Draw(BRect updateRect); + virtual void KeyDown(const char *bytes, int32 numBytes); + virtual void MouseDown(BPoint point); + virtual void MouseUp(BPoint point); + virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *message); + virtual void MessageReceived(BMessage *m); + + // own functions: + int mchInitFont(char_u *name); + void mchDrawString(int row, int col, char_u *s, int len, int flags); + void mchClearBlock(int row1, int col1, int row2, int col2); + void mchClearAll(); + void mchDeleteLines(int row, int num_lines); + void mchInsertLines(int row, int num_lines); + + static void guiSendMouseEvent(int button, int x, int y, int repeated_click, int_u modifiers); + static void guiBlankMouse(bool should_hide); + static int_u mouseModifiersToVim(int32 beModifiers); + + int32 mouseDragEventCount; + +private: + void init(BRect); + + int_u vimMouseButton; + int_u vimMouseModifiers; +}; + +class VimScrollBar: public BScrollBar +{ + typedef BScrollBar Inherited; +public: + VimScrollBar(scrollbar_T *gsb, orientation posture); + ~VimScrollBar(); + + virtual void ValueChanged(float newValue); + virtual void MouseUp(BPoint where); + void SetValue(float newval); + scrollbar_T *getGsb() + { return gsb; } + + int32 scrollEventCount; + +private: + scrollbar_T *gsb; + float ignoreValue; +}; + + +/* + * For caching the fonts that are used; + * Vim seems rather sloppy in this regard. + */ +class VimFont: public BFont +{ + typedef BFont Inherited; +public: + VimFont(); + VimFont(const VimFont *rhs); + VimFont(const BFont *rhs); + VimFont(const VimFont &rhs); + ~VimFont(); + + VimFont *next; + int refcount; + char_u *name; + +private: + void init(); +}; + +/* ---------------- end of GUI classes ---------------- */ + +struct MainArgs { + int argc; + char **argv; +}; + +/* + * These messages are copied through the VDCMP. + * Therefore they ought not to have anything fancy. + * They must be of POD type (Plain Old Data) + * as the C++ standard calls them. + */ + +#define KEY_MSG_BUFSIZ 7 +#if KEY_MSG_BUFSIZ < MAX_KEY_CODE_LEN +#error Increase KEY_MSG_BUFSIZ! +#endif + +struct VimKeyMsg { + char_u length; + char_u chars[KEY_MSG_BUFSIZ]; /* contains Vim encoding */ +}; + +struct VimResizeMsg { + int width; + int height; +}; + +struct VimScrollBarMsg { + VimScrollBar *sb; + long value; + int stillDragging; +}; + +struct VimMenuMsg { + vimmenu_T *guiMenu; +}; + +struct VimMouseMsg { + int button; + int x; + int y; + int repeated_click; + int_u modifiers; +}; + +struct VimFocusMsg { + bool active; +}; + +struct VimRefsMsg { + BMessage *message; + bool changedir; +}; + +struct VimMsg { + enum VimMsgType { + Key, Resize, ScrollBar, Menu, Mouse, Focus, Refs + }; + + union { + struct VimKeyMsg Key; + struct VimResizeMsg NewSize; + struct VimScrollBarMsg Scroll; + struct VimMenuMsg Menu; + struct VimMouseMsg Mouse; + struct VimFocusMsg Focus; + struct VimRefsMsg Refs; + } u; +}; + +#define RGB(r, g, b) ((char_u)(r) << 16 | (char_u)(g) << 8 | (char_u)(b) << 0) +#define GUI_TO_RGB(g) { (g) >> 16, (g) >> 8, (g) >> 0, 255 } + +/* ---------------- end of header part ---------------- */ + +static struct specialkey +{ + uint16 BeKeys; +#define KEY(a,b) ((a)<<8|(b)) +#define K(a) KEY(0,a) // for ASCII codes +#define F(b) KEY(1,b) // for scancodes + char_u vim_code0; + char_u vim_code1; +} special_keys[] = +{ + {K(B_UP_ARROW), 'k', 'u'}, + {K(B_DOWN_ARROW), 'k', 'd'}, + {K(B_LEFT_ARROW), 'k', 'l'}, + {K(B_RIGHT_ARROW), 'k', 'r'}, + {K(B_BACKSPACE), 'k', 'b'}, + {K(B_INSERT), 'k', 'I'}, + {K(B_DELETE), 'k', 'D'}, + {K(B_HOME), 'k', 'h'}, + {K(B_END), '@', '7'}, + {K(B_PAGE_UP), 'k', 'P'}, /* XK_Prior */ + {K(B_PAGE_DOWN), 'k', 'N'}, /* XK_Next, */ + +#define FIRST_FUNCTION_KEY 11 + {F(B_F1_KEY), 'k', '1'}, + {F(B_F2_KEY), 'k', '2'}, + {F(B_F3_KEY), 'k', '3'}, + {F(B_F4_KEY), 'k', '4'}, + {F(B_F5_KEY), 'k', '5'}, + {F(B_F6_KEY), 'k', '6'}, + {F(B_F7_KEY), 'k', '7'}, + {F(B_F8_KEY), 'k', '8'}, + {F(B_F9_KEY), 'k', '9'}, + {F(B_F10_KEY), 'k', ';'}, + + {F(B_F11_KEY), 'F', '1'}, + {F(B_F12_KEY), 'F', '2'}, +// {XK_F13, 'F', '3'}, /* would be print screen/ */ + /* sysreq */ + {F(0x0F), 'F', '4'}, /* scroll lock */ + {F(0x10), 'F', '5'}, /* pause/break */ +// {XK_F16, 'F', '6'}, +// {XK_F17, 'F', '7'}, +// {XK_F18, 'F', '8'}, +// {XK_F19, 'F', '9'}, +// {XK_F20, 'F', 'A'}, +// +// {XK_F21, 'F', 'B'}, +// {XK_F22, 'F', 'C'}, +// {XK_F23, 'F', 'D'}, +// {XK_F24, 'F', 'E'}, +// {XK_F25, 'F', 'F'}, +// {XK_F26, 'F', 'G'}, +// {XK_F27, 'F', 'H'}, +// {XK_F28, 'F', 'I'}, +// {XK_F29, 'F', 'J'}, +// {XK_F30, 'F', 'K'}, +// +// {XK_F31, 'F', 'L'}, +// {XK_F32, 'F', 'M'}, +// {XK_F33, 'F', 'N'}, +// {XK_F34, 'F', 'O'}, +// {XK_F35, 'F', 'P'}, /* keysymdef.h defines up to F35 */ + +// {XK_Help, '%', '1'}, /* XK_Help */ + {F(B_PRINT_KEY), '%', '9'}, + +#if 0 + /* Keypad keys: */ + {F(0x48), 'k', 'l'}, /* XK_KP_Left */ + {F(0x4A), 'k', 'r'}, /* XK_KP_Right */ + {F(0x38), 'k', 'u'}, /* XK_KP_Up */ + {F(0x59), 'k', 'd'}, /* XK_KP_Down */ + {F(0x64), 'k', 'I'}, /* XK_KP_Insert */ + {F(0x65), 'k', 'D'}, /* XK_KP_Delete */ + {F(0x37), 'k', 'h'}, /* XK_KP_Home */ + {F(0x58), '@', '7'}, /* XK_KP_End */ + {F(0x39), 'k', 'P'}, /* XK_KP_Prior */ + {F(0x60), 'k', 'N'}, /* XK_KP_Next */ + {F(0x49), '&', '8'}, /* XK_Undo, keypad 5 */ +#endif + + /* End of list marker: */ + {0, 0, 0} +}; + +#define NUM_SPECIAL_KEYS (sizeof(special_keys)/sizeof(special_keys[0])) + +/* ---------------- VimApp ---------------- */ + + static void +docd(BPath &path) +{ + mch_chdir(path.Path()); + /* Do this to get the side effects of a :cd command */ + do_cmdline_cmd((char_u *)"cd ."); +} + +/* + * Really handle dropped files and folders. + */ + static void +RefsReceived(BMessage *m, bool changedir) +{ + uint32 type; + int32 count; + + //m->PrintToStream(); + switch (m->what) { + case B_REFS_RECEIVED: + case B_SIMPLE_DATA: + m->GetInfo("refs", &type, &count); + if (type != B_REF_TYPE) + goto bad; + break; + case B_ARGV_RECEIVED: + m->GetInfo("argv", &type, &count); + if (type != B_STRING_TYPE) + goto bad; + if (changedir) { + char *dirname; + if (m->FindString("cwd", (const char **) &dirname) == B_OK) { + chdir(dirname); + do_cmdline_cmd((char_u *)"cd ."); + } + } + break; + default: + bad: + //fprintf(stderr, "bad!\n"); + delete m; + return; + } + +#ifdef FEAT_VISUAL + reset_VIsual(); +#endif + + char_u **fnames; + fnames = (char_u **) alloc(count * sizeof(char_u *)); + int fname_index = 0; + + switch (m->what) { + case B_REFS_RECEIVED: + case B_SIMPLE_DATA: + //fprintf(stderr, "case B_REFS_RECEIVED\n"); + for (int i = 0; i < count; ++i) + { + entry_ref ref; + if (m->FindRef("refs", i, &ref) == B_OK) { + BEntry entry(&ref, false); + BPath path; + entry.GetPath(&path); + + /* Change to parent directory? */ + if (changedir) { + BPath parentpath; + path.GetParent(&parentpath); + docd(parentpath); + } + + /* Is it a directory? If so, cd into it. */ + BDirectory bdir(&ref); + if (bdir.InitCheck() == B_OK) { + /* don't cd if we already did it */ + if (!changedir) + docd(path); + } else { + mch_dirname(IObuff, IOSIZE); + char_u *fname = shorten_fname((char_u *)path.Path(), IObuff); + if (fname == NULL) + fname = (char_u *)path.Path(); + fnames[fname_index++] = vim_strsave(fname); + //fprintf(stderr, "%s\n", fname); + } + + /* Only do it for the first file/dir */ + changedir = false; + } + } + break; + case B_ARGV_RECEIVED: + //fprintf(stderr, "case B_ARGV_RECEIVED\n"); + for (int i = 1; i < count; ++i) + { + char *fname; + + if (m->FindString("argv", i, (const char **) &fname) == B_OK) { + fnames[fname_index++] = vim_strsave((char_u *)fname); + } + } + break; + default: + //fprintf(stderr, "case default\n"); + break; + } + + delete m; + + /* Handle the drop, :edit to get to the file */ + if (fname_index > 0) { + handle_drop(fname_index, fnames, FALSE); + + /* Update the screen display */ + update_screen(NOT_VALID); + setcursor(); + out_flush(); + } else { + vim_free(fnames); + } +} + +VimApp::VimApp(const char *appsig): + BApplication(appsig) +{ +} + +VimApp::~VimApp() +{ +} + + void +VimApp::ReadyToRun() +{ + /* + * Apparently signals are inherited by the created thread - + * disable the most annoying ones. + */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); +} + + void +VimApp::ArgvReceived(int32 arg_argc, char **arg_argv) +{ + if (!IsLaunching()) { + /* + * This can happen if we are set to Single or Exclusive + * Launch. Be nice and open the file(s). + */ + if (gui.vimWindow) + gui.vimWindow->Minimize(false); + BMessage *m = CurrentMessage(); + DetachCurrentMessage(); + SendRefs(m, true); + } +} + + void +VimApp::RefsReceived(BMessage *m) +{ + /* Horrible hack!!! XXX XXX XXX + * The real problem is that b_start_ffc is set too late for + * the initial empty buffer. As a result the window will be + * split instead of abandoned. + */ + int limit = 15; + while (--limit >= 0 && (curbuf == NULL || curbuf->b_start_ffc == 0)) + snooze(100000); // 0.1 s + if (gui.vimWindow) + gui.vimWindow->Minimize(false); + DetachCurrentMessage(); + SendRefs(m, true); +} + +/* + * Pass a BMessage on to the main() thread. + * Caller must have detached the message. + */ + void +VimApp::SendRefs(BMessage *m, bool changedir) +{ + VimRefsMsg rm; + rm.message = m; + rm.changedir = changedir; + + write_port(gui.vdcmp, VimMsg::Refs, &rm, sizeof(rm)); + // calls ::RefsReceived +} + + bool +VimApp::QuitRequested() +{ + (void)Inherited::QuitRequested(); + return false; +} + +/* ---------------- VimWindow ---------------- */ + +VimWindow::VimWindow(): + BWindow(BRect(40, 40, 150, 150), + "Vim", + B_TITLED_WINDOW, + 0, + B_CURRENT_WORKSPACE) + +{ + init(); +} + +VimWindow::~VimWindow() +{ + if (formView) { + RemoveChild(formView); + delete formView; + } + gui.vimWindow = NULL; +} + + void +VimWindow::init() +{ + /* Attach the VimFormView */ + formView = new VimFormView(Bounds()); + if (formView != NULL) { + AddChild(formView); + } +} + + void +VimWindow::DispatchMessage(BMessage *m, BHandler *h) +{ + /* + * Route B_MOUSE_UP messages to MouseUp(), in + * a manner that should be compatible with the + * intended future system behaviour. + */ + switch (m->what) { + case B_MOUSE_UP: + // if (!h) h = PreferredHandler(); +// gcc isn't happy without this extra set of braces, complains about +// jump to case label crosses init of 'class BView * v' +// richard@whitequeen.com jul 99 + { + BView *v = dynamic_cast<BView *>(h); + if (v) { + //m->PrintToStream(); + BPoint where; + m->FindPoint("where", &where); + v->MouseUp(where); + } else { + Inherited::DispatchMessage(m, h); + } + } + break; + default: + Inherited::DispatchMessage(m, h); + } +} + + void +VimWindow::WindowActivated(bool active) +{ + Inherited::WindowActivated(active); + /* the textArea gets the keyboard action */ + if (active && gui.vimTextArea) + gui.vimTextArea->MakeFocus(true); + + struct VimFocusMsg fm; + fm.active = active; + + write_port(gui.vdcmp, VimMsg::Focus, &fm, sizeof(fm)); +} + + bool +VimWindow::QuitRequested() +{ + struct VimKeyMsg km; + km.length = 5; + memcpy((char *)km.chars, "\033:qa\r", km.length); + + write_port(gui.vdcmp, VimMsg::Key, &km, sizeof(km)); + + return false; +} + +/* ---------------- VimFormView ---------------- */ + +VimFormView::VimFormView(BRect frame): + BView(frame, "VimFormView", B_FOLLOW_ALL_SIDES, + B_WILL_DRAW | B_FRAME_EVENTS), + menuBar(NULL), + textArea(NULL) +{ + init(frame); +} + +VimFormView::~VimFormView() +{ + if (menuBar) { + RemoveChild(menuBar); +#ifdef never + // deleting the menuBar leads to SEGV on exit + // richard@whitequeen.com Jul 99 + delete menuBar; +#endif + } + if (textArea) { + RemoveChild(textArea); + delete textArea; + } + gui.vimForm = NULL; +} + + void +VimFormView::init(BRect frame) +{ + menuBar = new BMenuBar(BRect(0,0,-MENUBAR_MARGIN,-MENUBAR_MARGIN), + "VimMenuBar"); + + AddChild(menuBar); + + BRect remaining = frame; + textArea = new VimTextAreaView(remaining); + AddChild(textArea); + /* The textArea will be resized later when menus are added */ + + gui.vimForm = this; +} + + void +VimFormView::AllAttached() +{ + /* + * Apparently signals are inherited by the created thread - + * disable the most annoying ones. + */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + if (menuBar && textArea) { + /* + * Resize the textArea to fill the space left over by the menu. + * This is somewhat futile since it will be done again once + * menus are added to the menu bar. + */ + BRect remaining = Bounds(); + remaining.top = MenuHeight(); + textArea->ResizeTo(remaining.Width(), remaining.Height()); + textArea->MoveTo(remaining.left, remaining.top); + +#ifdef FEAT_MENU + menuBar->ResizeTo(remaining.right, remaining.top); + gui.menu_height = (int) remaining.top; +#endif + } + Inherited::AllAttached(); +} + + void +VimFormView::FrameResized(float new_width, float new_height) +{ + BWindow *w = Window(); +#if 1 + /* + * Look if there are more resize messages in the queue. + * If so, ignore this one. The later one will be handled + * eventually. + */ + BMessageQueue *q = w->MessageQueue(); + if (q->FindMessage(B_VIEW_RESIZED, 0) != NULL) { + return; + } +#endif + new_width += 1; // adjust from width to number of pixels occupied + new_height += 1; + +#if !HAVE_R3_OR_LATER + int adjust_h, adjust_w; + + adjust_w = ((int)new_width - gui_get_base_width()) % gui.char_width; + adjust_h = ((int)new_height - gui_get_base_height()) % gui.char_height; + + if (adjust_w > 0 || adjust_h > 0) { + /* + * This will generate a new FrameResized() message. + * If we're running R3 or later, SetWindowAlignment() should make + * sure that this does not happen. + */ + w->ResizeBy(-adjust_w, -adjust_h); + + return; + } +#endif + + struct VimResizeMsg sm; + sm.width = (int) new_width; + sm.height = (int) new_height; + + write_port(gui.vdcmp, VimMsg::Resize, &sm, sizeof(sm)); + // calls gui_resize_shell(new_width, new_height); + + return; + + /* + * The area below the vertical scrollbar is erased to the colour + * set with SetViewColor() automatically, because we had set + * B_WILL_DRAW. Resizing the window tight around the vertical + * scroll bar also helps to avoid debris. + */ +} + +/* ---------------- VimTextAreaView ---------------- */ + +VimTextAreaView::VimTextAreaView(BRect frame): + BView(frame, "VimTextAreaView", B_FOLLOW_ALL_SIDES, + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), + mouseDragEventCount(0) +{ + init(frame); +} + +VimTextAreaView::~VimTextAreaView() +{ + gui.vimTextArea = NULL; +} + + void +VimTextAreaView::init(BRect frame) +{ + /* set up global var for fast access */ + gui.vimTextArea = this; + + /* + * Tell the app server not to erase the view: we will + * fill it in completely by ourselves. + * (Does this really work? Even if not, it won't harm either.) + */ + SetViewColor(B_TRANSPARENT_32_BIT); +#define PEN_WIDTH 1 + SetPenSize(PEN_WIDTH); +} + + void +VimTextAreaView::Draw(BRect updateRect) +{ + /* + * XXX Other ports call here: + * out_flush(); * make sure all output has been processed * + * but we can't do that, since it involves too much information + * that is owned by other threads... + */ + + /* + * No need to use gui.vimWindow->Lock(): we are locked already. + * However, it would not hurt. + */ + gui_redraw((int) updateRect.left, (int) updateRect.top, + (int) (updateRect.Width() + PEN_WIDTH), (int) (updateRect.Height() + PEN_WIDTH)); + + /* Clear the border areas if needed */ + rgb_color rgb = GUI_TO_RGB(gui.back_pixel); + SetLowColor(rgb); + + if (updateRect.left < FILL_X(0)) // left border + FillRect(BRect(updateRect.left, updateRect.top, + FILL_X(0)-PEN_WIDTH, updateRect.bottom), B_SOLID_LOW); + if (updateRect.top < FILL_Y(0)) // top border + FillRect(BRect(updateRect.left, updateRect.top, + updateRect.right, FILL_Y(0)-PEN_WIDTH), B_SOLID_LOW); + if (updateRect.right >= FILL_X(Columns)) // right border + FillRect(BRect(FILL_X((int)Columns), updateRect.top, + updateRect.right, updateRect.bottom), B_SOLID_LOW); + if (updateRect.bottom >= FILL_Y(Rows)) // bottom border + FillRect(BRect(updateRect.left, FILL_Y((int)Rows), + updateRect.right, updateRect.bottom), B_SOLID_LOW); +} + + void +VimTextAreaView::KeyDown(const char *bytes, int32 numBytes) +{ + struct VimKeyMsg km; + char_u *dest = km.chars; + + BMessage *msg = Window()->CurrentMessage(); + assert(msg); + //msg->PrintToStream(); + + /* + * Convert special keys to Vim codes. + * I think it is better to do it in the window thread + * so we use at least a little bit of the potential + * of our 2 CPUs. Besides, due to the fantastic mapping + * of special keys to UTF-8, we have quite some work to + * do... + * TODO: I'm not quite happy with detection of special + * keys. Perhaps I should use scan codes after all... + */ + if (numBytes > 1) { + /* This cannot be a special key */ + if (numBytes > KEY_MSG_BUFSIZ) + numBytes = KEY_MSG_BUFSIZ; // should never happen... ??? + km.length = numBytes; + memcpy((char *)dest, bytes, numBytes); + } else { + int32 scancode = 0; + msg->FindInt32("key", &scancode); + + int32 beModifiers = 0; + msg->FindInt32("modifiers", &beModifiers); + + char_u string[3]; + int len = 0; + km.length = 0; + + bool canHaveVimModifiers = false; + + /* + * For normal, printable ASCII characters, don't look them up + * to check if they might be a special key. They aren't. + */ + assert(B_BACKSPACE <= 0x20); + assert(B_DELETE == 0x7F); + if (((char_u)bytes[0] <= 0x20 || (char_u)bytes[0] == 0x7F) && + numBytes == 1) { + /* + * Due to the great nature of Be's mapping of special keys, + * viz. into the range of the control characters, + * we can only be sure it is *really* a special key if + * if it is special without using ctrl. So, only if ctrl is + * used, we need to check it unmodified. + */ + if (beModifiers & B_CONTROL_KEY) { + int index = keyMap->normal_map[scancode]; + int newNumBytes = keyMapChars[index]; + char_u *newBytes = (char_u *)&keyMapChars[index + 1]; + + /* + * Check if still special without the control key. + * This is needed for BACKSPACE: that key does produce + * different values with modifiers (DEL). + * Otherwise we could simply have checked for equality. + */ + if (newNumBytes != 1 || (*newBytes > 0x20 && + *newBytes != 0x7F )) { + goto notspecial; + } + bytes = (char *)newBytes; + } + canHaveVimModifiers = true; + + uint16 beoskey; + int first, last; + + /* + * If numBytes == 0 that probably always indicates a special key. + * (does not happen yet) + */ + if (numBytes == 0 || bytes[0] == B_FUNCTION_KEY) { + beoskey = F(scancode); + first = FIRST_FUNCTION_KEY; + last = NUM_SPECIAL_KEYS; + } else if (*bytes == '\n' && scancode == 0x47) { + /* remap the (non-keypad) ENTER key from \n to \r. */ + string[0] = '\r'; + len = 1; + first = last = 0; + } else { + beoskey = K(bytes[0]); + first = 0; + last = FIRST_FUNCTION_KEY; + } + + for (int i = first; i < last; i++) { + if (special_keys[i].BeKeys == beoskey) { + string[0] = CSI; + string[1] = special_keys[i].vim_code0; + string[2] = special_keys[i].vim_code1; + len = 3; + } + } + } + notspecial: + if (len == 0) { + string[0] = bytes[0]; + len = 1; + } + + /* Special keys (and a few others) may have modifiers */ +#if 0 + if (len == 3 || + bytes[0] == B_SPACE || bytes[0] == B_TAB || + bytes[0] == B_RETURN || bytes[0] == '\r' || + bytes[0] == B_ESCAPE) +#else + if (canHaveVimModifiers) +#endif + { + int modifiers; + modifiers = 0; + if (beModifiers & B_SHIFT_KEY) + modifiers |= MOD_MASK_SHIFT; + if (beModifiers & B_CONTROL_KEY) + modifiers |= MOD_MASK_CTRL; + if (beModifiers & B_OPTION_KEY) + modifiers |= MOD_MASK_ALT; + + /* + * For some keys a shift modifier is translated into another key + * code. Do we need to handle the case where len != 1 and + * string[0] != CSI? (Not for BeOS, since len == 3 implies + * string[0] == CSI...) + */ + int key; + if (string[0] == CSI && len == 3) + key = TO_SPECIAL(string[1], string[2]); + else + key = string[0]; + key = simplify_key(key, &modifiers); + if (IS_SPECIAL(key)) + { + string[0] = CSI; + string[1] = K_SECOND(key); + string[2] = K_THIRD(key); + len = 3; + } + else + { + string[0] = key; + len = 1; + } + + if (modifiers) + { + *dest++ = CSI; + *dest++ = KS_MODIFIER; + *dest++ = modifiers; + km.length = 3; + } + } + memcpy((char *)dest, string, len); + km.length += len; + } + + write_port(gui.vdcmp, VimMsg::Key, &km, sizeof(km)); + + /* + * blank out the pointer if necessary + */ + if (p_mh && !gui.pointer_hidden) + { + guiBlankMouse(true); + gui.pointer_hidden = TRUE; + } +} + void +VimTextAreaView::guiSendMouseEvent( + int button, + int x, + int y, + int repeated_click, + int_u modifiers) +{ + VimMouseMsg mm; + + mm.button = button; + mm.x = x; + mm.y = y; + mm.repeated_click = repeated_click; + mm.modifiers = modifiers; + + write_port(gui.vdcmp, VimMsg::Mouse, &mm, sizeof(mm)); + // calls gui_send_mouse_event() + + /* + * if our pointer is currently hidden, then we should show it. + */ + if (gui.pointer_hidden) + { + guiBlankMouse(false); + gui.pointer_hidden = FALSE; + } +} + + void +VimTextAreaView::guiBlankMouse(bool should_hide) +{ + if (should_hide) { + //gui.vimApp->HideCursor(); + gui.vimApp->ObscureCursor(); + /* + * ObscureCursor() would even be easier, but then + * Vim's idea of mouse visibility does not necessarily + * correspond to reality. + */ + } else { + //gui.vimApp->ShowCursor(); + } +} + + int_u +VimTextAreaView::mouseModifiersToVim(int32 beModifiers) +{ + int_u vim_modifiers = 0x0; + + if (beModifiers & B_SHIFT_KEY) + vim_modifiers |= MOUSE_SHIFT; + if (beModifiers & B_CONTROL_KEY) + vim_modifiers |= MOUSE_CTRL; + if (beModifiers & B_OPTION_KEY) /* Alt or Meta key */ + vim_modifiers |= MOUSE_ALT; + + return vim_modifiers; +} + + void +VimTextAreaView::MouseDown(BPoint point) +{ + BMessage *m = Window()->CurrentMessage(); + assert(m); + + int32 buttons = 0; + m->FindInt32("buttons", &buttons); + + int vimButton; + + if (buttons & B_PRIMARY_MOUSE_BUTTON) + vimButton = MOUSE_LEFT; + else if (buttons & B_SECONDARY_MOUSE_BUTTON) + vimButton = MOUSE_RIGHT; + else if (buttons & B_TERTIARY_MOUSE_BUTTON) + vimButton = MOUSE_MIDDLE; + else + return; /* Unknown button */ + + vimMouseButton = 1; /* don't care which one */ + + /* Handle multiple clicks */ + int32 clicks = 0; + m->FindInt32("clicks", &clicks); + + int32 modifiers = 0; + m->FindInt32("modifiers", &modifiers); + + vimMouseModifiers = mouseModifiersToVim(modifiers); + + guiSendMouseEvent(vimButton, point.x, point.y, + clicks > 1 /* = repeated_click*/, vimMouseModifiers); +} + + void +VimTextAreaView::MouseUp(BPoint point) +{ + vimMouseButton = 0; + + BMessage *m = Window()->CurrentMessage(); + assert(m); + //m->PrintToStream(); + + int32 modifiers = 0; + m->FindInt32("modifiers", &modifiers); + + vimMouseModifiers = mouseModifiersToVim(modifiers); + + guiSendMouseEvent(MOUSE_RELEASE, point.x, point.y, + 0 /* = repeated_click*/, vimMouseModifiers); + + Inherited::MouseUp(point); +} + + void +VimTextAreaView::MouseMoved(BPoint point, uint32 transit, const BMessage *message) +{ + /* + * if our pointer is currently hidden, then we should show it. + */ + if (gui.pointer_hidden) + { + guiBlankMouse(false); + gui.pointer_hidden = FALSE; + } + + if (!vimMouseButton) /* could also check m->"buttons" */ + return; + + atomic_add(&mouseDragEventCount, 1); + + /* Don't care much about "transit" */ + guiSendMouseEvent(MOUSE_DRAG, point.x, point.y, 0, vimMouseModifiers); +} + + void +VimTextAreaView::MessageReceived(BMessage *m) +{ + switch (m->what) { + case 'menu': + { + VimMenuMsg mm; + mm.guiMenu = NULL; /* in case no pointer in msg */ + m->FindPointer("VimMenu", (void **)&mm.guiMenu); + + write_port(gui.vdcmp, VimMsg::Menu, &mm, sizeof(mm)); + } + break; + default: + if (m->WasDropped()) { + BWindow *w = Window(); + w->DetachCurrentMessage(); + w->Minimize(false); + VimApp::SendRefs(m, (modifiers() & B_SHIFT_KEY) != 0); + } else { + Inherited::MessageReceived(m); + } + break; + } +} + + int +VimTextAreaView::mchInitFont(char_u *name) +{ + VimFont *newFont = (VimFont *)gui_mch_get_font(name, 0); + + gui.norm_font = (GuiFont)newFont; + gui_mch_set_font((GuiFont)newFont); + if (name) + hl_set_font_name(name); + + SetDrawingMode(B_OP_COPY); + + /* + * Try to load other fonts for bold, italic, and bold-italic. + * We should also try to work out what font to use for these when they are + * not specified by X resources, but we don't yet. + */ + + return OK; +} + + void +VimTextAreaView::mchDrawString(int row, int col, char_u *s, int len, int flags) +{ + /* + * First we must erase the area, because DrawString won't do + * that for us. XXX Most of the time this is a waste of effort + * since the bachground has been erased already... DRAW_TRANSP + * should be set when appropriate!!! + * (Rectangles include the bottom and right edge) + */ + if (!(flags & DRAW_TRANSP)) { + BRect r(FILL_X(col), FILL_Y(row), + FILL_X(col + len) - PEN_WIDTH, FILL_Y(row + 1) - PEN_WIDTH); + FillRect(r, B_SOLID_LOW); + } + BPoint where(TEXT_X(col), TEXT_Y(row)); + DrawString((char *)s, len, where); + + if (flags & DRAW_BOLD) { + where.x += 1.0; + SetDrawingMode(B_OP_BLEND); + DrawString((char *)s, len, where); + SetDrawingMode(B_OP_COPY); + } + if (flags & DRAW_UNDERL) { + BPoint start(FILL_X(col), FILL_Y(row + 1) - PEN_WIDTH); + BPoint end(FILL_X(col + len) - PEN_WIDTH, start.y); + + StrokeLine(start, end); + } +} + + void +VimTextAreaView::mchClearBlock( + int row1, + int col1, + int row2, + int col2) +{ + BRect r(FILL_X(col1), FILL_Y(row1), + FILL_X(col2 + 1) - PEN_WIDTH, FILL_Y(row2 + 1) - PEN_WIDTH); + gui_mch_set_bg_color(gui.back_pixel); + FillRect(r, B_SOLID_LOW); +} + + void +VimTextAreaView::mchClearAll() +{ + gui_mch_set_bg_color(gui.back_pixel); + FillRect(Bounds(), B_SOLID_LOW); +} + +/* + * mchDeleteLines() Lock()s the window by itself. + */ + void +VimTextAreaView::mchDeleteLines(int row, int num_lines) +{ + if (row + num_lines > gui.scroll_region_bot) + { + /* Scrolled out of region, just blank the lines out */ + gui_clear_block(row, gui.scroll_region_left, + gui.scroll_region_bot, gui.scroll_region_right); + } + else + { + /* copy one extra pixel, for when bold has spilled over */ + int width = gui.char_width * (gui.scroll_region_right + - gui.scroll_region_left + 1) + 1 - PEN_WIDTH; + int height = gui.char_height * + (gui.scroll_region_bot - row - num_lines + 1) - PEN_WIDTH; + + BRect source, dest; + + source.left = FILL_X(gui.scroll_region_left); + source.top = FILL_Y(row + num_lines); + source.right = source.left + width; + source.bottom = source.top + height; + + dest.left = FILL_X(gui.scroll_region_left); + dest.top = FILL_Y(row); + dest.right = dest.left + width; + dest.bottom = dest.top + height; + + /* XXX Attempt at a hack: */ + gui.vimWindow->UpdateIfNeeded(); +#if 0 + /* XXX Attempt at a hack: */ + if (gui.vimWindow->NeedsUpdate()) { + fprintf(stderr, "mchDeleteLines: NeedsUpdate!\n"); + gui.vimWindow->UpdateIfNeeded(); + while (gui.vimWindow->NeedsUpdate()) { + if (false && gui.vimWindow->Lock()) { + Sync(); + gui.vimWindow->Unlock(); + } + snooze(2); + } + } +#endif + + if (gui.vimWindow->Lock()) { + Sync(); + CopyBits(source, dest); + //Sync(); + + /* Update gui.cursor_row if the cursor scrolled or copied over */ + if (gui.cursor_row >= row + && gui.cursor_col >= gui.scroll_region_left + && gui.cursor_col <= gui.scroll_region_right) + { + if (gui.cursor_row < row + num_lines) + gui.cursor_is_valid = FALSE; + else if (gui.cursor_row <= gui.scroll_region_bot) + gui.cursor_row -= num_lines; + } + + /* Clear one column more for when bold has spilled over */ + gui_clear_block(gui.scroll_region_bot - num_lines + 1, + gui.scroll_region_left, + gui.scroll_region_bot, gui.scroll_region_right); + + gui.vimWindow->Unlock(); + /* + * The Draw() callback will be called now if some of the source + * bits were not in the visible region. + */ + + //gui_x11_check_copy_area(); + } + } +} + +/* + * mchInsertLines() Lock()s the window by itself. + */ + void +VimTextAreaView::mchInsertLines(int row, int num_lines) +{ + if (row + num_lines > gui.scroll_region_bot) + { + /* Scrolled out of region, just blank the lines out */ + gui_clear_block(row, gui.scroll_region_left, + gui.scroll_region_bot, gui.scroll_region_right); + } + else + { + /* copy one extra pixel, for when bold has spilled over */ + int width = gui.char_width * (gui.scroll_region_right + - gui.scroll_region_left + 1) + 1 - PEN_WIDTH; + int height = gui.char_height * + (gui.scroll_region_bot - row - num_lines + 1) - PEN_WIDTH; + + BRect source, dest; + + source.left = FILL_X(gui.scroll_region_left); + source.top = FILL_Y(row); + source.right = source.left + width; + source.bottom = source.top + height; + + dest.left = FILL_X(gui.scroll_region_left); + dest.top = FILL_Y(row + num_lines); + dest.right = dest.left + width; + dest.bottom = dest.top + height; + + /* XXX Attempt at a hack: */ + gui.vimWindow->UpdateIfNeeded(); +#if 0 + /* XXX Attempt at a hack: */ + if (gui.vimWindow->NeedsUpdate()) + fprintf(stderr, "mchInsertLines: NeedsUpdate!\n"); + gui.vimWindow->UpdateIfNeeded(); + while (gui.vimWindow->NeedsUpdate()) + snooze(2); +#endif + + if (gui.vimWindow->Lock()) { + Sync(); + CopyBits(source, dest); + //Sync(); + + /* Update gui.cursor_row if the cursor scrolled or copied over */ + if (gui.cursor_row >= gui.row + && gui.cursor_col >= gui.scroll_region_left + && gui.cursor_col <= gui.scroll_region_right) + { + if (gui.cursor_row <= gui.scroll_region_bot - num_lines) + gui.cursor_row += num_lines; + else if (gui.cursor_row <= gui.scroll_region_bot) + gui.cursor_is_valid = FALSE; + } + /* Clear one column more for when bold has spilled over */ + gui_clear_block(row, gui.scroll_region_left, + row + num_lines - 1, gui.scroll_region_right); + + gui.vimWindow->Unlock(); + /* + * The Draw() callback will be called now if some of the source + * bits were not in the visible region. + * However, if we scroll too fast it can't keep up and the + * update region gets messed up. This seems to be because copying + * un-Draw()n bits does not generate Draw() calls for the copy... + * I moved the hack to before the CopyBits() to reduce the + * amount of additional waiting needed. + */ + + //gui_x11_check_copy_area(); + } + } + +} + +/* ---------------- VimScrollBar ---------------- */ + +/* BUG: XXX + * It seems that BScrollBar determine their direction not from + * "posture" but from if they are "tall" or "wide" in shape... + * + * Also, place them out of sight, because Vim enables them before + * they are positioned. + */ +VimScrollBar::VimScrollBar(scrollbar_T *g, orientation posture): + BScrollBar(posture == B_HORIZONTAL ? BRect(-100,-100,-10,-90) : + BRect(-100,-100,-90,-10), + "vim scrollbar", (BView *)NULL, + 0.0, 10.0, posture), + ignoreValue(-1), + scrollEventCount(0) +{ + gsb = g; + SetResizingMode(B_FOLLOW_NONE); +} + +VimScrollBar::~VimScrollBar() +{ +} + + void +VimScrollBar::ValueChanged(float newValue) +{ + if (ignoreValue >= 0.0 && newValue == ignoreValue) { + ignoreValue = -1; + return; + } + ignoreValue = -1; + /* + * We want to throttle the amount of scroll messages generated. + * Normally I presume you won't get a new message before we've + * handled the previous one, but because we're passing them on this + * happens very quickly. So instead we keep a counter of how many + * scroll events there are (or will be) in the VDCMP, and the + * throttling happens at the receiving end. + */ + atomic_add(&scrollEventCount, 1); + + struct VimScrollBarMsg sm; + + sm.sb = this; + sm.value = (long) newValue; + sm.stillDragging = TRUE; + + write_port(gui.vdcmp, VimMsg::ScrollBar, &sm, sizeof(sm)); + + // calls gui_drag_scrollbar(sb, newValue, TRUE); +} + +/* + * When the mouse goes up, report that scrolling has stopped. + * MouseUp() is NOT called when the mouse-up occurs outside + * the window, even though the thumb does move while the mouse + * is outside... This has some funny effects... XXX + * So we do special processing when the window de/activates. + */ + void +VimScrollBar::MouseUp(BPoint where) +{ + //BMessage *m = Window()->CurrentMessage(); + //m->PrintToStream(); + + atomic_add(&scrollEventCount, 1); + + struct VimScrollBarMsg sm; + + sm.sb = this; + sm.value = (long) Value(); + sm.stillDragging = FALSE; + + write_port(gui.vdcmp, VimMsg::ScrollBar, &sm, sizeof(sm)); + + // calls gui_drag_scrollbar(sb, newValue, FALSE); + + Inherited::MouseUp(where); +} + + void +VimScrollBar::SetValue(float newValue) +{ + if (newValue == Value()) + return; + + ignoreValue = newValue; + Inherited::SetValue(newValue); +} + +/* ---------------- VimFont ---------------- */ + +VimFont::VimFont(): BFont() +{ + init(); +} + +VimFont::VimFont(const VimFont *rhs): BFont(rhs) +{ + init(); +} + +VimFont::VimFont(const BFont *rhs): BFont(rhs) +{ + init(); +} + +VimFont::VimFont(const VimFont &rhs): BFont(rhs) +{ + init(); +} + +VimFont::~VimFont() +{ +} + + void +VimFont::init() +{ + next = NULL; + refcount = 1; + name = NULL; +} + +/* ---------------- ---------------- */ + +// some global variables +static char appsig[] = "application/x-vnd.Rhialto-Vim-5"; +key_map *keyMap; +char *keyMapChars; +int main_exitcode = 127; + + status_t +gui_beos_process_event(bigtime_t timeout) +{ + struct VimMsg vm; + long what; + ssize_t size; + + size = read_port_etc(gui.vdcmp, &what, &vm, sizeof(vm), + B_TIMEOUT, timeout); + + if (size >= 0) { + switch (what) { + case VimMsg::Key: + { + char_u *string = vm.u.Key.chars; + int len = vm.u.Key.length; + if (len == 1 && string[0] == Ctrl_chr('C')) { + trash_input_buf(); + got_int = TRUE; + } + add_to_input_buf(string, len); + } + break; + case VimMsg::Resize: + gui_resize_shell(vm.u.NewSize.width, vm.u.NewSize.height); + break; + case VimMsg::ScrollBar: + { + /* + * If loads of scroll messages queue up, use only the last + * one. Always report when the scrollbar stops dragging. + * This is not perfect yet anyway: these events are queued + * yet again, this time in the keyboard input buffer. + */ + int32 oldCount = + atomic_add(&vm.u.Scroll.sb->scrollEventCount, -1); + if (oldCount <= 1 || !vm.u.Scroll.stillDragging) + gui_drag_scrollbar(vm.u.Scroll.sb->getGsb(), + vm.u.Scroll.value, vm.u.Scroll.stillDragging); + } + break; + case VimMsg::Menu: + gui_menu_cb(vm.u.Menu.guiMenu); + break; + case VimMsg::Mouse: + { + int32 oldCount; + if (vm.u.Mouse.button == MOUSE_DRAG) + oldCount = + atomic_add(&gui.vimTextArea->mouseDragEventCount, -1); + else + oldCount = 0; + if (oldCount <= 1) + gui_send_mouse_event(vm.u.Mouse.button, vm.u.Mouse.x, + vm.u.Mouse.y, vm.u.Mouse.repeated_click, + vm.u.Mouse.modifiers); + } + break; + case VimMsg::Focus: + gui.in_focus = vm.u.Focus.active; + /* XXX Signal that scrollbar dragging has stopped? + * This is needed because we don't get a MouseUp if + * that happens while outside the window... :-( + */ + if (gui.dragged_sb) { + gui.dragged_sb = SBAR_NONE; + } + gui_update_cursor(TRUE, FALSE); + break; + case VimMsg::Refs: + ::RefsReceived(vm.u.Refs.message, vm.u.Refs.changedir); + break; + default: + // unrecognised message, ignore it + break; + } + } + + /* + * If size < B_OK, it is an error code. + */ + return size; +} + +/* + * Here are some functions to protect access to ScreenLines[] and + * LineOffset[]. These are used from the window thread to respond + * to a Draw() callback. When that occurs, the window is already + * locked by the system. + * + * Other code that needs to lock is any code that changes these + * variables. Other read-only access, or access merely to the + * contents of the screen buffer, need not be locked. + * + * If there is no window, don't call Lock() but do succeed. + */ + + int +vim_lock_screen() +{ + return !gui.vimWindow || gui.vimWindow->Lock(); +} + + void +vim_unlock_screen() +{ + if (gui.vimWindow) + gui.vimWindow->Unlock(); +} + +#define RUN_BAPPLICATION_IN_NEW_THREAD 0 + +#if RUN_BAPPLICATION_IN_NEW_THREAD + + int32 +run_vimapp(void *args) +{ + VimApp app(appsig); + + gui.vimApp = &app; + app.Run(); /* Run until Quit() called */ + + return 0; +} + +#else + + int32 +call_main(void *args) +{ + struct MainArgs *ma = (MainArgs *)args; + + return main(ma->argc, ma->argv); +} +#endif + +extern "C" { + +/* + * Parse the GUI related command-line arguments. Any arguments used are + * deleted from argv, and *argc is decremented accordingly. This is called + * when vim is started, whether or not the GUI has been started. + */ + void +gui_mch_prepare( + int *argc, + char **argv) +{ + /* + * We don't have any command line arguments for the BeOS GUI yet, + * but this is an excellent place to create our Application object. + */ + if (!gui.vimApp) { + thread_info tinfo; + get_thread_info(find_thread(NULL), &tinfo); + + /* May need the port very early on to process RefsReceived() */ + gui.vdcmp = create_port(B_MAX_PORT_COUNT, "vim VDCMP"); + +#if RUN_BAPPLICATION_IN_NEW_THREAD + thread_id tid = spawn_thread(run_vimapp, "vim VimApp", + tinfo.priority, NULL); + if (tid >= B_OK) { + resume_thread(tid); + } else { + getout(1); + } +#else + MainArgs ma = { *argc, argv }; + thread_id tid = spawn_thread(call_main, "vim main()", + tinfo.priority, &ma); + if (tid >= B_OK) { + VimApp app(appsig); + + gui.vimApp = &app; + resume_thread(tid); + /* + * This is rather horrible. + * call_main will call main() again... + * There will be no infinite recursion since + * gui.vimApp is set now. + */ + app.Run(); /* Run until Quit() called */ + //fprintf(stderr, "app.Run() returned...\n"); + status_t dummy_exitcode; + (void)wait_for_thread(tid, &dummy_exitcode); + + /* + * This path should be the normal one taken to exit Vim. + * The main() thread calls mch_exit() which calls + * gui_mch_exit() which terminates its thread. + */ + exit(main_exitcode); + } +#endif + } + /* Don't fork() when starting the GUI. Spawned threads are not + * duplicated with a fork(). The result is a mess. + */ + gui.dofork = FALSE; + /* + * XXX Try to determine whether we were started from + * the Tracker or the terminal. + * It would be nice to have this work, because the Tracker + * follows symlinks, so even if you double-click on gvim, + * when it is a link to vim it will still pass a command name + * of vim... + * We try here to see if stdin comes from /dev/null. If so, + * (or if there is an error, which should never happen) start the GUI. + * This does the wrong thing for vim - </dev/null, and we're + * too early to see the command line parsing. Tough. + * On the other hand, it starts the gui for vim file & which is nice. + */ + if (!isatty(0)) { + struct stat stat_stdin, stat_dev_null; + + if (fstat(0, &stat_stdin) == -1 || + stat("/dev/null", &stat_dev_null) == -1 || + (stat_stdin.st_dev == stat_dev_null.st_dev && + stat_stdin.st_ino == stat_dev_null.st_ino)) + gui.starting = TRUE; + } +} + +/* + * Check if the GUI can be started. Called before gvimrc is sourced. + * Return OK or FAIL. + */ + int +gui_mch_init_check(void) +{ + return OK; /* TODO: GUI can always be started? */ +} + +/* + * Initialise the GUI. Create all the windows, set up all the call-backs + * etc. + */ + int +gui_mch_init() +{ + gui.def_norm_pixel = RGB(0x00, 0x00, 0x00); // black + gui.def_back_pixel = RGB(0xFF, 0xFF, 0xFF); // white + gui.norm_pixel = gui.def_norm_pixel; + gui.back_pixel = gui.def_back_pixel; + + gui.scrollbar_width = (int) B_V_SCROLL_BAR_WIDTH; + gui.scrollbar_height = (int) B_H_SCROLL_BAR_HEIGHT; +#ifdef FEAT_MENU + gui.menu_height = 19; // initial guess - + // correct for my default settings +#endif + gui.border_offset = 3; // coordinates are inside window borders + + if (gui.vdcmp < B_OK) + return FAIL; + get_key_map(&keyMap, &keyMapChars); + + gui.vimWindow = new VimWindow(); /* hidden and locked */ + if (!gui.vimWindow) + return FAIL; + + gui.vimWindow->Run(); /* Run() unlocks but does not show */ + + /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc + * file) */ + set_normal_colors(); + + /* + * Check that none of the colors are the same as the background color + */ + gui_check_colors(); + + /* Get the colors for the highlight groups (gui_check_colors() might have + * changed them) */ + highlight_gui_started(); /* re-init colors and fonts */ + + gui_mch_new_colors(); /* window must exist for this */ + + return OK; +} + +/* + * Called when the foreground or background color has been changed. + */ + void +gui_mch_new_colors() +{ + rgb_color rgb = GUI_TO_RGB(gui.back_pixel); + + if (gui.vimWindow->Lock()) { + gui.vimForm->SetViewColor(rgb); + // Does this not have too much effect for those small rectangles? + gui.vimForm->Invalidate(); + gui.vimWindow->Unlock(); + } +} + +/* + * Open the GUI window which was created by a call to gui_mch_init(). + */ + int +gui_mch_open() +{ + if (gui_win_x != -1 && gui_win_y != -1) + gui_mch_set_winpos(gui_win_x, gui_win_y); + + /* Actually open the window */ + if (gui.vimWindow->Lock()) { + gui.vimWindow->Show(); + gui.vimWindow->Unlock(); + +#if USE_THREAD_FOR_INPUT_WITH_TIMEOUT + /* Kill the thread that may have been created for the Terminal */ + beos_cleanup_read_thread(); +#endif + + return OK; + } + + return FAIL; +} + + void +gui_mch_exit(int vim_exitcode) +{ + if (gui.vimWindow) { + thread_id tid = gui.vimWindow->Thread(); + gui.vimWindow->Lock(); + gui.vimWindow->Quit(); + /* Wait until it is truely gone */ + int32 exitcode; + wait_for_thread(tid, &exitcode); + } + delete_port(gui.vdcmp); +#if !RUN_BAPPLICATION_IN_NEW_THREAD + /* + * We are in the main() thread - quit the App thread and + * quit ourselves (passing on the exitcode). Use a global since the + * value from exit_thread() is only used if wait_for_thread() is + * called in time (race condition). + */ +#endif + if (gui.vimApp) { + VimTextAreaView::guiBlankMouse(false); + + main_exitcode = vim_exitcode; +#if RUN_BAPPLICATION_IN_NEW_THREAD + thread_id tid = gui.vimApp->Thread(); + int32 exitcode; + gui.vimApp->Lock(); + gui.vimApp->Quit(); + gui.vimApp->Unlock(); + wait_for_thread(tid, &exitcode); +#else + gui.vimApp->Lock(); + gui.vimApp->Quit(); + gui.vimApp->Unlock(); + /* suicide */ + exit_thread(vim_exitcode); +#endif + } + /* If we are somehow still here, let mch_exit() handle things. */ +} + +/* + * Get the position of the top left corner of the window. + */ + int +gui_mch_get_winpos(int *x, int *y) +{ + /* TODO */ + return FAIL; +} + +/* + * Set the position of the top left corner of the window to the given + * coordinates. + */ + void +gui_mch_set_winpos(int x, int y) +{ + /* TODO */ +} + +/* + * Set the size of the window to the given width and height in pixels. + */ + void +gui_mch_set_shellsize( + int width, + int height, + int min_width, + int min_height, + int base_width, + int base_height) +{ + /* + * We are basically given the size of the VimForm, if I understand + * correctly. Since it fills the window completely, this will also + * be the size of the window. + */ + if (gui.vimWindow->Lock()) { + gui.vimWindow->ResizeTo(width - PEN_WIDTH, height - PEN_WIDTH); + + /* set size limits */ + float minWidth, maxWidth, minHeight, maxHeight; + + gui.vimWindow->GetSizeLimits(&minWidth, &maxWidth, + &minHeight, &maxHeight); + gui.vimWindow->SetSizeLimits(min_width, maxWidth, + min_height, maxHeight); + +#if HAVE_R3_OR_LATER + /* + * Set the resizing alignment depending on font size. + * XXX This is untested, since I don't have R3 yet. + */ + SetWindowAlignment( + B_PIXEL_ALIGNMENT, // window_alignment mode, + 1, // int32 h, + 0, // int32 hOffset = 0, + gui.char_width, // int32 width = 0, + base_width, // int32 widthOffset = 0, + 1, // int32 v = 0, + 0, // int32 vOffset = 0, + gui.char_height, // int32 height = 0, + base_height // int32 heightOffset = 0 + ); +#else + /* don't know what to do with base_{width,height}. */ +#endif + + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_get_screen_dimensions( + int *screen_w, + int *screen_h) +{ + BRect frame; + + { + BScreen screen(gui.vimWindow); + + if (screen.IsValid()) { + frame = screen.Frame(); + } else { + frame.right = 640; + frame.bottom = 480; + } + } + + /* XXX approximations... */ + *screen_w = (int) frame.right - 2 * gui.scrollbar_width - 20; + *screen_h = (int) frame.bottom - gui.scrollbar_height +#ifdef FEAT_MENU + - gui.menu_height +#endif + - 30; +} + + void +gui_mch_set_text_area_pos( + int x, + int y, + int w, + int h) +{ + if (!gui.vimTextArea) + return; + + if (gui.vimWindow->Lock()) { + gui.vimTextArea->MoveTo(x, y); + gui.vimTextArea->ResizeTo(w - PEN_WIDTH, h - PEN_WIDTH); + gui.vimWindow->Unlock(); + } +} + + +/* + * Scrollbar stuff: + */ + + void +gui_mch_enable_scrollbar( + scrollbar_T *sb, + int flag) +{ + VimScrollBar *vsb = sb->id; + if (gui.vimWindow->Lock()) { + /* + * This function is supposed to be idempotent, but Show()/Hide() + * is not. Therefore we test if they are needed. + */ + if (flag) { + if (vsb->IsHidden()) { + vsb->Show(); + } + } else { + if (!vsb->IsHidden()) { + vsb->Hide(); + } + } + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_set_scrollbar_thumb( + scrollbar_T *sb, + int val, + int size, + int max) +{ + if (gui.vimWindow->Lock()) { + VimScrollBar *s = sb->id; + if (max == 0) { + s->SetValue(0); + s->SetRange(0.0, 0.0); + } else { + s->SetProportion((float)size / (max + 1.0)); + s->SetSteps(1.0, size > 5 ? size - 2 : size); +#ifndef SCROLL_PAST_END // really only defined in gui.c... + max = max + 1 - size; +#endif + if (max < s->Value()) { + /* + * If the new maximum is lower than the current value, + * setting it would cause the value to be clipped and + * therefore a ValueChanged() call. + * We avoid this by setting the value first, because + * it presumably is <= max. + */ + s->SetValue(val); + s->SetRange(0.0, max); + } else { + /* + * In the other case, set the range first, since the + * new value might be higher than the current max. + */ + s->SetRange(0.0, max); + s->SetValue(val); + } + } + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_set_scrollbar_pos( + scrollbar_T *sb, + int x, + int y, + int w, + int h) +{ + if (gui.vimWindow->Lock()) { + VimScrollBar *vsb = sb->id; + vsb->MoveTo(x, y); + vsb->ResizeTo(w - PEN_WIDTH, h - PEN_WIDTH); + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_create_scrollbar( + scrollbar_T *sb, + int orient) /* SBAR_VERT or SBAR_HORIZ */ +{ + orientation posture = + (orient == SBAR_HORIZ) ? B_HORIZONTAL : B_VERTICAL; + + VimScrollBar *vsb = sb->id = new VimScrollBar(sb, posture); + if (gui.vimWindow->Lock()) { + vsb->SetTarget(gui.vimTextArea); + vsb->Hide(); + gui.vimForm->AddChild(vsb); + gui.vimWindow->Unlock(); + } +} + +#if defined(FEAT_WINDOWS) || defined(PROTO) + void +gui_mch_destroy_scrollbar( + scrollbar_T *sb) +{ + if (gui.vimWindow->Lock()) { + sb->id->RemoveSelf(); + delete sb->id; + gui.vimWindow->Unlock(); + } +} +#endif + +/* + * Cursor blink functions. + * + * This is a simple state machine: + * BLINK_NONE not blinking at all + * BLINK_OFF blinking, cursor is not shown + * BLINK_ON blinking, cursor is shown + */ + +#define BLINK_NONE 0 +#define BLINK_OFF 1 +#define BLINK_ON 2 + +static int blink_state = BLINK_NONE; +static long_u blink_waittime = 700; +static long_u blink_ontime = 400; +static long_u blink_offtime = 250; +static int blink_timer = 0; + + void +gui_mch_set_blinking( + long waittime, + long on, + long off) +{ + /* TODO */ + blink_waittime = waittime; + blink_ontime = on; + blink_offtime = off; +} + +/* + * Stop the cursor blinking. Show the cursor if it wasn't shown. + */ + void +gui_mch_stop_blink() +{ + /* TODO */ + if (blink_timer != 0) + { + //XtRemoveTimeOut(blink_timer); + blink_timer = 0; + } + if (blink_state == BLINK_OFF) + gui_update_cursor(TRUE, FALSE); + blink_state = BLINK_NONE; +} + +/* + * Start the cursor blinking. If it was already blinking, this restarts the + * waiting time and shows the cursor. + */ + void +gui_mch_start_blink() +{ + /* TODO */ + if (blink_timer != 0) + ;//XtRemoveTimeOut(blink_timer); + /* Only switch blinking on if none of the times is zero */ + if (blink_waittime && blink_ontime && blink_offtime && gui.in_focus) + { + blink_timer = 1; //XtAppAddTimeOut(app_context, blink_waittime, + blink_state = BLINK_ON; + gui_update_cursor(TRUE, FALSE); + } +} + +/* + * Initialise vim to use the font with the given name. Return FAIL if the font + * could not be loaded, OK otherwise. + */ + int +gui_mch_init_font( + char_u *font_name, + int fontset) +{ + if (gui.vimWindow->Lock()) + { + int rc = gui.vimTextArea->mchInitFont(font_name); + gui.vimWindow->Unlock(); + + return rc; + } + + return FAIL; +} + + int +gui_mch_adjust_charsize() +{ + return FAIL; +} + + GuiFont +gui_mch_get_font( + char_u *name, + int giveErrorIfMissing) +{ + VimFont *font = 0; + static VimFont *fontList = NULL; + + if (!gui.in_use) /* can't do this when GUI not running */ + return NOFONT; + + if (!name) + name = (char_u *)"be_fixed_font"; + + VimFont *flp; + for (flp = fontList; flp; flp = flp->next) { + if (STRCMP(name, flp->name) == 0) { + flp->refcount++; + return (GuiFont)flp; + } + } + + font = new VimFont(be_fixed_font); + + /* Set some universal features: */ + font->SetSpacing(B_FIXED_SPACING); + font->SetEncoding(B_ISO_8859_1); + + /* Remember font for later use */ + font->name = vim_strsave(name); + font->next = fontList; + fontList = font; + + font_family family; + font_style style; + int size; + int len; + char_u *end; + +#ifdef never + // This leads to SEGV/BUS on R4+ + // Replace underscores with spaces, and I can't see why ? + // richard@whitequeen.com jul 99 + while (end = (char_u *)strchr((char *)name, '_')) + *end = ' '; +#endif + /* + * Parse font names as Family/Style/Size. + * On errors, just keep the be_fixed_font. + */ + end = (char_u *)strchr((char *)name, '/'); + if (!end) + goto error; + strncpy(family, (char *)name, len = end - name); + family[len] = '\0'; + + name = end + 1; + end = (char_u *)strchr((char *)name, '/'); + if (!end) + goto error; + strncpy(style, (char *)name, len = end - name); + style[len] = '\0'; + + name = end + 1; + size = atoi((char *)name); + if (size <= 0) + goto error; + + font->SetFamilyAndStyle(family, style); + font->SetSize(size); + font->SetSpacing(B_FIXED_SPACING); + font->SetEncoding(B_ISO_8859_1); + //font->PrintToStream(); + + return (GuiFont)font; + +error: + if (giveErrorIfMissing) + EMSG2("(fe0) Unknown font: %s", name); + + return (GuiFont)font; +} + +/* + * Set the current text font. + */ + void +gui_mch_set_font( + GuiFont font) +{ + if (gui.vimWindow->Lock()) { + VimFont *vf = (VimFont *)font; + + gui.vimTextArea->SetFont(vf); + + gui.char_width = (int) vf->StringWidth("n"); + font_height fh; + vf->GetHeight(&fh); + gui.char_height = (int)(fh.ascent + 0.9999) + + (int)(fh.descent + 0.9999) + (int)(fh.leading + 0.9999); + gui.char_ascent = (int)(fh.ascent + 0.9999); + + gui.vimWindow->Unlock(); + } +} + +#if 0 /* not used */ +/* + * Return TRUE if the two fonts given are equivalent. + */ + int +gui_mch_same_font( + GuiFont f1, + GuiFont f2) +{ + VimFont *vf1 = (VimFont *)f1; + VimFont *vf2 = (VimFont *)f2; + + return f1 == f2 || + (vf1->FamilyAndStyle() == vf2->FamilyAndStyle() && + vf1->Size() == vf2->Size()); +} +#endif + +/* XXX TODO This is apparently never called... */ + void +gui_mch_free_font( + GuiFont font) +{ + VimFont *f = (VimFont *)font; + if (--f->refcount <= 0) { + if (f->refcount < 0) + fprintf(stderr, "VimFont: refcount < 0\n"); + delete f; + } +} + + static int +hex_digit(int c) +{ + if (isdigit(c)) + return c - '0'; + c = TOLOWER_ASC(c); + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1000; +} + +/* + * This function has been lifted from gui_w32.c and extended a bit. + * + * Return the Pixel value (color) for the given color name. + * Return INVALCOLOR for error. + */ + guicolor_T +gui_mch_get_color( + char_u *name) +{ + typedef struct GuiColourTable + { + char *name; + guicolor_T colour; + } GuiColourTable; + +#define NSTATIC_COLOURS 32 +#define NDYNAMIC_COLOURS 33 +#define NCOLOURS (NSTATIC_COLOURS + NDYNAMIC_COLOURS) + + static GuiColourTable table[NCOLOURS] = + { + {"Black", RGB(0x00, 0x00, 0x00)}, + {"DarkGray", RGB(0x80, 0x80, 0x80)}, + {"DarkGrey", RGB(0x80, 0x80, 0x80)}, + {"Gray", RGB(0xC0, 0xC0, 0xC0)}, + {"Grey", RGB(0xC0, 0xC0, 0xC0)}, + {"LightGray", RGB(0xD3, 0xD3, 0xD3)}, + {"LightGrey", RGB(0xD3, 0xD3, 0xD3)}, + {"White", RGB(0xFF, 0xFF, 0xFF)}, + {"DarkRed", RGB(0x80, 0x00, 0x00)}, + {"Red", RGB(0xFF, 0x00, 0x00)}, + {"LightRed", RGB(0xFF, 0xA0, 0xA0)}, + {"DarkBlue", RGB(0x00, 0x00, 0x80)}, + {"Blue", RGB(0x00, 0x00, 0xFF)}, + {"LightBlue", RGB(0xA0, 0xA0, 0xFF)}, + {"DarkGreen", RGB(0x00, 0x80, 0x00)}, + {"Green", RGB(0x00, 0xFF, 0x00)}, + {"LightGreen", RGB(0xA0, 0xFF, 0xA0)}, + {"DarkCyan", RGB(0x00, 0x80, 0x80)}, + {"Cyan", RGB(0x00, 0xFF, 0xFF)}, + {"LightCyan", RGB(0xA0, 0xFF, 0xFF)}, + {"DarkMagenta", RGB(0x80, 0x00, 0x80)}, + {"Magenta", RGB(0xFF, 0x00, 0xFF)}, + {"LightMagenta", RGB(0xFF, 0xA0, 0xFF)}, + {"Brown", RGB(0x80, 0x40, 0x40)}, + {"Yellow", RGB(0xFF, 0xFF, 0x00)}, + {"LightYellow", RGB(0xFF, 0xFF, 0xA0)}, + {"DarkYellow", RGB(0xBB, 0xBB, 0x00)}, + {"SeaGreen", RGB(0x2E, 0x8B, 0x57)}, + {"Orange", RGB(0xFF, 0xA5, 0x00)}, + {"Purple", RGB(0xA0, 0x20, 0xF0)}, + {"SlateBlue", RGB(0x6A, 0x5A, 0xCD)}, + {"Violet", RGB(0xEE, 0x82, 0xEE)}, + }; + + static int endColour = NSTATIC_COLOURS; + static int newColour = NSTATIC_COLOURS; + + int r, g, b; + int i; + + if (name[0] == '#' && STRLEN(name) == 7) + { + /* Name is in "#rrggbb" format */ + r = hex_digit(name[1]) * 16 + hex_digit(name[2]); + g = hex_digit(name[3]) * 16 + hex_digit(name[4]); + b = hex_digit(name[5]) * 16 + hex_digit(name[6]); + if (r < 0 || g < 0 || b < 0) + return INVALCOLOR; + return RGB(r, g, b); + } + else + { + /* Check if the name is one of the colours we know */ + for (i = 0; i < endColour; i++) + if (STRICMP(name, table[i].name) == 0) + return table[i].colour; + } + + /* + * Last attempt. Look in the file "$VIM/rgb.txt". + */ + { +#define LINE_LEN 100 + FILE *fd; + char line[LINE_LEN]; + char_u *fname; + + fname = expand_env_save((char_u *)"$VIM/rgb.txt"); + if (fname == NULL) + return INVALCOLOR; + + fd = fopen((char *)fname, "rt"); + vim_free(fname); + if (fd == NULL) + return INVALCOLOR; + + while (!feof(fd)) + { + int len; + int pos; + char *colour; + + fgets(line, LINE_LEN, fd); + len = strlen(line); + + if (len <= 1 || line[len-1] != '\n') + continue; + + line[len-1] = '\0'; + + i = sscanf(line, "%d %d %d %n", &r, &g, &b, &pos); + if (i != 3) + continue; + + colour = line + pos; + + if (STRICMP(colour, name) == 0) + { + fclose(fd); + /* + * Now remember this colour in the table. + * A LRU scheme might be better but this is simpler. + * Or could use a growing array. + */ + guicolor_T gcolour = RGB(r,g,b); + + vim_free(table[newColour].name); + table[newColour].name = (char *)vim_strsave((char_u *)colour); + table[newColour].colour = gcolour; + + newColour++; + if (newColour >= NCOLOURS) + newColour = NSTATIC_COLOURS; + if (endColour < NCOLOURS) + endColour = newColour; + + return gcolour; + } + } + + fclose(fd); + } + + return INVALCOLOR; +} + +/* + * Set the current text foreground color. + */ + void +gui_mch_set_fg_color( + guicolor_T color) +{ + rgb_color rgb = GUI_TO_RGB(color); + if (gui.vimWindow->Lock()) { + gui.vimTextArea->SetHighColor(rgb); + gui.vimWindow->Unlock(); + } +} + +/* + * Set the current text background color. + */ + void +gui_mch_set_bg_color( + guicolor_T color) +{ + rgb_color rgb = GUI_TO_RGB(color); + if (gui.vimWindow->Lock()) { + gui.vimTextArea->SetLowColor(rgb); + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_draw_string( + int row, + int col, + char_u *s, + int len, + int flags) +{ + if (gui.vimWindow->Lock()) { + gui.vimTextArea->mchDrawString(row, col, s, len, flags); + gui.vimWindow->Unlock(); + } +} + +/* + * Return OK if the key with the termcap name "name" is supported. + */ + int +gui_mch_haskey( + char_u *name) +{ + int i; + + for (i = 0; special_keys[i].BeKeys != 0; i++) + if (name[0] == special_keys[i].vim_code0 && + name[1] == special_keys[i].vim_code1) + return OK; + return FAIL; +} + + void +gui_mch_beep() +{ + ::beep(); +} + + void +gui_mch_flash(int msec) +{ + /* Do a visual beep by reversing the foreground and background colors */ + + if (gui.vimWindow->Lock()) { + BRect rect = gui.vimTextArea->Bounds(); + + gui.vimTextArea->SetDrawingMode(B_OP_INVERT); + gui.vimTextArea->FillRect(rect); + gui.vimTextArea->Sync(); + snooze(msec * 1000); /* wait for a few msec */ + gui.vimTextArea->FillRect(rect); + gui.vimTextArea->SetDrawingMode(B_OP_COPY); + gui.vimTextArea->Flush(); + gui.vimWindow->Unlock(); + } +} + +/* + * Invert a rectangle from row r, column c, for nr rows and nc columns. + */ + void +gui_mch_invert_rectangle( + int r, + int c, + int nr, + int nc) +{ + BRect rect; + rect.left = FILL_X(c); + rect.top = FILL_Y(r); + rect.right = rect.left + nc * gui.char_width - PEN_WIDTH; + rect.bottom = rect.top + nr * gui.char_height - PEN_WIDTH; + + if (gui.vimWindow->Lock()) { + gui.vimTextArea->SetDrawingMode(B_OP_INVERT); + gui.vimTextArea->FillRect(rect); + gui.vimTextArea->SetDrawingMode(B_OP_COPY); + gui.vimWindow->Unlock(); + } +} + +/* + * Iconify the GUI window. + */ + void +gui_mch_iconify() +{ + if (gui.vimWindow->Lock()) { + gui.vimWindow->Minimize(true); + gui.vimWindow->Unlock(); + } +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Bring the Vim window to the foreground. + */ + void +gui_mch_set_foreground() +{ + /* TODO */ +} +#endif + +/* + * Set the window title + */ + void +gui_mch_settitle( + char_u *title, + char_u *icon) +{ + if (gui.vimWindow->Lock()) { + gui.vimWindow->SetTitle((char *)title); + gui.vimWindow->Unlock(); + } +} + +/* + * Draw a cursor without focus. + */ + void +gui_mch_draw_hollow_cursor(guicolor_T color) +{ + gui_mch_set_fg_color(color); + + BRect r; + r.left = FILL_X(gui.col); + r.top = FILL_Y(gui.row); + r.right = r.left + gui.char_width - PEN_WIDTH; + r.bottom = r.top + gui.char_height - PEN_WIDTH; + + if (gui.vimWindow->Lock()) { + gui.vimTextArea->StrokeRect(r); + gui.vimWindow->Unlock(); + //gui_mch_flush(); + } +} + +/* + * Draw part of a cursor, only w pixels wide, and h pixels high. + */ + void +gui_mch_draw_part_cursor( + int w, + int h, + guicolor_T color) +{ + gui_mch_set_fg_color(color); + + BRect r; + r.left = +#ifdef FEAT_RIGHTLEFT + /* vertical line should be on the right of current point */ + 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_beos_process_event(0) >= B_OK) + /* nothing */ ; +} + +/* + * GUI input routine called by gui_wait_for_chars(). Waits for a character + * from the keyboard. + * wtime == -1 Wait forever. + * wtime == 0 This should never happen. + * wtime > 0 Wait wtime milliseconds for a character. + * Returns OK if a character was found to be available within the given time, + * or FAIL otherwise. + */ + int +gui_mch_wait_for_chars( + int wtime) +{ + int focus; + bigtime_t until, timeout; + status_t st; + + if (wtime >= 0) { + timeout = wtime * 1000; + until = system_time() + timeout; + } else { + timeout = B_INFINITE_TIMEOUT; + } + + focus = gui.in_focus; + for (;;) + { + /* Stop or start blinking when focus changes */ + if (gui.in_focus != focus) + { + if (gui.in_focus) + gui_mch_start_blink(); + else + gui_mch_stop_blink(); + focus = gui.in_focus; + } + + gui_mch_flush(); + /* + * Don't use gui_mch_update() because then we will spin-lock until a + * char arrives, instead we use gui_beos_process_event() to hang until + * an event arrives. No need to check for input_buf_full because we + * are returning as soon as it contains a single char. + */ + st = gui_beos_process_event(timeout); + + if (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; + + if (!menu_is_menubar(menu->name) + || (parent != NULL && parent->submenu_id == NULL)) + return; + + if (gui.vimWindow->Lock()) + { +/* Major re-write of the menu code, it was failing with memory corruption when + * we started loading multiple files (the Buffer menu) + * + * Note we don't use the preference values yet, all are inserted into the + * menubar on a first come-first served basis... + * + * richard@whitequeen.com jul 99 + */ + + BMenu *tmp; + + if ( parent ) + tmp = parent->submenu_id; + else + tmp = gui.vimForm->MenuBar(); +// make sure we don't try and add the same menu twice. The Buffers menu tries to +// do this and Be starts to crash... + + if ( ! tmp->FindItem((const char *) menu->dname)) { + + BMenu *bmenu = new BMenu((char *)menu->dname); + + menu->submenu_id = bmenu; + +// when we add a BMenu to another Menu, it creates the interconnecting BMenuItem + tmp->AddItem(bmenu); + +// Now its safe to query the menu for the associated MenuItem.... + menu->id = tmp->FindItem((const char *) menu->dname); + + } + gui.vimWindow->Unlock(); + } +} + + void +gui_mch_toggle_tearoffs(int enable) +{ + /* no tearoff menus */ +} + + static BMessage * +MenuMessage(vimmenu_T *menu) +{ + BMessage *m = new BMessage('menu'); + m->AddPointer("VimMenu", (void *)menu); + + return m; +} + +/* + * Add a menu item to a menu + */ + void +gui_mch_add_menu_item( + vimmenu_T *menu, + int idx) +{ + int mnemonic = 0; + vimmenu_T *parent = menu->parent; + + if (parent->submenu_id == NULL) + return; + +#ifdef never + /* why not add separators ? + * richard + */ + /* Don't add menu separator */ + if (menu_is_separator(menu->name)) + return; +#endif + + /* TODO: use menu->actext */ + /* This is difficult, since on Be, an accelerator must be a single char + * and a lot of Vim ones are the standard VI commands. + * + * Punt for Now... + * richard@whiequeen.com jul 99 + */ + if (gui.vimWindow->Lock()) + { + if ( menu_is_separator(menu->name)) { + BSeparatorItem *item = new BSeparatorItem(); + parent->submenu_id->AddItem(item); + menu->id = item; + menu->submenu_id = NULL; + } + else { + BMenuItem *item = new BMenuItem((char *)menu->dname, + MenuMessage(menu)); + item->SetTarget(gui.vimTextArea); + item->SetTrigger((char) menu->mnemonic); + parent->submenu_id->AddItem(item); + menu->id = item; + menu->submenu_id = NULL; + } + gui.vimWindow->Unlock(); + } +} + +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu( + vimmenu_T *menu) +{ + if (gui.vimWindow->Lock()) + { + assert(menu->submenu_id == NULL || menu->submenu_id->CountItems() == 0); + /* + * Detach this menu from its parent, so that it is not deleted + * twice once we get to delete that parent. + * Deleting a BMenuItem also deletes the associated BMenu, if any + * (which does not have any items anymore since they were + * removed and deleted before). + */ + BMenu *bmenu = menu->id->Menu(); + if (bmenu) + { + bmenu->RemoveItem(menu->id); + /* + * If we removed the last item from the menu bar, + * resize it out of sight. + */ + if (bmenu == gui.vimForm->MenuBar() && bmenu->CountItems() == 0) + { + bmenu->ResizeTo(-MENUBAR_MARGIN, -MENUBAR_MARGIN); + } + } + delete menu->id; + menu->id = NULL; + menu->submenu_id = NULL; + + gui.menu_height = (int) gui.vimForm->MenuHeight(); + gui.vimWindow->Unlock(); + } +} + +/* + * Make a menu either grey or not grey. + */ + void +gui_mch_menu_grey( + vimmenu_T *menu, + int grey) +{ + if (menu->id != NULL) + menu->id->SetEnabled(!grey); +} + +/* + * Make menu item hidden or not hidden + */ + void +gui_mch_menu_hidden( + vimmenu_T *menu, + int hidden) +{ + if (menu->id != NULL) + menu->id->SetEnabled(!hidden); +} + +/* + * This is called after setting all the menus to grey/hidden or not. + */ + void +gui_mch_draw_menubar() +{ + /* Nothing to do in BeOS */ +} + +#endif /* FEAT_MENU */ + +/* Mouse stuff */ + +#ifdef FEAT_CLIPBOARD +/* + * Clipboard stuff, for cutting and pasting text to other windows. + */ +char textplain[] = "text/plain"; +char vimselectiontype[] = "application/x-vnd.Rhialto-Vim-selectiontype"; + +/* + * Get the current selection and put it in the clipboard register. + */ + void +clip_mch_request_selection(VimClipboard *cbd) +{ + if (be_clipboard->Lock()) + { + BMessage *m = be_clipboard->Data(); + //m->PrintToStream(); + + char_u *string = NULL; + ssize_t stringlen = -1; + + if (m->FindData(textplain, B_MIME_TYPE, + (const void **)&string, &stringlen) == B_OK + || m->FindString("text", (const char **)&string) == B_OK) + { + if (stringlen == -1) + stringlen = STRLEN(string); + + int type; + char *seltype; + ssize_t seltypelen; + + /* + * Try to get the special vim selection type first + */ + if (m->FindData(vimselectiontype, B_MIME_TYPE, + (const void **)&seltype, &seltypelen) == B_OK) + { + switch (*seltype) + { + default: + case 'L': type = MLINE; break; + case 'C': type = MCHAR; break; +#ifdef FEAT_VISUAL + case 'B': type = MBLOCK; break; +#endif + } + } + else + { + /* Otherwise use heuristic as documented */ + type = memchr(string, stringlen, '\n') ? MLINE : MCHAR; + } + clip_yank_selection(type, string, (long)stringlen, cbd); + } + be_clipboard->Unlock(); + } +} +/* + * Make vim the owner of the current selection. + */ + void +clip_mch_lose_selection(VimClipboard *cbd) +{ + /* Nothing needs to be done here */ +} + +/* + * Make vim the owner of the current selection. Return OK upon success. + */ + int +clip_mch_own_selection(VimClipboard *cbd) +{ + /* + * Never actually own the clipboard. If another application sets the + * clipboard, we don't want to think that we still own it. + */ + return FAIL; +} + +/* + * Send the current selection to the clipboard. + */ + void +clip_mch_set_selection(VimClipboard *cbd) +{ + if (be_clipboard->Lock()) + { + be_clipboard->Clear(); + BMessage *m = be_clipboard->Data(); + assert(m); + + /* If the '*' register isn't already filled in, fill it in now */ + cbd->owned = TRUE; + clip_get_selection(cbd); + cbd->owned = FALSE; + + char_u *str = NULL; + long_u count; + int type; + + type = clip_convert_selection(&str, &count, cbd); + + if (type < 0) + return; + + m->AddData(textplain, B_MIME_TYPE, (void *)str, count); + + /* Add type of selection */ + char vtype; + switch (type) + { + default: + case MLINE: vtype = 'L'; break; + case MCHAR: vtype = 'C'; break; +#ifdef FEAT_VISUAL + case MBLOCK: vtype = 'B'; break; +#endif + } + m->AddData(vimselectiontype, B_MIME_TYPE, (void *)&vtype, 1); + + vim_free(str); + + be_clipboard->Commit(); + be_clipboard->Unlock(); + } +} + +#endif /* FEAT_CLIPBOARD */ + +/* + * Return the RGB value of a pixel as long. + */ + long_u +gui_mch_get_rgb(guicolor_T pixel) +{ + rgb_color rgb = GUI_TO_RGB(pixel); + + return ((rgb.red & 0xff) << 16) + ((rgb.green & 0xff) << 8) + + (rgb.blue & 0xff); +} + + void +gui_mch_setmouse(int x, int y) +{ + TRACE(); + /* TODO */ +} + + void +gui_mch_show_popupmenu(vimmenu_T *menu) +{ + TRACE(); + /* TODO */ +} + +int +gui_mch_get_mouse_x() +{ + TRACE(); + return 0; +} + + +int +gui_mch_get_mouse_y() +{ + TRACE(); + return 0; +} + +} /* extern "C" */ |