/* vi:set ts=8 sts=4 sw=4 noet: * * 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. */ /* * Windows GUI. * * GUI support for Microsoft Windows, aka Win32. Also for Win64. * * George V. Reilly wrote the original Win32 GUI. * Robert Webb reworked it to use the existing GUI stuff and added menu, * scrollbars, etc. * * Note: Clipboard stuff, for cutting and pasting text to other windows, is in * winclip.c. (It can also be done from the terminal version). * * TODO: Some of the function signatures ought to be updated for Win64; * e.g., replace LONG with LONG_PTR, etc. */ #include "vim.h" #if defined(FEAT_DIRECTX) # include "gui_dwrite.h" #endif // values for "dead_key" #define DEAD_KEY_OFF 0 // no dead key #define DEAD_KEY_SET_DEFAULT 1 // dead key pressed #define DEAD_KEY_TRANSIENT_IN_ON_CHAR 2 // wait for next key press #define DEAD_KEY_SKIP_ON_CHAR 3 // skip next _OnChar() #if defined(FEAT_DIRECTX) static DWriteContext *s_dwc = NULL; static int s_directx_enabled = 0; static int s_directx_load_attempted = 0; # define IS_ENABLE_DIRECTX() (s_directx_enabled && s_dwc != NULL && enc_utf8) static int directx_enabled(void); static void directx_binddc(void); #endif #ifdef FEAT_MENU static int gui_mswin_get_menu_height(int fix_window); #else # define gui_mswin_get_menu_height(fix_window) 0 #endif #if defined(FEAT_RENDER_OPTIONS) || defined(PROTO) int gui_mch_set_rendering_options(char_u *s) { # ifdef FEAT_DIRECTX char_u *p, *q; int dx_enable = 0; int dx_flags = 0; float dx_gamma = 0.0f; float dx_contrast = 0.0f; float dx_level = 0.0f; int dx_geom = 0; int dx_renmode = 0; int dx_taamode = 0; // parse string as rendering options. for (p = s; p != NULL && *p != NUL; ) { char_u item[256]; char_u name[128]; char_u value[128]; copy_option_part(&p, item, sizeof(item), ","); if (p == NULL) break; q = &item[0]; copy_option_part(&q, name, sizeof(name), ":"); if (q == NULL) return FAIL; copy_option_part(&q, value, sizeof(value), ":"); if (STRCMP(name, "type") == 0) { if (STRCMP(value, "directx") == 0) dx_enable = 1; else return FAIL; } else if (STRCMP(name, "gamma") == 0) { dx_flags |= 1 << 0; dx_gamma = (float)atof((char *)value); } else if (STRCMP(name, "contrast") == 0) { dx_flags |= 1 << 1; dx_contrast = (float)atof((char *)value); } else if (STRCMP(name, "level") == 0) { dx_flags |= 1 << 2; dx_level = (float)atof((char *)value); } else if (STRCMP(name, "geom") == 0) { dx_flags |= 1 << 3; dx_geom = atoi((char *)value); if (dx_geom < 0 || dx_geom > 2) return FAIL; } else if (STRCMP(name, "renmode") == 0) { dx_flags |= 1 << 4; dx_renmode = atoi((char *)value); if (dx_renmode < 0 || dx_renmode > 6) return FAIL; } else if (STRCMP(name, "taamode") == 0) { dx_flags |= 1 << 5; dx_taamode = atoi((char *)value); if (dx_taamode < 0 || dx_taamode > 3) return FAIL; } else if (STRCMP(name, "scrlines") == 0) { // Deprecated. Simply ignore it. } else return FAIL; } if (!gui.in_use) return OK; // only checking the syntax of the value // Enable DirectX/DirectWrite if (dx_enable) { if (!directx_enabled()) return FAIL; DWriteContext_SetRenderingParams(s_dwc, NULL); if (dx_flags) { DWriteRenderingParams param; DWriteContext_GetRenderingParams(s_dwc, ¶m); if (dx_flags & (1 << 0)) param.gamma = dx_gamma; if (dx_flags & (1 << 1)) param.enhancedContrast = dx_contrast; if (dx_flags & (1 << 2)) param.clearTypeLevel = dx_level; if (dx_flags & (1 << 3)) param.pixelGeometry = dx_geom; if (dx_flags & (1 << 4)) param.renderingMode = dx_renmode; if (dx_flags & (1 << 5)) param.textAntialiasMode = dx_taamode; DWriteContext_SetRenderingParams(s_dwc, ¶m); } } s_directx_enabled = dx_enable; return OK; # else return FAIL; # endif } #endif /* * These are new in Windows ME/XP, only defined in recent compilers. */ #ifndef HANDLE_WM_XBUTTONUP # define HANDLE_WM_XBUTTONUP(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L) #endif #ifndef HANDLE_WM_XBUTTONDOWN # define HANDLE_WM_XBUTTONDOWN(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), FALSE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L) #endif #ifndef HANDLE_WM_XBUTTONDBLCLK # define HANDLE_WM_XBUTTONDBLCLK(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), TRUE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L) #endif #include "version.h" // used by dialog box routine for default title #ifdef DEBUG # include #endif // cproto fails on missing include files #ifndef PROTO # ifndef __MINGW32__ # include # endif # include # include #endif // PROTO #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_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 Shell Dlg" #define DLG_FONT_POINT_SIZE 8 #define DLG_MIN_MAX_WIDTH 400 #define DLG_MIN_MAX_HEIGHT 400 #define DLG_NONBUTTON_CONTROL 5000 // First ID of non-button controls #ifndef WM_DPICHANGED # define WM_DPICHANGED 0x02E0 #endif #ifndef WM_MOUSEHWHEEL # define WM_MOUSEHWHEEL 0x020E #endif #ifndef SPI_GETWHEELSCROLLCHARS # define SPI_GETWHEELSCROLLCHARS 0x006C #endif #ifndef SPI_SETWHEELSCROLLCHARS # define SPI_SETWHEELSCROLLCHARS 0x006D #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 WINAPI # undef _cdecl # 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 LOGFONTW[]; 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; typedef int HRESULT; # undef MSG typedef int MSG; typedef int NEWTEXTMETRIC; typedef int NMHDR; typedef int OSVERSIONINFO; typedef int PWORD; typedef int RECT; typedef int SIZE; 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; typedef int WNDPROC; typedef int UINT_PTR; typedef int COLORREF; typedef int HCURSOR; #endif static void _OnPaint(HWND hwnd); static void fill_rect(const RECT *rcp, HBRUSH hbr, COLORREF color); static void clear_rect(RECT *rcp); 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 interrupted 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; static FINDREPLACEW 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 HWND s_hwnd = NULL; static HDC s_hdc = NULL; static HBRUSH s_brush = NULL; #ifdef FEAT_TOOLBAR static HWND s_toolbarhwnd = NULL; static WNDPROC s_toolbar_wndproc = NULL; #endif #ifdef FEAT_GUI_TABLINE static HWND s_tabhwnd = NULL; static WNDPROC s_tabline_wndproc = NULL; static int showing_tabline = 0; #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; #ifndef _DPI_AWARENESS_CONTEXTS_ typedef HANDLE DPI_AWARENESS_CONTEXT; typedef enum DPI_AWARENESS { DPI_AWARENESS_INVALID = -1, DPI_AWARENESS_UNAWARE = 0, DPI_AWARENESS_SYSTEM_AWARE = 1, DPI_AWARENESS_PER_MONITOR_AWARE = 2 } DPI_AWARENESS; # define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) # define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) # define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) # define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) # define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5) #endif #define DEFAULT_DPI 96 static int s_dpi = DEFAULT_DPI; static BOOL s_in_dpichanged = FALSE; static DPI_AWARENESS s_process_dpi_aware = DPI_AWARENESS_INVALID; static UINT (WINAPI *pGetDpiForSystem)(void) = NULL; static UINT (WINAPI *pGetDpiForWindow)(HWND hwnd) = NULL; static int (WINAPI *pGetSystemMetricsForDpi)(int, UINT) = NULL; //static INT (WINAPI *pGetWindowDpiAwarenessContext)(HWND hwnd) = NULL; static DPI_AWARENESS_CONTEXT (WINAPI *pSetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext) = NULL; static DPI_AWARENESS (WINAPI *pGetAwarenessFromDpiAwarenessContext)(DPI_AWARENESS_CONTEXT) = NULL; static UINT WINAPI stubGetDpiForSystem(void) { HWND hwnd = GetDesktopWindow(); HDC hdc = GetWindowDC(hwnd); UINT dpi = GetDeviceCaps(hdc, LOGPIXELSY); ReleaseDC(hwnd, hdc); return dpi; } static int WINAPI stubGetSystemMetricsForDpi(int nIndex, UINT dpi UNUSED) { return GetSystemMetrics(nIndex); } static int adjust_fontsize_by_dpi(int size) { return size * s_dpi / (int)pGetDpiForSystem(); } static int adjust_by_system_dpi(int size) { return size * (int)pGetDpiForSystem() / DEFAULT_DPI; } #if defined(FEAT_DIRECTX) static int directx_enabled(void) { if (s_dwc != NULL) return 1; else if (s_directx_load_attempted) return 0; // load DirectX DWrite_Init(); s_directx_load_attempted = 1; s_dwc = DWriteContext_Open(); directx_binddc(); return s_dwc != NULL ? 1 : 0; } static void directx_binddc(void) { if (s_textArea == NULL) return; RECT rect; GetClientRect(s_textArea, &rect); DWriteContext_BindDC(s_dwc, s_hdc, &rect); } #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; // s_getting_focus is set when we got focus but didn't see mouse-up event yet, // so don't reset s_button_pending. static int s_getting_focus = FALSE; static int s_x_pending; static int s_y_pending; static UINT s_kFlags_pending; static UINT_PTR s_wait_timer = 0; // Timer for get char from user static int s_timed_out = FALSE; static int dead_key = DEAD_KEY_OFF; static UINT surrogate_pending_ch = 0; // 0: no surrogate pending, // else a high surrogate #ifdef FEAT_BEVAL_GUI // balloon-eval WM_NOTIFY_HANDLER static void Handle_WM_Notify(HWND hwnd, LPNMHDR pnmh); static void track_user_activity(UINT uMsg); #endif /* * For control IME. * * These LOGFONTW used for IME. */ #ifdef FEAT_MBYTE_IME // holds LOGFONTW for 'guifontwide' if available, otherwise 'guifont' static LOGFONTW norm_logfont; // holds LOGFONTW for 'guifont' always. static LOGFONTW sub_logfont; #endif #ifdef FEAT_MBYTE_IME static LRESULT _OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData); #endif #if defined(FEAT_BROWSE) static char_u *convert_filter(char_u *s); #endif #ifdef DEBUG_PRINT_ERROR /* * 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 /* * 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_PTR blink_timer = 0; int gui_mch_is_blinking(void) { return blink_state != BLINK_NONE; } int gui_mch_is_blink_off(void) { return blink_state == BLINK_OFF; } void gui_mch_set_blinking(long wait, long on, long off) { blink_waittime = wait; blink_ontime = on; blink_offtime = off; } static VOID CALLBACK _OnBlinkTimer( HWND hwnd, UINT uMsg UNUSED, UINT_PTR idEvent, DWORD dwTime UNUSED) { MSG msg; /* TRACE2("Got timer event, id %d, blink_timer %d\n", idEvent, blink_timer); */ KillTimer(NULL, idEvent); // Eat spurious WM_TIMER messages while (PeekMessageW(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) ; if (blink_state == BLINK_ON) { gui_undraw_cursor(); blink_state = BLINK_OFF; blink_timer = SetTimer(NULL, 0, (UINT)blink_offtime, _OnBlinkTimer); } else { gui_update_cursor(TRUE, FALSE); blink_state = BLINK_ON; blink_timer = SetTimer(NULL, 0, (UINT)blink_ontime, _OnBlinkTimer); } gui_mch_flush(); } static void gui_mswin_rm_blink_timer(void) { MSG msg; if (blink_timer == 0) return; KillTimer(NULL, blink_timer); // Eat spurious WM_TIMER messages while (PeekMessageW(&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(int may_call_gui_update_cursor) { gui_mswin_rm_blink_timer(); if (blink_state == BLINK_OFF && may_call_gui_update_cursor) { gui_update_cursor(TRUE, FALSE); gui_mch_flush(); } 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 = SetTimer(NULL, 0, (UINT)blink_waittime, _OnBlinkTimer); blink_state = BLINK_ON; gui_update_cursor(TRUE, FALSE); gui_mch_flush(); } } /* * Call-back routines. */ static VOID CALLBACK _OnTimer( HWND hwnd, UINT uMsg UNUSED, UINT_PTR idEvent, DWORD dwTime UNUSED) { 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 (PeekMessageW(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) ; if (idEvent == s_wait_timer) s_wait_timer = 0; } static void _OnDeadChar( HWND hwnd UNUSED, UINT ch UNUSED, int cRepeat UNUSED) { dead_key = 1; } /* * Convert Unicode character "ch" to bytes in "string[slen]". * When "had_alt" is TRUE the ALT key was included in "ch". * Return the length. * Because the Windows API uses UTF-16, we have to deal with surrogate * pairs; this is where we choose to deal with them: if "ch" is a high * surrogate, it will be stored, and the length returned will be zero; the next * char_to_string call will then include the high surrogate, decoding the pair * of UTF-16 code units to a single Unicode code point, presuming it is the * matching low surrogate. */ static int char_to_string(int ch, char_u *string, int slen, int had_alt) { int len; int i; WCHAR wstring[2]; char_u *ws = NULL; if (surrogate_pending_ch != 0) { // We don't guarantee ch is a low surrogate to match the high surrogate // we already have; it should be, but if it isn't, tough luck. wstring[0] = surrogate_pending_ch; wstring[1] = ch; surrogate_pending_ch = 0; len = 2; } else if (ch >= 0xD800 && ch <= 0xDBFF) // high surrogate { // We don't have the entire code point yet, only the first UTF-16 code // unit; so just remember it and use it in the next call. surrogate_pending_ch = ch; return 0; } else { wstring[0] = ch; len = 1; } // "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). if (enc_codepage > 0) { len = WideCharToMultiByte(enc_codepage, 0, wstring, len, (LPSTR)string, slen, 0, NULL); // If we had included the ALT key into the character but now the // upper bit is no longer set, that probably means the conversion // failed. Convert the original character and set the upper bit // afterwards. if (had_alt && len == 1 && ch >= 0x80 && string[0] < 0x80) { wstring[0] = ch & 0x7f; len = WideCharToMultiByte(enc_codepage, 0, wstring, len, (LPSTR)string, slen, 0, NULL); if (len == 1) // safety check string[0] |= 0x80; } } else { ws = utf16_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) { 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; } static int get_active_modifiers(void) { int modifiers = 0; if (GetKeyState(VK_CONTROL) & 0x8000) modifiers |= MOD_MASK_CTRL; if (GetKeyState(VK_SHIFT) & 0x8000) modifiers |= MOD_MASK_SHIFT; // Windows handles Ctrl + Alt as AltGr and vice-versa. We can distinguish // the two cases by checking whether the left or the right Alt key is // pressed. if (GetKeyState(VK_LMENU) & 0x8000) modifiers |= MOD_MASK_ALT; if ((modifiers & MOD_MASK_CTRL) && (GetKeyState(VK_RMENU) & 0x8000)) modifiers &= ~MOD_MASK_CTRL; // Add RightALT only if it is hold alone (without Ctrl), because if AltGr // is pressed, Windows claims that Ctrl is hold as well. That way we can // recognize Right-ALT alone and be sure that not AltGr is hold. if (!(GetKeyState(VK_CONTROL) & 0x8000) && (GetKeyState(VK_RMENU) & 0x8000) && !(GetKeyState(VK_LMENU) & 0x8000)) // seems AltGr has both set modifiers |= MOD_MASK_ALT; return modifiers; } /* * Key hit, add it to the input buffer. */ static void _OnChar( HWND hwnd UNUSED, UINT cch, int cRepeat UNUSED) { char_u string[40]; int len = 0; int modifiers; int ch = cch; // special keys are negative if (dead_key == DEAD_KEY_SKIP_ON_CHAR) return; // keep DEAD_KEY_TRANSIENT_IN_ON_CHAR value for later handling in // process_message() if (dead_key != DEAD_KEY_TRANSIENT_IN_ON_CHAR) dead_key = DEAD_KEY_OFF; modifiers = get_active_modifiers(); ch = simplify_key(ch, &modifiers); // Some keys need adjustment when the Ctrl modifier is used. ++no_reduce_keys; ch = may_adjust_key_for_ctrl(modifiers, ch); --no_reduce_keys; // remove the SHIFT modifier for keys where it's already included, e.g., // '(' and '*' modifiers = may_remove_shift_modifier(modifiers, ch); // Unify modifiers somewhat. No longer use ALT to set the 8th bit. ch = extract_modifiers(ch, &modifiers, FALSE, NULL); if (ch == CSI) ch = K_CSI; if (modifiers) { string[0] = CSI; string[1] = KS_MODIFIER; string[2] = modifiers; add_to_input_buf(string, 3); } len = char_to_string(ch, string, 40, FALSE); 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 UNUSED, UINT cch, int cRepeat UNUSED) { char_u string[40]; // Enough for multibyte character int len; int modifiers; int ch = cch; // special keys are negative dead_key = DEAD_KEY_OFF; // 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 = get_active_modifiers(); ch = simplify_key(ch, &modifiers); // remove the SHIFT modifier for keys where it's already included, e.g., // '(' and '*' modifiers = may_remove_shift_modifier(modifiers, ch); // Unify modifiers somewhat. No longer use ALT to set the 8th bit. ch = extract_modifiers(ch, &modifiers, FALSE, NULL); 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, TRUE); } add_to_input_buf(string, len); } static void _OnMouseEvent( int button, int x, int y, int repeated_click, UINT keyFlags) { int vim_modifiers = 0x0; s_getting_focus = FALSE; if (keyFlags & MK_SHIFT) vim_modifiers |= MOUSE_SHIFT; if (keyFlags & MK_CONTROL) vim_modifiers |= MOUSE_CTRL; if (GetKeyState(VK_LMENU) & 0x8000) vim_modifiers |= MOUSE_ALT; gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers); } static void _OnMouseButtonDown( HWND hwnd UNUSED, BOOL fDoubleClick UNUSED, 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; else if (s_uMsg == WM_XBUTTONDOWN || s_uMsg == WM_XBUTTONDBLCLK) { 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; } if (button < 0) return; 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 UNUSED, int x, int y, UINT keyFlags) { int button; s_getting_focus = FALSE; 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); } static void _OnSizeTextArea( HWND hwnd UNUSED, UINT state UNUSED, int cx UNUSED, int cy UNUSED) { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) directx_binddc(); #endif } #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 UNUSED, int id, HWND hwndCtl UNUSED, UINT codeNotify UNUSED) { 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) return; char_u *p, *q; // 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; p = utf16_to_enc(s_findrep_struct.lpstrFindWhat, NULL); q = utf16_to_enc(s_findrep_struct.lpstrReplaceWith, NULL); if (p != NULL && q != NULL) gui_do_findrepl(flags, p, q, down); vim_free(p); vim_free(q); } #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 || uMsg == WM_NCMOUSEMOVE) { 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_GUI track_user_activity(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); HANDLE_MSG(hwnd, WM_XBUTTONDBLCLK,_OnMouseButtonDown); HANDLE_MSG(hwnd, WM_XBUTTONDOWN,_OnMouseButtonDown); HANDLE_MSG(hwnd, WM_XBUTTONUP, _OnMouseMoveOrRelease); HANDLE_MSG(hwnd, WM_SIZE, _OnSizeTextArea); #ifdef FEAT_BEVAL_GUI case WM_NOTIFY: Handle_WM_Notify(hwnd, (LPNMHDR)lParam); return TRUE; #endif default: return DefWindowProcW(hwnd, uMsg, wParam, lParam); } } /* * Called when the foreground or background color has been changed. */ void gui_mch_new_colors(void) { HBRUSH prevBrush; s_brush = CreateSolidBrush(gui.back_pixel); prevBrush = (HBRUSH)SetClassLongPtr( s_hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)s_brush); InvalidateRect(s_hwnd, NULL, TRUE); DeleteObject(prevBrush); } /* * Set the colors to their default values. */ void gui_mch_def_colors(void) { 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) { // 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); #ifdef MSWIN_FIND_REPLACE // Init replace string here, so that we keep it when re-opening the // dialog. s_findrep_struct.lpstrReplaceWith[0] = NUL; #endif 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, MAKELPARAM(w, gui.toolbar_height)); #endif #if defined(FEAT_GUI_TABLINE) if (showing_tabline) { int top = 0; RECT rect; # ifdef FEAT_TOOLBAR if (vim_strchr(p_go, GO_TOOLBAR) != NULL) top = gui.toolbar_height; # endif GetClientRect(s_hwnd, &rect); MoveWindow(s_tabhwnd, 0, top, rect.right, gui.tabline_height, TRUE); } #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 // forcedly redrawn. (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); } int gui_mch_get_scrollbar_xpadding(void) { RECT rcTxt, rcWnd; int xpad; GetWindowRect(s_textArea, &rcTxt); GetWindowRect(s_hwnd, &rcWnd); xpad = rcWnd.right - rcTxt.right - gui.scrollbar_width - pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) - pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi); return (xpad < 0) ? 0 : xpad; } int gui_mch_get_scrollbar_ypadding(void) { RECT rcTxt, rcWnd; int ypad; GetWindowRect(s_textArea, &rcTxt); GetWindowRect(s_hwnd, &rcWnd); ypad = rcWnd.bottom - rcTxt.bottom - gui.scrollbar_height - pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) - pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi); return (ypad < 0) ? 0 : ypad; } 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, g_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; } static void update_scrollbar_size(void) { gui.scrollbar_width = pGetSystemMetricsForDpi(SM_CXVSCROLL, s_dpi); gui.scrollbar_height = pGetSystemMetricsForDpi(SM_CYHSCROLL, s_dpi); } /* * Get the average character size of a font. */ static void GetAverageFontSize(HDC hdc, SIZE *size) { // GetTextMetrics() may not return the right value in tmAveCharWidth // for some fonts. Do our own average computation. GetTextExtentPoint(hdc, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, size); size->cx = (size->cx / 26 + 1) / 2; } /* * 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); SIZE size; TEXTMETRIC tm; GetTextMetrics(hdc, &tm); GetAverageFontSize(hdc, &size); gui.char_width = size.cx + tm.tmOverhang; gui.char_height = tm.tmHeight + p_linespace; SelectFont(hdc, hfntOld); ReleaseDC(hwnd, hdc); } /* * Adjust gui.char_height (after 'linespace' was changed). */ int gui_mch_adjust_charheight(void) { GetFontSize(gui.norm_font); return OK; } static GuiFont get_font_handle(LOGFONTW *lf) { HFONT font = NULL; // Load the font font = CreateFontIndirectW(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) { LOGFONTW lf; GuiFont font = NOFONT; if (get_logfont(&lf, name, NULL, giveErrorIfMissing) == OK) { lf.lfHeight = adjust_fontsize_by_dpi(lf.lfHeight); font = get_font_handle(&lf); } if (font == NOFONT && giveErrorIfMissing) semsg(_(e_unknown_font_str), name); return font; } #if defined(FEAT_EVAL) || defined(PROTO) /* * Return the name of font "font" in allocated memory. * Don't know how to get the actual name, thus use the provided name. */ char_u * gui_mch_get_fontname(GuiFont font UNUSED, char_u *name) { if (name == NULL) return NULL; return vim_strsave(name); } #endif void gui_mch_free_font(GuiFont font) { if (font) DeleteObject((HFONT)font); } /* * Return the Pixel value (color) for the given color name. * Return INVALCOLOR for error. */ guicolor_T gui_mch_get_color(char_u *name) { int i; typedef struct SysColorTable { char *name; int color; } SysColorTable; static SysColorTable sys_table[] = { {"SYS_3DDKSHADOW", COLOR_3DDKSHADOW}, {"SYS_3DHILIGHT", COLOR_3DHILIGHT}, #ifdef COLOR_3DHIGHLIGHT {"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}, {"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} }; /* * Try to look up a system colour. */ for (i = 0; i < (int)ARRAY_LENGTH(sys_table); i++) if (STRICMP(name, sys_table[i].name) == 0) return GetSysColor(sys_table[i].color); return gui_get_color_cmn(name); } guicolor_T gui_mch_get_rgb_color(int r, int g, int b) { return gui_get_rgb_color_cmn(r, g, b); } /* * Return OK if the key with the termcap name "name" is supported. */ int gui_mch_haskey(char_u *name) { int i; for (i = 0; special_keys[i].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((UINT)-1); } /* * 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; #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif /* * 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; #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif /* * 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; if (mb_lefthalve(gui.row, gui.col)) rc.right += gui.char_width; 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) { 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; fill_rect(&rc, NULL, color); } /* * Generates a VK_SPACE when the internal dead_key flag is set to output the * dead key's nominal character and re-post the original message. */ static void outputDeadKey_rePost_Ex(MSG originalMsg, int dead_key2set) { static MSG deadCharExpel; if (dead_key == DEAD_KEY_OFF) return; dead_key = dead_key2set; // Make Windows generate the dead key's character deadCharExpel.message = originalMsg.message; deadCharExpel.hwnd = originalMsg.hwnd; deadCharExpel.wParam = VK_SPACE; TranslateMessage(&deadCharExpel); // re-generate the current character free of the dead char influence PostMessage(originalMsg.hwnd, originalMsg.message, originalMsg.wParam, originalMsg.lParam); } /* * Wrapper for outputDeadKey_rePost_Ex which always reset dead_key value. */ static void outputDeadKey_rePost(MSG originalMsg) { outputDeadKey_rePost_Ex(originalMsg, DEAD_KEY_OFF); } /* * 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; #ifdef FEAT_MENU static char_u k10[] = {K_SPECIAL, 'k', ';', 0}; #endif BYTE keyboard_state[256]; GetMessageW(&msg, NULL, 0, 0); #ifdef FEAT_OLE // Look after OLE Automation commands if (msg.message == WM_OLE) { char_u *str = (char_u *)msg.lParam; if (str == NULL || *str == NUL) { // Message can't be ours, forward it. Fixes problem with Ultramon // 3.0.4 DispatchMessageW(&msg); } else { add_to_input_buf(str, (int)STRLEN(str)); vim_free(str); // was allocated in CVim::SendKeys() } return; } #endif #ifdef MSWIN_FIND_REPLACE // Don't process messages used by the dialog if (s_findrep_hwnd != NULL && IsDialogMessageW(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 dead keys in special conditions in other cases we let Windows * handle them and do not interfere. * * The dead_key flag must be reset on several occasions: * - in _OnChar() (or _OnSysChar()) as any dead key was necessarily * consumed at that point (This is when we let Windows combine the * dead character on its own) * * - Before doing something special such as regenerating keypresses to * expel the dead character as this could trigger an infinite loop if * for some reason TranslateMessage() do not trigger a call * immediately to _OnChar() (or _OnSysChar()). */ /* * We are at the moment after WM_CHAR with DEAD_KEY_SKIP_ON_CHAR event * was handled by _WndProc, this keypress we want to process normally */ if (dead_key == DEAD_KEY_SKIP_ON_CHAR) dead_key = DEAD_KEY_OFF; if (dead_key != DEAD_KEY_OFF) { /* * Expel the dead key pressed with Ctrl in a special way. * * After dead key was pressed with Ctrl in some cases, ESC was * artificially injected and handled by _OnChar(), now we are * dealing with completely new key press from the user. If we don't * do anything, ToUnicode() call will interpret this vk+scan_code * under influence of "dead-modifier". To prevent this we translate * this message replacing current char from user with VK_SPACE, * which will cause WM_CHAR with dead_key's character itself. Using * DEAD_KEY_SKIP_ON_CHAR value of dead_char we force _OnChar() to * ignore this one WM_CHAR event completely. Afterwards (due to * usage of PostMessage), this procedure is scheduled to be called * again with user char and on next entry we will clean * DEAD_KEY_SKIP_ON_CHAR. We cannot use original * outputDeadKey_rePost() since we do not wish to reset dead_key * value. */ if (dead_key == DEAD_KEY_TRANSIENT_IN_ON_CHAR) { outputDeadKey_rePost_Ex(msg, /*dead_key2set=*/DEAD_KEY_SKIP_ON_CHAR); return; } if (dead_key != DEAD_KEY_SET_DEFAULT) { // should never happen - is there a way to make ASSERT here? return; } /* * If a dead key was pressed and the user presses VK_SPACE, * VK_BACK, or VK_ESCAPE it means that he actually wants to deal * with the dead char now, so do nothing special and let Windows * handle it. * * Note that VK_SPACE combines with the dead_key's character and * only one WM_CHAR will be generated by TranslateMessage(), in * the two other cases two WM_CHAR will be generated: the dead * char and VK_BACK or VK_ESCAPE. That is most likely what the * user expects. */ if ((vk == VK_SPACE || vk == VK_BACK || vk == VK_ESCAPE)) { dead_key = DEAD_KEY_OFF; TranslateMessage(&msg); return; } // In modes where we are not typing, dead keys should behave // normally else if ((get_real_state() & (MODE_INSERT | MODE_CMDLINE | MODE_SELECT)) == 0) { outputDeadKey_rePost(msg); return; } } // Check for CTRL-BREAK if (vk == VK_CANCEL) { trash_input_buf(); got_int = TRUE; ctrl_break_was_pressed = TRUE; string[0] = Ctrl_C; add_to_input_buf(string, 1); } // This is an IME event or a synthetic keystroke, let Windows handle it. if (vk == VK_PROCESSKEY || vk == VK_PACKET) { TranslateMessage(&msg); return; } 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))) { /* * Behave as expected if we have a dead key and the special key * is a key that would normally trigger the dead key nominal * character output (such as a NUMPAD printable character or * the TAB key, etc...). */ if (dead_key == DEAD_KEY_SET_DEFAULT && (special_keys[i].vim_code0 == 'K' || vk == VK_TAB || vk == CAR)) { outputDeadKey_rePost(msg); return; } #ifdef FEAT_MENU // Check for : Windows selects the menu. When is // mapped we want to use the mapping instead. if (vk == VK_F10 && gui.menu_is_active && check_map(k10, State, FALSE, TRUE, FALSE, NULL, NULL) == NULL) break; #endif modifiers = get_active_modifiers(); 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, FALSE); add_to_input_buf(string, len); } break; } } // Not a special key. if (special_keys[i].key_sym == 0) { WCHAR ch[8]; int len; int i; UINT scan_code; // Construct the state table with only a few modifiers, we don't // really care about the presence of Ctrl/Alt as those modifiers are // handled by Vim separately. memset(keyboard_state, 0, 256); if (GetKeyState(VK_SHIFT) & 0x8000) keyboard_state[VK_SHIFT] = 0x80; if (GetKeyState(VK_CAPITAL) & 0x0001) keyboard_state[VK_CAPITAL] = 0x01; // Alt-Gr is synthesized as Alt + Ctrl. if ((GetKeyState(VK_RMENU) & 0x8000) && (GetKeyState(VK_CONTROL) & 0x8000)) { keyboard_state[VK_MENU] = 0x80; keyboard_state[VK_CONTROL] = 0x80; } // Translate the virtual key according to the current keyboard // layout. scan_code = MapVirtualKey(vk, MAPVK_VK_TO_VSC); // Convert the scan-code into a sequence of zero or more unicode // codepoints. // If this is a dead key ToUnicode returns a negative value. len = ToUnicode(vk, scan_code, keyboard_state, ch, ARRAY_LENGTH(ch), 0); if (len < 0) dead_key = DEAD_KEY_SET_DEFAULT; if (len <= 0) { int wm_char = NUL; if (dead_key == DEAD_KEY_SET_DEFAULT && (GetKeyState(VK_CONTROL) & 0x8000)) { if ( // AZERTY CTRL+dead_circumflex (vk == 221 && scan_code == 26) // QWERTZ CTRL+dead_circumflex || (vk == 220 && scan_code == 41)) wm_char = '['; if ( // QWERTZ CTRL+dead_two-overdots (vk == 192 && scan_code == 27)) wm_char = ']'; } if (wm_char != NUL) { // post WM_CHAR='[' - which will be interpreted with CTRL // still hold as ESC PostMessageW(msg.hwnd, WM_CHAR, wm_char, msg.lParam); // ask _OnChar() to not touch this state, wait for next key // press and maintain knowledge that we are "poisoned" with // "dead state" dead_key = DEAD_KEY_TRANSIENT_IN_ON_CHAR; } return; } // Post the message as TranslateMessage would do. if (msg.message == WM_KEYDOWN) { for (i = 0; i < len; i++) PostMessageW(msg.hwnd, WM_CHAR, ch[i], msg.lParam); } else { for (i = 0; i < len; i++) PostMessageW(msg.hwnd, WM_SYSCHAR, ch[i], msg.lParam); } } } #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) TranslateMessage(&msg); #endif #ifdef FEAT_MENU // Check for : Default effect is to select the menu. When is // mapped we need to stop it here to avoid strange effects (e.g., for the // key-up event) if (vk != VK_F10 || check_map(k10, State, FALSE, TRUE, FALSE, NULL, NULL) == NULL) #endif DispatchMessageW(&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 (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE) && !vim_is_input_buf_full()) process_message(); } static void remove_any_timer(void) { MSG msg; if (s_wait_timer != 0 && !s_timed_out) { KillTimer(NULL, s_wait_timer); // Eat spurious WM_TIMER messages while (PeekMessageW(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE)) ; s_wait_timer = 0; } } /* * GUI input routine called by gui_wait_for_chars(). Waits for a character * from the keyboard. * wtime == -1 Wait forever. * wtime == 0 This should never happen. * wtime > 0 Wait wtime milliseconds for a character. * Returns OK if a character was found to be available within the given time, * or FAIL otherwise. */ int gui_mch_wait_for_chars(int wtime) { int focus; s_timed_out = FALSE; if (wtime >= 0) { // Don't do anything while processing a (scroll) message. if (s_busy_processing) return FAIL; // When called with "wtime" zero, just want one msec. s_wait_timer = SetTimer(NULL, 0, (UINT)(wtime == 0 ? 1 : wtime), _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(TRUE); focus = gui.in_focus; } if (s_need_activate) { (void)SetForegroundWindow(s_hwnd); s_need_activate = FALSE; } #ifdef FEAT_TIMERS did_add_timer = FALSE; #endif #ifdef MESSAGE_QUEUE // Check channel I/O while waiting for a message. for (;;) { MSG msg; parse_queued_messages(); # ifdef FEAT_TIMERS if (did_add_timer) break; # endif if (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE)) { process_message(); break; } else if (input_available() // TODO: The 10 msec is a compromise between laggy response // and consuming more CPU time. Better would be to handle // channel messages when they arrive. || MsgWaitForMultipleObjects(0, NULL, FALSE, 10, QS_ALLINPUT) != WAIT_TIMEOUT) break; } #else // 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(); #endif if (input_available()) { remove_any_timer(); allow_scrollbar = FALSE; // Clear pending mouse button, the release event may have been // taken by the dialog window. But don't do this when getting // focus, we need the mouse-up event then. if (!s_getting_focus) s_button_pending = -1; return OK; } #ifdef FEAT_TIMERS if (did_add_timer) { // Need to recompute the waiting time. remove_any_timer(); break; } #endif } 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 UNUSED, int y UNUSED, int w UNUSED, int h UNUSED) { // 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 /* * Return the RGB value of a pixel as a long. */ guicolor_T gui_mch_get_rgb(guicolor_T pixel) { return (guicolor_T)((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, (LPCSTR)str, len, &size); return size.cx; } /* * 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; wstr = enc_to_utf16(str, &wlen); if (wstr == NULL) return 0; n = GetTextExtentPointW(hdc, wstr, wlen, &size); vim_free(wstr); if (n) return size.cx; return 0; } static void get_work_area(RECT *spi_rect); /* * A quick little routine that will center one window over another, handy for * dialog boxes. Taken from the Win32SDK samples and modified for multiple * monitors. */ static BOOL CenterWindow( HWND hwndChild, HWND hwndParent) { HMONITOR mon; MONITORINFO moninfo; RECT rChild, rParent, rScreen; int wChild, hChild, wParent, hParent; int 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 (hwndParent == NULL || IsMinimized(hwndParent)) get_work_area(&rParent); else GetWindowRect(hwndParent, &rParent); wParent = rParent.right - rParent.left; hParent = rParent.bottom - rParent.top; moninfo.cbSize = sizeof(MONITORINFO); mon = MonitorFromWindow(hwndChild, MONITOR_DEFAULTTOPRIMARY); if (mon != NULL && GetMonitorInfo(mon, &moninfo)) { rScreen = moninfo.rcWork; } else { hdc = GetDC(hwndChild); rScreen.left = 0; rScreen.top = 0; rScreen.right = GetDeviceCaps(hdc, HORZRES); rScreen.bottom = GetDeviceCaps(hdc, VERTRES); ReleaseDC(hwndChild, hdc); } xNew = rParent.left + ((wParent - wChild) / 2); if (xNew < rScreen.left) xNew = rScreen.left; else if ((xNew + wChild) > rScreen.right) xNew = rScreen.right - wChild; yNew = rParent.top + ((hParent - hChild) / 2); if (yNew < rScreen.top) yNew = rScreen.top; else if ((yNew + hChild) > rScreen.bottom) yNew = rScreen.bottom - hChild; return SetWindowPos(hwndChild, NULL, xNew, yNew, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } #endif // FEAT_GUI_DIALOG #if defined(FEAT_TOOLBAR) || defined(PROTO) void gui_mch_show_toolbar(int showit) { if (s_toolbarhwnd == NULL) return; if (showit) { // Enable unicode support SendMessage(s_toolbarhwnd, TB_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)0); ShowWindow(s_toolbarhwnd, SW_SHOW); } else ShowWindow(s_toolbarhwnd, SW_HIDE); } // The number of bitmaps is fixed. Exit is missing! # define TOOLBAR_BITMAP_COUNT 31 #endif #if defined(FEAT_GUI_TABLINE) || defined(PROTO) static void add_tabline_popup_menu_entry(HMENU pmenu, UINT item_id, char_u *item_text) { WCHAR *wn; MENUITEMINFOW infow; wn = enc_to_utf16(item_text, NULL); if (wn == NULL) return; infow.cbSize = sizeof(infow); infow.fMask = MIIM_TYPE | MIIM_ID; infow.wID = item_id; infow.fType = MFT_STRING; infow.dwTypeData = wn; infow.cch = (UINT)wcslen(wn); InsertMenuItemW(pmenu, item_id, FALSE, &infow); vim_free(wn); } static void show_tabline_popup_menu(void) { HMENU tab_pmenu; long rval; POINT pt; // When ignoring events don't show the menu. if (hold_gui_events || cmdwin_type != 0) return; tab_pmenu = CreatePopupMenu(); if (tab_pmenu == NULL) return; if (first_tabpage->tp_next != NULL) add_tabline_popup_menu_entry(tab_pmenu, TABLINE_MENU_CLOSE, (char_u *)_("Close tab")); add_tabline_popup_menu_entry(tab_pmenu, TABLINE_MENU_NEW, (char_u *)_("New tab")); add_tabline_popup_menu_entry(tab_pmenu, TABLINE_MENU_OPEN, (char_u *)_("Open tab...")); GetCursorPos(&pt); rval = TrackPopupMenuEx(tab_pmenu, TPM_RETURNCMD, pt.x, pt.y, s_tabhwnd, NULL); DestroyMenu(tab_pmenu); // Add the string cmd into input buffer if (rval > 0) { TCHITTESTINFO htinfo; int idx; if (ScreenToClient(s_tabhwnd, &pt) == 0) return; htinfo.pt.x = pt.x; htinfo.pt.y = pt.y; idx = TabCtrl_HitTest(s_tabhwnd, &htinfo); if (idx == -1) idx = 0; else idx += 1; send_tabline_menu_event(idx, (int)rval); } } /* * Show or hide the tabline. */ void gui_mch_show_tabline(int showit) { if (s_tabhwnd == NULL) return; if (!showit != !showing_tabline) { if (showit) ShowWindow(s_tabhwnd, SW_SHOW); else ShowWindow(s_tabhwnd, SW_HIDE); showing_tabline = showit; } } /* * Return TRUE when tabline is displayed. */ int gui_mch_showing_tabline(void) { return s_tabhwnd != NULL && showing_tabline; } /* * Update the labels of the tabline. */ void gui_mch_update_tabline(void) { tabpage_T *tp; TCITEM tie; int nr = 0; int curtabidx = 0; int tabadded = 0; WCHAR *wstr = NULL; if (s_tabhwnd == NULL) return; // Enable unicode support SendMessage(s_tabhwnd, CCM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)0); tie.mask = TCIF_TEXT; tie.iImage = -1; // Disable redraw for tab updates to eliminate O(N^2) draws. SendMessage(s_tabhwnd, WM_SETREDRAW, (WPARAM)FALSE, 0); // Add a label for each tab page. They all contain the same text area. for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr) { if (tp == curtab) curtabidx = nr; if (nr >= TabCtrl_GetItemCount(s_tabhwnd)) { // Add the tab tie.pszText = "-Empty-"; TabCtrl_InsertItem(s_tabhwnd, nr, &tie); tabadded = 1; } get_tabline_label(tp, FALSE); tie.pszText = (LPSTR)NameBuff; wstr = enc_to_utf16(NameBuff, NULL); if (wstr != NULL) { TCITEMW tiw; tiw.mask = TCIF_TEXT; tiw.iImage = -1; tiw.pszText = wstr; SendMessage(s_tabhwnd, TCM_SETITEMW, (WPARAM)nr, (LPARAM)&tiw); vim_free(wstr); } } // Remove any old labels. while (nr < TabCtrl_GetItemCount(s_tabhwnd)) TabCtrl_DeleteItem(s_tabhwnd, nr); if (!tabadded && TabCtrl_GetCurSel(s_tabhwnd) != curtabidx) TabCtrl_SetCurSel(s_tabhwnd, curtabidx); // Re-enable redraw and redraw. SendMessage(s_tabhwnd, WM_SETREDRAW, (WPARAM)TRUE, 0); RedrawWindow(s_tabhwnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); if (tabadded && TabCtrl_GetCurSel(s_tabhwnd) != curtabidx) TabCtrl_SetCurSel(s_tabhwnd, curtabidx); } /* * Set the current tab to "nr". First tab is 1. */ void gui_mch_set_curtab(int nr) { if (s_tabhwnd == NULL) return; if (TabCtrl_GetCurSel(s_tabhwnd) != nr - 1) TabCtrl_SetCurSel(s_tabhwnd, nr - 1); } #endif /* * ":simalt" command. */ void ex_simalt(exarg_T *eap) { char_u *keys = eap->arg; int fill_typebuf = FALSE; char_u key_name[4]; 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++; fill_typebuf = TRUE; } if (fill_typebuf) { // Put a NOP in the typeahead buffer so that the message will get // processed. key_name[0] = K_SPECIAL; key_name[1] = KS_EXTRA; key_name[2] = KE_NOP; key_name[3] = NUL; #if defined(FEAT_CLIENTSERVER) || defined(FEAT_EVAL) typebuf_was_filled = TRUE; #endif (void)ins_typebuf(key_name, REMAP_NONE, 0, TRUE, FALSE); } } /* * 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) { WCHAR *p = enc_to_utf16(entry_text, NULL); if (p != NULL) { int len = s_findrep_struct.wFindWhatLen - 1; wcsncpy(s_findrep_struct.lpstrFindWhat, p, len); s_findrep_struct.lpstrFindWhat[len] = NUL; vim_free(p); } } vim_free(entry_text); } #endif static void set_window_title(HWND hwnd, char *title) { if (title != NULL) { WCHAR *wbuf; // Convert the title from 'encoding' to UTF-16. wbuf = (WCHAR *)enc_to_utf16((char_u *)title, NULL); if (wbuf != NULL) { SetWindowTextW(hwnd, wbuf); vim_free(wbuf); } } else (void)SetWindowTextW(hwnd, NULL); } 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 = FindTextW(&s_findrep_struct); } set_window_title(s_findrep_hwnd, _("Find string")); (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 = ReplaceTextW(&s_findrep_struct); } set_window_title(s_findrep_hwnd, _("Find & Replace")); (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) return; 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 UNUSED) { gui_shell_closed(); } /* * Get a message when the window is being destroyed. */ static void _OnDestroy(HWND hwnd) { if (!destroying) _OnClose(hwnd); } static void _OnPaint( HWND hwnd) { if (IsMinimized(hwnd)) return; PAINTSTRUCT ps; out_flush(); // make sure all output has been processed (void)BeginPaint(hwnd, &ps); // 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; } 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 UNUSED, int cx, int cy) { if (!IsMinimized(hwnd) && !s_in_dpichanged) { gui_resize_shell(cx, cy); // Menu bar may wrap differently now gui_mswin_get_menu_height(TRUE); } } static void _OnSetFocus( HWND hwnd, HWND hwndOldFocus) { gui_focus_change(TRUE); s_getting_focus = TRUE; (void)DefWindowProcW(hwnd, WM_SETFOCUS, (WPARAM)hwndOldFocus, 0); } static void _OnKillFocus( HWND hwnd, HWND hwndNewFocus) { if (destroying) return; gui_focus_change(FALSE); s_getting_focus = FALSE; (void)DefWindowProcW(hwnd, WM_KILLFOCUS, (WPARAM)hwndNewFocus, 0); } /* * Get a message when the user switches back to vim */ static LRESULT _OnActivateApp( HWND hwnd, BOOL fActivate, DWORD dwThreadId) { // we call gui_focus_change() in _OnSetFocus() // gui_focus_change((int)fActivate); return DefWindowProcW(hwnd, WM_ACTIVATEAPP, fActivate, (DWORD)dwThreadId); } void gui_mch_destroy_scrollbar(scrollbar_T *sb) { DestroyWindow(sb->id); } /* * Get current mouse coordinates in text window. */ void gui_mch_getmouse(int *x, int *y) { RECT rct; POINT mp; (void)GetWindowRect(s_textArea, &rct); (void)GetCursorPos(&mp); *x = (int)(mp.x - rct.left); *y = (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 *cols, int *rows) { int base_width, base_height; base_width = gui_get_base_width() + (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2; base_height = gui_get_base_height() + (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2 + pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi) + gui_mswin_get_menu_height(FALSE); *cols = (w - base_width) / gui.char_width; *rows = (h - base_height) / gui.char_height; *valid_w = base_width + *cols * gui.char_width; *valid_h = base_height + *rows * gui.char_height; } void gui_mch_flash(int msec) { RECT rc; #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif /* * 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); } /* * Check if the specified point is on-screen. (multi-monitor aware) */ static BOOL is_point_onscreen(int x, int y) { POINT pt = {x, y}; return MonitorFromPoint(pt, MONITOR_DEFAULTTONULL) != NULL; } /* * Check if the whole client area of the specified window is on-screen. * * Note about DirectX: Windows 10 1809 or above no longer maintains image of * the window portion that is off-screen. Scrolling by DWriteContext_Scroll() * only works when the whole window is on-screen. */ static BOOL is_window_onscreen(HWND hwnd) { RECT rc; POINT p1, p2; GetClientRect(hwnd, &rc); p1.x = rc.left; p1.y = rc.top; p2.x = rc.right - 1; p2.y = rc.bottom - 1; ClientToScreen(hwnd, &p1); ClientToScreen(hwnd, &p2); if (!is_point_onscreen(p1.x, p1.y)) return FALSE; if (!is_point_onscreen(p1.x, p2.y)) return FALSE; if (!is_point_onscreen(p2.x, p1.y)) return FALSE; if (!is_point_onscreen(p2.x, p2.y)) return FALSE; return TRUE; } /* * 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; // Check if the window is (partly) off-screen. if (!is_window_onscreen(s_hwnd)) return SW_INVALIDATE; // Check if there is a window (partly) on top of us. 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; } /* * On some Intel GPUs, the regions drawn just prior to ScrollWindowEx() * may not be scrolled out properly. * For gVim, when _OnScroll() is repeated, the character at the * previous cursor position may be left drawn after scroll. * The problem can be avoided by calling GetPixel() to get a pixel in * the region before ScrollWindowEx(). */ static void intel_gpu_workaround(void) { GetPixel(s_hdc, FILL_X(gui.col), FILL_Y(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( 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); #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX() && is_window_onscreen(s_hwnd)) { DWriteContext_Scroll(s_dwc, 0, -num_lines * gui.char_height, &rc); } else #endif { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif intel_gpu_workaround(); 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); #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX() && is_window_onscreen(s_hwnd)) { DWriteContext_Scroll(s_dwc, 0, num_lines * gui.char_height, &rc); } else #endif { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif intel_gpu_workaround(); // 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 UNUSED) { #if defined(FEAT_DIRECTX) DWriteContext_Close(s_dwc); DWrite_Final(); s_dwc = NULL; #endif 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); } } static char_u * logfont2name(LOGFONTW lf) { char *p; char *res; char *charset_name; char *quality_name; char *font_name; int points; font_name = (char *)utf16_to_enc(lf.lfFaceName, NULL); if (font_name == NULL) return NULL; charset_name = charset_id2name((int)lf.lfCharSet); quality_name = quality_id2name((int)lf.lfQuality); res = alloc(strlen(font_name) + 30 + (charset_name == NULL ? 0 : strlen(charset_name) + 2) + (quality_name == NULL ? 0 : strlen(quality_name) + 2)); if (res != NULL) { p = res; // make a normal font string out of the lf thing: points = pixels_to_points( lf.lfHeight < 0 ? -lf.lfHeight : lf.lfHeight, TRUE); if (lf.lfWeight == FW_NORMAL || lf.lfWeight == FW_BOLD) sprintf((char *)p, "%s:h%d", font_name, points); else sprintf((char *)p, "%s:h%d:W%ld", font_name, points, lf.lfWeight); while (*p) { if (*p == ' ') *p = '_'; ++p; } if (lf.lfItalic) STRCAT(p, ":i"); if (lf.lfWeight == FW_BOLD) STRCAT(p, ":b"); if (lf.lfUnderline) STRCAT(p, ":u"); if (lf.lfStrikeOut) STRCAT(p, ":s"); if (charset_name != NULL) { STRCAT(p, ":c"); STRCAT(p, charset_name); } if (quality_name != NULL) { STRCAT(p, ":q"); STRCAT(p, quality_name); } } vim_free(font_name); return (char_u *)res; } #ifdef FEAT_MBYTE_IME /* * Set correct LOGFONTW to IME. Use 'guifontwide' if available, otherwise use * 'guifont'. */ static void update_im_font(void) { LOGFONTW lf_wide, lf; if (p_guifontwide != NULL && *p_guifontwide != NUL && gui.wide_font != NOFONT && GetObjectW((HFONT)gui.wide_font, sizeof(lf_wide), &lf_wide)) norm_logfont = lf_wide; else norm_logfont = sub_logfont; lf = norm_logfont; if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE) // Work around when PerMonitorV2 is not enabled in the process level. lf.lfHeight = lf.lfHeight * DEFAULT_DPI / s_dpi; im_set_font(&lf); } #endif /* * Handler of gui.wide_font (p_guifontwide) changed notification. */ void gui_mch_wide_font_changed(void) { LOGFONTW lf; #ifdef FEAT_MBYTE_IME update_im_font(); #endif gui_mch_free_font(gui.wide_ital_font); gui.wide_ital_font = NOFONT; gui_mch_free_font(gui.wide_bold_font); gui.wide_bold_font = NOFONT; gui_mch_free_font(gui.wide_boldital_font); gui.wide_boldital_font = NOFONT; if (gui.wide_font && GetObjectW((HFONT)gui.wide_font, sizeof(lf), &lf)) { if (!lf.lfItalic) { lf.lfItalic = TRUE; gui.wide_ital_font = get_font_handle(&lf); lf.lfItalic = FALSE; } if (lf.lfWeight < FW_BOLD) { lf.lfWeight = FW_BOLD; gui.wide_bold_font = get_font_handle(&lf); if (!lf.lfItalic) { lf.lfItalic = TRUE; gui.wide_boldital_font = get_font_handle(&lf); } } } } /* * 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 UNUSED) { LOGFONTW lf, lfOrig; GuiFont font = NOFONT; char_u *p; // Load the font if (get_logfont(&lf, font_name, NULL, TRUE) == OK) { lfOrig = lf; lf.lfHeight = adjust_fontsize_by_dpi(lf.lfHeight); font = get_font_handle(&lf); } if (font == NOFONT) return FAIL; if (font_name == NULL) font_name = (char_u *)""; #ifdef FEAT_MBYTE_IME norm_logfont = lf; sub_logfont = lf; if (!s_in_dpichanged) update_im_font(); #endif gui_mch_free_font(gui.norm_font); gui.norm_font = font; current_font_height = lfOrig.lfHeight; GetFontSize(font); p = logfont2name(lfOrig); if (p != NULL) { hl_set_font_name(p); // When setting 'guifont' to "*" replace it with the actual font name. if (STRCMP(font_name, "*") == 0 && STRCMP(p_guifont, "*") == 0) { vim_free(p_guifont); p_guifont = p; } else vim_free(p); } 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); } } return OK; } /* * Return TRUE if the GUI window is maximized, filling the whole screen. * Also return TRUE if the window is snapped. */ int gui_mch_maximized(void) { WINDOWPLACEMENT wp; RECT rc; wp.length = sizeof(WINDOWPLACEMENT); if (GetWindowPlacement(s_hwnd, &wp)) { if (wp.showCmd == SW_SHOWMAXIMIZED || (wp.showCmd == SW_SHOWMINIMIZED && wp.flags == WPF_RESTORETOMAXIMIZED)) return TRUE; if (wp.showCmd == SW_SHOWMINIMIZED) return FALSE; // Assume the window is snapped when the sizes from two APIs differ. GetWindowRect(s_hwnd, &rc); if ((rc.right - rc.left != wp.rcNormalPosition.right - wp.rcNormalPosition.left) || (rc.bottom - rc.top != wp.rcNormalPosition.bottom - wp.rcNormalPosition.top)) return TRUE; } return FALSE; } /* * Called when the font changed while the window is maximized or GO_KEEPWINSIZE * is set. Compute the new Rows and Columns. This is like resizing the * window. */ void gui_mch_newfont(void) { RECT rect; GetWindowRect(s_hwnd, &rect); if (win_socket_id == 0) { gui_resize_shell(rect.right - rect.left - (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2, rect.bottom - rect.top - (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2 - pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi) - gui_mswin_get_menu_height(FALSE)); } else { // Inside another window, don't use the frame and border. gui_resize_shell(rect.right - rect.left, rect.bottom - rect.top - gui_mswin_get_menu_height(FALSE)); } } /* * Set the window title */ void gui_mch_settitle( char_u *title, char_u *icon UNUSED) { set_window_title(s_hwnd, (title == NULL ? "VIM" : (char *)title)); } #if defined(FEAT_MOUSESHAPE) || defined(PROTO) // Table for shape IDCs. Keep in sync with the mshape_names[] table in // misc2.c! static LPCSTR mshape_idcs[] = { IDC_ARROW, // arrow MAKEINTRESOURCE(0), // blank IDC_IBEAM, // beam IDC_SIZENS, // updown IDC_SIZENS, // udsizing IDC_SIZEWE, // leftright IDC_SIZEWE, // lrsizing IDC_WAIT, // busy IDC_NO, // no IDC_ARROW, // crosshair IDC_ARROW, // hand1 IDC_ARROW, // hand2 IDC_ARROW, // pencil IDC_ARROW, // question IDC_ARROW, // right-arrow IDC_UPARROW, // up-arrow 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 = IDC_ARROW; else idc = mshape_idcs[shape]; SetClassLongPtr(s_textArea, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, idc)); if (!p_mh) { POINT mp; // Set the position to make it redrawn with the new shape. (void)GetCursorPos(&mp); (void)SetCursorPos(mp.x, mp.y); ShowCursor(TRUE); } } } #endif #if defined(FEAT_BROWSE) || defined(PROTO) /* * Wide version of convert_filter(). */ static WCHAR * convert_filterW(char_u *s) { char_u *tmp; int len; WCHAR *res; tmp = convert_filter(s); if (tmp == NULL) return NULL; len = (int)STRLEN(s) + 3; res = enc_to_utf16(tmp, &len); vim_free(tmp); 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. */ char_u * gui_mch_browse( 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_utf16() 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, *q; BOOL ret; if (dflt == NULL) fileBuf[0] = NUL; else { wp = enc_to_utf16(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); CLEAR_FIELD(fileStruct); # ifdef OPENFILENAME_SIZE_VERSION_400W // be compatible with Windows NT 4.0 fileStruct.lStructSize = OPENFILENAME_SIZE_VERSION_400W; # else fileStruct.lStructSize = sizeof(fileStruct); # endif if (title != NULL) titlep = enc_to_utf16(title, NULL); fileStruct.lpstrTitle = titlep; if (ext != NULL) extp = enc_to_utf16(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_utf16(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) ret = GetSaveFileNameW(&fileStruct); else ret = GetOpenFileNameW(&fileStruct); vim_free(filterp); vim_free(initdirp); vim_free(titlep); vim_free(extp); if (!ret) return NULL; // Convert from UTF-16 to 'encoding'. p = utf16_to_enc(fileBuf, NULL); if (p == NULL) return NULL; // Give focus back to main window (when using MDI). SetFocus(s_hwnd); // Shorten the file name if possible q = vim_strsave(shorten_fname1(p)); vim_free(p); return q; } /* * Convert the string s to the proper format for a filter string by replacing * the \t and \n delimiters 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; } /* * Select a directory. */ char_u * gui_mch_browsedir(char_u *title, char_u *initdir) { // We fake this: Use a filter that doesn't select anything and a default // file name that won't be used. return gui_mch_browse(0, title, (char_u *)_("Not Used"), NULL, initdir, (char_u *)_("Directory\t*.nothing\n")); } #endif // FEAT_BROWSE static void _OnDropFiles( HWND hwnd UNUSED, HDROP hDrop) { #define BUFPATHLEN _MAX_PATH #define DRAGQVAL 0xFFFFFFFF WCHAR wszFile[BUFPATHLEN]; char szFile[BUFPATHLEN]; UINT cFiles = DragQueryFile(hDrop, DRAGQVAL, NULL, 0); UINT i; char_u **fnames; POINT pt; int_u modifiers = 0; // Obtain dropped position DragQueryPoint(hDrop, &pt); MapWindowPoints(s_hwnd, s_textArea, &pt, 1); reset_VIsual(); fnames = ALLOC_MULT(char_u *, cFiles); if (fnames != NULL) for (i = 0; i < cFiles; ++i) { if (DragQueryFileW(hDrop, i, wszFile, BUFPATHLEN) > 0) fnames[i] = utf16_to_enc(wszFile, NULL); else { DragQueryFile(hDrop, i, szFile, BUFPATHLEN); fnames[i] = vim_strsave((char_u *)szFile); } } DragFinish(hDrop); if (fnames == NULL) return; int kbd_modifiers = get_active_modifiers(); if ((kbd_modifiers & MOD_MASK_SHIFT) != 0) modifiers |= MOUSE_SHIFT; if ((kbd_modifiers & MOD_MASK_CTRL) != 0) modifiers |= MOUSE_CTRL; if ((kbd_modifiers & MOD_MASK_ALT) != 0) modifiers |= MOUSE_ALT; gui_handle_drop(pt.x, pt.y, modifiers, fnames, cFiles); s_need_activate = TRUE; } static int _OnScroll( HWND hwnd UNUSED, 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; SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_POS; 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; si.nPos = (sb->scroll_shift > 0) ? val >> sb->scroll_shift : val; SetScrollInfo(hwndCtl, SB_CTL, &si, TRUE); /* * 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; SetScrollInfo(id, SB_CTL, &si, TRUE); } // 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; mch_disable_flush(); gui_drag_scrollbar(sb, val, dragging); mch_enable_flush(); gui_may_flush(); s_busy_processing = FALSE; dont_scroll = dont_scroll_save; return 0; } #ifdef FEAT_XPM_W32 # include "xpm_w32.h" #endif // Some parameters for tearoff menus. All in pixels. #define TEAROFF_PADDING_X 2 #define TEAROFF_BUTTON_PAD_X 8 #define TEAROFF_MIN_WIDTH 200 #define TEAROFF_SUBMENU_LABEL ">>" #define TEAROFF_COLUMN_PADDING 3 // # spaces to pad column with. #ifdef FEAT_BEVAL_GUI # define ID_BEVAL_TOOLTIP 200 # define BEVAL_TEXT_LEN MAXPATHL static BalloonEval *cur_beval = NULL; static UINT_PTR beval_timer_id = 0; static DWORD last_user_activity = 0; #endif // defined(FEAT_BEVAL_GUI) // Local variables: #ifdef FEAT_MENU static UINT s_menu_id = 100; #endif /* * Use the system font for dialogs and tear-off menus. Remove this line to * use DLG_FONT_NAME. */ #define USE_SYSMENU_FONT #define VIM_NAME "vim" #define VIM_CLASSW L"Vim" // Initial size for the dialog template. For gui_mch_dialog() it's fixed, // thus there should be room for every dialog. For tearoffs it's made bigger // when needed. #define DLG_ALLOC_SIZE 16 * 1024 /* * stuff for dialogs, menus, tearoffs etc. */ static PWORD add_dialog_element( PWORD p, DWORD lStyle, WORD x, WORD y, WORD w, WORD h, WORD Id, WORD clss, const char *caption); static LPWORD lpwAlign(LPWORD); static int nCopyAnsiToWideChar(LPWORD, LPSTR, BOOL); #if defined(FEAT_MENU) && defined(FEAT_TEAROFF) static void gui_mch_tearoff(char_u *title, vimmenu_T *menu, int initX, int initY); #endif static void get_dialog_font_metrics(void); static int dialog_default_button = -1; #ifdef FEAT_TOOLBAR static void initialise_toolbar(void); static void update_toolbar_size(void); static LRESULT CALLBACK toolbar_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static int get_toolbar_bitmap(vimmenu_T *menu); #else # define update_toolbar_size() #endif #ifdef FEAT_GUI_TABLINE static void initialise_tabline(void); static LRESULT CALLBACK tabline_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif #ifdef FEAT_MBYTE_IME static LRESULT _OnImeComposition(HWND hwnd, WPARAM dbcs, LPARAM param); static char_u *GetResultStr(HWND hwnd, int GCS, int *lenp); #endif #if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME) # ifdef NOIME typedef struct tagCOMPOSITIONFORM { DWORD dwStyle; POINT ptCurrentPos; RECT rcArea; } COMPOSITIONFORM, *PCOMPOSITIONFORM, NEAR *NPCOMPOSITIONFORM, FAR *LPCOMPOSITIONFORM; typedef HANDLE HIMC; # endif static HINSTANCE hLibImm = NULL; static LONG (WINAPI *pImmGetCompositionStringW)(HIMC, DWORD, LPVOID, DWORD); static HIMC (WINAPI *pImmGetContext)(HWND); static HIMC (WINAPI *pImmAssociateContext)(HWND, HIMC); static BOOL (WINAPI *pImmReleaseContext)(HWND, HIMC); static BOOL (WINAPI *pImmGetOpenStatus)(HIMC); static BOOL (WINAPI *pImmSetOpenStatus)(HIMC, BOOL); static BOOL (WINAPI *pImmGetCompositionFontW)(HIMC, LPLOGFONTW); static BOOL (WINAPI *pImmSetCompositionFontW)(HIMC, LPLOGFONTW); static BOOL (WINAPI *pImmSetCompositionWindow)(HIMC, LPCOMPOSITIONFORM); static BOOL (WINAPI *pImmGetConversionStatus)(HIMC, LPDWORD, LPDWORD); static BOOL (WINAPI *pImmSetConversionStatus)(HIMC, DWORD, DWORD); static void dyn_imm_load(void); #else # define pImmGetCompositionStringW ImmGetCompositionStringW # define pImmGetContext ImmGetContext # define pImmAssociateContext ImmAssociateContext # define pImmReleaseContext ImmReleaseContext # define pImmGetOpenStatus ImmGetOpenStatus # define pImmSetOpenStatus ImmSetOpenStatus # define pImmGetCompositionFontW ImmGetCompositionFontW # define pImmSetCompositionFontW ImmSetCompositionFontW # define pImmSetCompositionWindow ImmSetCompositionWindow # define pImmGetConversionStatus ImmGetConversionStatus # define pImmSetConversionStatus ImmSetConversionStatus #endif #ifdef FEAT_MENU /* * Figure out how high the menu bar is at the moment. */ static int gui_mswin_get_menu_height( int fix_window) // If TRUE, resize window if menu height changed { static int old_menu_height = -1; RECT rc1, rc2; int num; int menu_height; if (gui.menu_is_active) num = GetMenuItemCount(s_menuBar); else num = 0; if (num == 0) menu_height = 0; else if (IsMinimized(s_hwnd)) { // The height of the menu cannot be determined while the window is // minimized. Take the previous height if the menu is changed in that // state, to avoid that Vim's vertical window size accidentally // increases due to the unaccounted-for menu height. menu_height = old_menu_height == -1 ? 0 : old_menu_height; } else { /* * In case 'lines' is set in _vimrc/_gvimrc window width doesn't * seem to have been set yet, so menu wraps in default window * width which is very narrow. Instead just return height of a * single menu item. Will still be wrong when the menu really * should wrap over more than one line. */ GetMenuItemRect(s_hwnd, s_menuBar, 0, &rc1); if (gui.starting) menu_height = rc1.bottom - rc1.top + 1; else { GetMenuItemRect(s_hwnd, s_menuBar, num - 1, &rc2); menu_height = rc2.bottom - rc1.top + 1; } } if (fix_window && menu_height != old_menu_height) gui_set_shellsize(FALSE, FALSE, RESIZE_VERT); old_menu_height = menu_height; return menu_height; } #endif // FEAT_MENU /* * Setup for the Intellimouse */ static long mouse_vertical_scroll_step(void) { UINT val; if (SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &val, 0)) return (val != WHEEL_PAGESCROLL) ? (long)val : -1; return 3; // Safe default; } static long mouse_horizontal_scroll_step(void) { UINT val; if (SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &val, 0)) return (long)val; return 3; // Safe default; } static void init_mouse_wheel(void) { // Get the default values for the horizontal and vertical scroll steps from // the system. mouse_set_vert_scroll_step(mouse_vertical_scroll_step()); mouse_set_hor_scroll_step(mouse_horizontal_scroll_step()); } /* * Mouse scroll event handler. */ static void _OnMouseWheel(HWND hwnd UNUSED, WPARAM wParam, LPARAM lParam, int horizontal) { int button; win_T *wp; int modifiers = 0; int kbd_modifiers; int zDelta = GET_WHEEL_DELTA_WPARAM(wParam); POINT pt; wp = gui_mouse_window(FIND_POPUP); #ifdef FEAT_PROP_POPUP if (wp != NULL && popup_is_popup(wp)) { cmdarg_T cap; oparg_T oa; // Mouse hovers over popup window, scroll it if possible. mouse_row = wp->w_winrow; mouse_col = wp->w_wincol; CLEAR_FIELD(cap); if (horizontal) { cap.arg = zDelta < 0 ? MSCR_LEFT : MSCR_RIGHT; cap.cmdchar = zDelta < 0 ? K_MOUSELEFT : K_MOUSERIGHT; } else { cap.arg = zDelta < 0 ? MSCR_UP : MSCR_DOWN; cap.cmdchar = zDelta < 0 ? K_MOUSEUP : K_MOUSEDOWN; } clear_oparg(&oa); cap.oap = &oa; nv_mousescroll(&cap); update_screen(0); setcursor(); out_flush(); return; } #endif if (wp == NULL || !p_scf) wp = curwin; // Translate the scroll event into an event that Vim can process so that // the user has a chance to map the scrollwheel buttons. if (horizontal) button = zDelta >= 0 ? MOUSE_6 : MOUSE_7; else button = zDelta >= 0 ? MOUSE_4 : MOUSE_5; kbd_modifiers = get_active_modifiers(); if ((kbd_modifiers & MOD_MASK_SHIFT) != 0) modifiers |= MOUSE_SHIFT; if ((kbd_modifiers & MOD_MASK_CTRL) != 0) modifiers |= MOUSE_CTRL; if ((kbd_modifiers & MOD_MASK_ALT) != 0) modifiers |= MOUSE_ALT; // The cursor position is relative to the upper-left corner of the screen. pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ScreenToClient(s_textArea, &pt); gui_send_mouse_event(button, pt.x, pt.y, FALSE, modifiers); } #ifdef USE_SYSMENU_FONT /* * Get Menu Font. * Return OK or FAIL. */ static int gui_w32_get_menu_font(LOGFONTW *lf) { NONCLIENTMETRICSW nm; nm.cbSize = sizeof(NONCLIENTMETRICSW); if (!SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICSW), &nm, 0)) return FAIL; *lf = nm.lfMenuFont; return OK; } #endif #if defined(FEAT_GUI_TABLINE) && defined(USE_SYSMENU_FONT) /* * Set the GUI tabline font to the system menu font */ static void set_tabline_font(void) { LOGFONTW lfSysmenu; HFONT font; HWND hwnd; HDC hdc; HFONT hfntOld; TEXTMETRIC tm; if (gui_w32_get_menu_font(&lfSysmenu) != OK) return; lfSysmenu.lfHeight = adjust_fontsize_by_dpi(lfSysmenu.lfHeight); font = CreateFontIndirectW(&lfSysmenu); SendMessage(s_tabhwnd, WM_SETFONT, (WPARAM)font, TRUE); /* * Compute the height of the font used for the tab text */ hwnd = GetDesktopWindow(); hdc = GetWindowDC(hwnd); hfntOld = SelectFont(hdc, font); GetTextMetrics(hdc, &tm); SelectFont(hdc, hfntOld); ReleaseDC(hwnd, hdc); /* * The space used by the tab border and the space between the tab label * and the tab border is included as 7. */ gui.tabline_height = tm.tmHeight + tm.tmInternalLeading + 7; } #else # define set_tabline_font() #endif /* * Invoked when a setting was changed. */ static LRESULT CALLBACK _OnSettingChange(UINT param) { switch (param) { case SPI_SETWHEELSCROLLLINES: mouse_set_vert_scroll_step(mouse_vertical_scroll_step()); break; case SPI_SETWHEELSCROLLCHARS: mouse_set_hor_scroll_step(mouse_horizontal_scroll_step()); break; case SPI_SETNONCLIENTMETRICS: set_tabline_font(); break; default: break; } return 0; } #ifdef FEAT_NETBEANS_INTG static void _OnWindowPosChanged( HWND hwnd, const LPWINDOWPOS lpwpos) { static int x = 0, y = 0, cx = 0, cy = 0; extern int WSInitialized; if (WSInitialized && (lpwpos->x != x || lpwpos->y != y || lpwpos->cx != cx || lpwpos->cy != cy)) { x = lpwpos->x; y = lpwpos->y; cx = lpwpos->cx; cy = lpwpos->cy; netbeans_frame_moved(x, y); } // Allow to send WM_SIZE and WM_MOVE FORWARD_WM_WINDOWPOSCHANGED(hwnd, lpwpos, DefWindowProcW); } #endif static HWND hwndTip = NULL; static void show_sizing_tip(int cols, int rows) { TOOLINFOA ti; char buf[32]; ti.cbSize = sizeof(ti); ti.hwnd = s_hwnd; ti.uId = (UINT_PTR)s_hwnd; ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND; ti.lpszText = buf; sprintf(buf, "%dx%d", cols, rows); if (hwndTip == NULL) { hwndTip = CreateWindowExA(0, TOOLTIPS_CLASSA, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, s_hwnd, NULL, GetModuleHandle(NULL), NULL); SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti); SendMessage(hwndTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); } else { SendMessage(hwndTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); } SendMessage(hwndTip, TTM_POPUP, 0, 0); } static void destroy_sizing_tip(void) { if (hwndTip == NULL) return; DestroyWindow(hwndTip); hwndTip = NULL; } static int _DuringSizing( UINT fwSide, LPRECT lprc) { int w, h; int valid_w, valid_h; int w_offset, h_offset; int cols, rows; w = lprc->right - lprc->left; h = lprc->bottom - lprc->top; gui_mswin_get_valid_dimensions(w, h, &valid_w, &valid_h, &cols, &rows); w_offset = w - valid_w; h_offset = h - valid_h; if (fwSide == WMSZ_LEFT || fwSide == WMSZ_TOPLEFT || fwSide == WMSZ_BOTTOMLEFT) lprc->left += w_offset; else if (fwSide == WMSZ_RIGHT || fwSide == WMSZ_TOPRIGHT || fwSide == WMSZ_BOTTOMRIGHT) lprc->right -= w_offset; if (fwSide == WMSZ_TOP || fwSide == WMSZ_TOPLEFT || fwSide == WMSZ_TOPRIGHT) lprc->top += h_offset; else if (fwSide == WMSZ_BOTTOM || fwSide == WMSZ_BOTTOMLEFT || fwSide == WMSZ_BOTTOMRIGHT) lprc->bottom -= h_offset; show_sizing_tip(cols, rows); return TRUE; } #ifdef FEAT_GUI_TABLINE static void _OnRButtonUp(HWND hwnd, int x, int y, UINT keyFlags) { if (gui_mch_showing_tabline()) { POINT pt; RECT rect; /* * If the cursor is on the tabline, display the tab menu */ GetCursorPos(&pt); GetWindowRect(s_textArea, &rect); if (pt.y < rect.top) { show_tabline_popup_menu(); return; } } FORWARD_WM_RBUTTONUP(hwnd, x, y, keyFlags, DefWindowProcW); } static void _OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { /* * If the user double clicked the tabline, create a new tab */ if (gui_mch_showing_tabline()) { POINT pt; RECT rect; GetCursorPos(&pt); GetWindowRect(s_textArea, &rect); if (pt.y < rect.top) send_tabline_menu_event(0, TABLINE_MENU_NEW); } FORWARD_WM_LBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, DefWindowProcW); } #endif static UINT _OnNCHitTest(HWND hwnd, int xPos, int yPos) { UINT result; int x, y; result = FORWARD_WM_NCHITTEST(hwnd, xPos, yPos, DefWindowProcW); if (result != HTCLIENT) return result; #ifdef FEAT_GUI_TABLINE if (gui_mch_showing_tabline()) { RECT rct; // If the cursor is on the GUI tabline, don't process this event GetWindowRect(s_textArea, &rct); if (yPos < rct.top) return result; } #endif (void)gui_mch_get_winpos(&x, &y); xPos -= x; if (xPos < 48) // TODO should use system metric? return HTBOTTOMLEFT; else return HTBOTTOMRIGHT; } #if defined(FEAT_TOOLBAR) || defined(FEAT_GUI_TABLINE) static LRESULT _OnNotify(HWND hwnd, UINT id, NMHDR *hdr) { switch (hdr->code) { case TTN_GETDISPINFOW: case TTN_GETDISPINFO: { char_u *str = NULL; static void *tt_text = NULL; VIM_CLEAR(tt_text); # ifdef FEAT_GUI_TABLINE if (gui_mch_showing_tabline() && hdr->hwndFrom == TabCtrl_GetToolTips(s_tabhwnd)) { POINT pt; /* * Mouse is over the GUI tabline. Display the * tooltip for the tab under the cursor * * Get the cursor position within the tab control */ GetCursorPos(&pt); if (ScreenToClient(s_tabhwnd, &pt) != 0) { TCHITTESTINFO htinfo; int idx; /* * Get the tab under the cursor */ htinfo.pt.x = pt.x; htinfo.pt.y = pt.y; idx = TabCtrl_HitTest(s_tabhwnd, &htinfo); if (idx != -1) { tabpage_T *tp; tp = find_tabpage(idx + 1); if (tp != NULL) { get_tabline_label(tp, TRUE); str = NameBuff; } } } } # endif # ifdef FEAT_TOOLBAR # ifdef FEAT_GUI_TABLINE else # endif { UINT idButton; vimmenu_T *pMenu; idButton = (UINT) hdr->idFrom; pMenu = gui_mswin_find_menu(root_menu, idButton); if (pMenu) str = pMenu->strings[MENU_INDEX_TIP]; } # endif if (str == NULL) break; // Set the maximum width, this also enables using \n for // line break. SendMessage(hdr->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 500); if (hdr->code == TTN_GETDISPINFOW) { LPNMTTDISPINFOW lpdi = (LPNMTTDISPINFOW)hdr; tt_text = enc_to_utf16(str, NULL); lpdi->lpszText = tt_text; // can't show tooltip if failed } else { LPNMTTDISPINFO lpdi = (LPNMTTDISPINFO)hdr; if (STRLEN(str) < sizeof(lpdi->szText) || ((tt_text = vim_strsave(str)) == NULL)) vim_strncpy((char_u *)lpdi->szText, str, sizeof(lpdi->szText) - 1); else lpdi->lpszText = tt_text; } } break; # ifdef FEAT_GUI_TABLINE case TCN_SELCHANGE: if (gui_mch_showing_tabline() && (hdr->hwndFrom == s_tabhwnd)) { send_tabline_event(TabCtrl_GetCurSel(s_tabhwnd) + 1); return 0L; } break; case NM_RCLICK: if (gui_mch_showing_tabline() && (hdr->hwndFrom == s_tabhwnd)) { show_tabline_popup_menu(); return 0L; } break; # endif default: break; } return DefWindowProcW(hwnd, WM_NOTIFY, (WPARAM)id, (LPARAM)hdr); } #endif #if defined(MENUHINTS) && defined(FEAT_MENU) static LRESULT _OnMenuSelect(HWND hwnd, WPARAM wParam, LPARAM lParam) { if (((UINT) HIWORD(wParam) & (0xffff ^ (MF_MOUSESELECT + MF_BITMAP + MF_POPUP))) == MF_HILITE && (State & MODE_CMDLINE) == 0) { UINT idButton; vimmenu_T *pMenu; static int did_menu_tip = FALSE; if (did_menu_tip) { msg_clr_cmdline(); setcursor(); out_flush(); did_menu_tip = FALSE; } idButton = (UINT)LOWORD(wParam); pMenu = gui_mswin_find_menu(root_menu, idButton); if (pMenu != NULL && pMenu->strings[MENU_INDEX_TIP] != 0 && GetMenuState(s_menuBar, pMenu->id, MF_BYCOMMAND) != -1) { ++msg_hist_off; msg((char *)pMenu->strings[MENU_INDEX_TIP]); --msg_hist_off; setcursor(); out_flush(); did_menu_tip = TRUE; } return 0L; } return DefWindowProcW(hwnd, WM_MENUSELECT, wParam, lParam); } #endif static LRESULT _OnDpiChanged(HWND hwnd, UINT xdpi UNUSED, UINT ydpi, RECT *rc UNUSED) { s_dpi = ydpi; s_in_dpichanged = TRUE; //TRACE("DPI: %d", ydpi); update_scrollbar_size(); update_toolbar_size(); set_tabline_font(); gui_init_font(*p_guifont == NUL ? hl_get_font_name() : p_guifont, FALSE); gui_get_wide_font(); gui_mswin_get_menu_height(FALSE); #ifdef FEAT_MBYTE_IME im_set_position(gui.row, gui.col); #endif InvalidateRect(hwnd, NULL, TRUE); s_in_dpichanged = FALSE; return 0L; } static LRESULT CALLBACK _WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // ch_log(NULL, "WndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x", // hwnd, uMsg, wParam, lParam); HandleMouseHide(uMsg, lParam); s_uMsg = uMsg; s_wParam = wParam; s_lParam = lParam; switch (uMsg) { HANDLE_MSG(hwnd, WM_DEADCHAR, _OnDeadChar); HANDLE_MSG(hwnd, WM_SYSDEADCHAR, _OnDeadChar); // HANDLE_MSG(hwnd, WM_ACTIVATE, _OnActivate); HANDLE_MSG(hwnd, WM_CLOSE, _OnClose); // HANDLE_MSG(hwnd, WM_COMMAND, _OnCommand); HANDLE_MSG(hwnd, WM_DESTROY, _OnDestroy); HANDLE_MSG(hwnd, WM_DROPFILES, _OnDropFiles); HANDLE_MSG(hwnd, WM_HSCROLL, _OnScroll); HANDLE_MSG(hwnd, WM_KILLFOCUS, _OnKillFocus); #ifdef FEAT_MENU HANDLE_MSG(hwnd, WM_COMMAND, _OnMenu); #endif // HANDLE_MSG(hwnd, WM_MOVE, _OnMove); // HANDLE_MSG(hwnd, WM_NCACTIVATE, _OnNCActivate); HANDLE_MSG(hwnd, WM_SETFOCUS, _OnSetFocus); HANDLE_MSG(hwnd, WM_SIZE, _OnSize); // HANDLE_MSG(hwnd, WM_SYSCOMMAND, _OnSysCommand); // HANDLE_MSG(hwnd, WM_SYSKEYDOWN, _OnAltKey); HANDLE_MSG(hwnd, WM_VSCROLL, _OnScroll); // HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING, _OnWindowPosChanging); HANDLE_MSG(hwnd, WM_ACTIVATEAPP, _OnActivateApp); #ifdef FEAT_NETBEANS_INTG HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, _OnWindowPosChanged); #endif #ifdef FEAT_GUI_TABLINE HANDLE_MSG(hwnd, WM_RBUTTONUP, _OnRButtonUp); HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, _OnLButtonDown); #endif HANDLE_MSG(hwnd, WM_NCHITTEST, _OnNCHitTest); case WM_QUERYENDSESSION: // System wants to go down. gui_shell_closed(); // Will exit when no changed buffers. return FALSE; // Do NOT allow system to go down. case WM_ENDSESSION: if (wParam) // system only really goes down when wParam is TRUE { _OnEndSession(); return 0L; } break; case WM_CHAR: // Don't use HANDLE_MSG() for WM_CHAR, it truncates wParam to a single // byte while we want the UTF-16 character value. _OnChar(hwnd, (UINT)wParam, (int)(short)LOWORD(lParam)); return 0L; case WM_SYSCHAR: /* * if 'winaltkeys' is "no", or it's "menu" and it's not a menu * shortcut key, handle like a typed ALT key, otherwise call Windows * ALT key handling. */ #ifdef FEAT_MENU if ( !gui.menu_is_active || p_wak[0] == 'n' || (p_wak[0] == 'm' && !gui_is_menu_shortcut((int)wParam)) ) #endif { _OnSysChar(hwnd, (UINT)wParam, (int)(short)LOWORD(lParam)); return 0L; } #ifdef FEAT_MENU else return DefWindowProcW(hwnd, uMsg, wParam, lParam); #endif case WM_SYSKEYUP: #ifdef FEAT_MENU // This used to be done only when menu is active: ALT key is used for // that. But that caused problems when menu is disabled and using // Alt-Tab-Esc: get into a strange state where no mouse-moved events // are received, mouse pointer remains hidden. return DefWindowProcW(hwnd, uMsg, wParam, lParam); #else return 0L; #endif case WM_EXITSIZEMOVE: destroy_sizing_tip(); break; case WM_SIZING: // HANDLE_MSG doesn't seem to handle this one return _DuringSizing((UINT)wParam, (LPRECT)lParam); case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: _OnMouseWheel(hwnd, wParam, lParam, uMsg == WM_MOUSEHWHEEL); return 0L; // Notification for change in SystemParametersInfo() case WM_SETTINGCHANGE: return _OnSettingChange((UINT)wParam); #if defined(FEAT_TOOLBAR) || defined(FEAT_GUI_TABLINE) case WM_NOTIFY: return _OnNotify(hwnd, (UINT)wParam, (NMHDR*)lParam); #endif #if defined(MENUHINTS) && defined(FEAT_MENU) case WM_MENUSELECT: return _OnMenuSelect(hwnd, wParam, lParam); #endif #ifdef FEAT_MBYTE_IME case WM_IME_NOTIFY: if (!_OnImeNotify(hwnd, (DWORD)wParam, (DWORD)lParam)) return DefWindowProcW(hwnd, uMsg, wParam, lParam); return 1L; case WM_IME_COMPOSITION: if (!_OnImeComposition(hwnd, wParam, lParam)) return DefWindowProcW(hwnd, uMsg, wParam, lParam); return 1L; #endif case WM_DPICHANGED: return _OnDpiChanged(hwnd, (UINT)LOWORD(wParam), (UINT)HIWORD(wParam), (RECT*)lParam); default: #ifdef MSWIN_FIND_REPLACE if (uMsg == s_findrep_msg && s_findrep_msg != 0) _OnFindRepl(); #endif break; } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } /* * End of call-back routines */ // parent window, if specified with -P HWND vim_parent_hwnd = NULL; static BOOL CALLBACK FindWindowTitle(HWND hwnd, LPARAM lParam) { char buf[2048]; char *title = (char *)lParam; if (GetWindowText(hwnd, buf, sizeof(buf))) { if (strstr(buf, title) != NULL) { // Found it. Store the window ref. and quit searching if MDI // works. vim_parent_hwnd = FindWindowEx(hwnd, NULL, "MDIClient", NULL); if (vim_parent_hwnd != NULL) return FALSE; } } return TRUE; // continue searching } /* * Invoked for '-P "title"' argument: search for parent application to open * our window in. */ void gui_mch_set_parent(char *title) { EnumWindows(FindWindowTitle, (LPARAM)title); if (vim_parent_hwnd == NULL) { semsg(_(e_cannot_find_window_title_str), title); mch_exit(2); } } #ifndef FEAT_OLE static void ole_error(char *arg) { char buf[IOSIZE]; # ifdef VIMDLL gui.in_use = mch_is_gui_executable(); # endif // Can't use emsg() here, we have not finished initialisation yet. vim_snprintf(buf, IOSIZE, _(e_argument_not_supported_str_use_ole_version), arg); mch_errmsg(buf); } #endif #if defined(GUI_MAY_SPAWN) || defined(PROTO) static char * gvim_error(void) { char *msg = _(e_gui_cannot_be_used_cannot_execute_gvim_exe); if (starting) { mch_errmsg(msg); mch_errmsg("\n"); mch_exit(2); } return msg; } char * gui_mch_do_spawn(char_u *arg) { int len; # if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD) char_u *session = NULL; LPWSTR tofree1 = NULL; # endif WCHAR name[MAX_PATH]; LPWSTR cmd, newcmd = NULL, p, warg, tofree2 = NULL; STARTUPINFOW si = {sizeof(si)}; PROCESS_INFORMATION pi; if (!GetModuleFileNameW(g_hinst, name, MAX_PATH)) goto error; p = wcsrchr(name, L'\\'); if (p == NULL) goto error; // Replace the executable name from vim(d).exe to gvim(d).exe. # ifdef DEBUG wcscpy(p + 1, L"gvimd.exe"); # else wcscpy(p + 1, L"gvim.exe"); # endif # if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD) if (starting) # endif { // Pass the command line to the new process. p = GetCommandLineW(); // Skip 1st argument. while (*p && *p != L' ' && *p != L'\t') { if (*p == L'"') { while (*p && *p != L'"') ++p; if (*p) ++p; } else ++p; } cmd = p; } # if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD) else { // Create a session file and pass it to the new process. LPWSTR wsession; char_u *savebg; int ret; session = vim_tempname('s', FALSE); if (session == NULL) goto error; savebg = p_bg; p_bg = vim_strsave((char_u *)"light"); // Set 'bg' to "light". ret = write_session_file(session); vim_free(p_bg); p_bg = savebg; if (!ret) goto error; wsession = enc_to_utf16(session, NULL); if (wsession == NULL) goto error; len = (int)wcslen(wsession) * 2 + 27 + 1; cmd = ALLOC_MULT(WCHAR, len); if (cmd == NULL) { vim_free(wsession); goto error; } tofree1 = cmd; _snwprintf(cmd, len, L" -S \"%s\" -c \"call delete('%s')\"", wsession, wsession); vim_free(wsession); } # endif // Check additional arguments to the `:gui` command. if (arg != NULL) { warg = enc_to_utf16(arg, NULL); if (warg == NULL) goto error; tofree2 = warg; } else warg = L""; // Set up the new command line. len = (int)wcslen(name) + (int)wcslen(cmd) + (int)wcslen(warg) + 4; newcmd = ALLOC_MULT(WCHAR, len); if (newcmd == NULL) goto error; _snwprintf(newcmd, len, L"\"%s\"%s %s", name, cmd, warg); // Spawn a new GUI process. if (!CreateProcessW(NULL, newcmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) goto error; CloseHandle(pi.hProcess); CloseHandle(pi.hThread); mch_exit(0); error: # if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD) if (session) mch_remove(session); vim_free(session); vim_free(tofree1); # endif vim_free(newcmd); vim_free(tofree2); return gvim_error(); } #endif /* * Parse the GUI related command-line arguments. Any arguments used are * deleted from argv, and *argc is decremented accordingly. This is called * when Vim is started, whether or not the GUI has been started. */ void gui_mch_prepare(int *argc, char **argv) { int silent = FALSE; int idx; // Check for special OLE command line parameters if ((*argc == 2 || *argc == 3) && (argv[1][0] == '-' || argv[1][0] == '/')) { // Check for a "-silent" argument first. if (*argc == 3 && STRICMP(argv[1] + 1, "silent") == 0 && (argv[2][0] == '-' || argv[2][0] == '/')) { silent = TRUE; idx = 2; } else idx = 1; // Register Vim as an OLE Automation server if (STRICMP(argv[idx] + 1, "register") == 0) { #ifdef FEAT_OLE RegisterMe(silent); mch_exit(0); #else if (!silent) ole_error("register"); mch_exit(2); #endif } // Unregister Vim as an OLE Automation server if (STRICMP(argv[idx] + 1, "unregister") == 0) { #ifdef FEAT_OLE UnregisterMe(!silent); mch_exit(0); #else if (!silent) ole_error("unregister"); mch_exit(2); #endif } // Ignore an -embedding argument. It is only relevant if the // application wants to treat the case when it is started manually // differently from the case where it is started via automation (and // we don't). if (STRICMP(argv[idx] + 1, "embedding") == 0) { #ifdef FEAT_OLE *argc = 1; #else ole_error("embedding"); mch_exit(2); #endif } } #ifdef FEAT_OLE { int bDoRestart = FALSE; InitOLE(&bDoRestart); // automatically exit after registering if (bDoRestart) mch_exit(0); } #endif #ifdef FEAT_NETBEANS_INTG { // stolen from gui_x11.c int arg; for (arg = 1; arg < *argc; arg++) if (strncmp("-nb", argv[arg], 3) == 0) { netbeansArg = argv[arg]; mch_memmove(&argv[arg], &argv[arg + 1], (--*argc - arg) * sizeof(char *)); argv[*argc] = NULL; break; // enough? } } #endif } static void load_dpi_func(void) { HMODULE hUser32; hUser32 = GetModuleHandle("user32.dll"); if (hUser32 == NULL) goto fail; pGetDpiForSystem = (UINT (WINAPI *)(void))GetProcAddress(hUser32, "GetDpiForSystem"); pGetDpiForWindow = (UINT (WINAPI *)(HWND))GetProcAddress(hUser32, "GetDpiForWindow"); pGetSystemMetricsForDpi = (int (WINAPI *)(int, UINT))GetProcAddress(hUser32, "GetSystemMetricsForDpi"); //pGetWindowDpiAwarenessContext = (void*)GetProcAddress(hUser32, "GetWindowDpiAwarenessContext"); pSetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(DPI_AWARENESS_CONTEXT))GetProcAddress(hUser32, "SetThreadDpiAwarenessContext"); pGetAwarenessFromDpiAwarenessContext = (DPI_AWARENESS (WINAPI *)(DPI_AWARENESS_CONTEXT))GetProcAddress(hUser32, "GetAwarenessFromDpiAwarenessContext"); if (pSetThreadDpiAwarenessContext != NULL) { DPI_AWARENESS_CONTEXT oldctx = pSetThreadDpiAwarenessContext( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); if (oldctx != NULL) { TRACE("DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 enabled"); s_process_dpi_aware = pGetAwarenessFromDpiAwarenessContext(oldctx); #ifdef DEBUG if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE) { TRACE("WARNING: PerMonitorV2 is not enabled in the process level for some reasons. IME window may not shown correctly."); } #endif return; } } fail: // Disable PerMonitorV2 APIs. pGetDpiForSystem = stubGetDpiForSystem; pGetDpiForWindow = NULL; pGetSystemMetricsForDpi = stubGetSystemMetricsForDpi; pSetThreadDpiAwarenessContext = NULL; pGetAwarenessFromDpiAwarenessContext = NULL; } /* * Initialise the GUI. Create all the windows, set up all the call-backs * etc. */ int gui_mch_init(void) { const WCHAR szVimWndClassW[] = VIM_CLASSW; const WCHAR szTextAreaClassW[] = L"VimTextArea"; WNDCLASSW wndclassw; // Return here if the window was already opened (happens when // gui_mch_dialog() is called early). if (s_hwnd != NULL) goto theend; /* * Load the tearoff bitmap */ #ifdef FEAT_TEAROFF s_htearbitmap = LoadBitmap(g_hinst, "IDB_TEAROFF"); #endif load_dpi_func(); s_dpi = pGetDpiForSystem(); update_scrollbar_size(); #ifdef FEAT_MENU gui.menu_height = 0; // Windows takes care of this #endif gui.border_width = 0; #ifdef FEAT_TOOLBAR gui.toolbar_height = TOOLBAR_BUTTON_HEIGHT + TOOLBAR_BORDER_HEIGHT; #endif s_brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); // First try using the wide version, so that we can use any title. // Otherwise only characters in the active codepage will work. if (GetClassInfoW(g_hinst, szVimWndClassW, &wndclassw) == 0) { wndclassw.style = CS_DBLCLKS; wndclassw.lpfnWndProc = _WndProc; wndclassw.cbClsExtra = 0; wndclassw.cbWndExtra = 0; wndclassw.hInstance = g_hinst; wndclassw.hIcon = LoadIcon(wndclassw.hInstance, "IDR_VIM"); wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); wndclassw.hbrBackground = s_brush; wndclassw.lpszMenuName = NULL; wndclassw.lpszClassName = szVimWndClassW; if (RegisterClassW(&wndclassw) == 0) return FAIL; } if (vim_parent_hwnd != NULL) { #ifdef HAVE_TRY_EXCEPT __try { #endif // Open inside the specified parent window. // TODO: last argument should point to a CLIENTCREATESTRUCT // structure. s_hwnd = CreateWindowExW( WS_EX_MDICHILD, szVimWndClassW, L"Vim MSWindows GUI", WS_OVERLAPPEDWINDOW | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | 0xC000, gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x, gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y, 100, // Any value will do 100, // Any value will do vim_parent_hwnd, NULL, g_hinst, NULL); #ifdef HAVE_TRY_EXCEPT } __except(EXCEPTION_EXECUTE_HANDLER) { // NOP } #endif if (s_hwnd == NULL) { emsg(_(e_unable_to_open_window_inside_mdi_application)); mch_exit(2); } } else { // If the provided windowid is not valid reset it to zero, so that it // is ignored and we open our own window. if (IsWindow((HWND)win_socket_id) <= 0) win_socket_id = 0; // Create a window. If win_socket_id is not zero without border and // titlebar, it will be reparented below. s_hwnd = CreateWindowW( szVimWndClassW, L"Vim MSWindows GUI", (win_socket_id == 0 ? WS_OVERLAPPEDWINDOW : WS_POPUP) | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x, gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y, 100, // Any value will do 100, // Any value will do NULL, NULL, g_hinst, NULL); if (s_hwnd != NULL && win_socket_id != 0) { SetParent(s_hwnd, (HWND)win_socket_id); ShowWindow(s_hwnd, SW_SHOWMAXIMIZED); } } if (s_hwnd == NULL) return FAIL; if (pGetDpiForWindow != NULL) { s_dpi = pGetDpiForWindow(s_hwnd); update_scrollbar_size(); //TRACE("System DPI: %d, DPI: %d", pGetDpiForSystem(), s_dpi); } #if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME) dyn_imm_load(); #endif // Create the text area window if (GetClassInfoW(g_hinst, szTextAreaClassW, &wndclassw) == 0) { wndclassw.style = CS_OWNDC; wndclassw.lpfnWndProc = _TextAreaWndProc; wndclassw.cbClsExtra = 0; wndclassw.cbWndExtra = 0; wndclassw.hInstance = g_hinst; wndclassw.hIcon = NULL; wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW); wndclassw.hbrBackground = NULL; wndclassw.lpszMenuName = NULL; wndclassw.lpszClassName = szTextAreaClassW; if (RegisterClassW(&wndclassw) == 0) return FAIL; } s_textArea = CreateWindowExW( 0, szTextAreaClassW, L"Vim text area", WS_CHILD | WS_VISIBLE, 0, 0, 100, // Any value will do for now 100, // Any value will do for now s_hwnd, NULL, g_hinst, NULL); if (s_textArea == NULL) return FAIL; #ifdef FEAT_LIBCALL // Try loading an icon from $RUNTIMEPATH/bitmaps/vim.ico. { HANDLE hIcon = NULL; if (mch_icon_load(&hIcon) == OK && hIcon != NULL) SendMessage(s_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); } #endif #ifdef FEAT_MENU s_menuBar = CreateMenu(); #endif s_hdc = GetDC(s_textArea); DragAcceptFiles(s_hwnd, TRUE); // Do we need to bother with this? // m_fMouseAvail = pGetSystemMetricsForDpi(SM_MOUSEPRESENT, s_dpi); // Get background/foreground colors from the system gui_mch_def_colors(); // Get the colors from the "Normal" group (set in syntax.c or in a vimrc // file) set_normal_colors(); /* * Check that none of the colors are the same as the background color. * Then store the current values as the defaults. */ gui_check_colors(); gui.def_norm_pixel = gui.norm_pixel; gui.def_back_pixel = gui.back_pixel; // Get the colors for the highlight groups (gui_check_colors() might have // changed them) highlight_gui_started(); /* * Start out by adding the configured border width into the border offset. */ gui.border_offset = gui.border_width; /* * Set up for Intellimouse processing */ init_mouse_wheel(); /* * compute a couple of metrics used for the dialogs */ get_dialog_font_metrics(); #ifdef FEAT_TOOLBAR /* * Create the toolbar */ initialise_toolbar(); #endif #ifdef FEAT_GUI_TABLINE /* * Create the tabline */ initialise_tabline(); #endif #ifdef MSWIN_FIND_REPLACE /* * Initialise the dialog box stuff */ s_findrep_msg = RegisterWindowMessage(FINDMSGSTRING); // Initialise the struct s_findrep_struct.lStructSize = sizeof(s_findrep_struct); s_findrep_struct.lpstrFindWhat = ALLOC_MULT(WCHAR, MSWIN_FR_BUFSIZE); s_findrep_struct.lpstrFindWhat[0] = NUL; s_findrep_struct.lpstrReplaceWith = ALLOC_MULT(WCHAR, MSWIN_FR_BUFSIZE); s_findrep_struct.lpstrReplaceWith[0] = NUL; s_findrep_struct.wFindWhatLen = MSWIN_FR_BUFSIZE; s_findrep_struct.wReplaceWithLen = MSWIN_FR_BUFSIZE; #endif #ifdef FEAT_EVAL // set the v:windowid variable set_vim_var_nr(VV_WINDOWID, HandleToLong(s_hwnd)); #endif #ifdef FEAT_RENDER_OPTIONS if (p_rop) (void)gui_mch_set_rendering_options(p_rop); #endif theend: // Display any pending error messages display_errors(); return OK; } /* * Get the size of the screen, taking position on multiple monitors into * account (if supported). */ static void get_work_area(RECT *spi_rect) { HMONITOR mon; MONITORINFO moninfo; // work out which monitor the window is on, and get *its* work area mon = MonitorFromWindow(s_hwnd, MONITOR_DEFAULTTOPRIMARY); if (mon != NULL) { moninfo.cbSize = sizeof(MONITORINFO); if (GetMonitorInfo(mon, &moninfo)) { *spi_rect = moninfo.rcWork; return; } } // this is the old method... SystemParametersInfo(SPI_GETWORKAREA, 0, spi_rect, 0); } /* * Set the size of the window to the given width and height in pixels. */ void gui_mch_set_shellsize( int width, int height, int min_width UNUSED, int min_height UNUSED, int base_width UNUSED, int base_height UNUSED, int direction) { RECT workarea_rect; RECT window_rect; int win_width, win_height; // Try to keep window completely on screen. // Get position of the screen work area. This is the part that is not // used by the taskbar or appbars. get_work_area(&workarea_rect); // Resizing a maximized window looks very strange, unzoom it first. // But don't do it when still starting up, it may have been requested in // the shortcut. if (IsZoomed(s_hwnd) && starting == 0) ShowWindow(s_hwnd, SW_SHOWNORMAL); GetWindowRect(s_hwnd, &window_rect); // compute the size of the outside of the window win_width = width + (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2; win_height = height + (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2 + pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi) + gui_mswin_get_menu_height(FALSE); // The following should take care of keeping Vim on the same monitor, no // matter if the secondary monitor is left or right of the primary // monitor. window_rect.right = window_rect.left + win_width; window_rect.bottom = window_rect.top + win_height; // If the window is going off the screen, move it on to the screen. if ((direction & RESIZE_HOR) && window_rect.right > workarea_rect.right) OffsetRect(&window_rect, workarea_rect.right - window_rect.right, 0); if ((direction & RESIZE_HOR) && window_rect.left < workarea_rect.left) OffsetRect(&window_rect, workarea_rect.left - window_rect.left, 0); if ((direction & RESIZE_VERT) && window_rect.bottom > workarea_rect.bottom) OffsetRect(&window_rect, 0, workarea_rect.bottom - window_rect.bottom); if ((direction & RESIZE_VERT) && window_rect.top < workarea_rect.top) OffsetRect(&window_rect, 0, workarea_rect.top - window_rect.top); MoveWindow(s_hwnd, window_rect.left, window_rect.top, win_width, win_height, TRUE); SetActiveWindow(s_hwnd); SetFocus(s_hwnd); // Menu may wrap differently now gui_mswin_get_menu_height(!gui.starting); } void gui_mch_set_scrollbar_thumb( scrollbar_T *sb, long val, long size, long max) { SCROLLINFO info; sb->scroll_shift = 0; while (max > 32767) { max = (max + 1) >> 1; val >>= 1; size >>= 1; ++sb->scroll_shift; } if (sb->scroll_shift > 0) ++size; info.cbSize = sizeof(info); info.fMask = SIF_POS | SIF_RANGE | SIF_PAGE; info.nPos = val; info.nMin = 0; info.nMax = max; info.nPage = size; SetScrollInfo(sb->id, SB_CTL, &info, TRUE); } /* * Set the current text font. */ void gui_mch_set_font(GuiFont font) { gui.currFont = font; } /* * Set the current text foreground color. */ void gui_mch_set_fg_color(guicolor_T color) { gui.currFgColor = color; } /* * Set the current text background color. */ void gui_mch_set_bg_color(guicolor_T color) { gui.currBgColor = color; } /* * Set the current text special color. */ void gui_mch_set_sp_color(guicolor_T color) { gui.currSpColor = color; } #ifdef FEAT_MBYTE_IME /* * Multi-byte handling, originally by Sung-Hoon Baek. * First static functions (no prototypes generated). */ # ifdef _MSC_VER # include // Apparently not needed for Cygwin or MinGW. # endif # include /* * handle WM_IME_NOTIFY message */ static LRESULT _OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData UNUSED) { LRESULT lResult = 0; HIMC hImc; if (!pImmGetContext || (hImc = pImmGetContext(hWnd)) == (HIMC)0) return lResult; switch (dwCommand) { case IMN_SETOPENSTATUS: if (pImmGetOpenStatus(hImc)) { LOGFONTW lf = norm_logfont; if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE) // Work around when PerMonitorV2 is not enabled in the process level. lf.lfHeight = lf.lfHeight * DEFAULT_DPI / s_dpi; pImmSetCompositionFontW(hImc, &lf); im_set_position(gui.row, gui.col); // Disable langmap State &= ~MODE_LANGMAP; if (State & MODE_INSERT) { # if defined(FEAT_KEYMAP) // Unshown 'keymap' in status lines if (curbuf->b_p_iminsert == B_IMODE_LMAP) { // Save cursor position int old_row = gui.row; int old_col = gui.col; // This must be called here before // status_redraw_curbuf(), otherwise the mode // message may appear in the wrong position. showmode(); status_redraw_curbuf(); update_screen(0); // Restore cursor position gui.row = old_row; gui.col = old_col; } # endif } } gui_update_cursor(TRUE, FALSE); gui_mch_flush(); lResult = 0; break; } pImmReleaseContext(hWnd, hImc); return lResult; } static LRESULT _OnImeComposition(HWND hwnd, WPARAM dbcs UNUSED, LPARAM param) { char_u *ret; int len; if ((param & GCS_RESULTSTR) == 0) // Composition unfinished. return 0; ret = GetResultStr(hwnd, GCS_RESULTSTR, &len); if (ret != NULL) { add_to_input_buf_csi(ret, len); vim_free(ret); return 1; } return 0; } /* * void GetResultStr() * * This handles WM_IME_COMPOSITION with GCS_RESULTSTR flag on. * get complete composition string */ static char_u * GetResultStr(HWND hwnd, int GCS, int *lenp) { HIMC hIMC; // Input context handle. LONG ret; WCHAR *buf = NULL; char_u *convbuf = NULL; if (!pImmGetContext || (hIMC = pImmGetContext(hwnd)) == (HIMC)0) return NULL; // Get the length of the composition string. ret = pImmGetCompositionStringW(hIMC, GCS, NULL, 0); if (ret <= 0) return NULL; // Allocate the requested buffer plus space for the NUL character. buf = alloc(ret + sizeof(WCHAR)); if (buf == NULL) return NULL; // Reads in the composition string. pImmGetCompositionStringW(hIMC, GCS, buf, ret); *lenp = ret / sizeof(WCHAR); convbuf = utf16_to_enc(buf, lenp); pImmReleaseContext(hwnd, hIMC); vim_free(buf); return convbuf; } #endif // For global functions we need prototypes. #if defined(FEAT_MBYTE_IME) || defined(PROTO) /* * set font to IM. */ void im_set_font(LOGFONTW *lf) { HIMC hImc; if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0) { pImmSetCompositionFontW(hImc, lf); pImmReleaseContext(s_hwnd, hImc); } } /* * Notify cursor position to IM. */ void im_set_position(int row, int col) { HIMC hImc; if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0) { COMPOSITIONFORM cfs; cfs.dwStyle = CFS_POINT; cfs.ptCurrentPos.x = FILL_X(col); cfs.ptCurrentPos.y = FILL_Y(row); MapWindowPoints(s_textArea, s_hwnd, &cfs.ptCurrentPos, 1); if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE) { // Work around when PerMonitorV2 is not enabled in the process level. cfs.ptCurrentPos.x = cfs.ptCurrentPos.x * DEFAULT_DPI / s_dpi; cfs.ptCurrentPos.y = cfs.ptCurrentPos.y * DEFAULT_DPI / s_dpi; } pImmSetCompositionWindow(hImc, &cfs); pImmReleaseContext(s_hwnd, hImc); } } /* * Set IM status on ("active" is TRUE) or off ("active" is FALSE). */ void im_set_active(int active) { HIMC hImc; static HIMC hImcOld = (HIMC)0; # ifdef VIMDLL if (!gui.in_use && !gui.starting) { mbyte_im_set_active(active); return; } # endif if (!pImmGetContext) // if NULL imm32.dll wasn't loaded (yet) return; if (p_imdisable) { if (hImcOld == (HIMC)0) { hImcOld = pImmGetContext(s_hwnd); if (hImcOld) pImmAssociateContext(s_hwnd, (HIMC)0); } active = FALSE; } else if (hImcOld != (HIMC)0) { pImmAssociateContext(s_hwnd, hImcOld); hImcOld = (HIMC)0; } hImc = pImmGetContext(s_hwnd); if (!hImc) return; /* * for Korean ime */ HKL hKL = GetKeyboardLayout(0); if (LOWORD(hKL) == MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN)) { static DWORD dwConversionSaved = 0, dwSentenceSaved = 0; static BOOL bSaved = FALSE; if (active) { // if we have a saved conversion status, restore it if (bSaved) pImmSetConversionStatus(hImc, dwConversionSaved, dwSentenceSaved); bSaved = FALSE; } else { // save conversion status and disable korean if (pImmGetConversionStatus(hImc, &dwConversionSaved, &dwSentenceSaved)) { bSaved = TRUE; pImmSetConversionStatus(hImc, dwConversionSaved & ~(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE), dwSentenceSaved); } } } pImmSetOpenStatus(hImc, active); pImmReleaseContext(s_hwnd, hImc); } /* * Get IM status. When IM is on, return not 0. Else return 0. */ int im_get_status(void) { int status = 0; HIMC hImc; # ifdef VIMDLL if (!gui.in_use && !gui.starting) return mbyte_im_get_status(); # endif if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0) { status = pImmGetOpenStatus(hImc) ? 1 : 0; pImmReleaseContext(s_hwnd, hImc); } return status; } #endif // FEAT_MBYTE_IME /* * Convert latin9 text "text[len]" to ucs-2 in "unicodebuf". */ static void latin9_to_ucs(char_u *text, int len, WCHAR *unicodebuf) { int c; while (--len >= 0) { c = *text++; switch (c) { case 0xa4: c = 0x20ac; break; // euro case 0xa6: c = 0x0160; break; // S hat case 0xa8: c = 0x0161; break; // S -hat case 0xb4: c = 0x017d; break; // Z hat case 0xb8: c = 0x017e; break; // Z -hat case 0xbc: c = 0x0152; break; // OE case 0xbd: c = 0x0153; break; // oe case 0xbe: c = 0x0178; break; // Y } *unicodebuf++ = c; } } #ifdef FEAT_RIGHTLEFT /* * What is this for? In the case where you are using Win98 or Win2K or later, * and you are using a Hebrew font (or Arabic!), Windows does you a favor and * reverses the string sent to the TextOut... family. This sucks, because we * go to a lot of effort to do the right thing, and there doesn't seem to be a * way to tell Windblows not to do this! * * The short of it is that this 'RevOut' only gets called if you are running * one of the new, "improved" MS OSes, and only if you are running in * 'rightleft' mode. It makes display take *slightly* longer, but not * noticeably so. */ static void RevOut( HDC hdc, int col, int row, UINT foptions, CONST RECT *pcliprect, LPCTSTR text, UINT len, CONST INT *padding) { int ix; for (ix = 0; ix < (int)len; ++ix) ExtTextOut(hdc, col + TEXT_X(ix), row, foptions, pcliprect, text + ix, 1, padding); } #endif static void draw_line( int x1, int y1, int x2, int y2, COLORREF color) { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_DrawLine(s_dwc, x1, y1, x2, y2, color); else #endif { HPEN hpen = CreatePen(PS_SOLID, 1, color); HPEN old_pen = SelectObject(s_hdc, hpen); MoveToEx(s_hdc, x1, y1, NULL); // Note: LineTo() excludes the last pixel in the line. LineTo(s_hdc, x2, y2); DeleteObject(SelectObject(s_hdc, old_pen)); } } static void set_pixel( int x, int y, COLORREF color) { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_SetPixel(s_dwc, x, y, color); else #endif SetPixel(s_hdc, x, y, color); } static void fill_rect( const RECT *rcp, HBRUSH hbr, COLORREF color) { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_FillRect(s_dwc, rcp, color); else #endif { HBRUSH hbr2; if (hbr == NULL) hbr2 = CreateSolidBrush(color); else hbr2 = hbr; FillRect(s_hdc, rcp, hbr2); if (hbr == NULL) DeleteBrush(hbr2); } } void gui_mch_draw_string( int row, int col, char_u *text, int len, int flags) { static int *padding = NULL; static int pad_size = 0; const RECT *pcliprect = NULL; UINT foptions = 0; static WCHAR *unicodebuf = NULL; static int *unicodepdy = NULL; static int unibuflen = 0; int n = 0; int y; /* * Italic and bold text seems to have an extra row of pixels at the bottom * (below where the bottom of the character should be). If we draw the * characters with a solid background, the top row of pixels in the * character below will be overwritten. We can fix this by filling in the * background ourselves, to the correct character proportions, and then * writing the character in transparent mode. Still have a problem when * the character is "_", which gets written on to the character below. * New fix: set gui.char_ascent to -1. This shifts all characters up one * pixel in their slots, which fixes the problem with the bottom row of * pixels. We still need this code because otherwise the top row of pixels * becomes a problem. - webb. */ static HBRUSH hbr_cache[2] = {NULL, NULL}; static guicolor_T brush_color[2] = {INVALCOLOR, INVALCOLOR}; static int brush_lru = 0; HBRUSH hbr; RECT rc; if (!(flags & DRAW_TRANSP)) { /* * Clear background first. * Note: FillRect() excludes right and bottom of rectangle. */ rc.left = FILL_X(col); rc.top = FILL_Y(row); if (has_mbyte) { // Compute the length in display cells. rc.right = FILL_X(col + mb_string2cells(text, len)); } else rc.right = FILL_X(col + len); rc.bottom = FILL_Y(row + 1); // Cache the created brush, that saves a lot of time. We need two: // one for cursor background and one for the normal background. if (gui.currBgColor == brush_color[0]) { hbr = hbr_cache[0]; brush_lru = 1; } else if (gui.currBgColor == brush_color[1]) { hbr = hbr_cache[1]; brush_lru = 0; } else { if (hbr_cache[brush_lru] != NULL) DeleteBrush(hbr_cache[brush_lru]); hbr_cache[brush_lru] = CreateSolidBrush(gui.currBgColor); brush_color[brush_lru] = gui.currBgColor; hbr = hbr_cache[brush_lru]; brush_lru = !brush_lru; } fill_rect(&rc, hbr, gui.currBgColor); SetBkMode(s_hdc, TRANSPARENT); /* * When drawing block cursor, prevent inverted character spilling * over character cell (can happen with bold/italic) */ if (flags & DRAW_CURSOR) { pcliprect = &rc; foptions = ETO_CLIPPED; } } SetTextColor(s_hdc, gui.currFgColor); SelectFont(s_hdc, gui.currFont); #ifdef FEAT_DIRECTX if (IS_ENABLE_DIRECTX()) DWriteContext_SetFont(s_dwc, (HFONT)gui.currFont); #endif if (pad_size != Columns || padding == NULL || padding[0] != gui.char_width) { int i; vim_free(padding); pad_size = Columns; // Don't give an out-of-memory message here, it would call us // recursively. padding = LALLOC_MULT(int, pad_size); if (padding != NULL) for (i = 0; i < pad_size; i++) padding[i] = gui.char_width; } /* * We have to provide the padding argument because italic and bold versions * of fixed-width fonts are often one pixel or so wider than their normal * versions. * No check for DRAW_BOLD, Windows will have done it already. */ // Check if there are any UTF-8 characters. If not, use normal text // output to speed up output. if (enc_utf8) for (n = 0; n < len; ++n) if (text[n] >= 0x80) break; #if defined(FEAT_DIRECTX) // Quick hack to enable DirectWrite. To use DirectWrite (antialias), it is // required that unicode drawing routine, currently. So this forces it // enabled. if (IS_ENABLE_DIRECTX()) n = 0; // Keep n < len, to enter block for unicode. #endif // Check if the Unicode buffer exists and is big enough. Create it // with the same length as the multi-byte string, the number of wide // characters is always equal or smaller. if ((enc_utf8 || (enc_codepage > 0 && (int)GetACP() != enc_codepage) || enc_latin9) && (unicodebuf == NULL || len > unibuflen)) { vim_free(unicodebuf); unicodebuf = LALLOC_MULT(WCHAR, len); vim_free(unicodepdy); unicodepdy = LALLOC_MULT(int, len); unibuflen = len; } if (enc_utf8 && n < len && unicodebuf != NULL) { // Output UTF-8 characters. Composing characters should be // handled here. int i; int wlen; // string length in words int cells; // cell width of string up to composing char int cw; // width of current cell int c; wlen = 0; cells = 0; for (i = 0; i < len; ) { c = utf_ptr2char(text + i); if (c >= 0x10000) { // Turn into UTF-16 encoding. unicodebuf[wlen++] = ((c - 0x10000) >> 10) + 0xD800; unicodebuf[wlen++] = ((c - 0x10000) & 0x3ff) + 0xDC00; } else { unicodebuf[wlen++] = c; } if (utf_iscomposing(c)) cw = 0; else { cw = utf_char2cells(c); if (cw > 2) // don't use 4 for unprintable char cw = 1; } if (unicodepdy != NULL) { // Use unicodepdy to make characters fit as we expect, even // when the font uses different widths (e.g., bold character // is wider). if (c >= 0x10000) { unicodepdy[wlen - 2] = cw * gui.char_width; unicodepdy[wlen - 1] = 0; } else unicodepdy[wlen - 1] = cw * gui.char_width; } cells += cw; i += utf_ptr2len_len(text + i, len - i); } #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) { // Add one to "cells" for italics. DWriteContext_DrawText(s_dwc, unicodebuf, wlen, TEXT_X(col), TEXT_Y(row), FILL_X(cells + 1), FILL_Y(1) - p_linespace, gui.char_width, gui.currFgColor, foptions, pcliprect, unicodepdy); } else #endif ExtTextOutW(s_hdc, TEXT_X(col), TEXT_Y(row), foptions, pcliprect, unicodebuf, wlen, unicodepdy); len = cells; // used for underlining } else if ((enc_codepage > 0 && (int)GetACP() != enc_codepage) || enc_latin9) { // If we want to display codepage data, and the current CP is not the // ANSI one, we need to go via Unicode. if (unicodebuf != NULL) { if (enc_latin9) latin9_to_ucs(text, len, unicodebuf); else len = MultiByteToWideChar(enc_codepage, MB_PRECOMPOSED, (char *)text, len, (LPWSTR)unicodebuf, unibuflen); if (len != 0) { // Use unicodepdy to make characters fit as we expect, even // when the font uses different widths (e.g., bold character // is wider). if (unicodepdy != NULL) { int i; int cw; for (i = 0; i < len; ++i) { cw = utf_char2cells(unicodebuf[i]); if (cw > 2) cw = 1; unicodepdy[i] = cw * gui.char_width; } } ExtTextOutW(s_hdc, TEXT_X(col), TEXT_Y(row), foptions, pcliprect, unicodebuf, len, unicodepdy); } } } else { #ifdef FEAT_RIGHTLEFT // Windows will mess up RL text, so we have to draw it character by // character. Only do this if RL is on, since it's slow. if (curwin->w_p_rl) RevOut(s_hdc, TEXT_X(col), TEXT_Y(row), foptions, pcliprect, (char *)text, len, padding); else #endif ExtTextOut(s_hdc, TEXT_X(col), TEXT_Y(row), foptions, pcliprect, (char *)text, len, padding); } // Underline if (flags & DRAW_UNDERL) { // When p_linespace is 0, overwrite the bottom row of pixels. // Otherwise put the line just below the character. y = FILL_Y(row + 1) - 1; if (p_linespace > 1) y -= p_linespace - 1; draw_line(FILL_X(col), y, FILL_X(col + len), y, gui.currFgColor); } // Strikethrough if (flags & DRAW_STRIKE) { y = FILL_Y(row + 1) - gui.char_height/2; draw_line(FILL_X(col), y, FILL_X(col + len), y, gui.currSpColor); } // Undercurl if (flags & DRAW_UNDERC) { int x; int offset; static const int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 }; y = FILL_Y(row + 1) - 1; for (x = FILL_X(col); x < FILL_X(col + len); ++x) { offset = val[x % 8]; set_pixel(x, y - offset, gui.currSpColor); } } } /* * Output routines. */ /* * Flush any output to the screen */ void gui_mch_flush(void) { #if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); #endif GdiFlush(); } static void clear_rect(RECT *rcp) { fill_rect(rcp, NULL, gui.back_pixel); } void gui_mch_get_screen_dimensions(int *screen_w, int *screen_h) { RECT workarea_rect; get_work_area(&workarea_rect); *screen_w = workarea_rect.right - workarea_rect.left - (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 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 = workarea_rect.bottom - workarea_rect.top - (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2 - pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi) - gui_mswin_get_menu_height(FALSE); } #if defined(FEAT_MENU) || defined(PROTO) /* * Add a sub menu to the menu bar. */ void gui_mch_add_menu( vimmenu_T *menu, int pos) { vimmenu_T *parent = menu->parent; menu->submenu_id = CreatePopupMenu(); menu->id = s_menu_id++; if (menu_is_menubar(menu->name)) { WCHAR *wn; MENUITEMINFOW infow; wn = enc_to_utf16(menu->name, NULL); if (wn == NULL) return; infow.cbSize = sizeof(infow); infow.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU; infow.dwItemData = (long_u)menu; infow.wID = menu->id; infow.fType = MFT_STRING; infow.dwTypeData = wn; infow.cch = (UINT)wcslen(wn); infow.hSubMenu = menu->submenu_id; InsertMenuItemW((parent == NULL) ? s_menuBar : parent->submenu_id, (UINT)pos, TRUE, &infow); vim_free(wn); } // Fix window size if menu may have wrapped if (parent == NULL) gui_mswin_get_menu_height(!gui.starting); # ifdef FEAT_TEAROFF else if (IsWindow(parent->tearoff_handle)) rebuild_tearoff(parent); # endif } void gui_mch_show_popupmenu(vimmenu_T *menu) { POINT mp; (void)GetCursorPos(&mp); gui_mch_show_popupmenu_at(menu, (int)mp.x, (int)mp.y); } void gui_make_popup(char_u *path_name, int mouse_pos) { vimmenu_T *menu = gui_find_menu(path_name); if (menu == NULL) return; POINT p; // Find the position of the current cursor GetDCOrgEx(s_hdc, &p); if (mouse_pos) { int mx, my; gui_mch_getmouse(&mx, &my); p.x += mx; p.y += my; } else if (curwin != NULL) { p.x += TEXT_X(curwin->w_wincol + curwin->w_wcol + 1); p.y += TEXT_Y(W_WINROW(curwin) + curwin->w_wrow + 1); } msg_scroll = FALSE; gui_mch_show_popupmenu_at(menu, (int)p.x, (int)p.y); } # if defined(FEAT_TEAROFF) || defined(PROTO) /* * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and * create it as a pseudo-"tearoff menu". */ void gui_make_tearoff(char_u *path_name) { vimmenu_T *menu = gui_find_menu(path_name); // Found the menu, so tear it off. if (menu != NULL) gui_mch_tearoff(menu->dname, menu, 0xffffL, 0xffffL); } # endif /* * Add a menu item to a menu */ void gui_mch_add_menu_item( vimmenu_T *menu, int idx) { vimmenu_T *parent = menu->parent; menu->id = s_menu_id++; menu->submenu_id = NULL; # ifdef FEAT_TEAROFF if (STRNCMP(menu->name, TEAR_STRING, TEAR_LEN) == 0) { InsertMenu(parent->submenu_id, (UINT)idx, MF_BITMAP|MF_BYPOSITION, (UINT)menu->id, (LPCTSTR) s_htearbitmap); } else # endif # ifdef FEAT_TOOLBAR if (menu_is_toolbar(parent->name)) { TBBUTTON newtb; CLEAR_FIELD(newtb); if (menu_is_separator(menu->name)) { newtb.iBitmap = 0; newtb.fsStyle = TBSTYLE_SEP; } else { newtb.iBitmap = get_toolbar_bitmap(menu); newtb.fsStyle = TBSTYLE_BUTTON; } newtb.idCommand = menu->id; newtb.fsState = TBSTATE_ENABLED; newtb.iString = 0; SendMessage(s_toolbarhwnd, TB_INSERTBUTTON, (WPARAM)idx, (LPARAM)&newtb); menu->submenu_id = (HMENU)-1; } else # endif { WCHAR *wn; wn = enc_to_utf16(menu->name, NULL); if (wn != NULL) { InsertMenuW(parent->submenu_id, (UINT)idx, (menu_is_separator(menu->name) ? MF_SEPARATOR : MF_STRING) | MF_BYPOSITION, (UINT)menu->id, wn); vim_free(wn); } # ifdef FEAT_TEAROFF if (IsWindow(parent->tearoff_handle)) rebuild_tearoff(parent); # endif } } /* * Destroy the machine specific menu widget. */ void gui_mch_destroy_menu(vimmenu_T *menu) { # ifdef FEAT_TOOLBAR /* * is this a toolbar button? */ if (menu->submenu_id == (HMENU)-1) { int iButton; iButton = (int)SendMessage(s_toolbarhwnd, TB_COMMANDTOINDEX, (WPARAM)menu->id, 0); SendMessage(s_toolbarhwnd, TB_DELETEBUTTON, (WPARAM)iButton, 0); } else # endif { if (menu->parent != NULL && menu_is_popup(menu->parent->dname) && menu->parent->submenu_id != NULL) RemoveMenu(menu->parent->submenu_id, menu->id, MF_BYCOMMAND); else RemoveMenu(s_menuBar, menu->id, MF_BYCOMMAND); if (menu->submenu_id != NULL) DestroyMenu(menu->submenu_id); # ifdef FEAT_TEAROFF if (IsWindow(menu->tearoff_handle)) DestroyWindow(menu->tearoff_handle); if (menu->parent != NULL && menu->parent->children != NULL && IsWindow(menu->parent->tearoff_handle)) { // This menu must not show up when rebuilding the tearoff window. menu->modes = 0; rebuild_tearoff(menu->parent); } # endif } } # ifdef FEAT_TEAROFF static void rebuild_tearoff(vimmenu_T *menu) { //hackish char_u tbuf[128]; RECT trect; RECT rct; RECT roct; int x, y; HWND thwnd = menu->tearoff_handle; GetWindowText(thwnd, (LPSTR)tbuf, 127); if (GetWindowRect(thwnd, &trect) && GetWindowRect(s_hwnd, &rct) && GetClientRect(s_hwnd, &roct)) { x = trect.left - rct.left; y = (trect.top - rct.bottom + roct.bottom); } else { x = y = 0xffffL; } DestroyWindow(thwnd); if (menu->children != NULL) { gui_mch_tearoff(tbuf, menu, x, y); if (IsWindow(menu->tearoff_handle)) (void) SetWindowPos(menu->tearoff_handle, NULL, (int)trect.left, (int)trect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } } # endif // FEAT_TEAROFF /* * Make a menu either grey or not grey. */ void gui_mch_menu_grey( vimmenu_T *menu, int grey) { # ifdef FEAT_TOOLBAR /* * is this a toolbar button? */ if (menu->submenu_id == (HMENU)-1) { SendMessage(s_toolbarhwnd, TB_ENABLEBUTTON, (WPARAM)menu->id, (LPARAM) MAKELONG((grey ? FALSE : TRUE), 0)); } else # endif (void)EnableMenuItem(menu->parent ? menu->parent->submenu_id : s_menuBar, menu->id, MF_BYCOMMAND | (grey ? MF_GRAYED : MF_ENABLED)); # ifdef FEAT_TEAROFF if ((menu->parent != NULL) && (IsWindow(menu->parent->tearoff_handle))) { WORD menuID; HWND menuHandle; /* * A tearoff button has changed state. */ if (menu->children == NULL) menuID = (WORD)(menu->id); else menuID = (WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000); menuHandle = GetDlgItem(menu->parent->tearoff_handle, menuID); if (menuHandle) EnableWindow(menuHandle, !grey); } # endif } #endif // FEAT_MENU // define some macros used to make the dialogue creation more readable #define add_word(x) *p++ = (x) #define add_long(x) dwp = (DWORD *)p; *dwp++ = (x); p = (WORD *)dwp #if defined(FEAT_GUI_DIALOG) || defined(PROTO) /* * stuff for dialogs */ /* * The callback routine used by all the dialogs. Very simple. First, * acknowledges the INITDIALOG message so that Windows knows to do standard * dialog stuff (Return = default, Esc = cancel....) Second, if a button is * pressed, return that button's ID - IDCANCEL (2), which is the button's * number. */ static LRESULT CALLBACK dialog_callback( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam UNUSED) { if (message == WM_INITDIALOG) { CenterWindow(hwnd, GetWindow(hwnd, GW_OWNER)); // Set focus to the dialog. Set the default button, if specified. (void)SetFocus(hwnd); if (dialog_default_button > IDCANCEL) (void)SetFocus(GetDlgItem(hwnd, dialog_default_button)); else // We don't have a default, set focus on another element of the // dialog window, probably the icon (void)SetFocus(GetDlgItem(hwnd, DLG_NONBUTTON_CONTROL)); return FALSE; } if (message == WM_COMMAND) { int button = LOWORD(wParam); // Don't end the dialog if something was selected that was // not a button. if (button >= DLG_NONBUTTON_CONTROL) return TRUE; // If the edit box exists, copy the string. if (s_textfield != NULL) { WCHAR *wp = ALLOC_MULT(WCHAR, IOSIZE); char_u *p; GetDlgItemTextW(hwnd, DLG_NONBUTTON_CONTROL + 2, wp, IOSIZE); p = utf16_to_enc(wp, NULL); vim_strncpy(s_textfield, p, IOSIZE); vim_free(p); vim_free(wp); } /* * Need to check for IDOK because if the user just hits Return to * accept the default value, some reason this is what we get. */ if (button == IDOK) { if (dialog_default_button > IDCANCEL) EndDialog(hwnd, dialog_default_button); } else EndDialog(hwnd, button - IDCANCEL); return TRUE; } if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) { EndDialog(hwnd, 0); return TRUE; } return FALSE; } /* * Create a dialog dynamically from the parameter strings. * type = type of dialog (question, alert, etc.) * title = dialog title. may be NULL for default title. * message = text to display. Dialog sizes to accommodate it. * buttons = '\n' separated list of button captions, default first. * dfltbutton = number of default button. * * This routine returns 1 if the first button is pressed, * 2 for the second, etc. * * 0 indicates Esc was pressed. * -1 for unexpected error * * If stubbing out this fn, return 1. */ static const char *dlg_icons[] = // must match names in resource file { "IDR_VIM", "IDR_VIM_ERROR", "IDR_VIM_ALERT", "IDR_VIM_INFO", "IDR_VIM_QUESTION" }; int gui_mch_dialog( int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd UNUSED) { WORD *p, *pdlgtemplate, *pnumitems; DWORD *dwp; int numButtons; int *buttonWidths, *buttonPositions; int buttonYpos; int nchar, i; DWORD lStyle; int dlgwidth = 0; int dlgheight; int editboxheight; int horizWidth = 0; int msgheight; char_u *pstart; char_u *pend; char_u *last_white; char_u *tbuffer; RECT rect; HWND hwnd; HDC hdc; HFONT font, oldFont; TEXTMETRIC fontInfo; int fontHeight; int textWidth, minButtonWidth, messageWidth; int maxDialogWidth; int maxDialogHeight; int scroll_flag = 0; int vertical; int dlgPaddingX; int dlgPaddingY; # ifdef USE_SYSMENU_FONT LOGFONTW lfSysmenu; int use_lfSysmenu = FALSE; # endif garray_T ga; int l; int dlg_icon_width; int dlg_icon_height; int dpi; # ifndef NO_CONSOLE // Don't output anything in silent mode ("ex -s") # ifdef VIMDLL if (!(gui.in_use || gui.starting)) # endif if (silent_mode) return dfltbutton; // return default option # endif if (s_hwnd == NULL) { load_dpi_func(); s_dpi = dpi = pGetDpiForSystem(); get_dialog_font_metrics(); } else dpi = pGetDpiForSystem(); if ((type < 0) || (type > VIM_LAST_TYPE)) type = 0; // allocate some memory for dialog template // TODO should compute this really pdlgtemplate = p = (PWORD)LocalAlloc(LPTR, DLG_ALLOC_SIZE + STRLEN(message) * 2); if (p == NULL) return -1; /* * make a copy of 'buttons' to fiddle with it. compiler grizzles because * vim_strsave() doesn't take a const arg (why not?), so cast away the * const. */ tbuffer = vim_strsave(buttons); if (tbuffer == NULL) return -1; --dfltbutton; // Change from one-based to zero-based // Count buttons numButtons = 1; for (i = 0; tbuffer[i] != '\0'; i++) { if (tbuffer[i] == DLG_BUTTON_SEP) numButtons++; } if (dfltbutton >= numButtons) dfltbutton = -1; // Allocate array to hold the width of each button buttonWidths = ALLOC_MULT(int, numButtons); if (buttonWidths == NULL) return -1; // Allocate array to hold the X position of each button buttonPositions = ALLOC_MULT(int, numButtons); if (buttonPositions == NULL) return -1; /* * Calculate how big the dialog must be. */ hwnd = GetDesktopWindow(); hdc = GetWindowDC(hwnd); # ifdef USE_SYSMENU_FONT if (gui_w32_get_menu_font(&lfSysmenu) == OK) { font = CreateFontIndirectW(&lfSysmenu); use_lfSysmenu = TRUE; } else # endif font = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME); oldFont = SelectFont(hdc, font); dlgPaddingX = DLG_PADDING_X; dlgPaddingY = DLG_PADDING_Y; GetTextMetrics(hdc, &fontInfo); fontHeight = fontInfo.tmHeight; // Minimum width for horizontal button minButtonWidth = GetTextWidth(hdc, (char_u *)"Cancel", 6); // Maximum width of a dialog, if possible if (s_hwnd == NULL) { RECT workarea_rect; // We don't have a window, use the desktop area. get_work_area(&workarea_rect); maxDialogWidth = workarea_rect.right - workarea_rect.left - 100; if (maxDialogWidth > adjust_by_system_dpi(600)) maxDialogWidth = adjust_by_system_dpi(600); // Leave some room for the taskbar. maxDialogHeight = workarea_rect.bottom - workarea_rect.top - 150; } else { // Use our own window for the size, unless it's very small. GetWindowRect(s_hwnd, &rect); maxDialogWidth = rect.right - rect.left - (pGetSystemMetricsForDpi(SM_CXFRAME, dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) * 2; if (maxDialogWidth < adjust_by_system_dpi(DLG_MIN_MAX_WIDTH)) maxDialogWidth = adjust_by_system_dpi(DLG_MIN_MAX_WIDTH); maxDialogHeight = rect.bottom - rect.top - (pGetSystemMetricsForDpi(SM_CYFRAME, dpi) + pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) * 4 - pGetSystemMetricsForDpi(SM_CYCAPTION, dpi); if (maxDialogHeight < adjust_by_system_dpi(DLG_MIN_MAX_HEIGHT)) maxDialogHeight = adjust_by_system_dpi(DLG_MIN_MAX_HEIGHT); } // Set dlgwidth to width of message. // Copy the message into "ga", changing NL to CR-NL and inserting line // breaks where needed. pstart = message; messageWidth = 0; msgheight = 0; ga_init2(&ga, sizeof(char), 500); do { msgheight += fontHeight; // at least one line // Need to figure out where to break the string. The system does it // at a word boundary, which would mean we can't compute the number of // wrapped lines. textWidth = 0; last_white = NULL; for (pend = pstart; *pend != NUL && *pend != '\n'; ) { l = (*mb_ptr2len)(pend); if (l == 1 && VIM_ISWHITE(*pend) && textWidth > maxDialogWidth * 3 / 4) last_white = pend; textWidth += GetTextWidthEnc(hdc, pend, l); if (textWidth >= maxDialogWidth) { // Line will wrap. messageWidth = maxDialogWidth; msgheight += fontHeight; textWidth = 0; if (last_white != NULL) { // break the line just after a space if (pend > last_white) ga.ga_len -= (int)(pend - (last_white + 1)); pend = last_white + 1; last_white = NULL; } ga_append(&ga, '\r'); ga_append(&ga, '\n'); continue; } while (--l >= 0) ga_append(&ga, *pend++); } if (textWidth > messageWidth) messageWidth = textWidth; ga_append(&ga, '\r'); ga_append(&ga, '\n'); pstart = pend + 1; } while (*pend != NUL); if (ga.ga_data != NULL) message = ga.ga_data; messageWidth += 10; // roundoff space dlg_icon_width = adjust_by_system_dpi(DLG_ICON_WIDTH); dlg_icon_height = adjust_by_system_dpi(DLG_ICON_HEIGHT); // Add width of icon to dlgwidth, and some space dlgwidth = messageWidth + dlg_icon_width + 3 * dlgPaddingX + pGetSystemMetricsForDpi(SM_CXVSCROLL, dpi); if (msgheight < dlg_icon_height) msgheight = dlg_icon_height; /* * Check button names. A long one will make the dialog wider. * When called early (-register error message) p_go isn't initialized. */ vertical = (p_go != NULL && vim_strchr(p_go, GO_VERTICAL) != NULL); if (!vertical) { // Place buttons horizontally if they fit. horizWidth = dlgPaddingX; pstart = tbuffer; i = 0; do { pend = vim_strchr(pstart, DLG_BUTTON_SEP); if (pend == NULL) pend = pstart + STRLEN(pstart); // Last button name. textWidth = GetTextWidthEnc(hdc, pstart, (int)(pend - pstart)); if (textWidth < minButtonWidth) textWidth = minButtonWidth; textWidth += dlgPaddingX; // Padding within button buttonWidths[i] = textWidth; buttonPositions[i++] = horizWidth; horizWidth += textWidth + dlgPaddingX; // Pad between buttons pstart = pend + 1; } while (*pend != NUL); if (horizWidth > maxDialogWidth) vertical = TRUE; // Too wide to fit on the screen. else if (horizWidth > dlgwidth) dlgwidth = horizWidth; } if (vertical) { // Stack buttons vertically. pstart = tbuffer; do { pend = vim_strchr(pstart, DLG_BUTTON_SEP); if (pend == NULL) pend = pstart + STRLEN(pstart); // Last button name. textWidth = GetTextWidthEnc(hdc, pstart, (int)(pend - pstart)); textWidth += dlgPaddingX; // Padding within button textWidth += DLG_VERT_PADDING_X * 2; // Padding around button if (textWidth > dlgwidth) dlgwidth = textWidth; pstart = pend + 1; } while (*pend != NUL); } if (dlgwidth < DLG_MIN_WIDTH) dlgwidth = DLG_MIN_WIDTH; // Don't allow a really thin dialog! // start to fill in the dlgtemplate information. addressing by WORDs lStyle = DS_MODALFRAME | WS_CAPTION | DS_3DLOOK | WS_VISIBLE | DS_SETFONT; add_long(lStyle); add_long(0); // (lExtendedStyle) pnumitems = p; //save where the number of items must be stored add_word(0); // NumberOfItems(will change later) add_word(10); // x add_word(10); // y add_word(PixelToDialogX(dlgwidth)); // cx // Dialog height. if (vertical) dlgheight = msgheight + 2 * dlgPaddingY + DLG_VERT_PADDING_Y + 2 * fontHeight * numButtons; else dlgheight = msgheight + 3 * dlgPaddingY + 2 * fontHeight; // Dialog needs to be taller if contains an edit box. editboxheight = fontHeight + dlgPaddingY + 4 * DLG_VERT_PADDING_Y; if (textfield != NULL) dlgheight += editboxheight; // Restrict the size to a maximum. Causes a scrollbar to show up. if (dlgheight > maxDialogHeight) { msgheight = msgheight - (dlgheight - maxDialogHeight); dlgheight = maxDialogHeight; scroll_flag = WS_VSCROLL; // Make sure scrollbar doesn't appear in the middle of the dialog messageWidth = dlgwidth - dlg_icon_width - 3 * dlgPaddingX; } add_word(PixelToDialogY(dlgheight)); add_word(0); // Menu add_word(0); // Class // copy the title of the dialog nchar = nCopyAnsiToWideChar(p, (title ? (LPSTR)title : (LPSTR)("Vim "VIM_VERSION_MEDIUM)), TRUE); p += nchar; // do the font, since DS_3DLOOK doesn't work properly # ifdef USE_SYSMENU_FONT if (use_lfSysmenu) { // point size *p++ = -MulDiv(lfSysmenu.lfHeight, 72, GetDeviceCaps(hdc, LOGPIXELSY)); wcscpy(p, lfSysmenu.lfFaceName); nchar = (int)wcslen(lfSysmenu.lfFaceName) + 1; } else # endif { *p++ = DLG_FONT_POINT_SIZE; // point size nchar = nCopyAnsiToWideChar(p, DLG_FONT_NAME, FALSE); } p += nchar; buttonYpos = msgheight + 2 * dlgPaddingY; if (textfield != NULL) buttonYpos += editboxheight; pstart = tbuffer; if (!vertical) horizWidth = (dlgwidth - horizWidth) / 2; // Now it's X offset for (i = 0; i < numButtons; i++) { // get end of this button. for ( pend = pstart; *pend && (*pend != DLG_BUTTON_SEP); pend++) ; if (*pend) *pend = '\0'; /* * old NOTE: * setting the BS_DEFPUSHBUTTON style doesn't work because Windows sets * the focus to the first tab-able button and in so doing makes that * the default!! Grrr. Workaround: Make the default button the only * one with WS_TABSTOP style. Means user can't tab between buttons, but * he/she can use arrow keys. * * new NOTE: BS_DEFPUSHBUTTON is required to be able to select the * right button when hitting . E.g., for the ":confirm quit" * dialog. Also needed for when the textfield is the default control. * It appears to work now (perhaps not on Win95?). */ if (vertical) { p = add_dialog_element(p, (i == dfltbutton ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, PixelToDialogX(DLG_VERT_PADDING_X), PixelToDialogY(buttonYpos // TBK + 2 * fontHeight * i), PixelToDialogX(dlgwidth - 2 * DLG_VERT_PADDING_X), (WORD)(PixelToDialogY(2 * fontHeight) - 1), (WORD)(IDCANCEL + 1 + i), (WORD)0x0080, (char *)pstart); } else { p = add_dialog_element(p, (i == dfltbutton ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, PixelToDialogX(horizWidth + buttonPositions[i]), PixelToDialogY(buttonYpos), // TBK PixelToDialogX(buttonWidths[i]), (WORD)(PixelToDialogY(2 * fontHeight) - 1), (WORD)(IDCANCEL + 1 + i), (WORD)0x0080, (char *)pstart); } pstart = pend + 1; //next button } *pnumitems += numButtons; // Vim icon p = add_dialog_element(p, SS_ICON, PixelToDialogX(dlgPaddingX), PixelToDialogY(dlgPaddingY), PixelToDialogX(dlg_icon_width), PixelToDialogY(dlg_icon_height), DLG_NONBUTTON_CONTROL + 0, (WORD)0x0082, dlg_icons[type]); // Dialog message p = add_dialog_element(p, ES_LEFT|scroll_flag|ES_MULTILINE|ES_READONLY, PixelToDialogX(2 * dlgPaddingX + dlg_icon_width), PixelToDialogY(dlgPaddingY), (WORD)(PixelToDialogX(messageWidth) + 1), PixelToDialogY(msgheight), DLG_NONBUTTON_CONTROL + 1, (WORD)0x0081, (char *)message); // Edit box if (textfield != NULL) { p = add_dialog_element(p, ES_LEFT|ES_AUTOHSCROLL|WS_TABSTOP|WS_BORDER, PixelToDialogX(2 * dlgPaddingX), PixelToDialogY(2 * dlgPaddingY + msgheight), PixelToDialogX(dlgwidth - 4 * dlgPaddingX), PixelToDialogY(fontHeight + dlgPaddingY), DLG_NONBUTTON_CONTROL + 2, (WORD)0x0081, (char *)textfield); *pnumitems += 1; } *pnumitems += 2; SelectFont(hdc, oldFont); DeleteObject(font); ReleaseDC(hwnd, hdc); // Let the dialog_callback() function know which button to make default // If we have an edit box, make that the default. We also need to tell // dialog_callback() if this dialog contains an edit box or not. We do // this by setting s_textfield if it does. if (textfield != NULL) { dialog_default_button = DLG_NONBUTTON_CONTROL + 2; s_textfield = textfield; } else { dialog_default_button = IDCANCEL + 1 + dfltbutton; s_textfield = NULL; } // show the dialog box modally and get a return value nchar = (int)DialogBoxIndirect( g_hinst, (LPDLGTEMPLATE)pdlgtemplate, s_hwnd, (DLGPROC)dialog_callback); LocalFree(LocalHandle(pdlgtemplate)); vim_free(tbuffer); vim_free(buttonWidths); vim_free(buttonPositions); vim_free(ga.ga_data); // Focus back to our window (for when MDI is used). (void)SetFocus(s_hwnd); return nchar; } #endif // FEAT_GUI_DIALOG /* * Put a simple element (basic class) onto a dialog template in memory. * return a pointer to where the next item should be added. * * parameters: * lStyle = additional style flags * (be careful, NT3.51 & Win32s will ignore the new ones) * x,y = x & y positions IN DIALOG UNITS * w,h = width and height IN DIALOG UNITS * Id = ID used in messages * clss = class ID, e.g 0x0080 for a button, 0x0082 for a static * caption = usually text or resource name * * TODO: use the length information noted here to enable the dialog creation * routines to work out more exactly how much memory they need to alloc. */ static PWORD add_dialog_element( PWORD p, DWORD lStyle, WORD x, WORD y, WORD w, WORD h, WORD Id, WORD clss, const char *caption) { int nchar; p = lpwAlign(p); // Align to dword boundary lStyle = lStyle | WS_VISIBLE | WS_CHILD; *p++ = LOWORD(lStyle); *p++ = HIWORD(lStyle); *p++ = 0; // LOWORD (lExtendedStyle) *p++ = 0; // HIWORD (lExtendedStyle) *p++ = x; *p++ = y; *p++ = w; *p++ = h; *p++ = Id; //9 or 10 words in all *p++ = (WORD)0xffff; *p++ = clss; //2 more here nchar = nCopyAnsiToWideChar(p, (LPSTR)caption, TRUE); //strlen(caption)+1 p += nchar; *p++ = 0; // advance pointer over nExtraStuff WORD - 2 more return p; // total = 15 + strlen(caption) words // bytes read = 2 * total } /* * Helper routine. Take an input pointer, return closest pointer that is * aligned on a DWORD (4 byte) boundary. Taken from the Win32SDK samples. */ static LPWORD lpwAlign( LPWORD lpIn) { long_u ul; ul = (long_u)lpIn; ul += 3; ul >>= 2; ul <<= 2; return (LPWORD)ul; } /* * Helper routine. Takes second parameter as Ansi string, copies it to first * parameter as wide character (16-bits / char) string, and returns integer * number of wide characters (words) in string (including the trailing wide * char NULL). Partly taken from the Win32SDK samples. * If "use_enc" is TRUE, 'encoding' is used for "lpAnsiIn". If FALSE, current * ACP is used for "lpAnsiIn". */ static int nCopyAnsiToWideChar( LPWORD lpWCStr, LPSTR lpAnsiIn, BOOL use_enc) { int nChar = 0; int len = lstrlen(lpAnsiIn) + 1; // include NUL character int i; WCHAR *wn; if (use_enc && enc_codepage >= 0 && (int)GetACP() != enc_codepage) { // Not a codepage, use our own conversion function. wn = enc_to_utf16((char_u *)lpAnsiIn, NULL); if (wn != NULL) { wcscpy(lpWCStr, wn); nChar = (int)wcslen(wn) + 1; vim_free(wn); } } if (nChar == 0) // Use Win32 conversion function. nChar = MultiByteToWideChar( enc_codepage > 0 ? enc_codepage : CP_ACP, MB_PRECOMPOSED, lpAnsiIn, len, lpWCStr, len); for (i = 0; i < nChar; ++i) if (lpWCStr[i] == (WORD)'\t') // replace tabs with spaces lpWCStr[i] = (WORD)' '; return nChar; } #ifdef FEAT_TEAROFF /* * Lookup menu handle from "menu_id". */ static HMENU tearoff_lookup_menuhandle( vimmenu_T *menu, WORD menu_id) { for ( ; menu != NULL; menu = menu->next) { if (menu->modes == 0) // this menu has just been deleted continue; if (menu_is_separator(menu->dname)) continue; if ((WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000) == menu_id) return menu->submenu_id; } return NULL; } /* * The callback function for all the modeless dialogs that make up the * "tearoff menus" Very simple - forward button presses (to fool Vim into * thinking its menus have been clicked), and go away when closed. */ static LRESULT CALLBACK tearoff_callback( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_INITDIALOG) { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); return (TRUE); } // May show the mouse pointer again. HandleMouseHide(message, lParam); if (message == WM_COMMAND) { if ((WORD)(LOWORD(wParam)) & 0x8000) { POINT mp; RECT rect; if (GetCursorPos(&mp) && GetWindowRect(hwnd, &rect)) { vimmenu_T *menu; menu = (vimmenu_T*)GetWindowLongPtr(hwnd, DWLP_USER); (void)TrackPopupMenu( tearoff_lookup_menuhandle(menu, LOWORD(wParam)), TPM_LEFTALIGN | TPM_LEFTBUTTON, (int)rect.right - 8, (int)mp.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. */ } } else // Pass on messages to the main Vim window PostMessage(s_hwnd, WM_COMMAND, LOWORD(wParam), 0); /* * Give main window the focus back: this is so after * choosing a tearoff button you can start typing again * straight away. */ (void)SetFocus(s_hwnd); return TRUE; } if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) { DestroyWindow(hwnd); return TRUE; } // When moved around, give main window the focus back. if (message == WM_EXITSIZEMOVE) (void)SetActiveWindow(s_hwnd); return FALSE; } #endif /* * Computes the dialog base units based on the current dialog font. * We don't use the GetDialogBaseUnits() API, because we don't use the * (old-style) system font. */ static void get_dialog_font_metrics(void) { HDC hdc; HFONT hfontTools = 0; SIZE size; #ifdef USE_SYSMENU_FONT LOGFONTW lfSysmenu; #endif #ifdef USE_SYSMENU_FONT if (gui_w32_get_menu_font(&lfSysmenu) == OK) hfontTools = CreateFontIndirectW(&lfSysmenu); else #endif hfontTools = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME); hdc = GetDC(s_hwnd); SelectObject(hdc, hfontTools); GetAverageFontSize(hdc, &size); ReleaseDC(s_hwnd, hdc); s_dlgfntwidth = (WORD)size.cx; s_dlgfntheight = (WORD)size.cy; } #if defined(FEAT_MENU) && defined(FEAT_TEAROFF) /* * Create a pseudo-"tearoff menu" based on the child * items of a given menu pointer. */ static void gui_mch_tearoff( char_u *title, vimmenu_T *menu, int initX, int initY) { WORD *p, *pdlgtemplate, *pnumitems, *ptrueheight; int template_len; int nchar, textWidth, submenuWidth; DWORD lStyle; DWORD lExtendedStyle; WORD dlgwidth; WORD menuID; vimmenu_T *pmenu; vimmenu_T *top_menu; vimmenu_T *the_menu = menu; HWND hwnd; HDC hdc; HFONT font, oldFont; int col, spaceWidth, len; int columnWidths[2]; char_u *label, *text; int acLen = 0; int nameLen; int padding0, padding1, padding2 = 0; int sepPadding=0; int x; int y; # ifdef USE_SYSMENU_FONT LOGFONTW lfSysmenu; int use_lfSysmenu = FALSE; # endif /* * If this menu is already torn off, move it to the mouse position. */ if (IsWindow(menu->tearoff_handle)) { POINT mp; if (GetCursorPos(&mp)) { SetWindowPos(menu->tearoff_handle, NULL, mp.x, mp.y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); } return; } /* * Create a new tearoff. */ if (*title == MNU_HIDDEN_CHAR) title++; // Allocate memory to store the dialog template. It's made bigger when // needed. template_len = DLG_ALLOC_SIZE; pdlgtemplate = p = (WORD *)LocalAlloc(LPTR, template_len); if (p == NULL) return; hwnd = GetDesktopWindow(); hdc = GetWindowDC(hwnd); # ifdef USE_SYSMENU_FONT if (gui_w32_get_menu_font(&lfSysmenu) == OK) { font = CreateFontIndirectW(&lfSysmenu); use_lfSysmenu = TRUE; } else # endif font = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME); oldFont = SelectFont(hdc, font); // Calculate width of a single space. Used for padding columns to the // right width. spaceWidth = GetTextWidth(hdc, (char_u *)" ", 1); // Figure out max width of the text column, the accelerator column and the // optional submenu column. submenuWidth = 0; for (col = 0; col < 2; col++) { columnWidths[col] = 0; FOR_ALL_CHILD_MENUS(menu, pmenu) { // Use "dname" here to compute the width of the visible text. text = (col == 0) ? pmenu->dname : pmenu->actext; if (text != NULL && *text != NUL) { textWidth = GetTextWidthEnc(hdc, text, (int)STRLEN(text)); if (textWidth > columnWidths[col]) columnWidths[col] = textWidth; } if (pmenu->children != NULL) submenuWidth = TEAROFF_COLUMN_PADDING * spaceWidth; } } if (columnWidths[1] == 0) { // no accelerators if (submenuWidth != 0) columnWidths[0] += submenuWidth; else columnWidths[0] += spaceWidth; } else { // there is an accelerator column columnWidths[0] += TEAROFF_COLUMN_PADDING * spaceWidth; columnWidths[1] += submenuWidth; } /* * Now find the total width of our 'menu'. */ textWidth = columnWidths[0] + columnWidths[1]; if (submenuWidth != 0) { submenuWidth = GetTextWidth(hdc, (char_u *)TEAROFF_SUBMENU_LABEL, (int)STRLEN(TEAROFF_SUBMENU_LABEL)); textWidth += submenuWidth; } dlgwidth = GetTextWidthEnc(hdc, title, (int)STRLEN(title)); if (textWidth > dlgwidth) dlgwidth = textWidth; dlgwidth += 2 * TEAROFF_PADDING_X + TEAROFF_BUTTON_PAD_X; // start to fill in the dlgtemplate information. addressing by WORDs lStyle = DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | WS_VISIBLE; lExtendedStyle = WS_EX_TOOLWINDOW|WS_EX_STATICEDGE; *p++ = LOWORD(lStyle); *p++ = HIWORD(lStyle); *p++ = LOWORD(lExtendedStyle); *p++ = HIWORD(lExtendedStyle); pnumitems = p; // save where the number of items must be stored *p++ = 0; // NumberOfItems(will change later) gui_mch_getmouse(&x, &y); if (initX == 0xffffL) *p++ = PixelToDialogX(x); // x else *p++ = PixelToDialogX(initX); // x if (initY == 0xffffL) *p++ = PixelToDialogY(y); // y else *p++ = PixelToDialogY(initY); // y *p++ = PixelToDialogX(dlgwidth); // cx ptrueheight = p; *p++ = 0; // dialog height: changed later anyway *p++ = 0; // Menu *p++ = 0; // Class // copy the title of the dialog nchar = nCopyAnsiToWideChar(p, ((*title) ? (LPSTR)title : (LPSTR)("Vim "VIM_VERSION_MEDIUM)), TRUE); p += nchar; // do the font, since DS_3DLOOK doesn't work properly # ifdef USE_SYSMENU_FONT if (use_lfSysmenu) { // point size *p++ = -MulDiv(lfSysmenu.lfHeight, 72, GetDeviceCaps(hdc, LOGPIXELSY)); wcscpy(p, lfSysmenu.lfFaceName); nchar = (int)wcslen(lfSysmenu.lfFaceName) + 1; } else # endif { *p++ = DLG_FONT_POINT_SIZE; // point size nchar = nCopyAnsiToWideChar(p, DLG_FONT_NAME, FALSE); } p += nchar; /* * Loop over all the items in the menu. * But skip over the tearbar. */ if (STRCMP(menu->children->name, TEAR_STRING) == 0) menu = menu->children->next; else menu = menu->children; top_menu = menu; for ( ; menu != NULL; menu = menu->next) { if (menu->modes == 0) // this menu has just been deleted continue; if (menu_is_separator(menu->dname)) { sepPadding += 3; continue; } // Check if there still is plenty of room in the template. Make it // larger when needed. if (((char *)p - (char *)pdlgtemplate) + 1000 > template_len) { WORD *newp; newp = (WORD *)LocalAlloc(LPTR, template_len + 4096); if (newp != NULL) { template_len += 4096; mch_memmove(newp, pdlgtemplate, (char *)p - (char *)pdlgtemplate); p = newp + (p - pdlgtemplate); pnumitems = newp + (pnumitems - pdlgtemplate); ptrueheight = newp + (ptrueheight - pdlgtemplate); LocalFree(LocalHandle(pdlgtemplate)); pdlgtemplate = newp; } } // Figure out minimal length of this menu label. Use "name" for the // actual text, "dname" for estimating the displayed size. "name" // has "&a" for mnemonic and includes the accelerator. len = nameLen = (int)STRLEN(menu->name); padding0 = (columnWidths[0] - GetTextWidthEnc(hdc, menu->dname, (int)STRLEN(menu->dname))) / spaceWidth; len += padding0; if (menu->actext != NULL) { acLen = (int)STRLEN(menu->actext); len += acLen; textWidth = GetTextWidthEnc(hdc, menu->actext, acLen); } else textWidth = 0; padding1 = (columnWidths[1] - textWidth) / spaceWidth; len += padding1; if (menu->children == NULL) { padding2 = submenuWidth / spaceWidth; len += padding2; menuID = (WORD)(menu->id); } else { len += (int)STRLEN(TEAROFF_SUBMENU_LABEL); menuID = (WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000); } // Allocate menu label and fill it in text = label = alloc(len + 1); if (label == NULL) break; vim_strncpy(text, menu->name, nameLen); text = vim_strchr(text, TAB); // stop at TAB before actext if (text == NULL) text = label + nameLen; // no actext, use whole name while (padding0-- > 0) *text++ = ' '; if (menu->actext != NULL) { STRNCPY(text, menu->actext, acLen); text += acLen; } while (padding1-- > 0) *text++ = ' '; if (menu->children != NULL) { STRCPY(text, TEAROFF_SUBMENU_LABEL); text += STRLEN(TEAROFF_SUBMENU_LABEL); } else { while (padding2-- > 0) *text++ = ' '; } *text = NUL; /* * BS_LEFT will just be ignored on Win32s/NT3.5x - on * W95/NT4 it makes the tear-off look more like a menu. */ p = add_dialog_element(p, BS_PUSHBUTTON|BS_LEFT, (WORD)PixelToDialogX(TEAROFF_PADDING_X), (WORD)(sepPadding + 1 + 13 * (*pnumitems)), (WORD)PixelToDialogX(dlgwidth - 2 * TEAROFF_PADDING_X), (WORD)12, menuID, (WORD)0x0080, (char *)label); vim_free(label); (*pnumitems)++; } *ptrueheight = (WORD)(sepPadding + 1 + 13 * (*pnumitems)); // show modelessly the_menu->tearoff_handle = CreateDialogIndirectParam( g_hinst, (LPDLGTEMPLATE)pdlgtemplate, s_hwnd, (DLGPROC)tearoff_callback, (LPARAM)top_menu); LocalFree(LocalHandle(pdlgtemplate)); SelectFont(hdc, oldFont); DeleteObject(font); ReleaseDC(hwnd, hdc); /* * Reassert ourselves as the active window. This is so that after creating * a tearoff, the user doesn't have to click with the mouse just to start * typing again! */ (void)SetActiveWindow(s_hwnd); // make sure the right buttons are enabled force_menu_update = TRUE; } #endif #if defined(FEAT_TOOLBAR) || defined(PROTO) # include "gui_w32_rc.h" /* * Create the toolbar, initially unpopulated. * (just like the menu, there are no defaults, it's all * set up through menu.vim) */ static void initialise_toolbar(void) { InitCommonControls(); s_toolbarhwnd = CreateToolbarEx( s_hwnd, WS_CHILD | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, 4000, //any old big number 31, //number of images in initial bitmap g_hinst, IDR_TOOLBAR1, // id of initial bitmap NULL, 0, // initial number of buttons TOOLBAR_BUTTON_WIDTH, //api guide is wrong! TOOLBAR_BUTTON_HEIGHT, TOOLBAR_BUTTON_WIDTH, TOOLBAR_BUTTON_HEIGHT, sizeof(TBBUTTON) ); // Remove transparency from the toolbar to prevent the main window // background colour showing through SendMessage(s_toolbarhwnd, TB_SETSTYLE, 0, SendMessage(s_toolbarhwnd, TB_GETSTYLE, 0, 0) & ~TBSTYLE_TRANSPARENT); s_toolbar_wndproc = SubclassWindow(s_toolbarhwnd, toolbar_wndproc); gui_mch_show_toolbar(vim_strchr(p_go, GO_TOOLBAR) != NULL); update_toolbar_size(); } static void update_toolbar_size(void) { int w, h; TBMETRICS tbm; tbm.cbSize = sizeof(TBMETRICS); tbm.dwMask = TBMF_PAD | TBMF_BUTTONSPACING; SendMessage(s_toolbarhwnd, TB_GETMETRICS, 0, (LPARAM)&tbm); //TRACE("Pad: %d, %d", tbm.cxPad, tbm.cyPad); //TRACE("ButtonSpacing: %d, %d", tbm.cxButtonSpacing, tbm.cyButtonSpacing); w = (TOOLBAR_BUTTON_WIDTH + tbm.cxPad) * s_dpi / DEFAULT_DPI; h = (TOOLBAR_BUTTON_HEIGHT + tbm.cyPad) * s_dpi / DEFAULT_DPI; //TRACE("button size: %d, %d", w, h); SendMessage(s_toolbarhwnd, TB_SETBUTTONSIZE, 0, MAKELPARAM(w, h)); gui.toolbar_height = h + 6; //DWORD s = SendMessage(s_toolbarhwnd, TB_GETBUTTONSIZE, 0, 0); //TRACE("actual button size: %d, %d", LOWORD(s), HIWORD(s)); // TODO: // Currently, this function only updates the size of toolbar buttons. // It would be nice if the toolbar images are resized based on DPI. } static LRESULT CALLBACK toolbar_wndproc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HandleMouseHide(uMsg, lParam); return CallWindowProc(s_toolbar_wndproc, hwnd, uMsg, wParam, lParam); } static int get_toolbar_bitmap(vimmenu_T *menu) { int i = -1; /* * Check user bitmaps first, unless builtin is specified. */ if (!menu->icon_builtin) { char_u fname[MAXPATHL]; HANDLE hbitmap = NULL; if (menu->iconfile != NULL) { gui_find_iconfile(menu->iconfile, fname, "bmp"); hbitmap = LoadImage( NULL, (LPCSTR)fname, IMAGE_BITMAP, TOOLBAR_BUTTON_WIDTH, TOOLBAR_BUTTON_HEIGHT, LR_LOADFROMFILE | LR_LOADMAP3DCOLORS ); } /* * If the LoadImage call failed, or the "icon=" file * didn't exist or wasn't specified, try the menu name */ if (hbitmap == NULL && (gui_find_bitmap( # ifdef FEAT_MULTI_LANG menu->en_dname != NULL ? menu->en_dname : # endif menu->dname, fname, "bmp") == OK)) hbitmap = LoadImage( NULL, (LPCSTR)fname, IMAGE_BITMAP, TOOLBAR_BUTTON_WIDTH, TOOLBAR_BUTTON_HEIGHT, LR_LOADFROMFILE | LR_LOADMAP3DCOLORS ); if (hbitmap != NULL) { TBADDBITMAP tbAddBitmap; tbAddBitmap.hInst = NULL; tbAddBitmap.nID = (long_u)hbitmap; i = (int)SendMessage(s_toolbarhwnd, TB_ADDBITMAP, (WPARAM)1, (LPARAM)&tbAddBitmap); // i will be set to -1 if it fails } } if (i == -1 && menu->iconidx >= 0 && menu->iconidx < TOOLBAR_BITMAP_COUNT) i = menu->iconidx; return i; } #endif #if defined(FEAT_GUI_TABLINE) || defined(PROTO) static void initialise_tabline(void) { InitCommonControls(); s_tabhwnd = CreateWindow(WC_TABCONTROL, "Vim tabline", WS_CHILD|TCS_FOCUSNEVER|TCS_TOOLTIPS, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, s_hwnd, NULL, g_hinst, NULL); s_tabline_wndproc = SubclassWindow(s_tabhwnd, tabline_wndproc); gui.tabline_height = TABLINE_HEIGHT; set_tabline_font(); } /* * Get tabpage_T from POINT. */ static tabpage_T * GetTabFromPoint( HWND hWnd, POINT pt) { tabpage_T *ptp = NULL; if (gui_mch_showing_tabline()) { TCHITTESTINFO htinfo; htinfo.pt = pt; // ignore if a window under cursor is not tabcontrol. if (s_tabhwnd == hWnd) { int idx = TabCtrl_HitTest(s_tabhwnd, &htinfo); if (idx != -1) ptp = find_tabpage(idx + 1); } } return ptp; } static POINT s_pt = {0, 0}; static HCURSOR s_hCursor = NULL; static LRESULT CALLBACK tabline_wndproc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { POINT pt; tabpage_T *tp; RECT rect; int nCenter; int idx0; int idx1; HandleMouseHide(uMsg, lParam); switch (uMsg) { case WM_LBUTTONDOWN: { s_pt.x = GET_X_LPARAM(lParam); s_pt.y = GET_Y_LPARAM(lParam); SetCapture(hwnd); s_hCursor = GetCursor(); // backup default cursor break; } case WM_MOUSEMOVE: if (GetCapture() == hwnd && ((wParam & MK_LBUTTON)) != 0) { pt.x = GET_X_LPARAM(lParam); pt.y = s_pt.y; if (abs(pt.x - s_pt.x) > pGetSystemMetricsForDpi(SM_CXDRAG, s_dpi)) { SetCursor(LoadCursor(NULL, IDC_SIZEWE)); tp = GetTabFromPoint(hwnd, pt); if (tp != NULL) { idx0 = tabpage_index(curtab) - 1; idx1 = tabpage_index(tp) - 1; TabCtrl_GetItemRect(hwnd, idx1, &rect); nCenter = rect.left + (rect.right - rect.left) / 2; // Check if the mouse cursor goes over the center of // the next tab to prevent "flickering". if ((idx0 < idx1) && (nCenter < pt.x)) { tabpage_move(idx1 + 1); update_screen(0); } else if ((idx1 < idx0) && (pt.x < nCenter)) { tabpage_move(idx1); update_screen(0); } } } } break; case WM_LBUTTONUP: { if (GetCapture() == hwnd) { SetCursor(s_hCursor); ReleaseCapture(); } break; } case WM_MBUTTONUP: { TCHITTESTINFO htinfo; htinfo.pt.x = GET_X_LPARAM(lParam); htinfo.pt.y = GET_Y_LPARAM(lParam); idx0 = TabCtrl_HitTest(hwnd, &htinfo); if (idx0 != -1) { idx0 += 1; send_tabline_menu_event(idx0, TABLINE_MENU_CLOSE); } break; } default: break; } return CallWindowProc(s_tabline_wndproc, hwnd, uMsg, wParam, lParam); } #endif #if defined(FEAT_OLE) || defined(FEAT_EVAL) || defined(PROTO) /* * Make the GUI window come to the foreground. */ void gui_mch_set_foreground(void) { if (IsIconic(s_hwnd)) SendMessage(s_hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); SetForegroundWindow(s_hwnd); } #endif #if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME) static void dyn_imm_load(void) { hLibImm = vimLoadLib("imm32.dll"); if (hLibImm == NULL) return; pImmGetCompositionStringW = (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD))GetProcAddress(hLibImm, "ImmGetCompositionStringW"); pImmGetContext = (HIMC (WINAPI *)(HWND))GetProcAddress(hLibImm, "ImmGetContext"); pImmAssociateContext = (HIMC (WINAPI *)(HWND, HIMC))GetProcAddress(hLibImm, "ImmAssociateContext"); pImmReleaseContext = (BOOL (WINAPI *)(HWND, HIMC))GetProcAddress(hLibImm, "ImmReleaseContext"); pImmGetOpenStatus = (BOOL (WINAPI *)(HIMC))GetProcAddress(hLibImm, "ImmGetOpenStatus"); pImmSetOpenStatus = (BOOL (WINAPI *)(HIMC, BOOL))GetProcAddress(hLibImm, "ImmSetOpenStatus"); pImmGetCompositionFontW = (BOOL (WINAPI *)(HIMC, LPLOGFONTW))GetProcAddress(hLibImm, "ImmGetCompositionFontW"); pImmSetCompositionFontW = (BOOL (WINAPI *)(HIMC, LPLOGFONTW))GetProcAddress(hLibImm, "ImmSetCompositionFontW"); pImmSetCompositionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM))GetProcAddress(hLibImm, "ImmSetCompositionWindow"); pImmGetConversionStatus = (BOOL (WINAPI *)(HIMC, LPDWORD, LPDWORD))GetProcAddress(hLibImm, "ImmGetConversionStatus"); pImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD))GetProcAddress(hLibImm, "ImmSetConversionStatus"); if ( pImmGetCompositionStringW == NULL || pImmGetContext == NULL || pImmAssociateContext == NULL || pImmReleaseContext == NULL || pImmGetOpenStatus == NULL || pImmSetOpenStatus == NULL || pImmGetCompositionFontW == NULL || pImmSetCompositionFontW == NULL || pImmSetCompositionWindow == NULL || pImmGetConversionStatus == NULL || pImmSetConversionStatus == NULL) { FreeLibrary(hLibImm); hLibImm = NULL; pImmGetContext = NULL; return; } return; } #endif #if defined(FEAT_SIGN_ICONS) || defined(PROTO) # ifdef FEAT_XPM_W32 # define IMAGE_XPM 100 # endif typedef struct _signicon_t { HANDLE hImage; UINT uType; # ifdef FEAT_XPM_W32 HANDLE hShape; // Mask bitmap handle # endif } signicon_t; void gui_mch_drawsign(int row, int col, int typenr) { signicon_t *sign; int x, y, w, h; if (!gui.in_use || (sign = (signicon_t *)sign_get_image(typenr)) == NULL) return; # if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); # endif x = TEXT_X(col); y = TEXT_Y(row); w = gui.char_width * 2; h = gui.char_height; switch (sign->uType) { case IMAGE_BITMAP: { HDC hdcMem; HBITMAP hbmpOld; hdcMem = CreateCompatibleDC(s_hdc); hbmpOld = (HBITMAP)SelectObject(hdcMem, sign->hImage); BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, hbmpOld); DeleteDC(hdcMem); } break; case IMAGE_ICON: case IMAGE_CURSOR: DrawIconEx(s_hdc, x, y, (HICON)sign->hImage, w, h, 0, NULL, DI_NORMAL); break; # ifdef FEAT_XPM_W32 case IMAGE_XPM: { HDC hdcMem; HBITMAP hbmpOld; hdcMem = CreateCompatibleDC(s_hdc); hbmpOld = (HBITMAP)SelectObject(hdcMem, sign->hShape); // Make hole BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCAND); SelectObject(hdcMem, sign->hImage); // Paint sign BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCPAINT); SelectObject(hdcMem, hbmpOld); DeleteDC(hdcMem); } break; # endif } } static void close_signicon_image(signicon_t *sign) { if (sign == NULL) return; switch (sign->uType) { case IMAGE_BITMAP: DeleteObject((HGDIOBJ)sign->hImage); break; case IMAGE_CURSOR: DestroyCursor((HCURSOR)sign->hImage); break; case IMAGE_ICON: DestroyIcon((HICON)sign->hImage); break; # ifdef FEAT_XPM_W32 case IMAGE_XPM: DeleteObject((HBITMAP)sign->hImage); DeleteObject((HBITMAP)sign->hShape); break; # endif } } void * gui_mch_register_sign(char_u *signfile) { signicon_t sign, *psign; char_u *ext; sign.hImage = NULL; ext = signfile + STRLEN(signfile) - 4; // get extension if (ext > signfile) { int do_load = 1; if (!STRICMP(ext, ".bmp")) sign.uType = IMAGE_BITMAP; else if (!STRICMP(ext, ".ico")) sign.uType = IMAGE_ICON; else if (!STRICMP(ext, ".cur") || !STRICMP(ext, ".ani")) sign.uType = IMAGE_CURSOR; else do_load = 0; if (do_load) sign.hImage = (HANDLE)LoadImage(NULL, (LPCSTR)signfile, sign.uType, gui.char_width * 2, gui.char_height, LR_LOADFROMFILE | LR_CREATEDIBSECTION); # ifdef FEAT_XPM_W32 if (!STRICMP(ext, ".xpm")) { sign.uType = IMAGE_XPM; LoadXpmImage((char *)signfile, (HBITMAP *)&sign.hImage, (HBITMAP *)&sign.hShape); } # endif } psign = NULL; if (sign.hImage && (psign = ALLOC_ONE(signicon_t)) != NULL) *psign = sign; if (!psign) { if (sign.hImage) close_signicon_image(&sign); emsg(_(e_couldnt_read_in_sign_data)); } return (void *)psign; } void gui_mch_destroy_sign(void *sign) { if (sign == NULL) return; close_signicon_image((signicon_t *)sign); vim_free(sign); } #endif #if defined(FEAT_BEVAL_GUI) || defined(PROTO) /* * BALLOON-EVAL IMPLEMENTATION FOR WINDOWS. * Added by Sergey Khorev * * The only reused thing is beval.h and get_beval_info() * from gui_beval.c (note it uses x and y of the BalloonEval struct * to get current mouse position). * * Trying to use as more Windows services as possible, and as less * IE version as possible :)). * * 1) Don't create ToolTip in gui_mch_create_beval_area, only initialize * BalloonEval struct. * 2) Enable/Disable simply create/kill BalloonEval Timer * 3) When there was enough inactivity, timer procedure posts * async request to debugger * 4) gui_mch_post_balloon (invoked from netbeans.c) creates tooltip control * and performs some actions to show it ASAP * 5) WM_NOTIFY:TTN_POP destroys created tooltip */ static void make_tooltip(BalloonEval *beval, char *text, POINT pt) { TOOLINFOW *pti; RECT rect; pti = alloc(sizeof(TOOLINFOW)); if (pti == NULL) return; beval->balloon = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, beval->target, NULL, g_hinst, NULL); SetWindowPos(beval->balloon, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); pti->cbSize = sizeof(TOOLINFOW); pti->uFlags = TTF_SUBCLASS; pti->hwnd = beval->target; pti->hinst = 0; // Don't use string resources pti->uId = ID_BEVAL_TOOLTIP; pti->lpszText = LPSTR_TEXTCALLBACKW; beval->tofree = enc_to_utf16((char_u*)text, NULL); pti->lParam = (LPARAM)beval->tofree; // switch multiline tooltips on if (GetClientRect(s_textArea, &rect)) SendMessageW(beval->balloon, TTM_SETMAXTIPWIDTH, 0, (LPARAM)rect.right); // Limit ballooneval bounding rect to CursorPos neighbourhood. pti->rect.left = pt.x - 3; pti->rect.top = pt.y - 3; pti->rect.right = pt.x + 3; pti->rect.bottom = pt.y + 3; SendMessageW(beval->balloon, TTM_ADDTOOLW, 0, (LPARAM)pti); // Make tooltip appear sooner. SendMessageW(beval->balloon, TTM_SETDELAYTIME, TTDT_INITIAL, 10); // I've performed some tests and it seems the longest possible life time // of tooltip is 30 seconds. SendMessageW(beval->balloon, TTM_SETDELAYTIME, TTDT_AUTOPOP, 30000); /* * HACK: force tooltip to appear, because it'll not appear until * first mouse move. D*mn M$ * Amazingly moving (2, 2) and then (-1, -1) the mouse doesn't move. */ mouse_event(MOUSEEVENTF_MOVE, 2, 2, 0, 0); mouse_event(MOUSEEVENTF_MOVE, (DWORD)-1, (DWORD)-1, 0, 0); vim_free(pti); } static void delete_tooltip(BalloonEval *beval) { PostMessage(beval->balloon, WM_CLOSE, 0, 0); } static VOID CALLBACK beval_timer_proc( HWND hwnd UNUSED, UINT uMsg UNUSED, UINT_PTR idEvent UNUSED, DWORD dwTime) { POINT pt; RECT rect; if (cur_beval == NULL || cur_beval->showState == ShS_SHOWING || !p_beval) return; GetCursorPos(&pt); if (WindowFromPoint(pt) != s_textArea) return; ScreenToClient(s_textArea, &pt); GetClientRect(s_textArea, &rect); if (!PtInRect(&rect, pt)) return; if (last_user_activity > 0 && (dwTime - last_user_activity) >= (DWORD)p_bdlay && (cur_beval->showState != ShS_PENDING || abs(cur_beval->x - pt.x) > 3 || abs(cur_beval->y - pt.y) > 3)) { // Pointer resting in one place long enough, it's time to show // the tooltip. cur_beval->showState = ShS_PENDING; cur_beval->x = pt.x; cur_beval->y = pt.y; if (cur_beval->msgCB != NULL) (*cur_beval->msgCB)(cur_beval, 0); } } void gui_mch_disable_beval_area(BalloonEval *beval UNUSED) { KillTimer(s_textArea, beval_timer_id); } void gui_mch_enable_beval_area(BalloonEval *beval) { if (beval == NULL) return; beval_timer_id = SetTimer(s_textArea, 0, (UINT)(p_bdlay / 2), beval_timer_proc); } void gui_mch_post_balloon(BalloonEval *beval, char_u *mesg) { POINT pt; vim_free(beval->msg); beval->msg = mesg == NULL ? NULL : vim_strsave(mesg); if (beval->msg == NULL) { delete_tooltip(beval); beval->showState = ShS_NEUTRAL; return; } if (beval->showState == ShS_SHOWING) return; GetCursorPos(&pt); ScreenToClient(s_textArea, &pt); if (abs(beval->x - pt.x) < 3 && abs(beval->y - pt.y) < 3) { // cursor is still here gui_mch_disable_beval_area(cur_beval); beval->showState = ShS_SHOWING; make_tooltip(beval, (char *)mesg, pt); } } BalloonEval * gui_mch_create_beval_area( void *target UNUSED, // ignored, always use s_textArea char_u *mesg, void (*mesgCB)(BalloonEval *, int), void *clientData) { // partially stolen from gui_beval.c BalloonEval *beval; if (mesg != NULL && mesgCB != NULL) { iemsg(_(e_cannot_create_ballooneval_with_both_message_and_callback)); return NULL; } beval = ALLOC_CLEAR_ONE(BalloonEval); if (beval != NULL) { beval->target = s_textArea; beval->showState = ShS_NEUTRAL; beval->msg = mesg; beval->msgCB = mesgCB; beval->clientData = clientData; InitCommonControls(); cur_beval = beval; if (p_beval) gui_mch_enable_beval_area(beval); } return beval; } static void Handle_WM_Notify(HWND hwnd UNUSED, LPNMHDR pnmh) { if (pnmh->idFrom != ID_BEVAL_TOOLTIP) // it is not our tooltip return; if (cur_beval == NULL) return; switch (pnmh->code) { case TTN_SHOW: break; case TTN_POP: // Before tooltip disappear delete_tooltip(cur_beval); gui_mch_enable_beval_area(cur_beval); cur_beval->showState = ShS_NEUTRAL; break; case TTN_GETDISPINFO: { // if you get there then we have new common controls NMTTDISPINFO *info = (NMTTDISPINFO *)pnmh; info->lpszText = (LPSTR)info->lParam; info->uFlags |= TTF_DI_SETITEM; } break; case TTN_GETDISPINFOW: { // if we get here then we have new common controls NMTTDISPINFOW *info = (NMTTDISPINFOW *)pnmh; info->lpszText = (LPWSTR)info->lParam; info->uFlags |= TTF_DI_SETITEM; } break; } } static void track_user_activity(UINT uMsg) { if ((uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) || (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST)) last_user_activity = GetTickCount(); } void gui_mch_destroy_beval_area(BalloonEval *beval) { # ifdef FEAT_VARTABS vim_free(beval->vts); # endif vim_free(beval->tofree); vim_free(beval); } #endif // FEAT_BEVAL_GUI #if defined(FEAT_NETBEANS_INTG) || defined(PROTO) /* * We have multiple signs to draw at the same location. Draw the * multi-sign indicator (down-arrow) instead. This is the Win32 version. */ void netbeans_draw_multisign_indicator(int row) { int i; int y; int x; if (!netbeans_active()) return; x = 0; y = TEXT_Y(row); # if defined(FEAT_DIRECTX) if (IS_ENABLE_DIRECTX()) DWriteContext_Flush(s_dwc); # endif for (i = 0; i < gui.char_height - 3; i++) SetPixel(s_hdc, x+2, y++, gui.currFgColor); SetPixel(s_hdc, x+0, y, gui.currFgColor); SetPixel(s_hdc, x+2, y, gui.currFgColor); SetPixel(s_hdc, x+4, y++, gui.currFgColor); SetPixel(s_hdc, x+1, y, gui.currFgColor); SetPixel(s_hdc, x+2, y, gui.currFgColor); SetPixel(s_hdc, x+3, y++, gui.currFgColor); SetPixel(s_hdc, x+2, y, gui.currFgColor); } #endif #if defined(FEAT_EVAL) || defined(PROTO) // TODO: at the moment, this is just a copy of test_gui_mouse_event. // But, we could instead generate actual Win32 mouse event messages, // ie. to make it consistent wih test_gui_w32_sendevent_keyboard. static int test_gui_w32_sendevent_mouse(dict_T *args) { if (!dict_has_key(args, "row") || !dict_has_key(args, "col")) return FALSE; // Note: "move" is optional, requires fewer arguments int move = (int)dict_get_bool(args, "move", FALSE); if (!move && (!dict_has_key(args, "button") || !dict_has_key(args, "multiclick") || !dict_has_key(args, "modifiers"))) return FALSE; int row = (int)dict_get_number(args, "row"); int col = (int)dict_get_number(args, "col"); if (move) { // the "move" argument expects row and col coordnates to be in pixels, // unless "cell" is specified and is TRUE. if (dict_get_bool(args, "cell", FALSE)) { // calculate the middle of the character cell // Note: Cell coordinates are 1-based from vimscript int pY = (row - 1) * gui.char_height + gui.char_height / 2; int pX = (col - 1) * gui.char_width + gui.char_width / 2; gui_mouse_moved(pX, pY); } else gui_mouse_moved(col, row); } else { int button = (int)dict_get_number(args, "button"); int repeated_click = (int)dict_get_number(args, "multiclick"); int_u mods = (int)dict_get_number(args, "modifiers"); // Reset the scroll values to known values. // XXX: Remove this when/if the scroll step is made configurable. mouse_set_hor_scroll_step(6); mouse_set_vert_scroll_step(3); gui_send_mouse_event(button, TEXT_X(col - 1), TEXT_Y(row - 1), repeated_click, mods); } return TRUE; } static int test_gui_w32_sendevent_keyboard(dict_T *args) { INPUT inputs[1]; INPUT modkeys[3]; SecureZeroMemory(inputs, sizeof(INPUT)); SecureZeroMemory(modkeys, 3 * sizeof(INPUT)); char_u *event = dict_get_string(args, "event", TRUE); if (event && (STRICMP(event, "keydown") == 0 || STRICMP(event, "keyup") == 0)) { WORD vkCode = dict_get_number_def(args, "keycode", 0); if (vkCode <= 0 || vkCode >= 0xFF) { semsg(_(e_invalid_argument_nr), (long)vkCode); return FALSE; } BOOL isModKey = (vkCode == VK_SHIFT || vkCode == VK_CONTROL || vkCode == VK_MENU || vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_LCONTROL || vkCode == VK_RCONTROL || vkCode == VK_LMENU || vkCode == VK_RMENU ); BOOL unwrapMods = FALSE; int mods = (int)dict_get_number(args, "modifiers"); // If there are modifiers in the args, and it is not a keyup event and // vkCode is not a modifier key, then we generate virtual modifier key // messages before sending the actual key message. if(mods && STRICMP(event, "keydown") == 0 && !isModKey) { int n = 0; if (mods & MOD_MASK_SHIFT) { modkeys[n].type = INPUT_KEYBOARD; modkeys[n].ki.wVk = VK_LSHIFT; n++; } if (mods & MOD_MASK_CTRL) { modkeys[n].type = INPUT_KEYBOARD; modkeys[n].ki.wVk = VK_LCONTROL; n++; } if (mods & MOD_MASK_ALT) { modkeys[n].type = INPUT_KEYBOARD; modkeys[n].ki.wVk = VK_LMENU; n++; } if (n) { (void)SetForegroundWindow(s_hwnd); SendInput(n, modkeys, sizeof(INPUT)); } } inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = vkCode; if (STRICMP(event, "keyup") == 0) { inputs[0].ki.dwFlags = KEYEVENTF_KEYUP; if(!isModKey) unwrapMods = TRUE; } (void)SetForegroundWindow(s_hwnd); SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); vim_free(event); if (unwrapMods) { modkeys[0].type = INPUT_KEYBOARD; modkeys[0].ki.wVk = VK_LSHIFT; modkeys[0].ki.dwFlags = KEYEVENTF_KEYUP; modkeys[1].type = INPUT_KEYBOARD; modkeys[1].ki.wVk = VK_LCONTROL; modkeys[1].ki.dwFlags = KEYEVENTF_KEYUP; modkeys[2].type = INPUT_KEYBOARD; modkeys[2].ki.wVk = VK_LMENU; modkeys[2].ki.dwFlags = KEYEVENTF_KEYUP; (void)SetForegroundWindow(s_hwnd); SendInput(3, modkeys, sizeof(INPUT)); } } else { if (event == NULL) { semsg(_(e_missing_argument_str), "event"); } else { semsg(_(e_invalid_value_for_argument_str_str), "event", event); vim_free(event); } return FALSE; } return TRUE; } int test_gui_w32_sendevent(char_u *event, dict_T *args) { if (STRICMP(event, "key") == 0) return test_gui_w32_sendevent_keyboard(args); else if (STRICMP(event, "mouse") == 0) return test_gui_w32_sendevent_mouse(args); else { semsg(_(e_invalid_value_for_argument_str_str), "event", event); return FALSE; } } #endif