diff options
Diffstat (limited to 'src/gui_w48.c')
-rw-r--r-- | src/gui_w48.c | 3402 |
1 files changed, 3402 insertions, 0 deletions
diff --git a/src/gui_w48.c b/src/gui_w48.c new file mode 100644 index 000000000..761c1af16 --- /dev/null +++ b/src/gui_w48.c @@ -0,0 +1,3402 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI 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. + * See README.txt for an overview of the Vim source code. + */ +/* + * gui_w48.c: This file is included in gui_w16.c and gui_w32.c. + * + * GUI support for Microsoft Windows (Win16 + Win32 = Win48 :-) + * + * The combined efforts of: + * George V. Reilly <george@reilly.org> + * Robert Webb + * Vince Negri + * ...and contributions from many others + * + */ + +#include "vim.h" +#include "version.h" /* used by dialog box routine for default title */ +#ifdef DEBUG +# include <tchar.h> +#endif +#ifndef __MINGW32__ +# include <shellapi.h> +#endif +#if defined(FEAT_TOOLBAR) || defined(FEAT_BEVAL) +# include <commctrl.h> +#endif +#ifdef WIN16 +# include <commdlg.h> +# include <shellapi.h> +# ifdef WIN16_3DLOOK +# include <ctl3d.h> +# endif +#endif +#include <windowsx.h> + +#ifdef GLOBAL_IME +# include "glbl_ime.h" +#endif + +#ifdef FEAT_MENU +# define MENUHINTS /* show menu hints in command line */ +#endif + +/* Some parameters for dialog boxes. All in pixels. */ +#define DLG_PADDING_X 10 +#define DLG_PADDING_Y 10 +#define DLG_OLD_STYLE_PADDING_X 5 +#define DLG_OLD_STYLE_PADDING_Y 5 +#define DLG_VERT_PADDING_X 4 /* For vertical buttons */ +#define DLG_VERT_PADDING_Y 4 +#define DLG_ICON_WIDTH 34 +#define DLG_ICON_HEIGHT 34 +#define DLG_MIN_WIDTH 150 +#define DLG_FONT_NAME "MS Sans Serif" +#define DLG_FONT_POINT_SIZE 8 +#define DLG_MIN_MAX_WIDTH 400 + +#define DLG_NONBUTTON_CONTROL 5000 /* First ID of non-button controls */ + +#ifndef WM_XBUTTONDOWN /* For Win2K / winME ONLY */ +# define WM_XBUTTONDOWN 0x020B +# define WM_XBUTTONUP 0x020C +# define WM_XBUTTONDBLCLK 0x020D +# define MK_XBUTTON1 0x0020 +# define MK_XBUTTON2 0x0040 +#endif + +#ifdef PROTO +/* + * Define a few things for generating prototypes. This is just to avoid + * syntax errors, the defines do not need to be correct. + */ +# define APIENTRY +# define CALLBACK +# define CONST +# define FAR +# define NEAR +# define _cdecl +typedef int BOOL; +typedef int BYTE; +typedef int DWORD; +typedef int WCHAR; +typedef int ENUMLOGFONT; +typedef int FINDREPLACE; +typedef int HANDLE; +typedef int HBITMAP; +typedef int HBRUSH; +typedef int HDROP; +typedef int INT; +typedef int LOGFONT[]; +typedef int LPARAM; +typedef int LPCREATESTRUCT; +typedef int LPCSTR; +typedef int LPCTSTR; +typedef int LPRECT; +typedef int LPSTR; +typedef int LPWINDOWPOS; +typedef int LPWORD; +typedef int LRESULT; +# undef MSG +typedef int MSG; +typedef int NEWTEXTMETRIC; +typedef int OSVERSIONINFO; +typedef int PWORD; +typedef int RECT; +typedef int UINT; +typedef int WORD; +typedef int WPARAM; +typedef int POINT; +typedef void *HINSTANCE; +typedef void *HMENU; +typedef void *HWND; +typedef void *HDC; +typedef void VOID; +typedef int LPNMHDR; +typedef int LONG; +#endif + +#ifndef GET_X_LPARAM +# define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) +#endif + +static void _OnPaint( HWND hwnd); +static void clear_rect(RECT *rcp); +static int gui_mswin_get_menu_height(int fix_window); + +static WORD s_dlgfntheight; /* height of the dialog font */ +static WORD s_dlgfntwidth; /* width of the dialog font */ + +#ifdef FEAT_MENU +static HMENU s_menuBar = NULL; +#endif +#ifdef FEAT_TEAROFF +static void rebuild_tearoff(vimmenu_T *menu); +static HBITMAP s_htearbitmap; /* bitmap used to indicate tearoff */ +#endif + +/* Flag that is set while processing a message that must not be interupted by + * processing another message. */ +static int s_busy_processing = FALSE; + +static int destroying = FALSE; /* call DestroyWindow() ourselves */ + +#ifdef MSWIN_FIND_REPLACE +static UINT s_findrep_msg = 0; /* set in gui_w[16/32].c */ +static FINDREPLACE s_findrep_struct; +static HWND s_findrep_hwnd = NULL; +static int s_findrep_is_find; /* TRUE for find dialog, FALSE + for find/replace dialog */ +#endif + +static HINSTANCE s_hinst = NULL; +#if !defined(FEAT_SNIFF) && !defined(FEAT_GUI) +static +#endif +HWND s_hwnd = NULL; +static HDC s_hdc = NULL; +static HBRUSH s_brush = NULL; + +#ifdef FEAT_TOOLBAR +static HWND s_toolbarhwnd = NULL; +#endif + +static WPARAM s_wParam = 0; +static LPARAM s_lParam = 0; + +static HWND s_textArea = NULL; +static UINT s_uMsg = 0; + +static char_u *s_textfield; /* Used by dialogs to pass back strings */ + +static int s_need_activate = FALSE; + +/* This variable is set when waiting for an event, which is the only moment + * scrollbar dragging can be done directly. It's not allowed while commands + * are executed, because it may move the cursor and that may cause unexpected + * problems (e.g., while ":s" is working). + */ +static int allow_scrollbar = FALSE; + +#ifdef GLOBAL_IME +# define MyTranslateMessage(x) global_ime_TranslateMessage(x) +#else +# define MyTranslateMessage(x) TranslateMessage(x) +#endif + +#if (defined(WIN3264) && defined(FEAT_MBYTE)) || defined(GLOBAL_IME) + /* use of WindowProc depends on wide_WindowProc */ +# define MyWindowProc vim_WindowProc +#else + /* use ordinary WindowProc */ +# define MyWindowProc DefWindowProc +#endif + +extern int current_font_height; /* this is in os_mswin.c */ + +static struct +{ + UINT key_sym; + char_u vim_code0; + char_u vim_code1; +} special_keys[] = +{ + {VK_UP, 'k', 'u'}, + {VK_DOWN, 'k', 'd'}, + {VK_LEFT, 'k', 'l'}, + {VK_RIGHT, 'k', 'r'}, + + {VK_F1, 'k', '1'}, + {VK_F2, 'k', '2'}, + {VK_F3, 'k', '3'}, + {VK_F4, 'k', '4'}, + {VK_F5, 'k', '5'}, + {VK_F6, 'k', '6'}, + {VK_F7, 'k', '7'}, + {VK_F8, 'k', '8'}, + {VK_F9, 'k', '9'}, + {VK_F10, 'k', ';'}, + + {VK_F11, 'F', '1'}, + {VK_F12, 'F', '2'}, + {VK_F13, 'F', '3'}, + {VK_F14, 'F', '4'}, + {VK_F15, 'F', '5'}, + {VK_F16, 'F', '6'}, + {VK_F17, 'F', '7'}, + {VK_F18, 'F', '8'}, + {VK_F19, 'F', '9'}, + {VK_F20, 'F', 'A'}, + + {VK_F21, 'F', 'B'}, +#ifdef FEAT_NETBEANS_INTG + {VK_PAUSE, 'F', 'B'}, /* Pause == F21 (see gui_gtk_x11.c) */ +#endif + {VK_F22, 'F', 'C'}, + {VK_F23, 'F', 'D'}, + {VK_F24, 'F', 'E'}, /* winuser.h defines up to F24 */ + + {VK_HELP, '%', '1'}, + {VK_BACK, 'k', 'b'}, + {VK_INSERT, 'k', 'I'}, + {VK_DELETE, 'k', 'D'}, + {VK_HOME, 'k', 'h'}, + {VK_END, '@', '7'}, + {VK_PRIOR, 'k', 'P'}, + {VK_NEXT, 'k', 'N'}, + {VK_PRINT, '%', '9'}, + {VK_ADD, 'K', '6'}, + {VK_SUBTRACT, 'K', '7'}, + {VK_DIVIDE, 'K', '8'}, + {VK_MULTIPLY, 'K', '9'}, + {VK_SEPARATOR, 'K', 'A'}, /* Keypad Enter */ + {VK_DECIMAL, 'K', 'B'}, + + {VK_NUMPAD0, 'K', 'C'}, + {VK_NUMPAD1, 'K', 'D'}, + {VK_NUMPAD2, 'K', 'E'}, + {VK_NUMPAD3, 'K', 'F'}, + {VK_NUMPAD4, 'K', 'G'}, + {VK_NUMPAD5, 'K', 'H'}, + {VK_NUMPAD6, 'K', 'I'}, + {VK_NUMPAD7, 'K', 'J'}, + {VK_NUMPAD8, 'K', 'K'}, + {VK_NUMPAD9, 'K', 'L'}, + + /* Keys that we want to be able to use any modifier with: */ + {VK_SPACE, ' ', NUL}, + {VK_TAB, TAB, NUL}, + {VK_ESCAPE, ESC, NUL}, + {NL, NL, NUL}, + {CAR, CAR, NUL}, + + /* End of list marker: */ + {0, 0, 0} +}; + +/* Local variables */ +static int s_button_pending = -1; +static int s_x_pending; +static int s_y_pending; +static UINT s_kFlags_pending; +static UINT s_wait_timer = 0; /* Timer for get char from user */ +static int s_timed_out = FALSE; +static int dead_key = 0; /* 0 - no dead key, 1 - dead key pressed */ + +#ifdef WIN3264 +static OSVERSIONINFO os_version; /* like it says. Init in gui_mch_init() */ +#endif + +#ifdef FEAT_BEVAL +/* balloon-eval WM_NOTIFY_HANDLER */ +static void Handle_WM_Notify __ARGS((HWND hwnd, LPNMHDR pnmh)); +static void TrackUserActivity __ARGS((UINT uMsg)); +#endif + +/* + * For control IME. + */ +#ifdef FEAT_MBYTE +# ifdef USE_IM_CONTROL +static LOGFONT norm_logfont; +# endif +#endif + +#ifdef FEAT_MBYTE_IME +static LRESULT _OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData); +#endif + +#ifdef DEBUG +/* + * Print out the last Windows error message + */ + static void +print_windows_error(void) +{ + LPVOID lpMsgBuf; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, 0, NULL); + TRACE1("Error: %s\n", lpMsgBuf); + LocalFree(lpMsgBuf); +} +#endif /* DEBUG */ + +/* + * 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 UINT blink_timer = 0; + + void +gui_mch_set_blinking(long wait, long on, long off) +{ + blink_waittime = wait; + blink_ontime = on; + blink_offtime = off; +} + +/* ARGSUSED */ + static VOID CALLBACK +_OnBlinkTimer( + HWND hwnd, + UINT uMsg, + UINT idEvent, + DWORD dwTime) +{ + MSG msg; + + /* + TRACE2("Got timer event, id %d, blink_timer %d\n", idEvent, blink_timer); + */ + + KillTimer(NULL, idEvent); + + /* Eat spurious WM_TIMER messages */ + while (PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) + ; + + if (blink_state == BLINK_ON) + { + gui_undraw_cursor(); + blink_state = BLINK_OFF; + blink_timer = (UINT) SetTimer(NULL, 0, (UINT)blink_offtime, + (TIMERPROC)_OnBlinkTimer); + } + else + { + gui_update_cursor(TRUE, FALSE); + blink_state = BLINK_ON; + blink_timer = (UINT) SetTimer(NULL, 0, (UINT)blink_ontime, + (TIMERPROC)_OnBlinkTimer); + } +} + + static void +gui_mswin_rm_blink_timer(void) +{ + MSG msg; + + if (blink_timer != 0) + { + KillTimer(NULL, blink_timer); + /* Eat spurious WM_TIMER messages */ + while (PeekMessage(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) + ; + blink_timer = 0; + } +} + +/* + * Stop the cursor blinking. Show the cursor if it wasn't shown. + */ + void +gui_mch_stop_blink(void) +{ + gui_mswin_rm_blink_timer(); + 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(void) +{ + gui_mswin_rm_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 = (UINT)SetTimer(NULL, 0, (UINT)blink_waittime, + (TIMERPROC)_OnBlinkTimer); + blink_state = BLINK_ON; + gui_update_cursor(TRUE, FALSE); + } +} + +/* + * Call-back routines. + */ + + static VOID CALLBACK +_OnTimer( + HWND hwnd, + UINT uMsg, + UINT idEvent, + DWORD dwTime) +{ + MSG msg; + + /* + TRACE2("Got timer event, id %d, s_wait_timer %d\n", idEvent, s_wait_timer); + */ + KillTimer(NULL, idEvent); + s_timed_out = TRUE; + + /* Eat spurious WM_TIMER messages */ + while (PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) + ; + if (idEvent == s_wait_timer) + s_wait_timer = 0; +} + + static void +_OnDeadChar( + HWND hwnd, + UINT ch, + int cRepeat) +{ + dead_key = 1; +} + +/* + * Convert Unicode character "ch" to bytes in "string[slen]". + * Return the length. + */ + static int +char_to_string(int ch, char_u *string, int slen) +{ + int len; + int i; +#ifdef FEAT_MBYTE + WCHAR wstring[2]; + char_u *ws = NULL;; + + /* "ch" is a UTF-16 character. Convert it to a string of bytes. When + * "enc_codepage" is non-zero use the standard Win32 function, otherwise + * use our own conversion function (e.g., for UTF-8). */ + wstring[0] = ch; + if (enc_codepage > 0) + len = WideCharToMultiByte(enc_codepage, 0, wstring, 1, string, slen, + 0, NULL); + else + { + len = 1; + ws = ucs2_to_enc(wstring, &len); + if (ws == NULL) + len = 0; + else + { + if (len > slen) /* just in case */ + len = slen; + mch_memmove(string, ws, len); + vim_free(ws); + } + } + if (len == 0) +#endif + { + string[0] = ch; + len = 1; + } + + for (i = 0; i < len; ++i) + if (string[i] == CSI && len <= slen - 2) + { + /* Insert CSI as K_CSI. */ + mch_memmove(string + i + 3, string + i + 1, len - i - 1); + string[++i] = KS_EXTRA; + string[++i] = (int)KE_CSI; + len += 2; + } + + return len; +} + +/* + * Key hit, add it to the input buffer. + */ + static void +_OnChar( + HWND hwnd, + UINT ch, + int cRepeat) +{ + char_u string[40]; + int len = 0; + + len = char_to_string(ch, string, 40); + if (len == 1 && string[0] == Ctrl_C && ctrl_c_interrupts) + { + trash_input_buf(); + got_int = TRUE; + } + + add_to_input_buf(string, len); +} + +/* + * Alt-Key hit, add it to the input buffer. + */ + static void +_OnSysChar( + HWND hwnd, + UINT cch, + int cRepeat) +{ + char_u string[40]; /* Enough for multibyte character */ + int len; + int modifiers; + int ch = cch; /* special keys are negative */ + + /* TRACE("OnSysChar(%d, %c)\n", ch, ch); */ + + /* OK, we have a character key (given by ch) which was entered with the + * ALT key pressed. Eg, if the user presses Alt-A, then ch == 'A'. Note + * that the system distinguishes Alt-a and Alt-A (Alt-Shift-a unless + * CAPSLOCK is pressed) at this point. + */ + modifiers = MOD_MASK_ALT; + if (GetKeyState(VK_SHIFT) & 0x8000) + modifiers |= MOD_MASK_SHIFT; + if (GetKeyState(VK_CONTROL) & 0x8000) + modifiers |= MOD_MASK_CTRL; + + ch = simplify_key(ch, &modifiers); + /* remove the SHIFT modifier for keys where it's already included, e.g., + * '(' and '*' */ + if (ch < 0x100 && !isalpha(ch) && isprint(ch)) + modifiers &= ~MOD_MASK_SHIFT; + + /* Interpret the ALT key as making the key META, include SHIFT, etc. */ + ch = extract_modifiers(ch, &modifiers); + if (ch == CSI) + ch = K_CSI; + + len = 0; + if (modifiers) + { + string[len++] = CSI; + string[len++] = KS_MODIFIER; + string[len++] = modifiers; + } + + if (IS_SPECIAL((int)ch)) + { + string[len++] = CSI; + string[len++] = K_SECOND((int)ch); + string[len++] = K_THIRD((int)ch); + } + else + { + /* Although the documentation isn't clear about it, we assume "ch" is + * a Unicode character. */ + len += char_to_string(ch, string + len, 40 - len); + } + + add_to_input_buf(string, len); +} + + static void +_OnMouseEvent( + int button, + int x, + int y, + int repeated_click, + UINT keyFlags) +{ + int vim_modifiers = 0x0; + + if (keyFlags & MK_SHIFT) + vim_modifiers |= MOUSE_SHIFT; + if (keyFlags & MK_CONTROL) + vim_modifiers |= MOUSE_CTRL; + if (GetKeyState(VK_MENU) & 0x8000) + vim_modifiers |= MOUSE_ALT; + + gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers); +} + + static void +_OnMouseButtonDown( + HWND hwnd, + BOOL fDoubleClick, + int x, + int y, + UINT keyFlags) +{ + static LONG s_prevTime = 0; + + LONG currentTime = GetMessageTime(); + int button = -1; + int repeated_click; + + /* Give main window the focus: this is so the cursor isn't hollow. */ + (void)SetFocus(s_hwnd); + + if (s_uMsg == WM_LBUTTONDOWN || s_uMsg == WM_LBUTTONDBLCLK) + button = MOUSE_LEFT; + else if (s_uMsg == WM_MBUTTONDOWN || s_uMsg == WM_MBUTTONDBLCLK) + button = MOUSE_MIDDLE; + else if (s_uMsg == WM_RBUTTONDOWN || s_uMsg == WM_RBUTTONDBLCLK) + button = MOUSE_RIGHT; +#ifndef WIN16 /*<VN>*/ + else if (s_uMsg == WM_XBUTTONDOWN || s_uMsg == WM_XBUTTONDBLCLK) + { +#ifndef GET_XBUTTON_WPARAM +# define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) +#endif + button = ((GET_XBUTTON_WPARAM(s_wParam) == 1) ? MOUSE_X1 : MOUSE_X2); + } + else if (s_uMsg == WM_CAPTURECHANGED) + { + /* on W95/NT4, somehow you get in here with an odd Msg + * if you press one button while holding down the other..*/ + if (s_button_pending == MOUSE_LEFT) + button = MOUSE_RIGHT; + else + button = MOUSE_LEFT; + } +#endif + if (button >= 0) + { + repeated_click = ((int)(currentTime - s_prevTime) < p_mouset); + + /* + * Holding down the left and right buttons simulates pushing the middle + * button. + */ + if (repeated_click && + ((button == MOUSE_LEFT && s_button_pending == MOUSE_RIGHT) || + (button == MOUSE_RIGHT && s_button_pending == MOUSE_LEFT))) + { + /* + * Hmm, gui.c will ignore more than one button down at a time, so + * pretend we let go of it first. + */ + gui_send_mouse_event(MOUSE_RELEASE, x, y, FALSE, 0x0); + button = MOUSE_MIDDLE; + repeated_click = FALSE; + s_button_pending = -1; + _OnMouseEvent(button, x, y, repeated_click, keyFlags); + } + else if ((repeated_click) + || (mouse_model_popup() && (button == MOUSE_RIGHT))) + { + if (s_button_pending > -1) + { + _OnMouseEvent(s_button_pending, x, y, FALSE, keyFlags); + s_button_pending = -1; + } + /* TRACE("Button down at x %d, y %d\n", x, y); */ + _OnMouseEvent(button, x, y, repeated_click, keyFlags); + } + else + { + /* + * If this is the first press (i.e. not a multiple click) don't + * action immediately, but store and wait for: + * i) button-up + * ii) mouse move + * iii) another button press + * before using it. + * This enables us to make left+right simulate middle button, + * without left or right being actioned first. The side-effect is + * that if you click and hold the mouse without dragging, the + * cursor doesn't move until you release the button. In practice + * this is hardly a problem. + */ + s_button_pending = button; + s_x_pending = x; + s_y_pending = y; + s_kFlags_pending = keyFlags; + } + + s_prevTime = currentTime; + } +} + + static void +_OnMouseMoveOrRelease( + HWND hwnd, + int x, + int y, + UINT keyFlags) +{ + int button; + + if (s_button_pending > -1) + { + /* Delayed action for mouse down event */ + _OnMouseEvent(s_button_pending, s_x_pending, + s_y_pending, FALSE, s_kFlags_pending); + s_button_pending = -1; + } + if (s_uMsg == WM_MOUSEMOVE) + { + /* + * It's only a MOUSE_DRAG if one or more mouse buttons are being held + * down. + */ + if (!(keyFlags & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON + | MK_XBUTTON1 | MK_XBUTTON2))) + { + gui_mouse_moved(x, y); + return; + } + + /* + * While button is down, keep grabbing mouse move events when + * the mouse goes outside the window + */ + SetCapture(s_textArea); + button = MOUSE_DRAG; + /* TRACE(" move at x %d, y %d\n", x, y); */ + } + else + { + ReleaseCapture(); + button = MOUSE_RELEASE; + /* TRACE(" up at x %d, y %d\n", x, y); */ + } + + _OnMouseEvent(button, x, y, FALSE, keyFlags); +} + +#ifdef FEAT_MENU +/* + * Find the vimmenu_T with the given id + */ + static vimmenu_T * +gui_mswin_find_menu( + vimmenu_T *pMenu, + int id) +{ + vimmenu_T *pChildMenu; + + while (pMenu) + { + if (pMenu->id == (UINT)id) + break; + if (pMenu->children != NULL) + { + pChildMenu = gui_mswin_find_menu(pMenu->children, id); + if (pChildMenu) + { + pMenu = pChildMenu; + break; + } + } + pMenu = pMenu->next; + } + return pMenu; +} + + static void +_OnMenu( + HWND hwnd, + int id, + HWND hwndCtl, + UINT codeNotify) +{ + vimmenu_T *pMenu; + + pMenu = gui_mswin_find_menu(root_menu, id); + if (pMenu) + gui_menu_cb(pMenu); +} +#endif + +#ifdef MSWIN_FIND_REPLACE +/* + * Handle a Find/Replace window message. + */ + static void +_OnFindRepl(void) +{ + int flags = 0; + int down; + + if (s_findrep_struct.Flags & FR_DIALOGTERM) + /* Give main window the focus back. */ + (void)SetFocus(s_hwnd); + + if (s_findrep_struct.Flags & FR_FINDNEXT) + { + flags = FRD_FINDNEXT; + + /* Give main window the focus back: this is so the cursor isn't + * hollow. */ + (void)SetFocus(s_hwnd); + } + else if (s_findrep_struct.Flags & FR_REPLACE) + { + flags = FRD_REPLACE; + + /* Give main window the focus back: this is so the cursor isn't + * hollow. */ + (void)SetFocus(s_hwnd); + } + else if (s_findrep_struct.Flags & FR_REPLACEALL) + { + flags = FRD_REPLACEALL; + } + + if (flags != 0) + { + /* Call the generic GUI function to do the actual work. */ + if (s_findrep_struct.Flags & FR_WHOLEWORD) + flags |= FRD_WHOLE_WORD; + if (s_findrep_struct.Flags & FR_MATCHCASE) + flags |= FRD_MATCH_CASE; + down = (s_findrep_struct.Flags & FR_DOWN) != 0; + gui_do_findrepl(flags, s_findrep_struct.lpstrFindWhat, + s_findrep_struct.lpstrReplaceWith, down); + } +} +#endif + + static void +HandleMouseHide(UINT uMsg, LPARAM lParam) +{ + static LPARAM last_lParam = 0L; + + /* We sometimes get a mousemove when the mouse didn't move... */ + if (uMsg == WM_MOUSEMOVE) + { + if (lParam == last_lParam) + return; + last_lParam = lParam; + } + + /* Handle specially, to centralise coding. We need to be sure we catch all + * possible events which should cause us to restore the cursor (as it is a + * shared resource, we take full responsibility for it). + */ + switch (uMsg) + { + case WM_KEYUP: + case WM_CHAR: + /* + * blank out the pointer if necessary + */ + if (p_mh) + gui_mch_mousehide(TRUE); + break; + + case WM_SYSKEYUP: /* show the pointer when a system-key is pressed */ + case WM_SYSCHAR: + case WM_MOUSEMOVE: /* show the pointer on any mouse action */ + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONUP: + case WM_KILLFOCUS: + /* + * if the pointer is currently hidden, then we should show it. + */ + gui_mch_mousehide(FALSE); + break; + } +} + + static LRESULT CALLBACK +_TextAreaWndProc( + HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + /* + TRACE("TextAreaWndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n", + hwnd, uMsg, wParam, lParam); + */ + + HandleMouseHide(uMsg, lParam); + + s_uMsg = uMsg; + s_wParam = wParam; + s_lParam = lParam; + +#ifdef FEAT_BEVAL + TrackUserActivity(uMsg); +#endif + + switch (uMsg) + { + HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_LBUTTONDOWN,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_LBUTTONUP, _OnMouseMoveOrRelease); + HANDLE_MSG(hwnd, WM_MBUTTONDBLCLK,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_MBUTTONDOWN,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_MBUTTONUP, _OnMouseMoveOrRelease); + HANDLE_MSG(hwnd, WM_MOUSEMOVE, _OnMouseMoveOrRelease); + HANDLE_MSG(hwnd, WM_PAINT, _OnPaint); + HANDLE_MSG(hwnd, WM_RBUTTONDBLCLK,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_RBUTTONDOWN,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_RBUTTONUP, _OnMouseMoveOrRelease); +#ifndef WIN16 /*<VN>*/ + HANDLE_MSG(hwnd, WM_XBUTTONDBLCLK,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_XBUTTONDOWN,_OnMouseButtonDown); + HANDLE_MSG(hwnd, WM_XBUTTONUP, _OnMouseMoveOrRelease); +#endif + +#ifdef FEAT_BEVAL + case WM_NOTIFY: Handle_WM_Notify(hwnd, (LPNMHDR)lParam); + return TRUE; +#endif + + default: + return MyWindowProc(hwnd, uMsg, wParam, lParam); + } +} + +#if (defined(WIN3264) && defined(FEAT_MBYTE)) \ + || defined(GLOBAL_IME) \ + || defined(PROTO) +# ifdef PROTO +typedef int WINAPI; +# endif + + LRESULT WINAPI +vim_WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ +# ifdef GLOBAL_IME + return global_ime_DefWindowProc(hwnd, message, wParam, lParam); +# else + if (wide_WindowProc) + return DefWindowProcW(hwnd, message, wParam, lParam); + return DefWindowProc(hwnd, message, wParam, lParam); +#endif +} +#endif + +/* + * Called when the foreground or background color has been changed. + */ + void +gui_mch_new_colors(void) +{ + /* nothing to do? */ +} + +/* + * Set the colors to their default values. + */ + void +gui_mch_def_colors() +{ + gui.norm_pixel = GetSysColor(COLOR_WINDOWTEXT); + gui.back_pixel = GetSysColor(COLOR_WINDOW); + gui.def_norm_pixel = gui.norm_pixel; + gui.def_back_pixel = gui.back_pixel; +} + +/* + * Open the GUI window which was created by a call to gui_mch_init(). + */ + int +gui_mch_open(void) +{ +#ifndef SW_SHOWDEFAULT +# define SW_SHOWDEFAULT 10 /* Borland 5.0 doesn't have it */ +#endif + /* Actually open the window, if not already visible + * (may be done already in gui_mch_set_shellsize) */ + if (!IsWindowVisible(s_hwnd)) + ShowWindow(s_hwnd, SW_SHOWDEFAULT); + + return OK; +} + +/* + * Get the position of the top left corner of the window. + */ + int +gui_mch_get_winpos(int *x, int *y) +{ + RECT rect; + + GetWindowRect(s_hwnd, &rect); + *x = rect.left; + *y = rect.top; + return OK; +} + +/* + * Set the position of the top left corner of the window to the given + * coordinates. + */ + void +gui_mch_set_winpos(int x, int y) +{ + SetWindowPos(s_hwnd, NULL, x, y, 0, 0, + SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); +} + void +gui_mch_set_text_area_pos(int x, int y, int w, int h) +{ + static int oldx = 0; + static int oldy = 0; + + SetWindowPos(s_textArea, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE); + +#ifdef FEAT_TOOLBAR + if (vim_strchr(p_go, GO_TOOLBAR) != NULL) + SendMessage(s_toolbarhwnd, WM_SIZE, + (WPARAM)0, (LPARAM)(w + ((long)(TOOLBAR_BUTTON_HEIGHT+8)<<16))); +#endif + /* When side scroll bar is unshown, the size of window will change. + * then, the text area move left or right. thus client rect should be + * forcely redraw. (Yasuhiro Matsumoto) */ + if (oldx != x || oldy != y) + { + InvalidateRect(s_hwnd, NULL, FALSE); + oldx = x; + oldy = y; + } + +} + + +/* + * Scrollbar stuff: + */ + + void +gui_mch_enable_scrollbar( + scrollbar_T *sb, + int flag) +{ + ShowScrollBar(sb->id, SB_CTL, flag); + + /* TODO: When the window is maximized, the size of the window stays the + * same, thus the size of the text area changes. On Win98 it's OK, on Win + * NT 4.0 it's not... */ +} + + void +gui_mch_set_scrollbar_pos( + scrollbar_T *sb, + int x, + int y, + int w, + int h) +{ + SetWindowPos(sb->id, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW); +} + + void +gui_mch_create_scrollbar( + scrollbar_T *sb, + int orient) /* SBAR_VERT or SBAR_HORIZ */ +{ + sb->id = CreateWindow( + "SCROLLBAR", "Scrollbar", + WS_CHILD | ((orient == SBAR_VERT) ? SBS_VERT : SBS_HORZ), 0, 0, + 10, /* Any value will do for now */ + 10, /* Any value will do for now */ + s_hwnd, NULL, + s_hinst, NULL); +} + +/* + * Find the scrollbar with the given hwnd. + */ + static scrollbar_T * +gui_mswin_find_scrollbar(HWND hwnd) +{ + win_T *wp; + + if (gui.bottom_sbar.id == hwnd) + return &gui.bottom_sbar; + FOR_ALL_WINDOWS(wp) + { + if (wp->w_scrollbars[SBAR_LEFT].id == hwnd) + return &wp->w_scrollbars[SBAR_LEFT]; + if (wp->w_scrollbars[SBAR_RIGHT].id == hwnd) + return &wp->w_scrollbars[SBAR_RIGHT]; + } + return NULL; +} + +/* + * Get the character size of a font. + */ + static void +GetFontSize(GuiFont font) +{ + HWND hwnd = GetDesktopWindow(); + HDC hdc = GetWindowDC(hwnd); + HFONT hfntOld = SelectFont(hdc, (HFONT)font); + TEXTMETRIC tm; + + GetTextMetrics(hdc, &tm); + gui.char_width = tm.tmAveCharWidth + tm.tmOverhang; + + gui.char_height = tm.tmHeight +#ifndef MSWIN16_FASTTEXT + + p_linespace +#endif + ; + + SelectFont(hdc, hfntOld); + + ReleaseDC(hwnd, hdc); +} + + int +gui_mch_adjust_charsize(void) +{ + GetFontSize(gui.norm_font); + return OK; +} + + static GuiFont +get_font_handle(LOGFONT *lf) +{ + HFONT font = NULL; + + /* Load the font */ + font = CreateFontIndirect(lf); + + if (font == NULL) + return NOFONT; + + return (GuiFont)font; +} + + static int +pixels_to_points(int pixels, int vertical) +{ + int points; + HWND hwnd; + HDC hdc; + + hwnd = GetDesktopWindow(); + hdc = GetWindowDC(hwnd); + + points = MulDiv(pixels, 72, + GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX)); + + ReleaseDC(hwnd, hdc); + + return points; +} + + GuiFont +gui_mch_get_font( + char_u *name, + int giveErrorIfMissing) +{ + LOGFONT lf; + GuiFont font; + + get_logfont(&lf, name, NULL); + font = get_font_handle(&lf); + if (font == NOFONT && giveErrorIfMissing) + EMSG2(_(e_font), name); + return font; +} + void +gui_mch_free_font(GuiFont font) +{ + if (font) + DeleteObject((HFONT)font); +} + + static int +hex_digit(int c) +{ + if (VIM_ISDIGIT(c)) + return c - '0'; + c = TOLOWER_ASC(c); + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1000; +} +/* + * 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 guicolor_tTable + { + char *name; + COLORREF color; + } guicolor_tTable; + + static guicolor_tTable table[] = + { + {"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(0xE0, 0xE0, 0xE0)}, + {"LightGrey", RGB(0xE0, 0xE0, 0xE0)}, + {"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)}, + }; + + typedef struct SysColorTable + { + char *name; + int color; + } SysColorTable; + + static SysColorTable sys_table[] = + { +#ifdef WIN3264 + {"SYS_3DDKSHADOW", COLOR_3DDKSHADOW}, + {"SYS_3DHILIGHT", COLOR_3DHILIGHT}, +#ifndef __MINGW32__ + {"SYS_3DHIGHLIGHT", COLOR_3DHIGHLIGHT}, +#endif + {"SYS_BTNHILIGHT", COLOR_BTNHILIGHT}, + {"SYS_BTNHIGHLIGHT", COLOR_BTNHIGHLIGHT}, + {"SYS_3DLIGHT", COLOR_3DLIGHT}, + {"SYS_3DSHADOW", COLOR_3DSHADOW}, + {"SYS_DESKTOP", COLOR_DESKTOP}, + {"SYS_INFOBK", COLOR_INFOBK}, + {"SYS_INFOTEXT", COLOR_INFOTEXT}, + {"SYS_3DFACE", COLOR_3DFACE}, +#endif + {"SYS_BTNFACE", COLOR_BTNFACE}, + {"SYS_BTNSHADOW", COLOR_BTNSHADOW}, + {"SYS_ACTIVEBORDER", COLOR_ACTIVEBORDER}, + {"SYS_ACTIVECAPTION", COLOR_ACTIVECAPTION}, + {"SYS_APPWORKSPACE", COLOR_APPWORKSPACE}, + {"SYS_BACKGROUND", COLOR_BACKGROUND}, + {"SYS_BTNTEXT", COLOR_BTNTEXT}, + {"SYS_CAPTIONTEXT", COLOR_CAPTIONTEXT}, + {"SYS_GRAYTEXT", COLOR_GRAYTEXT}, + {"SYS_HIGHLIGHT", COLOR_HIGHLIGHT}, + {"SYS_HIGHLIGHTTEXT", COLOR_HIGHLIGHTTEXT}, + {"SYS_INACTIVEBORDER", COLOR_INACTIVEBORDER}, + {"SYS_INACTIVECAPTION", COLOR_INACTIVECAPTION}, + {"SYS_INACTIVECAPTIONTEXT", COLOR_INACTIVECAPTIONTEXT}, + {"SYS_MENU", COLOR_MENU}, + {"SYS_MENUTEXT", COLOR_MENUTEXT}, + {"SYS_SCROLLBAR", COLOR_SCROLLBAR}, + {"SYS_WINDOW", COLOR_WINDOW}, + {"SYS_WINDOWFRAME", COLOR_WINDOWFRAME}, + {"SYS_WINDOWTEXT", COLOR_WINDOWTEXT} + }; + + 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 colors we know */ + for (i = 0; i < sizeof(table) / sizeof(table[0]); i++) + if (STRICMP(name, table[i].name) == 0) + return table[i].color; + } + + /* + * Try to look up a system colour. + */ + for (i = 0; i < sizeof(sys_table) / sizeof(sys_table[0]); i++) + if (STRICMP(name, sys_table[i].name) == 0) + return GetSysColor(sys_table[i].color); + + /* + * Last attempt. Look in the file "$VIMRUNTIME/rgb.txt". + */ + { +#define LINE_LEN 100 + FILE *fd; + char line[LINE_LEN]; + char_u *fname; + + fname = expand_env_save((char_u *)"$VIMRUNTIME/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 *color; + + fgets(line, LINE_LEN, fd); + len = (int)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; + + color = line + pos; + + if (STRICMP(color, name) == 0) + { + fclose(fd); + return (guicolor_T) RGB(r, g, b); + } + } + + fclose(fd); + } + + return INVALCOLOR; +} +/* + * 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].vim_code1 != NUL; i++) + if (name[0] == special_keys[i].vim_code0 && + name[1] == special_keys[i].vim_code1) + return OK; + return FAIL; +} + + void +gui_mch_beep(void) +{ + MessageBeep(MB_OK); +} +/* + * 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) +{ + RECT rc; + + /* + * Note: InvertRect() excludes right and bottom of rectangle. + */ + rc.left = FILL_X(c); + rc.top = FILL_Y(r); + rc.right = rc.left + nc * gui.char_width; + rc.bottom = rc.top + nr * gui.char_height; + InvertRect(s_hdc, &rc); +} + +/* + * Iconify the GUI window. + */ + void +gui_mch_iconify(void) +{ + ShowWindow(s_hwnd, SW_MINIMIZE); +} + +/* + * Draw a cursor without focus. + */ + void +gui_mch_draw_hollow_cursor(guicolor_T color) +{ + HBRUSH hbr; + RECT rc; + + /* + * Note: FrameRect() excludes right and bottom of rectangle. + */ + rc.left = FILL_X(gui.col); + rc.top = FILL_Y(gui.row); + rc.right = rc.left + gui.char_width; +#ifdef FEAT_MBYTE + if (mb_lefthalve(gui.row, gui.col)) + rc.right += gui.char_width; +#endif + rc.bottom = rc.top + gui.char_height; + hbr = CreateSolidBrush(color); + FrameRect(s_hdc, &rc, hbr); + DeleteBrush(hbr); +} +/* + * Draw part of a cursor, "w" pixels wide, and "h" pixels high, using + * color "color". + */ + void +gui_mch_draw_part_cursor( + int w, + int h, + guicolor_T color) +{ + HBRUSH hbr; + RECT rc; + + /* + * Note: FillRect() excludes right and bottom of rectangle. + */ + rc.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); + rc.top = FILL_Y(gui.row) + gui.char_height - h; + rc.right = rc.left + w; + rc.bottom = rc.top + h; + hbr = CreateSolidBrush(color); + FillRect(s_hdc, &rc, hbr); + DeleteBrush(hbr); +} + +/* + * Process a single Windows message. + * If one is not available we hang until one is. + */ + static void +process_message(void) +{ + MSG msg; + UINT vk = 0; /* Virtual key */ + char_u string[40]; + int i; + int modifiers = 0; + int key; + + GetMessage(&msg, NULL, 0, 0); + +#ifdef FEAT_OLE + /* Look after OLE Automation commands */ + if (msg.message == WM_OLE) + { + char_u *str = (char_u *)msg.lParam; + add_to_input_buf(str, (int)STRLEN(str)); + vim_free(str); + return; + } +#endif + +#ifdef FEAT_NETBEANS_INTG + if (msg.message == WM_NETBEANS) + { + messageFromNetbeansW32(); + return; + } +#endif + +#ifdef FEAT_SNIFF + if (sniff_request_waiting && want_sniff_request) + { + static char_u bytes[3] = {CSI, (char_u)KS_EXTRA, (char_u)KE_SNIFF}; + add_to_input_buf(bytes, 3); /* K_SNIFF */ + sniff_request_waiting = 0; + want_sniff_request = 0; + /* request is handled in normal.c */ + } + if (msg.message == WM_USER) + return; +#endif + +#ifdef MSWIN_FIND_REPLACE + /* Don't process messages used by the dialog */ + if (s_findrep_hwnd != NULL && IsDialogMessage(s_findrep_hwnd, &msg)) + { + HandleMouseHide(msg.message, msg.lParam); + return; + } +#endif + + /* + * Check if it's a special key that we recognise. If not, call + * TranslateMessage(). + */ + if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN) + { + vk = (int) msg.wParam; + /* handle key after dead key, but ignore shift, alt and control */ + if (dead_key && vk != VK_SHIFT && vk != VK_MENU && vk != VK_CONTROL) + { + dead_key = 0; + /* handle non-alphabetic keys (ones that hopefully cannot generate + * umlaut-characters), unless when control is down */ + if (vk < 'A' || vk > 'Z' || (GetKeyState(VK_CONTROL) & 0x8000)) + { + MSG dm; + + dm.message = msg.message; + dm.hwnd = msg.hwnd; + dm.wParam = VK_SPACE; + MyTranslateMessage(&dm); /* generate dead character */ + if (vk != VK_SPACE) /* and send current character once more */ + PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam); + return; + } + } + + /* Check for CTRL-BREAK */ + if (vk == VK_CANCEL) + { + trash_input_buf(); + got_int = TRUE; + string[0] = Ctrl_C; + add_to_input_buf(string, 1); + } + + for (i = 0; special_keys[i].key_sym != 0; i++) + { + /* ignore VK_SPACE when ALT key pressed: system menu */ + if (special_keys[i].key_sym == vk + && (vk != VK_SPACE || !(GetKeyState(VK_MENU) & 0x8000))) + { +#ifdef FEAT_MENU + /* Check for <F10>: Windows selects the menu. Ignore it when + * 'winaltkeys' is "yes" or "menu" */ + if (vk == VK_F10 + && gui.menu_is_active + && (*p_wak == 'y' || *p_wak == 'm')) + break; +#endif + if (GetKeyState(VK_SHIFT) & 0x8000) + modifiers |= MOD_MASK_SHIFT; + /* + * Don't use caps-lock as shift, because these are special keys + * being considered here, and we only want letters to get + * shifted -- webb + */ + /* + if (GetKeyState(VK_CAPITAL) & 0x0001) + modifiers ^= MOD_MASK_SHIFT; + */ + if (GetKeyState(VK_CONTROL) & 0x8000) + modifiers |= MOD_MASK_CTRL; + if (GetKeyState(VK_MENU) & 0x8000) + modifiers |= MOD_MASK_ALT; + + if (special_keys[i].vim_code1 == NUL) + key = special_keys[i].vim_code0; + else + key = TO_SPECIAL(special_keys[i].vim_code0, + special_keys[i].vim_code1); + key = simplify_key(key, &modifiers); + if (key == CSI) + key = K_CSI; + + if (modifiers) + { + string[0] = CSI; + string[1] = KS_MODIFIER; + string[2] = modifiers; + add_to_input_buf(string, 3); + } + + if (IS_SPECIAL(key)) + { + string[0] = CSI; + string[1] = K_SECOND(key); + string[2] = K_THIRD(key); + add_to_input_buf(string, 3); + } + else + { + int len; + + /* Handle "key" as a Unicode character. */ + len = char_to_string(key, string, 40); + add_to_input_buf(string, len); + } + break; + } + } + if (special_keys[i].key_sym == 0) + { + /* Some keys need C-S- where they should only need C-. + * Ignore 0xff, Windows XP sends it when NUMLOCK has changed since + * system startup (Helmut Stiegler, 2003 Oct 3). */ + if (vk != 0xff + && (GetKeyState(VK_CONTROL) & 0x8000) + && !(GetKeyState(VK_SHIFT) & 0x8000) + && !(GetKeyState(VK_MENU) & 0x8000)) + { + /* CTRL-6 is '^'; Japanese keyboard maps '^' to vk == 0xDE */ + if (vk == '6' || MapVirtualKey(vk, 2) == (UINT)'^') + { + string[0] = Ctrl_HAT; + add_to_input_buf(string, 1); + } + /* vk == 0xBD AZERTY for CTRL-'-', but CTRL-[ for * QWERTY! */ + else if (vk == 0xBD) /* QWERTY for CTRL-'-' */ + { + string[0] = Ctrl__; + add_to_input_buf(string, 1); + } + /* CTRL-2 is '@'; Japanese keyboard maps '@' to vk == 0xC0 */ + else if (vk == '2' || MapVirtualKey(vk, 2) == (UINT)'@') + { + string[0] = Ctrl_AT; + add_to_input_buf(string, 1); + } + else + MyTranslateMessage(&msg); + } + else + MyTranslateMessage(&msg); + } + } +#ifdef FEAT_MBYTE_IME + else if (msg.message == WM_IME_NOTIFY) + _OnImeNotify(msg.hwnd, (DWORD)msg.wParam, (DWORD)msg.lParam); + else if (msg.message == WM_KEYUP && im_get_status()) + /* added for non-MS IME (Yasuhiro Matsumoto) */ + MyTranslateMessage(&msg); +#endif +#if !defined(FEAT_MBYTE_IME) && defined(GLOBAL_IME) +/* GIME_TEST */ + else if (msg.message == WM_IME_STARTCOMPOSITION) + { + POINT point; + + global_ime_set_font(&norm_logfont); + point.x = FILL_X(gui.col); + point.y = FILL_Y(gui.row); + MapWindowPoints(s_textArea, s_hwnd, &point, 1); + global_ime_set_position(&point); + } +#endif + +#ifdef FEAT_MENU + /* Check for <F10>: Windows selects the menu. Don't let Windows handle it + * when 'winaltkeys' is "no" */ + if (vk != VK_F10 || *p_wak != 'n') +#endif + DispatchMessage(&msg); +} + +/* + * 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(void) +{ + MSG msg; + + if (!s_busy_processing) + while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) + && !vim_is_input_buf_full()) + process_message(); +} + +/* + * 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) +{ + MSG msg; + int focus; + + s_timed_out = FALSE; + + if (wtime > 0) + { + /* Don't do anything while processing a (scroll) message. */ + if (s_busy_processing) + return FAIL; + s_wait_timer = (UINT)SetTimer(NULL, 0, (UINT)wtime, + (TIMERPROC)_OnTimer); + } + + allow_scrollbar = TRUE; + + focus = gui.in_focus; + while (!s_timed_out) + { + /* 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; + } + + if (s_need_activate) + { +#ifdef WIN32 + (void)SetForegroundWindow(s_hwnd); +#else + (void)SetActiveWindow(s_hwnd); +#endif + s_need_activate = FALSE; + } + + /* + * Don't use gui_mch_update() because then we will spin-lock until a + * char arrives, instead we use GetMessage() 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 -- webb + */ + process_message(); + + if (input_available()) + { + if (s_wait_timer != 0 && !s_timed_out) + { + KillTimer(NULL, s_wait_timer); + + /* Eat spurious WM_TIMER messages */ + while (PeekMessage(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) + ; + s_wait_timer = 0; + } + allow_scrollbar = FALSE; + return OK; + } + } + allow_scrollbar = FALSE; + return FAIL; +} + +/* + * 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) +{ + RECT rc; + + /* + * Clear one extra pixel at the far right, for when bold characters have + * spilled over to the window border. + * Note: FillRect() excludes right and bottom of rectangle. + */ + rc.left = FILL_X(col1); + rc.top = FILL_Y(row1); + rc.right = FILL_X(col2 + 1) + (col2 == Columns - 1); + rc.bottom = FILL_Y(row2 + 1); + clear_rect(&rc); +} + +/* + * Clear the whole text window. + */ + void +gui_mch_clear_all(void) +{ + RECT rc; + + rc.left = 0; + rc.top = 0; + rc.right = Columns * gui.char_width + 2 * gui.border_width; + rc.bottom = Rows * gui.char_height + 2 * gui.border_width; + clear_rect(&rc); +} +/* + * Menu stuff. + */ + + void +gui_mch_enable_menu(int flag) +{ +#ifdef FEAT_MENU + SetMenu(s_hwnd, flag ? s_menuBar : NULL); +#endif +} + + void +gui_mch_set_menu_pos( + int x, + int y, + int w, + int h) +{ + /* It will be in the right place anyway */ +} + +#if defined(FEAT_MENU) || defined(PROTO) +/* + * Make menu item hidden or not hidden + */ + void +gui_mch_menu_hidden( + vimmenu_T *menu, + int hidden) +{ + /* + * This doesn't do what we want. Hmm, just grey the menu items for now. + */ + /* + if (hidden) + EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_DISABLED); + else + EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED); + */ + gui_mch_menu_grey(menu, hidden); +} + +/* + * This is called after setting all the menus to grey/hidden or not. + */ + void +gui_mch_draw_menubar(void) +{ + DrawMenuBar(s_hwnd); +} +#endif /*FEAT_MENU*/ + +#ifndef PROTO +void +#ifdef VIMDLL +_export +#endif +_cdecl +SaveInst(HINSTANCE hInst) +{ + s_hinst = hInst; +} +#endif + +/* + * Return the RGB value of a pixel as a long. + */ + long_u +gui_mch_get_rgb(guicolor_T pixel) +{ + return (GetRValue(pixel) << 16) + (GetGValue(pixel) << 8) + + GetBValue(pixel); +} + +#if defined(FEAT_GUI_DIALOG) || defined(PROTO) +/* Convert pixels in X to dialog units */ + static WORD +PixelToDialogX(int numPixels) +{ + return (WORD)((numPixels * 4) / s_dlgfntwidth); +} + +/* Convert pixels in Y to dialog units */ + static WORD +PixelToDialogY(int numPixels) +{ + return (WORD)((numPixels * 8) / s_dlgfntheight); +} + +/* Return the width in pixels of the given text in the given DC. */ + static int +GetTextWidth(HDC hdc, char_u *str, int len) +{ + SIZE size; + + GetTextExtentPoint(hdc, str, len, &size); + return size.cx; +} + +#ifdef FEAT_MBYTE +/* + * Return the width in pixels of the given text in the given DC, taking care + * of 'encoding' to active codepage conversion. + */ + static int +GetTextWidthEnc(HDC hdc, char_u *str, int len) +{ + SIZE size; + WCHAR *wstr; + int n; + int wlen = len; + + if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) + { + /* 'encoding' differs from active codepage: convert text and use wide + * function */ + wstr = enc_to_ucs2(str, &wlen); + if (wstr != NULL) + { + n = GetTextExtentPointW(hdc, wstr, wlen, &size); + vim_free(wstr); + if (n) + return size.cx; + } + } + + return GetTextWidth(hdc, str, len); +} +#else +# define GetTextWidthEnc(h, s, l) GetTextWidth((h), (s), (l)) +#endif + +/* + * A quick little routine that will center one window over another, handy for + * dialog boxes. Taken from the Win32SDK samples. + */ + static BOOL +CenterWindow( + HWND hwndChild, + HWND hwndParent) +{ + RECT rChild, rParent; + int wChild, hChild, wParent, hParent; + int wScreen, hScreen, xNew, yNew; + HDC hdc; + + GetWindowRect(hwndChild, &rChild); + wChild = rChild.right - rChild.left; + hChild = rChild.bottom - rChild.top; + + /* If Vim is minimized put the window in the middle of the screen. */ + if (IsMinimized(hwndParent)) + { +#ifdef WIN16 + rParent.left = 0; + rParent.top = 0; + rParent.right = GetSystemMetrics(SM_CXSCREEN); + rParent.bottom = GetSystemMetrics(SM_CYFULLSCREEN); +#else + SystemParametersInfo(SPI_GETWORKAREA, 0, &rParent, 0); +#endif + } + else + GetWindowRect(hwndParent, &rParent); + wParent = rParent.right - rParent.left; + hParent = rParent.bottom - rParent.top; + + hdc = GetDC(hwndChild); + wScreen = GetDeviceCaps (hdc, HORZRES); + hScreen = GetDeviceCaps (hdc, VERTRES); + ReleaseDC(hwndChild, hdc); + + xNew = rParent.left + ((wParent - wChild) /2); + if (xNew < 0) + { + xNew = 0; + } + else if ((xNew+wChild) > wScreen) + { + xNew = wScreen - wChild; + } + + yNew = rParent.top + ((hParent - hChild) /2); + if (yNew < 0) + yNew = 0; + else if ((yNew+hChild) > hScreen) + yNew = hScreen - hChild; + + return SetWindowPos(hwndChild, NULL, xNew, yNew, 0, 0, + SWP_NOSIZE | SWP_NOZORDER); +} +#endif /* FEAT_GUI_DIALOG */ + +void +gui_mch_activate_window(void) +{ + (void)SetActiveWindow(s_hwnd); +} + +#if defined(FEAT_TOOLBAR) || defined(PROTO) + void +gui_mch_show_toolbar(int showit) +{ + if (s_toolbarhwnd == NULL) + return; + + if (showit) + ShowWindow(s_toolbarhwnd, SW_SHOW); + else + ShowWindow(s_toolbarhwnd, SW_HIDE); +} + +/* Then number of bitmaps is fixed. Exit is missing! */ +#define TOOLBAR_BITMAP_COUNT 31 + +#endif + +/* + * ":simalt" command. + */ + void +ex_simalt(exarg_T *eap) +{ + char_u *keys = eap->arg; + + PostMessage(s_hwnd, WM_SYSCOMMAND, (WPARAM)SC_KEYMENU, (LPARAM)0); + while (*keys) + { + if (*keys == '~') + *keys = ' '; /* for showing system menu */ + PostMessage(s_hwnd, WM_CHAR, (WPARAM)*keys, (LPARAM)0); + keys++; + } +} + +/* + * Create the find & replace dialogs. + * You can't have both at once: ":find" when replace is showing, destroys + * the replace dialog first, and the other way around. + */ +#ifdef MSWIN_FIND_REPLACE + static void +initialise_findrep(char_u *initial_string) +{ + int wword = FALSE; + int mcase = !p_ic; + char_u *entry_text; + + /* Get the search string to use. */ + entry_text = get_find_dialog_text(initial_string, &wword, &mcase); + + s_findrep_struct.hwndOwner = s_hwnd; + s_findrep_struct.Flags = FR_DOWN; + if (mcase) + s_findrep_struct.Flags |= FR_MATCHCASE; + if (wword) + s_findrep_struct.Flags |= FR_WHOLEWORD; + if (entry_text != NULL && *entry_text != NUL) + { + STRNCPY(s_findrep_struct.lpstrFindWhat, entry_text, + s_findrep_struct.wFindWhatLen); + s_findrep_struct.lpstrFindWhat[s_findrep_struct.wFindWhatLen - 1] = NUL; + s_findrep_struct.lpstrReplaceWith[0] = NUL; + } + vim_free(entry_text); +} +#endif + + void +gui_mch_find_dialog(exarg_T *eap) +{ +#ifdef MSWIN_FIND_REPLACE + if (s_findrep_msg != 0) + { + if (IsWindow(s_findrep_hwnd) && !s_findrep_is_find) + DestroyWindow(s_findrep_hwnd); + + if (!IsWindow(s_findrep_hwnd)) + { + initialise_findrep(eap->arg); + s_findrep_hwnd = FindText((LPFINDREPLACE) &s_findrep_struct); + } + + (void)SetWindowText(s_findrep_hwnd, + (LPCSTR)_("Find string (use '\\\\' to find a '\\')")); + (void)SetFocus(s_findrep_hwnd); + + s_findrep_is_find = TRUE; + } +#endif +} + + + void +gui_mch_replace_dialog(exarg_T *eap) +{ +#ifdef MSWIN_FIND_REPLACE + if (s_findrep_msg != 0) + { + if (IsWindow(s_findrep_hwnd) && s_findrep_is_find) + DestroyWindow(s_findrep_hwnd); + + if (!IsWindow(s_findrep_hwnd)) + { + initialise_findrep(eap->arg); + s_findrep_hwnd = ReplaceText((LPFINDREPLACE) &s_findrep_struct); + } + + (void)SetWindowText(s_findrep_hwnd, + (LPCSTR)_("Find & Replace (use '\\\\' to find a '\\')")); + (void)SetFocus(s_findrep_hwnd); + + s_findrep_is_find = FALSE; + } +#endif +} + + +/* + * Set visibility of the pointer. + */ + void +gui_mch_mousehide(int hide) +{ + if (hide != gui.pointer_hidden) + { + ShowCursor(!hide); + gui.pointer_hidden = hide; + } +} + +#ifdef FEAT_MENU + static void +gui_mch_show_popupmenu_at(vimmenu_T *menu, int x, int y) +{ + /* Unhide the mouse, we don't get move events here. */ + gui_mch_mousehide(FALSE); + + (void)TrackPopupMenu( + (HMENU)menu->submenu_id, + TPM_LEFTALIGN | TPM_LEFTBUTTON, + x, y, + (int)0, /*reserved param*/ + s_hwnd, + NULL); + /* + * NOTE: The pop-up menu can eat the mouse up event. + * We deal with this in normal.c. + */ +} +#endif + +/* + * Got a message when the system will go down. + */ + static void +_OnEndSession(void) +{ + getout_preserve_modified(1); +} + +/* + * Get this message when the user clicks on the cross in the top right corner + * of a Windows95 window. + */ + static void +_OnClose( + HWND hwnd) +{ + gui_shell_closed(); +} + +/* + * Get a message when the window is being destroyed. + */ + static void +_OnDestroy( + HWND hwnd) +{ +#ifdef WIN16_3DLOOK + Ctl3dUnregister(s_hinst); +#endif + if (!destroying) + _OnClose(hwnd); +} + + static void +_OnPaint( + HWND hwnd) +{ + if (!IsMinimized(hwnd)) + { + PAINTSTRUCT ps; + + out_flush(); /* make sure all output has been processed */ + (void)BeginPaint(hwnd, &ps); + +#ifdef FEAT_MBYTE + /* prevent multi-byte characters from misprinting on an invalid + * rectangle */ + if (has_mbyte) + { + RECT rect; + + GetClientRect(hwnd, &rect); + ps.rcPaint.left = rect.left; + ps.rcPaint.right = rect.right; + } +#endif + + if (!IsRectEmpty(&ps.rcPaint)) + gui_redraw(ps.rcPaint.left, ps.rcPaint.top, + ps.rcPaint.right - ps.rcPaint.left + 1, + ps.rcPaint.bottom - ps.rcPaint.top + 1); + EndPaint(hwnd, &ps); + } +} + + static void +_OnSize( + HWND hwnd, + UINT state, + int cx, + int cy) +{ + if (!IsMinimized(hwnd)) + { + gui_resize_shell(cx, cy); + +#ifdef FEAT_MENU + /* Menu bar may wrap differently now */ + gui_mswin_get_menu_height(TRUE); +#endif + } +} + + static void +_OnSetFocus( + HWND hwnd, + HWND hwndOldFocus) +{ + gui_focus_change(TRUE); + (void)MyWindowProc(hwnd, WM_SETFOCUS, (WPARAM)hwndOldFocus, 0); +} + + static void +_OnKillFocus( + HWND hwnd, + HWND hwndNewFocus) +{ + gui_focus_change(FALSE); + (void)MyWindowProc(hwnd, WM_KILLFOCUS, (WPARAM)hwndNewFocus, 0); +} + +/* + * Get a message when the user switches back to vim + */ +#ifdef WIN16 + static BOOL +#else + static LRESULT +#endif +_OnActivateApp( + HWND hwnd, + BOOL fActivate, +#ifdef WIN16 + HTASK dwThreadId +#else + DWORD dwThreadId +#endif + ) +{ + /* we call gui_focus_change() in _OnSetFocus() */ + /* gui_focus_change((int)fActivate); */ + return MyWindowProc(hwnd, WM_ACTIVATEAPP, fActivate, (DWORD)dwThreadId); +} + +#if defined(FEAT_WINDOWS) || defined(PROTO) + void +gui_mch_destroy_scrollbar(scrollbar_T *sb) +{ + DestroyWindow(sb->id); +} +#endif + +/* + * Get current x mouse coordinate in text window. + * Return -1 when unknown. + */ + int +gui_mch_get_mouse_x(void) +{ + RECT rct; + POINT mp; + + (void)GetWindowRect(s_textArea, &rct); + (void)GetCursorPos((LPPOINT)&mp); + return (int)(mp.x - rct.left); +} + +/* + * Get current y mouse coordinate in text window. + * Return -1 when unknown. + */ + int +gui_mch_get_mouse_y(void) +{ + RECT rct; + POINT mp; + + (void)GetWindowRect(s_textArea, &rct); + (void)GetCursorPos((LPPOINT)&mp); + return (int)(mp.y - rct.top); +} + +/* + * Move mouse pointer to character at (x, y). + */ + void +gui_mch_setmouse(int x, int y) +{ + RECT rct; + + (void)GetWindowRect(s_textArea, &rct); + (void)SetCursorPos(x + gui.border_offset + rct.left, + y + gui.border_offset + rct.top); +} + + static void +gui_mswin_get_valid_dimensions( + int w, + int h, + int *valid_w, + int *valid_h) +{ + int base_width, base_height; + + base_width = gui_get_base_width() + + GetSystemMetrics(SM_CXFRAME) * 2; + base_height = gui_get_base_height() + + GetSystemMetrics(SM_CYFRAME) * 2 + + GetSystemMetrics(SM_CYCAPTION) +#ifdef FEAT_MENU + + gui_mswin_get_menu_height(FALSE) +#endif + ; + *valid_w = base_width + + ((w - base_width) / gui.char_width) * gui.char_width; + *valid_h = base_height + + ((h - base_height) / gui.char_height) * gui.char_height; +} + + void +gui_mch_get_screen_dimensions(int *screen_w, int *screen_h) +{ + + *screen_w = GetSystemMetrics(SM_CXFULLSCREEN) + - GetSystemMetrics(SM_CXFRAME) * 2; + /* FIXME: dirty trick: Because the gui_get_base_height() doesn't include + * the menubar for MSwin, we subtract it from the screen height, so that + * the window size can be made to fit on the screen. */ + *screen_h = GetSystemMetrics(SM_CYFULLSCREEN) + - GetSystemMetrics(SM_CYFRAME) * 2 +#ifdef FEAT_MENU + - gui_mswin_get_menu_height(FALSE) +#endif + ; +} + + void +gui_mch_flash(int msec) +{ + RECT rc; + + /* + * Note: InvertRect() excludes right and bottom of rectangle. + */ + rc.left = 0; + rc.top = 0; + rc.right = gui.num_cols * gui.char_width; + rc.bottom = gui.num_rows * gui.char_height; + InvertRect(s_hdc, &rc); + gui_mch_flush(); /* make sure it's displayed */ + + ui_delay((long)msec, TRUE); /* wait for a few msec */ + + InvertRect(s_hdc, &rc); +} + +/* + * Return flags used for scrolling. + * The SW_INVALIDATE is required when part of the window is covered or + * off-screen. Refer to MS KB Q75236. + */ + static int +get_scroll_flags(void) +{ + HWND hwnd; + RECT rcVim, rcOther, rcDest; + + GetWindowRect(s_hwnd, &rcVim); + for (hwnd = s_hwnd; (hwnd = GetWindow(hwnd, GW_HWNDPREV)) != (HWND)0; ) + if (IsWindowVisible(hwnd)) + { + GetWindowRect(hwnd, &rcOther); + if (IntersectRect(&rcDest, &rcVim, &rcOther)) + return SW_INVALIDATE; + } + return 0; +} + +/* + * 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) +{ + RECT rc; + + rc.left = FILL_X(gui.scroll_region_left); + rc.right = FILL_X(gui.scroll_region_right + 1); + rc.top = FILL_Y(row); + rc.bottom = FILL_Y(gui.scroll_region_bot + 1); + + ScrollWindowEx(s_textArea, 0, -num_lines * gui.char_height, + &rc, &rc, NULL, NULL, get_scroll_flags()); + + UpdateWindow(s_textArea); + /* This seems to be required to avoid the cursor disappearing when + * scrolling such that the cursor ends up in the top-left character on + * the screen... But why? (Webb) */ + /* It's probably fixed by disabling drawing the cursor while scrolling. */ + /* gui.cursor_is_valid = FALSE; */ + + gui_clear_block(gui.scroll_region_bot - num_lines + 1, + gui.scroll_region_left, + gui.scroll_region_bot, gui.scroll_region_right); +} + +/* + * 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) +{ + RECT rc; + + rc.left = FILL_X(gui.scroll_region_left); + rc.right = FILL_X(gui.scroll_region_right + 1); + rc.top = FILL_Y(row); + rc.bottom = FILL_Y(gui.scroll_region_bot + 1); + /* The SW_INVALIDATE is required when part of the window is covered or + * off-screen. How do we avoid it when it's not needed? */ + ScrollWindowEx(s_textArea, 0, num_lines * gui.char_height, + &rc, &rc, NULL, NULL, get_scroll_flags()); + + UpdateWindow(s_textArea); + + gui_clear_block(row, gui.scroll_region_left, + row + num_lines - 1, gui.scroll_region_right); +} + + + void +gui_mch_exit(int rc) +{ + ReleaseDC(s_textArea, s_hdc); + DeleteObject(s_brush); + +#ifdef FEAT_TEAROFF + /* Unload the tearoff bitmap */ + (void)DeleteObject((HGDIOBJ)s_htearbitmap); +#endif + + /* Destroy our window (if we have one). */ + if (s_hwnd != NULL) + { + destroying = TRUE; /* ignore WM_DESTROY message now */ + DestroyWindow(s_hwnd); + } + +#ifdef GLOBAL_IME + global_ime_end(); +#endif +} + +/* + * 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) +{ + LOGFONT lf; + GuiFont font = NOFONT; + + /* Load the font */ + if (get_logfont(&lf, font_name, NULL)) + font = get_font_handle(&lf); + if (font == NOFONT) + return FAIL; + if (font_name == NULL) + font_name = lf.lfFaceName; +#if defined(FEAT_MBYTE_IME) || defined(GLOBAL_IME) + norm_logfont = lf; +#endif +#ifdef FEAT_MBYTE_IME + im_set_font(&lf); +#endif + gui_mch_free_font(gui.norm_font); + gui.norm_font = font; + current_font_height = lf.lfHeight; + GetFontSize(font); + hl_set_font_name(lf.lfFaceName); + + /* When setting 'guifont' to "*" replace it with the actual font name. */ + if (STRCMP(font_name, "*") == 0 && STRCMP(p_guifont, "*") == 0) + { + char *charset_name; + char_u *p; + + charset_name = charset_id2name((int)lf.lfCharSet); + p = alloc((unsigned)(strlen(lf.lfFaceName) + 20 + + (charset_name == NULL ? 0 : strlen(charset_name) + 2))); + if (p != NULL) + { + /* make a normal font string out of the lf thing:*/ + sprintf((char *)p, "%s:h%d", lf.lfFaceName, pixels_to_points( + lf.lfHeight < 0 ? -lf.lfHeight : lf.lfHeight, TRUE)); + vim_free(p_guifont); + p_guifont = p; + while (*p) + { + if (*p == ' ') + *p = '_'; + ++p; + } +#ifndef MSWIN16_FASTTEXT + if (lf.lfItalic) + STRCAT(p, ":i"); + if (lf.lfWeight >= FW_BOLD) + STRCAT(p, ":b"); +#endif + if (lf.lfUnderline) + STRCAT(p, ":u"); + if (lf.lfStrikeOut) + STRCAT(p, ":s"); + if (charset_name != NULL) + { + STRCAT(p, ":c"); + STRCAT(p, charset_name); + } + } + } + +#ifndef MSWIN16_FASTTEXT + gui_mch_free_font(gui.ital_font); + gui.ital_font = NOFONT; + gui_mch_free_font(gui.bold_font); + gui.bold_font = NOFONT; + gui_mch_free_font(gui.boldital_font); + gui.boldital_font = NOFONT; + + if (!lf.lfItalic) + { + lf.lfItalic = TRUE; + gui.ital_font = get_font_handle(&lf); + lf.lfItalic = FALSE; + } + if (lf.lfWeight < FW_BOLD) + { + lf.lfWeight = FW_BOLD; + gui.bold_font = get_font_handle(&lf); + if (!lf.lfItalic) + { + lf.lfItalic = TRUE; + gui.boldital_font = get_font_handle(&lf); + } + } +#endif + + return OK; +} + +/* + * Return TRUE if the GUI window is maximized, filling the whole screen. + */ + int +gui_mch_maximized() +{ + return IsZoomed(s_hwnd); +} + +/* + * Called when the font changed while the window is maximized. Compute the + * new Rows and Columns. This is like resizing the window. + */ + void +gui_mch_newfont() +{ + RECT rect; + + GetWindowRect(s_hwnd, &rect); + gui_resize_shell(rect.right - rect.left + - GetSystemMetrics(SM_CXFRAME) * 2, + rect.bottom - rect.top + - GetSystemMetrics(SM_CYFRAME) * 2 + - GetSystemMetrics(SM_CYCAPTION) +#ifdef FEAT_MENU + - gui_mswin_get_menu_height(FALSE) +#endif + ); +} + +/* + * Set the window title + */ + void +gui_mch_settitle( + char_u *title, + char_u *icon) +{ +#ifdef FEAT_MBYTE + if (title != NULL && enc_codepage >= 0 && enc_codepage != (int)GetACP()) + { + WCHAR *wbuf; + int n; + + /* Convert the title from 'encoding' to ucs2. */ + wbuf = (WCHAR *)enc_to_ucs2(title, NULL); + if (wbuf != NULL) + { + n = SetWindowTextW(s_hwnd, wbuf); + vim_free(wbuf); + if (n != 0 || GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + return; + /* Retry with non-wide function (for Windows 98). */ + } + } +#endif + SetWindowText(s_hwnd, (LPCSTR)(title == NULL ? "VIM" : (char *)title)); +} + +#ifdef FEAT_MOUSESHAPE +/* Table for shape IDCs. Keep in sync with the mshape_names[] table in + * misc2.c! */ +static LPCSTR mshape_idcs[] = +{ + MAKEINTRESOURCE(IDC_ARROW), /* arrow */ + MAKEINTRESOURCE(0), /* blank */ + MAKEINTRESOURCE(IDC_IBEAM), /* beam */ + MAKEINTRESOURCE(IDC_SIZENS), /* updown */ + MAKEINTRESOURCE(IDC_SIZENS), /* udsizing */ + MAKEINTRESOURCE(IDC_SIZEWE), /* leftright */ + MAKEINTRESOURCE(IDC_SIZEWE), /* lrsizing */ + MAKEINTRESOURCE(IDC_WAIT), /* busy */ +#ifdef WIN3264 + MAKEINTRESOURCE(IDC_NO), /* no */ +#else + MAKEINTRESOURCE(IDC_ICON), /* no */ +#endif + MAKEINTRESOURCE(IDC_ARROW), /* crosshair */ + MAKEINTRESOURCE(IDC_ARROW), /* hand1 */ + MAKEINTRESOURCE(IDC_ARROW), /* hand2 */ + MAKEINTRESOURCE(IDC_ARROW), /* pencil */ + MAKEINTRESOURCE(IDC_ARROW), /* question */ + MAKEINTRESOURCE(IDC_ARROW), /* right-arrow */ + MAKEINTRESOURCE(IDC_UPARROW), /* up-arrow */ + MAKEINTRESOURCE(IDC_ARROW) /* last one */ +}; + + void +mch_set_mouse_shape(int shape) +{ + LPCSTR idc; + + if (shape == MSHAPE_HIDE) + ShowCursor(FALSE); + else + { + if (shape >= MSHAPE_NUMBERED) + idc = MAKEINTRESOURCE(IDC_ARROW); + else + idc = mshape_idcs[shape]; +#ifdef _WIN64 + SetClassLongPtr(s_textArea, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, idc)); +#else +# ifdef WIN32 + SetClassLong(s_textArea, GCL_HCURSOR, (LONG)LoadCursor(NULL, idc)); +# else + SetClassWord(s_textArea, GCW_HCURSOR, (WORD)LoadCursor(NULL, idc)); +# endif +#endif + if (!p_mh) + { + POINT mp; + + /* Set the position to make it redrawn with the new shape. */ + (void)GetCursorPos((LPPOINT)&mp); + (void)SetCursorPos(mp.x, mp.y); + ShowCursor(TRUE); + } + } +} +#endif + +#ifdef FEAT_BROWSE +/* + * The file browser exists in two versions: with "W" uses wide characters, + * without "W" the current codepage. When FEAT_MBYTE is defined and on + * Windows NT/2000/XP the "W" functions are used. + */ + +# if defined(FEAT_MBYTE) && defined(WIN3264) +/* + * Wide version of convert_filter(). Keep in sync! + */ + static WCHAR * +convert_filterW(char_u *s) +{ + WCHAR *res; + unsigned s_len = (unsigned)STRLEN(s); + unsigned i; + + res = (WCHAR *)alloc((s_len + 3) * sizeof(WCHAR)); + if (res != NULL) + { + for (i = 0; i < s_len; ++i) + if (s[i] == '\t' || s[i] == '\n') + res[i] = '\0'; + else + res[i] = s[i]; + res[s_len] = NUL; + /* Add two extra NULs to make sure it's properly terminated. */ + res[s_len + 1] = NUL; + res[s_len + 2] = NUL; + } + return res; +} + +/* + * Wide version of gui_mch_browse(). Keep in sync! + */ + static char_u * +gui_mch_browseW( + int saving, + char_u *title, + char_u *dflt, + char_u *ext, + char_u *initdir, + char_u *filter) +{ + /* We always use the wide function. This means enc_to_ucs2() must work, + * otherwise it fails miserably! */ + OPENFILENAMEW fileStruct; + WCHAR fileBuf[MAXPATHL]; + WCHAR *wp; + int i; + WCHAR *titlep = NULL; + WCHAR *extp = NULL; + WCHAR *initdirp = NULL; + WCHAR *filterp; + char_u *p; + + if (dflt == NULL) + fileBuf[0] = NUL; + else + { + wp = enc_to_ucs2(dflt, NULL); + if (wp == NULL) + fileBuf[0] = NUL; + else + { + for (i = 0; wp[i] != NUL && i < MAXPATHL - 1; ++i) + fileBuf[i] = wp[i]; + fileBuf[i] = NUL; + vim_free(wp); + } + } + + /* Convert the filter to Windows format. */ + filterp = convert_filterW(filter); + + memset(&fileStruct, 0, sizeof(OPENFILENAMEW)); +#ifdef OPENFILENAME_SIZE_VERSION_400 + /* be compatible with Windows NT 4.0 */ + /* TODO: what to use for OPENFILENAMEW??? */ + fileStruct.lStructSize = sizeof(OPENFILENAME_SIZE_VERSION_400); +#else + fileStruct.lStructSize = sizeof(fileStruct); +#endif + + if (title != NULL) + titlep = enc_to_ucs2(title, NULL); + fileStruct.lpstrTitle = titlep; + + if (ext != NULL) + extp = enc_to_ucs2(ext, NULL); + fileStruct.lpstrDefExt = extp; + + fileStruct.lpstrFile = fileBuf; + fileStruct.nMaxFile = MAXPATHL; + fileStruct.lpstrFilter = filterp; + fileStruct.hwndOwner = s_hwnd; /* main Vim window is owner*/ + /* has an initial dir been specified? */ + if (initdir != NULL && *initdir != NUL) + { + /* Must have backslashes here, no matter what 'shellslash' says */ + initdirp = enc_to_ucs2(initdir, NULL); + if (initdirp != NULL) + { + for (wp = initdirp; *wp != NUL; ++wp) + if (*wp == '/') + *wp = '\\'; + } + fileStruct.lpstrInitialDir = initdirp; + } + + /* + * TODO: Allow selection of multiple files. Needs another arg to this + * function to ask for it, and need to use OFN_ALLOWMULTISELECT below. + * Also, should we use OFN_FILEMUSTEXIST when opening? Vim can edit on + * files that don't exist yet, so I haven't put it in. What about + * OFN_PATHMUSTEXIST? + * Don't use OFN_OVERWRITEPROMPT, Vim has its own ":confirm" dialog. + */ + fileStruct.Flags = (OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY); +#ifdef FEAT_SHORTCUT + if (curbuf->b_p_bin) + fileStruct.Flags |= OFN_NODEREFERENCELINKS; +#endif + if (saving) + { + if (!GetSaveFileNameW(&fileStruct)) + return NULL; + } + else + { + if (!GetOpenFileNameW(&fileStruct)) + return NULL; + } + + vim_free(filterp); + vim_free(initdirp); + vim_free(titlep); + vim_free(extp); + + /* Convert from UCS2 to 'encoding'. */ + p = ucs2_to_enc(fileBuf, NULL); + if (p != NULL) + /* when out of memory we get garbage for non-ASCII chars */ + STRCPY(fileBuf, p); + vim_free(p); + + /* Give focus back to main window (when using MDI). */ + SetFocus(s_hwnd); + + /* Shorten the file name if possible */ + mch_dirname(IObuff, IOSIZE); + p = shorten_fname((char_u *)fileBuf, IObuff); + if (p == NULL) + p = (char_u *)fileBuf; + return vim_strsave(p); +} +# endif /* FEAT_MBYTE */ + + +/* + * Convert the string s to the proper format for a filter string by replacing + * the \t and \n delimeters with \0. + * Returns the converted string in allocated memory. + * + * Keep in sync with convert_filterW() above! + */ + static char_u * +convert_filter(char_u *s) +{ + char_u *res; + unsigned s_len = (unsigned)STRLEN(s); + unsigned i; + + res = alloc(s_len + 3); + if (res != NULL) + { + for (i = 0; i < s_len; ++i) + if (s[i] == '\t' || s[i] == '\n') + res[i] = '\0'; + else + res[i] = s[i]; + res[s_len] = NUL; + /* Add two extra NULs to make sure it's properly terminated. */ + res[s_len + 1] = NUL; + res[s_len + 2] = NUL; + } + return res; +} + +/* + * Pop open a file browser and return the file selected, in allocated memory, + * or NULL if Cancel is hit. + * saving - TRUE if the file will be saved to, FALSE if it will be opened. + * title - Title message for the file browser dialog. + * dflt - Default name of file. + * ext - Default extension to be added to files without extensions. + * initdir - directory in which to open the browser (NULL = current dir) + * filter - Filter for matched files to choose from. + * + * Keep in sync with gui_mch_browseW() above! + */ + char_u * +gui_mch_browse( + int saving, + char_u *title, + char_u *dflt, + char_u *ext, + char_u *initdir, + char_u *filter) +{ + OPENFILENAME fileStruct; + char_u fileBuf[MAXPATHL]; + char_u *initdirp = NULL; + char_u *filterp; + char_u *p; + +# if defined(FEAT_MBYTE) && defined(WIN3264) + if (os_version.dwPlatformId == VER_PLATFORM_WIN32_NT) + return gui_mch_browseW(saving, title, dflt, ext, initdir, filter); +# endif + + if (dflt == NULL) + fileBuf[0] = NUL; + else + { + STRNCPY(fileBuf, dflt, MAXPATHL - 1); + fileBuf[MAXPATHL - 1] = NUL; + } + + /* Convert the filter to Windows format. */ + filterp = convert_filter(filter); + + memset(&fileStruct, 0, sizeof(OPENFILENAME)); +#ifdef OPENFILENAME_SIZE_VERSION_400 + /* be compatible with Windows NT 4.0 */ + fileStruct.lStructSize = sizeof(OPENFILENAME_SIZE_VERSION_400); +#else + fileStruct.lStructSize = sizeof(fileStruct); +#endif + + fileStruct.lpstrTitle = title; + fileStruct.lpstrDefExt = ext; + + fileStruct.lpstrFile = fileBuf; + fileStruct.nMaxFile = MAXPATHL; + fileStruct.lpstrFilter = filterp; + fileStruct.hwndOwner = s_hwnd; /* main Vim window is owner*/ + /* has an initial dir been specified? */ + if (initdir != NULL && *initdir != NUL) + { + /* Must have backslashes here, no matter what 'shellslash' says */ + initdirp = vim_strsave(initdir); + if (initdirp != NULL) + for (p = initdirp; *p != NUL; ++p) + if (*p == '/') + *p = '\\'; + fileStruct.lpstrInitialDir = initdirp; + } + + /* + * TODO: Allow selection of multiple files. Needs another arg to this + * function to ask for it, and need to use OFN_ALLOWMULTISELECT below. + * Also, should we use OFN_FILEMUSTEXIST when opening? Vim can edit on + * files that don't exist yet, so I haven't put it in. What about + * OFN_PATHMUSTEXIST? + * Don't use OFN_OVERWRITEPROMPT, Vim has its own ":confirm" dialog. + */ + fileStruct.Flags = (OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY); +#ifdef FEAT_SHORTCUT + if (curbuf->b_p_bin) + fileStruct.Flags |= OFN_NODEREFERENCELINKS; +#endif + if (saving) + { + if (!GetSaveFileName(&fileStruct)) + return NULL; + } + else + { + if (!GetOpenFileName(&fileStruct)) + return NULL; + } + + vim_free(filterp); + vim_free(initdirp); + + /* Give focus back to main window (when using MDI). */ + SetFocus(s_hwnd); + + /* Shorten the file name if possible */ + mch_dirname(IObuff, IOSIZE); + p = shorten_fname((char_u *)fileBuf, IObuff); + if (p == NULL) + p = (char_u *)fileBuf; + return vim_strsave(p); +} +#endif /* FEAT_BROWSE */ + + static void +_OnDropFiles( + HWND hwnd, + HDROP hDrop) +{ +#ifdef FEAT_WINDOWS +#ifdef WIN3264 +# define BUFPATHLEN _MAX_PATH +# define DRAGQVAL 0xFFFFFFFF +#else +# define BUFPATHLEN MAXPATHL +# define DRAGQVAL 0xFFFF +#endif +#ifdef FEAT_MBYTE + WCHAR wszFile[BUFPATHLEN]; +#endif + char szFile[BUFPATHLEN]; + UINT cFiles = DragQueryFile(hDrop, DRAGQVAL, NULL, 0); + UINT i; + char_u **fnames; + POINT pt; + int_u modifiers = 0; + + /* TRACE("_OnDropFiles: %d files dropped\n", cFiles); */ + + /* Obtain dropped position */ + DragQueryPoint(hDrop, &pt); + MapWindowPoints(s_hwnd, s_textArea, &pt, 1); + +# ifdef FEAT_VISUAL + reset_VIsual(); +# endif + + fnames = (char_u **)alloc(cFiles * sizeof(char_u *)); + + if (fnames != NULL) + for (i = 0; i < cFiles; ++i) + { +#ifdef FEAT_MBYTE + if (DragQueryFileW(hDrop, i, wszFile, BUFPATHLEN) > 0) + fnames[i] = ucs2_to_enc(wszFile, NULL); + else +#endif + { + DragQueryFile(hDrop, i, szFile, BUFPATHLEN); + fnames[i] = vim_strsave(szFile); + } + } + + DragFinish(hDrop); + + if (fnames != NULL) + { + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) + modifiers |= MOUSE_SHIFT; + if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) + modifiers |= MOUSE_CTRL; + if ((GetKeyState(VK_MENU) & 0x8000) != 0) + modifiers |= MOUSE_ALT; + + gui_handle_drop(pt.x, pt.y, modifiers, fnames, cFiles); + + s_need_activate = TRUE; + } +#endif +} + + static int +_OnScroll( + HWND hwnd, + HWND hwndCtl, + UINT code, + int pos) +{ + static UINT prev_code = 0; /* code of previous call */ + scrollbar_T *sb, *sb_info; + long val; + int dragging = FALSE; + int dont_scroll_save = dont_scroll; +#ifndef WIN3264 + int nPos; +#else + SCROLLINFO si; + + si.cbSize = sizeof(si); + si.fMask = SIF_POS; +#endif + + sb = gui_mswin_find_scrollbar(hwndCtl); + if (sb == NULL) + return 0; + + if (sb->wp != NULL) /* Left or right scrollbar */ + { + /* + * Careful: need to get scrollbar info out of first (left) scrollbar + * for window, but keep real scrollbar too because we must pass it to + * gui_drag_scrollbar(). + */ + sb_info = &sb->wp->w_scrollbars[0]; + } + else /* Bottom scrollbar */ + sb_info = sb; + val = sb_info->value; + + switch (code) + { + case SB_THUMBTRACK: + val = pos; + dragging = TRUE; + if (sb->scroll_shift > 0) + val <<= sb->scroll_shift; + break; + case SB_LINEDOWN: + val++; + break; + case SB_LINEUP: + val--; + break; + case SB_PAGEDOWN: + val += (sb_info->size > 2 ? sb_info->size - 2 : 1); + break; + case SB_PAGEUP: + val -= (sb_info->size > 2 ? sb_info->size - 2 : 1); + break; + case SB_TOP: + val = 0; + break; + case SB_BOTTOM: + val = sb_info->max; + break; + case SB_ENDSCROLL: + if (prev_code == SB_THUMBTRACK) + { + /* + * "pos" only gives us 16-bit data. In case of large file, + * use GetScrollPos() which returns 32-bit. Unfortunately it + * is not valid while the scrollbar is being dragged. + */ + val = GetScrollPos(hwndCtl, SB_CTL); + if (sb->scroll_shift > 0) + val <<= sb->scroll_shift; + } + break; + + default: + /* TRACE("Unknown scrollbar event %d\n", code); */ + return 0; + } + prev_code = code; + +#ifdef WIN3264 + si.nPos = (sb->scroll_shift > 0) ? val >> sb->scroll_shift : val; + SetScrollInfo(hwndCtl, SB_CTL, &si, TRUE); +#else + nPos = (sb->scroll_shift > 0) ? val >> sb->scroll_shift : val; + SetScrollPos(hwndCtl, SB_CTL, nPos, TRUE); +#endif + + /* + * When moving a vertical scrollbar, move the other vertical scrollbar too. + */ + if (sb->wp != NULL) + { + scrollbar_T *sba = sb->wp->w_scrollbars; + HWND id = sba[ (sb == sba + SBAR_LEFT) ? SBAR_RIGHT : SBAR_LEFT].id; + +#ifdef WIN3264 + SetScrollInfo(id, SB_CTL, &si, TRUE); +#else + SetScrollPos(id, SB_CTL, nPos, TRUE); +#endif + } + + /* Don't let us be interrupted here by another message. */ + s_busy_processing = TRUE; + + /* When "allow_scrollbar" is FALSE still need to remember the new + * position, but don't actually scroll by setting "dont_scroll". */ + dont_scroll = !allow_scrollbar; + + gui_drag_scrollbar(sb, val, dragging); + + s_busy_processing = FALSE; + dont_scroll = dont_scroll_save; + + return 0; +} + +/* + * Get command line arguments. + * Use "prog" as the name of the program and "cmdline" as the arguments. + * Copy the arguments to allocated memory. + * Return the number of arguments (including program name). + * Return pointers to the arguments in "argvp". + * Return pointer to buffer in "tofree". + * Returns zero when out of memory. + */ + int +get_cmd_args(char *prog, char *cmdline, char ***argvp, char **tofree) +{ + int i; + char *p; + char *progp; + char *pnew = NULL; + char *newcmdline; + int inquote; + int argc; + char **argv = NULL; + int round; + + /* Handle the program name. Remove the ".exe" extension, and find the 1st + * non-space. */ + p = strrchr(prog, '.'); + if (p != NULL) + *p = NUL; + for (progp = prog; *progp == ' '; ++progp) + ; + + /* The command line is copied to allocated memory, so that we can change + * it. Add the size of the string, the separating NUL and a terminating + * NUL. */ + newcmdline = malloc(STRLEN(cmdline) + STRLEN(progp) + 2); + if (newcmdline == NULL) + return 0; + + /* + * First round: count the number of arguments ("pnew" == NULL). + * Second round: produce the arguments. + */ + for (round = 1; round <= 2; ++round) + { + /* First argument is the program name. */ + if (pnew != NULL) + { + argv[0] = pnew; + strcpy(pnew, progp); + pnew += strlen(pnew); + *pnew++ = NUL; + } + + /* + * Isolate each argument and put it in argv[]. + */ + p = cmdline; + argc = 1; + while (*p != NUL) + { + inquote = FALSE; + if (pnew != NULL) + argv[argc] = pnew; + ++argc; + while (*p != NUL && (inquote || (*p != ' ' && *p != '\t'))) + { + /* Backslashes are only special when followed by a double + * quote. */ + i = strspn(p, "\\"); + if (p[i] == '"') + { + /* Halve the number of backslashes. */ + if (i > 1 && pnew != NULL) + { + memset(pnew, '\\', i / 2); + pnew += i / 2; + } + + /* Even nr of backslashes toggles quoting, uneven copies + * the double quote. */ + if ((i & 1) == 0) + inquote = !inquote; + else if (pnew != NULL) + *pnew++ = '"'; + p += i + 1; + } + else if (i > 0) + { + /* Copy span of backslashes unmodified. */ + if (pnew != NULL) + { + memset(pnew, '\\', i); + pnew += i; + } + p += i; + } + else + { + if (pnew != NULL) + *pnew++ = *p; + ++p; + } + } + + if (pnew != NUL) + *pnew++ = NUL; + while (*p == ' ' || *p == '\t') + ++p; /* advance until a non-space */ + } + + if (round == 1) + { + argv = (char **)malloc((argc + 1) * sizeof(char *)); + if (argv == NULL ) + return 0; /* malloc error */ + pnew = newcmdline; + } + } + + argv[argc] = NULL; /* NULL-terminated list */ + + *argvp = argv; + return argc; +} |