Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
vim
GitHub Repository: vim/vim
Path: blob/master/src/gui_w32.c
5171 views
1
/* vi:set ts=8 sts=4 sw=4 noet:
2
*
3
* VIM - Vi IMproved by Bram Moolenaar
4
* GUI support by Robert Webb
5
*
6
* Do ":help uganda" in Vim to read copying and usage conditions.
7
* Do ":help credits" in Vim to see a list of people who contributed.
8
* See README.txt for an overview of the Vim source code.
9
*/
10
/*
11
* Windows GUI.
12
*
13
* GUI support for Microsoft Windows, aka Win32. Also for Win64.
14
*
15
* George V. Reilly <[email protected]> wrote the original Win32 GUI.
16
* Robert Webb reworked it to use the existing GUI stuff and added menu,
17
* scrollbars, etc.
18
*
19
* Note: Clipboard stuff, for cutting and pasting text to other windows, is in
20
* winclip.c. (It can also be done from the terminal version).
21
*
22
* TODO: Some of the function signatures ought to be updated for Win64;
23
* e.g., replace LONG with LONG_PTR, etc.
24
*/
25
26
#include "vim.h"
27
28
#if defined(FEAT_DIRECTX)
29
# include "gui_dwrite.h"
30
#endif
31
32
// values for "dead_key"
33
#define DEAD_KEY_OFF 0 // no dead key
34
#define DEAD_KEY_SET_DEFAULT 1 // dead key pressed
35
#define DEAD_KEY_TRANSIENT_IN_ON_CHAR 2 // wait for next key press
36
#define DEAD_KEY_SKIP_ON_CHAR 3 // skip next _OnChar()
37
38
#if defined(FEAT_DIRECTX)
39
static DWriteContext *s_dwc = NULL;
40
static int s_directx_enabled = 0;
41
static int s_directx_load_attempted = 0;
42
# define IS_ENABLE_DIRECTX() (s_directx_enabled && s_dwc != NULL && enc_utf8)
43
static int directx_enabled(void);
44
static void directx_binddc(void);
45
#endif
46
47
#ifdef FEAT_MENU
48
static int gui_mswin_get_menu_height(int fix_window);
49
#else
50
# define gui_mswin_get_menu_height(fix_window) 0
51
#endif
52
53
typedef struct keycode_trans_strategy {
54
void (*ptr_on_char) (HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
55
void (*ptr_on_sys_char) (HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
56
void (*ptr_process_message_usual_key) (UINT /*vk*/, const MSG* /*pmsg*/);
57
int (*ptr_get_active_modifiers)(void);
58
int (*is_experimental)(void);
59
} keycode_trans_strategy;
60
61
// forward declarations for input instance initializer
62
static void _OnChar_experimental(HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
63
static void _OnSysChar_experimental(HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
64
static void process_message_usual_key_experimental(UINT /*vk*/, const MSG* /*pmsg*/);
65
static int get_active_modifiers_experimental(void);
66
static int is_experimental_true(void);
67
68
keycode_trans_strategy keycode_trans_strategy_experimental = {
69
_OnChar_experimental // ptr_on_char
70
, _OnSysChar_experimental // ptr_on_sys_char
71
, process_message_usual_key_experimental // ptr_process_message_usual_key
72
, get_active_modifiers_experimental
73
, is_experimental_true
74
};
75
76
// forward declarations for input instance initializer
77
static void _OnChar_classic(HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
78
static void _OnSysChar_classic(HWND /*hwnd UNUSED*/, UINT /*cch*/, int /*cRepeat UNUSED*/);
79
static void process_message_usual_key_classic(UINT /*vk*/, const MSG* /*pmsg*/);
80
static int get_active_modifiers_classic(void);
81
static int is_experimental_false(void);
82
83
keycode_trans_strategy keycode_trans_strategy_classic = {
84
_OnChar_classic // ptr_on_char
85
, _OnSysChar_classic // ptr_on_sys_char
86
, process_message_usual_key_classic // ptr_process_message_usual_key
87
, get_active_modifiers_classic
88
, is_experimental_false
89
};
90
91
keycode_trans_strategy *keycode_trans_strategy_used = NULL;
92
93
static int is_experimental_true(void)
94
{
95
return 1;
96
}
97
98
static int is_experimental_false(void)
99
{
100
return 0;
101
}
102
103
/*
104
* Initialize the keycode translation strategy.
105
*/
106
static void keycode_trans_strategy_init(void)
107
{
108
const char *strategy = NULL;
109
110
// set default value as fallback
111
keycode_trans_strategy_used = &keycode_trans_strategy_classic;
112
113
strategy = getenv("VIM_KEYCODE_TRANS_STRATEGY");
114
if (strategy == NULL)
115
{
116
return;
117
}
118
119
if (STRICMP(strategy, "classic") == 0)
120
{
121
keycode_trans_strategy_used = &keycode_trans_strategy_classic;
122
return;
123
}
124
125
if (STRICMP(strategy, "experimental") == 0)
126
{
127
keycode_trans_strategy_used = &keycode_trans_strategy_experimental;
128
return;
129
}
130
131
}
132
133
#if defined(FEAT_RENDER_OPTIONS)
134
int
135
gui_mch_set_rendering_options(char_u *s)
136
{
137
# ifdef FEAT_DIRECTX
138
char_u *p, *q;
139
140
int dx_enable = 0;
141
int dx_flags = 0;
142
float dx_gamma = 0.0f;
143
float dx_contrast = 0.0f;
144
float dx_level = 0.0f;
145
int dx_geom = 0;
146
int dx_renmode = 0;
147
int dx_taamode = 0;
148
149
// parse string as rendering options.
150
for (p = s; p != NULL && *p != NUL; )
151
{
152
char_u item[256];
153
char_u name[128];
154
char_u value[128];
155
156
copy_option_part(&p, item, sizeof(item), ",");
157
if (p == NULL)
158
break;
159
q = &item[0];
160
copy_option_part(&q, name, sizeof(name), ":");
161
if (q == NULL)
162
return FAIL;
163
copy_option_part(&q, value, sizeof(value), ":");
164
165
if (STRCMP(name, "type") == 0)
166
{
167
if (STRCMP(value, "directx") == 0)
168
dx_enable = 1;
169
else
170
return FAIL;
171
}
172
else if (STRCMP(name, "gamma") == 0)
173
{
174
dx_flags |= 1 << 0;
175
dx_gamma = (float)atof((char *)value);
176
}
177
else if (STRCMP(name, "contrast") == 0)
178
{
179
dx_flags |= 1 << 1;
180
dx_contrast = (float)atof((char *)value);
181
}
182
else if (STRCMP(name, "level") == 0)
183
{
184
dx_flags |= 1 << 2;
185
dx_level = (float)atof((char *)value);
186
}
187
else if (STRCMP(name, "geom") == 0)
188
{
189
dx_flags |= 1 << 3;
190
dx_geom = atoi((char *)value);
191
if (dx_geom < 0 || dx_geom > 2)
192
return FAIL;
193
}
194
else if (STRCMP(name, "renmode") == 0)
195
{
196
dx_flags |= 1 << 4;
197
dx_renmode = atoi((char *)value);
198
if (dx_renmode < 0 || dx_renmode > 6)
199
return FAIL;
200
}
201
else if (STRCMP(name, "taamode") == 0)
202
{
203
dx_flags |= 1 << 5;
204
dx_taamode = atoi((char *)value);
205
if (dx_taamode < 0 || dx_taamode > 3)
206
return FAIL;
207
}
208
else if (STRCMP(name, "scrlines") == 0)
209
{
210
// Deprecated. Simply ignore it.
211
}
212
else
213
return FAIL;
214
}
215
216
if (!gui.in_use)
217
return OK; // only checking the syntax of the value
218
219
// Enable DirectX/DirectWrite
220
if (dx_enable)
221
{
222
if (!directx_enabled())
223
return FAIL;
224
DWriteContext_SetRenderingParams(s_dwc, NULL);
225
if (dx_flags)
226
{
227
DWriteRenderingParams param;
228
DWriteContext_GetRenderingParams(s_dwc, &param);
229
if (dx_flags & (1 << 0))
230
param.gamma = dx_gamma;
231
if (dx_flags & (1 << 1))
232
param.enhancedContrast = dx_contrast;
233
if (dx_flags & (1 << 2))
234
param.clearTypeLevel = dx_level;
235
if (dx_flags & (1 << 3))
236
param.pixelGeometry = dx_geom;
237
if (dx_flags & (1 << 4))
238
param.renderingMode = dx_renmode;
239
if (dx_flags & (1 << 5))
240
param.textAntialiasMode = dx_taamode;
241
DWriteContext_SetRenderingParams(s_dwc, &param);
242
}
243
}
244
s_directx_enabled = dx_enable;
245
246
return OK;
247
# else
248
return FAIL;
249
# endif
250
}
251
#endif
252
253
/*
254
* These are new in Windows ME/XP, only defined in recent compilers.
255
*/
256
#ifndef HANDLE_WM_XBUTTONUP
257
# define HANDLE_WM_XBUTTONUP(hwnd, wParam, lParam, fn) \
258
((fn)((hwnd), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)
259
#endif
260
#ifndef HANDLE_WM_XBUTTONDOWN
261
# define HANDLE_WM_XBUTTONDOWN(hwnd, wParam, lParam, fn) \
262
((fn)((hwnd), FALSE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)
263
#endif
264
#ifndef HANDLE_WM_XBUTTONDBLCLK
265
# define HANDLE_WM_XBUTTONDBLCLK(hwnd, wParam, lParam, fn) \
266
((fn)((hwnd), TRUE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)
267
#endif
268
269
270
#include "version.h" // used by dialog box routine for default title
271
#ifdef DEBUG
272
# include <tchar.h>
273
#endif
274
275
#include <shellapi.h>
276
#include <commctrl.h>
277
#include <windowsx.h>
278
279
#ifdef FEAT_MENU
280
# define MENUHINTS // show menu hints in command line
281
#endif
282
283
// Some parameters for dialog boxes. All in pixels.
284
#define DLG_PADDING_X 10
285
#define DLG_PADDING_Y 10
286
#define DLG_VERT_PADDING_X 4 // For vertical buttons
287
#define DLG_VERT_PADDING_Y 4
288
#define DLG_ICON_WIDTH 34
289
#define DLG_ICON_HEIGHT 34
290
#define DLG_MIN_WIDTH 150
291
#define DLG_FONT_NAME "MS Shell Dlg"
292
#define DLG_FONT_POINT_SIZE 8
293
#define DLG_MIN_MAX_WIDTH 400
294
#define DLG_MIN_MAX_HEIGHT 400
295
296
#define DLG_NONBUTTON_CONTROL 5000 // First ID of non-button controls
297
298
#ifndef WM_DPICHANGED
299
# define WM_DPICHANGED 0x02E0
300
#endif
301
302
#ifndef WM_GETDPISCALEDSIZE
303
# define WM_GETDPISCALEDSIZE 0x02E4
304
#endif
305
306
#ifndef WM_MOUSEHWHEEL
307
# define WM_MOUSEHWHEEL 0x020E
308
#endif
309
310
#ifndef SPI_GETWHEELSCROLLCHARS
311
# define SPI_GETWHEELSCROLLCHARS 0x006C
312
#endif
313
314
#ifndef SPI_SETWHEELSCROLLCHARS
315
# define SPI_SETWHEELSCROLLCHARS 0x006D
316
#endif
317
318
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
319
# define DWMWA_USE_IMMERSIVE_DARK_MODE 20
320
#endif
321
322
#ifndef DWMWA_CAPTION_COLOR
323
# define DWMWA_CAPTION_COLOR 35
324
#endif
325
326
#ifndef DWMWA_TEXT_COLOR
327
# define DWMWA_TEXT_COLOR 36
328
#endif
329
330
static void _OnPaint(HWND hwnd);
331
static void fill_rect(const RECT *rcp, HBRUSH hbr, COLORREF color);
332
static void clear_rect(RECT *rcp);
333
334
static WORD s_dlgfntheight; // height of the dialog font
335
static WORD s_dlgfntwidth; // width of the dialog font
336
337
#ifdef FEAT_MENU
338
static HMENU s_menuBar = NULL;
339
#endif
340
#ifdef FEAT_TEAROFF
341
static void rebuild_tearoff(vimmenu_T *menu);
342
static HBITMAP s_htearbitmap; // bitmap used to indicate tearoff
343
#endif
344
345
// Flag that is set while processing a message that must not be interrupted by
346
// processing another message.
347
static int s_busy_processing = FALSE;
348
349
static int destroying = FALSE; // call DestroyWindow() ourselves
350
351
#ifdef MSWIN_FIND_REPLACE
352
static UINT s_findrep_msg = 0;
353
static FINDREPLACEW s_findrep_struct;
354
static HWND s_findrep_hwnd = NULL;
355
static int s_findrep_is_find; // TRUE for find dialog, FALSE
356
// for find/replace dialog
357
#endif
358
359
HWND s_hwnd = NULL;
360
static HDC s_hdc = NULL;
361
static HBRUSH s_brush = NULL;
362
363
#ifdef FEAT_TOOLBAR
364
static HWND s_toolbarhwnd = NULL;
365
static WNDPROC s_toolbar_wndproc = NULL;
366
#endif
367
368
#ifdef FEAT_GUI_TABLINE
369
static HWND s_tabhwnd = NULL;
370
static WNDPROC s_tabline_wndproc = NULL;
371
static int showing_tabline = 0;
372
#endif
373
374
static WPARAM s_wParam = 0;
375
static LPARAM s_lParam = 0;
376
377
static HWND s_textArea = NULL;
378
static UINT s_uMsg = 0;
379
380
static char_u *s_textfield; // Used by dialogs to pass back strings
381
382
static int s_need_activate = FALSE;
383
384
// This variable is set when waiting for an event, which is the only moment
385
// scrollbar dragging can be done directly. It's not allowed while commands
386
// are executed, because it may move the cursor and that may cause unexpected
387
// problems (e.g., while ":s" is working).
388
static int allow_scrollbar = FALSE;
389
390
#ifndef _DPI_AWARENESS_CONTEXTS_
391
typedef HANDLE DPI_AWARENESS_CONTEXT;
392
393
typedef enum DPI_AWARENESS {
394
DPI_AWARENESS_INVALID = -1,
395
DPI_AWARENESS_UNAWARE = 0,
396
DPI_AWARENESS_SYSTEM_AWARE = 1,
397
DPI_AWARENESS_PER_MONITOR_AWARE = 2
398
} DPI_AWARENESS;
399
400
# define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
401
# define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
402
# define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
403
# define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
404
# define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
405
#endif
406
407
#define DEFAULT_DPI 96
408
static int s_dpi = DEFAULT_DPI;
409
static BOOL s_in_dpichanged = FALSE;
410
static DPI_AWARENESS s_process_dpi_aware = DPI_AWARENESS_INVALID;
411
static RECT s_suggested_rect;
412
413
static UINT (WINAPI *pGetDpiForSystem)(void) = NULL;
414
static UINT (WINAPI *pGetDpiForWindow)(HWND hwnd) = NULL;
415
static int (WINAPI *pGetSystemMetricsForDpi)(int, UINT) = NULL;
416
//static INT (WINAPI *pGetWindowDpiAwarenessContext)(HWND hwnd) = NULL;
417
static DPI_AWARENESS_CONTEXT (WINAPI *pSetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext) = NULL;
418
static DPI_AWARENESS (WINAPI *pGetAwarenessFromDpiAwarenessContext)(DPI_AWARENESS_CONTEXT) = NULL;
419
420
static HINSTANCE hLibDwm = NULL;
421
static HRESULT (WINAPI *pDwmSetWindowAttribute)(HWND, DWORD, LPCVOID, DWORD);
422
static void dyn_dwm_load(void);
423
424
static int fullscreen_on = FALSE;
425
426
#ifdef FEAT_GUI_DARKTHEME
427
428
static HINSTANCE hUxThemeLib = NULL;
429
static DWORD (WINAPI *pSetPreferredAppMode)(DWORD) = NULL;
430
static void (WINAPI *pFlushMenuThemes)(void) = NULL;
431
static void dyn_uxtheme_load(void);
432
#endif
433
434
static int WINAPI
435
stubGetSystemMetricsForDpi(int nIndex, UINT dpi UNUSED)
436
{
437
return GetSystemMetrics(nIndex);
438
}
439
440
static int
441
adjust_fontsize_by_dpi(int size)
442
{
443
return size * s_dpi / (int)pGetDpiForSystem();
444
}
445
446
static int
447
adjust_by_system_dpi(int size)
448
{
449
return size * (int)pGetDpiForSystem() / DEFAULT_DPI;
450
}
451
452
#if defined(FEAT_DIRECTX)
453
static int
454
directx_enabled(void)
455
{
456
if (s_dwc != NULL)
457
return 1;
458
else if (s_directx_load_attempted)
459
return 0;
460
// load DirectX
461
DWrite_Init();
462
s_directx_load_attempted = 1;
463
s_dwc = DWriteContext_Open();
464
directx_binddc();
465
return s_dwc != NULL ? 1 : 0;
466
}
467
468
static void
469
directx_binddc(void)
470
{
471
if (s_textArea == NULL)
472
return;
473
474
RECT rect;
475
GetClientRect(s_textArea, &rect);
476
DWriteContext_BindDC(s_dwc, s_hdc, &rect);
477
}
478
#endif
479
480
extern int current_font_height; // this is in os_mswin.c
481
482
static struct
483
{
484
UINT key_sym;
485
char_u vim_code0;
486
char_u vim_code1;
487
} special_keys[] =
488
{
489
{VK_UP, 'k', 'u'},
490
{VK_DOWN, 'k', 'd'},
491
{VK_LEFT, 'k', 'l'},
492
{VK_RIGHT, 'k', 'r'},
493
494
{VK_F1, 'k', '1'},
495
{VK_F2, 'k', '2'},
496
{VK_F3, 'k', '3'},
497
{VK_F4, 'k', '4'},
498
{VK_F5, 'k', '5'},
499
{VK_F6, 'k', '6'},
500
{VK_F7, 'k', '7'},
501
{VK_F8, 'k', '8'},
502
{VK_F9, 'k', '9'},
503
{VK_F10, 'k', ';'},
504
505
{VK_F11, 'F', '1'},
506
{VK_F12, 'F', '2'},
507
{VK_F13, 'F', '3'},
508
{VK_F14, 'F', '4'},
509
{VK_F15, 'F', '5'},
510
{VK_F16, 'F', '6'},
511
{VK_F17, 'F', '7'},
512
{VK_F18, 'F', '8'},
513
{VK_F19, 'F', '9'},
514
{VK_F20, 'F', 'A'},
515
516
{VK_F21, 'F', 'B'},
517
#ifdef FEAT_NETBEANS_INTG
518
{VK_PAUSE, 'F', 'B'}, // Pause == F21 (see gui_gtk_x11.c)
519
#endif
520
{VK_F22, 'F', 'C'},
521
{VK_F23, 'F', 'D'},
522
{VK_F24, 'F', 'E'}, // winuser.h defines up to F24
523
524
{VK_HELP, '%', '1'},
525
{VK_BACK, 'k', 'b'},
526
{VK_INSERT, 'k', 'I'},
527
{VK_DELETE, 'k', 'D'},
528
{VK_HOME, 'k', 'h'},
529
{VK_END, '@', '7'},
530
{VK_PRIOR, 'k', 'P'},
531
{VK_NEXT, 'k', 'N'},
532
{VK_PRINT, '%', '9'},
533
{VK_ADD, 'K', '6'},
534
{VK_SUBTRACT, 'K', '7'},
535
{VK_DIVIDE, 'K', '8'},
536
{VK_MULTIPLY, 'K', '9'},
537
{VK_SEPARATOR, 'K', 'A'}, // Keypad Enter
538
{VK_DECIMAL, 'K', 'B'},
539
540
{VK_NUMPAD0, 'K', 'C'},
541
{VK_NUMPAD1, 'K', 'D'},
542
{VK_NUMPAD2, 'K', 'E'},
543
{VK_NUMPAD3, 'K', 'F'},
544
{VK_NUMPAD4, 'K', 'G'},
545
{VK_NUMPAD5, 'K', 'H'},
546
{VK_NUMPAD6, 'K', 'I'},
547
{VK_NUMPAD7, 'K', 'J'},
548
{VK_NUMPAD8, 'K', 'K'},
549
{VK_NUMPAD9, 'K', 'L'},
550
551
// Keys that we want to be able to use any modifier with:
552
{VK_SPACE, ' ', NUL},
553
{VK_TAB, TAB, NUL},
554
{VK_ESCAPE, ESC, NUL},
555
{NL, NL, NUL},
556
{CAR, CAR, NUL},
557
558
// End of list marker:
559
{0, 0, 0}
560
};
561
562
// Local variables
563
static int s_button_pending = -1;
564
565
// s_getting_focus is set when we got focus but didn't see mouse-up event yet,
566
// so don't reset s_button_pending.
567
static int s_getting_focus = FALSE;
568
569
static int s_x_pending;
570
static int s_y_pending;
571
static UINT s_kFlags_pending;
572
static UINT_PTR s_wait_timer = 0; // Timer for get char from user
573
static int s_timed_out = FALSE;
574
static int dead_key = DEAD_KEY_OFF;
575
static UINT surrogate_pending_ch = 0; // 0: no surrogate pending,
576
// else a high surrogate
577
578
#ifdef FEAT_BEVAL_GUI
579
// balloon-eval WM_NOTIFY_HANDLER
580
static void Handle_WM_Notify(HWND hwnd, LPNMHDR pnmh);
581
static void track_user_activity(UINT uMsg);
582
#endif
583
584
/*
585
* For control IME.
586
*
587
* These LOGFONTW used for IME.
588
*/
589
#ifdef FEAT_MBYTE_IME
590
// holds LOGFONTW for 'guifontwide' if available, otherwise 'guifont'
591
static LOGFONTW norm_logfont;
592
// holds LOGFONTW for 'guifont' always.
593
static LOGFONTW sub_logfont;
594
#endif
595
596
#ifdef FEAT_MBYTE_IME
597
static LRESULT _OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData);
598
#endif
599
600
#if defined(FEAT_BROWSE)
601
static char_u *convert_filter(char_u *s);
602
#endif
603
604
#ifdef DEBUG_PRINT_ERROR
605
/*
606
* Print out the last Windows error message
607
*/
608
static void
609
print_windows_error(void)
610
{
611
LPVOID lpMsgBuf;
612
613
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
614
NULL, GetLastError(),
615
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
616
(LPTSTR) &lpMsgBuf, 0, NULL);
617
TRACE1("Error: %s\n", lpMsgBuf);
618
LocalFree(lpMsgBuf);
619
}
620
#endif
621
622
/*
623
* Cursor blink functions.
624
*
625
* This is a simple state machine:
626
* BLINK_NONE not blinking at all
627
* BLINK_OFF blinking, cursor is not shown
628
* BLINK_ON blinking, cursor is shown
629
*/
630
631
#define BLINK_NONE 0
632
#define BLINK_OFF 1
633
#define BLINK_ON 2
634
635
static int blink_state = BLINK_NONE;
636
static long_u blink_waittime = 700;
637
static long_u blink_ontime = 400;
638
static long_u blink_offtime = 250;
639
static UINT_PTR blink_timer = 0;
640
641
int
642
gui_mch_is_blinking(void)
643
{
644
return blink_state != BLINK_NONE;
645
}
646
647
int
648
gui_mch_is_blink_off(void)
649
{
650
return blink_state == BLINK_OFF;
651
}
652
653
void
654
gui_mch_set_blinking(long wait, long on, long off)
655
{
656
blink_waittime = wait;
657
blink_ontime = on;
658
blink_offtime = off;
659
}
660
661
static VOID CALLBACK
662
_OnBlinkTimer(
663
HWND hwnd,
664
UINT uMsg UNUSED,
665
UINT_PTR idEvent,
666
DWORD dwTime UNUSED)
667
{
668
MSG msg;
669
670
/*
671
TRACE2("Got timer event, id %d, blink_timer %d\n", idEvent, blink_timer);
672
*/
673
674
KillTimer(NULL, idEvent);
675
676
// Eat spurious WM_TIMER messages
677
while (PeekMessageW(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
678
;
679
680
if (blink_state == BLINK_ON)
681
{
682
gui_undraw_cursor();
683
blink_state = BLINK_OFF;
684
blink_timer = SetTimer(NULL, 0, (UINT)blink_offtime, _OnBlinkTimer);
685
}
686
else
687
{
688
gui_update_cursor(TRUE, FALSE);
689
blink_state = BLINK_ON;
690
blink_timer = SetTimer(NULL, 0, (UINT)blink_ontime, _OnBlinkTimer);
691
}
692
gui_mch_flush();
693
}
694
695
static void
696
gui_mswin_rm_blink_timer(void)
697
{
698
MSG msg;
699
700
if (blink_timer == 0)
701
return;
702
703
KillTimer(NULL, blink_timer);
704
// Eat spurious WM_TIMER messages
705
while (PeekMessageW(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
706
;
707
blink_timer = 0;
708
}
709
710
/*
711
* Stop the cursor blinking. Show the cursor if it wasn't shown.
712
*/
713
void
714
gui_mch_stop_blink(int may_call_gui_update_cursor)
715
{
716
gui_mswin_rm_blink_timer();
717
if (blink_state == BLINK_OFF && may_call_gui_update_cursor)
718
{
719
gui_update_cursor(TRUE, FALSE);
720
gui_mch_flush();
721
}
722
blink_state = BLINK_NONE;
723
}
724
725
/*
726
* Start the cursor blinking. If it was already blinking, this restarts the
727
* waiting time and shows the cursor.
728
*/
729
void
730
gui_mch_start_blink(void)
731
{
732
gui_mswin_rm_blink_timer();
733
734
// Only switch blinking on if none of the times is zero
735
if (blink_waittime && blink_ontime && blink_offtime && gui.in_focus)
736
{
737
blink_timer = SetTimer(NULL, 0, (UINT)blink_waittime, _OnBlinkTimer);
738
blink_state = BLINK_ON;
739
gui_update_cursor(TRUE, FALSE);
740
gui_mch_flush();
741
}
742
}
743
744
/*
745
* Call-back routines.
746
*/
747
748
static VOID CALLBACK
749
_OnTimer(
750
HWND hwnd,
751
UINT uMsg UNUSED,
752
UINT_PTR idEvent,
753
DWORD dwTime UNUSED)
754
{
755
MSG msg;
756
757
/*
758
TRACE2("Got timer event, id %d, s_wait_timer %d\n", idEvent, s_wait_timer);
759
*/
760
KillTimer(NULL, idEvent);
761
s_timed_out = TRUE;
762
763
// Eat spurious WM_TIMER messages
764
while (PeekMessageW(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
765
;
766
if (idEvent == s_wait_timer)
767
s_wait_timer = 0;
768
}
769
770
static void
771
_OnDeadChar(
772
HWND hwnd UNUSED,
773
UINT ch UNUSED,
774
int cRepeat UNUSED)
775
{
776
dead_key = DEAD_KEY_SET_DEFAULT;
777
}
778
779
/*
780
* Convert Unicode character "ch" to bytes in "string[slen]".
781
* When "had_alt" is TRUE the ALT key was included in "ch".
782
* Return the length.
783
* Because the Windows API uses UTF-16, we have to deal with surrogate
784
* pairs; this is where we choose to deal with them: if "ch" is a high
785
* surrogate, it will be stored, and the length returned will be zero; the next
786
* char_to_string call will then include the high surrogate, decoding the pair
787
* of UTF-16 code units to a single Unicode code point, presuming it is the
788
* matching low surrogate.
789
*/
790
static int
791
char_to_string(int ch, char_u *string, int slen, int had_alt)
792
{
793
int len;
794
int i;
795
WCHAR wstring[2];
796
char_u *ws = NULL;
797
798
if (surrogate_pending_ch != 0)
799
{
800
// We don't guarantee ch is a low surrogate to match the high surrogate
801
// we already have; it should be, but if it isn't, tough luck.
802
wstring[0] = surrogate_pending_ch;
803
wstring[1] = ch;
804
surrogate_pending_ch = 0;
805
len = 2;
806
}
807
else if (ch >= 0xD800 && ch <= 0xDBFF) // high surrogate
808
{
809
// We don't have the entire code point yet, only the first UTF-16 code
810
// unit; so just remember it and use it in the next call.
811
surrogate_pending_ch = ch;
812
return 0;
813
}
814
else
815
{
816
wstring[0] = ch;
817
len = 1;
818
}
819
820
// "ch" is a UTF-16 character. Convert it to a string of bytes. When
821
// "enc_codepage" is non-zero use the standard Win32 function,
822
// otherwise use our own conversion function (e.g., for UTF-8).
823
if (enc_codepage > 0)
824
{
825
len = WideCharToMultiByte(enc_codepage, 0, wstring, len,
826
(LPSTR)string, slen, 0, NULL);
827
// If we had included the ALT key into the character but now the
828
// upper bit is no longer set, that probably means the conversion
829
// failed. Convert the original character and set the upper bit
830
// afterwards.
831
if (had_alt && len == 1 && ch >= 0x80 && string[0] < 0x80)
832
{
833
wstring[0] = ch & 0x7f;
834
len = WideCharToMultiByte(enc_codepage, 0, wstring, len,
835
(LPSTR)string, slen, 0, NULL);
836
if (len == 1) // safety check
837
string[0] |= 0x80;
838
}
839
}
840
else
841
{
842
ws = utf16_to_enc(wstring, &len);
843
if (ws == NULL)
844
len = 0;
845
else
846
{
847
if (len > slen) // just in case
848
len = slen;
849
mch_memmove(string, ws, len);
850
vim_free(ws);
851
}
852
}
853
854
if (len == 0)
855
{
856
string[0] = ch;
857
len = 1;
858
}
859
860
for (i = 0; i < len; ++i)
861
if (string[i] == CSI && len <= slen - 2)
862
{
863
// Insert CSI as K_CSI.
864
mch_memmove(string + i + 3, string + i + 1, len - i - 1);
865
string[++i] = KS_EXTRA;
866
string[++i] = (int)KE_CSI;
867
len += 2;
868
}
869
870
return len;
871
}
872
873
/*
874
* Experimental implementation, introduced in v8.2.4807
875
* "processing key event in Win32 GUI is not ideal"
876
*
877
* TODO: since introduction, this experimental function started
878
* to be used as well outside of original key press/processing
879
* area, and usages not via "get_active_modifiers_via_ptr" should
880
* be watched.
881
*/
882
static int
883
get_active_modifiers_experimental(void)
884
{
885
int modifiers = 0;
886
887
if (GetKeyState(VK_CONTROL) & 0x8000)
888
modifiers |= MOD_MASK_CTRL;
889
if (GetKeyState(VK_SHIFT) & 0x8000)
890
modifiers |= MOD_MASK_SHIFT;
891
// Windows handles Ctrl + Alt as AltGr and vice-versa. We can distinguish
892
// the two cases by checking whether the left or the right Alt key is
893
// pressed.
894
if (GetKeyState(VK_LMENU) & 0x8000)
895
modifiers |= MOD_MASK_ALT;
896
if ((modifiers & MOD_MASK_CTRL) && (GetKeyState(VK_RMENU) & 0x8000))
897
modifiers &= ~MOD_MASK_CTRL;
898
// Add RightALT only if it is hold alone (without Ctrl), because if AltGr
899
// is pressed, Windows claims that Ctrl is hold as well. That way we can
900
// recognize Right-ALT alone and be sure that not AltGr is hold.
901
if (!(GetKeyState(VK_CONTROL) & 0x8000)
902
&& (GetKeyState(VK_RMENU) & 0x8000)
903
&& !(GetKeyState(VK_LMENU) & 0x8000)) // seems AltGr has both set
904
modifiers |= MOD_MASK_ALT;
905
906
return modifiers;
907
}
908
909
/*
910
* "Classic" implementation, existing prior to v8.2.4807
911
*/
912
static int
913
get_active_modifiers_classic(void)
914
{
915
int modifiers = 0;
916
917
if (GetKeyState(VK_SHIFT) & 0x8000)
918
modifiers |= MOD_MASK_SHIFT;
919
/*
920
* Don't use caps-lock as shift, because these are special keys
921
* being considered here, and we only want letters to get
922
* shifted -- webb
923
*/
924
/*
925
if (GetKeyState(VK_CAPITAL) & 0x0001)
926
modifiers ^= MOD_MASK_SHIFT;
927
*/
928
if (GetKeyState(VK_CONTROL) & 0x8000)
929
modifiers |= MOD_MASK_CTRL;
930
if (GetKeyState(VK_MENU) & 0x8000)
931
modifiers |= MOD_MASK_ALT;
932
933
return modifiers;
934
}
935
936
static int
937
get_active_modifiers(void)
938
{
939
return get_active_modifiers_experimental();
940
}
941
942
static int
943
get_active_modifiers_via_ptr(void)
944
{
945
// marshal to corresponding implementation
946
return keycode_trans_strategy_used->ptr_get_active_modifiers();
947
}
948
949
/*
950
* Key hit, add it to the input buffer.
951
*/
952
static void
953
_OnChar(
954
HWND hwnd UNUSED,
955
UINT cch,
956
int cRepeat UNUSED)
957
{
958
// marshal to corresponding implementation
959
keycode_trans_strategy_used->ptr_on_char(hwnd, cch, cRepeat);
960
}
961
962
/*
963
* Experimental implementation, introduced in v8.2.4807
964
* "processing key event in Win32 GUI is not ideal"
965
*/
966
static void
967
_OnChar_experimental(
968
HWND hwnd UNUSED,
969
UINT cch,
970
int cRepeat UNUSED)
971
{
972
char_u string[40];
973
int len = 0;
974
int modifiers;
975
int ch = cch; // special keys are negative
976
977
if (dead_key == DEAD_KEY_SKIP_ON_CHAR)
978
return;
979
980
// keep DEAD_KEY_TRANSIENT_IN_ON_CHAR value for later handling in
981
// process_message()
982
if (dead_key != DEAD_KEY_TRANSIENT_IN_ON_CHAR)
983
dead_key = DEAD_KEY_OFF;
984
985
modifiers = get_active_modifiers_experimental();
986
987
ch = simplify_key(ch, &modifiers);
988
989
// Some keys need adjustment when the Ctrl modifier is used.
990
++no_reduce_keys;
991
ch = may_adjust_key_for_ctrl(modifiers, ch);
992
--no_reduce_keys;
993
994
// remove the SHIFT modifier for keys where it's already included, e.g.,
995
// '(' and '*'
996
modifiers = may_remove_shift_modifier(modifiers, ch);
997
998
// Unify modifiers somewhat. No longer use ALT to set the 8th bit.
999
ch = extract_modifiers(ch, &modifiers, FALSE, NULL);
1000
if (ch == CSI)
1001
ch = K_CSI;
1002
1003
if (modifiers)
1004
{
1005
string[0] = CSI;
1006
string[1] = KS_MODIFIER;
1007
string[2] = modifiers;
1008
add_to_input_buf(string, 3);
1009
}
1010
1011
len = char_to_string(ch, string, 40, FALSE);
1012
if (len == 1 && string[0] == Ctrl_C && ctrl_c_interrupts)
1013
{
1014
trash_input_buf();
1015
got_int = TRUE;
1016
}
1017
1018
add_to_input_buf(string, len);
1019
}
1020
1021
/*
1022
* "Classic" implementation, existing prior to v8.2.4807
1023
*/
1024
static void
1025
_OnChar_classic(
1026
HWND hwnd UNUSED,
1027
UINT ch,
1028
int cRepeat UNUSED)
1029
{
1030
char_u string[40];
1031
int len = 0;
1032
1033
dead_key = 0;
1034
1035
len = char_to_string(ch, string, 40, FALSE);
1036
if (len == 1 && string[0] == Ctrl_C && ctrl_c_interrupts)
1037
{
1038
trash_input_buf();
1039
got_int = TRUE;
1040
}
1041
1042
add_to_input_buf(string, len);
1043
}
1044
1045
/*
1046
* Alt-Key hit, add it to the input buffer.
1047
*/
1048
static void
1049
_OnSysChar(
1050
HWND hwnd UNUSED,
1051
UINT cch,
1052
int cRepeat UNUSED)
1053
{
1054
// marshal to corresponding implementation
1055
keycode_trans_strategy_used->ptr_on_sys_char(hwnd, cch, cRepeat);
1056
}
1057
1058
/*
1059
* Experimental implementation, introduced in v8.2.4807
1060
* "processing key event in Win32 GUI is not ideal"
1061
*/
1062
static void
1063
_OnSysChar_experimental(
1064
HWND hwnd UNUSED,
1065
UINT cch,
1066
int cRepeat UNUSED)
1067
{
1068
char_u string[40]; // Enough for multibyte character
1069
int len;
1070
int modifiers;
1071
int ch = cch; // special keys are negative
1072
1073
dead_key = DEAD_KEY_OFF;
1074
1075
// OK, we have a character key (given by ch) which was entered with the
1076
// ALT key pressed. Eg, if the user presses Alt-A, then ch == 'A'. Note
1077
// that the system distinguishes Alt-a and Alt-A (Alt-Shift-a unless
1078
// CAPSLOCK is pressed) at this point.
1079
modifiers = get_active_modifiers_experimental();
1080
ch = simplify_key(ch, &modifiers);
1081
// remove the SHIFT modifier for keys where it's already included, e.g.,
1082
// '(' and '*'
1083
modifiers = may_remove_shift_modifier(modifiers, ch);
1084
1085
// Unify modifiers somewhat. No longer use ALT to set the 8th bit.
1086
ch = extract_modifiers(ch, &modifiers, FALSE, NULL);
1087
if (ch == CSI)
1088
ch = K_CSI;
1089
1090
len = 0;
1091
if (modifiers)
1092
{
1093
string[len++] = CSI;
1094
string[len++] = KS_MODIFIER;
1095
string[len++] = modifiers;
1096
}
1097
1098
if (IS_SPECIAL((int)ch))
1099
{
1100
string[len++] = CSI;
1101
string[len++] = K_SECOND((int)ch);
1102
string[len++] = K_THIRD((int)ch);
1103
}
1104
else
1105
{
1106
// Although the documentation isn't clear about it, we assume "ch" is
1107
// a Unicode character.
1108
len += char_to_string(ch, string + len, 40 - len, TRUE);
1109
}
1110
1111
add_to_input_buf(string, len);
1112
}
1113
1114
/*
1115
* "Classic" implementation, existing prior to v8.2.4807
1116
*/
1117
static void
1118
_OnSysChar_classic(
1119
HWND hwnd UNUSED,
1120
UINT cch,
1121
int cRepeat UNUSED)
1122
{
1123
char_u string[40]; // Enough for multibyte character
1124
int len;
1125
int modifiers;
1126
int ch = cch; // special keys are negative
1127
1128
dead_key = 0;
1129
1130
// TRACE("OnSysChar(%d, %c)\n", ch, ch);
1131
1132
// OK, we have a character key (given by ch) which was entered with the
1133
// ALT key pressed. Eg, if the user presses Alt-A, then ch == 'A'. Note
1134
// that the system distinguishes Alt-a and Alt-A (Alt-Shift-a unless
1135
// CAPSLOCK is pressed) at this point.
1136
modifiers = MOD_MASK_ALT;
1137
if (GetKeyState(VK_SHIFT) & 0x8000)
1138
modifiers |= MOD_MASK_SHIFT;
1139
if (GetKeyState(VK_CONTROL) & 0x8000)
1140
modifiers |= MOD_MASK_CTRL;
1141
1142
ch = simplify_key(ch, &modifiers);
1143
// remove the SHIFT modifier for keys where it's already included, e.g.,
1144
// '(' and '*'
1145
modifiers = may_remove_shift_modifier(modifiers, ch);
1146
1147
// Unify modifiers somewhat. No longer use ALT to set the 8th bit.
1148
ch = extract_modifiers(ch, &modifiers, FALSE, NULL);
1149
if (ch == CSI)
1150
ch = K_CSI;
1151
1152
len = 0;
1153
if (modifiers)
1154
{
1155
string[len++] = CSI;
1156
string[len++] = KS_MODIFIER;
1157
string[len++] = modifiers;
1158
}
1159
1160
if (IS_SPECIAL((int)ch))
1161
{
1162
string[len++] = CSI;
1163
string[len++] = K_SECOND((int)ch);
1164
string[len++] = K_THIRD((int)ch);
1165
}
1166
else
1167
{
1168
// Although the documentation isn't clear about it, we assume "ch" is
1169
// a Unicode character.
1170
len += char_to_string(ch, string + len, 40 - len, TRUE);
1171
}
1172
1173
add_to_input_buf(string, len);
1174
}
1175
1176
static void
1177
_OnMouseEvent(
1178
int button,
1179
int x,
1180
int y,
1181
int repeated_click,
1182
UINT keyFlags)
1183
{
1184
int vim_modifiers = 0x0;
1185
1186
s_getting_focus = FALSE;
1187
1188
if (keyFlags & MK_SHIFT)
1189
vim_modifiers |= MOUSE_SHIFT;
1190
if (keyFlags & MK_CONTROL)
1191
vim_modifiers |= MOUSE_CTRL;
1192
if (GetKeyState(VK_LMENU) & 0x8000)
1193
vim_modifiers |= MOUSE_ALT;
1194
1195
gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers);
1196
}
1197
1198
static void
1199
_OnMouseButtonDown(
1200
HWND hwnd UNUSED,
1201
BOOL fDoubleClick UNUSED,
1202
int x,
1203
int y,
1204
UINT keyFlags)
1205
{
1206
static LONG s_prevTime = 0;
1207
1208
LONG currentTime = GetMessageTime();
1209
int button = -1;
1210
int repeated_click;
1211
1212
// Give main window the focus: this is so the cursor isn't hollow.
1213
(void)SetFocus(s_hwnd);
1214
1215
if (s_uMsg == WM_LBUTTONDOWN || s_uMsg == WM_LBUTTONDBLCLK)
1216
button = MOUSE_LEFT;
1217
else if (s_uMsg == WM_MBUTTONDOWN || s_uMsg == WM_MBUTTONDBLCLK)
1218
button = MOUSE_MIDDLE;
1219
else if (s_uMsg == WM_RBUTTONDOWN || s_uMsg == WM_RBUTTONDBLCLK)
1220
button = MOUSE_RIGHT;
1221
else if (s_uMsg == WM_XBUTTONDOWN || s_uMsg == WM_XBUTTONDBLCLK)
1222
{
1223
button = ((GET_XBUTTON_WPARAM(s_wParam) == 1) ? MOUSE_X1 : MOUSE_X2);
1224
}
1225
else if (s_uMsg == WM_CAPTURECHANGED)
1226
{
1227
// on W95/NT4, somehow you get in here with an odd Msg
1228
// if you press one button while holding down the other..
1229
if (s_button_pending == MOUSE_LEFT)
1230
button = MOUSE_RIGHT;
1231
else
1232
button = MOUSE_LEFT;
1233
}
1234
1235
if (button < 0)
1236
return;
1237
1238
repeated_click = ((int)(currentTime - s_prevTime) < p_mouset);
1239
1240
/*
1241
* Holding down the left and right buttons simulates pushing the middle
1242
* button.
1243
*/
1244
if (repeated_click
1245
&& ((button == MOUSE_LEFT && s_button_pending == MOUSE_RIGHT)
1246
|| (button == MOUSE_RIGHT
1247
&& s_button_pending == MOUSE_LEFT)))
1248
{
1249
/*
1250
* Hmm, gui.c will ignore more than one button down at a time, so
1251
* pretend we let go of it first.
1252
*/
1253
gui_send_mouse_event(MOUSE_RELEASE, x, y, FALSE, 0x0);
1254
button = MOUSE_MIDDLE;
1255
repeated_click = FALSE;
1256
s_button_pending = -1;
1257
_OnMouseEvent(button, x, y, repeated_click, keyFlags);
1258
}
1259
else if ((repeated_click)
1260
|| (mouse_model_popup() && (button == MOUSE_RIGHT)))
1261
{
1262
if (s_button_pending > -1)
1263
{
1264
_OnMouseEvent(s_button_pending, x, y, FALSE, keyFlags);
1265
s_button_pending = -1;
1266
}
1267
// TRACE("Button down at x %d, y %d\n", x, y);
1268
_OnMouseEvent(button, x, y, repeated_click, keyFlags);
1269
}
1270
else
1271
{
1272
/*
1273
* If this is the first press (i.e. not a multiple click) don't
1274
* action immediately, but store and wait for:
1275
* i) button-up
1276
* ii) mouse move
1277
* iii) another button press
1278
* before using it.
1279
* This enables us to make left+right simulate middle button,
1280
* without left or right being actioned first. The side-effect is
1281
* that if you click and hold the mouse without dragging, the
1282
* cursor doesn't move until you release the button. In practice
1283
* this is hardly a problem.
1284
*/
1285
s_button_pending = button;
1286
s_x_pending = x;
1287
s_y_pending = y;
1288
s_kFlags_pending = keyFlags;
1289
}
1290
1291
s_prevTime = currentTime;
1292
}
1293
1294
static void
1295
_OnMouseMoveOrRelease(
1296
HWND hwnd UNUSED,
1297
int x,
1298
int y,
1299
UINT keyFlags)
1300
{
1301
int button;
1302
1303
s_getting_focus = FALSE;
1304
if (s_button_pending > -1)
1305
{
1306
// Delayed action for mouse down event
1307
_OnMouseEvent(s_button_pending, s_x_pending,
1308
s_y_pending, FALSE, s_kFlags_pending);
1309
s_button_pending = -1;
1310
}
1311
if (s_uMsg == WM_MOUSEMOVE)
1312
{
1313
/*
1314
* It's only a MOUSE_DRAG if one or more mouse buttons are being held
1315
* down.
1316
*/
1317
if (!(keyFlags & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON
1318
| MK_XBUTTON1 | MK_XBUTTON2)))
1319
{
1320
gui_mouse_moved(x, y);
1321
return;
1322
}
1323
1324
/*
1325
* While button is down, keep grabbing mouse move events when
1326
* the mouse goes outside the window
1327
*/
1328
SetCapture(s_textArea);
1329
button = MOUSE_DRAG;
1330
// TRACE(" move at x %d, y %d\n", x, y);
1331
}
1332
else
1333
{
1334
ReleaseCapture();
1335
button = MOUSE_RELEASE;
1336
// TRACE(" up at x %d, y %d\n", x, y);
1337
}
1338
1339
_OnMouseEvent(button, x, y, FALSE, keyFlags);
1340
}
1341
1342
static void
1343
_OnSizeTextArea(
1344
HWND hwnd UNUSED,
1345
UINT state UNUSED,
1346
int cx UNUSED,
1347
int cy UNUSED)
1348
{
1349
#if defined(FEAT_DIRECTX)
1350
if (IS_ENABLE_DIRECTX())
1351
directx_binddc();
1352
#endif
1353
}
1354
1355
#ifdef FEAT_MENU
1356
/*
1357
* Find the vimmenu_T with the given id
1358
*/
1359
static vimmenu_T *
1360
gui_mswin_find_menu(
1361
vimmenu_T *pMenu,
1362
int id)
1363
{
1364
vimmenu_T *pChildMenu;
1365
1366
while (pMenu)
1367
{
1368
if (pMenu->id == (UINT)id)
1369
break;
1370
if (pMenu->children != NULL)
1371
{
1372
pChildMenu = gui_mswin_find_menu(pMenu->children, id);
1373
if (pChildMenu)
1374
{
1375
pMenu = pChildMenu;
1376
break;
1377
}
1378
}
1379
pMenu = pMenu->next;
1380
}
1381
return pMenu;
1382
}
1383
1384
static void
1385
_OnMenu(
1386
HWND hwnd UNUSED,
1387
int id,
1388
HWND hwndCtl UNUSED,
1389
UINT codeNotify UNUSED)
1390
{
1391
vimmenu_T *pMenu;
1392
1393
pMenu = gui_mswin_find_menu(root_menu, id);
1394
if (pMenu)
1395
gui_menu_cb(pMenu);
1396
}
1397
#endif
1398
1399
#ifdef MSWIN_FIND_REPLACE
1400
/*
1401
* Handle a Find/Replace window message.
1402
*/
1403
static void
1404
_OnFindRepl(void)
1405
{
1406
int flags = 0;
1407
int down;
1408
1409
if (s_findrep_struct.Flags & FR_DIALOGTERM)
1410
// Give main window the focus back.
1411
(void)SetFocus(s_hwnd);
1412
1413
if (s_findrep_struct.Flags & FR_FINDNEXT)
1414
{
1415
flags = FRD_FINDNEXT;
1416
1417
// Give main window the focus back: this is so the cursor isn't
1418
// hollow.
1419
(void)SetFocus(s_hwnd);
1420
}
1421
else if (s_findrep_struct.Flags & FR_REPLACE)
1422
{
1423
flags = FRD_REPLACE;
1424
1425
// Give main window the focus back: this is so the cursor isn't
1426
// hollow.
1427
(void)SetFocus(s_hwnd);
1428
}
1429
else if (s_findrep_struct.Flags & FR_REPLACEALL)
1430
{
1431
flags = FRD_REPLACEALL;
1432
}
1433
1434
if (flags == 0)
1435
return;
1436
1437
char_u *p, *q;
1438
1439
// Call the generic GUI function to do the actual work.
1440
if (s_findrep_struct.Flags & FR_WHOLEWORD)
1441
flags |= FRD_WHOLE_WORD;
1442
if (s_findrep_struct.Flags & FR_MATCHCASE)
1443
flags |= FRD_MATCH_CASE;
1444
down = (s_findrep_struct.Flags & FR_DOWN) != 0;
1445
p = utf16_to_enc(s_findrep_struct.lpstrFindWhat, NULL);
1446
q = utf16_to_enc(s_findrep_struct.lpstrReplaceWith, NULL);
1447
if (p != NULL && q != NULL)
1448
gui_do_findrepl(flags, p, q, down);
1449
vim_free(p);
1450
vim_free(q);
1451
}
1452
#endif
1453
1454
static void
1455
HandleMouseHide(UINT uMsg, LPARAM lParam)
1456
{
1457
static LPARAM last_lParam = 0L;
1458
1459
// We sometimes get a mousemove when the mouse didn't move...
1460
if (uMsg == WM_MOUSEMOVE || uMsg == WM_NCMOUSEMOVE)
1461
{
1462
if (lParam == last_lParam)
1463
return;
1464
last_lParam = lParam;
1465
}
1466
1467
// Handle specially, to centralise coding. We need to be sure we catch all
1468
// possible events which should cause us to restore the cursor (as it is a
1469
// shared resource, we take full responsibility for it).
1470
switch (uMsg)
1471
{
1472
case WM_KEYUP:
1473
case WM_CHAR:
1474
/*
1475
* blank out the pointer if necessary
1476
*/
1477
if (p_mh)
1478
gui_mch_mousehide(TRUE);
1479
break;
1480
1481
case WM_SYSKEYUP: // show the pointer when a system-key is pressed
1482
case WM_SYSCHAR:
1483
case WM_MOUSEMOVE: // show the pointer on any mouse action
1484
case WM_LBUTTONDOWN:
1485
case WM_LBUTTONUP:
1486
case WM_MBUTTONDOWN:
1487
case WM_MBUTTONUP:
1488
case WM_RBUTTONDOWN:
1489
case WM_RBUTTONUP:
1490
case WM_XBUTTONDOWN:
1491
case WM_XBUTTONUP:
1492
case WM_NCMOUSEMOVE:
1493
case WM_NCLBUTTONDOWN:
1494
case WM_NCLBUTTONUP:
1495
case WM_NCMBUTTONDOWN:
1496
case WM_NCMBUTTONUP:
1497
case WM_NCRBUTTONDOWN:
1498
case WM_NCRBUTTONUP:
1499
case WM_KILLFOCUS:
1500
/*
1501
* if the pointer is currently hidden, then we should show it.
1502
*/
1503
gui_mch_mousehide(FALSE);
1504
break;
1505
}
1506
}
1507
1508
static LRESULT CALLBACK
1509
_TextAreaWndProc(
1510
HWND hwnd,
1511
UINT uMsg,
1512
WPARAM wParam,
1513
LPARAM lParam)
1514
{
1515
/*
1516
TRACE("TextAreaWndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n",
1517
hwnd, uMsg, wParam, lParam);
1518
*/
1519
1520
HandleMouseHide(uMsg, lParam);
1521
1522
s_uMsg = uMsg;
1523
s_wParam = wParam;
1524
s_lParam = lParam;
1525
1526
#ifdef FEAT_BEVAL_GUI
1527
track_user_activity(uMsg);
1528
#endif
1529
1530
switch (uMsg)
1531
{
1532
HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK,_OnMouseButtonDown);
1533
HANDLE_MSG(hwnd, WM_LBUTTONDOWN,_OnMouseButtonDown);
1534
HANDLE_MSG(hwnd, WM_LBUTTONUP, _OnMouseMoveOrRelease);
1535
HANDLE_MSG(hwnd, WM_MBUTTONDBLCLK,_OnMouseButtonDown);
1536
HANDLE_MSG(hwnd, WM_MBUTTONDOWN,_OnMouseButtonDown);
1537
HANDLE_MSG(hwnd, WM_MBUTTONUP, _OnMouseMoveOrRelease);
1538
HANDLE_MSG(hwnd, WM_MOUSEMOVE, _OnMouseMoveOrRelease);
1539
HANDLE_MSG(hwnd, WM_PAINT, _OnPaint);
1540
HANDLE_MSG(hwnd, WM_RBUTTONDBLCLK,_OnMouseButtonDown);
1541
HANDLE_MSG(hwnd, WM_RBUTTONDOWN,_OnMouseButtonDown);
1542
HANDLE_MSG(hwnd, WM_RBUTTONUP, _OnMouseMoveOrRelease);
1543
HANDLE_MSG(hwnd, WM_XBUTTONDBLCLK,_OnMouseButtonDown);
1544
HANDLE_MSG(hwnd, WM_XBUTTONDOWN,_OnMouseButtonDown);
1545
HANDLE_MSG(hwnd, WM_XBUTTONUP, _OnMouseMoveOrRelease);
1546
HANDLE_MSG(hwnd, WM_SIZE, _OnSizeTextArea);
1547
1548
#ifdef FEAT_BEVAL_GUI
1549
case WM_NOTIFY: Handle_WM_Notify(hwnd, (LPNMHDR)lParam);
1550
return TRUE;
1551
#endif
1552
default:
1553
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1554
}
1555
}
1556
1557
static void
1558
dyn_dwm_load(void)
1559
{
1560
hLibDwm = vimLoadLib("dwmapi.dll");
1561
if (hLibDwm == NULL)
1562
return;
1563
1564
pDwmSetWindowAttribute = (HRESULT (WINAPI *)(HWND, DWORD, LPCVOID, DWORD))
1565
GetProcAddress(hLibDwm, "DwmSetWindowAttribute");
1566
1567
if (pDwmSetWindowAttribute == NULL)
1568
{
1569
FreeLibrary(hLibDwm);
1570
hLibDwm = NULL;
1571
return;
1572
}
1573
}
1574
1575
extern BOOL win11_or_later; // this is in os_win32.c
1576
1577
/*
1578
* Set TitleBar's color. Handle hl-TitleBar and hl-TitleBarNC.
1579
*
1580
* Only enabled when 'guioptions' has 'C'.
1581
* if "TitleBar guibg=NONE guifg=NONE" reset the window back to using the
1582
* system's default behavior for the border color.
1583
*/
1584
void
1585
gui_mch_set_titlebar_colors(void)
1586
{
1587
if (pDwmSetWindowAttribute == NULL || !win11_or_later)
1588
return;
1589
1590
guicolor_T captionColor = 0xFFFFFFFF;
1591
guicolor_T textColor = 0xFFFFFFFF;
1592
1593
if (vim_strchr(p_go, GO_TITLEBAR) != NULL)
1594
{
1595
if (gui.in_focus)
1596
{
1597
captionColor = gui.title_bg_pixel;
1598
textColor = gui.title_fg_pixel;
1599
}
1600
else
1601
{
1602
captionColor = gui.titlenc_bg_pixel;
1603
textColor = gui.titlenc_fg_pixel;
1604
}
1605
1606
if (captionColor == INVALCOLOR)
1607
captionColor = 0xFFFFFFFF;
1608
if (textColor == INVALCOLOR)
1609
textColor = 0xFFFFFFFF;
1610
}
1611
1612
pDwmSetWindowAttribute(s_hwnd, DWMWA_CAPTION_COLOR,
1613
&captionColor, sizeof(captionColor));
1614
pDwmSetWindowAttribute(s_hwnd, DWMWA_TEXT_COLOR,
1615
&textColor, sizeof(textColor));
1616
}
1617
1618
/*
1619
* Called when the foreground or background color has been changed.
1620
*/
1621
void
1622
gui_mch_new_colors(void)
1623
{
1624
HBRUSH prevBrush;
1625
1626
s_brush = CreateSolidBrush(gui.back_pixel);
1627
prevBrush = (HBRUSH)SetClassLongPtr(
1628
s_hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)s_brush);
1629
InvalidateRect(s_hwnd, NULL, TRUE);
1630
DeleteObject(prevBrush);
1631
}
1632
1633
/*
1634
* Set the colors to their default values.
1635
*/
1636
void
1637
gui_mch_def_colors(void)
1638
{
1639
gui.norm_pixel = GetSysColor(COLOR_WINDOWTEXT);
1640
gui.back_pixel = GetSysColor(COLOR_WINDOW);
1641
gui.def_norm_pixel = gui.norm_pixel;
1642
gui.def_back_pixel = gui.back_pixel;
1643
}
1644
1645
/*
1646
* Open the GUI window which was created by a call to gui_mch_init().
1647
*/
1648
int
1649
gui_mch_open(void)
1650
{
1651
// Actually open the window, if not already visible
1652
// (may be done already in gui_mch_set_shellsize)
1653
if (!IsWindowVisible(s_hwnd))
1654
ShowWindow(s_hwnd, SW_SHOWDEFAULT);
1655
1656
#ifdef MSWIN_FIND_REPLACE
1657
// Init replace string here, so that we keep it when re-opening the
1658
// dialog.
1659
s_findrep_struct.lpstrReplaceWith[0] = NUL;
1660
#endif
1661
1662
return OK;
1663
}
1664
1665
/*
1666
* Get the position of the top left corner of the window.
1667
*/
1668
int
1669
gui_mch_get_winpos(int *x, int *y)
1670
{
1671
RECT rect;
1672
1673
GetWindowRect(s_hwnd, &rect);
1674
*x = rect.left;
1675
*y = rect.top;
1676
return OK;
1677
}
1678
1679
/*
1680
* Set the position of the top left corner of the window to the given
1681
* coordinates.
1682
*/
1683
void
1684
gui_mch_set_winpos(int x, int y)
1685
{
1686
SetWindowPos(s_hwnd, NULL, x, y, 0, 0,
1687
SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);
1688
}
1689
void
1690
gui_mch_set_text_area_pos(int x, int y, int w, int h)
1691
{
1692
static int oldx = 0;
1693
static int oldy = 0;
1694
1695
SetWindowPos(s_textArea, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);
1696
1697
#ifdef FEAT_TOOLBAR
1698
if (vim_strchr(p_go, GO_TOOLBAR) != NULL)
1699
SendMessage(s_toolbarhwnd, WM_SIZE,
1700
(WPARAM)0, MAKELPARAM(w, gui.toolbar_height));
1701
#endif
1702
#if defined(FEAT_GUI_TABLINE)
1703
if (showing_tabline)
1704
{
1705
int top = 0;
1706
RECT rect;
1707
1708
# ifdef FEAT_TOOLBAR
1709
if (vim_strchr(p_go, GO_TOOLBAR) != NULL)
1710
top = gui.toolbar_height;
1711
# endif
1712
GetClientRect(s_hwnd, &rect);
1713
MoveWindow(s_tabhwnd, 0, top, rect.right, gui.tabline_height, TRUE);
1714
}
1715
#endif
1716
1717
// When side scroll bar is unshown, the size of window will change.
1718
// then, the text area move left or right. thus client rect should be
1719
// forcedly redrawn. (Yasuhiro Matsumoto)
1720
if (oldx != x || oldy != y)
1721
{
1722
InvalidateRect(s_hwnd, NULL, FALSE);
1723
oldx = x;
1724
oldy = y;
1725
}
1726
}
1727
1728
1729
/*
1730
* Scrollbar stuff:
1731
*/
1732
1733
void
1734
gui_mch_enable_scrollbar(
1735
scrollbar_T *sb,
1736
int flag)
1737
{
1738
ShowScrollBar(sb->id, SB_CTL, flag);
1739
1740
// TODO: When the window is maximized, the size of the window stays the
1741
// same, thus the size of the text area changes. On Win98 it's OK, on Win
1742
// NT 4.0 it's not...
1743
}
1744
1745
void
1746
gui_mch_set_scrollbar_pos(
1747
scrollbar_T *sb,
1748
int x,
1749
int y,
1750
int w,
1751
int h)
1752
{
1753
SetWindowPos(sb->id, NULL, x, y, w, h,
1754
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
1755
}
1756
1757
int
1758
gui_mch_get_scrollbar_xpadding(void)
1759
{
1760
RECT rcTxt, rcWnd;
1761
int xpad;
1762
1763
GetWindowRect(s_textArea, &rcTxt);
1764
GetWindowRect(s_hwnd, &rcWnd);
1765
xpad = rcWnd.right - rcTxt.right - gui.scrollbar_width
1766
- pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi)
1767
- pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi);
1768
return (xpad < 0) ? 0 : xpad;
1769
}
1770
1771
int
1772
gui_mch_get_scrollbar_ypadding(void)
1773
{
1774
RECT rcTxt, rcWnd;
1775
int ypad;
1776
1777
GetWindowRect(s_textArea, &rcTxt);
1778
GetWindowRect(s_hwnd, &rcWnd);
1779
ypad = rcWnd.bottom - rcTxt.bottom - gui.scrollbar_height
1780
- pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi)
1781
- pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi);
1782
return (ypad < 0) ? 0 : ypad;
1783
}
1784
1785
void
1786
gui_mch_create_scrollbar(
1787
scrollbar_T *sb,
1788
int orient) // SBAR_VERT or SBAR_HORIZ
1789
{
1790
sb->id = CreateWindow(
1791
"SCROLLBAR", "Scrollbar",
1792
WS_CHILD | ((orient == SBAR_VERT) ? SBS_VERT : SBS_HORZ), 0, 0,
1793
10, // Any value will do for now
1794
10, // Any value will do for now
1795
s_hwnd, NULL,
1796
g_hinst, NULL);
1797
}
1798
1799
/*
1800
* Find the scrollbar with the given hwnd.
1801
*/
1802
static scrollbar_T *
1803
gui_mswin_find_scrollbar(HWND hwnd)
1804
{
1805
win_T *wp;
1806
1807
if (gui.bottom_sbar.id == hwnd)
1808
return &gui.bottom_sbar;
1809
FOR_ALL_WINDOWS(wp)
1810
{
1811
if (wp->w_scrollbars[SBAR_LEFT].id == hwnd)
1812
return &wp->w_scrollbars[SBAR_LEFT];
1813
if (wp->w_scrollbars[SBAR_RIGHT].id == hwnd)
1814
return &wp->w_scrollbars[SBAR_RIGHT];
1815
}
1816
return NULL;
1817
}
1818
1819
static void
1820
update_scrollbar_size(void)
1821
{
1822
gui.scrollbar_width = pGetSystemMetricsForDpi(SM_CXVSCROLL, s_dpi);
1823
gui.scrollbar_height = pGetSystemMetricsForDpi(SM_CYHSCROLL, s_dpi);
1824
}
1825
1826
/*
1827
* Get the average character size of a font.
1828
*/
1829
static void
1830
GetAverageFontSize(HDC hdc, SIZE *size)
1831
{
1832
// GetTextMetrics() may not return the right value in tmAveCharWidth
1833
// for some fonts. Do our own average computation.
1834
GetTextExtentPoint(hdc,
1835
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
1836
52, size);
1837
size->cx = (size->cx / 26 + 1) / 2;
1838
}
1839
1840
/*
1841
* Get the character size of a font.
1842
*/
1843
static void
1844
GetFontSize(GuiFont font, int *char_width, int *char_height)
1845
{
1846
HWND hwnd = GetDesktopWindow();
1847
HDC hdc = GetWindowDC(hwnd);
1848
HFONT hfntOld = SelectFont(hdc, (HFONT)font);
1849
SIZE size;
1850
TEXTMETRIC tm;
1851
1852
GetTextMetrics(hdc, &tm);
1853
GetAverageFontSize(hdc, &size);
1854
1855
if (char_width)
1856
*char_width = size.cx + tm.tmOverhang;
1857
if (char_height)
1858
*char_height = tm.tmHeight + p_linespace;
1859
1860
SelectFont(hdc, hfntOld);
1861
1862
ReleaseDC(hwnd, hdc);
1863
}
1864
1865
/*
1866
* Update the character size in "gui" structure with the specified font.
1867
*/
1868
static void
1869
UpdateFontSize(GuiFont font)
1870
{
1871
GetFontSize(font, &gui.char_width, &gui.char_height);
1872
}
1873
1874
/*
1875
* Adjust gui.char_height (after 'linespace' was changed).
1876
*/
1877
int
1878
gui_mch_adjust_charheight(void)
1879
{
1880
UpdateFontSize(gui.norm_font);
1881
return OK;
1882
}
1883
1884
static GuiFont
1885
get_font_handle(LOGFONTW *lf)
1886
{
1887
HFONT font = NULL;
1888
1889
// Load the font
1890
font = CreateFontIndirectW(lf);
1891
1892
if (font == NULL)
1893
return NOFONT;
1894
1895
return (GuiFont)font;
1896
}
1897
1898
static int
1899
pixels_to_points(int pixels, int vertical)
1900
{
1901
int points;
1902
HWND hwnd;
1903
HDC hdc;
1904
1905
hwnd = GetDesktopWindow();
1906
hdc = GetWindowDC(hwnd);
1907
1908
points = MulDiv(pixels, 72,
1909
GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX));
1910
1911
ReleaseDC(hwnd, hdc);
1912
1913
return points;
1914
}
1915
1916
GuiFont
1917
gui_mch_get_font(
1918
char_u *name,
1919
int giveErrorIfMissing)
1920
{
1921
LOGFONTW lf;
1922
GuiFont font = NOFONT;
1923
1924
if (get_logfont(&lf, name, NULL, giveErrorIfMissing) == OK)
1925
{
1926
lf.lfHeight = adjust_fontsize_by_dpi(lf.lfHeight);
1927
font = get_font_handle(&lf);
1928
}
1929
if (font == NOFONT && giveErrorIfMissing)
1930
semsg(_(e_unknown_font_str), name);
1931
return font;
1932
}
1933
1934
#if defined(FEAT_EVAL)
1935
/*
1936
* Return the name of font "font" in allocated memory.
1937
* Don't know how to get the actual name, thus use the provided name.
1938
*/
1939
char_u *
1940
gui_mch_get_fontname(GuiFont font UNUSED, char_u *name)
1941
{
1942
if (name == NULL)
1943
return NULL;
1944
return vim_strsave(name);
1945
}
1946
#endif
1947
1948
void
1949
gui_mch_free_font(GuiFont font)
1950
{
1951
if (font)
1952
DeleteObject((HFONT)font);
1953
}
1954
1955
/*
1956
* Return the Pixel value (color) for the given color name.
1957
* Return INVALCOLOR for error.
1958
*/
1959
guicolor_T
1960
gui_mch_get_color(char_u *name)
1961
{
1962
int i;
1963
1964
typedef struct SysColorTable
1965
{
1966
char *name;
1967
int color;
1968
} SysColorTable;
1969
1970
static SysColorTable sys_table[] =
1971
{
1972
{"SYS_3DDKSHADOW", COLOR_3DDKSHADOW},
1973
{"SYS_3DHILIGHT", COLOR_3DHILIGHT},
1974
#ifdef COLOR_3DHIGHLIGHT
1975
{"SYS_3DHIGHLIGHT", COLOR_3DHIGHLIGHT},
1976
#endif
1977
{"SYS_BTNHILIGHT", COLOR_BTNHILIGHT},
1978
{"SYS_BTNHIGHLIGHT", COLOR_BTNHIGHLIGHT},
1979
{"SYS_3DLIGHT", COLOR_3DLIGHT},
1980
{"SYS_3DSHADOW", COLOR_3DSHADOW},
1981
{"SYS_DESKTOP", COLOR_DESKTOP},
1982
{"SYS_INFOBK", COLOR_INFOBK},
1983
{"SYS_INFOTEXT", COLOR_INFOTEXT},
1984
{"SYS_3DFACE", COLOR_3DFACE},
1985
{"SYS_BTNFACE", COLOR_BTNFACE},
1986
{"SYS_BTNSHADOW", COLOR_BTNSHADOW},
1987
{"SYS_ACTIVEBORDER", COLOR_ACTIVEBORDER},
1988
{"SYS_ACTIVECAPTION", COLOR_ACTIVECAPTION},
1989
{"SYS_APPWORKSPACE", COLOR_APPWORKSPACE},
1990
{"SYS_BACKGROUND", COLOR_BACKGROUND},
1991
{"SYS_BTNTEXT", COLOR_BTNTEXT},
1992
{"SYS_CAPTIONTEXT", COLOR_CAPTIONTEXT},
1993
{"SYS_GRAYTEXT", COLOR_GRAYTEXT},
1994
{"SYS_HIGHLIGHT", COLOR_HIGHLIGHT},
1995
{"SYS_HIGHLIGHTTEXT", COLOR_HIGHLIGHTTEXT},
1996
{"SYS_INACTIVEBORDER", COLOR_INACTIVEBORDER},
1997
{"SYS_INACTIVECAPTION", COLOR_INACTIVECAPTION},
1998
{"SYS_INACTIVECAPTIONTEXT", COLOR_INACTIVECAPTIONTEXT},
1999
{"SYS_MENU", COLOR_MENU},
2000
{"SYS_MENUTEXT", COLOR_MENUTEXT},
2001
{"SYS_SCROLLBAR", COLOR_SCROLLBAR},
2002
{"SYS_WINDOW", COLOR_WINDOW},
2003
{"SYS_WINDOWFRAME", COLOR_WINDOWFRAME},
2004
{"SYS_WINDOWTEXT", COLOR_WINDOWTEXT}
2005
};
2006
2007
/*
2008
* Try to look up a system colour.
2009
*/
2010
for (i = 0; i < (int)ARRAY_LENGTH(sys_table); i++)
2011
if (STRICMP(name, sys_table[i].name) == 0)
2012
return GetSysColor(sys_table[i].color);
2013
2014
return gui_get_color_cmn(name);
2015
}
2016
2017
guicolor_T
2018
gui_mch_get_rgb_color(int r, int g, int b)
2019
{
2020
return gui_get_rgb_color_cmn(r, g, b);
2021
}
2022
2023
/*
2024
* Return OK if the key with the termcap name "name" is supported.
2025
*/
2026
int
2027
gui_mch_haskey(char_u *name)
2028
{
2029
int i;
2030
2031
for (i = 0; special_keys[i].vim_code1 != NUL; i++)
2032
if (name[0] == special_keys[i].vim_code0
2033
&& name[1] == special_keys[i].vim_code1)
2034
return OK;
2035
return FAIL;
2036
}
2037
2038
void
2039
gui_mch_beep(void)
2040
{
2041
MessageBeep((UINT)-1);
2042
}
2043
/*
2044
* Invert a rectangle from row r, column c, for nr rows and nc columns.
2045
*/
2046
void
2047
gui_mch_invert_rectangle(
2048
int r,
2049
int c,
2050
int nr,
2051
int nc)
2052
{
2053
RECT rc;
2054
2055
#if defined(FEAT_DIRECTX)
2056
if (IS_ENABLE_DIRECTX())
2057
DWriteContext_Flush(s_dwc);
2058
#endif
2059
2060
/*
2061
* Note: InvertRect() excludes right and bottom of rectangle.
2062
*/
2063
rc.left = FILL_X(c);
2064
rc.top = FILL_Y(r);
2065
rc.right = rc.left + nc * gui.char_width;
2066
rc.bottom = rc.top + nr * gui.char_height;
2067
InvertRect(s_hdc, &rc);
2068
}
2069
2070
/*
2071
* Iconify the GUI window.
2072
*/
2073
void
2074
gui_mch_iconify(void)
2075
{
2076
ShowWindow(s_hwnd, SW_MINIMIZE);
2077
}
2078
2079
/*
2080
* Draw a cursor without focus.
2081
*/
2082
void
2083
gui_mch_draw_hollow_cursor(guicolor_T color)
2084
{
2085
HBRUSH hbr;
2086
RECT rc;
2087
2088
#if defined(FEAT_DIRECTX)
2089
if (IS_ENABLE_DIRECTX())
2090
DWriteContext_Flush(s_dwc);
2091
#endif
2092
2093
/*
2094
* Note: FrameRect() excludes right and bottom of rectangle.
2095
*/
2096
rc.left = FILL_X(gui.col);
2097
rc.top = FILL_Y(gui.row);
2098
rc.right = rc.left + gui.char_width;
2099
if (mb_lefthalve(gui.row, gui.col))
2100
rc.right += gui.char_width;
2101
rc.bottom = rc.top + gui.char_height;
2102
hbr = CreateSolidBrush(color);
2103
FrameRect(s_hdc, &rc, hbr);
2104
DeleteBrush(hbr);
2105
}
2106
/*
2107
* Draw part of a cursor, "w" pixels wide, and "h" pixels high, using
2108
* color "color".
2109
*/
2110
void
2111
gui_mch_draw_part_cursor(
2112
int w,
2113
int h,
2114
guicolor_T color)
2115
{
2116
RECT rc;
2117
2118
/*
2119
* Note: FillRect() excludes right and bottom of rectangle.
2120
*/
2121
rc.left =
2122
#ifdef FEAT_RIGHTLEFT
2123
// vertical line should be on the right of current point
2124
CURSOR_BAR_RIGHT ? FILL_X(gui.col + 1) - w :
2125
#endif
2126
FILL_X(gui.col);
2127
rc.top = FILL_Y(gui.row) + gui.char_height - h;
2128
rc.right = rc.left + w;
2129
rc.bottom = rc.top + h;
2130
2131
fill_rect(&rc, NULL, color);
2132
}
2133
2134
2135
/*
2136
* Generates a VK_SPACE when the internal dead_key flag is set to output the
2137
* dead key's nominal character and re-post the original message.
2138
*/
2139
static void
2140
outputDeadKey_rePost_Ex(MSG originalMsg, int dead_key2set)
2141
{
2142
static MSG deadCharExpel;
2143
2144
if (dead_key == DEAD_KEY_OFF)
2145
return;
2146
2147
dead_key = dead_key2set;
2148
2149
// Make Windows generate the dead key's character
2150
deadCharExpel.message = originalMsg.message;
2151
deadCharExpel.hwnd = originalMsg.hwnd;
2152
deadCharExpel.wParam = VK_SPACE;
2153
2154
TranslateMessage(&deadCharExpel);
2155
2156
// re-generate the current character free of the dead char influence
2157
PostMessage(originalMsg.hwnd, originalMsg.message, originalMsg.wParam,
2158
originalMsg.lParam);
2159
}
2160
2161
/*
2162
* Wrapper for outputDeadKey_rePost_Ex which always reset dead_key value.
2163
*/
2164
static void
2165
outputDeadKey_rePost(MSG originalMsg)
2166
{
2167
outputDeadKey_rePost_Ex(originalMsg, DEAD_KEY_OFF);
2168
}
2169
2170
/*
2171
* Refactored out part of process_message(), responsible for
2172
* handling the case of "not a special key"
2173
*/
2174
static void process_message_usual_key(UINT vk, const MSG *pmsg)
2175
{
2176
// marshal to corresponding implementation
2177
keycode_trans_strategy_used->ptr_process_message_usual_key(vk, pmsg);
2178
}
2179
2180
/*
2181
* Experimental implementation, introduced in v8.2.4807
2182
* "processing key event in Win32 GUI is not ideal"
2183
*/
2184
static void
2185
process_message_usual_key_experimental(UINT vk, const MSG *pmsg)
2186
{
2187
WCHAR ch[8];
2188
int len;
2189
int i;
2190
UINT scan_code;
2191
BYTE keyboard_state[256];
2192
2193
// Construct the state table with only a few modifiers, we don't
2194
// really care about the presence of Ctrl/Alt as those modifiers are
2195
// handled by Vim separately.
2196
memset(keyboard_state, 0, 256);
2197
if (GetKeyState(VK_SHIFT) & 0x8000)
2198
keyboard_state[VK_SHIFT] = 0x80;
2199
if (GetKeyState(VK_CAPITAL) & 0x0001)
2200
keyboard_state[VK_CAPITAL] = 0x01;
2201
// Alt-Gr is synthesized as Alt + Ctrl.
2202
if ((GetKeyState(VK_RMENU) & 0x8000)
2203
&& (GetKeyState(VK_CONTROL) & 0x8000))
2204
{
2205
keyboard_state[VK_MENU] = 0x80;
2206
keyboard_state[VK_CONTROL] = 0x80;
2207
}
2208
2209
// Translate the virtual key according to the current keyboard
2210
// layout.
2211
scan_code = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
2212
// Convert the scan-code into a sequence of zero or more unicode
2213
// codepoints.
2214
// If this is a dead key ToUnicode returns a negative value.
2215
len = ToUnicode(vk, scan_code, keyboard_state, ch, ARRAY_LENGTH(ch),
2216
0);
2217
if (len < 0)
2218
dead_key = DEAD_KEY_SET_DEFAULT;
2219
2220
if (len <= 0)
2221
{
2222
int wm_char = NUL;
2223
2224
if (dead_key == DEAD_KEY_SET_DEFAULT
2225
&& (GetKeyState(VK_CONTROL) & 0x8000))
2226
{
2227
if ( // AZERTY CTRL+dead_circumflex
2228
(vk == 221 && scan_code == 26)
2229
// QWERTZ CTRL+dead_circumflex
2230
|| (vk == 220 && scan_code == 41))
2231
wm_char = '[';
2232
if ( // QWERTZ CTRL+dead_two-overdots
2233
(vk == 192 && scan_code == 27))
2234
wm_char = ']';
2235
}
2236
if (wm_char != NUL)
2237
{
2238
// post WM_CHAR='[' - which will be interpreted with CTRL
2239
// still hold as ESC
2240
PostMessageW(pmsg->hwnd, WM_CHAR, wm_char, pmsg->lParam);
2241
// ask _OnChar() to not touch this state, wait for next key
2242
// press and maintain knowledge that we are "poisoned" with
2243
// "dead state"
2244
dead_key = DEAD_KEY_TRANSIENT_IN_ON_CHAR;
2245
}
2246
return;
2247
}
2248
2249
// Post the message as TranslateMessage would do.
2250
if (pmsg->message == WM_KEYDOWN)
2251
{
2252
for (i = 0; i < len; i++)
2253
PostMessageW(pmsg->hwnd, WM_CHAR, ch[i], pmsg->lParam);
2254
}
2255
else
2256
{
2257
for (i = 0; i < len; i++)
2258
PostMessageW(pmsg->hwnd, WM_SYSCHAR, ch[i], pmsg->lParam);
2259
}
2260
}
2261
2262
/*
2263
* "Classic" implementation, existing prior to v8.2.4807
2264
*/
2265
static void
2266
process_message_usual_key_classic(UINT vk, const MSG *pmsg)
2267
{
2268
char_u string[40];
2269
2270
// Some keys need C-S- where they should only need C-.
2271
// Ignore 0xff, Windows XP sends it when NUMLOCK has changed since
2272
// system startup (Helmut Stiegler, 2003 Oct 3).
2273
if (vk != 0xff
2274
&& (GetKeyState(VK_CONTROL) & 0x8000)
2275
&& !(GetKeyState(VK_SHIFT) & 0x8000)
2276
&& !(GetKeyState(VK_MENU) & 0x8000))
2277
{
2278
// CTRL-6 is '^'; Japanese keyboard maps '^' to vk == 0xDE
2279
if (vk == '6' || MapVirtualKey(vk, 2) == (UINT)'^')
2280
{
2281
string[0] = Ctrl_HAT;
2282
add_to_input_buf(string, 1);
2283
}
2284
// vk == 0xBD AZERTY for CTRL-'-', but CTRL-[ for * QWERTY!
2285
else if (vk == 0xBD) // QWERTY for CTRL-'-'
2286
{
2287
string[0] = Ctrl__;
2288
add_to_input_buf(string, 1);
2289
}
2290
// CTRL-2 is '@'; Japanese keyboard maps '@' to vk == 0xC0
2291
else if (vk == '2' || MapVirtualKey(vk, 2) == (UINT)'@')
2292
{
2293
string[0] = Ctrl_AT;
2294
add_to_input_buf(string, 1);
2295
}
2296
else
2297
TranslateMessage(pmsg);
2298
}
2299
else
2300
TranslateMessage(pmsg);
2301
}
2302
2303
/*
2304
* Process a single Windows message.
2305
* If one is not available we hang until one is.
2306
*/
2307
static void
2308
process_message(void)
2309
{
2310
MSG msg;
2311
UINT vk = 0; // Virtual key
2312
char_u string[40];
2313
int i;
2314
int modifiers = 0;
2315
int key;
2316
#ifdef FEAT_MENU
2317
static char_u k10[] = {K_SPECIAL, 'k', ';', 0};
2318
#endif
2319
static int keycode_trans_strategy_initialized = 0;
2320
2321
// lazy initialize - first time only
2322
if (!keycode_trans_strategy_initialized)
2323
{
2324
keycode_trans_strategy_initialized = 1;
2325
keycode_trans_strategy_init();
2326
}
2327
2328
GetMessageW(&msg, NULL, 0, 0);
2329
2330
#ifdef FEAT_OLE
2331
// Look after OLE Automation commands
2332
if (msg.message == WM_OLE)
2333
{
2334
char_u *str = (char_u *)msg.lParam;
2335
if (str == NULL || *str == NUL)
2336
{
2337
// Message can't be ours, forward it. Fixes problem with Ultramon
2338
// 3.0.4
2339
DispatchMessageW(&msg);
2340
}
2341
else
2342
{
2343
add_to_input_buf(str, (int)STRLEN(str));
2344
vim_free(str); // was allocated in CVim::SendKeys()
2345
}
2346
return;
2347
}
2348
#endif
2349
2350
#ifdef MSWIN_FIND_REPLACE
2351
// Don't process messages used by the dialog
2352
if (s_findrep_hwnd != NULL && IsDialogMessageW(s_findrep_hwnd, &msg))
2353
{
2354
HandleMouseHide(msg.message, msg.lParam);
2355
return;
2356
}
2357
#endif
2358
2359
/*
2360
* Check if it's a special key that we recognise. If not, call
2361
* TranslateMessage().
2362
*/
2363
if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN)
2364
{
2365
vk = (int) msg.wParam;
2366
2367
/*
2368
* Handle dead keys in special conditions in other cases we let Windows
2369
* handle them and do not interfere.
2370
*
2371
* The dead_key flag must be reset on several occasions:
2372
* - in _OnChar() (or _OnSysChar()) as any dead key was necessarily
2373
* consumed at that point (This is when we let Windows combine the
2374
* dead character on its own)
2375
*
2376
* - Before doing something special such as regenerating keypresses to
2377
* expel the dead character as this could trigger an infinite loop if
2378
* for some reason TranslateMessage() do not trigger a call
2379
* immediately to _OnChar() (or _OnSysChar()).
2380
*/
2381
2382
/*
2383
* We are at the moment after WM_CHAR with DEAD_KEY_SKIP_ON_CHAR event
2384
* was handled by _WndProc, this keypress we want to process normally
2385
*/
2386
if (keycode_trans_strategy_used->is_experimental()
2387
&& dead_key == DEAD_KEY_SKIP_ON_CHAR)
2388
{
2389
dead_key = DEAD_KEY_OFF;
2390
}
2391
2392
if (dead_key != DEAD_KEY_OFF)
2393
{
2394
/*
2395
* Expel the dead key pressed with Ctrl in a special way.
2396
*
2397
* After dead key was pressed with Ctrl in some cases, ESC was
2398
* artificially injected and handled by _OnChar(), now we are
2399
* dealing with completely new key press from the user. If we don't
2400
* do anything, ToUnicode() call will interpret this vk+scan_code
2401
* under influence of "dead-modifier". To prevent this we translate
2402
* this message replacing current char from user with VK_SPACE,
2403
* which will cause WM_CHAR with dead_key's character itself. Using
2404
* DEAD_KEY_SKIP_ON_CHAR value of dead_char we force _OnChar() to
2405
* ignore this one WM_CHAR event completely. Afterwards (due to
2406
* usage of PostMessage), this procedure is scheduled to be called
2407
* again with user char and on next entry we will clean
2408
* DEAD_KEY_SKIP_ON_CHAR. We cannot use original
2409
* outputDeadKey_rePost() since we do not wish to reset dead_key
2410
* value.
2411
*/
2412
if (keycode_trans_strategy_used->is_experimental() &&
2413
dead_key == DEAD_KEY_TRANSIENT_IN_ON_CHAR)
2414
{
2415
outputDeadKey_rePost_Ex(msg,
2416
/*dead_key2set=*/DEAD_KEY_SKIP_ON_CHAR);
2417
return;
2418
}
2419
2420
if (dead_key != DEAD_KEY_SET_DEFAULT)
2421
{
2422
// should never happen - is there a way to make ASSERT here?
2423
return;
2424
}
2425
2426
/*
2427
* If a dead key was pressed and the user presses VK_SPACE,
2428
* VK_BACK, or VK_ESCAPE it means that he actually wants to deal
2429
* with the dead char now, so do nothing special and let Windows
2430
* handle it.
2431
*
2432
* Note that VK_SPACE combines with the dead_key's character and
2433
* only one WM_CHAR will be generated by TranslateMessage(), in
2434
* the two other cases two WM_CHAR will be generated: the dead
2435
* char and VK_BACK or VK_ESCAPE. That is most likely what the
2436
* user expects.
2437
*/
2438
if ((vk == VK_SPACE || vk == VK_BACK || vk == VK_ESCAPE))
2439
{
2440
dead_key = DEAD_KEY_OFF;
2441
TranslateMessage(&msg);
2442
return;
2443
}
2444
// In modes where we are not typing, dead keys should behave
2445
// normally
2446
else if ((get_real_state()
2447
& (MODE_INSERT | MODE_CMDLINE | MODE_SELECT)) == 0)
2448
{
2449
outputDeadKey_rePost(msg);
2450
return;
2451
}
2452
}
2453
2454
// Check for CTRL-BREAK
2455
if (vk == VK_CANCEL)
2456
{
2457
trash_input_buf();
2458
got_int = TRUE;
2459
ctrl_break_was_pressed = TRUE;
2460
string[0] = Ctrl_C;
2461
add_to_input_buf(string, 1);
2462
}
2463
2464
// This is an IME event or a synthetic keystroke, let Windows handle it.
2465
if (vk == VK_PROCESSKEY || vk == VK_PACKET)
2466
{
2467
TranslateMessage(&msg);
2468
return;
2469
}
2470
2471
for (i = 0; special_keys[i].key_sym != 0; i++)
2472
{
2473
// ignore VK_SPACE when ALT key pressed: system menu
2474
if (special_keys[i].key_sym == vk
2475
&& (vk != VK_SPACE || !(GetKeyState(VK_MENU) & 0x8000)))
2476
{
2477
/*
2478
* Behave as expected if we have a dead key and the special key
2479
* is a key that would normally trigger the dead key nominal
2480
* character output (such as a NUMPAD printable character or
2481
* the TAB key, etc...).
2482
*/
2483
if (dead_key == DEAD_KEY_SET_DEFAULT
2484
&& (special_keys[i].vim_code0 == 'K'
2485
|| vk == VK_TAB || vk == CAR))
2486
{
2487
outputDeadKey_rePost(msg);
2488
return;
2489
}
2490
2491
#ifdef FEAT_MENU
2492
// Check for <F10>: Windows selects the menu. When <F10> is
2493
// mapped we want to use the mapping instead.
2494
if (vk == VK_F10
2495
&& gui.menu_is_active
2496
&& check_map(k10, State, FALSE, TRUE, FALSE,
2497
NULL, NULL) == NULL)
2498
break;
2499
#endif
2500
modifiers = get_active_modifiers_via_ptr();
2501
2502
if (special_keys[i].vim_code1 == NUL)
2503
key = special_keys[i].vim_code0;
2504
else
2505
key = TO_SPECIAL(special_keys[i].vim_code0,
2506
special_keys[i].vim_code1);
2507
key = simplify_key(key, &modifiers);
2508
if (key == CSI)
2509
key = K_CSI;
2510
2511
if (modifiers)
2512
{
2513
string[0] = CSI;
2514
string[1] = KS_MODIFIER;
2515
string[2] = modifiers;
2516
add_to_input_buf(string, 3);
2517
}
2518
2519
if (IS_SPECIAL(key))
2520
{
2521
string[0] = CSI;
2522
string[1] = K_SECOND(key);
2523
string[2] = K_THIRD(key);
2524
add_to_input_buf(string, 3);
2525
}
2526
else
2527
{
2528
int len;
2529
2530
// Handle "key" as a Unicode character.
2531
len = char_to_string(key, string, 40, FALSE);
2532
add_to_input_buf(string, len);
2533
}
2534
break;
2535
}
2536
}
2537
2538
// Not a special key.
2539
if (special_keys[i].key_sym == 0)
2540
{
2541
process_message_usual_key(vk, &msg);
2542
}
2543
}
2544
#ifdef FEAT_MBYTE_IME
2545
else if (msg.message == WM_IME_NOTIFY)
2546
_OnImeNotify(msg.hwnd, (DWORD)msg.wParam, (DWORD)msg.lParam);
2547
else if (msg.message == WM_KEYUP && im_get_status())
2548
// added for non-MS IME (Yasuhiro Matsumoto)
2549
TranslateMessage(&msg);
2550
#endif
2551
2552
#ifdef FEAT_MENU
2553
// Check for <F10>: Default effect is to select the menu. When <F10> is
2554
// mapped we need to stop it here to avoid strange effects (e.g., for the
2555
// key-up event)
2556
if (vk != VK_F10 || check_map(k10, State, FALSE, TRUE, FALSE,
2557
NULL, NULL) == NULL)
2558
#endif
2559
DispatchMessageW(&msg);
2560
}
2561
2562
/*
2563
* Catch up with any queued events. This may put keyboard input into the
2564
* input buffer, call resize call-backs, trigger timers etc. If there is
2565
* nothing in the event queue (& no timers pending), then we return
2566
* immediately.
2567
*/
2568
void
2569
gui_mch_update(void)
2570
{
2571
MSG msg;
2572
2573
if (!s_busy_processing)
2574
while (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE)
2575
&& !vim_is_input_buf_full())
2576
process_message();
2577
}
2578
2579
static void
2580
remove_any_timer(void)
2581
{
2582
MSG msg;
2583
2584
if (s_wait_timer != 0 && !s_timed_out)
2585
{
2586
KillTimer(NULL, s_wait_timer);
2587
2588
// Eat spurious WM_TIMER messages
2589
while (PeekMessageW(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
2590
;
2591
s_wait_timer = 0;
2592
}
2593
}
2594
2595
/*
2596
* GUI input routine called by gui_wait_for_chars(). Waits for a character
2597
* from the keyboard.
2598
* wtime == -1 Wait forever.
2599
* wtime == 0 This should never happen.
2600
* wtime > 0 Wait wtime milliseconds for a character.
2601
* Returns OK if a character was found to be available within the given time,
2602
* or FAIL otherwise.
2603
*/
2604
int
2605
gui_mch_wait_for_chars(int wtime)
2606
{
2607
int focus;
2608
2609
s_timed_out = FALSE;
2610
2611
if (wtime >= 0)
2612
{
2613
// Don't do anything while processing a (scroll) message.
2614
if (s_busy_processing)
2615
return FAIL;
2616
2617
// When called with "wtime" zero, just want one msec.
2618
s_wait_timer = SetTimer(NULL, 0, (UINT)(wtime == 0 ? 1 : wtime),
2619
_OnTimer);
2620
}
2621
2622
allow_scrollbar = TRUE;
2623
2624
focus = gui.in_focus;
2625
while (!s_timed_out)
2626
{
2627
// Stop or start blinking when focus changes
2628
if (gui.in_focus != focus)
2629
{
2630
if (gui.in_focus)
2631
gui_mch_start_blink();
2632
else
2633
gui_mch_stop_blink(TRUE);
2634
focus = gui.in_focus;
2635
}
2636
2637
if (s_need_activate)
2638
{
2639
(void)SetForegroundWindow(s_hwnd);
2640
s_need_activate = FALSE;
2641
}
2642
2643
#ifdef FEAT_TIMERS
2644
did_add_timer = FALSE;
2645
#endif
2646
#ifdef MESSAGE_QUEUE
2647
// Check channel I/O while waiting for a message.
2648
for (;;)
2649
{
2650
MSG msg;
2651
2652
parse_queued_messages();
2653
# ifdef FEAT_TIMERS
2654
if (did_add_timer)
2655
break;
2656
# endif
2657
if (PeekMessageW(&msg, NULL, 0, 0, PM_NOREMOVE))
2658
{
2659
process_message();
2660
break;
2661
}
2662
else if (input_available()
2663
// TODO: The 10 msec is a compromise between laggy response
2664
// and consuming more CPU time. Better would be to handle
2665
// channel messages when they arrive.
2666
|| MsgWaitForMultipleObjects(0, NULL, FALSE, 10,
2667
QS_ALLINPUT) != WAIT_TIMEOUT)
2668
break;
2669
}
2670
#else
2671
// Don't use gui_mch_update() because then we will spin-lock until a
2672
// char arrives, instead we use GetMessage() to hang until an
2673
// event arrives. No need to check for input_buf_full because we are
2674
// returning as soon as it contains a single char -- webb
2675
process_message();
2676
#endif
2677
2678
if (input_available())
2679
{
2680
remove_any_timer();
2681
allow_scrollbar = FALSE;
2682
2683
// Clear pending mouse button, the release event may have been
2684
// taken by the dialog window. But don't do this when getting
2685
// focus, we need the mouse-up event then.
2686
if (!s_getting_focus)
2687
s_button_pending = -1;
2688
2689
return OK;
2690
}
2691
2692
#ifdef FEAT_TIMERS
2693
if (did_add_timer)
2694
{
2695
// Need to recompute the waiting time.
2696
remove_any_timer();
2697
break;
2698
}
2699
#endif
2700
}
2701
allow_scrollbar = FALSE;
2702
return FAIL;
2703
}
2704
2705
/*
2706
* Clear a rectangular region of the screen from text pos (row1, col1) to
2707
* (row2, col2) inclusive.
2708
*/
2709
void
2710
gui_mch_clear_block(
2711
int row1,
2712
int col1,
2713
int row2,
2714
int col2)
2715
{
2716
RECT rc;
2717
2718
/*
2719
* Clear one extra pixel at the far right, for when bold characters have
2720
* spilled over to the window border.
2721
* Note: FillRect() excludes right and bottom of rectangle.
2722
*/
2723
rc.left = FILL_X(col1);
2724
rc.top = FILL_Y(row1);
2725
rc.right = FILL_X(col2 + 1) + (col2 == Columns - 1);
2726
rc.bottom = FILL_Y(row2 + 1);
2727
clear_rect(&rc);
2728
}
2729
2730
/*
2731
* Clear the whole text window.
2732
*/
2733
void
2734
gui_mch_clear_all(void)
2735
{
2736
RECT rc;
2737
2738
rc.left = 0;
2739
rc.top = 0;
2740
rc.right = Columns * gui.char_width + 2 * gui.border_width;
2741
rc.bottom = Rows * gui.char_height + 2 * gui.border_width;
2742
clear_rect(&rc);
2743
}
2744
/*
2745
* Menu stuff.
2746
*/
2747
2748
void
2749
gui_mch_enable_menu(int flag)
2750
{
2751
#ifdef FEAT_MENU
2752
SetMenu(s_hwnd, flag ? s_menuBar : NULL);
2753
#endif
2754
}
2755
2756
void
2757
gui_mch_set_menu_pos(
2758
int x UNUSED,
2759
int y UNUSED,
2760
int w UNUSED,
2761
int h UNUSED)
2762
{
2763
// It will be in the right place anyway
2764
}
2765
2766
#if defined(FEAT_MENU)
2767
/*
2768
* Make menu item hidden or not hidden
2769
*/
2770
void
2771
gui_mch_menu_hidden(
2772
vimmenu_T *menu,
2773
int hidden)
2774
{
2775
/*
2776
* This doesn't do what we want. Hmm, just grey the menu items for now.
2777
*/
2778
/*
2779
if (hidden)
2780
EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_DISABLED);
2781
else
2782
EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED);
2783
*/
2784
gui_mch_menu_grey(menu, hidden);
2785
}
2786
2787
/*
2788
* This is called after setting all the menus to grey/hidden or not.
2789
*/
2790
void
2791
gui_mch_draw_menubar(void)
2792
{
2793
DrawMenuBar(s_hwnd);
2794
}
2795
#endif // FEAT_MENU
2796
2797
/*
2798
* Return the RGB value of a pixel as a long.
2799
*/
2800
guicolor_T
2801
gui_mch_get_rgb(guicolor_T pixel)
2802
{
2803
return (guicolor_T)((GetRValue(pixel) << 16) + (GetGValue(pixel) << 8)
2804
+ GetBValue(pixel));
2805
}
2806
2807
#if defined(FEAT_GUI_DIALOG)
2808
/*
2809
* Convert pixels in X to dialog units
2810
*/
2811
static WORD
2812
PixelToDialogX(int numPixels)
2813
{
2814
return (WORD)((numPixels * 4) / s_dlgfntwidth);
2815
}
2816
2817
/*
2818
* Convert pixels in Y to dialog units
2819
*/
2820
static WORD
2821
PixelToDialogY(int numPixels)
2822
{
2823
return (WORD)((numPixels * 8) / s_dlgfntheight);
2824
}
2825
2826
/*
2827
* Return the width in pixels of the given text in the given DC.
2828
*/
2829
static int
2830
GetTextWidth(HDC hdc, char_u *str, int len)
2831
{
2832
SIZE size;
2833
2834
GetTextExtentPoint(hdc, (LPCSTR)str, len, &size);
2835
return size.cx;
2836
}
2837
2838
/*
2839
* Return the width in pixels of the given text in the given DC, taking care
2840
* of 'encoding' to active codepage conversion.
2841
*/
2842
static int
2843
GetTextWidthEnc(HDC hdc, char_u *str, int len)
2844
{
2845
SIZE size;
2846
WCHAR *wstr;
2847
int n;
2848
int wlen = len;
2849
2850
wstr = enc_to_utf16(str, &wlen);
2851
if (wstr == NULL)
2852
return 0;
2853
2854
n = GetTextExtentPointW(hdc, wstr, wlen, &size);
2855
vim_free(wstr);
2856
if (n)
2857
return size.cx;
2858
return 0;
2859
}
2860
2861
static void get_work_area(RECT *spi_rect);
2862
2863
/*
2864
* A quick little routine that will center one window over another, handy for
2865
* dialog boxes. Taken from the Win32SDK samples and modified for multiple
2866
* monitors.
2867
*/
2868
static BOOL
2869
CenterWindow(
2870
HWND hwndChild,
2871
HWND hwndParent)
2872
{
2873
HMONITOR mon;
2874
MONITORINFO moninfo;
2875
RECT rChild, rParent, rScreen;
2876
int wChild, hChild, wParent, hParent;
2877
int xNew, yNew;
2878
HDC hdc;
2879
2880
GetWindowRect(hwndChild, &rChild);
2881
wChild = rChild.right - rChild.left;
2882
hChild = rChild.bottom - rChild.top;
2883
2884
// If Vim is minimized put the window in the middle of the screen.
2885
if (hwndParent == NULL || IsMinimized(hwndParent))
2886
get_work_area(&rParent);
2887
else
2888
GetWindowRect(hwndParent, &rParent);
2889
wParent = rParent.right - rParent.left;
2890
hParent = rParent.bottom - rParent.top;
2891
2892
moninfo.cbSize = sizeof(MONITORINFO);
2893
mon = MonitorFromWindow(hwndChild, MONITOR_DEFAULTTOPRIMARY);
2894
if (mon != NULL && GetMonitorInfo(mon, &moninfo))
2895
{
2896
rScreen = moninfo.rcWork;
2897
}
2898
else
2899
{
2900
hdc = GetDC(hwndChild);
2901
rScreen.left = 0;
2902
rScreen.top = 0;
2903
rScreen.right = GetDeviceCaps(hdc, HORZRES);
2904
rScreen.bottom = GetDeviceCaps(hdc, VERTRES);
2905
ReleaseDC(hwndChild, hdc);
2906
}
2907
2908
xNew = rParent.left + ((wParent - wChild) / 2);
2909
if (xNew < rScreen.left)
2910
xNew = rScreen.left;
2911
else if ((xNew + wChild) > rScreen.right)
2912
xNew = rScreen.right - wChild;
2913
2914
yNew = rParent.top + ((hParent - hChild) / 2);
2915
if (yNew < rScreen.top)
2916
yNew = rScreen.top;
2917
else if ((yNew + hChild) > rScreen.bottom)
2918
yNew = rScreen.bottom - hChild;
2919
2920
return SetWindowPos(hwndChild, NULL, xNew, yNew, 0, 0,
2921
SWP_NOSIZE | SWP_NOZORDER);
2922
}
2923
#endif // FEAT_GUI_DIALOG
2924
2925
#if defined(FEAT_TOOLBAR)
2926
void
2927
gui_mch_show_toolbar(int showit)
2928
{
2929
if (s_toolbarhwnd == NULL)
2930
return;
2931
2932
if (showit)
2933
{
2934
// Enable unicode support
2935
SendMessage(s_toolbarhwnd, TB_SETUNICODEFORMAT, (WPARAM)TRUE,
2936
(LPARAM)0);
2937
ShowWindow(s_toolbarhwnd, SW_SHOW);
2938
}
2939
else
2940
ShowWindow(s_toolbarhwnd, SW_HIDE);
2941
}
2942
2943
// The number of bitmaps is fixed. Exit is missing!
2944
# define TOOLBAR_BITMAP_COUNT 31
2945
2946
#endif
2947
2948
#if defined(FEAT_GUI_TABLINE)
2949
static void
2950
add_tabline_popup_menu_entry(HMENU pmenu, UINT item_id, char_u *item_text)
2951
{
2952
WCHAR *wn;
2953
MENUITEMINFOW infow;
2954
2955
wn = enc_to_utf16(item_text, NULL);
2956
if (wn == NULL)
2957
return;
2958
2959
infow.cbSize = sizeof(infow);
2960
infow.fMask = MIIM_TYPE | MIIM_ID;
2961
infow.wID = item_id;
2962
infow.fType = MFT_STRING;
2963
infow.dwTypeData = wn;
2964
infow.cch = (UINT)wcslen(wn);
2965
InsertMenuItemW(pmenu, item_id, FALSE, &infow);
2966
vim_free(wn);
2967
}
2968
2969
static void
2970
show_tabline_popup_menu(void)
2971
{
2972
HMENU tab_pmenu;
2973
long rval;
2974
POINT pt;
2975
2976
// When ignoring events don't show the menu.
2977
if (hold_gui_events || cmdwin_type != 0)
2978
return;
2979
2980
tab_pmenu = CreatePopupMenu();
2981
if (tab_pmenu == NULL)
2982
return;
2983
2984
if (first_tabpage->tp_next != NULL)
2985
add_tabline_popup_menu_entry(tab_pmenu,
2986
TABLINE_MENU_CLOSE, (char_u *)_("Close tab"));
2987
add_tabline_popup_menu_entry(tab_pmenu,
2988
TABLINE_MENU_NEW, (char_u *)_("New tab"));
2989
add_tabline_popup_menu_entry(tab_pmenu,
2990
TABLINE_MENU_OPEN, (char_u *)_("Open tab..."));
2991
2992
GetCursorPos(&pt);
2993
rval = TrackPopupMenuEx(tab_pmenu, TPM_RETURNCMD, pt.x, pt.y, s_tabhwnd,
2994
NULL);
2995
2996
DestroyMenu(tab_pmenu);
2997
2998
// Add the string cmd into input buffer
2999
if (rval > 0)
3000
{
3001
TCHITTESTINFO htinfo;
3002
int idx;
3003
3004
if (ScreenToClient(s_tabhwnd, &pt) == 0)
3005
return;
3006
3007
htinfo.pt.x = pt.x;
3008
htinfo.pt.y = pt.y;
3009
idx = TabCtrl_HitTest(s_tabhwnd, &htinfo);
3010
if (idx == -1)
3011
idx = 0;
3012
else
3013
idx += 1;
3014
3015
send_tabline_menu_event(idx, (int)rval);
3016
}
3017
}
3018
3019
/*
3020
* Show or hide the tabline.
3021
*/
3022
void
3023
gui_mch_show_tabline(int showit)
3024
{
3025
if (s_tabhwnd == NULL)
3026
return;
3027
3028
if (!showit != !showing_tabline)
3029
{
3030
if (showit)
3031
ShowWindow(s_tabhwnd, SW_SHOW);
3032
else
3033
ShowWindow(s_tabhwnd, SW_HIDE);
3034
showing_tabline = showit;
3035
}
3036
}
3037
3038
/*
3039
* Return TRUE when tabline is displayed.
3040
*/
3041
int
3042
gui_mch_showing_tabline(void)
3043
{
3044
return s_tabhwnd != NULL && showing_tabline;
3045
}
3046
3047
/*
3048
* Update the labels of the tabline.
3049
*/
3050
void
3051
gui_mch_update_tabline(void)
3052
{
3053
tabpage_T *tp;
3054
TCITEM tie;
3055
int nr = 0;
3056
int curtabidx = 0;
3057
int tabadded = 0;
3058
WCHAR *wstr = NULL;
3059
3060
if (s_tabhwnd == NULL)
3061
return;
3062
3063
// Enable unicode support
3064
SendMessage(s_tabhwnd, CCM_SETUNICODEFORMAT, (WPARAM)TRUE, (LPARAM)0);
3065
3066
tie.mask = TCIF_TEXT;
3067
tie.iImage = -1;
3068
3069
// Disable redraw for tab updates to eliminate O(N^2) draws.
3070
SendMessage(s_tabhwnd, WM_SETREDRAW, (WPARAM)FALSE, 0);
3071
3072
// Add a label for each tab page. They all contain the same text area.
3073
for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr)
3074
{
3075
if (tp == curtab)
3076
curtabidx = nr;
3077
3078
if (nr >= TabCtrl_GetItemCount(s_tabhwnd))
3079
{
3080
// Add the tab
3081
tie.pszText = "-Empty-";
3082
TabCtrl_InsertItem(s_tabhwnd, nr, &tie);
3083
tabadded = 1;
3084
}
3085
3086
get_tabline_label(tp, FALSE);
3087
tie.pszText = (LPSTR)NameBuff;
3088
3089
wstr = enc_to_utf16(NameBuff, NULL);
3090
if (wstr != NULL)
3091
{
3092
TCITEMW tiw;
3093
3094
tiw.mask = TCIF_TEXT;
3095
tiw.iImage = -1;
3096
tiw.pszText = wstr;
3097
SendMessage(s_tabhwnd, TCM_SETITEMW, (WPARAM)nr, (LPARAM)&tiw);
3098
vim_free(wstr);
3099
}
3100
}
3101
3102
// Remove any old labels.
3103
while (nr < TabCtrl_GetItemCount(s_tabhwnd))
3104
TabCtrl_DeleteItem(s_tabhwnd, nr);
3105
3106
if (!tabadded && TabCtrl_GetCurSel(s_tabhwnd) != curtabidx)
3107
TabCtrl_SetCurSel(s_tabhwnd, curtabidx);
3108
3109
// Re-enable redraw and redraw.
3110
SendMessage(s_tabhwnd, WM_SETREDRAW, (WPARAM)TRUE, 0);
3111
RedrawWindow(s_tabhwnd, NULL, NULL,
3112
RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
3113
3114
if (tabadded && TabCtrl_GetCurSel(s_tabhwnd) != curtabidx)
3115
TabCtrl_SetCurSel(s_tabhwnd, curtabidx);
3116
}
3117
3118
/*
3119
* Set the current tab to "nr". First tab is 1.
3120
*/
3121
void
3122
gui_mch_set_curtab(int nr)
3123
{
3124
if (s_tabhwnd == NULL)
3125
return;
3126
3127
if (TabCtrl_GetCurSel(s_tabhwnd) != nr - 1)
3128
TabCtrl_SetCurSel(s_tabhwnd, nr - 1);
3129
}
3130
3131
#endif
3132
3133
#ifdef FEAT_GUI_DARKTHEME
3134
extern BOOL win10_22H2_or_later; // this is in os_win32.c
3135
3136
void
3137
gui_mch_set_dark_theme(int dark)
3138
{
3139
if (!win10_22H2_or_later)
3140
return;
3141
3142
if (pDwmSetWindowAttribute != NULL)
3143
pDwmSetWindowAttribute(s_hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark,
3144
sizeof(dark));
3145
3146
if (pSetPreferredAppMode != NULL)
3147
pSetPreferredAppMode(dark);
3148
3149
if (pFlushMenuThemes != NULL)
3150
pFlushMenuThemes();
3151
}
3152
3153
static void
3154
dyn_uxtheme_load(void)
3155
{
3156
hUxThemeLib = vimLoadLib("uxtheme.dll");
3157
if (hUxThemeLib == NULL)
3158
return;
3159
3160
pSetPreferredAppMode = (DWORD (WINAPI *)(DWORD))
3161
GetProcAddress(hUxThemeLib, MAKEINTRESOURCE(135));
3162
pFlushMenuThemes = (void (WINAPI *)(void))
3163
GetProcAddress(hUxThemeLib, MAKEINTRESOURCE(136));
3164
3165
if (pSetPreferredAppMode == NULL || pFlushMenuThemes == NULL)
3166
{
3167
FreeLibrary(hUxThemeLib);
3168
hUxThemeLib = NULL;
3169
return;
3170
}
3171
}
3172
3173
#endif // FEAT_GUI_DARKTHEME
3174
3175
/*
3176
* When flag is true, set fullscreen on.
3177
* When flag is false, set fullscreen off.
3178
*/
3179
void
3180
gui_mch_set_fullscreen(int flag)
3181
{
3182
static RECT normal_rect;
3183
static LONG_PTR normal_style, normal_exstyle;
3184
HMONITOR mon;
3185
MONITORINFO moninfo;
3186
RECT rc;
3187
3188
if (!full_screen) // Windows not set yet.
3189
return;
3190
3191
if (flag)
3192
{
3193
if (fullscreen_on)
3194
return;
3195
3196
// Enter fullscreen mode
3197
GetWindowRect(s_hwnd, &rc);
3198
3199
moninfo.cbSize = sizeof(MONITORINFO);
3200
mon = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
3201
if (mon == NULL || !GetMonitorInfo(mon, &moninfo))
3202
return;
3203
3204
// Save current window state
3205
GetWindowRect(s_hwnd, &normal_rect);
3206
normal_style = GetWindowLongPtr(s_hwnd, GWL_STYLE);
3207
normal_exstyle = GetWindowLongPtr(s_hwnd, GWL_EXSTYLE);
3208
3209
// Set fullscreen styles
3210
SetWindowLongPtr(s_hwnd, GWL_STYLE,
3211
normal_style & ~(WS_CAPTION | WS_THICKFRAME));
3212
SetWindowLongPtr(s_hwnd, GWL_EXSTYLE,
3213
normal_exstyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE |
3214
WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
3215
SetWindowPos(s_hwnd, NULL,
3216
moninfo.rcMonitor.left,
3217
moninfo.rcMonitor.top,
3218
moninfo.rcMonitor.right - moninfo.rcMonitor.left,
3219
moninfo.rcMonitor.bottom - moninfo.rcMonitor.top,
3220
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
3221
3222
fullscreen_on = TRUE;
3223
}
3224
else
3225
{
3226
if (!fullscreen_on)
3227
return;
3228
3229
// Exit fullscreen mode
3230
SetWindowLongPtr(s_hwnd, GWL_STYLE, normal_style);
3231
SetWindowLongPtr(s_hwnd, GWL_EXSTYLE, normal_exstyle);
3232
3233
// Restore original window position and size
3234
SetWindowPos(s_hwnd, NULL,
3235
normal_rect.left,
3236
normal_rect.top,
3237
normal_rect.right - normal_rect.left,
3238
normal_rect.bottom - normal_rect.top,
3239
SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
3240
3241
fullscreen_on = FALSE;
3242
}
3243
}
3244
3245
/*
3246
* ":simalt" command.
3247
*/
3248
void
3249
ex_simalt(exarg_T *eap)
3250
{
3251
char_u *keys = eap->arg;
3252
int fill_typebuf = FALSE;
3253
char_u key_name[4];
3254
3255
PostMessage(s_hwnd, WM_SYSCOMMAND, (WPARAM)SC_KEYMENU, (LPARAM)0);
3256
while (*keys)
3257
{
3258
if (*keys == '~')
3259
*keys = ' '; // for showing system menu
3260
PostMessage(s_hwnd, WM_CHAR, (WPARAM)*keys, (LPARAM)0);
3261
keys++;
3262
fill_typebuf = TRUE;
3263
}
3264
if (fill_typebuf)
3265
{
3266
// Put a NOP in the typeahead buffer so that the message will get
3267
// processed.
3268
key_name[0] = K_SPECIAL;
3269
key_name[1] = KS_EXTRA;
3270
key_name[2] = KE_NOP;
3271
key_name[3] = NUL;
3272
#if defined(FEAT_CLIENTSERVER) || defined(FEAT_EVAL)
3273
typebuf_was_filled = TRUE;
3274
#endif
3275
(void)ins_typebuf(key_name, REMAP_NONE, 0, TRUE, FALSE);
3276
}
3277
}
3278
3279
/*
3280
* Create the find & replace dialogs.
3281
* You can't have both at once: ":find" when replace is showing, destroys
3282
* the replace dialog first, and the other way around.
3283
*/
3284
#ifdef MSWIN_FIND_REPLACE
3285
static void
3286
initialise_findrep(char_u *initial_string)
3287
{
3288
int wword = FALSE;
3289
int mcase = !p_ic;
3290
char_u *entry_text;
3291
3292
// Get the search string to use.
3293
entry_text = get_find_dialog_text(initial_string, &wword, &mcase);
3294
3295
s_findrep_struct.hwndOwner = s_hwnd;
3296
s_findrep_struct.Flags = FR_DOWN;
3297
if (mcase)
3298
s_findrep_struct.Flags |= FR_MATCHCASE;
3299
if (wword)
3300
s_findrep_struct.Flags |= FR_WHOLEWORD;
3301
if (entry_text != NULL && *entry_text != NUL)
3302
{
3303
WCHAR *p = enc_to_utf16(entry_text, NULL);
3304
if (p != NULL)
3305
{
3306
int len = s_findrep_struct.wFindWhatLen - 1;
3307
3308
wcsncpy(s_findrep_struct.lpstrFindWhat, p, len);
3309
s_findrep_struct.lpstrFindWhat[len] = NUL;
3310
vim_free(p);
3311
}
3312
}
3313
vim_free(entry_text);
3314
}
3315
#endif
3316
3317
static void
3318
set_window_title(HWND hwnd, char *title)
3319
{
3320
if (title != NULL)
3321
{
3322
WCHAR *wbuf;
3323
3324
// Convert the title from 'encoding' to UTF-16.
3325
wbuf = (WCHAR *)enc_to_utf16((char_u *)title, NULL);
3326
if (wbuf != NULL)
3327
{
3328
SetWindowTextW(hwnd, wbuf);
3329
vim_free(wbuf);
3330
}
3331
}
3332
else
3333
(void)SetWindowTextW(hwnd, NULL);
3334
}
3335
3336
void
3337
gui_mch_find_dialog(exarg_T *eap)
3338
{
3339
#ifdef MSWIN_FIND_REPLACE
3340
if (s_findrep_msg != 0)
3341
{
3342
if (IsWindow(s_findrep_hwnd) && !s_findrep_is_find)
3343
DestroyWindow(s_findrep_hwnd);
3344
3345
if (!IsWindow(s_findrep_hwnd))
3346
{
3347
initialise_findrep(eap->arg);
3348
s_findrep_hwnd = FindTextW(&s_findrep_struct);
3349
}
3350
3351
set_window_title(s_findrep_hwnd, _("Find string"));
3352
(void)SetFocus(s_findrep_hwnd);
3353
3354
s_findrep_is_find = TRUE;
3355
}
3356
#endif
3357
}
3358
3359
3360
void
3361
gui_mch_replace_dialog(exarg_T *eap)
3362
{
3363
#ifdef MSWIN_FIND_REPLACE
3364
if (s_findrep_msg != 0)
3365
{
3366
if (IsWindow(s_findrep_hwnd) && s_findrep_is_find)
3367
DestroyWindow(s_findrep_hwnd);
3368
3369
if (!IsWindow(s_findrep_hwnd))
3370
{
3371
initialise_findrep(eap->arg);
3372
s_findrep_hwnd = ReplaceTextW(&s_findrep_struct);
3373
}
3374
3375
set_window_title(s_findrep_hwnd, _("Find & Replace"));
3376
(void)SetFocus(s_findrep_hwnd);
3377
3378
s_findrep_is_find = FALSE;
3379
}
3380
#endif
3381
}
3382
3383
3384
/*
3385
* Set visibility of the pointer.
3386
*/
3387
void
3388
gui_mch_mousehide(int hide)
3389
{
3390
if (hide == gui.pointer_hidden)
3391
return;
3392
3393
ShowCursor(!hide);
3394
gui.pointer_hidden = hide;
3395
}
3396
3397
#ifdef FEAT_MENU
3398
static void
3399
gui_mch_show_popupmenu_at(vimmenu_T *menu, int x, int y)
3400
{
3401
// Unhide the mouse, we don't get move events here.
3402
gui_mch_mousehide(FALSE);
3403
3404
(void)TrackPopupMenu(
3405
(HMENU)menu->submenu_id,
3406
TPM_LEFTALIGN | TPM_LEFTBUTTON,
3407
x, y,
3408
(int)0, //reserved param
3409
s_hwnd,
3410
NULL);
3411
/*
3412
* NOTE: The pop-up menu can eat the mouse up event.
3413
* We deal with this in normal.c.
3414
*/
3415
}
3416
#endif
3417
3418
/*
3419
* Got a message when the system will go down.
3420
*/
3421
static void
3422
_OnEndSession(void)
3423
{
3424
getout_preserve_modified(1);
3425
}
3426
3427
/*
3428
* Get this message when the user clicks on the cross in the top right corner
3429
* of a Windows95 window.
3430
*/
3431
static void
3432
_OnClose(HWND hwnd UNUSED)
3433
{
3434
gui_shell_closed();
3435
}
3436
3437
/*
3438
* Get a message when the window is being destroyed.
3439
*/
3440
static void
3441
_OnDestroy(HWND hwnd)
3442
{
3443
if (!destroying)
3444
_OnClose(hwnd);
3445
}
3446
3447
static void
3448
_OnPaint(
3449
HWND hwnd)
3450
{
3451
if (IsMinimized(hwnd))
3452
return;
3453
3454
PAINTSTRUCT ps;
3455
3456
out_flush(); // make sure all output has been processed
3457
(void)BeginPaint(hwnd, &ps);
3458
3459
// prevent multi-byte characters from misprinting on an invalid
3460
// rectangle
3461
if (has_mbyte)
3462
{
3463
RECT rect;
3464
3465
GetClientRect(hwnd, &rect);
3466
ps.rcPaint.left = rect.left;
3467
ps.rcPaint.right = rect.right;
3468
}
3469
3470
if (!IsRectEmpty(&ps.rcPaint))
3471
{
3472
gui_redraw(ps.rcPaint.left, ps.rcPaint.top,
3473
ps.rcPaint.right - ps.rcPaint.left + 1,
3474
ps.rcPaint.bottom - ps.rcPaint.top + 1);
3475
}
3476
3477
EndPaint(hwnd, &ps);
3478
}
3479
3480
static void
3481
_OnSize(
3482
HWND hwnd,
3483
UINT state UNUSED,
3484
int cx,
3485
int cy)
3486
{
3487
if (!IsMinimized(hwnd) && !s_in_dpichanged)
3488
{
3489
gui_resize_shell(cx, cy);
3490
3491
// Menu bar may wrap differently now
3492
gui_mswin_get_menu_height(TRUE);
3493
}
3494
}
3495
3496
static void
3497
_OnSetFocus(
3498
HWND hwnd,
3499
HWND hwndOldFocus)
3500
{
3501
gui_focus_change(TRUE);
3502
s_getting_focus = TRUE;
3503
(void)DefWindowProcW(hwnd, WM_SETFOCUS, (WPARAM)hwndOldFocus, 0);
3504
}
3505
3506
static void
3507
_OnKillFocus(
3508
HWND hwnd,
3509
HWND hwndNewFocus)
3510
{
3511
if (destroying)
3512
return;
3513
gui_focus_change(FALSE);
3514
s_getting_focus = FALSE;
3515
(void)DefWindowProcW(hwnd, WM_KILLFOCUS, (WPARAM)hwndNewFocus, 0);
3516
}
3517
3518
/*
3519
* Get a message when the user switches back to vim
3520
*/
3521
static LRESULT
3522
_OnActivateApp(
3523
HWND hwnd,
3524
BOOL fActivate,
3525
DWORD dwThreadId)
3526
{
3527
// we call gui_focus_change() in _OnSetFocus()
3528
// gui_focus_change((int)fActivate);
3529
return DefWindowProcW(hwnd, WM_ACTIVATEAPP, fActivate, (DWORD)dwThreadId);
3530
}
3531
3532
void
3533
gui_mch_destroy_scrollbar(scrollbar_T *sb)
3534
{
3535
DestroyWindow(sb->id);
3536
}
3537
3538
/*
3539
* Get current mouse coordinates in text window.
3540
*/
3541
void
3542
gui_mch_getmouse(int *x, int *y)
3543
{
3544
RECT rct;
3545
POINT mp;
3546
3547
(void)GetWindowRect(s_textArea, &rct);
3548
(void)GetCursorPos(&mp);
3549
*x = (int)(mp.x - rct.left);
3550
*y = (int)(mp.y - rct.top);
3551
}
3552
3553
/*
3554
* Move mouse pointer to character at (x, y).
3555
*/
3556
void
3557
gui_mch_setmouse(int x, int y)
3558
{
3559
RECT rct;
3560
3561
(void)GetWindowRect(s_textArea, &rct);
3562
(void)SetCursorPos(x + gui.border_offset + rct.left,
3563
y + gui.border_offset + rct.top);
3564
}
3565
3566
static void
3567
gui_mswin_get_valid_dimensions(
3568
int w,
3569
int h,
3570
int *valid_w,
3571
int *valid_h,
3572
int *cols,
3573
int *rows)
3574
{
3575
int base_width, base_height;
3576
3577
base_width = gui_get_base_width()
3578
+ (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) +
3579
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2;
3580
base_height = gui_get_base_height()
3581
+ (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) +
3582
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2
3583
+ pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi)
3584
+ gui_mswin_get_menu_height(FALSE);
3585
*cols = (w - base_width) / gui.char_width;
3586
*rows = (h - base_height) / gui.char_height;
3587
*valid_w = base_width + *cols * gui.char_width;
3588
*valid_h = base_height + *rows * gui.char_height;
3589
}
3590
3591
void
3592
gui_mch_flash(int msec)
3593
{
3594
RECT rc;
3595
3596
#if defined(FEAT_DIRECTX)
3597
if (IS_ENABLE_DIRECTX())
3598
DWriteContext_Flush(s_dwc);
3599
#endif
3600
3601
/*
3602
* Note: InvertRect() excludes right and bottom of rectangle.
3603
*/
3604
rc.left = 0;
3605
rc.top = 0;
3606
rc.right = gui.num_cols * gui.char_width;
3607
rc.bottom = gui.num_rows * gui.char_height;
3608
InvertRect(s_hdc, &rc);
3609
gui_mch_flush(); // make sure it's displayed
3610
3611
ui_delay((long)msec, TRUE); // wait for a few msec
3612
3613
InvertRect(s_hdc, &rc);
3614
}
3615
3616
/*
3617
* Check if the specified point is on-screen. (multi-monitor aware)
3618
*/
3619
static BOOL
3620
is_point_onscreen(int x, int y)
3621
{
3622
POINT pt = {x, y};
3623
3624
return MonitorFromPoint(pt, MONITOR_DEFAULTTONULL) != NULL;
3625
}
3626
3627
/*
3628
* Check if the whole client area of the specified window is on-screen.
3629
*
3630
* Note about DirectX: Windows 10 1809 or above no longer maintains image of
3631
* the window portion that is off-screen. Scrolling by DWriteContext_Scroll()
3632
* only works when the whole window is on-screen.
3633
*/
3634
static BOOL
3635
is_window_onscreen(HWND hwnd)
3636
{
3637
RECT rc;
3638
POINT p1, p2;
3639
3640
GetClientRect(hwnd, &rc);
3641
p1.x = rc.left;
3642
p1.y = rc.top;
3643
p2.x = rc.right - 1;
3644
p2.y = rc.bottom - 1;
3645
ClientToScreen(hwnd, &p1);
3646
ClientToScreen(hwnd, &p2);
3647
3648
if (!is_point_onscreen(p1.x, p1.y))
3649
return FALSE;
3650
if (!is_point_onscreen(p1.x, p2.y))
3651
return FALSE;
3652
if (!is_point_onscreen(p2.x, p1.y))
3653
return FALSE;
3654
if (!is_point_onscreen(p2.x, p2.y))
3655
return FALSE;
3656
return TRUE;
3657
}
3658
3659
/*
3660
* Return flags used for scrolling.
3661
* The SW_INVALIDATE is required when part of the window is covered or
3662
* off-screen. Refer to MS KB Q75236.
3663
*/
3664
static int
3665
get_scroll_flags(void)
3666
{
3667
HWND hwnd;
3668
RECT rcVim, rcOther, rcDest;
3669
3670
// Check if the window is (partly) off-screen.
3671
if (!is_window_onscreen(s_hwnd))
3672
return SW_INVALIDATE;
3673
3674
// Check if there is a window (partly) on top of us.
3675
GetWindowRect(s_hwnd, &rcVim);
3676
for (hwnd = s_hwnd; (hwnd = GetWindow(hwnd, GW_HWNDPREV)) != (HWND)0; )
3677
if (IsWindowVisible(hwnd))
3678
{
3679
GetWindowRect(hwnd, &rcOther);
3680
if (IntersectRect(&rcDest, &rcVim, &rcOther))
3681
return SW_INVALIDATE;
3682
}
3683
return 0;
3684
}
3685
3686
/*
3687
* On some Intel GPUs, the regions drawn just prior to ScrollWindowEx()
3688
* may not be scrolled out properly.
3689
* For gVim, when _OnScroll() is repeated, the character at the
3690
* previous cursor position may be left drawn after scroll.
3691
* The problem can be avoided by calling GetPixel() to get a pixel in
3692
* the region before ScrollWindowEx().
3693
*/
3694
static void
3695
intel_gpu_workaround(void)
3696
{
3697
GetPixel(s_hdc, FILL_X(gui.col), FILL_Y(gui.row));
3698
}
3699
3700
/*
3701
* Delete the given number of lines from the given row, scrolling up any
3702
* text further down within the scroll region.
3703
*/
3704
void
3705
gui_mch_delete_lines(
3706
int row,
3707
int num_lines)
3708
{
3709
RECT rc;
3710
3711
rc.left = FILL_X(gui.scroll_region_left);
3712
rc.right = FILL_X(gui.scroll_region_right + 1);
3713
rc.top = FILL_Y(row);
3714
rc.bottom = FILL_Y(gui.scroll_region_bot + 1);
3715
3716
#if defined(FEAT_DIRECTX)
3717
if (IS_ENABLE_DIRECTX() && is_window_onscreen(s_hwnd))
3718
{
3719
DWriteContext_Scroll(s_dwc, 0, -num_lines * gui.char_height, &rc);
3720
}
3721
else
3722
#endif
3723
{
3724
#if defined(FEAT_DIRECTX)
3725
if (IS_ENABLE_DIRECTX())
3726
DWriteContext_Flush(s_dwc);
3727
#endif
3728
intel_gpu_workaround();
3729
ScrollWindowEx(s_textArea, 0, -num_lines * gui.char_height,
3730
&rc, &rc, NULL, NULL, get_scroll_flags());
3731
UpdateWindow(s_textArea);
3732
}
3733
3734
// This seems to be required to avoid the cursor disappearing when
3735
// scrolling such that the cursor ends up in the top-left character on
3736
// the screen... But why? (Webb)
3737
// It's probably fixed by disabling drawing the cursor while scrolling.
3738
// gui.cursor_is_valid = FALSE;
3739
3740
gui_clear_block(gui.scroll_region_bot - num_lines + 1,
3741
gui.scroll_region_left,
3742
gui.scroll_region_bot, gui.scroll_region_right);
3743
}
3744
3745
/*
3746
* Insert the given number of lines before the given row, scrolling down any
3747
* following text within the scroll region.
3748
*/
3749
void
3750
gui_mch_insert_lines(
3751
int row,
3752
int num_lines)
3753
{
3754
RECT rc;
3755
3756
rc.left = FILL_X(gui.scroll_region_left);
3757
rc.right = FILL_X(gui.scroll_region_right + 1);
3758
rc.top = FILL_Y(row);
3759
rc.bottom = FILL_Y(gui.scroll_region_bot + 1);
3760
3761
#if defined(FEAT_DIRECTX)
3762
if (IS_ENABLE_DIRECTX() && is_window_onscreen(s_hwnd))
3763
{
3764
DWriteContext_Scroll(s_dwc, 0, num_lines * gui.char_height, &rc);
3765
}
3766
else
3767
#endif
3768
{
3769
#if defined(FEAT_DIRECTX)
3770
if (IS_ENABLE_DIRECTX())
3771
DWriteContext_Flush(s_dwc);
3772
#endif
3773
intel_gpu_workaround();
3774
// The SW_INVALIDATE is required when part of the window is covered or
3775
// off-screen. How do we avoid it when it's not needed?
3776
ScrollWindowEx(s_textArea, 0, num_lines * gui.char_height,
3777
&rc, &rc, NULL, NULL, get_scroll_flags());
3778
UpdateWindow(s_textArea);
3779
}
3780
3781
gui_clear_block(row, gui.scroll_region_left,
3782
row + num_lines - 1, gui.scroll_region_right);
3783
}
3784
3785
3786
void
3787
gui_mch_exit(int rc UNUSED)
3788
{
3789
#if defined(FEAT_DIRECTX)
3790
DWriteContext_Close(s_dwc);
3791
DWrite_Final();
3792
s_dwc = NULL;
3793
#endif
3794
3795
ReleaseDC(s_textArea, s_hdc);
3796
DeleteObject(s_brush);
3797
3798
#ifdef FEAT_TEAROFF
3799
// Unload the tearoff bitmap
3800
(void)DeleteObject((HGDIOBJ)s_htearbitmap);
3801
#endif
3802
3803
// Destroy our window (if we have one).
3804
if (s_hwnd != NULL)
3805
{
3806
destroying = TRUE; // ignore WM_DESTROY message now
3807
DestroyWindow(s_hwnd);
3808
}
3809
}
3810
3811
static char_u *
3812
logfont2name(LOGFONTW lf)
3813
{
3814
char *charset_name;
3815
char *quality_name;
3816
char *font_name;
3817
size_t res_size;
3818
char *res;
3819
3820
font_name = (char *)utf16_to_enc(lf.lfFaceName, NULL);
3821
if (font_name == NULL)
3822
return NULL;
3823
charset_name = charset_id2name((int)lf.lfCharSet);
3824
quality_name = quality_id2name((int)lf.lfQuality);
3825
3826
res_size = STRLEN(font_name) + 30
3827
+ (charset_name == NULL ? 0 : STRLEN(charset_name) + 2)
3828
+ (quality_name == NULL ? 0 : STRLEN(quality_name) + 2);
3829
res = alloc(res_size);
3830
if (res != NULL)
3831
{
3832
char *p;
3833
int points;
3834
size_t res_len;
3835
3836
// replace spaces in font_name with underscores.
3837
for (p = font_name; *p != NUL; ++p)
3838
{
3839
if (isspace(*p))
3840
*p = '_';
3841
}
3842
3843
// make a normal font string out of the lf thing:
3844
points = pixels_to_points(
3845
lf.lfHeight < 0 ? -lf.lfHeight : lf.lfHeight, TRUE);
3846
if (lf.lfWeight == FW_NORMAL || lf.lfWeight == FW_BOLD)
3847
res_len = vim_snprintf_safelen(
3848
(char *)res, res_size, "%s:h%d", font_name, points);
3849
else
3850
res_len = vim_snprintf_safelen(
3851
(char *)res, res_size, "%s:h%d:W%ld", font_name, points, lf.lfWeight);
3852
3853
res_len += vim_snprintf_safelen(
3854
(char *)res + res_len,
3855
res_size - res_len,
3856
"%s%s%s%s",
3857
lf.lfItalic ? ":i" : "",
3858
lf.lfWeight == FW_BOLD ? ":b" : "",
3859
lf.lfUnderline ? ":u" : "",
3860
lf.lfStrikeOut ? ":s" : "");
3861
3862
if (charset_name != NULL)
3863
res_len += vim_snprintf_safelen((char *)res + res_len,
3864
res_size - res_len, ":c%s", charset_name);
3865
if (quality_name != NULL)
3866
vim_snprintf((char *)res + res_len,
3867
res_size - res_len, ":q%s", quality_name);
3868
}
3869
3870
vim_free(font_name);
3871
return (char_u *)res;
3872
}
3873
3874
3875
#ifdef FEAT_MBYTE_IME
3876
/*
3877
* Set correct LOGFONTW to IME. Use 'guifontwide' if available, otherwise use
3878
* 'guifont'.
3879
*/
3880
static void
3881
update_im_font(void)
3882
{
3883
LOGFONTW lf_wide, lf;
3884
3885
if (p_guifontwide != NULL && *p_guifontwide != NUL
3886
&& gui.wide_font != NOFONT
3887
&& GetObjectW((HFONT)gui.wide_font, sizeof(lf_wide), &lf_wide))
3888
norm_logfont = lf_wide;
3889
else
3890
norm_logfont = sub_logfont;
3891
3892
lf = norm_logfont;
3893
if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE)
3894
// Work around when PerMonitorV2 is not enabled in the process level.
3895
lf.lfHeight = lf.lfHeight * DEFAULT_DPI / s_dpi;
3896
im_set_font(&lf);
3897
}
3898
#endif
3899
3900
/*
3901
* Handler of gui.wide_font (p_guifontwide) changed notification.
3902
*/
3903
void
3904
gui_mch_wide_font_changed(void)
3905
{
3906
LOGFONTW lf;
3907
3908
#ifdef FEAT_MBYTE_IME
3909
update_im_font();
3910
#endif
3911
3912
gui_mch_free_font(gui.wide_ital_font);
3913
gui.wide_ital_font = NOFONT;
3914
gui_mch_free_font(gui.wide_bold_font);
3915
gui.wide_bold_font = NOFONT;
3916
gui_mch_free_font(gui.wide_boldital_font);
3917
gui.wide_boldital_font = NOFONT;
3918
3919
if (gui.wide_font
3920
&& GetObjectW((HFONT)gui.wide_font, sizeof(lf), &lf))
3921
{
3922
if (!lf.lfItalic)
3923
{
3924
lf.lfItalic = TRUE;
3925
gui.wide_ital_font = get_font_handle(&lf);
3926
lf.lfItalic = FALSE;
3927
}
3928
if (lf.lfWeight < FW_BOLD)
3929
{
3930
lf.lfWeight = FW_BOLD;
3931
gui.wide_bold_font = get_font_handle(&lf);
3932
if (!lf.lfItalic)
3933
{
3934
lf.lfItalic = TRUE;
3935
gui.wide_boldital_font = get_font_handle(&lf);
3936
}
3937
}
3938
}
3939
}
3940
3941
/*
3942
* Initialise vim to use the font with the given name.
3943
* Return FAIL if the font could not be loaded, OK otherwise.
3944
*/
3945
int
3946
gui_mch_init_font(char_u *font_name, int fontset UNUSED)
3947
{
3948
LOGFONTW lf, lfOrig;
3949
GuiFont font = NOFONT;
3950
char_u *p;
3951
3952
// Load the font
3953
if (get_logfont(&lf, font_name, NULL, TRUE) == OK)
3954
{
3955
lfOrig = lf;
3956
lf.lfHeight = adjust_fontsize_by_dpi(lf.lfHeight);
3957
font = get_font_handle(&lf);
3958
}
3959
if (font == NOFONT)
3960
return FAIL;
3961
3962
if (font_name == NULL)
3963
font_name = (char_u *)"";
3964
#ifdef FEAT_MBYTE_IME
3965
norm_logfont = lf;
3966
sub_logfont = lf;
3967
if (!s_in_dpichanged)
3968
update_im_font();
3969
#endif
3970
gui_mch_free_font(gui.norm_font);
3971
gui.norm_font = font;
3972
current_font_height = lfOrig.lfHeight;
3973
UpdateFontSize(font);
3974
3975
p = logfont2name(lfOrig);
3976
if (p != NULL)
3977
{
3978
hl_set_font_name(p);
3979
3980
// When setting 'guifont' to "*" replace it with the actual font name.
3981
if (STRCMP(font_name, "*") == 0 && STRCMP(p_guifont, "*") == 0)
3982
{
3983
vim_free(p_guifont);
3984
p_guifont = p;
3985
}
3986
else
3987
vim_free(p);
3988
}
3989
3990
gui_mch_free_font(gui.ital_font);
3991
gui.ital_font = NOFONT;
3992
gui_mch_free_font(gui.bold_font);
3993
gui.bold_font = NOFONT;
3994
gui_mch_free_font(gui.boldital_font);
3995
gui.boldital_font = NOFONT;
3996
3997
if (!lf.lfItalic)
3998
{
3999
lf.lfItalic = TRUE;
4000
gui.ital_font = get_font_handle(&lf);
4001
lf.lfItalic = FALSE;
4002
}
4003
if (lf.lfWeight < FW_BOLD)
4004
{
4005
lf.lfWeight = FW_BOLD;
4006
gui.bold_font = get_font_handle(&lf);
4007
if (!lf.lfItalic)
4008
{
4009
lf.lfItalic = TRUE;
4010
gui.boldital_font = get_font_handle(&lf);
4011
}
4012
}
4013
4014
return OK;
4015
}
4016
4017
/*
4018
* Return TRUE if the GUI window is maximized, filling the whole screen.
4019
* Also return TRUE if the window is snapped.
4020
*/
4021
int
4022
gui_mch_maximized(void)
4023
{
4024
WINDOWPLACEMENT wp;
4025
RECT rc;
4026
4027
wp.length = sizeof(WINDOWPLACEMENT);
4028
if (GetWindowPlacement(s_hwnd, &wp))
4029
{
4030
if (wp.showCmd == SW_SHOWMAXIMIZED
4031
|| (wp.showCmd == SW_SHOWMINIMIZED
4032
&& wp.flags == WPF_RESTORETOMAXIMIZED))
4033
return TRUE;
4034
if (wp.showCmd == SW_SHOWMINIMIZED)
4035
return FALSE;
4036
4037
// Assume the window is snapped when the sizes from two APIs differ.
4038
GetWindowRect(s_hwnd, &rc);
4039
if ((rc.right - rc.left !=
4040
wp.rcNormalPosition.right - wp.rcNormalPosition.left)
4041
|| (rc.bottom - rc.top !=
4042
wp.rcNormalPosition.bottom - wp.rcNormalPosition.top))
4043
return TRUE;
4044
}
4045
return FALSE;
4046
}
4047
4048
/*
4049
* Called when the font changed while the window is maximized or GO_KEEPWINSIZE
4050
* is set. Compute the new Rows and Columns. This is like resizing the
4051
* window.
4052
*/
4053
void
4054
gui_mch_newfont(void)
4055
{
4056
RECT rect;
4057
4058
GetWindowRect(s_hwnd, &rect);
4059
if (win_socket_id == 0)
4060
{
4061
gui_resize_shell(rect.right - rect.left
4062
- (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) +
4063
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2,
4064
rect.bottom - rect.top
4065
- (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) +
4066
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2
4067
- pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi)
4068
- gui_mswin_get_menu_height(FALSE));
4069
}
4070
else
4071
{
4072
// Inside another window, don't use the frame and border.
4073
gui_resize_shell(rect.right - rect.left,
4074
rect.bottom - rect.top - gui_mswin_get_menu_height(FALSE));
4075
}
4076
}
4077
4078
/*
4079
* Set the window title
4080
*/
4081
void
4082
gui_mch_settitle(
4083
char_u *title,
4084
char_u *icon UNUSED)
4085
{
4086
set_window_title(s_hwnd, (title == NULL ? "VIM" : (char *)title));
4087
}
4088
4089
#if defined(FEAT_MOUSESHAPE)
4090
// Table for shape IDCs. Keep in sync with the mshape_names[] table in
4091
// misc2.c!
4092
static LPCSTR mshape_idcs[] =
4093
{
4094
IDC_ARROW, // arrow
4095
MAKEINTRESOURCE(0), // blank
4096
IDC_IBEAM, // beam
4097
IDC_SIZENS, // updown
4098
IDC_SIZENS, // udsizing
4099
IDC_SIZEWE, // leftright
4100
IDC_SIZEWE, // lrsizing
4101
IDC_WAIT, // busy
4102
IDC_NO, // no
4103
IDC_ARROW, // crosshair
4104
IDC_ARROW, // hand1
4105
IDC_ARROW, // hand2
4106
IDC_ARROW, // pencil
4107
IDC_ARROW, // question
4108
IDC_ARROW, // right-arrow
4109
IDC_UPARROW, // up-arrow
4110
IDC_ARROW // last one
4111
};
4112
4113
void
4114
mch_set_mouse_shape(int shape)
4115
{
4116
LPCSTR idc;
4117
4118
if (shape == MSHAPE_HIDE)
4119
ShowCursor(FALSE);
4120
else
4121
{
4122
if (shape >= MSHAPE_NUMBERED)
4123
idc = IDC_ARROW;
4124
else
4125
idc = mshape_idcs[shape];
4126
SetClassLongPtr(s_textArea, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, idc));
4127
if (!p_mh)
4128
{
4129
POINT mp;
4130
4131
// Set the position to make it redrawn with the new shape.
4132
(void)GetCursorPos(&mp);
4133
(void)SetCursorPos(mp.x, mp.y);
4134
ShowCursor(TRUE);
4135
}
4136
}
4137
}
4138
#endif
4139
4140
#if defined(FEAT_BROWSE)
4141
/*
4142
* Wide version of convert_filter().
4143
*/
4144
static WCHAR *
4145
convert_filterW(char_u *s)
4146
{
4147
char_u *tmp;
4148
int len;
4149
WCHAR *res;
4150
4151
tmp = convert_filter(s);
4152
if (tmp == NULL)
4153
return NULL;
4154
len = (int)STRLEN(s) + 3;
4155
res = enc_to_utf16(tmp, &len);
4156
vim_free(tmp);
4157
return res;
4158
}
4159
4160
/*
4161
* Pop open a file browser and return the file selected, in allocated memory,
4162
* or NULL if Cancel is hit.
4163
* saving - TRUE if the file will be saved to, FALSE if it will be opened.
4164
* title - Title message for the file browser dialog.
4165
* dflt - Default name of file.
4166
* ext - Default extension to be added to files without extensions.
4167
* initdir - directory in which to open the browser (NULL = current dir)
4168
* filter - Filter for matched files to choose from.
4169
*/
4170
char_u *
4171
gui_mch_browse(
4172
int saving,
4173
char_u *title,
4174
char_u *dflt,
4175
char_u *ext,
4176
char_u *initdir,
4177
char_u *filter)
4178
{
4179
// We always use the wide function. This means enc_to_utf16() must work,
4180
// otherwise it fails miserably!
4181
OPENFILENAMEW fileStruct;
4182
WCHAR fileBuf[MAXPATHL];
4183
WCHAR *wp;
4184
int i;
4185
WCHAR *titlep = NULL;
4186
WCHAR *extp = NULL;
4187
WCHAR *initdirp = NULL;
4188
WCHAR *filterp;
4189
char_u *p, *q;
4190
BOOL ret;
4191
4192
if (dflt == NULL)
4193
fileBuf[0] = NUL;
4194
else
4195
{
4196
wp = enc_to_utf16(dflt, NULL);
4197
if (wp == NULL)
4198
fileBuf[0] = NUL;
4199
else
4200
{
4201
for (i = 0; wp[i] != NUL && i < MAXPATHL - 1; ++i)
4202
fileBuf[i] = wp[i];
4203
fileBuf[i] = NUL;
4204
vim_free(wp);
4205
}
4206
}
4207
4208
// Convert the filter to Windows format.
4209
filterp = convert_filterW(filter);
4210
4211
CLEAR_FIELD(fileStruct);
4212
# ifdef OPENFILENAME_SIZE_VERSION_400W
4213
// be compatible with Windows NT 4.0
4214
fileStruct.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
4215
# else
4216
fileStruct.lStructSize = sizeof(fileStruct);
4217
# endif
4218
4219
if (title != NULL)
4220
titlep = enc_to_utf16(title, NULL);
4221
fileStruct.lpstrTitle = titlep;
4222
4223
if (ext != NULL)
4224
extp = enc_to_utf16(ext, NULL);
4225
fileStruct.lpstrDefExt = extp;
4226
4227
fileStruct.lpstrFile = fileBuf;
4228
fileStruct.nMaxFile = MAXPATHL;
4229
fileStruct.lpstrFilter = filterp;
4230
fileStruct.hwndOwner = s_hwnd; // main Vim window is owner
4231
// has an initial dir been specified?
4232
if (initdir != NULL && *initdir != NUL)
4233
{
4234
// Must have backslashes here, no matter what 'shellslash' says
4235
initdirp = enc_to_utf16(initdir, NULL);
4236
if (initdirp != NULL)
4237
{
4238
for (wp = initdirp; *wp != NUL; ++wp)
4239
if (*wp == '/')
4240
*wp = '\\';
4241
}
4242
fileStruct.lpstrInitialDir = initdirp;
4243
}
4244
4245
/*
4246
* TODO: Allow selection of multiple files. Needs another arg to this
4247
* function to ask for it, and need to use OFN_ALLOWMULTISELECT below.
4248
* Also, should we use OFN_FILEMUSTEXIST when opening? Vim can edit on
4249
* files that don't exist yet, so I haven't put it in. What about
4250
* OFN_PATHMUSTEXIST?
4251
* Don't use OFN_OVERWRITEPROMPT, Vim has its own ":confirm" dialog.
4252
*/
4253
fileStruct.Flags = (OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY);
4254
# ifdef FEAT_SHORTCUT
4255
if (curbuf->b_p_bin)
4256
fileStruct.Flags |= OFN_NODEREFERENCELINKS;
4257
# endif
4258
if (saving)
4259
ret = GetSaveFileNameW(&fileStruct);
4260
else
4261
ret = GetOpenFileNameW(&fileStruct);
4262
4263
vim_free(filterp);
4264
vim_free(initdirp);
4265
vim_free(titlep);
4266
vim_free(extp);
4267
4268
if (!ret)
4269
return NULL;
4270
4271
// Convert from UTF-16 to 'encoding'.
4272
p = utf16_to_enc(fileBuf, NULL);
4273
if (p == NULL)
4274
return NULL;
4275
4276
// Give focus back to main window (when using MDI).
4277
SetFocus(s_hwnd);
4278
4279
// Shorten the file name if possible
4280
q = vim_strsave(shorten_fname1(p));
4281
vim_free(p);
4282
return q;
4283
}
4284
4285
4286
/*
4287
* Convert the string s to the proper format for a filter string by replacing
4288
* the \t and \n delimiters with \0.
4289
* Returns the converted string in allocated memory.
4290
*
4291
* Keep in sync with convert_filterW() above!
4292
*/
4293
static char_u *
4294
convert_filter(char_u *s)
4295
{
4296
char_u *res;
4297
unsigned s_len = (unsigned)STRLEN(s);
4298
unsigned i;
4299
4300
res = alloc(s_len + 3);
4301
if (res != NULL)
4302
{
4303
for (i = 0; i < s_len; ++i)
4304
if (s[i] == '\t' || s[i] == '\n')
4305
res[i] = '\0';
4306
else
4307
res[i] = s[i];
4308
res[s_len] = NUL;
4309
// Add two extra NULs to make sure it's properly terminated.
4310
res[s_len + 1] = NUL;
4311
res[s_len + 2] = NUL;
4312
}
4313
return res;
4314
}
4315
4316
/*
4317
* Select a directory.
4318
*/
4319
char_u *
4320
gui_mch_browsedir(char_u *title, char_u *initdir)
4321
{
4322
// We fake this: Use a filter that doesn't select anything and a default
4323
// file name that won't be used.
4324
return gui_mch_browse(0, title, (char_u *)_("Not Used"), NULL,
4325
initdir, (char_u *)_("Directory\t*.nothing\n"));
4326
}
4327
#endif // FEAT_BROWSE
4328
4329
static void
4330
_OnDropFiles(
4331
HWND hwnd UNUSED,
4332
HDROP hDrop)
4333
{
4334
#define BUFPATHLEN _MAX_PATH
4335
#define DRAGQVAL 0xFFFFFFFF
4336
WCHAR wszFile[BUFPATHLEN];
4337
char szFile[BUFPATHLEN];
4338
UINT cFiles = DragQueryFile(hDrop, DRAGQVAL, NULL, 0);
4339
UINT i;
4340
char_u **fnames;
4341
POINT pt;
4342
int_u modifiers = 0;
4343
4344
// Obtain dropped position
4345
DragQueryPoint(hDrop, &pt);
4346
MapWindowPoints(s_hwnd, s_textArea, &pt, 1);
4347
4348
reset_VIsual();
4349
4350
fnames = ALLOC_MULT(char_u *, cFiles);
4351
4352
if (fnames != NULL)
4353
for (i = 0; i < cFiles; ++i)
4354
{
4355
if (DragQueryFileW(hDrop, i, wszFile, BUFPATHLEN) > 0)
4356
fnames[i] = utf16_to_enc(wszFile, NULL);
4357
else
4358
{
4359
DragQueryFile(hDrop, i, szFile, BUFPATHLEN);
4360
fnames[i] = vim_strsave((char_u *)szFile);
4361
}
4362
}
4363
4364
DragFinish(hDrop);
4365
4366
if (fnames == NULL)
4367
return;
4368
4369
int kbd_modifiers = get_active_modifiers();
4370
4371
if ((kbd_modifiers & MOD_MASK_SHIFT) != 0)
4372
modifiers |= MOUSE_SHIFT;
4373
if ((kbd_modifiers & MOD_MASK_CTRL) != 0)
4374
modifiers |= MOUSE_CTRL;
4375
if ((kbd_modifiers & MOD_MASK_ALT) != 0)
4376
modifiers |= MOUSE_ALT;
4377
4378
gui_handle_drop(pt.x, pt.y, modifiers, fnames, cFiles);
4379
4380
s_need_activate = TRUE;
4381
}
4382
4383
static int
4384
_OnScroll(
4385
HWND hwnd UNUSED,
4386
HWND hwndCtl,
4387
UINT code,
4388
int pos)
4389
{
4390
static UINT prev_code = 0; // code of previous call
4391
scrollbar_T *sb, *sb_info;
4392
long val;
4393
int dragging = FALSE;
4394
int dont_scroll_save = dont_scroll;
4395
SCROLLINFO si;
4396
4397
si.cbSize = sizeof(si);
4398
si.fMask = SIF_POS;
4399
4400
sb = gui_mswin_find_scrollbar(hwndCtl);
4401
if (sb == NULL)
4402
return 0;
4403
4404
if (sb->wp != NULL) // Left or right scrollbar
4405
{
4406
/*
4407
* Careful: need to get scrollbar info out of first (left) scrollbar
4408
* for window, but keep real scrollbar too because we must pass it to
4409
* gui_drag_scrollbar().
4410
*/
4411
sb_info = &sb->wp->w_scrollbars[0];
4412
}
4413
else // Bottom scrollbar
4414
sb_info = sb;
4415
val = sb_info->value;
4416
4417
switch (code)
4418
{
4419
case SB_THUMBTRACK:
4420
val = pos;
4421
dragging = TRUE;
4422
if (sb->scroll_shift > 0)
4423
val <<= sb->scroll_shift;
4424
break;
4425
case SB_LINEDOWN:
4426
val++;
4427
break;
4428
case SB_LINEUP:
4429
val--;
4430
break;
4431
case SB_PAGEDOWN:
4432
val += (sb_info->size > 2 ? sb_info->size - 2 : 1);
4433
break;
4434
case SB_PAGEUP:
4435
val -= (sb_info->size > 2 ? sb_info->size - 2 : 1);
4436
break;
4437
case SB_TOP:
4438
val = 0;
4439
break;
4440
case SB_BOTTOM:
4441
val = sb_info->max;
4442
break;
4443
case SB_ENDSCROLL:
4444
if (prev_code == SB_THUMBTRACK)
4445
{
4446
/*
4447
* "pos" only gives us 16-bit data. In case of large file,
4448
* use GetScrollPos() which returns 32-bit. Unfortunately it
4449
* is not valid while the scrollbar is being dragged.
4450
*/
4451
val = GetScrollPos(hwndCtl, SB_CTL);
4452
if (sb->scroll_shift > 0)
4453
val <<= sb->scroll_shift;
4454
}
4455
break;
4456
4457
default:
4458
// TRACE("Unknown scrollbar event %d\n", code);
4459
return 0;
4460
}
4461
prev_code = code;
4462
4463
si.nPos = (sb->scroll_shift > 0) ? val >> sb->scroll_shift : val;
4464
SetScrollInfo(hwndCtl, SB_CTL, &si, TRUE);
4465
4466
/*
4467
* When moving a vertical scrollbar, move the other vertical scrollbar too.
4468
*/
4469
if (sb->wp != NULL)
4470
{
4471
scrollbar_T *sba = sb->wp->w_scrollbars;
4472
HWND id = sba[ (sb == sba + SBAR_LEFT) ? SBAR_RIGHT : SBAR_LEFT].id;
4473
4474
SetScrollInfo(id, SB_CTL, &si, TRUE);
4475
}
4476
4477
// Don't let us be interrupted here by another message.
4478
s_busy_processing = TRUE;
4479
4480
// When "allow_scrollbar" is FALSE still need to remember the new
4481
// position, but don't actually scroll by setting "dont_scroll".
4482
dont_scroll = !allow_scrollbar;
4483
4484
mch_disable_flush();
4485
gui_drag_scrollbar(sb, val, dragging);
4486
mch_enable_flush();
4487
gui_may_flush();
4488
4489
s_busy_processing = FALSE;
4490
dont_scroll = dont_scroll_save;
4491
4492
return 0;
4493
}
4494
4495
4496
#ifdef FEAT_XPM_W32
4497
# include "xpm_w32.h"
4498
#endif
4499
4500
4501
// Some parameters for tearoff menus. All in pixels.
4502
#define TEAROFF_PADDING_X 2
4503
#define TEAROFF_BUTTON_PAD_X 8
4504
#define TEAROFF_MIN_WIDTH 200
4505
#define TEAROFF_SUBMENU_LABEL ">>"
4506
#define TEAROFF_COLUMN_PADDING 3 // # spaces to pad column with.
4507
4508
4509
#ifdef FEAT_BEVAL_GUI
4510
# define ID_BEVAL_TOOLTIP 200
4511
# define BEVAL_TEXT_LEN MAXPATHL
4512
4513
static BalloonEval *cur_beval = NULL;
4514
static UINT_PTR beval_timer_id = 0;
4515
static DWORD last_user_activity = 0;
4516
#endif // defined(FEAT_BEVAL_GUI)
4517
4518
4519
// Local variables:
4520
4521
#ifdef FEAT_MENU
4522
static UINT s_menu_id = 100;
4523
#endif
4524
4525
/*
4526
* Use the system font for dialogs and tear-off menus. Remove this line to
4527
* use DLG_FONT_NAME.
4528
*/
4529
#define USE_SYSMENU_FONT
4530
4531
#define VIM_NAME "vim"
4532
#define VIM_CLASSW L"Vim"
4533
4534
// Initial size for the dialog template. For gui_mch_dialog() it's fixed,
4535
// thus there should be room for every dialog. For tearoffs it's made bigger
4536
// when needed.
4537
#define DLG_ALLOC_SIZE 16 * 1024
4538
4539
/*
4540
* stuff for dialogs, menus, tearoffs etc.
4541
*/
4542
static PWORD
4543
add_dialog_element(
4544
PWORD p,
4545
DWORD lStyle,
4546
WORD x,
4547
WORD y,
4548
WORD w,
4549
WORD h,
4550
WORD Id,
4551
WORD clss,
4552
const char *caption);
4553
static LPWORD lpwAlign(LPWORD);
4554
static int nCopyAnsiToWideChar(LPWORD, LPSTR, BOOL);
4555
#if defined(FEAT_MENU) && defined(FEAT_TEAROFF)
4556
static void gui_mch_tearoff(char_u *title, vimmenu_T *menu, int initX, int initY);
4557
#endif
4558
static void get_dialog_font_metrics(void);
4559
4560
static int dialog_default_button = -1;
4561
4562
#ifdef FEAT_TOOLBAR
4563
static void initialise_toolbar(void);
4564
static void update_toolbar_size(void);
4565
static LRESULT CALLBACK toolbar_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
4566
static int get_toolbar_bitmap(vimmenu_T *menu);
4567
#else
4568
# define update_toolbar_size()
4569
#endif
4570
4571
#ifdef FEAT_GUI_TABLINE
4572
static void initialise_tabline(void);
4573
static LRESULT CALLBACK tabline_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
4574
#endif
4575
4576
#ifdef FEAT_MBYTE_IME
4577
static LRESULT _OnImeComposition(HWND hwnd, WPARAM dbcs, LPARAM param);
4578
static char_u *GetResultStr(HWND hwnd, int GCS, int *lenp);
4579
#endif
4580
#if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME)
4581
# ifdef NOIME
4582
typedef struct tagCOMPOSITIONFORM {
4583
DWORD dwStyle;
4584
POINT ptCurrentPos;
4585
RECT rcArea;
4586
} COMPOSITIONFORM, *PCOMPOSITIONFORM, NEAR *NPCOMPOSITIONFORM, FAR *LPCOMPOSITIONFORM;
4587
typedef HANDLE HIMC;
4588
# endif
4589
4590
static HINSTANCE hLibImm = NULL;
4591
static LONG (WINAPI *pImmGetCompositionStringW)(HIMC, DWORD, LPVOID, DWORD);
4592
static HIMC (WINAPI *pImmGetContext)(HWND);
4593
static HIMC (WINAPI *pImmAssociateContext)(HWND, HIMC);
4594
static BOOL (WINAPI *pImmReleaseContext)(HWND, HIMC);
4595
static BOOL (WINAPI *pImmGetOpenStatus)(HIMC);
4596
static BOOL (WINAPI *pImmSetOpenStatus)(HIMC, BOOL);
4597
static BOOL (WINAPI *pImmGetCompositionFontW)(HIMC, LPLOGFONTW);
4598
static BOOL (WINAPI *pImmSetCompositionFontW)(HIMC, LPLOGFONTW);
4599
static BOOL (WINAPI *pImmSetCompositionWindow)(HIMC, LPCOMPOSITIONFORM);
4600
static BOOL (WINAPI *pImmGetConversionStatus)(HIMC, LPDWORD, LPDWORD);
4601
static BOOL (WINAPI *pImmSetConversionStatus)(HIMC, DWORD, DWORD);
4602
static void dyn_imm_load(void);
4603
#else
4604
# define pImmGetCompositionStringW ImmGetCompositionStringW
4605
# define pImmGetContext ImmGetContext
4606
# define pImmAssociateContext ImmAssociateContext
4607
# define pImmReleaseContext ImmReleaseContext
4608
# define pImmGetOpenStatus ImmGetOpenStatus
4609
# define pImmSetOpenStatus ImmSetOpenStatus
4610
# define pImmGetCompositionFontW ImmGetCompositionFontW
4611
# define pImmSetCompositionFontW ImmSetCompositionFontW
4612
# define pImmSetCompositionWindow ImmSetCompositionWindow
4613
# define pImmGetConversionStatus ImmGetConversionStatus
4614
# define pImmSetConversionStatus ImmSetConversionStatus
4615
#endif
4616
4617
#ifdef FEAT_MENU
4618
/*
4619
* Figure out how high the menu bar is at the moment.
4620
*/
4621
static int
4622
gui_mswin_get_menu_height(
4623
int fix_window) // If TRUE, resize window if menu height changed
4624
{
4625
static int old_menu_height = -1;
4626
4627
RECT rc1, rc2;
4628
int num;
4629
int menu_height;
4630
4631
if (gui.menu_is_active)
4632
num = GetMenuItemCount(s_menuBar);
4633
else
4634
num = 0;
4635
4636
if (num == 0)
4637
menu_height = 0;
4638
else if (IsMinimized(s_hwnd))
4639
{
4640
// The height of the menu cannot be determined while the window is
4641
// minimized. Take the previous height if the menu is changed in that
4642
// state, to avoid that Vim's vertical window size accidentally
4643
// increases due to the unaccounted-for menu height.
4644
menu_height = old_menu_height == -1 ? 0 : old_menu_height;
4645
}
4646
else
4647
{
4648
/*
4649
* In case 'lines' is set in _vimrc/_gvimrc window width doesn't
4650
* seem to have been set yet, so menu wraps in default window
4651
* width which is very narrow. Instead just return height of a
4652
* single menu item. Will still be wrong when the menu really
4653
* should wrap over more than one line.
4654
*/
4655
GetMenuItemRect(s_hwnd, s_menuBar, 0, &rc1);
4656
if (gui.starting)
4657
menu_height = rc1.bottom - rc1.top + 1;
4658
else
4659
{
4660
GetMenuItemRect(s_hwnd, s_menuBar, num - 1, &rc2);
4661
menu_height = rc2.bottom - rc1.top + 1;
4662
}
4663
}
4664
4665
if (fix_window && menu_height != old_menu_height)
4666
gui_set_shellsize(FALSE, FALSE, RESIZE_VERT);
4667
old_menu_height = menu_height;
4668
4669
return menu_height;
4670
}
4671
#endif // FEAT_MENU
4672
4673
4674
/*
4675
* Setup for the Intellimouse
4676
*/
4677
static long
4678
mouse_vertical_scroll_step(void)
4679
{
4680
UINT val;
4681
if (SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &val, 0))
4682
return (val != WHEEL_PAGESCROLL) ? (long)val : -1;
4683
return 3; // Safe default;
4684
}
4685
4686
static long
4687
mouse_horizontal_scroll_step(void)
4688
{
4689
UINT val;
4690
if (SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &val, 0))
4691
return (long)val;
4692
return 3; // Safe default;
4693
}
4694
4695
static void
4696
init_mouse_wheel(void)
4697
{
4698
// Get the default values for the horizontal and vertical scroll steps from
4699
// the system.
4700
mouse_set_vert_scroll_step(mouse_vertical_scroll_step());
4701
mouse_set_hor_scroll_step(mouse_horizontal_scroll_step());
4702
}
4703
4704
/*
4705
* Mouse scroll event handler.
4706
*/
4707
static void
4708
_OnMouseWheel(HWND hwnd UNUSED, WPARAM wParam, LPARAM lParam, int horizontal)
4709
{
4710
int button;
4711
win_T *wp;
4712
int modifiers = 0;
4713
int kbd_modifiers;
4714
int zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
4715
POINT pt;
4716
4717
wp = gui_mouse_window(FIND_POPUP);
4718
4719
#ifdef FEAT_PROP_POPUP
4720
if (wp != NULL && popup_is_popup(wp))
4721
{
4722
cmdarg_T cap;
4723
oparg_T oa;
4724
4725
// Mouse hovers over popup window, scroll it if possible.
4726
mouse_row = wp->w_winrow;
4727
mouse_col = wp->w_wincol;
4728
CLEAR_FIELD(cap);
4729
if (horizontal)
4730
{
4731
cap.arg = zDelta < 0 ? MSCR_LEFT : MSCR_RIGHT;
4732
cap.cmdchar = zDelta < 0 ? K_MOUSELEFT : K_MOUSERIGHT;
4733
}
4734
else
4735
{
4736
cap.arg = zDelta < 0 ? MSCR_UP : MSCR_DOWN;
4737
cap.cmdchar = zDelta < 0 ? K_MOUSEUP : K_MOUSEDOWN;
4738
}
4739
clear_oparg(&oa);
4740
cap.oap = &oa;
4741
nv_mousescroll(&cap);
4742
update_screen(0);
4743
setcursor();
4744
out_flush();
4745
return;
4746
}
4747
#endif
4748
4749
if (wp == NULL || !p_scf)
4750
wp = curwin;
4751
4752
// Translate the scroll event into an event that Vim can process so that
4753
// the user has a chance to map the scrollwheel buttons.
4754
if (horizontal)
4755
button = zDelta >= 0 ? MOUSE_6 : MOUSE_7;
4756
else
4757
button = zDelta >= 0 ? MOUSE_4 : MOUSE_5;
4758
4759
kbd_modifiers = get_active_modifiers();
4760
4761
if ((kbd_modifiers & MOD_MASK_SHIFT) != 0)
4762
modifiers |= MOUSE_SHIFT;
4763
if ((kbd_modifiers & MOD_MASK_CTRL) != 0)
4764
modifiers |= MOUSE_CTRL;
4765
if ((kbd_modifiers & MOD_MASK_ALT) != 0)
4766
modifiers |= MOUSE_ALT;
4767
4768
// The cursor position is relative to the upper-left corner of the screen.
4769
pt.x = GET_X_LPARAM(lParam);
4770
pt.y = GET_Y_LPARAM(lParam);
4771
ScreenToClient(s_textArea, &pt);
4772
4773
gui_send_mouse_event(button, pt.x, pt.y, FALSE, modifiers);
4774
}
4775
4776
#ifdef USE_SYSMENU_FONT
4777
/*
4778
* Get Menu Font.
4779
* Return OK or FAIL.
4780
*/
4781
static int
4782
gui_w32_get_menu_font(LOGFONTW *lf)
4783
{
4784
NONCLIENTMETRICSW nm;
4785
4786
nm.cbSize = sizeof(NONCLIENTMETRICSW);
4787
if (!SystemParametersInfoW(
4788
SPI_GETNONCLIENTMETRICS,
4789
sizeof(NONCLIENTMETRICSW),
4790
&nm,
4791
0))
4792
return FAIL;
4793
*lf = nm.lfMenuFont;
4794
return OK;
4795
}
4796
#endif
4797
4798
4799
#if defined(FEAT_GUI_TABLINE) && defined(USE_SYSMENU_FONT)
4800
/*
4801
* Set the GUI tabline font to the system menu font
4802
*/
4803
static void
4804
set_tabline_font(void)
4805
{
4806
LOGFONTW lfSysmenu;
4807
HFONT font;
4808
HWND hwnd;
4809
HDC hdc;
4810
HFONT hfntOld;
4811
TEXTMETRIC tm;
4812
4813
if (gui_w32_get_menu_font(&lfSysmenu) != OK)
4814
return;
4815
4816
lfSysmenu.lfHeight = adjust_fontsize_by_dpi(lfSysmenu.lfHeight);
4817
font = CreateFontIndirectW(&lfSysmenu);
4818
4819
SendMessage(s_tabhwnd, WM_SETFONT, (WPARAM)font, TRUE);
4820
4821
/*
4822
* Compute the height of the font used for the tab text
4823
*/
4824
hwnd = GetDesktopWindow();
4825
hdc = GetWindowDC(hwnd);
4826
hfntOld = SelectFont(hdc, font);
4827
4828
GetTextMetrics(hdc, &tm);
4829
4830
SelectFont(hdc, hfntOld);
4831
ReleaseDC(hwnd, hdc);
4832
4833
/*
4834
* The space used by the tab border and the space between the tab label
4835
* and the tab border is included as 7.
4836
*/
4837
gui.tabline_height = tm.tmHeight + tm.tmInternalLeading + 7;
4838
}
4839
#else
4840
# define set_tabline_font()
4841
#endif
4842
4843
/*
4844
* Invoked when a setting was changed.
4845
*/
4846
static LRESULT CALLBACK
4847
_OnSettingChange(UINT param)
4848
{
4849
switch (param)
4850
{
4851
case SPI_SETWHEELSCROLLLINES:
4852
mouse_set_vert_scroll_step(mouse_vertical_scroll_step());
4853
break;
4854
case SPI_SETWHEELSCROLLCHARS:
4855
mouse_set_hor_scroll_step(mouse_horizontal_scroll_step());
4856
break;
4857
case SPI_SETNONCLIENTMETRICS:
4858
set_tabline_font();
4859
break;
4860
default:
4861
break;
4862
}
4863
return 0;
4864
}
4865
4866
#ifdef FEAT_NETBEANS_INTG
4867
static void
4868
_OnWindowPosChanged(
4869
HWND hwnd,
4870
const LPWINDOWPOS lpwpos)
4871
{
4872
static int x = 0, y = 0, cx = 0, cy = 0;
4873
extern int WSInitialized;
4874
4875
if (WSInitialized && (lpwpos->x != x || lpwpos->y != y
4876
|| lpwpos->cx != cx || lpwpos->cy != cy))
4877
{
4878
x = lpwpos->x;
4879
y = lpwpos->y;
4880
cx = lpwpos->cx;
4881
cy = lpwpos->cy;
4882
netbeans_frame_moved(x, y);
4883
}
4884
// Allow to send WM_SIZE and WM_MOVE
4885
FORWARD_WM_WINDOWPOSCHANGED(hwnd, lpwpos, DefWindowProcW);
4886
}
4887
#endif
4888
4889
4890
static HWND hwndTip = NULL;
4891
4892
static void
4893
show_sizing_tip(int cols, int rows)
4894
{
4895
TOOLINFOA ti;
4896
char buf[32];
4897
4898
ti.cbSize = sizeof(ti);
4899
ti.hwnd = s_hwnd;
4900
ti.uId = (UINT_PTR)s_hwnd;
4901
ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND;
4902
ti.lpszText = buf;
4903
sprintf(buf, "%dx%d", cols, rows);
4904
if (hwndTip == NULL)
4905
{
4906
hwndTip = CreateWindowExA(0, TOOLTIPS_CLASSA, NULL,
4907
WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX,
4908
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
4909
s_hwnd, NULL, GetModuleHandle(NULL), NULL);
4910
SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
4911
SendMessage(hwndTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
4912
}
4913
else
4914
{
4915
SendMessage(hwndTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
4916
}
4917
SendMessage(hwndTip, TTM_POPUP, 0, 0);
4918
}
4919
4920
static void
4921
destroy_sizing_tip(void)
4922
{
4923
if (hwndTip == NULL)
4924
return;
4925
4926
DestroyWindow(hwndTip);
4927
hwndTip = NULL;
4928
}
4929
4930
static int
4931
_DuringSizing(
4932
UINT fwSide,
4933
LPRECT lprc)
4934
{
4935
int w, h;
4936
int valid_w, valid_h;
4937
int w_offset, h_offset;
4938
int cols, rows;
4939
4940
w = lprc->right - lprc->left;
4941
h = lprc->bottom - lprc->top;
4942
gui_mswin_get_valid_dimensions(w, h, &valid_w, &valid_h, &cols, &rows);
4943
w_offset = w - valid_w;
4944
h_offset = h - valid_h;
4945
4946
if (fwSide == WMSZ_LEFT || fwSide == WMSZ_TOPLEFT
4947
|| fwSide == WMSZ_BOTTOMLEFT)
4948
lprc->left += w_offset;
4949
else if (fwSide == WMSZ_RIGHT || fwSide == WMSZ_TOPRIGHT
4950
|| fwSide == WMSZ_BOTTOMRIGHT)
4951
lprc->right -= w_offset;
4952
4953
if (fwSide == WMSZ_TOP || fwSide == WMSZ_TOPLEFT
4954
|| fwSide == WMSZ_TOPRIGHT)
4955
lprc->top += h_offset;
4956
else if (fwSide == WMSZ_BOTTOM || fwSide == WMSZ_BOTTOMLEFT
4957
|| fwSide == WMSZ_BOTTOMRIGHT)
4958
lprc->bottom -= h_offset;
4959
4960
show_sizing_tip(cols, rows);
4961
return TRUE;
4962
}
4963
4964
#ifdef FEAT_GUI_TABLINE
4965
static void
4966
_OnRButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
4967
{
4968
if (gui_mch_showing_tabline())
4969
{
4970
POINT pt;
4971
RECT rect;
4972
4973
/*
4974
* If the cursor is on the tabline, display the tab menu
4975
*/
4976
GetCursorPos(&pt);
4977
GetWindowRect(s_textArea, &rect);
4978
if (pt.y < rect.top)
4979
{
4980
show_tabline_popup_menu();
4981
return;
4982
}
4983
}
4984
FORWARD_WM_RBUTTONUP(hwnd, x, y, keyFlags, DefWindowProcW);
4985
}
4986
4987
static void
4988
_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
4989
{
4990
/*
4991
* If the user double clicked the tabline, create a new tab
4992
*/
4993
if (gui_mch_showing_tabline())
4994
{
4995
POINT pt;
4996
RECT rect;
4997
4998
GetCursorPos(&pt);
4999
GetWindowRect(s_textArea, &rect);
5000
if (pt.y < rect.top)
5001
send_tabline_menu_event(0, TABLINE_MENU_NEW);
5002
}
5003
FORWARD_WM_LBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, DefWindowProcW);
5004
}
5005
#endif
5006
5007
static UINT
5008
_OnNCHitTest(HWND hwnd, int xPos, int yPos)
5009
{
5010
UINT result;
5011
int x, y;
5012
5013
result = FORWARD_WM_NCHITTEST(hwnd, xPos, yPos, DefWindowProcW);
5014
if (result != HTCLIENT)
5015
return result;
5016
5017
#ifdef FEAT_GUI_TABLINE
5018
if (gui_mch_showing_tabline())
5019
{
5020
RECT rct;
5021
5022
// If the cursor is on the GUI tabline, don't process this event
5023
GetWindowRect(s_textArea, &rct);
5024
if (yPos < rct.top)
5025
return result;
5026
}
5027
#endif
5028
(void)gui_mch_get_winpos(&x, &y);
5029
xPos -= x;
5030
5031
if (xPos < 48) // <VN> TODO should use system metric?
5032
return HTBOTTOMLEFT;
5033
else
5034
return HTBOTTOMRIGHT;
5035
}
5036
5037
#if defined(FEAT_TOOLBAR) || defined(FEAT_GUI_TABLINE)
5038
static LRESULT
5039
_OnNotify(HWND hwnd, UINT id, NMHDR *hdr)
5040
{
5041
switch (hdr->code)
5042
{
5043
case TTN_GETDISPINFOW:
5044
case TTN_GETDISPINFO:
5045
{
5046
char_u *str = NULL;
5047
static void *tt_text = NULL;
5048
5049
VIM_CLEAR(tt_text);
5050
5051
# ifdef FEAT_GUI_TABLINE
5052
if (gui_mch_showing_tabline()
5053
&& hdr->hwndFrom == TabCtrl_GetToolTips(s_tabhwnd))
5054
{
5055
POINT pt;
5056
/*
5057
* Mouse is over the GUI tabline. Display the
5058
* tooltip for the tab under the cursor
5059
*
5060
* Get the cursor position within the tab control
5061
*/
5062
GetCursorPos(&pt);
5063
if (ScreenToClient(s_tabhwnd, &pt) != 0)
5064
{
5065
TCHITTESTINFO htinfo;
5066
int idx;
5067
5068
/*
5069
* Get the tab under the cursor
5070
*/
5071
htinfo.pt.x = pt.x;
5072
htinfo.pt.y = pt.y;
5073
idx = TabCtrl_HitTest(s_tabhwnd, &htinfo);
5074
if (idx != -1)
5075
{
5076
tabpage_T *tp;
5077
5078
tp = find_tabpage(idx + 1);
5079
if (tp != NULL)
5080
{
5081
get_tabline_label(tp, TRUE);
5082
str = NameBuff;
5083
}
5084
}
5085
}
5086
}
5087
# endif
5088
# ifdef FEAT_TOOLBAR
5089
# ifdef FEAT_GUI_TABLINE
5090
else
5091
# endif
5092
{
5093
UINT idButton;
5094
vimmenu_T *pMenu;
5095
5096
idButton = (UINT) hdr->idFrom;
5097
pMenu = gui_mswin_find_menu(root_menu, idButton);
5098
if (pMenu)
5099
str = pMenu->strings[MENU_INDEX_TIP];
5100
}
5101
# endif
5102
if (str == NULL)
5103
break;
5104
5105
// Set the maximum width, this also enables using \n for
5106
// line break.
5107
SendMessage(hdr->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 500);
5108
5109
if (hdr->code == TTN_GETDISPINFOW)
5110
{
5111
LPNMTTDISPINFOW lpdi = (LPNMTTDISPINFOW)hdr;
5112
5113
tt_text = enc_to_utf16(str, NULL);
5114
lpdi->lpszText = tt_text;
5115
// can't show tooltip if failed
5116
}
5117
else
5118
{
5119
LPNMTTDISPINFO lpdi = (LPNMTTDISPINFO)hdr;
5120
size_t len = STRLEN(str);
5121
5122
if (len < sizeof(lpdi->szText)
5123
|| ((tt_text = vim_strnsave(str, len)) == NULL))
5124
vim_strncpy((char_u *)lpdi->szText, str,
5125
sizeof(lpdi->szText) - 1);
5126
else
5127
lpdi->lpszText = tt_text;
5128
}
5129
}
5130
break;
5131
5132
# ifdef FEAT_GUI_TABLINE
5133
case TCN_SELCHANGE:
5134
if (gui_mch_showing_tabline() && (hdr->hwndFrom == s_tabhwnd))
5135
{
5136
send_tabline_event(TabCtrl_GetCurSel(s_tabhwnd) + 1);
5137
return 0L;
5138
}
5139
break;
5140
5141
case NM_RCLICK:
5142
if (gui_mch_showing_tabline() && (hdr->hwndFrom == s_tabhwnd))
5143
{
5144
show_tabline_popup_menu();
5145
return 0L;
5146
}
5147
break;
5148
# endif
5149
5150
default:
5151
break;
5152
}
5153
return DefWindowProcW(hwnd, WM_NOTIFY, (WPARAM)id, (LPARAM)hdr);
5154
}
5155
#endif
5156
5157
#if defined(MENUHINTS) && defined(FEAT_MENU)
5158
static LRESULT
5159
_OnMenuSelect(HWND hwnd, WPARAM wParam, LPARAM lParam)
5160
{
5161
if (((UINT) HIWORD(wParam)
5162
& (0xffff ^ (MF_MOUSESELECT + MF_BITMAP + MF_POPUP)))
5163
== MF_HILITE
5164
&& (State & MODE_CMDLINE) == 0)
5165
{
5166
vimmenu_T *pMenu;
5167
static int did_menu_tip = FALSE;
5168
5169
if (did_menu_tip)
5170
{
5171
msg_clr_cmdline();
5172
setcursor();
5173
out_flush();
5174
did_menu_tip = FALSE;
5175
}
5176
5177
pMenu = gui_mswin_find_menu(root_menu, (UINT)LOWORD(wParam));
5178
if (pMenu != NULL && pMenu->strings[MENU_INDEX_TIP] != NULL)
5179
{
5180
MENUITEMINFO menuinfo;
5181
5182
menuinfo.cbSize = sizeof(MENUITEMINFO);
5183
menuinfo.fMask = MIIM_ID; // We only want to check if the menu item exists,
5184
// so retrieve something simple.
5185
if (GetMenuItemInfo(s_menuBar, pMenu->id, FALSE, &menuinfo))
5186
{
5187
++msg_hist_off;
5188
msg((char *)pMenu->strings[MENU_INDEX_TIP]);
5189
--msg_hist_off;
5190
setcursor();
5191
out_flush();
5192
did_menu_tip = TRUE;
5193
}
5194
}
5195
return 0L;
5196
}
5197
return DefWindowProcW(hwnd, WM_MENUSELECT, wParam, lParam);
5198
}
5199
#endif
5200
5201
static BOOL
5202
_OnGetDpiScaledSize(HWND hwnd UNUSED, UINT dpi, SIZE *size)
5203
{
5204
int old_width, old_height;
5205
int new_width, new_height;
5206
LOGFONTW lf;
5207
HFONT font;
5208
5209
//TRACE("DPI: %d, SIZE=(%d,%d), s_dpi: %d", dpi, size->cx, size->cy, s_dpi);
5210
5211
// Calculate new approximate size.
5212
GetFontSize(gui.norm_font, &old_width, &old_height); // Current size
5213
GetObjectW((HFONT)gui.norm_font, sizeof(lf), &lf);
5214
lf.lfHeight = lf.lfHeight * (int)dpi / s_dpi;
5215
font = CreateFontIndirectW(&lf);
5216
if (font)
5217
{
5218
GetFontSize((GuiFont)font, &new_width, &new_height); // New size
5219
DeleteFont(font);
5220
}
5221
else
5222
{
5223
new_width = old_width;
5224
new_height = old_height;
5225
}
5226
size->cx = size->cx * new_width / old_width;
5227
size->cy = size->cy * new_height / old_height;
5228
//TRACE("New approx. SIZE=(%d,%d)", size->cx, size->cy);
5229
5230
return TRUE;
5231
}
5232
5233
static LRESULT
5234
_OnDpiChanged(HWND hwnd, UINT xdpi UNUSED, UINT ydpi, RECT *rc)
5235
{
5236
s_dpi = ydpi;
5237
s_in_dpichanged = TRUE;
5238
//TRACE("DPI: %d", ydpi);
5239
5240
s_suggested_rect = *rc;
5241
//TRACE("Suggested pos&size: %d,%d %d,%d", rc->left, rc->top,
5242
// rc->right - rc->left, rc->bottom - rc->top);
5243
5244
update_scrollbar_size();
5245
update_toolbar_size();
5246
set_tabline_font();
5247
5248
gui_init_font(*p_guifont == NUL ? hl_get_font_name() : p_guifont, FALSE);
5249
gui_get_wide_font();
5250
gui_mswin_get_menu_height(FALSE);
5251
#ifdef FEAT_MBYTE_IME
5252
im_set_position(gui.row, gui.col);
5253
#endif
5254
InvalidateRect(hwnd, NULL, TRUE);
5255
5256
s_in_dpichanged = FALSE;
5257
return 0L;
5258
}
5259
5260
5261
static LRESULT CALLBACK
5262
_WndProc(
5263
HWND hwnd,
5264
UINT uMsg,
5265
WPARAM wParam,
5266
LPARAM lParam)
5267
{
5268
// ch_log(NULL, "WndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x",
5269
// hwnd, uMsg, wParam, lParam);
5270
5271
HandleMouseHide(uMsg, lParam);
5272
5273
s_uMsg = uMsg;
5274
s_wParam = wParam;
5275
s_lParam = lParam;
5276
5277
switch (uMsg)
5278
{
5279
HANDLE_MSG(hwnd, WM_DEADCHAR, _OnDeadChar);
5280
HANDLE_MSG(hwnd, WM_SYSDEADCHAR, _OnDeadChar);
5281
// HANDLE_MSG(hwnd, WM_ACTIVATE, _OnActivate);
5282
HANDLE_MSG(hwnd, WM_CLOSE, _OnClose);
5283
// HANDLE_MSG(hwnd, WM_COMMAND, _OnCommand);
5284
HANDLE_MSG(hwnd, WM_DESTROY, _OnDestroy);
5285
HANDLE_MSG(hwnd, WM_DROPFILES, _OnDropFiles);
5286
HANDLE_MSG(hwnd, WM_HSCROLL, _OnScroll);
5287
HANDLE_MSG(hwnd, WM_KILLFOCUS, _OnKillFocus);
5288
#ifdef FEAT_MENU
5289
HANDLE_MSG(hwnd, WM_COMMAND, _OnMenu);
5290
#endif
5291
// HANDLE_MSG(hwnd, WM_MOVE, _OnMove);
5292
// HANDLE_MSG(hwnd, WM_NCACTIVATE, _OnNCActivate);
5293
HANDLE_MSG(hwnd, WM_SETFOCUS, _OnSetFocus);
5294
HANDLE_MSG(hwnd, WM_SIZE, _OnSize);
5295
// HANDLE_MSG(hwnd, WM_SYSCOMMAND, _OnSysCommand);
5296
// HANDLE_MSG(hwnd, WM_SYSKEYDOWN, _OnAltKey);
5297
HANDLE_MSG(hwnd, WM_VSCROLL, _OnScroll);
5298
// HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING, _OnWindowPosChanging);
5299
HANDLE_MSG(hwnd, WM_ACTIVATEAPP, _OnActivateApp);
5300
#ifdef FEAT_NETBEANS_INTG
5301
HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, _OnWindowPosChanged);
5302
#endif
5303
#ifdef FEAT_GUI_TABLINE
5304
HANDLE_MSG(hwnd, WM_RBUTTONUP, _OnRButtonUp);
5305
HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK, _OnLButtonDown);
5306
#endif
5307
HANDLE_MSG(hwnd, WM_NCHITTEST, _OnNCHitTest);
5308
5309
case WM_QUERYENDSESSION: // System wants to go down.
5310
gui_shell_closed(); // Will exit when no changed buffers.
5311
return FALSE; // Do NOT allow system to go down.
5312
5313
case WM_ENDSESSION:
5314
if (wParam) // system only really goes down when wParam is TRUE
5315
{
5316
_OnEndSession();
5317
return 0L;
5318
}
5319
break;
5320
5321
case WM_CHAR:
5322
// Don't use HANDLE_MSG() for WM_CHAR, it truncates wParam to a single
5323
// byte while we want the UTF-16 character value.
5324
_OnChar(hwnd, (UINT)wParam, (int)(short)LOWORD(lParam));
5325
return 0L;
5326
5327
case WM_SYSCHAR:
5328
/*
5329
* if 'winaltkeys' is "no", or it's "menu" and it's not a menu
5330
* shortcut key, handle like a typed ALT key, otherwise call Windows
5331
* ALT key handling.
5332
*/
5333
#ifdef FEAT_MENU
5334
if ( !gui.menu_is_active
5335
|| p_wak[0] == 'n'
5336
|| (p_wak[0] == 'm' && !gui_is_menu_shortcut((int)wParam))
5337
)
5338
#endif
5339
{
5340
_OnSysChar(hwnd, (UINT)wParam, (int)(short)LOWORD(lParam));
5341
return 0L;
5342
}
5343
#ifdef FEAT_MENU
5344
else
5345
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
5346
#endif
5347
5348
case WM_SYSKEYUP:
5349
#ifdef FEAT_MENU
5350
// This used to be done only when menu is active: ALT key is used for
5351
// that. But that caused problems when menu is disabled and using
5352
// Alt-Tab-Esc: get into a strange state where no mouse-moved events
5353
// are received, mouse pointer remains hidden.
5354
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
5355
#else
5356
return 0L;
5357
#endif
5358
5359
case WM_EXITSIZEMOVE:
5360
destroy_sizing_tip();
5361
break;
5362
5363
case WM_SIZING: // HANDLE_MSG doesn't seem to handle this one
5364
return _DuringSizing((UINT)wParam, (LPRECT)lParam);
5365
5366
case WM_MOUSEWHEEL:
5367
case WM_MOUSEHWHEEL:
5368
_OnMouseWheel(hwnd, wParam, lParam, uMsg == WM_MOUSEHWHEEL);
5369
return 0L;
5370
5371
// Notification for change in SystemParametersInfo()
5372
case WM_SETTINGCHANGE:
5373
return _OnSettingChange((UINT)wParam);
5374
5375
#if defined(FEAT_TOOLBAR) || defined(FEAT_GUI_TABLINE)
5376
case WM_NOTIFY:
5377
return _OnNotify(hwnd, (UINT)wParam, (NMHDR*)lParam);
5378
#endif
5379
5380
#if defined(MENUHINTS) && defined(FEAT_MENU)
5381
case WM_MENUSELECT:
5382
return _OnMenuSelect(hwnd, wParam, lParam);
5383
#endif
5384
5385
#ifdef FEAT_MBYTE_IME
5386
case WM_IME_NOTIFY:
5387
if (!_OnImeNotify(hwnd, (DWORD)wParam, (DWORD)lParam))
5388
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
5389
return 1L;
5390
5391
case WM_IME_COMPOSITION:
5392
if (!_OnImeComposition(hwnd, wParam, lParam))
5393
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
5394
return 1L;
5395
#endif
5396
case WM_GETDPISCALEDSIZE:
5397
return _OnGetDpiScaledSize(hwnd, (UINT)wParam, (SIZE *)lParam);
5398
case WM_DPICHANGED:
5399
return _OnDpiChanged(hwnd, (UINT)LOWORD(wParam), (UINT)HIWORD(wParam),
5400
(RECT*)lParam);
5401
5402
default:
5403
#ifdef MSWIN_FIND_REPLACE
5404
if (uMsg == s_findrep_msg && s_findrep_msg != 0)
5405
_OnFindRepl();
5406
#endif
5407
break;
5408
}
5409
5410
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
5411
}
5412
5413
/*
5414
* End of call-back routines
5415
*/
5416
5417
// parent window, if specified with -P
5418
HWND vim_parent_hwnd = NULL;
5419
5420
static BOOL CALLBACK
5421
FindWindowTitle(HWND hwnd, LPARAM lParam)
5422
{
5423
char buf[2048];
5424
char *title = (char *)lParam;
5425
5426
if (GetWindowText(hwnd, buf, sizeof(buf)))
5427
{
5428
if (strstr(buf, title) != NULL)
5429
{
5430
// Found it. Store the window ref. and quit searching if MDI
5431
// works.
5432
vim_parent_hwnd = FindWindowEx(hwnd, NULL, "MDIClient", NULL);
5433
if (vim_parent_hwnd != NULL)
5434
return FALSE;
5435
}
5436
}
5437
return TRUE; // continue searching
5438
}
5439
5440
/*
5441
* Invoked for '-P "title"' argument: search for parent application to open
5442
* our window in.
5443
*/
5444
void
5445
gui_mch_set_parent(char *title)
5446
{
5447
EnumWindows(FindWindowTitle, (LPARAM)title);
5448
if (vim_parent_hwnd == NULL)
5449
{
5450
semsg(_(e_cannot_find_window_title_str), title);
5451
mch_exit(2);
5452
}
5453
}
5454
5455
#ifndef FEAT_OLE
5456
static void
5457
ole_error(char *arg)
5458
{
5459
char buf[IOSIZE];
5460
5461
# ifdef VIMDLL
5462
gui.in_use = mch_is_gui_executable();
5463
# endif
5464
5465
// Can't use emsg() here, we have not finished initialisation yet.
5466
vim_snprintf(buf, IOSIZE,
5467
_(e_argument_not_supported_str_use_ole_version), arg);
5468
mch_errmsg(buf);
5469
}
5470
#endif
5471
5472
#if defined(GUI_MAY_SPAWN)
5473
static char *
5474
gvim_error(void)
5475
{
5476
char *msg = _(e_gui_cannot_be_used_cannot_execute_gvim_exe);
5477
5478
if (starting)
5479
{
5480
mch_errmsg(msg);
5481
mch_errmsg("\n");
5482
mch_exit(2);
5483
}
5484
return msg;
5485
}
5486
5487
char *
5488
gui_mch_do_spawn(char_u *arg)
5489
{
5490
int len;
5491
# if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD)
5492
char_u *session = NULL;
5493
LPWSTR tofree1 = NULL;
5494
# endif
5495
WCHAR name[MAX_PATH];
5496
LPWSTR cmd, newcmd = NULL, p, warg, tofree2 = NULL;
5497
STARTUPINFOW si = {sizeof(si)};
5498
PROCESS_INFORMATION pi;
5499
5500
if (!GetModuleFileNameW(g_hinst, name, MAX_PATH))
5501
goto error;
5502
p = wcsrchr(name, L'\\');
5503
if (p == NULL)
5504
goto error;
5505
// Replace the executable name from vim(d).exe to gvim(d).exe.
5506
# ifdef DEBUG
5507
wcscpy(p + 1, L"gvimd.exe");
5508
# else
5509
wcscpy(p + 1, L"gvim.exe");
5510
# endif
5511
5512
# if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD)
5513
if (starting)
5514
# endif
5515
{
5516
// Pass the command line to the new process.
5517
p = GetCommandLineW();
5518
// Skip 1st argument.
5519
while (*p && *p != L' ' && *p != L'\t')
5520
{
5521
if (*p == L'"')
5522
{
5523
// Skip quoted strings
5524
while (*++p && *p != L'"');
5525
}
5526
5527
++p;
5528
}
5529
cmd = p;
5530
}
5531
# if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD)
5532
else
5533
{
5534
// Create a session file and pass it to the new process.
5535
LPWSTR wsession;
5536
char_u *savebg;
5537
int ret;
5538
5539
session = vim_tempname('s', FALSE);
5540
if (session == NULL)
5541
goto error;
5542
savebg = p_bg;
5543
p_bg = vim_strnsave((char_u *)"light", 5); // Set 'bg' to "light".
5544
if (p_bg == NULL)
5545
{
5546
p_bg = savebg;
5547
goto error;
5548
}
5549
ret = write_session_file(session);
5550
vim_free(p_bg);
5551
p_bg = savebg;
5552
if (!ret)
5553
goto error;
5554
wsession = enc_to_utf16(session, NULL);
5555
if (wsession == NULL)
5556
goto error;
5557
len = (int)wcslen(wsession) * 2 + 27 + 1;
5558
cmd = ALLOC_MULT(WCHAR, len);
5559
if (cmd == NULL)
5560
{
5561
vim_free(wsession);
5562
goto error;
5563
}
5564
tofree1 = cmd;
5565
_snwprintf(cmd, len, L" -S \"%s\" -c \"call delete('%s')\"",
5566
wsession, wsession);
5567
vim_free(wsession);
5568
}
5569
# endif
5570
5571
// Check additional arguments to the `:gui` command.
5572
if (arg != NULL)
5573
{
5574
warg = enc_to_utf16(arg, NULL);
5575
if (warg == NULL)
5576
goto error;
5577
tofree2 = warg;
5578
}
5579
else
5580
warg = L"";
5581
5582
// Set up the new command line.
5583
len = (int)wcslen(name) + (int)wcslen(cmd) + (int)wcslen(warg) + 4;
5584
newcmd = ALLOC_MULT(WCHAR, len);
5585
if (newcmd == NULL)
5586
goto error;
5587
_snwprintf(newcmd, len, L"\"%s\"%s %s", name, cmd, warg);
5588
5589
// Spawn a new GUI process.
5590
if (!CreateProcessW(NULL, newcmd, NULL, NULL, TRUE, 0,
5591
NULL, NULL, &si, &pi))
5592
goto error;
5593
CloseHandle(pi.hProcess);
5594
CloseHandle(pi.hThread);
5595
mch_exit(0);
5596
5597
error:
5598
# if defined(FEAT_SESSION) && defined(EXPERIMENTAL_GUI_CMD)
5599
if (session)
5600
mch_remove(session);
5601
vim_free(session);
5602
vim_free(tofree1);
5603
# endif
5604
vim_free(newcmd);
5605
vim_free(tofree2);
5606
return gvim_error();
5607
}
5608
#endif
5609
5610
/*
5611
* Parse the GUI related command-line arguments. Any arguments used are
5612
* deleted from argv, and *argc is decremented accordingly. This is called
5613
* when Vim is started, whether or not the GUI has been started.
5614
*/
5615
void
5616
gui_mch_prepare(int *argc, char **argv)
5617
{
5618
int silent = FALSE;
5619
int idx;
5620
5621
// Check for special OLE command line parameters
5622
if ((*argc == 2 || *argc == 3) && (argv[1][0] == '-' || argv[1][0] == '/'))
5623
{
5624
// Check for a "-silent" argument first.
5625
if (*argc == 3 && STRICMP(argv[1] + 1, "silent") == 0
5626
&& (argv[2][0] == '-' || argv[2][0] == '/'))
5627
{
5628
silent = TRUE;
5629
idx = 2;
5630
}
5631
else
5632
idx = 1;
5633
5634
// Register Vim as an OLE Automation server
5635
if (STRICMP(argv[idx] + 1, "register") == 0)
5636
{
5637
#ifdef FEAT_OLE
5638
RegisterMe(silent);
5639
mch_exit(0);
5640
#else
5641
if (!silent)
5642
ole_error("register");
5643
mch_exit(2);
5644
#endif
5645
}
5646
5647
// Unregister Vim as an OLE Automation server
5648
if (STRICMP(argv[idx] + 1, "unregister") == 0)
5649
{
5650
#ifdef FEAT_OLE
5651
UnregisterMe(!silent);
5652
mch_exit(0);
5653
#else
5654
if (!silent)
5655
ole_error("unregister");
5656
mch_exit(2);
5657
#endif
5658
}
5659
5660
// Ignore an -embedding argument. It is only relevant if the
5661
// application wants to treat the case when it is started manually
5662
// differently from the case where it is started via automation (and
5663
// we don't).
5664
if (STRICMP(argv[idx] + 1, "embedding") == 0)
5665
{
5666
#ifdef FEAT_OLE
5667
*argc = 1;
5668
#else
5669
ole_error("embedding");
5670
mch_exit(2);
5671
#endif
5672
}
5673
}
5674
5675
#ifdef FEAT_OLE
5676
# ifdef VIMDLL
5677
if (mch_is_gui_executable())
5678
# endif
5679
{
5680
int bDoRestart = FALSE;
5681
5682
InitOLE(&bDoRestart);
5683
// automatically exit after registering
5684
if (bDoRestart)
5685
mch_exit(0);
5686
}
5687
#endif
5688
5689
#ifdef FEAT_NETBEANS_INTG
5690
{
5691
// stolen from gui_x11.c
5692
int arg;
5693
5694
for (arg = 1; arg < *argc; arg++)
5695
if (strncmp("-nb", argv[arg], 3) == 0)
5696
{
5697
netbeansArg = argv[arg];
5698
mch_memmove(&argv[arg], &argv[arg + 1],
5699
(--*argc - arg) * sizeof(char *));
5700
argv[*argc] = NULL;
5701
break; // enough?
5702
}
5703
}
5704
#endif
5705
}
5706
5707
static void
5708
load_dpi_func(void)
5709
{
5710
HMODULE hUser32;
5711
5712
hUser32 = GetModuleHandle("user32.dll");
5713
if (hUser32 == NULL)
5714
goto fail;
5715
5716
pGetDpiForSystem = (UINT (WINAPI *)(void))GetProcAddress(hUser32, "GetDpiForSystem");
5717
pGetDpiForWindow = (UINT (WINAPI *)(HWND))GetProcAddress(hUser32, "GetDpiForWindow");
5718
pGetSystemMetricsForDpi = (int (WINAPI *)(int, UINT))GetProcAddress(hUser32, "GetSystemMetricsForDpi");
5719
//pGetWindowDpiAwarenessContext = (void*)GetProcAddress(hUser32, "GetWindowDpiAwarenessContext");
5720
pSetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(DPI_AWARENESS_CONTEXT))GetProcAddress(hUser32, "SetThreadDpiAwarenessContext");
5721
pGetAwarenessFromDpiAwarenessContext = (DPI_AWARENESS (WINAPI *)(DPI_AWARENESS_CONTEXT))GetProcAddress(hUser32, "GetAwarenessFromDpiAwarenessContext");
5722
5723
if (pSetThreadDpiAwarenessContext != NULL)
5724
{
5725
DPI_AWARENESS_CONTEXT oldctx = pSetThreadDpiAwarenessContext(
5726
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
5727
if (oldctx != NULL)
5728
{
5729
TRACE("DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 enabled");
5730
s_process_dpi_aware = pGetAwarenessFromDpiAwarenessContext(oldctx);
5731
#ifdef DEBUG
5732
if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE)
5733
{
5734
TRACE("WARNING: PerMonitorV2 is not enabled in the process level for some reasons. IME window may not shown correctly.");
5735
}
5736
#endif
5737
return;
5738
}
5739
}
5740
5741
fail:
5742
// Disable PerMonitorV2 APIs.
5743
pGetDpiForSystem = vimGetDpiForSystem;
5744
pGetDpiForWindow = NULL;
5745
pGetSystemMetricsForDpi = stubGetSystemMetricsForDpi;
5746
pSetThreadDpiAwarenessContext = NULL;
5747
pGetAwarenessFromDpiAwarenessContext = NULL;
5748
}
5749
5750
/*
5751
* Initialise the GUI. Create all the windows, set up all the call-backs
5752
* etc.
5753
*/
5754
int
5755
gui_mch_init(void)
5756
{
5757
const WCHAR szVimWndClassW[] = VIM_CLASSW;
5758
const WCHAR szTextAreaClassW[] = L"VimTextArea";
5759
WNDCLASSW wndclassw;
5760
5761
// Return here if the window was already opened (happens when
5762
// gui_mch_dialog() is called early).
5763
if (s_hwnd != NULL)
5764
goto theend;
5765
5766
/*
5767
* Load the tearoff bitmap
5768
*/
5769
#ifdef FEAT_TEAROFF
5770
s_htearbitmap = LoadBitmap(g_hinst, "IDB_TEAROFF");
5771
#endif
5772
5773
load_dpi_func();
5774
5775
#ifdef FEAT_GUI_DARKTHEME
5776
dyn_uxtheme_load();
5777
#endif
5778
5779
dyn_dwm_load();
5780
5781
s_dpi = pGetDpiForSystem();
5782
update_scrollbar_size();
5783
5784
#ifdef FEAT_MENU
5785
gui.menu_height = 0; // Windows takes care of this
5786
#endif
5787
gui.border_width = 0;
5788
#ifdef FEAT_TOOLBAR
5789
gui.toolbar_height = TOOLBAR_BUTTON_HEIGHT + TOOLBAR_BORDER_HEIGHT;
5790
#endif
5791
5792
s_brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
5793
5794
// First try using the wide version, so that we can use any title.
5795
// Otherwise only characters in the active codepage will work.
5796
if (GetClassInfoW(g_hinst, szVimWndClassW, &wndclassw) == 0)
5797
{
5798
wndclassw.style = CS_DBLCLKS;
5799
wndclassw.lpfnWndProc = _WndProc;
5800
wndclassw.cbClsExtra = 0;
5801
wndclassw.cbWndExtra = 0;
5802
wndclassw.hInstance = g_hinst;
5803
wndclassw.hIcon = LoadIcon(wndclassw.hInstance, "IDR_VIM");
5804
wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW);
5805
wndclassw.hbrBackground = s_brush;
5806
wndclassw.lpszMenuName = NULL;
5807
wndclassw.lpszClassName = szVimWndClassW;
5808
5809
if (RegisterClassW(&wndclassw) == 0)
5810
return FAIL;
5811
}
5812
5813
if (vim_parent_hwnd != NULL)
5814
{
5815
#ifdef HAVE_TRY_EXCEPT
5816
__try
5817
{
5818
#endif
5819
// Open inside the specified parent window.
5820
// TODO: last argument should point to a CLIENTCREATESTRUCT
5821
// structure.
5822
s_hwnd = CreateWindowExW(
5823
WS_EX_MDICHILD,
5824
szVimWndClassW, L"Vim MSWindows GUI",
5825
WS_OVERLAPPEDWINDOW | WS_CHILD
5826
| WS_CLIPSIBLINGS | WS_CLIPCHILDREN | 0xC000,
5827
gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x,
5828
gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y,
5829
100, // Any value will do
5830
100, // Any value will do
5831
vim_parent_hwnd, NULL,
5832
g_hinst, NULL);
5833
#ifdef HAVE_TRY_EXCEPT
5834
}
5835
__except(EXCEPTION_EXECUTE_HANDLER)
5836
{
5837
// NOP
5838
}
5839
#endif
5840
if (s_hwnd == NULL)
5841
{
5842
emsg(_(e_unable_to_open_window_inside_mdi_application));
5843
mch_exit(2);
5844
}
5845
}
5846
else
5847
{
5848
// If the provided windowid is not valid reset it to zero, so that it
5849
// is ignored and we open our own window.
5850
if (IsWindow((HWND)win_socket_id) <= 0)
5851
win_socket_id = 0;
5852
5853
// Create a window. If win_socket_id is not zero without border and
5854
// titlebar, it will be reparented below.
5855
s_hwnd = CreateWindowW(
5856
szVimWndClassW, L"Vim MSWindows GUI",
5857
(win_socket_id == 0 ? WS_OVERLAPPEDWINDOW : WS_POPUP)
5858
| WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
5859
gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x,
5860
gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y,
5861
100, // Any value will do
5862
100, // Any value will do
5863
NULL, NULL,
5864
g_hinst, NULL);
5865
if (s_hwnd != NULL && win_socket_id != 0)
5866
{
5867
SetParent(s_hwnd, (HWND)win_socket_id);
5868
ShowWindow(s_hwnd, SW_SHOWMAXIMIZED);
5869
}
5870
}
5871
5872
if (s_hwnd == NULL)
5873
return FAIL;
5874
5875
if (pGetDpiForWindow != NULL)
5876
{
5877
s_dpi = pGetDpiForWindow(s_hwnd);
5878
update_scrollbar_size();
5879
//TRACE("System DPI: %d, DPI: %d", pGetDpiForSystem(), s_dpi);
5880
}
5881
5882
#if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME)
5883
dyn_imm_load();
5884
#endif
5885
5886
// Create the text area window
5887
if (GetClassInfoW(g_hinst, szTextAreaClassW, &wndclassw) == 0)
5888
{
5889
wndclassw.style = CS_OWNDC;
5890
wndclassw.lpfnWndProc = _TextAreaWndProc;
5891
wndclassw.cbClsExtra = 0;
5892
wndclassw.cbWndExtra = 0;
5893
wndclassw.hInstance = g_hinst;
5894
wndclassw.hIcon = NULL;
5895
wndclassw.hCursor = LoadCursor(NULL, IDC_ARROW);
5896
wndclassw.hbrBackground = NULL;
5897
wndclassw.lpszMenuName = NULL;
5898
wndclassw.lpszClassName = szTextAreaClassW;
5899
5900
if (RegisterClassW(&wndclassw) == 0)
5901
return FAIL;
5902
}
5903
5904
s_textArea = CreateWindowExW(
5905
0,
5906
szTextAreaClassW, L"Vim text area",
5907
WS_CHILD | WS_VISIBLE, 0, 0,
5908
100, // Any value will do for now
5909
100, // Any value will do for now
5910
s_hwnd, NULL,
5911
g_hinst, NULL);
5912
5913
if (s_textArea == NULL)
5914
return FAIL;
5915
5916
#ifdef FEAT_LIBCALL
5917
// Try loading an icon from $RUNTIMEPATH/bitmaps/vim.ico.
5918
{
5919
HANDLE hIcon = NULL;
5920
5921
if (mch_icon_load(&hIcon) == OK && hIcon != NULL)
5922
SendMessage(s_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
5923
}
5924
#endif
5925
5926
#ifdef FEAT_MENU
5927
s_menuBar = CreateMenu();
5928
#endif
5929
s_hdc = GetDC(s_textArea);
5930
5931
DragAcceptFiles(s_hwnd, TRUE);
5932
5933
// Do we need to bother with this?
5934
// m_fMouseAvail = pGetSystemMetricsForDpi(SM_MOUSEPRESENT, s_dpi);
5935
5936
// Get background/foreground colors from the system
5937
gui_mch_def_colors();
5938
5939
// Get the colors from the "Normal" group (set in syntax.c or in a vimrc
5940
// file)
5941
set_normal_colors();
5942
5943
/*
5944
* Check that none of the colors are the same as the background color.
5945
* Then store the current values as the defaults.
5946
*/
5947
gui_check_colors();
5948
gui.def_norm_pixel = gui.norm_pixel;
5949
gui.def_back_pixel = gui.back_pixel;
5950
5951
// Get the colors for the highlight groups (gui_check_colors() might have
5952
// changed them)
5953
highlight_gui_started();
5954
5955
/*
5956
* Start out by adding the configured border width into the border offset.
5957
*/
5958
gui.border_offset = gui.border_width;
5959
5960
/*
5961
* Set up for Intellimouse processing
5962
*/
5963
init_mouse_wheel();
5964
5965
/*
5966
* compute a couple of metrics used for the dialogs
5967
*/
5968
get_dialog_font_metrics();
5969
#ifdef FEAT_TOOLBAR
5970
/*
5971
* Create the toolbar
5972
*/
5973
initialise_toolbar();
5974
#endif
5975
#ifdef FEAT_GUI_TABLINE
5976
/*
5977
* Create the tabline
5978
*/
5979
initialise_tabline();
5980
#endif
5981
#ifdef MSWIN_FIND_REPLACE
5982
/*
5983
* Initialise the dialog box stuff
5984
*/
5985
s_findrep_msg = RegisterWindowMessage(FINDMSGSTRING);
5986
5987
// Initialise the struct
5988
s_findrep_struct.lStructSize = sizeof(s_findrep_struct);
5989
s_findrep_struct.lpstrFindWhat = ALLOC_MULT(WCHAR, MSWIN_FR_BUFSIZE);
5990
s_findrep_struct.lpstrFindWhat[0] = NUL;
5991
s_findrep_struct.lpstrReplaceWith = ALLOC_MULT(WCHAR, MSWIN_FR_BUFSIZE);
5992
s_findrep_struct.lpstrReplaceWith[0] = NUL;
5993
s_findrep_struct.wFindWhatLen = MSWIN_FR_BUFSIZE;
5994
s_findrep_struct.wReplaceWithLen = MSWIN_FR_BUFSIZE;
5995
#endif
5996
5997
#ifdef FEAT_EVAL
5998
// set the v:windowid variable
5999
set_vim_var_nr(VV_WINDOWID, HandleToLong(s_hwnd));
6000
#endif
6001
6002
#ifdef FEAT_RENDER_OPTIONS
6003
if (p_rop)
6004
(void)gui_mch_set_rendering_options(p_rop);
6005
#endif
6006
6007
theend:
6008
// Display any pending error messages
6009
display_errors();
6010
6011
return OK;
6012
}
6013
6014
/*
6015
* Get the size of the screen, taking position on multiple monitors into
6016
* account (if supported).
6017
*/
6018
static void
6019
get_work_area(RECT *spi_rect)
6020
{
6021
HMONITOR mon;
6022
MONITORINFO moninfo;
6023
6024
// work out which monitor the window is on, and get *its* work area
6025
mon = MonitorFromWindow(s_hwnd, MONITOR_DEFAULTTOPRIMARY);
6026
if (mon != NULL)
6027
{
6028
moninfo.cbSize = sizeof(MONITORINFO);
6029
if (GetMonitorInfo(mon, &moninfo))
6030
{
6031
*spi_rect = moninfo.rcWork;
6032
return;
6033
}
6034
}
6035
// this is the old method...
6036
SystemParametersInfo(SPI_GETWORKAREA, 0, spi_rect, 0);
6037
}
6038
6039
/*
6040
* Set the size of the window to the given width and height in pixels.
6041
*/
6042
void
6043
gui_mch_set_shellsize(
6044
int width,
6045
int height,
6046
int min_width UNUSED,
6047
int min_height UNUSED,
6048
int base_width UNUSED,
6049
int base_height UNUSED,
6050
int direction)
6051
{
6052
RECT workarea_rect;
6053
RECT window_rect;
6054
int win_width, win_height;
6055
6056
// Try to keep window completely on screen.
6057
// Get position of the screen work area. This is the part that is not
6058
// used by the taskbar or appbars.
6059
get_work_area(&workarea_rect);
6060
6061
// Resizing a maximized window looks very strange, unzoom it first.
6062
// But don't do it when still starting up, it may have been requested in
6063
// the shortcut.
6064
if (IsZoomed(s_hwnd) && starting == 0)
6065
ShowWindow(s_hwnd, SW_SHOWNORMAL);
6066
6067
if (s_in_dpichanged)
6068
// Use the suggested position when in WM_DPICHANGED.
6069
window_rect = s_suggested_rect;
6070
else
6071
// Use current position.
6072
GetWindowRect(s_hwnd, &window_rect);
6073
6074
// compute the size of the outside of the window
6075
win_width = width + (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) +
6076
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2;
6077
win_height = height + (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) +
6078
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2
6079
+ pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi)
6080
+ gui_mswin_get_menu_height(FALSE);
6081
6082
// The following should take care of keeping Vim on the same monitor, no
6083
// matter if the secondary monitor is left or right of the primary
6084
// monitor.
6085
window_rect.right = window_rect.left + win_width;
6086
window_rect.bottom = window_rect.top + win_height;
6087
6088
// If the window is going off the screen, move it on to the screen.
6089
// Don't adjust the position when in WM_DPICHANGED.
6090
if (!s_in_dpichanged)
6091
{
6092
if ((direction & RESIZE_HOR)
6093
&& window_rect.right > workarea_rect.right)
6094
OffsetRect(&window_rect,
6095
workarea_rect.right - window_rect.right, 0);
6096
6097
if ((direction & RESIZE_HOR)
6098
&& window_rect.left < workarea_rect.left)
6099
OffsetRect(&window_rect,
6100
workarea_rect.left - window_rect.left, 0);
6101
6102
if ((direction & RESIZE_VERT)
6103
&& window_rect.bottom > workarea_rect.bottom)
6104
OffsetRect(&window_rect,
6105
0, workarea_rect.bottom - window_rect.bottom);
6106
6107
if ((direction & RESIZE_VERT)
6108
&& window_rect.top < workarea_rect.top)
6109
OffsetRect(&window_rect,
6110
0, workarea_rect.top - window_rect.top);
6111
}
6112
6113
MoveWindow(s_hwnd, window_rect.left, window_rect.top,
6114
win_width, win_height, TRUE);
6115
6116
//TRACE("New pos: %d,%d New size: %d,%d",
6117
// window_rect.left, window_rect.top, win_width, win_height);
6118
6119
SetActiveWindow(s_hwnd);
6120
SetFocus(s_hwnd);
6121
6122
// Menu may wrap differently now
6123
gui_mswin_get_menu_height(!gui.starting);
6124
}
6125
6126
6127
void
6128
gui_mch_set_scrollbar_thumb(
6129
scrollbar_T *sb,
6130
long val,
6131
long size,
6132
long max)
6133
{
6134
SCROLLINFO info;
6135
6136
sb->scroll_shift = 0;
6137
while (max > 32767)
6138
{
6139
max = (max + 1) >> 1;
6140
val >>= 1;
6141
size >>= 1;
6142
++sb->scroll_shift;
6143
}
6144
6145
if (sb->scroll_shift > 0)
6146
++size;
6147
6148
info.cbSize = sizeof(info);
6149
info.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
6150
info.nPos = val;
6151
info.nMin = 0;
6152
info.nMax = max;
6153
info.nPage = size;
6154
SetScrollInfo(sb->id, SB_CTL, &info, TRUE);
6155
}
6156
6157
6158
/*
6159
* Set the current text font.
6160
*/
6161
void
6162
gui_mch_set_font(GuiFont font)
6163
{
6164
gui.currFont = font;
6165
}
6166
6167
6168
/*
6169
* Set the current text foreground color.
6170
*/
6171
void
6172
gui_mch_set_fg_color(guicolor_T color)
6173
{
6174
gui.currFgColor = color;
6175
}
6176
6177
/*
6178
* Set the current text background color.
6179
*/
6180
void
6181
gui_mch_set_bg_color(guicolor_T color)
6182
{
6183
gui.currBgColor = color;
6184
}
6185
6186
/*
6187
* Set the current text special color.
6188
*/
6189
void
6190
gui_mch_set_sp_color(guicolor_T color)
6191
{
6192
gui.currSpColor = color;
6193
}
6194
6195
#ifdef FEAT_MBYTE_IME
6196
/*
6197
* Multi-byte handling, originally by Sung-Hoon Baek.
6198
* First static functions (no prototypes generated).
6199
*/
6200
# ifdef _MSC_VER
6201
# include <ime.h> // Apparently not needed for Cygwin or MinGW.
6202
# endif
6203
# include <imm.h>
6204
6205
/*
6206
* handle WM_IME_NOTIFY message
6207
*/
6208
static LRESULT
6209
_OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData UNUSED)
6210
{
6211
LRESULT lResult = 0;
6212
HIMC hImc;
6213
6214
if (!pImmGetContext || (hImc = pImmGetContext(hWnd)) == (HIMC)0)
6215
return lResult;
6216
switch (dwCommand)
6217
{
6218
case IMN_SETOPENSTATUS:
6219
if (pImmGetOpenStatus(hImc))
6220
{
6221
LOGFONTW lf = norm_logfont;
6222
if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE)
6223
// Work around when PerMonitorV2 is not enabled in the process level.
6224
lf.lfHeight = lf.lfHeight * DEFAULT_DPI / s_dpi;
6225
pImmSetCompositionFontW(hImc, &lf);
6226
im_set_position(gui.row, gui.col);
6227
6228
// Disable langmap
6229
State &= ~MODE_LANGMAP;
6230
if (State & MODE_INSERT)
6231
{
6232
# if defined(FEAT_KEYMAP)
6233
// Unshown 'keymap' in status lines
6234
if (curbuf->b_p_iminsert == B_IMODE_LMAP)
6235
{
6236
// Save cursor position
6237
int old_row = gui.row;
6238
int old_col = gui.col;
6239
6240
// This must be called here before
6241
// status_redraw_curbuf(), otherwise the mode
6242
// message may appear in the wrong position.
6243
showmode();
6244
status_redraw_curbuf();
6245
update_screen(0);
6246
// Restore cursor position
6247
gui.row = old_row;
6248
gui.col = old_col;
6249
}
6250
# endif
6251
}
6252
}
6253
gui_update_cursor(TRUE, FALSE);
6254
gui_mch_flush();
6255
lResult = 0;
6256
break;
6257
}
6258
pImmReleaseContext(hWnd, hImc);
6259
return lResult;
6260
}
6261
6262
static LRESULT
6263
_OnImeComposition(HWND hwnd, WPARAM dbcs UNUSED, LPARAM param)
6264
{
6265
char_u *ret;
6266
int len;
6267
6268
if ((param & GCS_RESULTSTR) == 0) // Composition unfinished.
6269
return 0;
6270
6271
ret = GetResultStr(hwnd, GCS_RESULTSTR, &len);
6272
if (ret != NULL)
6273
{
6274
add_to_input_buf_csi(ret, len);
6275
vim_free(ret);
6276
return 1;
6277
}
6278
return 0;
6279
}
6280
6281
/*
6282
* void GetResultStr()
6283
*
6284
* This handles WM_IME_COMPOSITION with GCS_RESULTSTR flag on.
6285
* get complete composition string
6286
*/
6287
static char_u *
6288
GetResultStr(HWND hwnd, int GCS, int *lenp)
6289
{
6290
HIMC hIMC; // Input context handle.
6291
LONG ret;
6292
WCHAR *buf = NULL;
6293
char_u *convbuf = NULL;
6294
6295
if (!pImmGetContext || (hIMC = pImmGetContext(hwnd)) == (HIMC)0)
6296
return NULL;
6297
6298
// Get the length of the composition string.
6299
ret = pImmGetCompositionStringW(hIMC, GCS, NULL, 0);
6300
if (ret <= 0)
6301
return NULL;
6302
6303
// Allocate the requested buffer plus space for the NUL character.
6304
buf = alloc(ret + sizeof(WCHAR));
6305
if (buf == NULL)
6306
return NULL;
6307
6308
// Reads in the composition string.
6309
pImmGetCompositionStringW(hIMC, GCS, buf, ret);
6310
*lenp = ret / sizeof(WCHAR);
6311
6312
convbuf = utf16_to_enc(buf, lenp);
6313
pImmReleaseContext(hwnd, hIMC);
6314
vim_free(buf);
6315
return convbuf;
6316
}
6317
#endif
6318
6319
// For global functions we need prototypes.
6320
#if defined(FEAT_MBYTE_IME)
6321
6322
/*
6323
* set font to IM.
6324
*/
6325
void
6326
im_set_font(LOGFONTW *lf)
6327
{
6328
HIMC hImc;
6329
6330
if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0)
6331
{
6332
pImmSetCompositionFontW(hImc, lf);
6333
pImmReleaseContext(s_hwnd, hImc);
6334
}
6335
}
6336
6337
/*
6338
* Notify cursor position to IM.
6339
*/
6340
void
6341
im_set_position(int row, int col)
6342
{
6343
HIMC hImc;
6344
6345
if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0)
6346
{
6347
COMPOSITIONFORM cfs;
6348
6349
cfs.dwStyle = CFS_POINT;
6350
cfs.ptCurrentPos.x = FILL_X(col);
6351
cfs.ptCurrentPos.y = FILL_Y(row);
6352
MapWindowPoints(s_textArea, s_hwnd, &cfs.ptCurrentPos, 1);
6353
if (s_process_dpi_aware == DPI_AWARENESS_UNAWARE)
6354
{
6355
// Work around when PerMonitorV2 is not enabled in the process level.
6356
cfs.ptCurrentPos.x = cfs.ptCurrentPos.x * DEFAULT_DPI / s_dpi;
6357
cfs.ptCurrentPos.y = cfs.ptCurrentPos.y * DEFAULT_DPI / s_dpi;
6358
}
6359
pImmSetCompositionWindow(hImc, &cfs);
6360
6361
pImmReleaseContext(s_hwnd, hImc);
6362
}
6363
}
6364
6365
/*
6366
* Set IM status on ("active" is TRUE) or off ("active" is FALSE).
6367
*/
6368
void
6369
im_set_active(int active)
6370
{
6371
HIMC hImc;
6372
static HIMC hImcOld = (HIMC)0;
6373
6374
# ifdef VIMDLL
6375
if (!gui.in_use && !gui.starting)
6376
{
6377
mbyte_im_set_active(active);
6378
return;
6379
}
6380
# endif
6381
6382
if (!pImmGetContext) // if NULL imm32.dll wasn't loaded (yet)
6383
return;
6384
6385
if (p_imdisable)
6386
{
6387
if (hImcOld == (HIMC)0)
6388
{
6389
hImcOld = pImmGetContext(s_hwnd);
6390
if (hImcOld)
6391
pImmAssociateContext(s_hwnd, (HIMC)0);
6392
}
6393
active = FALSE;
6394
}
6395
else if (hImcOld != (HIMC)0)
6396
{
6397
pImmAssociateContext(s_hwnd, hImcOld);
6398
hImcOld = (HIMC)0;
6399
}
6400
6401
hImc = pImmGetContext(s_hwnd);
6402
if (!hImc)
6403
return;
6404
6405
/*
6406
* for Korean ime
6407
*/
6408
HKL hKL = GetKeyboardLayout(0);
6409
6410
if (LOWORD(hKL) == MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN))
6411
{
6412
static DWORD dwConversionSaved = 0, dwSentenceSaved = 0;
6413
static BOOL bSaved = FALSE;
6414
6415
if (active)
6416
{
6417
// if we have a saved conversion status, restore it
6418
if (bSaved)
6419
pImmSetConversionStatus(hImc, dwConversionSaved,
6420
dwSentenceSaved);
6421
bSaved = FALSE;
6422
}
6423
else
6424
{
6425
// save conversion status and disable korean
6426
if (pImmGetConversionStatus(hImc, &dwConversionSaved,
6427
&dwSentenceSaved))
6428
{
6429
bSaved = TRUE;
6430
pImmSetConversionStatus(hImc,
6431
dwConversionSaved & ~(IME_CMODE_NATIVE
6432
| IME_CMODE_FULLSHAPE),
6433
dwSentenceSaved);
6434
}
6435
}
6436
}
6437
6438
pImmSetOpenStatus(hImc, active);
6439
pImmReleaseContext(s_hwnd, hImc);
6440
}
6441
6442
/*
6443
* Get IM status. When IM is on, return not 0. Else return 0.
6444
*/
6445
int
6446
im_get_status(void)
6447
{
6448
int status = 0;
6449
HIMC hImc;
6450
6451
# ifdef VIMDLL
6452
if (!gui.in_use && !gui.starting)
6453
return mbyte_im_get_status();
6454
# endif
6455
6456
if (pImmGetContext && (hImc = pImmGetContext(s_hwnd)) != (HIMC)0)
6457
{
6458
status = pImmGetOpenStatus(hImc) ? 1 : 0;
6459
pImmReleaseContext(s_hwnd, hImc);
6460
}
6461
return status;
6462
}
6463
6464
#endif // FEAT_MBYTE_IME
6465
6466
6467
/*
6468
* Convert latin9 text "text[len]" to ucs-2 in "unicodebuf".
6469
*/
6470
static void
6471
latin9_to_ucs(char_u *text, int len, WCHAR *unicodebuf)
6472
{
6473
int c;
6474
6475
while (--len >= 0)
6476
{
6477
c = *text++;
6478
switch (c)
6479
{
6480
case 0xa4: c = 0x20ac; break; // euro
6481
case 0xa6: c = 0x0160; break; // S hat
6482
case 0xa8: c = 0x0161; break; // S -hat
6483
case 0xb4: c = 0x017d; break; // Z hat
6484
case 0xb8: c = 0x017e; break; // Z -hat
6485
case 0xbc: c = 0x0152; break; // OE
6486
case 0xbd: c = 0x0153; break; // oe
6487
case 0xbe: c = 0x0178; break; // Y
6488
}
6489
*unicodebuf++ = c;
6490
}
6491
}
6492
6493
#ifdef FEAT_RIGHTLEFT
6494
/*
6495
* What is this for? In the case where you are using Win98 or Win2K or later,
6496
* and you are using a Hebrew font (or Arabic!), Windows does you a favor and
6497
* reverses the string sent to the TextOut... family. This sucks, because we
6498
* go to a lot of effort to do the right thing, and there doesn't seem to be a
6499
* way to tell Windblows not to do this!
6500
*
6501
* The short of it is that this 'RevOut' only gets called if you are running
6502
* one of the new, "improved" MS OSes, and only if you are running in
6503
* 'rightleft' mode. It makes display take *slightly* longer, but not
6504
* noticeably so.
6505
*/
6506
static void
6507
RevOut( HDC hdc,
6508
int col,
6509
int row,
6510
UINT foptions,
6511
CONST RECT *pcliprect,
6512
LPCTSTR text,
6513
UINT len,
6514
CONST INT *padding)
6515
{
6516
int ix;
6517
6518
for (ix = 0; ix < (int)len; ++ix)
6519
ExtTextOut(hdc, col + TEXT_X(ix), row, foptions,
6520
pcliprect, text + ix, 1, padding);
6521
}
6522
#endif
6523
6524
static void
6525
draw_line(
6526
int x1,
6527
int y1,
6528
int x2,
6529
int y2,
6530
COLORREF color)
6531
{
6532
#if defined(FEAT_DIRECTX)
6533
if (IS_ENABLE_DIRECTX())
6534
DWriteContext_DrawLine(s_dwc, x1, y1, x2, y2, color);
6535
else
6536
#endif
6537
{
6538
HPEN hpen = CreatePen(PS_SOLID, 1, color);
6539
HPEN old_pen = SelectObject(s_hdc, hpen);
6540
MoveToEx(s_hdc, x1, y1, NULL);
6541
// Note: LineTo() excludes the last pixel in the line.
6542
LineTo(s_hdc, x2, y2);
6543
DeleteObject(SelectObject(s_hdc, old_pen));
6544
}
6545
}
6546
6547
static void
6548
set_pixel(
6549
int x,
6550
int y,
6551
COLORREF color)
6552
{
6553
#if defined(FEAT_DIRECTX)
6554
if (IS_ENABLE_DIRECTX())
6555
DWriteContext_SetPixel(s_dwc, x, y, color);
6556
else
6557
#endif
6558
SetPixel(s_hdc, x, y, color);
6559
}
6560
6561
static void
6562
fill_rect(
6563
const RECT *rcp,
6564
HBRUSH hbr,
6565
COLORREF color)
6566
{
6567
#if defined(FEAT_DIRECTX)
6568
if (IS_ENABLE_DIRECTX())
6569
DWriteContext_FillRect(s_dwc, rcp, color);
6570
else
6571
#endif
6572
{
6573
HBRUSH hbr2;
6574
6575
if (hbr == NULL)
6576
hbr2 = CreateSolidBrush(color);
6577
else
6578
hbr2 = hbr;
6579
FillRect(s_hdc, rcp, hbr2);
6580
if (hbr == NULL)
6581
DeleteBrush(hbr2);
6582
}
6583
}
6584
6585
void
6586
gui_mch_draw_string(
6587
int row,
6588
int col,
6589
char_u *text,
6590
int len,
6591
int flags)
6592
{
6593
static int *padding = NULL;
6594
static int pad_size = 0;
6595
const RECT *pcliprect = NULL;
6596
UINT foptions = 0;
6597
static WCHAR *unicodebuf = NULL;
6598
static int *unicodepdy = NULL;
6599
static int unibuflen = 0;
6600
int n = 0;
6601
int y;
6602
6603
/*
6604
* Italic and bold text seems to have an extra row of pixels at the bottom
6605
* (below where the bottom of the character should be). If we draw the
6606
* characters with a solid background, the top row of pixels in the
6607
* character below will be overwritten. We can fix this by filling in the
6608
* background ourselves, to the correct character proportions, and then
6609
* writing the character in transparent mode. Still have a problem when
6610
* the character is "_", which gets written on to the character below.
6611
* New fix: set gui.char_ascent to -1. This shifts all characters up one
6612
* pixel in their slots, which fixes the problem with the bottom row of
6613
* pixels. We still need this code because otherwise the top row of pixels
6614
* becomes a problem. - webb.
6615
*/
6616
static HBRUSH hbr_cache[2] = {NULL, NULL};
6617
static guicolor_T brush_color[2] = {INVALCOLOR, INVALCOLOR};
6618
static int brush_lru = 0;
6619
HBRUSH hbr;
6620
RECT rc;
6621
6622
if (!(flags & DRAW_TRANSP))
6623
{
6624
/*
6625
* Clear background first.
6626
* Note: FillRect() excludes right and bottom of rectangle.
6627
*/
6628
rc.left = FILL_X(col);
6629
rc.top = FILL_Y(row);
6630
if (has_mbyte)
6631
{
6632
// Compute the length in display cells.
6633
rc.right = FILL_X(col + mb_string2cells(text, len));
6634
}
6635
else
6636
rc.right = FILL_X(col + len);
6637
rc.bottom = FILL_Y(row + 1);
6638
6639
// Cache the created brush, that saves a lot of time. We need two:
6640
// one for cursor background and one for the normal background.
6641
if (gui.currBgColor == brush_color[0])
6642
{
6643
hbr = hbr_cache[0];
6644
brush_lru = 1;
6645
}
6646
else if (gui.currBgColor == brush_color[1])
6647
{
6648
hbr = hbr_cache[1];
6649
brush_lru = 0;
6650
}
6651
else
6652
{
6653
if (hbr_cache[brush_lru] != NULL)
6654
DeleteBrush(hbr_cache[brush_lru]);
6655
hbr_cache[brush_lru] = CreateSolidBrush(gui.currBgColor);
6656
brush_color[brush_lru] = gui.currBgColor;
6657
hbr = hbr_cache[brush_lru];
6658
brush_lru = !brush_lru;
6659
}
6660
6661
fill_rect(&rc, hbr, gui.currBgColor);
6662
6663
SetBkMode(s_hdc, TRANSPARENT);
6664
6665
/*
6666
* When drawing block cursor, prevent inverted character spilling
6667
* over character cell (can happen with bold/italic)
6668
*/
6669
if (flags & DRAW_CURSOR)
6670
{
6671
pcliprect = &rc;
6672
foptions = ETO_CLIPPED;
6673
}
6674
}
6675
SetTextColor(s_hdc, gui.currFgColor);
6676
SelectFont(s_hdc, gui.currFont);
6677
6678
#ifdef FEAT_DIRECTX
6679
if (IS_ENABLE_DIRECTX())
6680
DWriteContext_SetFont(s_dwc, (HFONT)gui.currFont);
6681
#endif
6682
6683
if (pad_size != Columns || padding == NULL || padding[0] != gui.char_width)
6684
{
6685
int i;
6686
6687
vim_free(padding);
6688
pad_size = Columns;
6689
6690
// Don't give an out-of-memory message here, it would call us
6691
// recursively.
6692
padding = LALLOC_MULT(int, pad_size);
6693
if (padding != NULL)
6694
for (i = 0; i < pad_size; i++)
6695
padding[i] = gui.char_width;
6696
}
6697
6698
/*
6699
* We have to provide the padding argument because italic and bold versions
6700
* of fixed-width fonts are often one pixel or so wider than their normal
6701
* versions.
6702
* No check for DRAW_BOLD, Windows will have done it already.
6703
*/
6704
6705
// Check if there are any UTF-8 characters. If not, use normal text
6706
// output to speed up output.
6707
if (enc_utf8)
6708
for (n = 0; n < len; ++n)
6709
if (text[n] >= 0x80)
6710
break;
6711
6712
#if defined(FEAT_DIRECTX)
6713
// Quick hack to enable DirectWrite. To use DirectWrite (antialias), it is
6714
// required that unicode drawing routine, currently. So this forces it
6715
// enabled.
6716
if (IS_ENABLE_DIRECTX())
6717
n = 0; // Keep n < len, to enter block for unicode.
6718
#endif
6719
6720
// Check if the Unicode buffer exists and is big enough. Create it
6721
// with the same length as the multi-byte string, the number of wide
6722
// characters is always equal or smaller.
6723
if ((enc_utf8
6724
|| (enc_codepage > 0 && (int)GetACP() != enc_codepage)
6725
|| enc_latin9)
6726
&& (unicodebuf == NULL || len > unibuflen))
6727
{
6728
vim_free(unicodebuf);
6729
unicodebuf = LALLOC_MULT(WCHAR, len);
6730
6731
vim_free(unicodepdy);
6732
unicodepdy = LALLOC_MULT(int, len);
6733
6734
unibuflen = len;
6735
}
6736
6737
if (enc_utf8 && n < len && unicodebuf != NULL)
6738
{
6739
// Output UTF-8 characters. Composing characters should be
6740
// handled here.
6741
int i;
6742
int wlen; // string length in words
6743
int cells; // cell width of string up to composing char
6744
int cw; // width of current cell
6745
int c;
6746
6747
wlen = 0;
6748
cells = 0;
6749
for (i = 0; i < len; )
6750
{
6751
c = utf_ptr2char(text + i);
6752
if (c >= 0x10000)
6753
{
6754
// Turn into UTF-16 encoding.
6755
unicodebuf[wlen++] = ((c - 0x10000) >> 10) + 0xD800;
6756
unicodebuf[wlen++] = ((c - 0x10000) & 0x3ff) + 0xDC00;
6757
}
6758
else
6759
{
6760
unicodebuf[wlen++] = c;
6761
}
6762
6763
if (utf_iscomposing(c))
6764
cw = 0;
6765
else
6766
{
6767
cw = utf_char2cells(c);
6768
if (cw > 2) // don't use 4 for unprintable char
6769
cw = 1;
6770
}
6771
6772
if (unicodepdy != NULL)
6773
{
6774
// Use unicodepdy to make characters fit as we expect, even
6775
// when the font uses different widths (e.g., bold character
6776
// is wider).
6777
if (c >= 0x10000)
6778
{
6779
unicodepdy[wlen - 2] = cw * gui.char_width;
6780
unicodepdy[wlen - 1] = 0;
6781
}
6782
else
6783
unicodepdy[wlen - 1] = cw * gui.char_width;
6784
}
6785
cells += cw;
6786
i += utf_ptr2len_len(text + i, len - i);
6787
}
6788
#if defined(FEAT_DIRECTX)
6789
if (IS_ENABLE_DIRECTX())
6790
{
6791
// Add one to "cells" for italics.
6792
DWriteContext_DrawText(s_dwc, unicodebuf, wlen,
6793
TEXT_X(col), TEXT_Y(row),
6794
FILL_X(cells + 1), FILL_Y(1) - p_linespace,
6795
gui.char_width, gui.currFgColor,
6796
foptions, pcliprect, unicodepdy);
6797
}
6798
else
6799
#endif
6800
ExtTextOutW(s_hdc, TEXT_X(col), TEXT_Y(row),
6801
foptions, pcliprect, unicodebuf, wlen, unicodepdy);
6802
len = cells; // used for underlining
6803
}
6804
else if ((enc_codepage > 0 && (int)GetACP() != enc_codepage) || enc_latin9)
6805
{
6806
// If we want to display codepage data, and the current CP is not the
6807
// ANSI one, we need to go via Unicode.
6808
if (unicodebuf != NULL)
6809
{
6810
if (enc_latin9)
6811
latin9_to_ucs(text, len, unicodebuf);
6812
else
6813
len = MultiByteToWideChar(enc_codepage,
6814
MB_PRECOMPOSED,
6815
(char *)text, len,
6816
(LPWSTR)unicodebuf, unibuflen);
6817
if (len != 0)
6818
{
6819
// Use unicodepdy to make characters fit as we expect, even
6820
// when the font uses different widths (e.g., bold character
6821
// is wider).
6822
if (unicodepdy != NULL)
6823
{
6824
int i;
6825
int cw;
6826
6827
for (i = 0; i < len; ++i)
6828
{
6829
cw = utf_char2cells(unicodebuf[i]);
6830
if (cw > 2)
6831
cw = 1;
6832
unicodepdy[i] = cw * gui.char_width;
6833
}
6834
}
6835
ExtTextOutW(s_hdc, TEXT_X(col), TEXT_Y(row),
6836
foptions, pcliprect, unicodebuf, len, unicodepdy);
6837
}
6838
}
6839
}
6840
else
6841
{
6842
#ifdef FEAT_RIGHTLEFT
6843
// Windows will mess up RL text, so we have to draw it character by
6844
// character. Only do this if RL is on, since it's slow.
6845
if (curwin->w_p_rl)
6846
RevOut(s_hdc, TEXT_X(col), TEXT_Y(row),
6847
foptions, pcliprect, (char *)text, len, padding);
6848
else
6849
#endif
6850
ExtTextOut(s_hdc, TEXT_X(col), TEXT_Y(row),
6851
foptions, pcliprect, (char *)text, len, padding);
6852
}
6853
6854
// Underline
6855
if (flags & DRAW_UNDERL)
6856
{
6857
// When p_linespace is 0, overwrite the bottom row of pixels.
6858
// Otherwise put the line just below the character.
6859
y = FILL_Y(row + 1) - 1;
6860
if (p_linespace > 1)
6861
y -= p_linespace - 1;
6862
draw_line(FILL_X(col), y, FILL_X(col + len), y, gui.currFgColor);
6863
}
6864
6865
// Strikethrough
6866
if (flags & DRAW_STRIKE)
6867
{
6868
y = FILL_Y(row + 1) - gui.char_height/2;
6869
draw_line(FILL_X(col), y, FILL_X(col + len), y, gui.currSpColor);
6870
}
6871
6872
// Undercurl
6873
if (flags & DRAW_UNDERC)
6874
{
6875
int x;
6876
int offset;
6877
static const int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 };
6878
6879
y = FILL_Y(row + 1) - 1;
6880
for (x = FILL_X(col); x < FILL_X(col + len); ++x)
6881
{
6882
offset = val[x % 8];
6883
set_pixel(x, y - offset, gui.currSpColor);
6884
}
6885
}
6886
}
6887
6888
6889
/*
6890
* Output routines.
6891
*/
6892
6893
/*
6894
* Flush any output to the screen
6895
*/
6896
void
6897
gui_mch_flush(void)
6898
{
6899
#if defined(FEAT_DIRECTX)
6900
if (IS_ENABLE_DIRECTX())
6901
DWriteContext_Flush(s_dwc);
6902
#endif
6903
6904
GdiFlush();
6905
}
6906
6907
static void
6908
clear_rect(RECT *rcp)
6909
{
6910
fill_rect(rcp, NULL, gui.back_pixel);
6911
}
6912
6913
6914
void
6915
gui_mch_get_screen_dimensions(int *screen_w, int *screen_h)
6916
{
6917
RECT workarea_rect;
6918
6919
get_work_area(&workarea_rect);
6920
6921
*screen_w = workarea_rect.right - workarea_rect.left
6922
- (pGetSystemMetricsForDpi(SM_CXFRAME, s_dpi) +
6923
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2;
6924
6925
// FIXME: dirty trick: Because the gui_get_base_height() doesn't include
6926
// the menubar for MSwin, we subtract it from the screen height, so that
6927
// the window size can be made to fit on the screen.
6928
*screen_h = workarea_rect.bottom - workarea_rect.top
6929
- (pGetSystemMetricsForDpi(SM_CYFRAME, s_dpi) +
6930
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, s_dpi)) * 2
6931
- pGetSystemMetricsForDpi(SM_CYCAPTION, s_dpi)
6932
- gui_mswin_get_menu_height(FALSE);
6933
}
6934
6935
6936
#if defined(FEAT_MENU)
6937
/*
6938
* Add a sub menu to the menu bar.
6939
*/
6940
void
6941
gui_mch_add_menu(
6942
vimmenu_T *menu,
6943
int pos)
6944
{
6945
vimmenu_T *parent = menu->parent;
6946
6947
menu->submenu_id = CreatePopupMenu();
6948
menu->id = s_menu_id++;
6949
6950
if (menu_is_menubar(menu->name))
6951
{
6952
WCHAR *wn;
6953
MENUITEMINFOW infow;
6954
6955
wn = enc_to_utf16(menu->name, NULL);
6956
if (wn == NULL)
6957
return;
6958
6959
infow.cbSize = sizeof(infow);
6960
infow.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID
6961
| MIIM_SUBMENU;
6962
infow.dwItemData = (long_u)menu;
6963
infow.wID = menu->id;
6964
infow.fType = MFT_STRING;
6965
infow.dwTypeData = wn;
6966
infow.cch = (UINT)wcslen(wn);
6967
infow.hSubMenu = menu->submenu_id;
6968
InsertMenuItemW((parent == NULL)
6969
? s_menuBar : parent->submenu_id,
6970
(UINT)pos, TRUE, &infow);
6971
vim_free(wn);
6972
}
6973
6974
// Fix window size if menu may have wrapped
6975
if (parent == NULL)
6976
gui_mswin_get_menu_height(!gui.starting);
6977
# ifdef FEAT_TEAROFF
6978
else if (IsWindow(parent->tearoff_handle))
6979
rebuild_tearoff(parent);
6980
# endif
6981
}
6982
6983
void
6984
gui_mch_show_popupmenu(vimmenu_T *menu)
6985
{
6986
POINT mp;
6987
6988
(void)GetCursorPos(&mp);
6989
gui_mch_show_popupmenu_at(menu, (int)mp.x, (int)mp.y);
6990
}
6991
6992
void
6993
gui_make_popup(char_u *path_name, int mouse_pos)
6994
{
6995
vimmenu_T *menu = gui_find_menu(path_name);
6996
6997
if (menu == NULL)
6998
return;
6999
7000
POINT p;
7001
7002
// Find the position of the current cursor
7003
GetDCOrgEx(s_hdc, &p);
7004
if (mouse_pos)
7005
{
7006
int mx, my;
7007
7008
gui_mch_getmouse(&mx, &my);
7009
p.x += mx;
7010
p.y += my;
7011
}
7012
else if (curwin != NULL)
7013
{
7014
p.x += TEXT_X(curwin->w_wincol + curwin->w_wcol + 1);
7015
p.y += TEXT_Y(W_WINROW(curwin) + curwin->w_wrow + 1);
7016
}
7017
msg_scroll = FALSE;
7018
gui_mch_show_popupmenu_at(menu, (int)p.x, (int)p.y);
7019
}
7020
7021
# if defined(FEAT_TEAROFF)
7022
/*
7023
* Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
7024
* create it as a pseudo-"tearoff menu".
7025
*/
7026
void
7027
gui_make_tearoff(char_u *path_name)
7028
{
7029
vimmenu_T *menu = gui_find_menu(path_name);
7030
7031
// Found the menu, so tear it off.
7032
if (menu != NULL)
7033
gui_mch_tearoff(menu->dname, menu, 0xffffL, 0xffffL);
7034
}
7035
# endif
7036
7037
/*
7038
* Add a menu item to a menu
7039
*/
7040
void
7041
gui_mch_add_menu_item(
7042
vimmenu_T *menu,
7043
int idx)
7044
{
7045
vimmenu_T *parent = menu->parent;
7046
7047
menu->id = s_menu_id++;
7048
menu->submenu_id = NULL;
7049
7050
# ifdef FEAT_TEAROFF
7051
if (STRNCMP(menu->name, TEAR_STRING, TEAR_LEN) == 0)
7052
{
7053
InsertMenu(parent->submenu_id, (UINT)idx, MF_BITMAP|MF_BYPOSITION,
7054
(UINT)menu->id, (LPCTSTR) s_htearbitmap);
7055
}
7056
else
7057
# endif
7058
# ifdef FEAT_TOOLBAR
7059
if (menu_is_toolbar(parent->name))
7060
{
7061
TBBUTTON newtb;
7062
7063
CLEAR_FIELD(newtb);
7064
if (menu_is_separator(menu->name))
7065
{
7066
newtb.iBitmap = 0;
7067
newtb.fsStyle = TBSTYLE_SEP;
7068
}
7069
else
7070
{
7071
newtb.iBitmap = get_toolbar_bitmap(menu);
7072
newtb.fsStyle = TBSTYLE_BUTTON;
7073
}
7074
newtb.idCommand = menu->id;
7075
newtb.fsState = TBSTATE_ENABLED;
7076
newtb.iString = 0;
7077
SendMessage(s_toolbarhwnd, TB_INSERTBUTTON, (WPARAM)idx,
7078
(LPARAM)&newtb);
7079
menu->submenu_id = (HMENU)-1;
7080
}
7081
else
7082
# endif
7083
{
7084
WCHAR *wn;
7085
7086
wn = enc_to_utf16(menu->name, NULL);
7087
if (wn != NULL)
7088
{
7089
InsertMenuW(parent->submenu_id, (UINT)idx,
7090
(menu_is_separator(menu->name)
7091
? MF_SEPARATOR : MF_STRING) | MF_BYPOSITION,
7092
(UINT)menu->id, wn);
7093
vim_free(wn);
7094
}
7095
# ifdef FEAT_TEAROFF
7096
if (IsWindow(parent->tearoff_handle))
7097
rebuild_tearoff(parent);
7098
# endif
7099
}
7100
}
7101
7102
/*
7103
* Destroy the machine specific menu widget.
7104
*/
7105
void
7106
gui_mch_destroy_menu(vimmenu_T *menu)
7107
{
7108
# ifdef FEAT_TOOLBAR
7109
/*
7110
* is this a toolbar button?
7111
*/
7112
if (menu->submenu_id == (HMENU)-1)
7113
{
7114
int iButton;
7115
7116
iButton = (int)SendMessage(s_toolbarhwnd, TB_COMMANDTOINDEX,
7117
(WPARAM)menu->id, 0);
7118
SendMessage(s_toolbarhwnd, TB_DELETEBUTTON, (WPARAM)iButton, 0);
7119
}
7120
else
7121
# endif
7122
{
7123
if (menu->parent != NULL
7124
&& menu_is_popup(menu->parent->dname)
7125
&& menu->parent->submenu_id != NULL)
7126
RemoveMenu(menu->parent->submenu_id, menu->id, MF_BYCOMMAND);
7127
else
7128
RemoveMenu(s_menuBar, menu->id, MF_BYCOMMAND);
7129
if (menu->submenu_id != NULL)
7130
DestroyMenu(menu->submenu_id);
7131
# ifdef FEAT_TEAROFF
7132
if (IsWindow(menu->tearoff_handle))
7133
DestroyWindow(menu->tearoff_handle);
7134
if (menu->parent != NULL
7135
&& menu->parent->children != NULL
7136
&& IsWindow(menu->parent->tearoff_handle))
7137
{
7138
// This menu must not show up when rebuilding the tearoff window.
7139
menu->modes = 0;
7140
rebuild_tearoff(menu->parent);
7141
}
7142
# endif
7143
}
7144
}
7145
7146
# ifdef FEAT_TEAROFF
7147
static void
7148
rebuild_tearoff(vimmenu_T *menu)
7149
{
7150
//hackish
7151
char_u tbuf[128];
7152
RECT trect;
7153
RECT rct;
7154
RECT roct;
7155
int x, y;
7156
7157
HWND thwnd = menu->tearoff_handle;
7158
7159
GetWindowText(thwnd, (LPSTR)tbuf, 127);
7160
if (GetWindowRect(thwnd, &trect)
7161
&& GetWindowRect(s_hwnd, &rct)
7162
&& GetClientRect(s_hwnd, &roct))
7163
{
7164
x = trect.left - rct.left;
7165
y = (trect.top - rct.bottom + roct.bottom);
7166
}
7167
else
7168
{
7169
x = y = 0xffffL;
7170
}
7171
DestroyWindow(thwnd);
7172
if (menu->children != NULL)
7173
{
7174
gui_mch_tearoff(tbuf, menu, x, y);
7175
if (IsWindow(menu->tearoff_handle))
7176
(void) SetWindowPos(menu->tearoff_handle,
7177
NULL,
7178
(int)trect.left,
7179
(int)trect.top,
7180
0, 0,
7181
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
7182
}
7183
}
7184
# endif // FEAT_TEAROFF
7185
7186
/*
7187
* Make a menu either grey or not grey.
7188
*/
7189
void
7190
gui_mch_menu_grey(
7191
vimmenu_T *menu,
7192
int grey)
7193
{
7194
# ifdef FEAT_TOOLBAR
7195
/*
7196
* is this a toolbar button?
7197
*/
7198
if (menu->submenu_id == (HMENU)-1)
7199
SendMessage(s_toolbarhwnd, TB_ENABLEBUTTON,
7200
(WPARAM)menu->id, (LPARAM) MAKELONG((grey ? FALSE : TRUE), 0));
7201
else
7202
# endif
7203
(void)EnableMenuItem(menu->parent ? menu->parent->submenu_id : s_menuBar,
7204
menu->id, MF_BYCOMMAND | (grey ? MF_GRAYED : MF_ENABLED));
7205
7206
# ifdef FEAT_TEAROFF
7207
if ((menu->parent != NULL) && (IsWindow(menu->parent->tearoff_handle)))
7208
{
7209
WORD menuID;
7210
HWND menuHandle;
7211
7212
/*
7213
* A tearoff button has changed state.
7214
*/
7215
if (menu->children == NULL)
7216
menuID = (WORD)(menu->id);
7217
else
7218
menuID = (WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000);
7219
menuHandle = GetDlgItem(menu->parent->tearoff_handle, menuID);
7220
if (menuHandle)
7221
EnableWindow(menuHandle, !grey);
7222
7223
}
7224
# endif
7225
}
7226
7227
#endif // FEAT_MENU
7228
7229
7230
// define some macros used to make the dialogue creation more readable
7231
7232
#define add_word(x) *p++ = (x)
7233
#define add_long(x) dwp = (DWORD *)p; *dwp++ = (x); p = (WORD *)dwp
7234
7235
#if defined(FEAT_GUI_DIALOG)
7236
/*
7237
* stuff for dialogs
7238
*/
7239
7240
/*
7241
* The callback routine used by all the dialogs. Very simple. First,
7242
* acknowledges the INITDIALOG message so that Windows knows to do standard
7243
* dialog stuff (Return = default, Esc = cancel....) Second, if a button is
7244
* pressed, return that button's ID - IDCANCEL (2), which is the button's
7245
* number.
7246
*/
7247
static LRESULT CALLBACK
7248
dialog_callback(
7249
HWND hwnd,
7250
UINT message,
7251
WPARAM wParam,
7252
LPARAM lParam UNUSED)
7253
{
7254
if (message == WM_INITDIALOG)
7255
{
7256
CenterWindow(hwnd, GetWindow(hwnd, GW_OWNER));
7257
// Set focus to the dialog. Set the default button, if specified.
7258
(void)SetFocus(hwnd);
7259
if (dialog_default_button > IDCANCEL)
7260
(void)SetFocus(GetDlgItem(hwnd, dialog_default_button));
7261
else
7262
// We don't have a default, set focus on another element of the
7263
// dialog window, probably the icon
7264
(void)SetFocus(GetDlgItem(hwnd, DLG_NONBUTTON_CONTROL));
7265
return FALSE;
7266
}
7267
7268
if (message == WM_COMMAND)
7269
{
7270
int button = LOWORD(wParam);
7271
7272
// Don't end the dialog if something was selected that was
7273
// not a button.
7274
if (button >= DLG_NONBUTTON_CONTROL)
7275
return TRUE;
7276
7277
// If the edit box exists, copy the string.
7278
if (s_textfield != NULL)
7279
{
7280
WCHAR *wp = ALLOC_MULT(WCHAR, IOSIZE);
7281
char_u *p;
7282
7283
GetDlgItemTextW(hwnd, DLG_NONBUTTON_CONTROL + 2, wp, IOSIZE);
7284
p = utf16_to_enc(wp, NULL);
7285
if (p != NULL)
7286
{
7287
vim_strncpy(s_textfield, p, IOSIZE);
7288
vim_free(p);
7289
}
7290
vim_free(wp);
7291
}
7292
7293
/*
7294
* Need to check for IDOK because if the user just hits Return to
7295
* accept the default value, some reason this is what we get.
7296
*/
7297
if (button == IDOK)
7298
{
7299
if (dialog_default_button > IDCANCEL)
7300
EndDialog(hwnd, dialog_default_button);
7301
}
7302
else
7303
EndDialog(hwnd, button - IDCANCEL);
7304
return TRUE;
7305
}
7306
7307
if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE))
7308
{
7309
EndDialog(hwnd, 0);
7310
return TRUE;
7311
}
7312
return FALSE;
7313
}
7314
7315
/*
7316
* Create a dialog dynamically from the parameter strings.
7317
* type = type of dialog (question, alert, etc.)
7318
* title = dialog title. may be NULL for default title.
7319
* message = text to display. Dialog sizes to accommodate it.
7320
* buttons = '\n' separated list of button captions, default first.
7321
* dfltbutton = number of default button.
7322
*
7323
* This routine returns 1 if the first button is pressed,
7324
* 2 for the second, etc.
7325
*
7326
* 0 indicates Esc was pressed.
7327
* -1 for unexpected error
7328
*
7329
* If stubbing out this fn, return 1.
7330
*/
7331
7332
static const char *dlg_icons[] = // must match names in resource file
7333
{
7334
"IDR_VIM",
7335
"IDR_VIM_ERROR",
7336
"IDR_VIM_ALERT",
7337
"IDR_VIM_INFO",
7338
"IDR_VIM_QUESTION"
7339
};
7340
7341
int
7342
gui_mch_dialog(
7343
int type,
7344
char_u *title,
7345
char_u *message,
7346
char_u *buttons,
7347
int dfltbutton,
7348
char_u *textfield,
7349
int ex_cmd UNUSED)
7350
{
7351
WORD *p, *pdlgtemplate, *pnumitems;
7352
DWORD *dwp;
7353
int numButtons;
7354
int *buttonWidths, *buttonPositions;
7355
int buttonYpos;
7356
int nchar, i;
7357
DWORD lStyle;
7358
int dlgwidth = 0;
7359
int dlgheight;
7360
int editboxheight;
7361
int horizWidth = 0;
7362
int msgheight;
7363
char_u *pstart;
7364
char_u *pend;
7365
char_u *last_white;
7366
char_u *tbuffer;
7367
RECT rect;
7368
HWND hwnd;
7369
HDC hdc;
7370
HFONT font, oldFont;
7371
TEXTMETRIC fontInfo;
7372
int fontHeight;
7373
int textWidth, minButtonWidth, messageWidth;
7374
int maxDialogWidth;
7375
int maxDialogHeight;
7376
int scroll_flag = 0;
7377
int vertical;
7378
int dlgPaddingX;
7379
int dlgPaddingY;
7380
# ifdef USE_SYSMENU_FONT
7381
LOGFONTW lfSysmenu;
7382
int use_lfSysmenu = FALSE;
7383
# endif
7384
garray_T ga;
7385
int l;
7386
int dlg_icon_width;
7387
int dlg_icon_height;
7388
int dpi;
7389
7390
# ifndef NO_CONSOLE
7391
// Don't output anything in silent mode ("ex -s")
7392
# ifdef VIMDLL
7393
if (!(gui.in_use || gui.starting))
7394
# endif
7395
if (silent_mode)
7396
return dfltbutton; // return default option
7397
# endif
7398
7399
if (s_hwnd == NULL)
7400
{
7401
load_dpi_func();
7402
s_dpi = dpi = pGetDpiForSystem();
7403
get_dialog_font_metrics();
7404
}
7405
else
7406
dpi = pGetDpiForSystem();
7407
7408
if ((type < 0) || (type > VIM_LAST_TYPE))
7409
type = 0;
7410
7411
// allocate some memory for dialog template
7412
// TODO should compute this really
7413
pdlgtemplate = p = (PWORD)LocalAlloc(LPTR,
7414
DLG_ALLOC_SIZE + STRLEN(message) * 2);
7415
7416
if (p == NULL)
7417
return -1;
7418
7419
/*
7420
* make a copy of 'buttons' to fiddle with it. compiler grizzles because
7421
* vim_strsave() doesn't take a const arg (why not?), so cast away the
7422
* const.
7423
*/
7424
tbuffer = vim_strsave(buttons);
7425
if (tbuffer == NULL)
7426
return -1;
7427
7428
--dfltbutton; // Change from one-based to zero-based
7429
7430
// Count buttons
7431
numButtons = 1;
7432
for (i = 0; tbuffer[i] != '\0'; i++)
7433
{
7434
if (tbuffer[i] == DLG_BUTTON_SEP)
7435
numButtons++;
7436
}
7437
if (dfltbutton >= numButtons)
7438
dfltbutton = -1;
7439
7440
// Allocate array to hold the width of each button
7441
buttonWidths = ALLOC_MULT(int, numButtons);
7442
if (buttonWidths == NULL)
7443
return -1;
7444
7445
// Allocate array to hold the X position of each button
7446
buttonPositions = ALLOC_MULT(int, numButtons);
7447
if (buttonPositions == NULL)
7448
return -1;
7449
7450
/*
7451
* Calculate how big the dialog must be.
7452
*/
7453
hwnd = GetDesktopWindow();
7454
hdc = GetWindowDC(hwnd);
7455
# ifdef USE_SYSMENU_FONT
7456
if (gui_w32_get_menu_font(&lfSysmenu) == OK)
7457
{
7458
font = CreateFontIndirectW(&lfSysmenu);
7459
use_lfSysmenu = TRUE;
7460
}
7461
else
7462
# endif
7463
font = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0,
7464
0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME);
7465
7466
oldFont = SelectFont(hdc, font);
7467
dlgPaddingX = DLG_PADDING_X;
7468
dlgPaddingY = DLG_PADDING_Y;
7469
7470
GetTextMetrics(hdc, &fontInfo);
7471
fontHeight = fontInfo.tmHeight;
7472
7473
// Minimum width for horizontal button
7474
minButtonWidth = GetTextWidth(hdc, (char_u *)"Cancel", 6);
7475
7476
// Maximum width of a dialog, if possible
7477
if (s_hwnd == NULL)
7478
{
7479
RECT workarea_rect;
7480
7481
// We don't have a window, use the desktop area.
7482
get_work_area(&workarea_rect);
7483
maxDialogWidth = workarea_rect.right - workarea_rect.left - 100;
7484
if (maxDialogWidth > adjust_by_system_dpi(600))
7485
maxDialogWidth = adjust_by_system_dpi(600);
7486
// Leave some room for the taskbar.
7487
maxDialogHeight = workarea_rect.bottom - workarea_rect.top - 150;
7488
}
7489
else
7490
{
7491
// Use our own window for the size, unless it's very small.
7492
GetWindowRect(s_hwnd, &rect);
7493
maxDialogWidth = rect.right - rect.left
7494
- (pGetSystemMetricsForDpi(SM_CXFRAME, dpi) +
7495
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) * 2;
7496
if (maxDialogWidth < adjust_by_system_dpi(DLG_MIN_MAX_WIDTH))
7497
maxDialogWidth = adjust_by_system_dpi(DLG_MIN_MAX_WIDTH);
7498
7499
maxDialogHeight = rect.bottom - rect.top
7500
- (pGetSystemMetricsForDpi(SM_CYFRAME, dpi) +
7501
pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)) * 4
7502
- pGetSystemMetricsForDpi(SM_CYCAPTION, dpi);
7503
if (maxDialogHeight < adjust_by_system_dpi(DLG_MIN_MAX_HEIGHT))
7504
maxDialogHeight = adjust_by_system_dpi(DLG_MIN_MAX_HEIGHT);
7505
}
7506
7507
// Set dlgwidth to width of message.
7508
// Copy the message into "ga", changing NL to CR-NL and inserting line
7509
// breaks where needed.
7510
pstart = message;
7511
messageWidth = 0;
7512
msgheight = 0;
7513
ga_init2(&ga, sizeof(char), 500);
7514
do
7515
{
7516
msgheight += fontHeight; // at least one line
7517
7518
// Need to figure out where to break the string. The system does it
7519
// at a word boundary, which would mean we can't compute the number of
7520
// wrapped lines.
7521
textWidth = 0;
7522
last_white = NULL;
7523
for (pend = pstart; *pend != NUL && *pend != '\n'; )
7524
{
7525
l = (*mb_ptr2len)(pend);
7526
if (l == 1 && VIM_ISWHITE(*pend)
7527
&& textWidth > maxDialogWidth * 3 / 4)
7528
last_white = pend;
7529
textWidth += GetTextWidthEnc(hdc, pend, l);
7530
if (textWidth >= maxDialogWidth)
7531
{
7532
// Line will wrap.
7533
messageWidth = maxDialogWidth;
7534
msgheight += fontHeight;
7535
textWidth = 0;
7536
7537
if (last_white != NULL)
7538
{
7539
// break the line just after a space
7540
if (pend > last_white)
7541
ga.ga_len -= (int)(pend - (last_white + 1));
7542
pend = last_white + 1;
7543
last_white = NULL;
7544
}
7545
ga_append(&ga, '\r');
7546
ga_append(&ga, '\n');
7547
continue;
7548
}
7549
7550
while (--l >= 0)
7551
ga_append(&ga, *pend++);
7552
}
7553
if (textWidth > messageWidth)
7554
messageWidth = textWidth;
7555
7556
ga_append(&ga, '\r');
7557
ga_append(&ga, '\n');
7558
pstart = pend + 1;
7559
} while (*pend != NUL);
7560
7561
if (ga.ga_data != NULL)
7562
message = ga.ga_data;
7563
7564
messageWidth += 10; // roundoff space
7565
7566
dlg_icon_width = adjust_by_system_dpi(DLG_ICON_WIDTH);
7567
dlg_icon_height = adjust_by_system_dpi(DLG_ICON_HEIGHT);
7568
7569
// Add width of icon to dlgwidth, and some space
7570
dlgwidth = messageWidth + dlg_icon_width + 3 * dlgPaddingX
7571
+ pGetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
7572
7573
if (msgheight < dlg_icon_height)
7574
msgheight = dlg_icon_height;
7575
7576
/*
7577
* Check button names. A long one will make the dialog wider.
7578
* When called early (-register error message) p_go isn't initialized.
7579
*/
7580
vertical = (p_go != NULL && vim_strchr(p_go, GO_VERTICAL) != NULL);
7581
if (!vertical)
7582
{
7583
// Place buttons horizontally if they fit.
7584
horizWidth = dlgPaddingX;
7585
pstart = tbuffer;
7586
i = 0;
7587
do
7588
{
7589
pend = vim_strchr(pstart, DLG_BUTTON_SEP);
7590
if (pend == NULL)
7591
pend = pstart + STRLEN(pstart); // Last button name.
7592
textWidth = GetTextWidthEnc(hdc, pstart, (int)(pend - pstart));
7593
if (textWidth < minButtonWidth)
7594
textWidth = minButtonWidth;
7595
textWidth += dlgPaddingX; // Padding within button
7596
buttonWidths[i] = textWidth;
7597
buttonPositions[i++] = horizWidth;
7598
horizWidth += textWidth + dlgPaddingX; // Pad between buttons
7599
pstart = pend + 1;
7600
} while (*pend != NUL);
7601
7602
if (horizWidth > maxDialogWidth)
7603
vertical = TRUE; // Too wide to fit on the screen.
7604
else if (horizWidth > dlgwidth)
7605
dlgwidth = horizWidth;
7606
}
7607
7608
if (vertical)
7609
{
7610
// Stack buttons vertically.
7611
pstart = tbuffer;
7612
do
7613
{
7614
pend = vim_strchr(pstart, DLG_BUTTON_SEP);
7615
if (pend == NULL)
7616
pend = pstart + STRLEN(pstart); // Last button name.
7617
textWidth = GetTextWidthEnc(hdc, pstart, (int)(pend - pstart));
7618
textWidth += dlgPaddingX; // Padding within button
7619
textWidth += DLG_VERT_PADDING_X * 2; // Padding around button
7620
if (textWidth > dlgwidth)
7621
dlgwidth = textWidth;
7622
pstart = pend + 1;
7623
} while (*pend != NUL);
7624
}
7625
7626
if (dlgwidth < DLG_MIN_WIDTH)
7627
dlgwidth = DLG_MIN_WIDTH; // Don't allow a really thin dialog!
7628
7629
// start to fill in the dlgtemplate information. addressing by WORDs
7630
lStyle = DS_MODALFRAME | WS_CAPTION | DS_3DLOOK | WS_VISIBLE | DS_SETFONT;
7631
7632
add_long(lStyle);
7633
add_long(0); // (lExtendedStyle)
7634
pnumitems = p; //save where the number of items must be stored
7635
add_word(0); // NumberOfItems(will change later)
7636
add_word(10); // x
7637
add_word(10); // y
7638
add_word(PixelToDialogX(dlgwidth)); // cx
7639
7640
// Dialog height.
7641
if (vertical)
7642
dlgheight = msgheight + 2 * dlgPaddingY
7643
+ DLG_VERT_PADDING_Y + 2 * fontHeight * numButtons;
7644
else
7645
dlgheight = msgheight + 3 * dlgPaddingY + 2 * fontHeight;
7646
7647
// Dialog needs to be taller if contains an edit box.
7648
editboxheight = fontHeight + dlgPaddingY + 4 * DLG_VERT_PADDING_Y;
7649
if (textfield != NULL)
7650
dlgheight += editboxheight;
7651
7652
// Restrict the size to a maximum. Causes a scrollbar to show up.
7653
if (dlgheight > maxDialogHeight)
7654
{
7655
msgheight = msgheight - (dlgheight - maxDialogHeight);
7656
dlgheight = maxDialogHeight;
7657
scroll_flag = WS_VSCROLL;
7658
// Make sure scrollbar doesn't appear in the middle of the dialog
7659
messageWidth = dlgwidth - dlg_icon_width - 3 * dlgPaddingX;
7660
}
7661
7662
add_word(PixelToDialogY(dlgheight));
7663
7664
add_word(0); // Menu
7665
add_word(0); // Class
7666
7667
// copy the title of the dialog
7668
nchar = nCopyAnsiToWideChar(p, (title ? (LPSTR)title
7669
: (LPSTR)("Vim "VIM_VERSION_MEDIUM)), TRUE);
7670
p += nchar;
7671
7672
// do the font, since DS_3DLOOK doesn't work properly
7673
# ifdef USE_SYSMENU_FONT
7674
if (use_lfSysmenu)
7675
{
7676
// point size
7677
*p++ = -MulDiv(lfSysmenu.lfHeight, 72,
7678
GetDeviceCaps(hdc, LOGPIXELSY));
7679
wcscpy(p, lfSysmenu.lfFaceName);
7680
nchar = (int)wcslen(lfSysmenu.lfFaceName) + 1;
7681
}
7682
else
7683
# endif
7684
{
7685
*p++ = DLG_FONT_POINT_SIZE; // point size
7686
nchar = nCopyAnsiToWideChar(p, DLG_FONT_NAME, FALSE);
7687
}
7688
p += nchar;
7689
7690
buttonYpos = msgheight + 2 * dlgPaddingY;
7691
7692
if (textfield != NULL)
7693
buttonYpos += editboxheight;
7694
7695
pstart = tbuffer;
7696
if (!vertical)
7697
horizWidth = (dlgwidth - horizWidth) / 2; // Now it's X offset
7698
for (i = 0; i < numButtons; i++)
7699
{
7700
// get end of this button.
7701
for ( pend = pstart;
7702
*pend && (*pend != DLG_BUTTON_SEP);
7703
pend++)
7704
;
7705
7706
if (*pend)
7707
*pend = '\0';
7708
7709
/*
7710
* old NOTE:
7711
* setting the BS_DEFPUSHBUTTON style doesn't work because Windows sets
7712
* the focus to the first tab-able button and in so doing makes that
7713
* the default!! Grrr. Workaround: Make the default button the only
7714
* one with WS_TABSTOP style. Means user can't tab between buttons, but
7715
* he/she can use arrow keys.
7716
*
7717
* new NOTE: BS_DEFPUSHBUTTON is required to be able to select the
7718
* right button when hitting <Enter>. E.g., for the ":confirm quit"
7719
* dialog. Also needed for when the textfield is the default control.
7720
* It appears to work now (perhaps not on Win95?).
7721
*/
7722
if (vertical)
7723
{
7724
p = add_dialog_element(p,
7725
(i == dfltbutton
7726
? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP,
7727
PixelToDialogX(DLG_VERT_PADDING_X),
7728
PixelToDialogY(buttonYpos // TBK
7729
+ 2 * fontHeight * i),
7730
PixelToDialogX(dlgwidth - 2 * DLG_VERT_PADDING_X),
7731
(WORD)(PixelToDialogY(2 * fontHeight) - 1),
7732
(WORD)(IDCANCEL + 1 + i), (WORD)0x0080, (char *)pstart);
7733
}
7734
else
7735
{
7736
p = add_dialog_element(p,
7737
(i == dfltbutton
7738
? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP,
7739
PixelToDialogX(horizWidth + buttonPositions[i]),
7740
PixelToDialogY(buttonYpos), // TBK
7741
PixelToDialogX(buttonWidths[i]),
7742
(WORD)(PixelToDialogY(2 * fontHeight) - 1),
7743
(WORD)(IDCANCEL + 1 + i), (WORD)0x0080, (char *)pstart);
7744
}
7745
pstart = pend + 1; //next button
7746
}
7747
*pnumitems += numButtons;
7748
7749
// Vim icon
7750
p = add_dialog_element(p, SS_ICON,
7751
PixelToDialogX(dlgPaddingX),
7752
PixelToDialogY(dlgPaddingY),
7753
PixelToDialogX(dlg_icon_width),
7754
PixelToDialogY(dlg_icon_height),
7755
DLG_NONBUTTON_CONTROL + 0, (WORD)0x0082,
7756
dlg_icons[type]);
7757
7758
// Dialog message
7759
p = add_dialog_element(p, ES_LEFT|scroll_flag|ES_MULTILINE|ES_READONLY,
7760
PixelToDialogX(2 * dlgPaddingX + dlg_icon_width),
7761
PixelToDialogY(dlgPaddingY),
7762
(WORD)(PixelToDialogX(messageWidth) + 1),
7763
PixelToDialogY(msgheight),
7764
DLG_NONBUTTON_CONTROL + 1, (WORD)0x0081, (char *)message);
7765
7766
// Edit box
7767
if (textfield != NULL)
7768
{
7769
p = add_dialog_element(p, ES_LEFT|ES_AUTOHSCROLL|WS_TABSTOP|WS_BORDER,
7770
PixelToDialogX(2 * dlgPaddingX),
7771
PixelToDialogY(2 * dlgPaddingY + msgheight),
7772
PixelToDialogX(dlgwidth - 4 * dlgPaddingX),
7773
PixelToDialogY(fontHeight + dlgPaddingY),
7774
DLG_NONBUTTON_CONTROL + 2, (WORD)0x0081, (char *)textfield);
7775
*pnumitems += 1;
7776
}
7777
7778
*pnumitems += 2;
7779
7780
SelectFont(hdc, oldFont);
7781
DeleteObject(font);
7782
ReleaseDC(hwnd, hdc);
7783
7784
// Let the dialog_callback() function know which button to make default
7785
// If we have an edit box, make that the default. We also need to tell
7786
// dialog_callback() if this dialog contains an edit box or not. We do
7787
// this by setting s_textfield if it does.
7788
if (textfield != NULL)
7789
{
7790
dialog_default_button = DLG_NONBUTTON_CONTROL + 2;
7791
s_textfield = textfield;
7792
}
7793
else
7794
{
7795
dialog_default_button = IDCANCEL + 1 + dfltbutton;
7796
s_textfield = NULL;
7797
}
7798
7799
// show the dialog box modally and get a return value
7800
nchar = (int)DialogBoxIndirect(
7801
g_hinst,
7802
(LPDLGTEMPLATE)pdlgtemplate,
7803
s_hwnd,
7804
(DLGPROC)dialog_callback);
7805
7806
LocalFree(LocalHandle(pdlgtemplate));
7807
vim_free(tbuffer);
7808
vim_free(buttonWidths);
7809
vim_free(buttonPositions);
7810
vim_free(ga.ga_data);
7811
7812
// Focus back to our window (for when MDI is used).
7813
(void)SetFocus(s_hwnd);
7814
7815
return nchar;
7816
}
7817
7818
#endif // FEAT_GUI_DIALOG
7819
7820
/*
7821
* Put a simple element (basic class) onto a dialog template in memory.
7822
* return a pointer to where the next item should be added.
7823
*
7824
* parameters:
7825
* lStyle = additional style flags
7826
* (be careful, NT3.51 & Win32s will ignore the new ones)
7827
* x,y = x & y positions IN DIALOG UNITS
7828
* w,h = width and height IN DIALOG UNITS
7829
* Id = ID used in messages
7830
* clss = class ID, e.g 0x0080 for a button, 0x0082 for a static
7831
* caption = usually text or resource name
7832
*
7833
* TODO: use the length information noted here to enable the dialog creation
7834
* routines to work out more exactly how much memory they need to alloc.
7835
*/
7836
static PWORD
7837
add_dialog_element(
7838
PWORD p,
7839
DWORD lStyle,
7840
WORD x,
7841
WORD y,
7842
WORD w,
7843
WORD h,
7844
WORD Id,
7845
WORD clss,
7846
const char *caption)
7847
{
7848
int nchar;
7849
7850
p = lpwAlign(p); // Align to dword boundary
7851
lStyle = lStyle | WS_VISIBLE | WS_CHILD;
7852
*p++ = LOWORD(lStyle);
7853
*p++ = HIWORD(lStyle);
7854
*p++ = 0; // LOWORD (lExtendedStyle)
7855
*p++ = 0; // HIWORD (lExtendedStyle)
7856
*p++ = x;
7857
*p++ = y;
7858
*p++ = w;
7859
*p++ = h;
7860
*p++ = Id; //9 or 10 words in all
7861
7862
*p++ = (WORD)0xffff;
7863
*p++ = clss; //2 more here
7864
7865
nchar = nCopyAnsiToWideChar(p, (LPSTR)caption, TRUE); //strlen(caption)+1
7866
p += nchar;
7867
7868
*p++ = 0; // advance pointer over nExtraStuff WORD - 2 more
7869
7870
return p; // total = 15 + strlen(caption) words
7871
// bytes read = 2 * total
7872
}
7873
7874
7875
/*
7876
* Helper routine. Take an input pointer, return closest pointer that is
7877
* aligned on a DWORD (4 byte) boundary. Taken from the Win32SDK samples.
7878
*/
7879
static LPWORD
7880
lpwAlign(
7881
LPWORD lpIn)
7882
{
7883
long_u ul;
7884
7885
ul = (long_u)lpIn;
7886
ul += 3;
7887
ul >>= 2;
7888
ul <<= 2;
7889
return (LPWORD)ul;
7890
}
7891
7892
/*
7893
* Helper routine. Takes second parameter as Ansi string, copies it to first
7894
* parameter as wide character (16-bits / char) string, and returns integer
7895
* number of wide characters (words) in string (including the trailing wide
7896
* char NULL). Partly taken from the Win32SDK samples.
7897
* If "use_enc" is TRUE, 'encoding' is used for "lpAnsiIn". If FALSE, current
7898
* ACP is used for "lpAnsiIn". */
7899
static int
7900
nCopyAnsiToWideChar(
7901
LPWORD lpWCStr,
7902
LPSTR lpAnsiIn,
7903
BOOL use_enc)
7904
{
7905
int nChar = 0;
7906
int len = lstrlen(lpAnsiIn) + 1; // include NUL character
7907
int i;
7908
WCHAR *wn;
7909
7910
if (use_enc && enc_codepage >= 0 && (int)GetACP() != enc_codepage)
7911
{
7912
// Not a codepage, use our own conversion function.
7913
wn = enc_to_utf16((char_u *)lpAnsiIn, NULL);
7914
if (wn != NULL)
7915
{
7916
wcscpy(lpWCStr, wn);
7917
nChar = (int)wcslen(wn) + 1;
7918
vim_free(wn);
7919
}
7920
}
7921
if (nChar == 0)
7922
// Use Win32 conversion function.
7923
nChar = MultiByteToWideChar(
7924
enc_codepage > 0 ? enc_codepage : CP_ACP,
7925
MB_PRECOMPOSED,
7926
lpAnsiIn, len,
7927
lpWCStr, len);
7928
for (i = 0; i < nChar; ++i)
7929
if (lpWCStr[i] == (WORD)'\t') // replace tabs with spaces
7930
lpWCStr[i] = (WORD)' ';
7931
7932
return nChar;
7933
}
7934
7935
7936
#ifdef FEAT_TEAROFF
7937
/*
7938
* Lookup menu handle from "menu_id".
7939
*/
7940
static HMENU
7941
tearoff_lookup_menuhandle(
7942
vimmenu_T *menu,
7943
WORD menu_id)
7944
{
7945
for ( ; menu != NULL; menu = menu->next)
7946
{
7947
if (menu->modes == 0) // this menu has just been deleted
7948
continue;
7949
if (menu_is_separator(menu->dname))
7950
continue;
7951
if ((WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000) == menu_id)
7952
return menu->submenu_id;
7953
}
7954
return NULL;
7955
}
7956
7957
/*
7958
* The callback function for all the modeless dialogs that make up the
7959
* "tearoff menus" Very simple - forward button presses (to fool Vim into
7960
* thinking its menus have been clicked), and go away when closed.
7961
*/
7962
static LRESULT CALLBACK
7963
tearoff_callback(
7964
HWND hwnd,
7965
UINT message,
7966
WPARAM wParam,
7967
LPARAM lParam)
7968
{
7969
if (message == WM_INITDIALOG)
7970
{
7971
SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam);
7972
return (TRUE);
7973
}
7974
7975
// May show the mouse pointer again.
7976
HandleMouseHide(message, lParam);
7977
7978
if (message == WM_COMMAND)
7979
{
7980
if ((WORD)(LOWORD(wParam)) & 0x8000)
7981
{
7982
POINT mp;
7983
RECT rect;
7984
7985
if (GetCursorPos(&mp) && GetWindowRect(hwnd, &rect))
7986
{
7987
vimmenu_T *menu;
7988
7989
menu = (vimmenu_T*)GetWindowLongPtr(hwnd, DWLP_USER);
7990
(void)TrackPopupMenu(
7991
tearoff_lookup_menuhandle(menu, LOWORD(wParam)),
7992
TPM_LEFTALIGN | TPM_LEFTBUTTON,
7993
(int)rect.right - 8,
7994
(int)mp.y,
7995
(int)0, // reserved param
7996
s_hwnd,
7997
NULL);
7998
/*
7999
* NOTE: The pop-up menu can eat the mouse up event.
8000
* We deal with this in normal.c.
8001
*/
8002
}
8003
}
8004
else
8005
// Pass on messages to the main Vim window
8006
PostMessage(s_hwnd, WM_COMMAND, LOWORD(wParam), 0);
8007
/*
8008
* Give main window the focus back: this is so after
8009
* choosing a tearoff button you can start typing again
8010
* straight away.
8011
*/
8012
(void)SetFocus(s_hwnd);
8013
return TRUE;
8014
}
8015
if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE))
8016
{
8017
DestroyWindow(hwnd);
8018
return TRUE;
8019
}
8020
8021
// When moved around, give main window the focus back.
8022
if (message == WM_EXITSIZEMOVE)
8023
(void)SetActiveWindow(s_hwnd);
8024
8025
return FALSE;
8026
}
8027
#endif
8028
8029
8030
/*
8031
* Computes the dialog base units based on the current dialog font.
8032
* We don't use the GetDialogBaseUnits() API, because we don't use the
8033
* (old-style) system font.
8034
*/
8035
static void
8036
get_dialog_font_metrics(void)
8037
{
8038
HDC hdc;
8039
HFONT hfontTools = 0;
8040
SIZE size;
8041
#ifdef USE_SYSMENU_FONT
8042
LOGFONTW lfSysmenu;
8043
#endif
8044
8045
#ifdef USE_SYSMENU_FONT
8046
if (gui_w32_get_menu_font(&lfSysmenu) == OK)
8047
hfontTools = CreateFontIndirectW(&lfSysmenu);
8048
else
8049
#endif
8050
hfontTools = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0,
8051
0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME);
8052
8053
hdc = GetDC(s_hwnd);
8054
SelectObject(hdc, hfontTools);
8055
GetAverageFontSize(hdc, &size);
8056
ReleaseDC(s_hwnd, hdc);
8057
8058
s_dlgfntwidth = (WORD)size.cx;
8059
s_dlgfntheight = (WORD)size.cy;
8060
}
8061
8062
#if defined(FEAT_MENU) && defined(FEAT_TEAROFF)
8063
/*
8064
* Create a pseudo-"tearoff menu" based on the child
8065
* items of a given menu pointer.
8066
*/
8067
static void
8068
gui_mch_tearoff(
8069
char_u *title,
8070
vimmenu_T *menu,
8071
int initX,
8072
int initY)
8073
{
8074
WORD *p, *pdlgtemplate, *pnumitems, *ptrueheight;
8075
int template_len;
8076
int nchar, textWidth, submenuWidth;
8077
DWORD lStyle;
8078
DWORD lExtendedStyle;
8079
WORD dlgwidth;
8080
WORD menuID;
8081
vimmenu_T *pmenu;
8082
vimmenu_T *top_menu;
8083
vimmenu_T *the_menu = menu;
8084
HWND hwnd;
8085
HDC hdc;
8086
HFONT font, oldFont;
8087
int col, spaceWidth, len;
8088
int columnWidths[2];
8089
char_u *label, *text;
8090
int acLen = 0;
8091
int nameLen;
8092
int padding0, padding1, padding2 = 0;
8093
int sepPadding=0;
8094
int x;
8095
int y;
8096
# ifdef USE_SYSMENU_FONT
8097
LOGFONTW lfSysmenu;
8098
int use_lfSysmenu = FALSE;
8099
# endif
8100
8101
/*
8102
* If this menu is already torn off, move it to the mouse position.
8103
*/
8104
if (IsWindow(menu->tearoff_handle))
8105
{
8106
POINT mp;
8107
if (GetCursorPos(&mp))
8108
{
8109
SetWindowPos(menu->tearoff_handle, NULL, mp.x, mp.y, 0, 0,
8110
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
8111
}
8112
return;
8113
}
8114
8115
/*
8116
* Create a new tearoff.
8117
*/
8118
if (*title == MNU_HIDDEN_CHAR)
8119
title++;
8120
8121
// Allocate memory to store the dialog template. It's made bigger when
8122
// needed.
8123
template_len = DLG_ALLOC_SIZE;
8124
pdlgtemplate = p = (WORD *)LocalAlloc(LPTR, template_len);
8125
if (p == NULL)
8126
return;
8127
8128
hwnd = GetDesktopWindow();
8129
hdc = GetWindowDC(hwnd);
8130
# ifdef USE_SYSMENU_FONT
8131
if (gui_w32_get_menu_font(&lfSysmenu) == OK)
8132
{
8133
font = CreateFontIndirectW(&lfSysmenu);
8134
use_lfSysmenu = TRUE;
8135
}
8136
else
8137
# endif
8138
font = CreateFont(-DLG_FONT_POINT_SIZE, 0, 0, 0, 0, 0, 0, 0,
8139
0, 0, 0, 0, VARIABLE_PITCH, DLG_FONT_NAME);
8140
8141
oldFont = SelectFont(hdc, font);
8142
8143
// Calculate width of a single space. Used for padding columns to the
8144
// right width.
8145
spaceWidth = GetTextWidth(hdc, (char_u *)" ", 1);
8146
8147
// Figure out max width of the text column, the accelerator column and the
8148
// optional submenu column.
8149
submenuWidth = 0;
8150
for (col = 0; col < 2; col++)
8151
{
8152
columnWidths[col] = 0;
8153
FOR_ALL_CHILD_MENUS(menu, pmenu)
8154
{
8155
// Use "dname" here to compute the width of the visible text.
8156
text = (col == 0) ? pmenu->dname : pmenu->actext;
8157
if (text != NULL && *text != NUL)
8158
{
8159
textWidth = GetTextWidthEnc(hdc, text, (int)STRLEN(text));
8160
if (textWidth > columnWidths[col])
8161
columnWidths[col] = textWidth;
8162
}
8163
if (pmenu->children != NULL)
8164
submenuWidth = TEAROFF_COLUMN_PADDING * spaceWidth;
8165
}
8166
}
8167
if (columnWidths[1] == 0)
8168
{
8169
// no accelerators
8170
if (submenuWidth != 0)
8171
columnWidths[0] += submenuWidth;
8172
else
8173
columnWidths[0] += spaceWidth;
8174
}
8175
else
8176
{
8177
// there is an accelerator column
8178
columnWidths[0] += TEAROFF_COLUMN_PADDING * spaceWidth;
8179
columnWidths[1] += submenuWidth;
8180
}
8181
8182
/*
8183
* Now find the total width of our 'menu'.
8184
*/
8185
textWidth = columnWidths[0] + columnWidths[1];
8186
if (submenuWidth != 0)
8187
{
8188
submenuWidth = GetTextWidth(hdc, (char_u *)TEAROFF_SUBMENU_LABEL,
8189
(int)STRLEN(TEAROFF_SUBMENU_LABEL));
8190
textWidth += submenuWidth;
8191
}
8192
dlgwidth = GetTextWidthEnc(hdc, title, (int)STRLEN(title));
8193
if (textWidth > dlgwidth)
8194
dlgwidth = textWidth;
8195
dlgwidth += 2 * TEAROFF_PADDING_X + TEAROFF_BUTTON_PAD_X;
8196
8197
// start to fill in the dlgtemplate information. addressing by WORDs
8198
lStyle = DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | WS_VISIBLE;
8199
8200
lExtendedStyle = WS_EX_TOOLWINDOW|WS_EX_STATICEDGE;
8201
*p++ = LOWORD(lStyle);
8202
*p++ = HIWORD(lStyle);
8203
*p++ = LOWORD(lExtendedStyle);
8204
*p++ = HIWORD(lExtendedStyle);
8205
pnumitems = p; // save where the number of items must be stored
8206
*p++ = 0; // NumberOfItems(will change later)
8207
gui_mch_getmouse(&x, &y);
8208
if (initX == 0xffffL)
8209
*p++ = PixelToDialogX(x); // x
8210
else
8211
*p++ = PixelToDialogX(initX); // x
8212
if (initY == 0xffffL)
8213
*p++ = PixelToDialogY(y); // y
8214
else
8215
*p++ = PixelToDialogY(initY); // y
8216
*p++ = PixelToDialogX(dlgwidth); // cx
8217
ptrueheight = p;
8218
*p++ = 0; // dialog height: changed later anyway
8219
*p++ = 0; // Menu
8220
*p++ = 0; // Class
8221
8222
// copy the title of the dialog
8223
nchar = nCopyAnsiToWideChar(p, ((*title)
8224
? (LPSTR)title
8225
: (LPSTR)("Vim "VIM_VERSION_MEDIUM)), TRUE);
8226
p += nchar;
8227
8228
// do the font, since DS_3DLOOK doesn't work properly
8229
# ifdef USE_SYSMENU_FONT
8230
if (use_lfSysmenu)
8231
{
8232
// point size
8233
*p++ = -MulDiv(lfSysmenu.lfHeight, 72,
8234
GetDeviceCaps(hdc, LOGPIXELSY));
8235
wcscpy(p, lfSysmenu.lfFaceName);
8236
nchar = (int)wcslen(lfSysmenu.lfFaceName) + 1;
8237
}
8238
else
8239
# endif
8240
{
8241
*p++ = DLG_FONT_POINT_SIZE; // point size
8242
nchar = nCopyAnsiToWideChar(p, DLG_FONT_NAME, FALSE);
8243
}
8244
p += nchar;
8245
8246
/*
8247
* Loop over all the items in the menu.
8248
* But skip over the tearbar.
8249
*/
8250
if (STRCMP(menu->children->name, TEAR_STRING) == 0)
8251
menu = menu->children->next;
8252
else
8253
menu = menu->children;
8254
top_menu = menu;
8255
for ( ; menu != NULL; menu = menu->next)
8256
{
8257
if (menu->modes == 0) // this menu has just been deleted
8258
continue;
8259
if (menu_is_separator(menu->dname))
8260
{
8261
sepPadding += 3;
8262
continue;
8263
}
8264
8265
// Check if there still is plenty of room in the template. Make it
8266
// larger when needed.
8267
if (((char *)p - (char *)pdlgtemplate) + 1000 > template_len)
8268
{
8269
WORD *newp;
8270
8271
newp = (WORD *)LocalAlloc(LPTR, template_len + 4096);
8272
if (newp != NULL)
8273
{
8274
template_len += 4096;
8275
mch_memmove(newp, pdlgtemplate,
8276
(char *)p - (char *)pdlgtemplate);
8277
p = newp + (p - pdlgtemplate);
8278
pnumitems = newp + (pnumitems - pdlgtemplate);
8279
ptrueheight = newp + (ptrueheight - pdlgtemplate);
8280
LocalFree(LocalHandle(pdlgtemplate));
8281
pdlgtemplate = newp;
8282
}
8283
}
8284
8285
// Figure out minimal length of this menu label. Use "name" for the
8286
// actual text, "dname" for estimating the displayed size. "name"
8287
// has "&a" for mnemonic and includes the accelerator.
8288
len = nameLen = (int)STRLEN(menu->name);
8289
padding0 = (columnWidths[0] - GetTextWidthEnc(hdc, menu->dname,
8290
(int)STRLEN(menu->dname))) / spaceWidth;
8291
len += padding0;
8292
8293
if (menu->actext != NULL)
8294
{
8295
acLen = (int)STRLEN(menu->actext);
8296
len += acLen;
8297
textWidth = GetTextWidthEnc(hdc, menu->actext, acLen);
8298
}
8299
else
8300
textWidth = 0;
8301
padding1 = (columnWidths[1] - textWidth) / spaceWidth;
8302
len += padding1;
8303
8304
if (menu->children == NULL)
8305
{
8306
padding2 = submenuWidth / spaceWidth;
8307
len += padding2;
8308
menuID = (WORD)(menu->id);
8309
}
8310
else
8311
{
8312
len += (int)STRLEN(TEAROFF_SUBMENU_LABEL);
8313
menuID = (WORD)((long_u)(menu->submenu_id) | (DWORD)0x8000);
8314
}
8315
8316
// Allocate menu label and fill it in
8317
text = label = alloc(len + 1);
8318
if (label == NULL)
8319
break;
8320
8321
vim_strncpy(text, menu->name, nameLen);
8322
text = vim_strchr(text, TAB); // stop at TAB before actext
8323
if (text == NULL)
8324
text = label + nameLen; // no actext, use whole name
8325
while (padding0-- > 0)
8326
*text++ = ' ';
8327
if (menu->actext != NULL)
8328
{
8329
STRNCPY(text, menu->actext, acLen);
8330
text += acLen;
8331
}
8332
while (padding1-- > 0)
8333
*text++ = ' ';
8334
if (menu->children != NULL)
8335
{
8336
STRCPY(text, TEAROFF_SUBMENU_LABEL);
8337
text += STRLEN(TEAROFF_SUBMENU_LABEL);
8338
}
8339
else
8340
{
8341
while (padding2-- > 0)
8342
*text++ = ' ';
8343
}
8344
*text = NUL;
8345
8346
/*
8347
* BS_LEFT will just be ignored on Win32s/NT3.5x - on
8348
* W95/NT4 it makes the tear-off look more like a menu.
8349
*/
8350
p = add_dialog_element(p,
8351
BS_PUSHBUTTON|BS_LEFT,
8352
(WORD)PixelToDialogX(TEAROFF_PADDING_X),
8353
(WORD)(sepPadding + 1 + 13 * (*pnumitems)),
8354
(WORD)PixelToDialogX(dlgwidth - 2 * TEAROFF_PADDING_X),
8355
(WORD)12,
8356
menuID, (WORD)0x0080, (char *)label);
8357
vim_free(label);
8358
(*pnumitems)++;
8359
}
8360
8361
*ptrueheight = (WORD)(sepPadding + 1 + 13 * (*pnumitems));
8362
8363
8364
// show modelessly
8365
the_menu->tearoff_handle = CreateDialogIndirectParam(
8366
g_hinst,
8367
(LPDLGTEMPLATE)pdlgtemplate,
8368
s_hwnd,
8369
(DLGPROC)tearoff_callback,
8370
(LPARAM)top_menu);
8371
8372
LocalFree(LocalHandle(pdlgtemplate));
8373
SelectFont(hdc, oldFont);
8374
DeleteObject(font);
8375
ReleaseDC(hwnd, hdc);
8376
8377
/*
8378
* Reassert ourselves as the active window. This is so that after creating
8379
* a tearoff, the user doesn't have to click with the mouse just to start
8380
* typing again!
8381
*/
8382
(void)SetActiveWindow(s_hwnd);
8383
8384
// make sure the right buttons are enabled
8385
force_menu_update = TRUE;
8386
}
8387
#endif
8388
8389
#if defined(FEAT_TOOLBAR)
8390
# include "gui_w32_rc.h"
8391
8392
/*
8393
* Create the toolbar, initially unpopulated.
8394
* (just like the menu, there are no defaults, it's all
8395
* set up through menu.vim)
8396
*/
8397
static void
8398
initialise_toolbar(void)
8399
{
8400
InitCommonControls();
8401
s_toolbarhwnd = CreateToolbarEx(
8402
s_hwnd,
8403
WS_CHILD | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT,
8404
4000, //any old big number
8405
31, //number of images in initial bitmap
8406
g_hinst,
8407
IDR_TOOLBAR1, // id of initial bitmap
8408
NULL,
8409
0, // initial number of buttons
8410
TOOLBAR_BUTTON_WIDTH, //api guide is wrong!
8411
TOOLBAR_BUTTON_HEIGHT,
8412
TOOLBAR_BUTTON_WIDTH,
8413
TOOLBAR_BUTTON_HEIGHT,
8414
sizeof(TBBUTTON)
8415
);
8416
8417
// Remove transparency from the toolbar to prevent the main window
8418
// background colour showing through
8419
SendMessage(s_toolbarhwnd, TB_SETSTYLE, 0,
8420
SendMessage(s_toolbarhwnd, TB_GETSTYLE, 0, 0) & ~TBSTYLE_TRANSPARENT);
8421
8422
s_toolbar_wndproc = SubclassWindow(s_toolbarhwnd, toolbar_wndproc);
8423
8424
gui_mch_show_toolbar(vim_strchr(p_go, GO_TOOLBAR) != NULL);
8425
8426
update_toolbar_size();
8427
}
8428
8429
static void
8430
update_toolbar_size(void)
8431
{
8432
int w, h;
8433
TBMETRICS tbm;
8434
8435
tbm.cbSize = sizeof(TBMETRICS);
8436
tbm.dwMask = TBMF_PAD | TBMF_BUTTONSPACING;
8437
SendMessage(s_toolbarhwnd, TB_GETMETRICS, 0, (LPARAM)&tbm);
8438
//TRACE("Pad: %d, %d", tbm.cxPad, tbm.cyPad);
8439
//TRACE("ButtonSpacing: %d, %d", tbm.cxButtonSpacing, tbm.cyButtonSpacing);
8440
8441
w = (TOOLBAR_BUTTON_WIDTH + tbm.cxPad) * s_dpi / DEFAULT_DPI;
8442
h = (TOOLBAR_BUTTON_HEIGHT + tbm.cyPad) * s_dpi / DEFAULT_DPI;
8443
//TRACE("button size: %d, %d", w, h);
8444
SendMessage(s_toolbarhwnd, TB_SETBUTTONSIZE, 0, MAKELPARAM(w, h));
8445
gui.toolbar_height = h + 6;
8446
8447
//DWORD s = SendMessage(s_toolbarhwnd, TB_GETBUTTONSIZE, 0, 0);
8448
//TRACE("actual button size: %d, %d", LOWORD(s), HIWORD(s));
8449
8450
// TODO:
8451
// Currently, this function only updates the size of toolbar buttons.
8452
// It would be nice if the toolbar images are resized based on DPI.
8453
}
8454
8455
static LRESULT CALLBACK
8456
toolbar_wndproc(
8457
HWND hwnd,
8458
UINT uMsg,
8459
WPARAM wParam,
8460
LPARAM lParam)
8461
{
8462
HandleMouseHide(uMsg, lParam);
8463
return CallWindowProc(s_toolbar_wndproc, hwnd, uMsg, wParam, lParam);
8464
}
8465
8466
static int
8467
get_toolbar_bitmap(vimmenu_T *menu)
8468
{
8469
int i = -1;
8470
8471
/*
8472
* Check user bitmaps first, unless builtin is specified.
8473
*/
8474
if (!menu->icon_builtin)
8475
{
8476
char_u fname[MAXPATHL];
8477
HANDLE hbitmap = NULL;
8478
8479
if (menu->iconfile != NULL)
8480
{
8481
gui_find_iconfile(menu->iconfile, fname, "bmp");
8482
hbitmap = LoadImage(
8483
NULL,
8484
(LPCSTR)fname,
8485
IMAGE_BITMAP,
8486
TOOLBAR_BUTTON_WIDTH,
8487
TOOLBAR_BUTTON_HEIGHT,
8488
LR_LOADFROMFILE |
8489
LR_LOADMAP3DCOLORS
8490
);
8491
}
8492
8493
/*
8494
* If the LoadImage call failed, or the "icon=" file
8495
* didn't exist or wasn't specified, try the menu name
8496
*/
8497
if (hbitmap == NULL
8498
&& (gui_find_bitmap(
8499
# ifdef FEAT_MULTI_LANG
8500
menu->en_dname != NULL ? menu->en_dname :
8501
# endif
8502
menu->dname, fname, "bmp") == OK))
8503
hbitmap = LoadImage(
8504
NULL,
8505
(LPCSTR)fname,
8506
IMAGE_BITMAP,
8507
TOOLBAR_BUTTON_WIDTH,
8508
TOOLBAR_BUTTON_HEIGHT,
8509
LR_LOADFROMFILE |
8510
LR_LOADMAP3DCOLORS
8511
);
8512
8513
if (hbitmap != NULL)
8514
{
8515
TBADDBITMAP tbAddBitmap;
8516
8517
tbAddBitmap.hInst = NULL;
8518
tbAddBitmap.nID = (long_u)hbitmap;
8519
8520
i = (int)SendMessage(s_toolbarhwnd, TB_ADDBITMAP,
8521
(WPARAM)1, (LPARAM)&tbAddBitmap);
8522
// i will be set to -1 if it fails
8523
}
8524
}
8525
if (i == -1 && menu->iconidx >= 0 && menu->iconidx < TOOLBAR_BITMAP_COUNT)
8526
i = menu->iconidx;
8527
8528
return i;
8529
}
8530
#endif
8531
8532
#if defined(FEAT_GUI_TABLINE)
8533
static void
8534
initialise_tabline(void)
8535
{
8536
InitCommonControls();
8537
8538
s_tabhwnd = CreateWindow(WC_TABCONTROL, "Vim tabline",
8539
WS_CHILD|TCS_FOCUSNEVER|TCS_TOOLTIPS,
8540
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
8541
CW_USEDEFAULT, s_hwnd, NULL, g_hinst, NULL);
8542
s_tabline_wndproc = SubclassWindow(s_tabhwnd, tabline_wndproc);
8543
8544
gui.tabline_height = TABLINE_HEIGHT;
8545
8546
set_tabline_font();
8547
}
8548
8549
/*
8550
* Get tabpage_T from POINT.
8551
*/
8552
static tabpage_T *
8553
GetTabFromPoint(
8554
HWND hWnd,
8555
POINT pt)
8556
{
8557
tabpage_T *ptp = NULL;
8558
8559
if (gui_mch_showing_tabline())
8560
{
8561
TCHITTESTINFO htinfo;
8562
htinfo.pt = pt;
8563
// ignore if a window under cursor is not tabcontrol.
8564
if (s_tabhwnd == hWnd)
8565
{
8566
int idx = TabCtrl_HitTest(s_tabhwnd, &htinfo);
8567
if (idx != -1)
8568
ptp = find_tabpage(idx + 1);
8569
}
8570
}
8571
return ptp;
8572
}
8573
8574
static POINT s_pt = {0, 0};
8575
static HCURSOR s_hCursor = NULL;
8576
8577
static LRESULT CALLBACK
8578
tabline_wndproc(
8579
HWND hwnd,
8580
UINT uMsg,
8581
WPARAM wParam,
8582
LPARAM lParam)
8583
{
8584
POINT pt;
8585
tabpage_T *tp;
8586
RECT rect;
8587
int nCenter;
8588
int idx0;
8589
int idx1;
8590
8591
HandleMouseHide(uMsg, lParam);
8592
8593
switch (uMsg)
8594
{
8595
case WM_LBUTTONDOWN:
8596
{
8597
s_pt.x = GET_X_LPARAM(lParam);
8598
s_pt.y = GET_Y_LPARAM(lParam);
8599
SetCapture(hwnd);
8600
s_hCursor = GetCursor(); // backup default cursor
8601
break;
8602
}
8603
case WM_MOUSEMOVE:
8604
if (GetCapture() == hwnd
8605
&& ((wParam & MK_LBUTTON)) != 0)
8606
{
8607
pt.x = GET_X_LPARAM(lParam);
8608
pt.y = s_pt.y;
8609
if (abs(pt.x - s_pt.x) >
8610
pGetSystemMetricsForDpi(SM_CXDRAG, s_dpi))
8611
{
8612
SetCursor(LoadCursor(NULL, IDC_SIZEWE));
8613
8614
tp = GetTabFromPoint(hwnd, pt);
8615
if (tp != NULL)
8616
{
8617
idx0 = tabpage_index(curtab) - 1;
8618
idx1 = tabpage_index(tp) - 1;
8619
8620
TabCtrl_GetItemRect(hwnd, idx1, &rect);
8621
nCenter = rect.left + (rect.right - rect.left) / 2;
8622
8623
// Check if the mouse cursor goes over the center of
8624
// the next tab to prevent "flickering".
8625
if ((idx0 < idx1) && (nCenter < pt.x))
8626
{
8627
tabpage_move(idx1 + 1);
8628
update_screen(0);
8629
}
8630
else if ((idx1 < idx0) && (pt.x < nCenter))
8631
{
8632
tabpage_move(idx1);
8633
update_screen(0);
8634
}
8635
}
8636
}
8637
}
8638
break;
8639
case WM_LBUTTONUP:
8640
{
8641
if (GetCapture() == hwnd)
8642
{
8643
SetCursor(s_hCursor);
8644
ReleaseCapture();
8645
}
8646
break;
8647
}
8648
case WM_MBUTTONUP:
8649
{
8650
TCHITTESTINFO htinfo;
8651
8652
htinfo.pt.x = GET_X_LPARAM(lParam);
8653
htinfo.pt.y = GET_Y_LPARAM(lParam);
8654
idx0 = TabCtrl_HitTest(hwnd, &htinfo);
8655
if (idx0 != -1)
8656
{
8657
idx0 += 1;
8658
send_tabline_menu_event(idx0, TABLINE_MENU_CLOSE);
8659
}
8660
break;
8661
}
8662
default:
8663
break;
8664
}
8665
8666
return CallWindowProc(s_tabline_wndproc, hwnd, uMsg, wParam, lParam);
8667
}
8668
#endif
8669
8670
#if defined(FEAT_OLE) || defined(FEAT_EVAL)
8671
/*
8672
* Make the GUI window come to the foreground.
8673
*/
8674
void
8675
gui_mch_set_foreground(void)
8676
{
8677
if (IsIconic(s_hwnd))
8678
SendMessage(s_hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
8679
SetForegroundWindow(s_hwnd);
8680
}
8681
#endif
8682
8683
#if defined(FEAT_MBYTE_IME) && defined(DYNAMIC_IME)
8684
static void
8685
dyn_imm_load(void)
8686
{
8687
hLibImm = vimLoadLib("imm32.dll");
8688
if (hLibImm == NULL)
8689
return;
8690
8691
pImmGetCompositionStringW
8692
= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD))GetProcAddress(hLibImm, "ImmGetCompositionStringW");
8693
pImmGetContext
8694
= (HIMC (WINAPI *)(HWND))GetProcAddress(hLibImm, "ImmGetContext");
8695
pImmAssociateContext
8696
= (HIMC (WINAPI *)(HWND, HIMC))GetProcAddress(hLibImm, "ImmAssociateContext");
8697
pImmReleaseContext
8698
= (BOOL (WINAPI *)(HWND, HIMC))GetProcAddress(hLibImm, "ImmReleaseContext");
8699
pImmGetOpenStatus
8700
= (BOOL (WINAPI *)(HIMC))GetProcAddress(hLibImm, "ImmGetOpenStatus");
8701
pImmSetOpenStatus
8702
= (BOOL (WINAPI *)(HIMC, BOOL))GetProcAddress(hLibImm, "ImmSetOpenStatus");
8703
pImmGetCompositionFontW
8704
= (BOOL (WINAPI *)(HIMC, LPLOGFONTW))GetProcAddress(hLibImm, "ImmGetCompositionFontW");
8705
pImmSetCompositionFontW
8706
= (BOOL (WINAPI *)(HIMC, LPLOGFONTW))GetProcAddress(hLibImm, "ImmSetCompositionFontW");
8707
pImmSetCompositionWindow
8708
= (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM))GetProcAddress(hLibImm, "ImmSetCompositionWindow");
8709
pImmGetConversionStatus
8710
= (BOOL (WINAPI *)(HIMC, LPDWORD, LPDWORD))GetProcAddress(hLibImm, "ImmGetConversionStatus");
8711
pImmSetConversionStatus
8712
= (BOOL (WINAPI *)(HIMC, DWORD, DWORD))GetProcAddress(hLibImm, "ImmSetConversionStatus");
8713
8714
if ( pImmGetCompositionStringW == NULL
8715
|| pImmGetContext == NULL
8716
|| pImmAssociateContext == NULL
8717
|| pImmReleaseContext == NULL
8718
|| pImmGetOpenStatus == NULL
8719
|| pImmSetOpenStatus == NULL
8720
|| pImmGetCompositionFontW == NULL
8721
|| pImmSetCompositionFontW == NULL
8722
|| pImmSetCompositionWindow == NULL
8723
|| pImmGetConversionStatus == NULL
8724
|| pImmSetConversionStatus == NULL)
8725
{
8726
FreeLibrary(hLibImm);
8727
hLibImm = NULL;
8728
pImmGetContext = NULL;
8729
return;
8730
}
8731
8732
return;
8733
}
8734
8735
#endif
8736
8737
#if defined(FEAT_SIGN_ICONS)
8738
8739
# ifdef FEAT_XPM_W32
8740
# define IMAGE_XPM 100
8741
# endif
8742
8743
typedef struct _signicon_t
8744
{
8745
HANDLE hImage;
8746
UINT uType;
8747
# ifdef FEAT_XPM_W32
8748
HANDLE hShape; // Mask bitmap handle
8749
# endif
8750
} signicon_t;
8751
8752
void
8753
gui_mch_drawsign(int row, int col, int typenr)
8754
{
8755
signicon_t *sign;
8756
int x, y, w, h;
8757
8758
if (!gui.in_use || (sign = (signicon_t *)sign_get_image(typenr)) == NULL)
8759
return;
8760
8761
# if defined(FEAT_DIRECTX)
8762
if (IS_ENABLE_DIRECTX())
8763
DWriteContext_Flush(s_dwc);
8764
# endif
8765
8766
x = TEXT_X(col);
8767
y = TEXT_Y(row);
8768
w = gui.char_width * 2;
8769
h = gui.char_height;
8770
switch (sign->uType)
8771
{
8772
case IMAGE_BITMAP:
8773
{
8774
HDC hdcMem;
8775
HBITMAP hbmpOld;
8776
8777
hdcMem = CreateCompatibleDC(s_hdc);
8778
hbmpOld = (HBITMAP)SelectObject(hdcMem, sign->hImage);
8779
BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCCOPY);
8780
SelectObject(hdcMem, hbmpOld);
8781
DeleteDC(hdcMem);
8782
}
8783
break;
8784
case IMAGE_ICON:
8785
case IMAGE_CURSOR:
8786
DrawIconEx(s_hdc, x, y, (HICON)sign->hImage, w, h, 0, NULL, DI_NORMAL);
8787
break;
8788
# ifdef FEAT_XPM_W32
8789
case IMAGE_XPM:
8790
{
8791
HDC hdcMem;
8792
HBITMAP hbmpOld;
8793
8794
hdcMem = CreateCompatibleDC(s_hdc);
8795
hbmpOld = (HBITMAP)SelectObject(hdcMem, sign->hShape);
8796
// Make hole
8797
BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCAND);
8798
8799
SelectObject(hdcMem, sign->hImage);
8800
// Paint sign
8801
BitBlt(s_hdc, x, y, w, h, hdcMem, 0, 0, SRCPAINT);
8802
SelectObject(hdcMem, hbmpOld);
8803
DeleteDC(hdcMem);
8804
}
8805
break;
8806
# endif
8807
}
8808
}
8809
8810
static void
8811
close_signicon_image(signicon_t *sign)
8812
{
8813
if (sign == NULL)
8814
return;
8815
8816
switch (sign->uType)
8817
{
8818
case IMAGE_BITMAP:
8819
DeleteObject((HGDIOBJ)sign->hImage);
8820
break;
8821
case IMAGE_CURSOR:
8822
DestroyCursor((HCURSOR)sign->hImage);
8823
break;
8824
case IMAGE_ICON:
8825
DestroyIcon((HICON)sign->hImage);
8826
break;
8827
# ifdef FEAT_XPM_W32
8828
case IMAGE_XPM:
8829
DeleteObject((HBITMAP)sign->hImage);
8830
DeleteObject((HBITMAP)sign->hShape);
8831
break;
8832
# endif
8833
}
8834
}
8835
8836
void *
8837
gui_mch_register_sign(char_u *signfile)
8838
{
8839
signicon_t sign, *psign;
8840
char_u *ext;
8841
8842
sign.hImage = NULL;
8843
ext = signfile + STRLEN(signfile) - 4; // get extension
8844
if (ext > signfile)
8845
{
8846
int do_load = 1;
8847
8848
if (!STRICMP(ext, ".bmp"))
8849
sign.uType = IMAGE_BITMAP;
8850
else if (!STRICMP(ext, ".ico"))
8851
sign.uType = IMAGE_ICON;
8852
else if (!STRICMP(ext, ".cur") || !STRICMP(ext, ".ani"))
8853
sign.uType = IMAGE_CURSOR;
8854
else
8855
do_load = 0;
8856
8857
if (do_load)
8858
sign.hImage = (HANDLE)LoadImage(NULL, (LPCSTR)signfile, sign.uType,
8859
gui.char_width * 2, gui.char_height,
8860
LR_LOADFROMFILE | LR_CREATEDIBSECTION);
8861
# ifdef FEAT_XPM_W32
8862
if (!STRICMP(ext, ".xpm"))
8863
{
8864
sign.uType = IMAGE_XPM;
8865
LoadXpmImage((char *)signfile, (HBITMAP *)&sign.hImage,
8866
(HBITMAP *)&sign.hShape);
8867
}
8868
# endif
8869
}
8870
8871
psign = NULL;
8872
if (sign.hImage && (psign = ALLOC_ONE(signicon_t)) != NULL)
8873
*psign = sign;
8874
8875
if (!psign)
8876
{
8877
if (sign.hImage)
8878
close_signicon_image(&sign);
8879
emsg(_(e_couldnt_read_in_sign_data));
8880
}
8881
return (void *)psign;
8882
8883
}
8884
8885
void
8886
gui_mch_destroy_sign(void *sign)
8887
{
8888
if (sign == NULL)
8889
return;
8890
8891
close_signicon_image((signicon_t *)sign);
8892
vim_free(sign);
8893
}
8894
#endif
8895
8896
#if defined(FEAT_BEVAL_GUI)
8897
8898
/*
8899
* BALLOON-EVAL IMPLEMENTATION FOR WINDOWS.
8900
* Added by Sergey Khorev <[email protected]>
8901
*
8902
* The only reused thing is beval.h and get_beval_info()
8903
* from gui_beval.c (note it uses x and y of the BalloonEval struct
8904
* to get current mouse position).
8905
*
8906
* Trying to use as more Windows services as possible, and as less
8907
* IE version as possible :)).
8908
*
8909
* 1) Don't create ToolTip in gui_mch_create_beval_area, only initialize
8910
* BalloonEval struct.
8911
* 2) Enable/Disable simply create/kill BalloonEval Timer
8912
* 3) When there was enough inactivity, timer procedure posts
8913
* async request to debugger
8914
* 4) gui_mch_post_balloon (invoked from netbeans.c) creates tooltip control
8915
* and performs some actions to show it ASAP
8916
* 5) WM_NOTIFY:TTN_POP destroys created tooltip
8917
*/
8918
8919
static void
8920
make_tooltip(BalloonEval *beval, char *text, POINT pt)
8921
{
8922
TOOLINFOW *pti;
8923
RECT rect;
8924
8925
pti = ALLOC_ONE(TOOLINFOW);
8926
if (pti == NULL)
8927
return;
8928
8929
beval->balloon = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW,
8930
NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
8931
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
8932
beval->target, NULL, g_hinst, NULL);
8933
8934
SetWindowPos(beval->balloon, HWND_TOPMOST, 0, 0, 0, 0,
8935
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
8936
8937
pti->cbSize = sizeof(TOOLINFOW);
8938
pti->uFlags = TTF_SUBCLASS;
8939
pti->hwnd = beval->target;
8940
pti->hinst = 0; // Don't use string resources
8941
pti->uId = ID_BEVAL_TOOLTIP;
8942
8943
pti->lpszText = LPSTR_TEXTCALLBACKW;
8944
beval->tofree = enc_to_utf16((char_u*)text, NULL);
8945
pti->lParam = (LPARAM)beval->tofree;
8946
// switch multiline tooltips on
8947
if (GetClientRect(s_textArea, &rect))
8948
SendMessageW(beval->balloon, TTM_SETMAXTIPWIDTH, 0,
8949
(LPARAM)rect.right);
8950
8951
// Limit ballooneval bounding rect to CursorPos neighbourhood.
8952
pti->rect.left = pt.x - 3;
8953
pti->rect.top = pt.y - 3;
8954
pti->rect.right = pt.x + 3;
8955
pti->rect.bottom = pt.y + 3;
8956
8957
SendMessageW(beval->balloon, TTM_ADDTOOLW, 0, (LPARAM)pti);
8958
// Make tooltip appear sooner.
8959
SendMessageW(beval->balloon, TTM_SETDELAYTIME, TTDT_INITIAL, 10);
8960
// I've performed some tests and it seems the longest possible life time
8961
// of tooltip is 30 seconds.
8962
SendMessageW(beval->balloon, TTM_SETDELAYTIME, TTDT_AUTOPOP, 30000);
8963
/*
8964
* HACK: force tooltip to appear, because it'll not appear until
8965
* first mouse move. D*mn M$
8966
* Amazingly moving (2, 2) and then (-1, -1) the mouse doesn't move.
8967
*/
8968
mouse_event(MOUSEEVENTF_MOVE, 2, 2, 0, 0);
8969
mouse_event(MOUSEEVENTF_MOVE, (DWORD)-1, (DWORD)-1, 0, 0);
8970
vim_free(pti);
8971
}
8972
8973
static void
8974
delete_tooltip(BalloonEval *beval)
8975
{
8976
PostMessage(beval->balloon, WM_CLOSE, 0, 0);
8977
}
8978
8979
static VOID CALLBACK
8980
beval_timer_proc(
8981
HWND hwnd UNUSED,
8982
UINT uMsg UNUSED,
8983
UINT_PTR idEvent UNUSED,
8984
DWORD dwTime)
8985
{
8986
POINT pt;
8987
RECT rect;
8988
8989
if (cur_beval == NULL || cur_beval->showState == ShS_SHOWING || !p_beval)
8990
return;
8991
8992
GetCursorPos(&pt);
8993
if (WindowFromPoint(pt) != s_textArea)
8994
return;
8995
8996
ScreenToClient(s_textArea, &pt);
8997
GetClientRect(s_textArea, &rect);
8998
if (!PtInRect(&rect, pt))
8999
return;
9000
9001
if (last_user_activity > 0
9002
&& (dwTime - last_user_activity) >= (DWORD)p_bdlay
9003
&& (cur_beval->showState != ShS_PENDING
9004
|| abs(cur_beval->x - pt.x) > 3
9005
|| abs(cur_beval->y - pt.y) > 3))
9006
{
9007
// Pointer resting in one place long enough, it's time to show
9008
// the tooltip.
9009
cur_beval->showState = ShS_PENDING;
9010
cur_beval->x = pt.x;
9011
cur_beval->y = pt.y;
9012
9013
if (cur_beval->msgCB != NULL)
9014
(*cur_beval->msgCB)(cur_beval, 0);
9015
}
9016
}
9017
9018
void
9019
gui_mch_disable_beval_area(BalloonEval *beval UNUSED)
9020
{
9021
KillTimer(s_textArea, beval_timer_id);
9022
}
9023
9024
void
9025
gui_mch_enable_beval_area(BalloonEval *beval)
9026
{
9027
if (beval == NULL)
9028
return;
9029
beval_timer_id = SetTimer(s_textArea, 0, (UINT)(p_bdlay / 2),
9030
beval_timer_proc);
9031
}
9032
9033
void
9034
gui_mch_post_balloon(BalloonEval *beval, char_u *mesg)
9035
{
9036
POINT pt;
9037
9038
vim_free(beval->msg);
9039
beval->msg = mesg == NULL ? NULL : vim_strsave(mesg);
9040
if (beval->msg == NULL)
9041
{
9042
delete_tooltip(beval);
9043
beval->showState = ShS_NEUTRAL;
9044
return;
9045
}
9046
9047
if (beval->showState == ShS_SHOWING)
9048
return;
9049
GetCursorPos(&pt);
9050
ScreenToClient(s_textArea, &pt);
9051
9052
if (abs(beval->x - pt.x) < 3 && abs(beval->y - pt.y) < 3)
9053
{
9054
// cursor is still here
9055
gui_mch_disable_beval_area(cur_beval);
9056
beval->showState = ShS_SHOWING;
9057
make_tooltip(beval, (char *)mesg, pt);
9058
}
9059
}
9060
9061
BalloonEval *
9062
gui_mch_create_beval_area(
9063
void *target UNUSED, // ignored, always use s_textArea
9064
char_u *mesg,
9065
void (*mesgCB)(BalloonEval *, int),
9066
void *clientData)
9067
{
9068
// partially stolen from gui_beval.c
9069
BalloonEval *beval;
9070
9071
if (mesg != NULL && mesgCB != NULL)
9072
{
9073
iemsg(e_cannot_create_ballooneval_with_both_message_and_callback);
9074
return NULL;
9075
}
9076
9077
beval = ALLOC_CLEAR_ONE(BalloonEval);
9078
if (beval != NULL)
9079
{
9080
beval->target = s_textArea;
9081
9082
beval->showState = ShS_NEUTRAL;
9083
beval->msg = mesg;
9084
beval->msgCB = mesgCB;
9085
beval->clientData = clientData;
9086
9087
InitCommonControls();
9088
cur_beval = beval;
9089
9090
if (p_beval)
9091
gui_mch_enable_beval_area(beval);
9092
}
9093
return beval;
9094
}
9095
9096
static void
9097
Handle_WM_Notify(HWND hwnd UNUSED, LPNMHDR pnmh)
9098
{
9099
if (pnmh->idFrom != ID_BEVAL_TOOLTIP) // it is not our tooltip
9100
return;
9101
9102
if (cur_beval == NULL)
9103
return;
9104
9105
switch (pnmh->code)
9106
{
9107
case TTN_SHOW:
9108
break;
9109
case TTN_POP: // Before tooltip disappear
9110
delete_tooltip(cur_beval);
9111
gui_mch_enable_beval_area(cur_beval);
9112
9113
cur_beval->showState = ShS_NEUTRAL;
9114
break;
9115
case TTN_GETDISPINFO:
9116
{
9117
// if you get there then we have new common controls
9118
NMTTDISPINFO *info = (NMTTDISPINFO *)pnmh;
9119
info->lpszText = (LPSTR)info->lParam;
9120
info->uFlags |= TTF_DI_SETITEM;
9121
}
9122
break;
9123
case TTN_GETDISPINFOW:
9124
{
9125
// if we get here then we have new common controls
9126
NMTTDISPINFOW *info = (NMTTDISPINFOW *)pnmh;
9127
info->lpszText = (LPWSTR)info->lParam;
9128
info->uFlags |= TTF_DI_SETITEM;
9129
}
9130
break;
9131
}
9132
}
9133
9134
static void
9135
track_user_activity(UINT uMsg)
9136
{
9137
if ((uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
9138
|| (uMsg >= WM_KEYFIRST && uMsg <= WM_KEYLAST))
9139
last_user_activity = GetTickCount();
9140
}
9141
9142
void
9143
gui_mch_destroy_beval_area(BalloonEval *beval)
9144
{
9145
# ifdef FEAT_VARTABS
9146
vim_free(beval->vts);
9147
# endif
9148
vim_free(beval->tofree);
9149
vim_free(beval);
9150
}
9151
#endif // FEAT_BEVAL_GUI
9152
9153
#if defined(FEAT_NETBEANS_INTG)
9154
/*
9155
* We have multiple signs to draw at the same location. Draw the
9156
* multi-sign indicator (down-arrow) instead. This is the Win32 version.
9157
*/
9158
void
9159
netbeans_draw_multisign_indicator(int row)
9160
{
9161
int i;
9162
int y;
9163
int x;
9164
9165
if (!netbeans_active())
9166
return;
9167
9168
x = 0;
9169
y = TEXT_Y(row);
9170
9171
# if defined(FEAT_DIRECTX)
9172
if (IS_ENABLE_DIRECTX())
9173
DWriteContext_Flush(s_dwc);
9174
# endif
9175
9176
for (i = 0; i < gui.char_height - 3; i++)
9177
SetPixel(s_hdc, x+2, y++, gui.currFgColor);
9178
9179
SetPixel(s_hdc, x+0, y, gui.currFgColor);
9180
SetPixel(s_hdc, x+2, y, gui.currFgColor);
9181
SetPixel(s_hdc, x+4, y++, gui.currFgColor);
9182
SetPixel(s_hdc, x+1, y, gui.currFgColor);
9183
SetPixel(s_hdc, x+2, y, gui.currFgColor);
9184
SetPixel(s_hdc, x+3, y++, gui.currFgColor);
9185
SetPixel(s_hdc, x+2, y, gui.currFgColor);
9186
}
9187
#endif
9188
9189
#if defined(FEAT_EVAL)
9190
9191
// TODO: at the moment, this is just a copy of test_gui_mouse_event.
9192
// But, we could instead generate actual Win32 mouse event messages,
9193
// ie. to make it consistent with test_gui_w32_sendevent_keyboard.
9194
static int
9195
test_gui_w32_sendevent_mouse(dict_T *args)
9196
{
9197
if (!dict_has_key(args, "row") || !dict_has_key(args, "col"))
9198
return FALSE;
9199
9200
// Note: "move" is optional, requires fewer arguments
9201
int move = (int)dict_get_bool(args, "move", FALSE);
9202
9203
if (!move && (!dict_has_key(args, "button")
9204
|| !dict_has_key(args, "multiclick")
9205
|| !dict_has_key(args, "modifiers")))
9206
return FALSE;
9207
9208
int row = (int)dict_get_number(args, "row");
9209
int col = (int)dict_get_number(args, "col");
9210
9211
if (move)
9212
{
9213
// the "move" argument expects row and col coordnates to be in pixels,
9214
// unless "cell" is specified and is TRUE.
9215
if (dict_get_bool(args, "cell", FALSE))
9216
{
9217
// calculate the middle of the character cell
9218
// Note: Cell coordinates are 1-based from Vim script
9219
int pY = (row - 1) * gui.char_height + gui.char_height / 2;
9220
int pX = (col - 1) * gui.char_width + gui.char_width / 2;
9221
gui_mouse_moved(pX, pY);
9222
}
9223
else
9224
gui_mouse_moved(col, row);
9225
}
9226
else
9227
{
9228
int button = (int)dict_get_number(args, "button");
9229
int repeated_click = (int)dict_get_number(args, "multiclick");
9230
int_u mods = (int)dict_get_number(args, "modifiers");
9231
9232
// Reset the scroll values to known values.
9233
// XXX: Remove this when/if the scroll step is made configurable.
9234
mouse_set_hor_scroll_step(6);
9235
mouse_set_vert_scroll_step(3);
9236
9237
gui_send_mouse_event(button, TEXT_X(col - 1), TEXT_Y(row - 1),
9238
repeated_click, mods);
9239
}
9240
return TRUE;
9241
}
9242
9243
static int
9244
test_gui_w32_sendevent_keyboard(dict_T *args)
9245
{
9246
INPUT inputs[1];
9247
INPUT modkeys[3];
9248
SecureZeroMemory(inputs, sizeof(INPUT));
9249
SecureZeroMemory(modkeys, 3 * sizeof(INPUT));
9250
9251
char_u *event = dict_get_string(args, "event", TRUE);
9252
9253
if (event && (STRICMP(event, "keydown") == 0
9254
|| STRICMP(event, "keyup") == 0))
9255
{
9256
WORD vkCode = dict_get_number_def(args, "keycode", 0);
9257
if (vkCode <= 0 || vkCode >= 0xFF)
9258
{
9259
semsg(_(e_invalid_argument_nr), (long)vkCode);
9260
return FALSE;
9261
}
9262
9263
BOOL isModKey = (vkCode == VK_SHIFT || vkCode == VK_CONTROL
9264
|| vkCode == VK_MENU || vkCode == VK_LSHIFT || vkCode == VK_RSHIFT
9265
|| vkCode == VK_LCONTROL || vkCode == VK_RCONTROL
9266
|| vkCode == VK_LMENU || vkCode == VK_RMENU );
9267
9268
BOOL unwrapMods = FALSE;
9269
int mods = (int)dict_get_number(args, "modifiers");
9270
9271
// If there are modifiers in the args, and it is not a keyup event and
9272
// vkCode is not a modifier key, then we generate virtual modifier key
9273
// messages before sending the actual key message.
9274
if (mods && STRICMP(event, "keydown") == 0 && !isModKey)
9275
{
9276
int n = 0;
9277
if (mods & MOD_MASK_SHIFT)
9278
{
9279
modkeys[n].type = INPUT_KEYBOARD;
9280
modkeys[n].ki.wVk = VK_LSHIFT;
9281
n++;
9282
}
9283
if (mods & MOD_MASK_CTRL)
9284
{
9285
modkeys[n].type = INPUT_KEYBOARD;
9286
modkeys[n].ki.wVk = VK_LCONTROL;
9287
n++;
9288
}
9289
if (mods & MOD_MASK_ALT)
9290
{
9291
modkeys[n].type = INPUT_KEYBOARD;
9292
modkeys[n].ki.wVk = VK_LMENU;
9293
n++;
9294
}
9295
if (n)
9296
{
9297
(void)SetForegroundWindow(s_hwnd);
9298
SendInput(n, modkeys, sizeof(INPUT));
9299
}
9300
}
9301
9302
inputs[0].type = INPUT_KEYBOARD;
9303
inputs[0].ki.wVk = vkCode;
9304
if (STRICMP(event, "keyup") == 0)
9305
{
9306
inputs[0].ki.dwFlags = KEYEVENTF_KEYUP;
9307
if (!isModKey)
9308
unwrapMods = TRUE;
9309
}
9310
9311
(void)SetForegroundWindow(s_hwnd);
9312
SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
9313
vim_free(event);
9314
9315
if (unwrapMods)
9316
{
9317
modkeys[0].type = INPUT_KEYBOARD;
9318
modkeys[0].ki.wVk = VK_LSHIFT;
9319
modkeys[0].ki.dwFlags = KEYEVENTF_KEYUP;
9320
9321
modkeys[1].type = INPUT_KEYBOARD;
9322
modkeys[1].ki.wVk = VK_LCONTROL;
9323
modkeys[1].ki.dwFlags = KEYEVENTF_KEYUP;
9324
9325
modkeys[2].type = INPUT_KEYBOARD;
9326
modkeys[2].ki.wVk = VK_LMENU;
9327
modkeys[2].ki.dwFlags = KEYEVENTF_KEYUP;
9328
9329
(void)SetForegroundWindow(s_hwnd);
9330
SendInput(3, modkeys, sizeof(INPUT));
9331
}
9332
}
9333
else
9334
{
9335
if (event == NULL)
9336
{
9337
semsg(_(e_missing_argument_str), "event");
9338
}
9339
else
9340
{
9341
semsg(_(e_invalid_value_for_argument_str_str), "event", event);
9342
vim_free(event);
9343
}
9344
return FALSE;
9345
}
9346
return TRUE;
9347
}
9348
9349
static int
9350
test_gui_w32_sendevent_set_keycode_trans_strategy(dict_T *args)
9351
{
9352
int handled = 0;
9353
char_u *strategy = dict_get_string(args, "strategy", TRUE);
9354
9355
if (strategy)
9356
{
9357
if (STRICMP(strategy, "classic") == 0)
9358
{
9359
handled = 1;
9360
keycode_trans_strategy_used = &keycode_trans_strategy_classic;
9361
}
9362
else if (STRICMP(strategy, "experimental") == 0)
9363
{
9364
handled = 1;
9365
keycode_trans_strategy_used = &keycode_trans_strategy_experimental;
9366
}
9367
}
9368
9369
if (!handled)
9370
{
9371
if (strategy == NULL)
9372
{
9373
semsg(_(e_missing_argument_str), "strategy");
9374
}
9375
else
9376
{
9377
semsg(_(e_invalid_value_for_argument_str_str), "strategy", strategy);
9378
vim_free(strategy);
9379
}
9380
return FALSE;
9381
}
9382
return TRUE;
9383
}
9384
9385
9386
int
9387
test_gui_w32_sendevent(char_u *event, dict_T *args)
9388
{
9389
if (STRICMP(event, "key") == 0)
9390
return test_gui_w32_sendevent_keyboard(args);
9391
else if (STRICMP(event, "mouse") == 0)
9392
return test_gui_w32_sendevent_mouse(args);
9393
else if (STRICMP(event, "set_keycode_trans_strategy") == 0)
9394
return test_gui_w32_sendevent_set_keycode_trans_strategy(args);
9395
else
9396
{
9397
semsg(_(e_invalid_value_for_argument_str_str), "event", event);
9398
return FALSE;
9399
}
9400
}
9401
#endif
9402
9403