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 0000000000..e2c659848b --- /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); |