/* $OpenBSD: gui_x11.c,v 1.1 1996/09/07 21:40:28 downsj Exp $ */ /* vi:set ts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * GUI/Motif support by Robert Webb * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. */ #include #include #include #include #include #include "vim.h" #include "globals.h" #include "proto.h" #include "option.h" #include "ops.h" #define VIM_NAME "vim" #define VIM_CLASS "Vim" /* Default resource values */ #define DFLT_FONT "7x13" #define DFLT_MENU_BG_COLOR "gray77" #define DFLT_MENU_FG_COLOR "black" #define DFLT_SCROLL_BG_COLOR "gray60" #define DFLT_SCROLL_FG_COLOR "gray77" Widget vimShell = (Widget)NULL; static XtAppContext app_context; static Atom Atom_WM_DELETE_WINDOW; static Pixel gui_x11_get_color __ARGS((char_u *)); static void gui_x11_set_color __ARGS((GC, Pixel)); static void gui_x11_set_font __ARGS((GC, Font)); static void gui_x11_check_copy_area __ARGS((void)); static void gui_x11_update_menus_recurse __ARGS((GuiMenu *, int)); static void gui_x11_request_selection_cb __ARGS((Widget, XtPointer, Atom *, Atom *, XtPointer, long_u *, int *)); static Boolean gui_x11_convert_selection_cb __ARGS((Widget, Atom *, Atom *, Atom *, XtPointer *, long_u *, int *)); static void gui_x11_lose_ownership_cb __ARGS((Widget, Atom *)); static void gui_x11_wm_protocol_handler __ARGS((Widget, XtPointer, XEvent *, Boolean *)); static void gui_x11_invert_area __ARGS((int, int, int, int)); static void gui_x11_yank_selection __ARGS((int, int, int, int)); static void gui_x11_get_word_boundaries __ARGS((GuiSelection *, int, int)); static int gui_x11_get_line_end __ARGS((int)); static void gui_x11_update_selection __ARGS((GuiSelection *, int, int, int, int)); #define char_class(c) (c <= ' ' ? ' ' : iswordchar(c)) #define invert_rectangle(r, c, nr, nc) \ XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, \ FILL_X(c), FILL_Y(r), (nc) * gui.char_width, \ (nr) * gui.char_height) static struct { KeySym key_sym; char_u vim_code0; char_u vim_code1; } special_keys[] = { {XK_Up, 'k', 'u'}, {XK_Down, 'k', 'd'}, {XK_Left, 'k', 'l'}, {XK_Right, 'k', 'r'}, {XK_F1, 'k', '1'}, {XK_F2, 'k', '2'}, {XK_F3, 'k', '3'}, {XK_F4, 'k', '4'}, {XK_F5, 'k', '5'}, {XK_F6, 'k', '6'}, {XK_F7, 'k', '7'}, {XK_F8, 'k', '8'}, {XK_F9, 'k', '9'}, {XK_F10, 'k', ';'}, {XK_F11, 'F', '1'}, {XK_F12, 'F', '2'}, {XK_F13, 'F', '3'}, {XK_F14, 'F', '4'}, {XK_F15, 'F', '5'}, {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_Undo, '&', '8'}, {XK_BackSpace, 'k', 'b'}, {XK_Insert, 'k', 'I'}, {XK_Delete, 'k', 'D'}, {XK_Home, 'k', 'h'}, {XK_End, '@', '7'}, {XK_Prior, 'k', 'P'}, {XK_Next, 'k', 'N'}, {XK_Print, '%', '9'}, /* Keypad keys: */ #ifdef XK_KP_Left {XK_KP_Left, 'k', 'l'}, {XK_KP_Right, 'k', 'r'}, {XK_KP_Up, 'k', 'u'}, {XK_KP_Down, 'k', 'd'}, {XK_KP_Insert, 'k', 'I'}, {XK_KP_Delete, 'k', 'D'}, {XK_KP_Home, 'k', 'h'}, {XK_KP_End, '@', '7'}, {XK_KP_Prior, 'k', 'P'}, {XK_KP_Next, 'k', 'N'}, #endif /* End of list marker: */ {(KeySym)0, 0, 0} }; #define XtNboldColor "boldColor" #define XtCBoldColor "BoldColor" #define XtNitalicColor "italicColor" #define XtCItalicColor "ItalicColor" #define XtNunderlineColor "underlineColor" #define XtCUnderlineColor "UnderlineColor" #define XtNcursorColor "cursorColor" #define XtCCursorColor "CursorColor" #define XtNboldFont "boldFont" #define XtCBoldFont "BoldFont" #define XtNitalicFont "italicFont" #define XtCItalicFont "ItalicFont" #define XtNboldItalicFont "boldItalicFont" #define XtCBoldItalicFont "BoldItalicFont" #define XtNscrollbarWidth "scrollbarWidth" #define XtCScrollbarWidth "ScrollbarWidth" #define XtNmenuHeight "menuHeight" #define XtCMenuHeight "MenuHeight" /* Resources for setting the foreground and background colors of menus */ #define XtNmenuBackground "menuBackground" #define XtCMenuBackground "MenuBackground" #define XtNmenuForeground "menuForeground" #define XtCMenuForeground "MenuForeground" /* Resources for setting the foreground and background colors of scrollbars */ #define XtNscrollBackground "scrollBackground" #define XtCScrollBackground "ScrollBackground" #define XtNscrollForeground "scrollForeground" #define XtCScrollForeground "ScrollForeground" /* * X Resources: */ static XtResource vim_resources[] = { { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, norm_pixel), XtRString, XtDefaultForeground }, { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, back_pixel), XtRString, XtDefaultBackground }, { XtNboldColor, XtCBoldColor, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, bold_pixel), XtRString, XtDefaultForeground }, { XtNitalicColor, XtCItalicColor, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, ital_pixel), XtRString, XtDefaultForeground }, { XtNunderlineColor, XtCUnderlineColor, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, underline_pixel), XtRString, XtDefaultForeground }, { XtNcursorColor, XtCCursorColor, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, cursor_pixel), XtRString, XtDefaultForeground }, { XtNfont, XtCFont, XtRString, sizeof(String *), XtOffsetOf(Gui, dflt_font), XtRImmediate, XtDefaultFont }, { XtNboldFont, XtCBoldFont, XtRString, sizeof(String *), XtOffsetOf(Gui, dflt_bold_fn), XtRImmediate, "" }, { XtNitalicFont, XtCItalicFont, XtRString, sizeof(String *), XtOffsetOf(Gui, dflt_ital_fn), XtRImmediate, "" }, { XtNboldItalicFont, XtCBoldItalicFont, XtRString, sizeof(String *), XtOffsetOf(Gui, dflt_boldital_fn), XtRImmediate, "" }, { XtNgeometry, XtCGeometry, XtRString, sizeof(String *), XtOffsetOf(Gui, geom), XtRImmediate, "" }, { XtNreverseVideo, XtCReverseVideo, XtRBool, sizeof(Bool), XtOffsetOf(Gui, rev_video), XtRImmediate, (XtPointer) False }, { XtNborderWidth, XtCBorderWidth, XtRInt, sizeof(int), XtOffsetOf(Gui, border_width), XtRImmediate, (XtPointer) 2 }, { XtNscrollbarWidth, XtCScrollbarWidth, XtRInt, sizeof(int), XtOffsetOf(Gui, scrollbar_width), XtRImmediate, (XtPointer) SB_DEFAULT_WIDTH }, { XtNmenuHeight, XtCMenuHeight, XtRInt, sizeof(int), XtOffsetOf(Gui, menu_height), XtRImmediate, (XtPointer) MENU_DEFAULT_HEIGHT }, { XtNmenuForeground, XtCMenuForeground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, menu_fg_pixel), XtRString, DFLT_MENU_FG_COLOR }, { XtNmenuBackground, XtCMenuBackground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, menu_bg_pixel), XtRString, DFLT_MENU_BG_COLOR }, { XtNscrollForeground, XtCScrollForeground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, scroll_fg_pixel), XtRString, DFLT_SCROLL_FG_COLOR }, { XtNscrollBackground, XtCScrollBackground, XtRPixel, sizeof(Pixel), XtOffsetOf(Gui, scroll_bg_pixel), XtRString, DFLT_SCROLL_BG_COLOR }, }; /* * This table holds all the X GUI command line options allowed. This includes * the standard ones so that we can skip them when vim is started without the * GUI (but the GUI might start up later). * When changing this, also update doc/vim_gui.txt and the usage message!!! */ static XrmOptionDescRec cmdline_options[] = { /* We handle these options ourselves */ {"-bg", ".background", XrmoptionSepArg, NULL}, {"-background", ".background", XrmoptionSepArg, NULL}, {"-fg", ".foreground", XrmoptionSepArg, NULL}, {"-foreground", ".foreground", XrmoptionSepArg, NULL}, {"-bold", ".boldColor", XrmoptionSepArg, NULL}, {"-italic", ".italicColor", XrmoptionSepArg, NULL}, {"-ul", ".underlineColor", XrmoptionSepArg, NULL}, {"-underline", ".underlineColor", XrmoptionSepArg, NULL}, {"-cursor", ".cursorColor", XrmoptionSepArg, NULL}, {"-fn", ".font", XrmoptionSepArg, NULL}, {"-font", ".font", XrmoptionSepArg, NULL}, {"-boldfont", ".boldFont", XrmoptionSepArg, NULL}, {"-italicfont", ".italicFont", XrmoptionSepArg, NULL}, {"-geom", ".geometry", XrmoptionSepArg, NULL}, {"-geometry", ".geometry", XrmoptionSepArg, NULL}, {"-reverse", "*reverseVideo", XrmoptionNoArg, "True"}, {"-rv", "*reverseVideo", XrmoptionNoArg, "True"}, {"+reverse", "*reverseVideo", XrmoptionNoArg, "False"}, {"+rv", "*reverseVideo", XrmoptionNoArg, "False"}, {"-display", ".display", XrmoptionSepArg, NULL}, {"-iconic", "*iconic", XrmoptionNoArg, "True"}, {"-name", ".name", XrmoptionSepArg, NULL}, {"-bw", ".borderWidth", XrmoptionSepArg, NULL}, {"-borderwidth", ".borderWidth", XrmoptionSepArg, NULL}, {"-sw", ".scrollbarWidth", XrmoptionSepArg, NULL}, {"-scrollbarwidth", ".scrollbarWidth", XrmoptionSepArg, NULL}, {"-mh", ".menuHeight", XrmoptionSepArg, NULL}, {"-menuheight", ".menuHeight", XrmoptionSepArg, NULL}, {"-xrm", NULL, XrmoptionResArg, NULL} }; static int gui_argc = 0; static char **gui_argv = NULL; /* * Call-back routines. */ void gui_x11_timer_cb(timed_out, interval_id) XtPointer timed_out; XtIntervalId *interval_id; { *((int *)timed_out) = TRUE; } void gui_x11_visibility_cb(w, dud, event, bool) Widget w; XtPointer dud; XEvent *event; Boolean *bool; { if (event->type != VisibilityNotify) return; gui.visibility = event->xvisibility.state; /* * When we do an XCopyArea(), and the window is partially obscured, we want * to receive an event to tell us whether it worked or not. */ XSetGraphicsExposures(gui.dpy, gui.text_gc, gui.visibility != VisibilityUnobscured); } void gui_x11_expose_cb(w, dud, event, bool) Widget w; XtPointer dud; XEvent *event; Boolean *bool; { XExposeEvent *gevent; if (event->type != Expose) return; gevent = (XExposeEvent *)event; gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height); /* Clear the border areas if needed */ if (gevent->x < FILL_X(0)) XClearArea(gui.dpy, gui.wid, 0, 0, FILL_X(0), 0, False); if (gevent->y < FILL_Y(0)) XClearArea(gui.dpy, gui.wid, 0, 0, 0, FILL_Y(0), False); if (gevent->x > FILL_X(Columns)) XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False); if (gevent->y > FILL_Y(Rows)) XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False); if (gui.selection.state != SELECT_CLEARED) gui_x11_redraw_selection(gevent->x, gevent->y, gevent->width, gevent->height); } void gui_x11_resize_window_cb(w, dud, event, bool) Widget w; XtPointer dud; XEvent *event; Boolean *bool; { if (event->type != ConfigureNotify) return; gui_resize_window(event->xconfigure.width, event->xconfigure.height); /* Make sure the border strips on the right and bottom get cleared. */ XClearArea(gui.dpy, gui.wid, FILL_X(Columns), 0, 0, 0, False); XClearArea(gui.dpy, gui.wid, 0, FILL_Y(Rows), 0, 0, False); } void gui_x11_focus_change_cb(w, data, event, bool) Widget w; XtPointer data; XEvent *event; Boolean *bool; { if (event->type == FocusIn) gui.in_focus = TRUE; else gui.in_focus = FALSE; if (gui.row == gui.cursor_row && gui.col == gui.cursor_col) INVALIDATE_CURSOR(); gui_update_cursor(); } void gui_x11_key_hit_cb(w, dud, event, bool) Widget w; XtPointer dud; XEvent *event; Boolean *bool; { XKeyPressedEvent *ev_press; char_u string[3], string2[3]; KeySym key_sym; int num, i; ev_press = (XKeyPressedEvent *)event; num = XLookupString(ev_press, (char *)string, sizeof(string), &key_sym, NULL); if (key_sym == XK_space) string[0] = ' '; /* Otherwise Ctrl-Space doesn't work */ /* Check for Alt/Meta key (Mod1Mask) */ if (num == 1 && (ev_press->state & Mod1Mask)) { /* * Before we set the 8th bit, check to make sure the user doesn't * already have a mapping defined for this sequence. We determine this * by checking to see if the input would be the same without the * Alt/Meta key. */ ev_press->state &= ~Mod1Mask; if (XLookupString(ev_press, (char *)string2, sizeof(string2), &key_sym, NULL) == 1 && string[0] == string2[0]) string[0] |= 0x80; ev_press->state |= Mod1Mask; } #if 0 if (num == 1 && string[0] == CSI) /* this doesn't work yet */ { string[1] = CSI; string[2] = CSI; num = 3; } #endif /* Check for special keys, making sure BS and DEL are recognised. */ if (num == 0 || key_sym == XK_BackSpace || key_sym == XK_Delete) { for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) { if (special_keys[i].key_sym == key_sym) { string[0] = CSI; string[1] = special_keys[i].vim_code0; string[2] = special_keys[i].vim_code1; num = 3; } } } /* Unrecognised key */ if (num == 0) return; /* Special keys (and a few others) may have modifiers */ if (num == 3 || key_sym == XK_space || key_sym == XK_Tab || key_sym == XK_Return || key_sym == XK_Linefeed || key_sym == XK_Escape) { string2[0] = CSI; string2[1] = KS_MODIFIER; string2[2] = 0; if (ev_press->state & ShiftMask) string2[2] |= MOD_MASK_SHIFT; if (ev_press->state & ControlMask) string2[2] |= MOD_MASK_CTRL; if (ev_press->state & Mod1Mask) string2[2] |= MOD_MASK_ALT; if (string2[2] != 0) add_to_input_buf(string2, 3); } if (num == 1 && string[0] == Ctrl('C')) { trash_input_buf(); got_int = TRUE; } add_to_input_buf(string, num); } void gui_x11_mouse_cb(w, dud, event, bool) Widget w; XtPointer dud; XEvent *event; Boolean *bool; { static XtIntervalId timer = (XtIntervalId)0; static int timed_out = TRUE; int button; int repeated_click = FALSE; int x, y; int_u x_modifiers; int_u vim_modifiers; int checkfor; if (event->type == MotionNotify) { x = event->xmotion.x; y = event->xmotion.y; button = MOUSE_DRAG; x_modifiers = event->xmotion.state; } else { x = event->xbutton.x; y = event->xbutton.y; if (event->type == ButtonPress) { /* Handle multiple clicks */ if (!timed_out) { XtRemoveTimeOut(timer); repeated_click = TRUE; } timed_out = FALSE; timer = XtAppAddTimeOut(app_context, (long_u)p_mouset, gui_x11_timer_cb, &timed_out); switch (event->xbutton.button) { case Button1: button = MOUSE_LEFT; break; case Button2: button = MOUSE_MIDDLE; break; case Button3: button = MOUSE_RIGHT; break; default: return; /* Unknown button */ } } else if (event->type == ButtonRelease) button = MOUSE_RELEASE; else return; /* Unknown mouse event type */ x_modifiers = event->xbutton.state; } vim_modifiers = 0x0; if (x_modifiers & ShiftMask) vim_modifiers |= MOUSE_SHIFT; if (x_modifiers & ControlMask) vim_modifiers |= MOUSE_CTRL; if (x_modifiers & Mod1Mask) /* Alt or Meta key */ vim_modifiers |= MOUSE_ALT; /* If an x11 selection is in progress, finish it */ if (gui.selection.state == SELECT_IN_PROGRESS) { gui_x11_process_selection(button, x, y, repeated_click, vim_modifiers); return; } /* Determine which mouse settings to look for based on the current mode */ switch (State) { case NORMAL_BUSY: case NORMAL: checkfor = MOUSE_NORMAL; break; case VISUAL: checkfor = MOUSE_VISUAL; break; case REPLACE: case INSERT: checkfor = MOUSE_INSERT; break; case HITRETURN: checkfor = MOUSE_RETURN; break; /* * On the command line, use the X11 selection on all lines but the * command line. */ case CMDLINE: if (Y_2_ROW(y) < cmdline_row) checkfor = ' '; else checkfor = MOUSE_COMMAND; break; default: checkfor = ' '; break; }; /* * Allow selection of text in the command line in "normal" modes. */ if ((State == NORMAL || State == NORMAL_BUSY || State == INSERT || State == REPLACE) && Y_2_ROW(y) >= gui.num_rows - p_ch) checkfor = ' '; /* * If the mouse settings say to not use the mouse, use the X11 selection. * But if Visual is active, assume that only the Visual area will be * selected. */ if (!mouse_has(checkfor) && !VIsual_active) { /* If the selection is done, allow the right button to extend it */ if (gui.selection.state == SELECT_DONE && button == MOUSE_RIGHT) { gui_x11_process_selection(button, x, y, repeated_click, vim_modifiers); return; } /* Allow the left button to start the selection */ else if (button == MOUSE_LEFT) { gui_x11_start_selection(button, x, y, repeated_click, vim_modifiers); return; } } if (gui.selection.state != SELECT_CLEARED) gui_mch_clear_selection(); gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers); } /* * End of call-back routines */ /* * 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(argc, argv) int *argc; char **argv; { int arg; int i; /* * Move all the entries in argv which are relevant to X into gui_argv. */ gui_argc = 0; gui_argv = (char **)lalloc(*argc * sizeof(char *), FALSE); if (gui_argv == NULL) return; gui_argv[gui_argc++] = argv[0]; arg = 1; while (arg < *argc) { /* Look for argv[arg] in cmdline_options[] table */ for (i = 0; i < XtNumber(cmdline_options); i++) if (strcmp(argv[arg], cmdline_options[i].option) == 0) break; if (i < XtNumber(cmdline_options)) { /* Found match in table, so move it into gui_argv */ gui_argv[gui_argc++] = argv[arg]; if (--*argc > arg) { vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg) * sizeof(char *)); if (cmdline_options[i].argKind != XrmoptionNoArg) { /* Move the options argument as well */ gui_argv[gui_argc++] = argv[arg]; if (--*argc > arg) vim_memmove(&argv[arg], &argv[arg + 1], (*argc - arg) * sizeof(char *)); } } } else arg++; } } /* * Initialise the X GUI. Create all the windows, set up all the call-backs * etc. */ int gui_mch_init() { Widget AppShell; long_u gc_mask; XGCValues gc_vals; Pixel tmp_pixel; int x, y, mask; unsigned w, h; XtToolkitInitialize(); app_context = XtCreateApplicationContext(); gui.dpy = XtOpenDisplay(app_context, 0, VIM_NAME, VIM_CLASS, cmdline_options, XtNumber(cmdline_options), #ifndef XtSpecificationRelease (Cardinal*)&gui_argc, gui_argv); #else #if XtSpecificationRelease == 4 (Cardinal*)&gui_argc, gui_argv); #else &gui_argc, gui_argv); #endif #endif vim_free(gui_argv); if (gui.dpy == NULL) { x = full_screen; full_screen = FALSE; /* use fprintf() */ EMSG("cannot open display"); full_screen = x; return FAIL; } /* Uncomment this to enable synchronous mode for debugging */ /* XSynchronize(gui.dpy, True); */ /* * So converters work. */ XtInitializeWidgetClass(applicationShellWidgetClass); XtInitializeWidgetClass(topLevelShellWidgetClass); /* * The applicationShell is created as an unrealized * parent for multiple topLevelShells. The topLevelShells * are created as popup children of the applicationShell. * This is a recommendation of Paul Asente & Ralph Swick in * _X_Window_System_Toolkit_ p. 677. */ AppShell = XtVaAppCreateShell(VIM_NAME, VIM_CLASS, applicationShellWidgetClass, gui.dpy, NULL); /* * Get the application resources */ XtVaGetApplicationResources(AppShell, &gui, vim_resources, XtNumber(vim_resources), NULL); /* * Check validity of resources */ if (gui.border_width < 0) gui.border_width = 0; /* For reverse video, swap foreground and background colours */ if (gui.rev_video) { tmp_pixel = gui.norm_pixel; gui.norm_pixel = gui.back_pixel; gui.back_pixel = tmp_pixel; } /* Create shell widget to put vim in */ vimShell = XtVaCreatePopupShell("VIM", topLevelShellWidgetClass, AppShell, XtNborderWidth, 0, NULL); /* * Check that none of the colours are the same as the background color */ if (gui.norm_pixel == gui.back_pixel) { gui.norm_pixel = gui_x11_get_color((char_u *)"White"); if (gui.norm_pixel == gui.back_pixel) gui.norm_pixel = gui_x11_get_color((char_u *)"Black"); } if (gui.bold_pixel == gui.back_pixel) gui.bold_pixel = gui.norm_pixel; if (gui.ital_pixel == gui.back_pixel) gui.ital_pixel = gui.norm_pixel; if (gui.underline_pixel == gui.back_pixel) gui.underline_pixel = gui.norm_pixel; if (gui.cursor_pixel == gui.back_pixel) gui.cursor_pixel = gui.norm_pixel; /* * Set up the GCs. The font attributes will be set in gui_init_font(). */ gc_mask = GCForeground | GCBackground; gc_vals.foreground = gui.norm_pixel; gc_vals.background = gui.back_pixel; gui.text_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, &gc_vals); gc_vals.foreground = gui.back_pixel; gc_vals.background = gui.norm_pixel; gui.back_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, &gc_vals); gc_mask |= GCFunction; gc_vals.foreground = gui.norm_pixel ^ gui.back_pixel; gc_vals.background = gui.norm_pixel ^ gui.back_pixel; gc_vals.function = GXxor; gui.invert_gc = XCreateGC(gui.dpy, DefaultRootWindow(gui.dpy), gc_mask, &gc_vals); gui.visibility = VisibilityUnobscured; gui.selection.atom = XInternAtom(gui.dpy, "VIM_SELECTION", False); /* * Set up the fonts. This call will also set the window to the correct * size for the used font. */ gui.norm_font = NULL; gui.bold_font = NULL; gui.ital_font = NULL; gui.boldital_font = NULL; if (gui_init_font() == FAIL) return FAIL; /* Now adapt the supplied(?) geometry-settings */ /* Added by Kjetil Jacobsen */ if (gui.geom != NULL && *gui.geom != NUL) { mask = XParseGeometry((char *)gui.geom, &x, &y, &w, &h); if (mask & WidthValue) Columns = w; if (mask & HeightValue) Rows = h; /* * Set the (x,y) position of the main window only if specified in the * users geometry, so we get good defaults when they don't. This needs * to be done before the shell is popped up. */ if (mask & (XValue|YValue)) XtVaSetValues(vimShell, XtNgeometry, gui.geom, NULL); } gui.num_cols = Columns; gui.num_rows = Rows; gui_reset_scroll_region(); gui_mch_create_widgets(); /* Configure the desired scrollbars */ gui_init_which_components(NULL); /* Actually open the window */ XtPopup(vimShell, XtGrabNone); gui_mch_set_winsize(); gui.wid = gui_mch_get_wid(); /* Add a callback for the Close item on the window managers menu */ Atom_WM_DELETE_WINDOW = XInternAtom(gui.dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(gui.dpy, XtWindow(vimShell), &Atom_WM_DELETE_WINDOW, 1); XtAddEventHandler(vimShell, NoEventMask, True, gui_x11_wm_protocol_handler, NULL); #ifdef ENABLE_EDITRES /* * Enable editres protocol (see editres(1)) * Usually will need to add -lXmu to the linker line as well. */ { extern void _XEditResCheckMessages(); XtAddEventHandler(vimShell, 0, True, _XEditResCheckMessages, (XtPointer)NULL); } #endif return OK; } void gui_mch_exit() { XtCloseDisplay(gui.dpy); } /* * While unmanaging and re-managing scrollbars etc, we don't want the resize * callback to be called, so this function is used to disable this callback, * and then re-enable it afterwards. XSync() makes sure we don't register the * callback before the server has finished processing any resize events we have * created, otherwise we can get in a loop where the window flashes between two * sizes, since resize_window_cb() calls set_winsize(). */ void gui_x11_use_resize_callback(widget, enabled) Widget widget; int enabled; { if (enabled) { XSync(gui.dpy, False); XtAddEventHandler(widget, StructureNotifyMask, FALSE, gui_x11_resize_window_cb, (XtPointer)0); XtAddEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb, (XtPointer)0); } else { XtRemoveEventHandler(widget, StructureNotifyMask, FALSE, gui_x11_resize_window_cb, (XtPointer)0); XtRemoveEventHandler(widget, ExposureMask, FALSE, gui_x11_expose_cb, (XtPointer)0); XSync(gui.dpy, 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(font_name) char_u *font_name; { XFontStruct *font = NULL; if (font_name == NULL) { /* * If none of the fonts in 'font' could be loaded, try the one set in * the X resource, and finally just try using DFLT_FONT, which will * hopefully always be there. */ font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_font); if (font == NULL) font_name = (char_u *)DFLT_FONT; font_name = (char_u *)DFLT_FONT; } if (font == NULL) font = XLoadQueryFont(gui.dpy, (char *)font_name); if (font == NULL) return FAIL; if (font->max_bounds.width != font->min_bounds.width) { sprintf((char *)IObuff, "Font \"%s\" is not fixed-width", (char *)font_name); emsg(IObuff); XFreeFont(gui.dpy, font); return FAIL; } if (gui.norm_font != NULL) XFreeFont(gui.dpy, gui.norm_font); gui.norm_font = font; gui.char_width = font->max_bounds.width; gui.char_height = font->ascent + font->descent; gui.char_ascent = font->ascent; #ifdef DEBUG printf("Font Information for '%s':\n", font_name); printf(" w = %d, h = %d, ascent = %d, descent = %d\n", gui.char_width, gui.char_height, gui.char_ascent, font->descent); printf(" max ascent = %d, max descent = %d, max h = %d\n", font->max_bounds.ascent, font->max_bounds.descent, font->max_bounds.ascent + font->max_bounds.descent); printf(" min lbearing = %d, min rbearing = %d\n", font->min_bounds.lbearing, font->min_bounds.rbearing); printf(" max lbearing = %d, max rbearing = %d\n", font->max_bounds.lbearing, font->max_bounds.rbearing); printf(" leftink = %d, rightink = %d\n", (font->min_bounds.lbearing < 0), (font->max_bounds.rbearing > gui.char_width)); printf("\n"); #endif /* * 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. */ if (gui.dflt_bold_fn != NULL && *gui.dflt_bold_fn != NUL) gui.bold_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_bold_fn); if (gui.dflt_ital_fn != NULL && *gui.dflt_ital_fn != NUL) gui.ital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_ital_fn); if (gui.dflt_boldital_fn != NULL && *gui.dflt_boldital_fn != NUL) gui.boldital_font = XLoadQueryFont(gui.dpy, (char *)gui.dflt_boldital_fn); /* Must set the appropriate fonts for all the GCs */ gui_x11_set_font(gui.text_gc, gui.norm_font->fid); gui_x11_set_font(gui.back_gc, gui.norm_font->fid); /* * Set the window sizes if the window is already visible. */ if (vimShell != (Widget)NULL && XtIsRealized(vimShell)) { WIN *wp; gui_mch_set_winsize(); /* Force the scrollbar heights to get updated when a font is changed */ if (gui.which_scrollbars[SB_LEFT]) { for (wp = firstwin; wp; wp = wp->w_next) wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_HEIGHT; gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_LEFT); } if (gui.which_scrollbars[SB_RIGHT]) { for (wp = firstwin; wp; wp = wp->w_next) wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_HEIGHT; gui_mch_update_scrollbars(SB_UPDATE_HEIGHT, SB_RIGHT); } } return OK; } /* * Return OK if the key with the termcap name "name" is supported. */ int gui_mch_haskey(name) char_u *name; { int i; for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) if (name[0] == special_keys[i].vim_code0 && name[1] == special_keys[i].vim_code1) return OK; return FAIL; } /* * Return the text window-id and display. Only required for X-based GUI's */ int gui_get_x11_windis(win, dis) Window *win; Display **dis; { *win = XtWindow(vimShell); *dis = gui.dpy; return OK; } void gui_mch_beep() { XBell(gui.dpy, 0); } void gui_mch_flash() { /* Do a visual beep by reversing the foreground and background colors */ XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0, FILL_X(Columns) + gui.border_offset, FILL_Y(Rows) + gui.border_offset); XSync(gui.dpy, False); mch_delay(20L, TRUE); /* wait 1/50 of a second */ XFillRectangle(gui.dpy, gui.wid, gui.invert_gc, 0, 0, FILL_X(Columns) + gui.border_offset, FILL_Y(Rows) + gui.border_offset); } /* * Iconify the GUI window. */ void gui_mch_iconify() { XIconifyWindow(gui.dpy, XtWindow(vimShell), DefaultScreen(gui.dpy)); } /* * Return the Pixel value (color) for the given color name. This routine was * pretty much taken from example code in the Silicon Graphics OSF/Motif * Programmer's Guide. */ static Pixel gui_x11_get_color(name) char_u *name; { XrmValue from, to; from.size = STRLEN(name) + 1; if (from.size < sizeof(String)) from.size = sizeof(String); from.addr = (char *)name; to.addr = NULL; XtConvert(vimShell, XtRString, &from, XtRPixel, &to); if (to.addr != NULL) return (Pixel) *((Pixel *)to.addr); else return (Pixel)NULL; } /* * Set the current text font (gc should be either gui.text_gc or gui.back_gc) */ static void gui_x11_set_font(gc, fid) GC gc; Font fid; { static Font prev_text_fid = (Font) -1; static Font prev_back_fid = (Font) -1; if (gc == gui.text_gc && fid != prev_text_fid) { XSetFont(gui.dpy, gc, fid); prev_text_fid = fid; } else if (gc == gui.back_gc && fid != prev_back_fid) { XSetFont(gui.dpy, gc, fid); prev_back_fid = fid; } } /* * Set the current text color (gc should be either gui.text_gc or gui.back_gc) */ static void gui_x11_set_color(gc, pixel) GC gc; Pixel pixel; { static Pixel prev_text_pixel = (Pixel) -1; static Pixel prev_back_pixel = (Pixel) -1; if (gc == gui.text_gc && pixel != prev_text_pixel) { XSetForeground(gui.dpy, gc, pixel); prev_text_pixel = pixel; } else if (gc == gui.back_gc && pixel != prev_back_pixel) { XSetBackground(gui.dpy, gc, pixel); prev_back_pixel = pixel; } } /* * Draw the cursor. */ void gui_mch_draw_cursor() { /* Only write to the screen after LinePointers[] has been initialized */ if (screen_cleared && NextScreen != NULL) { /* Clear the selection if we are about to write over it */ if (gui.selection.state == SELECT_DONE && gui.row >= gui.selection.start.lnum && gui.row <= gui.selection.end.lnum) gui_mch_clear_selection(); if (gui.row < screen_Rows && gui.col < screen_Columns) gui_mch_outstr_nowrap(LinePointers[gui.row] + gui.col, 1, FALSE, TRUE); if (!gui.in_focus) { gui_x11_set_color(gui.text_gc, gui.cursor_pixel); XDrawRectangle(gui.dpy, gui.wid, gui.text_gc, FILL_X(gui.col), FILL_Y(gui.row), gui.char_width - 1, gui.char_height - 1); } } } /* * Catch up with any queued X events. This may put keyboard input into the * input buffer, call resize call-backs, trigger timers etc. If there is * nothing in the X event queue (& no timers pending), then we return * immediately. */ void gui_mch_update() { while(XtAppPending(app_context) && !is_input_buf_full()) XtAppProcessEvent(app_context, XtIMAll); } /* * The main GUI input routine. Waits for a character from the keyboard. * wtime == -1 Wait forever. * wtime == 0 Don't wait. * 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(wtime) int wtime; { XtIntervalId timer = (XtIntervalId)0; XtIntervalId updatescript_timer = (XtIntervalId)0; /* * Make these static, in case gui_x11_timer_cb is called after leaving * this function (otherwise a random value on the stack may be changed). */ static int timed_out; static int do_updatescript; timed_out = FALSE; do_updatescript = FALSE; /* * If we're going to wait a bit, update the menus for the current State. */ if (wtime != 0) gui_x11_update_menus(0); gui_mch_update(); if (!is_input_buf_empty()) /* Got char, return immediately */ return OK; else if (wtime == 0) /* Don't wait for char */ return FAIL; else if (wtime > 0) { timer = XtAppAddTimeOut(app_context, (long_u)wtime, gui_x11_timer_cb, &timed_out); } else { /* We are waiting for ever, so update swap files after p_ut msec's */ updatescript_timer = XtAppAddTimeOut(app_context, (long_u)p_ut, gui_x11_timer_cb, &do_updatescript); } while (!timed_out) { /* * Don't use gui_mch_update() because then we will spin-lock until a * char arrives, instead we use XtAppProcessEvent() 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. Note that * XtAppNextEvent() may not be used because it will not return after a * timer event has arrived -- webb */ XtAppProcessEvent(app_context, XtIMAll); /* * If no characters arrive within 'updatetime' milli-seconds, flush all * the swap files to disk. */ if (do_updatescript) { updatescript(0); do_updatescript = FALSE; updatescript_timer = (XtIntervalId)0; } if (!is_input_buf_empty()) { if (timer != (XtIntervalId)0 && !timed_out) XtRemoveTimeOut(timer); if (updatescript_timer != (XtIntervalId)0 && !do_updatescript) XtRemoveTimeOut(updatescript_timer); return OK; } } return FAIL; } /* * Output routines. */ /* Flush any output to the screen */ void gui_mch_flush() { XFlush(gui.dpy); } /* * Clear a rectangular region of the screen from text pos (row1, col1) to * (row2, col2) inclusive. */ void gui_mch_clear_block(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { /* Clear the selection if we are about to write over it */ if (gui.selection.state == SELECT_DONE && row2 >= gui.selection.start.lnum && row1 <= gui.selection.end.lnum) gui_mch_clear_selection(); XFillRectangle(gui.dpy, gui.wid, gui.back_gc, FILL_X(col1), FILL_Y(row1), (col2 - col1 + 1) * gui.char_width, (row2 - row1 + 1) * gui.char_height); /* Invalidate cursor if it was in this block */ if (gui.cursor_row >= row1 && gui.cursor_row <= row2 && gui.cursor_col >= col1 && gui.cursor_col <= col2) INVALIDATE_CURSOR(); } /* * Output the given string at the current cursor position. If the string is * too long to fit on the line, then it is truncated. wrap_cursor may be * TRUE if the cursor position should be wrapped when the end of the line is * reached, however the string will still be truncated and not continue on the * next line. is_cursor should only be TRUE when this function is being called * to actually draw the cursor. */ void gui_mch_outstr_nowrap(s, len, wrap_cursor, is_cursor) char_u *s; int len; int wrap_cursor; int is_cursor; { GC text_gc; long_u highlight_mask; if (len == 0) return; if (len < 0) len = STRLEN(s); if (is_cursor && gui.in_focus) highlight_mask = gui.highlight_mask | HL_INVERSE; else highlight_mask = gui.highlight_mask; if (highlight_mask & (HL_INVERSE | HL_STANDOUT)) text_gc = gui.back_gc; else text_gc = gui.text_gc; /* Set the font */ if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font != NULL) if ((highlight_mask & HL_ITAL) && gui.boldital_font != NULL) gui_x11_set_font(text_gc, gui.boldital_font->fid); else gui_x11_set_font(text_gc, gui.bold_font->fid); else if ((highlight_mask & HL_ITAL) && gui.ital_font != NULL) gui_x11_set_font(text_gc, gui.ital_font->fid); else gui_x11_set_font(text_gc, gui.norm_font->fid); /* Set the color */ if (is_cursor && gui.in_focus) gui_x11_set_color(text_gc, gui.cursor_pixel); else if (highlight_mask & (HL_BOLD | HL_STANDOUT)) gui_x11_set_color(text_gc, gui.bold_pixel); else if (highlight_mask & HL_ITAL) gui_x11_set_color(text_gc, gui.ital_pixel); else if (highlight_mask & HL_UNDERLINE) gui_x11_set_color(text_gc, gui.underline_pixel); else gui_x11_set_color(text_gc, gui.norm_pixel); /* Clear the selection if we are about to write over it */ if (gui.selection.state == SELECT_DONE && gui.row >= gui.selection.start.lnum && gui.row <= gui.selection.end.lnum) gui_mch_clear_selection(); /* Draw the text */ XDrawImageString(gui.dpy, gui.wid, text_gc, TEXT_X(gui.col), TEXT_Y(gui.row), (char *)s, len); /* No bold font, so fake it */ if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font == NULL) XDrawString(gui.dpy, gui.wid, text_gc, TEXT_X(gui.col) + 1, TEXT_Y(gui.row), (char *)s, len); /* Underline the text */ if ((highlight_mask & HL_UNDERLINE) || ((highlight_mask & HL_ITAL) && gui.ital_font == NULL)) XDrawLine(gui.dpy, gui.wid, text_gc, FILL_X(gui.col), FILL_Y(gui.row + 1) - 1, FILL_X(gui.col + len) - 1, FILL_Y(gui.row + 1) - 1); if (!is_cursor) { /* Invalidate the old physical cursor position if we wrote over it */ if (gui.cursor_row == gui.row && gui.cursor_col >= gui.col && gui.cursor_col < gui.col + len) INVALIDATE_CURSOR(); /* Update the cursor position */ gui.col += len; if (wrap_cursor && gui.col >= Columns) { gui.col = 0; gui.row++; } } } /* * 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(row, num_lines) int row; int num_lines; { if (gui.visibility == VisibilityFullyObscured) return; /* Can't see the window */ if (num_lines <= 0) return; if (row + num_lines > gui.scroll_region_bot) { /* Scrolled out of region, just blank the lines out */ gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1); } else { XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc, FILL_X(0), FILL_Y(row + num_lines), gui.char_width * Columns, gui.char_height * (gui.scroll_region_bot - row - num_lines + 1), FILL_X(0), FILL_Y(row)); /* Update gui.cursor_row if the cursor scrolled or copied over */ if (gui.cursor_row >= row) { if (gui.cursor_row < row + num_lines) INVALIDATE_CURSOR(); else if (gui.cursor_row <= gui.scroll_region_bot) gui.cursor_row -= num_lines; } gui_mch_clear_block(gui.scroll_region_bot - num_lines + 1, 0, gui.scroll_region_bot, Columns - 1); gui_x11_check_copy_area(); } } /* * Insert the given number of lines before the given row, scrolling down any * following text within the scroll region. */ void gui_mch_insert_lines(row, num_lines) int row; int num_lines; { if (gui.visibility == VisibilityFullyObscured) return; /* Can't see the window */ if (num_lines <= 0) return; if (row + num_lines > gui.scroll_region_bot) { /* Scrolled out of region, just blank the lines out */ gui_mch_clear_block(row, 0, gui.scroll_region_bot, Columns - 1); } else { XCopyArea(gui.dpy, gui.wid, gui.wid, gui.text_gc, FILL_X(0), FILL_Y(row), gui.char_width * Columns, gui.char_height * (gui.scroll_region_bot - row - num_lines + 1), FILL_X(0), FILL_Y(row + num_lines)); /* Update gui.cursor_row if the cursor scrolled or copied over */ if (gui.cursor_row >= gui.row) { if (gui.cursor_row <= gui.scroll_region_bot - num_lines) gui.cursor_row += num_lines; else if (gui.cursor_row <= gui.scroll_region_bot) INVALIDATE_CURSOR(); } gui_mch_clear_block(row, 0, row + num_lines - 1, Columns - 1); gui_x11_check_copy_area(); } } /* * Scroll the text between gui.scroll_region_top & gui.scroll_region_bot by the * number of lines given. Positive scrolls down (text goes up) and negative * scrolls up (text goes down). */ static void gui_x11_check_copy_area() { XEvent event; XGraphicsExposeEvent *gevent; if (gui.visibility != VisibilityPartiallyObscured) return; XFlush(gui.dpy); /* Wait to check whether the scroll worked or not */ for (;;) { if (XCheckTypedEvent(gui.dpy, NoExpose, &event)) return; /* The scroll worked. */ if (XCheckTypedEvent(gui.dpy, GraphicsExpose, &event)) { gevent = (XGraphicsExposeEvent *)&event; gui_redraw(gevent->x, gevent->y, gevent->width, gevent->height); if (gevent->count == 0) return; /* This was the last expose event */ } XSync(gui.dpy, False); } } /* * X Selection stuff, for cutting and pasting text to other windows. */ static void gui_x11_request_selection_cb(w, success, selection, type, value, length, format) Widget w; XtPointer success; Atom *selection; Atom *type; XtPointer value; long_u *length; int *format; { int motion_type; long_u len; char_u *p; if (value == NULL || *length == 0) { gui_free_selection(); /* ??? */ *(int *)success = FALSE; return; } motion_type = MCHAR; p = (char_u *)value; len = *length; if (*type == gui.selection.atom) { motion_type = *p++; len--; } gui_yank_selection(motion_type, p, len); XtFree((char *)value); *(int *)success = TRUE; } void gui_request_selection() { XEvent event; Atom type = gui.selection.atom; int success; int i; for (i = 0; i < 2; i++) { XtGetSelectionValue(vimShell, XA_PRIMARY, type, gui_x11_request_selection_cb, (XtPointer)&success, CurrentTime); /* Do we need this?: */ XFlush(gui.dpy); /* * Wait for result of selection request, otherwise if we type more * characters, then they will appear before the one that requested the * paste! Don't worry, we will catch up with any other events later. */ for (;;) { if (XCheckTypedEvent(gui.dpy, SelectionNotify, &event)) break; /* Do we need this?: */ XSync(gui.dpy, False); } XtDispatchEvent(&event); if (success) return; type = XA_STRING; } } static Boolean gui_x11_convert_selection_cb(w, selection, target, type, value, length, format) Widget w; Atom *selection; Atom *target; Atom *type; XtPointer *value; long_u *length; int *format; { char_u *string; char_u *result; int motion_type; if (!gui.selection.owned) return False; /* Shouldn't ever happen */ if (*target == gui.selection.atom) { gui_get_selection(); motion_type = gui_convert_selection(&string, length); if (motion_type < 0) return False; (*length)++; *value = XtMalloc(*length); result = (char_u *)*value; if (result == NULL) return False; result[0] = motion_type; vim_memmove(result + 1, string, (size_t)(*length - 1)); *type = *target; *format = 8; /* 8 bits per char */ return True; } else if (*target == XA_STRING) { gui_get_selection(); motion_type = gui_convert_selection(&string, length); if (motion_type < 0) return False; *value = XtMalloc(*length); result = (char_u *)*value; if (result == NULL) return False; vim_memmove(result, string, (size_t)(*length)); *type = *target; *format = 8; /* 8 bits per char */ return True; } else return False; } static void gui_x11_lose_ownership_cb(w, selection) Widget w; Atom *selection; { gui_lose_selection(); } void gui_mch_lose_selection() { XtDisownSelection(vimShell, XA_PRIMARY, CurrentTime); gui_mch_clear_selection(); } int gui_mch_own_selection() { if (XtOwnSelection(vimShell, XA_PRIMARY, CurrentTime, gui_x11_convert_selection_cb, gui_x11_lose_ownership_cb, NULL) == False) return FAIL; return OK; } /* * Menu stuff. */ void gui_x11_menu_cb(w, client_data, call_data) Widget w; XtPointer client_data, call_data; { gui_menu_cb((GuiMenu *)client_data); } /* * Used recursively by gui_x11_update_menus (see below) */ static void gui_x11_update_menus_recurse(menu, mode) GuiMenu *menu; int mode; { while (menu) { if (menu->modes & mode) { if (vim_strchr(p_guioptions, GO_GREY) != NULL) XtSetSensitive(menu->id, True); else XtManageChild(menu->id); gui_x11_update_menus_recurse(menu->children, mode); } else { if (vim_strchr(p_guioptions, GO_GREY) != NULL) XtSetSensitive(menu->id, False); else XtUnmanageChild(menu->id); } menu = menu->next; } } /* * Make sure only the valid menu items appear for this mode. If * force_menu_update is not TRUE, then we only do this if the mode has changed * since last time. If "modes" is not 0, then we use these modes instead. */ void gui_x11_update_menus(modes) int modes; { static int prev_mode = -1; int mode = 0; if (modes != 0x0) mode = modes; else if (VIsual_active) mode = MENU_VISUAL_MODE; else if (State & NORMAL) mode = MENU_NORMAL_MODE; else if (State & INSERT) mode = MENU_INSERT_MODE; else if (State & CMDLINE) mode = MENU_CMDLINE_MODE; if (force_menu_update || mode != prev_mode) { gui_x11_update_menus_recurse(gui.root_menu, mode); prev_mode = mode; force_menu_update = FALSE; } } /* * Function called when window closed. Preserve files and exit. * Should put up a requester! */ /*ARGSUSED*/ static void gui_x11_wm_protocol_handler(w, client_data, event, bool) Widget w; XtPointer client_data; XEvent *event; Boolean *bool; { /* * On some HPUX system with Motif 1.2 this function is somehow called when * starting up. This if () avoids an unexpected exit. */ if (event->type != ClientMessage || ((XClientMessageEvent *)event)->data.l[0] != Atom_WM_DELETE_WINDOW) return; STRCPY(IObuff, "Vim: Window closed\n"); preserve_exit(); /* preserve files and exit */ } /* * Compare two screen positions ala strcmp() */ static int gui_x11_compare_pos(row1, col1, row2, col2) short_u row1; short_u col1; short_u row2; short_u col2; { if (row1 > row2) return( 1); if (row1 < row2) return(-1); if (col1 > col2) return( 1); if (col1 < col2) return(-1); return( 0); } /* * Start out the selection */ void gui_x11_start_selection(button, x, y, repeated_click, modifiers) int button; int x; int y; int repeated_click; int_u modifiers; { GuiSelection *gs = &gui.selection; if (gs->state == SELECT_DONE) gui_mch_clear_selection(); gs->start.lnum = Y_2_ROW(y); gs->start.col = X_2_COL(x); gs->end = gs->start; gs->origin_row = gs->start.lnum; gs->state = SELECT_IN_PROGRESS; if (repeated_click) { if (++(gs->mode) > SELECT_MODE_LINE) gs->mode = SELECT_MODE_CHAR; } else gs->mode = SELECT_MODE_CHAR; /* clear the cursor until the selection is made */ gui_undraw_cursor(); switch (gs->mode) { case SELECT_MODE_CHAR: gs->origin_start_col = gs->start.col; gs->word_end_col = gui_x11_get_line_end(gs->start.lnum); break; case SELECT_MODE_WORD: gui_x11_get_word_boundaries(gs, gs->start.lnum, gs->start.col); gs->origin_start_col = gs->word_start_col; gs->origin_end_col = gs->word_end_col; gui_x11_invert_area(gs->start.lnum, gs->word_start_col, gs->end.lnum, gs->word_end_col); gs->start.col = gs->word_start_col; gs->end.col = gs->word_end_col; break; case SELECT_MODE_LINE: gui_x11_invert_area(gs->start.lnum, 0, gs->start.lnum, gui.num_cols); gs->start.col = 0; gs->end.col = gui.num_cols; break; } gs->prev = gs->start; #ifdef DEBUG_SELECTION printf("Selection started at (%u,%u)\n", gs->start.lnum, gs->start.col); #endif } /* * Continue processing the selection */ void gui_x11_process_selection(button, x, y, repeated_click, modifiers) int button; int x; int y; int repeated_click; int_u modifiers; { GuiSelection *gs = &gui.selection; int row, col; int diff; if (button == MOUSE_RELEASE) { /* Check to make sure we have something selected */ if (gs->start.lnum == gs->end.lnum && gs->start.col == gs->end.col) { gui_update_cursor(); gs->state = SELECT_CLEARED; return; } #ifdef DEBUG_SELECTION printf("Selection ended: (%u,%u) to (%u,%u)\n", gs->start.lnum, gs->start.col, gs->end.lnum, gs->end.col); #endif gui_free_selection(); gui_own_selection(); gui_x11_yank_selection(gs->start.lnum, gs->start.col, gs->end.lnum, gs->end.col); gui_update_cursor(); gs->state = SELECT_DONE; return; } row = Y_2_ROW(y); col = X_2_COL(x); row = check_row(row); col = check_col(col); if (col == gs->prev.col && row == gs->prev.lnum) return; /* * When extending the selection with the right mouse button, swap the * start and end if the position is before half the selection */ if (gs->state == SELECT_DONE && button == MOUSE_RIGHT) { /* * If the click is before the start, or the click is inside the * selection and the start is the closest side, set the origin to the * end of the selection. */ if (gui_x11_compare_pos(row, col, gs->start.lnum, gs->start.col) < 0 || (gui_x11_compare_pos(row, col, gs->end.lnum, gs->end.col) < 0 && (((gs->start.lnum == gs->end.lnum && gs->end.col - col > col - gs->start.col)) || ((diff = (gs->end.lnum - row) - (row - gs->start.lnum)) > 0 || (diff == 0 && col < (gs->start.col + gs->end.col) / 2))))) { gs->origin_row = gs->end.lnum; gs->origin_start_col = gs->end.col - 1; gs->origin_end_col = gs->end.col; } else { gs->origin_row = gs->start.lnum; gs->origin_start_col = gs->start.col; gs->origin_end_col = gs->start.col; } if (gs->mode == SELECT_MODE_WORD) { gui_x11_get_word_boundaries(gs, gs->origin_row, gs->origin_start_col); gs->origin_start_col = gs->word_start_col; gs->origin_end_col = gs->word_end_col; } } /* set state, for when using the right mouse button */ gs->state = SELECT_IN_PROGRESS; #ifdef DEBUG_SELECTION printf("Selection extending to (%d,%d)\n", row, col); #endif switch (gs->mode) { case SELECT_MODE_CHAR: /* If we're on a different line, find where the line ends */ if (row != gs->prev.lnum) gs->word_end_col = gui_x11_get_line_end(row); /* See if we are before or after the origin of the selection */ if (gui_x11_compare_pos(row, col, gs->origin_row, gs->origin_start_col) >= 0) { if (col >= (int)gs->word_end_col) gui_x11_update_selection(gs, gs->origin_row, gs->origin_start_col, row, gui.num_cols); else gui_x11_update_selection(gs, gs->origin_row, gs->origin_start_col, row, col + 1); } else { if (col >= (int)gs->word_end_col) gui_x11_update_selection(gs, row, gs->word_end_col, gs->origin_row, gs->origin_start_col + 1); else gui_x11_update_selection(gs, row, col, gs->origin_row, gs->origin_start_col + 1); } break; case SELECT_MODE_WORD: /* If we are still within the same word, do nothing */ if (row == gs->prev.lnum && col >= (int)gs->word_start_col && col < (int)gs->word_end_col) return; /* Get new word boundaries */ gui_x11_get_word_boundaries(gs, row, col); /* Handle being after the origin point of selection */ if (gui_x11_compare_pos(row, col, gs->origin_row, gs->origin_start_col) >= 0) gui_x11_update_selection(gs, gs->origin_row, gs->origin_start_col, row, gs->word_end_col); else gui_x11_update_selection(gs, row, gs->word_start_col, gs->origin_row, gs->origin_end_col); break; case SELECT_MODE_LINE: if (row == gs->prev.lnum) return; if (gui_x11_compare_pos(row, col, gs->origin_row, gs->origin_start_col) >= 0) gui_x11_update_selection(gs, gs->origin_row, 0, row, gui.num_cols); else gui_x11_update_selection(gs, row, 0, gs->origin_row, gui.num_cols); break; } gs->prev.lnum = row; gs->prev.col = col; #ifdef DEBUG_SELECTION printf("Selection is: (%u,%u) to (%u,%u)\n", gs->start.lnum, gs->start.col, gs->end.lnum, gs->end.col); #endif } /* * Called after an Expose event to redraw the selection */ void gui_x11_redraw_selection(x, y, w, h) int x; int y; int w; int h; { GuiSelection *gs = &gui.selection; int row1, col1, row2, col2; int row; int start; int end; if (gs->state == SELECT_CLEARED) return; row1 = Y_2_ROW(y); col1 = X_2_COL(x); row2 = Y_2_ROW(y + h - 1); col2 = X_2_COL(x + w - 1); /* Limit the rows that need to be re-drawn */ if (gs->start.lnum > row1) row1 = gs->start.lnum; if (gs->end.lnum < row2) row2 = gs->end.lnum; /* Look at each row that might need to be re-drawn */ for (row = row1; row <= row2; row++) { /* For the first selection row, use the starting selection column */ if (row == gs->start.lnum) start = gs->start.col; else start = 0; /* For the last selection row, use the ending selection column */ if (row == gs->end.lnum) end = gs->end.col; else end = gui.num_cols; if (col1 > start) start = col1; if (col2 < end) end = col2 + 1; if (end > start) invert_rectangle(row, start, 1, end - start); } } /* * Called from outside to clear selected region from the display */ void gui_mch_clear_selection() { GuiSelection *gs = &gui.selection; if (gs->state == SELECT_CLEARED) return; gui_x11_invert_area(gs->start.lnum, gs->start.col, gs->end.lnum, gs->end.col); gs->state = SELECT_CLEARED; } /* * Invert a region of the display between a starting and ending row and column */ static void gui_x11_invert_area(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { /* Swap the from and to positions so the from is always before */ if (gui_x11_compare_pos(row1, col1, row2, col2) > 0) { int tmp_row, tmp_col; tmp_row = row1; tmp_col = col1; row1 = row2; col1 = col2; row2 = tmp_row; col2 = tmp_col; } /* If all on the same line, do it the easy way */ if (row1 == row2) { invert_rectangle(row1, col1, 1, col2 - col1); return; } /* Handle a piece of the first line */ if (col1 > 0) { invert_rectangle(row1, col1, 1, gui.num_cols - col1); row1++; } /* Handle a piece of the last line */ if (col2 < gui.num_cols - 1) { invert_rectangle(row2, 0, 1, col2); row2--; } /* Handle the rectangle thats left */ if (row2 >= row1) invert_rectangle(row1, 0, row2 - row1 + 1, gui.num_cols); } /* * Yank the currently selected area into the special selection buffer so it * will be available for pasting. */ static void gui_x11_yank_selection(row1, col1, row2, col2) int row1; int col1; int row2; int col2; { char_u *buffer; char_u *bufp; int row; int start_col; int end_col; int line_end_col; int add_newline_flag = FALSE; /* Create a temporary buffer for storing the text */ buffer = lalloc((row2 - row1 + 1) * gui.num_cols + 1, TRUE); if (buffer == NULL) return; /* Should there be an error message here? */ /* Process each row in the selection */ for (bufp = buffer, row = row1; row <= row2; row++) { if (row == row1) start_col = col1; else start_col = 0; if (row == row2) end_col = col2; else end_col = gui.num_cols; line_end_col = gui_x11_get_line_end(row); /* See if we need to nuke some trailing whitespace */ if (end_col >= gui.num_cols && (row < row2 || end_col > line_end_col)) { /* Get rid of trailing whitespace */ end_col = line_end_col; if (end_col < start_col) end_col = start_col; /* If the last line extended to the end, add an extra newline */ if (row == row2) add_newline_flag = TRUE; } /* If after the first row, we need to always add a newline */ if (row > row1) *bufp++ = NL; if (gui.row < screen_Rows && end_col <= screen_Columns) { STRNCPY(bufp, &LinePointers[row][start_col], end_col - start_col); bufp += end_col - start_col; } } /* Add a newline at the end if the selection ended there */ if (add_newline_flag) *bufp++ = NL; gui_yank_selection(MCHAR, buffer, bufp - buffer); vim_free(buffer); } /* * Find the starting and ending positions of the word at the given row and * column. */ static void gui_x11_get_word_boundaries(gs, row, col) GuiSelection *gs; int row; int col; { char start_class; int temp_col; if (row >= screen_Rows || col >= screen_Columns) return; start_class = char_class(LinePointers[row][col]); temp_col = col; for ( ; temp_col > 0; temp_col--) if (char_class(LinePointers[row][temp_col - 1]) != start_class) break; gs->word_start_col = temp_col; temp_col = col; for ( ; temp_col < screen_Columns; temp_col++) if (char_class(LinePointers[row][temp_col]) != start_class) break; gs->word_end_col = temp_col; #ifdef DEBUG_SELECTION printf("Current word: col %u to %u\n", gs->word_start_col, gs->word_end_col); #endif } /* * Find the column position for the last non-whitespace character on the given * line. */ static int gui_x11_get_line_end(row) int row; { int i; if (row >= screen_Rows) return 0; for (i = screen_Columns; i > 0; i--) if (LinePointers[row][i - 1] != ' ') break; return i; } /* * Update the currently selected region by adding and/or subtracting from the * beginning or end and inverting the changed area(s). */ static void gui_x11_update_selection(gs, row1, col1, row2, col2) GuiSelection *gs; int row1; int col1; int row2; int col2; { /* See if we changed at the beginning of the selection */ if (row1 != gs->start.lnum || col1 != gs->start.col) { gui_x11_invert_area(row1, col1, gs->start.lnum, gs->start.col); gs->start.lnum = row1; gs->start.col = col1; } /* See if we changed at the end of the selection */ if (row2 != gs->end.lnum || col2 != gs->end.col) { gui_x11_invert_area(row2, col2, gs->end.lnum, gs->end.col); gs->end.lnum = row2; gs->end.col = col2; } }