Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 1 | /* vi:set ts=8 sts=4 sw=4: |
| 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 | * gui_w16.c |
| 12 | * |
| 13 | * GUI support for Microsoft Windows 3.1x |
| 14 | * |
| 15 | * George V. Reilly <george@reilly.org> wrote the original Win32 GUI. |
| 16 | * Robert Webb reworked it to use the existing GUI stuff and added menu, |
| 17 | * scrollbars, etc. |
| 18 | * |
| 19 | * Vince Negri then butchered the code to get it compiling for |
| 20 | * 16-bit windows. |
| 21 | * |
| 22 | */ |
| 23 | |
| 24 | /* |
| 25 | * Include the common stuff for MS-Windows GUI. |
| 26 | */ |
| 27 | #include "gui_w48.c" |
| 28 | |
| 29 | #include "guiw16rc.h" |
| 30 | |
| 31 | /* Undocumented Windows Message - not even defined in some SDK headers */ |
| 32 | #define WM_EXITSIZEMOVE 0x0232 |
| 33 | |
| 34 | |
| 35 | #ifdef FEAT_TOOLBAR |
| 36 | # define CMD_TB_BASE (99) |
| 37 | # include <vimtbar.h> |
| 38 | #endif |
| 39 | |
| 40 | #ifdef PROTO |
| 41 | # define WINAPI |
| 42 | #endif |
| 43 | |
| 44 | #define HANDLE_WM_DROPFILES(hwnd, wParam, lParam, fn) \ |
| 45 | ((fn)((hwnd), (HDROP)(wParam)), 0L) |
| 46 | |
| 47 | |
| 48 | /* Local variables: */ |
| 49 | |
| 50 | #ifdef FEAT_MENU |
| 51 | static UINT s_menu_id = 100; |
| 52 | #endif |
| 53 | |
| 54 | |
| 55 | #define VIM_NAME "vim" |
| 56 | #define VIM_CLASS "Vim" |
| 57 | |
| 58 | #define DLG_ALLOC_SIZE 16 * 1024 |
| 59 | |
| 60 | /* |
| 61 | * stuff for dialogs, menus, tearoffs etc. |
| 62 | */ |
| 63 | #if defined(FEAT_GUI_DIALOG) || defined(PROTO) |
| 64 | static BOOL CALLBACK dialog_callback(HWND, UINT, WPARAM, LPARAM); |
| 65 | |
| 66 | static LPWORD |
| 67 | add_dialog_element( |
| 68 | LPWORD p, |
| 69 | DWORD lStyle, |
| 70 | WORD x, |
| 71 | WORD y, |
| 72 | WORD w, |
| 73 | WORD h, |
| 74 | WORD Id, |
| 75 | BYTE clss, |
| 76 | const char *caption); |
| 77 | |
| 78 | static int dialog_default_button = -1; |
| 79 | #endif |
| 80 | |
| 81 | static void get_dialog_font_metrics(void); |
| 82 | |
| 83 | #ifdef FEAT_TOOLBAR |
| 84 | static void initialise_toolbar(void); |
| 85 | #endif |
| 86 | |
| 87 | |
| 88 | #ifdef FEAT_MENU |
| 89 | /* |
| 90 | * Figure out how high the menu bar is at the moment. |
| 91 | */ |
| 92 | static int |
| 93 | gui_mswin_get_menu_height( |
| 94 | int fix_window) /* If TRUE, resize window if menu height changed */ |
| 95 | { |
| 96 | static int old_menu_height = -1; |
| 97 | |
| 98 | int num; |
| 99 | int menu_height; |
| 100 | |
| 101 | if (gui.menu_is_active) |
| 102 | num = GetMenuItemCount(s_menuBar); |
| 103 | else |
| 104 | num = 0; |
| 105 | |
| 106 | if (num == 0) |
| 107 | menu_height = 0; |
| 108 | else if (gui.starting) |
| 109 | menu_height = GetSystemMetrics(SM_CYMENU); |
| 110 | else |
| 111 | { |
| 112 | RECT r1, r2; |
| 113 | int frameht = GetSystemMetrics(SM_CYFRAME); |
| 114 | int capht = GetSystemMetrics(SM_CYCAPTION); |
| 115 | |
| 116 | /* get window rect of s_hwnd |
| 117 | * get client rect of s_hwnd |
| 118 | * get cap height |
| 119 | * subtract from window rect, the sum of client height, |
| 120 | * (if not maximized)frame thickness, and caption height. |
| 121 | */ |
| 122 | GetWindowRect(s_hwnd, &r1); |
| 123 | GetClientRect(s_hwnd, &r2); |
| 124 | menu_height = r1.bottom - r1.top - (r2.bottom-r2.top + |
| 125 | 2 * frameht * (!IsZoomed(s_hwnd)) + capht); |
| 126 | } |
| 127 | |
| 128 | if (fix_window && menu_height != old_menu_height) |
| 129 | { |
| 130 | old_menu_height = menu_height; |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 131 | gui_set_shellsize(FALSE, FALSE, RESIZE_VERT); |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | return menu_height; |
| 135 | } |
| 136 | #endif /*FEAT_MENU*/ |
| 137 | |
| 138 | |
| 139 | /* |
| 140 | * Even though we have _DuringSizing() which makes the rubber band a valid |
| 141 | * size, we need this for when the user maximises the window. |
| 142 | * TODO: Doesn't seem to adjust the width though for some reason. |
| 143 | */ |
| 144 | static BOOL |
| 145 | _OnWindowPosChanging( |
| 146 | HWND hwnd, |
| 147 | LPWINDOWPOS lpwpos) |
| 148 | { |
| 149 | |
| 150 | if (!IsIconic(hwnd) && !(lpwpos->flags & SWP_NOSIZE)) |
| 151 | { |
| 152 | gui_mswin_get_valid_dimensions(lpwpos->cx, lpwpos->cy, |
| 153 | &lpwpos->cx, &lpwpos->cy); |
| 154 | } |
| 155 | return 0; |
| 156 | } |
| 157 | |
| 158 | |
| 159 | |
| 160 | |
| 161 | |
| 162 | static LRESULT CALLBACK |
| 163 | _WndProc( |
| 164 | HWND hwnd, |
| 165 | UINT uMsg, |
| 166 | WPARAM wParam, |
| 167 | LPARAM lParam) |
| 168 | { |
| 169 | /* |
| 170 | TRACE("WndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n", |
| 171 | hwnd, uMsg, wParam, lParam); |
| 172 | */ |
| 173 | |
| 174 | HandleMouseHide(uMsg, lParam); |
| 175 | |
| 176 | s_uMsg = uMsg; |
| 177 | s_wParam = wParam; |
| 178 | s_lParam = lParam; |
| 179 | |
| 180 | switch (uMsg) |
| 181 | { |
| 182 | HANDLE_MSG(hwnd, WM_DEADCHAR, _OnDeadChar); |
| 183 | HANDLE_MSG(hwnd, WM_SYSDEADCHAR, _OnDeadChar); |
| 184 | /* HANDLE_MSG(hwnd, WM_ACTIVATE, _OnActivate); */ |
| 185 | HANDLE_MSG(hwnd, WM_CHAR, _OnChar); |
| 186 | HANDLE_MSG(hwnd, WM_CLOSE, _OnClose); |
| 187 | /* HANDLE_MSG(hwnd, WM_COMMAND, _OnCommand); */ |
| 188 | HANDLE_MSG(hwnd, WM_DESTROY, _OnDestroy); |
| 189 | HANDLE_MSG(hwnd, WM_DROPFILES, _OnDropFiles); |
| 190 | HANDLE_MSG(hwnd, WM_HSCROLL, _OnScroll); |
| 191 | HANDLE_MSG(hwnd, WM_KILLFOCUS, _OnKillFocus); |
| 192 | #ifdef FEAT_MENU |
| 193 | HANDLE_MSG(hwnd, WM_COMMAND, _OnMenu); |
| 194 | #endif |
| 195 | /* HANDLE_MSG(hwnd, WM_MOVE, _OnMove); */ |
| 196 | /* HANDLE_MSG(hwnd, WM_NCACTIVATE, _OnNCActivate); */ |
| 197 | HANDLE_MSG(hwnd, WM_SETFOCUS, _OnSetFocus); |
| 198 | HANDLE_MSG(hwnd, WM_SIZE, _OnSize); |
| 199 | /* HANDLE_MSG(hwnd, WM_SYSCOMMAND, _OnSysCommand); */ |
| 200 | /* HANDLE_MSG(hwnd, WM_SYSKEYDOWN, _OnAltKey); */ |
| 201 | HANDLE_MSG(hwnd, WM_VSCROLL, _OnScroll); |
| 202 | HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING, _OnWindowPosChanging); |
| 203 | HANDLE_MSG(hwnd, WM_ACTIVATEAPP, _OnActivateApp); |
| 204 | |
| 205 | case WM_QUERYENDSESSION: /* System wants to go down. */ |
| 206 | gui_shell_closed(); /* Will exit when no changed buffers. */ |
| 207 | return FALSE; /* Do NOT allow system to go down. */ |
| 208 | |
| 209 | case WM_ENDSESSION: |
| 210 | if (wParam) /* system only really goes down when wParam is TRUE */ |
| 211 | _OnEndSession(); |
| 212 | break; |
| 213 | |
| 214 | case WM_SYSCHAR: |
| 215 | /* |
| 216 | * if 'winaltkeys' is "no", or it's "menu" and it's not a menu |
| 217 | * shortcut key, handle like a typed ALT key, otherwise call Windows |
| 218 | * ALT key handling. |
| 219 | */ |
| 220 | #ifdef FEAT_MENU |
| 221 | if ( !gui.menu_is_active |
| 222 | || p_wak[0] == 'n' |
| 223 | || (p_wak[0] == 'm' && !gui_is_menu_shortcut((int)wParam)) |
| 224 | ) |
| 225 | #endif |
| 226 | return HANDLE_WM_SYSCHAR((hwnd), (wParam), (lParam), (_OnSysChar)); |
| 227 | #ifdef FEAT_MENU |
| 228 | else |
| 229 | return MyWindowProc(hwnd, uMsg, wParam, lParam); |
| 230 | #endif |
| 231 | |
| 232 | case WM_SYSKEYUP: |
| 233 | #ifdef FEAT_MENU |
| 234 | /* Only when menu is active, ALT key is used for that. */ |
| 235 | if (gui.menu_is_active) |
| 236 | { |
| 237 | return MyWindowProc(hwnd, uMsg, wParam, lParam); |
| 238 | } |
| 239 | else |
| 240 | #endif |
| 241 | return 0; |
| 242 | |
| 243 | #if defined(MENUHINTS) && defined(FEAT_MENU) |
| 244 | case WM_MENUSELECT: |
| 245 | if (((UINT) LOWORD(lParam) |
| 246 | & (0xffff ^ (MF_MOUSESELECT + MF_BITMAP + MF_POPUP))) |
| 247 | == MF_HILITE |
| 248 | && (State & CMDLINE) == 0) |
| 249 | { |
| 250 | UINT idButton; |
| 251 | int idx; |
| 252 | vimmenu_T *pMenu; |
| 253 | |
| 254 | idButton = (UINT)LOWORD(wParam); |
| 255 | pMenu = gui_mswin_find_menu(root_menu, idButton); |
| 256 | if (pMenu) |
| 257 | { |
| 258 | idx = MENU_INDEX_TIP; |
| 259 | msg_clr_cmdline(); |
| 260 | if (pMenu->strings[idx]) |
| 261 | msg(pMenu->strings[idx]); |
| 262 | else |
| 263 | msg(""); |
| 264 | setcursor(); |
| 265 | out_flush(); |
| 266 | } |
| 267 | } |
| 268 | break; |
| 269 | #endif |
| 270 | case WM_NCHITTEST: |
| 271 | { |
| 272 | LRESULT result; |
| 273 | int x, y; |
| 274 | int xPos = GET_X_LPARAM(lParam); |
| 275 | |
| 276 | result = MyWindowProc(hwnd, uMsg, wParam, lParam); |
| 277 | if (result == HTCLIENT) |
| 278 | { |
| 279 | gui_mch_get_winpos(&x, &y); |
| 280 | xPos -= x; |
| 281 | |
| 282 | if (xPos < 48) /*<VN> TODO should use system metric?*/ |
| 283 | return HTBOTTOMLEFT; |
| 284 | else |
| 285 | return HTBOTTOMRIGHT; |
| 286 | } |
| 287 | else |
| 288 | return result; |
| 289 | } |
| 290 | /* break; */ |
| 291 | default: |
| 292 | #ifdef MSWIN_FIND_REPLACE |
| 293 | if (uMsg == s_findrep_msg && s_findrep_msg != 0) |
| 294 | { |
| 295 | _OnFindRepl(); |
| 296 | } |
| 297 | #endif |
| 298 | return MyWindowProc(hwnd, uMsg, wParam, lParam); |
| 299 | } |
| 300 | |
| 301 | return 1; |
| 302 | } |
| 303 | |
| 304 | |
| 305 | |
| 306 | /* |
| 307 | * End of call-back routines |
| 308 | */ |
| 309 | |
| 310 | |
| 311 | /* |
| 312 | * Parse the GUI related command-line arguments. Any arguments used are |
| 313 | * deleted from argv, and *argc is decremented accordingly. This is called |
| 314 | * when vim is started, whether or not the GUI has been started. |
| 315 | */ |
| 316 | void |
| 317 | gui_mch_prepare(int *argc, char **argv) |
| 318 | { |
| 319 | /* No special args for win16 GUI at the moment. */ |
| 320 | |
| 321 | } |
| 322 | |
| 323 | /* |
| 324 | * Initialise the GUI. Create all the windows, set up all the call-backs |
| 325 | * etc. |
| 326 | */ |
| 327 | int |
| 328 | gui_mch_init(void) |
| 329 | { |
| 330 | const char szVimWndClass[] = VIM_CLASS; |
| 331 | const char szTextAreaClass[] = "VimTextArea"; |
| 332 | WNDCLASS wndclass; |
| 333 | |
| 334 | #ifdef WIN16_3DLOOK |
| 335 | Ctl3dRegister(s_hinst); |
| 336 | Ctl3dAutoSubclass(s_hinst); |
| 337 | #endif |
| 338 | |
| 339 | /* Display any pending error messages */ |
| 340 | display_errors(); |
| 341 | |
| 342 | gui.scrollbar_width = GetSystemMetrics(SM_CXVSCROLL); |
| 343 | gui.scrollbar_height = GetSystemMetrics(SM_CYHSCROLL); |
| 344 | #ifdef FEAT_MENU |
| 345 | gui.menu_height = 0; /* Windows takes care of this */ |
| 346 | #endif |
| 347 | gui.border_width = 0; |
| 348 | |
| 349 | gui.currBgColor = INVALCOLOR; |
| 350 | |
| 351 | s_brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); |
| 352 | |
| 353 | if (GetClassInfo(s_hinst, szVimWndClass, &wndclass) == 0) { |
| 354 | wndclass.style = 0; |
| 355 | wndclass.lpfnWndProc = _WndProc; |
| 356 | wndclass.cbClsExtra = 0; |
| 357 | wndclass.cbWndExtra = 0; |
| 358 | wndclass.hInstance = s_hinst; |
| 359 | wndclass.hIcon = LoadIcon(wndclass.hInstance, MAKEINTRESOURCE(IDR_VIM)); |
| 360 | wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); |
| 361 | wndclass.hbrBackground = s_brush; |
| 362 | wndclass.lpszMenuName = NULL; |
| 363 | wndclass.lpszClassName = szVimWndClass; |
| 364 | |
| 365 | if (( |
| 366 | #ifdef GLOBAL_IME |
| 367 | atom = |
| 368 | #endif |
| 369 | RegisterClass(&wndclass)) == 0) |
| 370 | return FAIL; |
| 371 | } |
| 372 | |
| 373 | s_hwnd = CreateWindow( |
| 374 | szVimWndClass, "Vim MSWindows GUI", |
| 375 | WS_OVERLAPPEDWINDOW, |
| 376 | gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x, |
| 377 | gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y, |
| 378 | 100, /* Any value will do */ |
| 379 | 100, /* Any value will do */ |
| 380 | NULL, NULL, |
| 381 | s_hinst, NULL); |
| 382 | |
| 383 | if (s_hwnd == NULL) |
| 384 | return FAIL; |
| 385 | |
| 386 | #ifdef GLOBAL_IME |
| 387 | global_ime_init(atom, s_hwnd); |
| 388 | #endif |
| 389 | |
| 390 | /* Create the text area window */ |
| 391 | if (GetClassInfo(s_hinst, szTextAreaClass, &wndclass) == 0) { |
| 392 | wndclass.style = CS_OWNDC; |
| 393 | wndclass.lpfnWndProc = _TextAreaWndProc; |
| 394 | wndclass.cbClsExtra = 0; |
| 395 | wndclass.cbWndExtra = 0; |
| 396 | wndclass.hInstance = s_hinst; |
| 397 | wndclass.hIcon = NULL; |
| 398 | wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); |
| 399 | wndclass.hbrBackground = NULL; |
| 400 | wndclass.lpszMenuName = NULL; |
| 401 | wndclass.lpszClassName = szTextAreaClass; |
| 402 | |
| 403 | if (RegisterClass(&wndclass) == 0) |
| 404 | return FAIL; |
| 405 | } |
| 406 | s_textArea = CreateWindow( |
| 407 | szTextAreaClass, "Vim text area", |
| 408 | WS_CHILD | WS_VISIBLE, 0, 0, |
| 409 | 100, /* Any value will do for now */ |
| 410 | 100, /* Any value will do for now */ |
| 411 | s_hwnd, NULL, |
| 412 | s_hinst, NULL); |
| 413 | |
| 414 | if (s_textArea == NULL) |
| 415 | return FAIL; |
| 416 | |
| 417 | #ifdef FEAT_MENU |
| 418 | s_menuBar = CreateMenu(); |
| 419 | #endif |
| 420 | s_hdc = GetDC(s_textArea); |
| 421 | |
| 422 | #ifdef MSWIN16_FASTTEXT |
| 423 | SetBkMode(s_hdc, OPAQUE); |
| 424 | #endif |
| 425 | |
| 426 | DragAcceptFiles(s_hwnd, TRUE); |
| 427 | |
| 428 | /* Do we need to bother with this? */ |
| 429 | /* m_fMouseAvail = GetSystemMetrics(SM_MOUSEPRESENT); */ |
| 430 | |
| 431 | /* Get background/foreground colors from the system */ |
| 432 | gui_mch_def_colors(); |
| 433 | |
| 434 | /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc |
| 435 | * file) */ |
| 436 | set_normal_colors(); |
| 437 | |
| 438 | /* |
| 439 | * Check that none of the colors are the same as the background color. |
| 440 | * Then store the current values as the defaults. |
| 441 | */ |
| 442 | gui_check_colors(); |
| 443 | gui.def_norm_pixel = gui.norm_pixel; |
| 444 | gui.def_back_pixel = gui.back_pixel; |
| 445 | |
| 446 | /* Get the colors for the highlight groups (gui_check_colors() might have |
| 447 | * changed them) */ |
| 448 | highlight_gui_started(); |
| 449 | |
| 450 | /* |
| 451 | * Start out by adding the configured border width into the border offset |
| 452 | */ |
| 453 | gui.border_offset = gui.border_width; |
| 454 | |
| 455 | |
| 456 | /* |
| 457 | * compute a couple of metrics used for the dialogs |
| 458 | */ |
| 459 | get_dialog_font_metrics(); |
| 460 | #ifdef FEAT_TOOLBAR |
| 461 | /* |
| 462 | * Create the toolbar |
| 463 | */ |
| 464 | initialise_toolbar(); |
| 465 | #endif |
| 466 | #ifdef MSWIN_FIND_REPLACE |
| 467 | /* |
| 468 | * Initialise the dialog box stuff |
| 469 | */ |
| 470 | s_findrep_msg = RegisterWindowMessage(FINDMSGSTRING); |
| 471 | |
| 472 | /* Initialise the struct */ |
| 473 | s_findrep_struct.lStructSize = sizeof(s_findrep_struct); |
| 474 | s_findrep_struct.lpstrFindWhat = alloc(MSWIN_FR_BUFSIZE); |
| 475 | s_findrep_struct.lpstrFindWhat[0] = NUL; |
| 476 | s_findrep_struct.lpstrReplaceWith = alloc(MSWIN_FR_BUFSIZE); |
| 477 | s_findrep_struct.lpstrReplaceWith[0] = NUL; |
| 478 | s_findrep_struct.wFindWhatLen = MSWIN_FR_BUFSIZE; |
| 479 | s_findrep_struct.wReplaceWithLen = MSWIN_FR_BUFSIZE; |
| 480 | #endif |
| 481 | |
| 482 | return OK; |
| 483 | } |
| 484 | |
| 485 | |
| 486 | /* |
| 487 | * Set the size of the window to the given width and height in pixels. |
| 488 | */ |
| 489 | void |
| 490 | gui_mch_set_shellsize(int width, int height, |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 491 | int min_width, int min_height, int base_width, int base_height, |
| 492 | int direction) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 493 | { |
| 494 | RECT workarea_rect; |
| 495 | int win_width, win_height; |
| 496 | int win_xpos, win_ypos; |
| 497 | WINDOWPLACEMENT wndpl; |
| 498 | |
| 499 | /* try to keep window completely on screen */ |
| 500 | /* get size of the screen work area - use SM_CYFULLSCREEN |
| 501 | * instead of SM_CYSCREEN so that we don't overlap the |
| 502 | * taskbar if someone fires us up on Win95/NT */ |
| 503 | workarea_rect.left = 0; |
| 504 | workarea_rect.top = 0; |
| 505 | workarea_rect.right = GetSystemMetrics(SM_CXSCREEN); |
| 506 | workarea_rect.bottom = GetSystemMetrics(SM_CYFULLSCREEN); |
| 507 | |
| 508 | /* get current posision of our window */ |
| 509 | wndpl.length = sizeof(WINDOWPLACEMENT); |
| 510 | GetWindowPlacement(s_hwnd, &wndpl); |
| 511 | if (wndpl.showCmd == SW_SHOWNORMAL) |
| 512 | { |
| 513 | win_xpos = wndpl.rcNormalPosition.left; |
| 514 | win_ypos = wndpl.rcNormalPosition.top; |
| 515 | } |
| 516 | else |
| 517 | { |
| 518 | win_xpos = workarea_rect.left; |
| 519 | win_ypos = workarea_rect.top; |
| 520 | } |
| 521 | |
| 522 | /* compute the size of the outside of the window */ |
| 523 | win_width = width + GetSystemMetrics(SM_CXFRAME) * 2; |
| 524 | win_height = height + GetSystemMetrics(SM_CYFRAME) * 2 |
| 525 | + GetSystemMetrics(SM_CYCAPTION) |
| 526 | #ifdef FEAT_MENU |
| 527 | + gui_mswin_get_menu_height(FALSE) |
| 528 | #endif |
| 529 | ; |
| 530 | |
| 531 | /* if the window is going off the screen, move it on to the screen */ |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 532 | if ((direction & RESIZE_HOR) && win_xpos + win_width > workarea_rect.right) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 533 | win_xpos = workarea_rect.right - win_width; |
| 534 | |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 535 | if ((direction & RESIZE_HOR) && win_xpos < workarea_rect.left) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 536 | win_xpos = workarea_rect.left; |
| 537 | |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 538 | if ((direction & RESIZE_VERT) |
| 539 | && win_ypos + win_height > workarea_rect.bottom) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 540 | win_ypos = workarea_rect.bottom - win_height; |
| 541 | |
Bram Moolenaar | 2e2a281 | 2006-03-27 20:55:21 +0000 | [diff] [blame] | 542 | if ((direction & RESIZE_VERT) && win_ypos < workarea_rect.top) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 543 | win_ypos = workarea_rect.top; |
| 544 | |
| 545 | /* set window position */ |
| 546 | SetWindowPos(s_hwnd, NULL, win_xpos, win_ypos, win_width, win_height, |
| 547 | SWP_NOZORDER | SWP_NOACTIVATE); |
| 548 | |
| 549 | #ifdef FEAT_MENU |
| 550 | /* Menu may wrap differently now */ |
| 551 | gui_mswin_get_menu_height(!gui.starting); |
| 552 | #endif |
| 553 | } |
| 554 | |
| 555 | void |
| 556 | gui_mch_set_scrollbar_thumb( |
| 557 | scrollbar_T *sb, |
| 558 | long val, |
| 559 | long size, |
| 560 | long max) |
| 561 | { |
| 562 | sb->scroll_shift = 0; |
| 563 | while (max > 32767) |
| 564 | { |
| 565 | max = (max + 1) >> 1; |
| 566 | val >>= 1; |
| 567 | size >>= 1; |
| 568 | ++sb->scroll_shift; |
| 569 | } |
| 570 | |
| 571 | if (sb->scroll_shift > 0) |
| 572 | ++size; |
| 573 | |
| 574 | SetScrollRange(sb->id, SB_CTL, 0, (int) max, FALSE); |
| 575 | SetScrollPos(sb->id, SB_CTL, (int) val, TRUE); |
| 576 | } |
| 577 | |
| 578 | |
| 579 | /* |
| 580 | * Set the current text font. |
| 581 | */ |
| 582 | void |
| 583 | gui_mch_set_font(GuiFont font) |
| 584 | { |
| 585 | gui.currFont = font; |
| 586 | SelectFont(s_hdc, gui.currFont); |
| 587 | } |
| 588 | |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 589 | /* |
| 590 | * Set the current text foreground color. |
| 591 | */ |
| 592 | void |
| 593 | gui_mch_set_fg_color(guicolor_T color) |
| 594 | { |
| 595 | gui.currFgColor = color; |
| 596 | SetTextColor(s_hdc, gui.currFgColor); |
| 597 | } |
| 598 | |
| 599 | /* |
| 600 | * Set the current text background color. |
| 601 | */ |
| 602 | void |
| 603 | gui_mch_set_bg_color(guicolor_T color) |
| 604 | { |
| 605 | if (gui.currBgColor == color) |
| 606 | return; |
| 607 | |
| 608 | gui.currBgColor = color; |
| 609 | SetBkColor(s_hdc, gui.currBgColor); |
| 610 | } |
| 611 | |
Bram Moolenaar | 3918c95 | 2005-03-15 22:34:55 +0000 | [diff] [blame] | 612 | /* |
| 613 | * Set the current text special color. |
| 614 | */ |
| 615 | void |
| 616 | gui_mch_set_sp_color(guicolor_T color) |
| 617 | { |
| 618 | /* TODO */ |
| 619 | } |
| 620 | |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 621 | |
| 622 | |
| 623 | void |
| 624 | gui_mch_draw_string( |
| 625 | int row, |
| 626 | int col, |
| 627 | char_u *text, |
| 628 | int len, |
| 629 | int flags) |
| 630 | { |
| 631 | #ifndef MSWIN16_FASTTEXT |
| 632 | static int *padding = NULL; |
| 633 | static int pad_size = 0; |
| 634 | int i; |
| 635 | #endif |
| 636 | HPEN hpen, old_pen; |
| 637 | int y; |
| 638 | |
| 639 | #ifndef MSWIN16_FASTTEXT |
| 640 | /* |
| 641 | * Italic and bold text seems to have an extra row of pixels at the bottom |
| 642 | * (below where the bottom of the character should be). If we draw the |
| 643 | * characters with a solid background, the top row of pixels in the |
| 644 | * character below will be overwritten. We can fix this by filling in the |
| 645 | * background ourselves, to the correct character proportions, and then |
| 646 | * writing the character in transparent mode. Still have a problem when |
| 647 | * the character is "_", which gets written on to the character below. |
| 648 | * New fix: set gui.char_ascent to -1. This shifts all characters up one |
| 649 | * pixel in their slots, which fixes the problem with the bottom row of |
| 650 | * pixels. We still need this code because otherwise the top row of pixels |
| 651 | * becomes a problem. - webb. |
| 652 | */ |
| 653 | HBRUSH hbr; |
| 654 | RECT rc; |
| 655 | |
| 656 | if (!(flags & DRAW_TRANSP)) |
| 657 | { |
| 658 | /* |
| 659 | * Clear background first. |
| 660 | * Note: FillRect() excludes right and bottom of rectangle. |
| 661 | */ |
| 662 | rc.left = FILL_X(col); |
| 663 | rc.top = FILL_Y(row); |
| 664 | #ifdef FEAT_MBYTE |
| 665 | if (has_mbyte) |
| 666 | { |
| 667 | int cell_len = 0; |
| 668 | |
| 669 | /* Compute the length in display cells. */ |
| 670 | for (n = 0; n < len; n += MB_BYTE2LEN(text[n])) |
| 671 | cell_len += (*mb_ptr2cells)(text + n); |
| 672 | rc.right = FILL_X(col + cell_len); |
| 673 | } |
| 674 | else |
| 675 | #endif |
| 676 | rc.right = FILL_X(col + len); |
| 677 | rc.bottom = FILL_Y(row + 1); |
| 678 | hbr = CreateSolidBrush(gui.currBgColor); |
| 679 | FillRect(s_hdc, &rc, hbr); |
| 680 | DeleteBrush(hbr); |
| 681 | |
| 682 | SetBkMode(s_hdc, TRANSPARENT); |
| 683 | |
| 684 | /* |
| 685 | * When drawing block cursor, prevent inverted character spilling |
| 686 | * over character cell (can happen with bold/italic) |
| 687 | */ |
| 688 | if (flags & DRAW_CURSOR) |
| 689 | { |
| 690 | pcliprect = &rc; |
| 691 | foptions = ETO_CLIPPED; |
| 692 | } |
| 693 | } |
| 694 | #else |
| 695 | /* |
| 696 | * Alternative: write the characters in opaque mode, since we have blocked |
| 697 | * bold or italic fonts. |
| 698 | */ |
| 699 | /* The OPAQUE mode and backcolour have already been set */ |
| 700 | #endif |
| 701 | /* The forecolor and font have already been set */ |
| 702 | |
| 703 | #ifndef MSWIN16_FASTTEXT |
| 704 | |
| 705 | if (pad_size != Columns || padding == NULL || padding[0] != gui.char_width) |
| 706 | { |
| 707 | vim_free(padding); |
| 708 | pad_size = Columns; |
| 709 | |
| 710 | padding = (int *)alloc(pad_size * sizeof(int)); |
| 711 | if (padding != NULL) |
| 712 | for (i = 0; i < pad_size; i++) |
| 713 | padding[i] = gui.char_width; |
| 714 | } |
| 715 | #endif |
| 716 | |
| 717 | /* |
| 718 | * We have to provide the padding argument because italic and bold versions |
| 719 | * of fixed-width fonts are often one pixel or so wider than their normal |
| 720 | * versions. |
| 721 | * No check for DRAW_BOLD, Windows will have done it already. |
| 722 | */ |
| 723 | #ifndef MSWIN16_FASTTEXT |
| 724 | ExtTextOut(s_hdc, TEXT_X(col), TEXT_Y(row), 0, NULL, |
| 725 | (char *)text, len, padding); |
| 726 | #else |
| 727 | TextOut(s_hdc, TEXT_X(col), TEXT_Y(row), (char *)text, len); |
| 728 | #endif |
| 729 | |
| 730 | if (flags & DRAW_UNDERL) |
| 731 | { |
| 732 | hpen = CreatePen(PS_SOLID, 1, gui.currFgColor); |
| 733 | old_pen = SelectObject(s_hdc, hpen); |
| 734 | /* When p_linespace is 0, overwrite the bottom row of pixels. |
| 735 | * Otherwise put the line just below the character. */ |
| 736 | y = FILL_Y(row + 1) - 1; |
| 737 | #ifndef MSWIN16_FASTTEXT |
| 738 | if (p_linespace > 1) |
| 739 | y -= p_linespace - 1; |
| 740 | #endif |
| 741 | MoveToEx(s_hdc, FILL_X(col), y, NULL); |
| 742 | /* Note: LineTo() excludes the last pixel in the line. */ |
| 743 | LineTo(s_hdc, FILL_X(col + len), y); |
| 744 | DeleteObject(SelectObject(s_hdc, old_pen)); |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | |
| 749 | /* |
| 750 | * Output routines. |
| 751 | */ |
| 752 | |
| 753 | /* Flush any output to the screen */ |
| 754 | void |
| 755 | gui_mch_flush(void) |
| 756 | { |
| 757 | /* Is anything needed here? */ |
| 758 | } |
| 759 | |
| 760 | static void |
| 761 | clear_rect(RECT *rcp) |
| 762 | { |
| 763 | /* Use trick for fast rect clear */ |
| 764 | gui_mch_set_bg_color(gui.back_pixel); |
| 765 | ExtTextOut(s_hdc, 0, 0, ETO_CLIPPED | ETO_OPAQUE, rcp, NULL, 0, NULL); |
| 766 | } |
| 767 | |
| 768 | |
Bram Moolenaar | b1b715d | 2006-01-21 22:09:43 +0000 | [diff] [blame] | 769 | void |
| 770 | gui_mch_get_screen_dimensions(int *screen_w, int *screen_h) |
| 771 | { |
| 772 | |
| 773 | *screen_w = GetSystemMetrics(SM_CXFULLSCREEN) |
| 774 | - GetSystemMetrics(SM_CXFRAME) * 2; |
| 775 | /* FIXME: dirty trick: Because the gui_get_base_height() doesn't include |
| 776 | * the menubar for MSwin, we subtract it from the screen height, so that |
| 777 | * the window size can be made to fit on the screen. */ |
| 778 | *screen_h = GetSystemMetrics(SM_CYFULLSCREEN) |
| 779 | - GetSystemMetrics(SM_CYFRAME) * 2 |
| 780 | #ifdef FEAT_MENU |
| 781 | - gui_mswin_get_menu_height(FALSE) |
| 782 | #endif |
| 783 | ; |
| 784 | } |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 785 | |
| 786 | |
| 787 | #if defined(FEAT_MENU) || defined(PROTO) |
| 788 | /* |
| 789 | * Add a sub menu to the menu bar. |
| 790 | */ |
| 791 | void |
| 792 | gui_mch_add_menu( |
| 793 | vimmenu_T *menu, |
| 794 | int pos) |
| 795 | { |
| 796 | vimmenu_T *parent = menu->parent; |
| 797 | |
| 798 | menu->submenu_id = CreatePopupMenu(); |
| 799 | menu->id = s_menu_id++; |
| 800 | |
| 801 | if (menu_is_menubar(menu->name)) |
| 802 | { |
| 803 | InsertMenu((parent == NULL) ? s_menuBar : parent->submenu_id, |
| 804 | (UINT)pos, MF_POPUP | MF_STRING | MF_BYPOSITION, |
| 805 | (UINT)menu->submenu_id, menu->name); |
| 806 | } |
| 807 | |
| 808 | /* Fix window size if menu may have wrapped */ |
| 809 | if (parent == NULL) |
| 810 | gui_mswin_get_menu_height(!gui.starting); |
| 811 | } |
| 812 | |
| 813 | void |
| 814 | gui_mch_show_popupmenu(vimmenu_T *menu) |
| 815 | { |
| 816 | POINT mp; |
| 817 | |
| 818 | (void)GetCursorPos((LPPOINT)&mp); |
| 819 | gui_mch_show_popupmenu_at(menu, (int)mp.x, (int)mp.y); |
| 820 | } |
| 821 | |
| 822 | void |
Bram Moolenaar | ec8a10a | 2005-07-08 22:19:11 +0000 | [diff] [blame] | 823 | gui_make_popup(char_u *path_name, int mouse_pos) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 824 | { |
| 825 | vimmenu_T *menu = gui_find_menu(path_name); |
| 826 | |
| 827 | if (menu != NULL) |
| 828 | { |
| 829 | /* Find the position of the current cursor */ |
| 830 | DWORD temp_p; |
| 831 | POINT p; |
| 832 | temp_p = GetDCOrg(s_hdc); |
| 833 | p.x = LOWORD(temp_p); |
| 834 | p.y = HIWORD(temp_p); |
Bram Moolenaar | ec8a10a | 2005-07-08 22:19:11 +0000 | [diff] [blame] | 835 | if (mouse_pos) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 836 | { |
Bram Moolenaar | ec8a10a | 2005-07-08 22:19:11 +0000 | [diff] [blame] | 837 | int mx, my; |
| 838 | |
| 839 | gui_mch_getmouse(&mx, &my); |
| 840 | p.x += mx; |
| 841 | p.y += my; |
| 842 | } |
| 843 | else if (curwin != NULL) |
| 844 | { |
| 845 | p.x += TEXT_X(W_WINCOL(curwin) + curwin->w_wcol + 1); |
| 846 | p.y += TEXT_Y(W_WINROW(curwin) + curwin->w_wrow + 1); |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 847 | } |
| 848 | msg_scroll = FALSE; |
| 849 | gui_mch_show_popupmenu_at(menu, (int)p.x, (int)p.y); |
| 850 | } |
| 851 | } |
| 852 | |
| 853 | /* |
| 854 | * Add a menu item to a menu |
| 855 | */ |
| 856 | void |
| 857 | gui_mch_add_menu_item( |
| 858 | vimmenu_T *menu, |
| 859 | int idx) |
| 860 | { |
| 861 | vimmenu_T *parent = menu->parent; |
| 862 | |
| 863 | menu->id = s_menu_id++; |
| 864 | menu->submenu_id = NULL; |
| 865 | |
| 866 | #ifdef FEAT_TOOLBAR |
| 867 | if (menu_is_toolbar(parent->name)) |
| 868 | { |
| 869 | TBBUTTON newtb; |
| 870 | |
| 871 | vim_memset(&newtb, 0, sizeof(newtb)); |
| 872 | if (menu_is_separator(menu->name)) |
| 873 | { |
| 874 | newtb.iBitmap = 0; |
| 875 | newtb.fsStyle = TBSTYLE_SEP; |
| 876 | } |
| 877 | else |
| 878 | { |
| 879 | if (menu->iconidx >= TOOLBAR_BITMAP_COUNT) |
| 880 | newtb.iBitmap = -1; |
| 881 | else |
| 882 | newtb.iBitmap = menu->iconidx; |
| 883 | newtb.fsStyle = TBSTYLE_BUTTON; |
| 884 | } |
| 885 | newtb.idCommand = menu->id; |
| 886 | newtb.fsState = TBSTATE_ENABLED; |
| 887 | SendMessage(s_toolbarhwnd, TB_INSERTBUTTON, (WPARAM)idx, |
| 888 | (LPARAM)&newtb); |
| 889 | menu->submenu_id = (HMENU)-1; |
| 890 | } |
| 891 | else |
| 892 | #endif |
| 893 | { |
| 894 | InsertMenu(parent->submenu_id, (UINT)idx, |
| 895 | (menu_is_separator(menu->name) ? MF_SEPARATOR : MF_STRING) |
| 896 | | MF_BYPOSITION, |
| 897 | (UINT)menu->id, menu->name); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | /* |
| 902 | * Destroy the machine specific menu widget. |
| 903 | */ |
| 904 | void |
| 905 | gui_mch_destroy_menu(vimmenu_T *menu) |
| 906 | { |
| 907 | UINT i, j; |
| 908 | char pants[80]; /*<VN> hack*/ |
| 909 | #ifdef FEAT_TOOLBAR |
| 910 | /* |
| 911 | * is this a toolbar button? |
| 912 | */ |
| 913 | if (menu->submenu_id == (HMENU)-1) |
| 914 | { |
| 915 | int iButton; |
| 916 | |
| 917 | iButton = SendMessage(s_toolbarhwnd, TB_COMMANDTOINDEX, (WPARAM)menu->id, 0); |
| 918 | SendMessage(s_toolbarhwnd, TB_DELETEBUTTON, (WPARAM)iButton, 0); |
| 919 | } |
| 920 | else |
| 921 | #endif |
| 922 | { |
| 923 | /* |
| 924 | * negri: horrible API bug when running 16-bit programs under Win9x or |
| 925 | * NT means that we can't use MF_BYCOMMAND for menu items which have |
| 926 | * submenus, including the top-level headings. We have to find the menu |
| 927 | * item and use MF_BYPOSITION instead. :-p |
| 928 | */ |
| 929 | if (menu->parent != NULL |
| 930 | && menu_is_popup(menu->parent->dname) |
| 931 | && menu->parent->submenu_id != NULL) |
| 932 | RemoveMenu(menu->parent->submenu_id, menu->id, MF_BYCOMMAND); |
| 933 | else if (menu->submenu_id == NULL) |
| 934 | RemoveMenu(s_menuBar, menu->id, MF_BYCOMMAND); |
| 935 | else if (menu->parent != NULL) |
| 936 | { |
| 937 | i = GetMenuItemCount(menu->parent->submenu_id); |
| 938 | for (j = 0; j < i; ++j) |
| 939 | { |
| 940 | GetMenuString(menu->parent->submenu_id, j, |
| 941 | pants, 80, MF_BYPOSITION); |
| 942 | if (strcmp(pants, menu->name) == 0) |
| 943 | { |
| 944 | RemoveMenu(menu->parent->submenu_id, j, MF_BYPOSITION); |
| 945 | break; |
| 946 | } |
| 947 | } |
| 948 | } |
| 949 | else |
| 950 | { |
| 951 | i = GetMenuItemCount(s_menuBar); |
| 952 | for (j = 0; j < i; ++j) |
| 953 | { |
| 954 | GetMenuString(s_menuBar, j, pants, 80, MF_BYPOSITION); |
| 955 | if (strcmp(pants, menu->name) == 0) |
| 956 | { |
| 957 | RemoveMenu(s_menuBar, j, MF_BYPOSITION); |
| 958 | break; |
| 959 | } |
| 960 | } |
| 961 | } |
| 962 | |
| 963 | if (menu->submenu_id != NULL) |
| 964 | DestroyMenu(menu->submenu_id); |
| 965 | } |
| 966 | DrawMenuBar(s_hwnd); |
| 967 | } |
| 968 | |
| 969 | |
| 970 | /* |
| 971 | * Make a menu either grey or not grey. |
| 972 | */ |
| 973 | void |
| 974 | gui_mch_menu_grey( |
| 975 | vimmenu_T *menu, |
| 976 | int grey) |
| 977 | { |
| 978 | #ifdef FEAT_TOOLBAR |
| 979 | /* |
| 980 | * is this a toolbar button? |
| 981 | */ |
| 982 | if (menu->submenu_id == (HMENU)-1) |
| 983 | { |
| 984 | SendMessage(s_toolbarhwnd, TB_ENABLEBUTTON, |
| 985 | (WPARAM)menu->id, (LPARAM) MAKELONG((grey ? FALSE : TRUE), 0) ); |
| 986 | } |
| 987 | else |
| 988 | #endif |
| 989 | if (grey) |
| 990 | EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_GRAYED); |
| 991 | else |
| 992 | EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED); |
| 993 | |
| 994 | } |
| 995 | |
| 996 | |
| 997 | #endif /*FEAT_MENU*/ |
| 998 | |
| 999 | |
| 1000 | /* define some macros used to make the dialogue creation more readable */ |
| 1001 | |
| 1002 | #define add_string(s) strcpy((LPSTR)p, s); (LPSTR)p += (strlen((LPSTR)p) + 1) |
| 1003 | #define add_word(x) *p++ = (x) |
| 1004 | #define add_byte(x) *((LPSTR)p)++ = (x) |
| 1005 | #define add_long(x) *((LPDWORD)p)++ = (x) |
| 1006 | |
| 1007 | #if defined(FEAT_GUI_DIALOG) || defined(PROTO) |
| 1008 | /* |
| 1009 | * stuff for dialogs |
| 1010 | */ |
| 1011 | |
| 1012 | /* |
| 1013 | * The callback routine used by all the dialogs. Very simple. First, |
| 1014 | * acknowledges the INITDIALOG message so that Windows knows to do standard |
| 1015 | * dialog stuff (Return = default, Esc = cancel....) Second, if a button is |
| 1016 | * pressed, return that button's ID - IDCANCEL (2), which is the button's |
| 1017 | * number. |
| 1018 | */ |
| 1019 | static BOOL CALLBACK |
| 1020 | dialog_callback( |
| 1021 | HWND hwnd, |
| 1022 | UINT message, |
| 1023 | WPARAM wParam, |
| 1024 | LPARAM lParam) |
| 1025 | { |
| 1026 | if (message == WM_INITDIALOG) |
| 1027 | { |
| 1028 | CenterWindow(hwnd, GetWindow(hwnd, GW_OWNER)); |
| 1029 | /* Set focus to the dialog. Set the default button, if specified. */ |
| 1030 | (void)SetFocus(hwnd); |
| 1031 | if (dialog_default_button > IDCANCEL) |
| 1032 | (void)SetFocus(GetDlgItem(hwnd, dialog_default_button)); |
| 1033 | // if (dialog_default_button > 0) |
| 1034 | // (void)SetFocus(GetDlgItem(hwnd, dialog_default_button + IDCANCEL)); |
| 1035 | return FALSE; |
| 1036 | } |
| 1037 | |
| 1038 | if (message == WM_COMMAND) |
| 1039 | { |
| 1040 | int button = LOWORD(wParam); |
| 1041 | |
| 1042 | /* Don't end the dialog if something was selected that was |
| 1043 | * not a button. |
| 1044 | */ |
| 1045 | if (button >= DLG_NONBUTTON_CONTROL) |
| 1046 | return TRUE; |
| 1047 | |
| 1048 | /* If the edit box exists, copy the string. */ |
| 1049 | if (s_textfield != NULL) |
| 1050 | GetDlgItemText(hwnd, DLG_NONBUTTON_CONTROL + 2, |
| 1051 | s_textfield, IOSIZE); |
| 1052 | |
| 1053 | /* |
| 1054 | * Need to check for IDOK because if the user just hits Return to |
| 1055 | * accept the default value, some reason this is what we get. |
| 1056 | */ |
| 1057 | if (button == IDOK) |
| 1058 | EndDialog(hwnd, dialog_default_button); |
| 1059 | else |
| 1060 | EndDialog(hwnd, button - IDCANCEL); |
| 1061 | return TRUE; |
| 1062 | } |
| 1063 | |
| 1064 | if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) |
| 1065 | { |
| 1066 | EndDialog(hwnd, 0); |
| 1067 | return TRUE; |
| 1068 | } |
| 1069 | return FALSE; |
| 1070 | } |
| 1071 | |
| 1072 | /* |
| 1073 | * Create a dialog dynamically from the parameter strings. |
| 1074 | * type = type of dialog (question, alert, etc.) |
| 1075 | * title = dialog title. may be NULL for default title. |
| 1076 | * message = text to display. Dialog sizes to accommodate it. |
| 1077 | * buttons = '\n' separated list of button captions, default first. |
| 1078 | * dfltbutton = number of default button. |
| 1079 | * |
| 1080 | * This routine returns 1 if the first button is pressed, |
| 1081 | * 2 for the second, etc. |
| 1082 | * |
| 1083 | * 0 indicates Esc was pressed. |
| 1084 | * -1 for unexpected error |
| 1085 | * |
| 1086 | * If stubbing out this fn, return 1. |
| 1087 | */ |
| 1088 | |
| 1089 | static const char_u dlg_icons[] = /* must match names in resource file */ |
| 1090 | { |
| 1091 | IDR_VIM, |
| 1092 | IDR_VIM_ERROR, |
| 1093 | IDR_VIM_ALERT, |
| 1094 | IDR_VIM_INFO, |
| 1095 | IDR_VIM_QUESTION |
| 1096 | }; |
| 1097 | |
| 1098 | int |
| 1099 | gui_mch_dialog( |
| 1100 | int type, |
| 1101 | char_u *title, |
| 1102 | char_u *message, |
| 1103 | char_u *buttons, |
| 1104 | int dfltbutton, |
| 1105 | char_u *textfield) |
| 1106 | { |
| 1107 | FARPROC dp; |
| 1108 | LPWORD p, pnumitems; |
| 1109 | int numButtons; |
| 1110 | int *buttonWidths, *buttonPositions; |
| 1111 | int buttonYpos; |
| 1112 | int nchar, i; |
| 1113 | DWORD lStyle; |
| 1114 | int dlgwidth = 0; |
| 1115 | int dlgheight; |
| 1116 | int editboxheight; |
| 1117 | int horizWidth; |
| 1118 | int msgheight; |
| 1119 | char_u *pstart; |
| 1120 | char_u *pend; |
| 1121 | char_u *tbuffer; |
| 1122 | RECT rect; |
| 1123 | HWND hwnd; |
| 1124 | HDC hdc; |
| 1125 | HFONT oldFont; |
| 1126 | TEXTMETRIC fontInfo; |
| 1127 | int fontHeight; |
| 1128 | int textWidth, minButtonWidth, messageWidth; |
| 1129 | int maxDialogWidth; |
| 1130 | int vertical; |
| 1131 | int dlgPaddingX; |
| 1132 | int dlgPaddingY; |
| 1133 | HGLOBAL hglbDlgTemp; |
| 1134 | |
| 1135 | #ifndef NO_CONSOLE |
| 1136 | /* Don't output anything in silent mode ("ex -s") */ |
| 1137 | if (silent_mode) |
| 1138 | return dfltbutton; /* return default option */ |
| 1139 | #endif |
| 1140 | |
| 1141 | /* If there is no window yet, open it. */ |
| 1142 | if (s_hwnd == NULL && gui_mch_init() == FAIL) |
| 1143 | return dfltbutton; |
| 1144 | |
| 1145 | if ((type < 0) || (type > VIM_LAST_TYPE)) |
| 1146 | type = 0; |
| 1147 | |
| 1148 | /* allocate some memory for dialog template */ |
| 1149 | /* TODO should compute this really*/ |
| 1150 | |
| 1151 | hglbDlgTemp = GlobalAlloc(GHND, DLG_ALLOC_SIZE); |
| 1152 | if (hglbDlgTemp == NULL) |
| 1153 | return -1; |
| 1154 | |
| 1155 | p = (LPWORD) GlobalLock(hglbDlgTemp); |
| 1156 | |
| 1157 | if (p == NULL) |
| 1158 | return -1; |
| 1159 | |
| 1160 | /* |
| 1161 | * make a copy of 'buttons' to fiddle with it. complier grizzles because |
| 1162 | * vim_strsave() doesn't take a const arg (why not?), so cast away the |
| 1163 | * const. |
| 1164 | */ |
| 1165 | tbuffer = vim_strsave(buttons); |
| 1166 | if (tbuffer == NULL) |
| 1167 | return -1; |
| 1168 | |
| 1169 | --dfltbutton; /* Change from one-based to zero-based */ |
| 1170 | |
| 1171 | /* Count buttons */ |
| 1172 | numButtons = 1; |
| 1173 | for (i = 0; tbuffer[i] != '\0'; i++) |
| 1174 | { |
| 1175 | if (tbuffer[i] == DLG_BUTTON_SEP) |
| 1176 | numButtons++; |
| 1177 | } |
| 1178 | if (dfltbutton >= numButtons) |
| 1179 | dfltbutton = 0; |
| 1180 | |
| 1181 | /* Allocate array to hold the width of each button */ |
| 1182 | buttonWidths = (int *) lalloc(numButtons * sizeof(int), TRUE); |
| 1183 | if (buttonWidths == NULL) |
| 1184 | return -1; |
| 1185 | |
| 1186 | /* Allocate array to hold the X position of each button */ |
| 1187 | buttonPositions = (int *) lalloc(numButtons * sizeof(int), TRUE); |
| 1188 | if (buttonPositions == NULL) |
| 1189 | return -1; |
| 1190 | |
| 1191 | /* |
| 1192 | * Calculate how big the dialog must be. |
| 1193 | */ |
| 1194 | hwnd = GetDesktopWindow(); |
| 1195 | hdc = GetWindowDC(hwnd); |
| 1196 | oldFont = SelectFont(hdc, GetStockObject(SYSTEM_FONT)); |
| 1197 | dlgPaddingX = DLG_OLD_STYLE_PADDING_X; |
| 1198 | dlgPaddingY = DLG_OLD_STYLE_PADDING_Y; |
| 1199 | |
| 1200 | GetTextMetrics(hdc, &fontInfo); |
| 1201 | fontHeight = fontInfo.tmHeight; |
| 1202 | |
| 1203 | /* Minimum width for horizontal button */ |
| 1204 | minButtonWidth = GetTextWidth(hdc, "Cancel", 6); |
| 1205 | |
| 1206 | /* Maximum width of a dialog, if possible */ |
| 1207 | GetWindowRect(s_hwnd, &rect); |
| 1208 | maxDialogWidth = rect.right - rect.left |
| 1209 | - GetSystemMetrics(SM_CXFRAME) * 2; |
| 1210 | if (maxDialogWidth < DLG_MIN_MAX_WIDTH) |
| 1211 | maxDialogWidth = DLG_MIN_MAX_WIDTH; |
| 1212 | |
| 1213 | /* Set dlgwidth to width of message */ |
| 1214 | pstart = message; |
| 1215 | messageWidth = 0; |
| 1216 | msgheight = 0; |
| 1217 | do |
| 1218 | { |
| 1219 | pend = vim_strchr(pstart, DLG_BUTTON_SEP); |
| 1220 | if (pend == NULL) |
| 1221 | pend = pstart + STRLEN(pstart); /* Last line of message. */ |
| 1222 | msgheight += fontHeight; |
| 1223 | textWidth = GetTextWidth(hdc, pstart, pend - pstart); |
| 1224 | if (textWidth > messageWidth) |
| 1225 | messageWidth = textWidth; |
| 1226 | pstart = pend + 1; |
| 1227 | } while (*pend != NUL); |
| 1228 | dlgwidth = messageWidth; |
| 1229 | |
| 1230 | /* Add width of icon to dlgwidth, and some space */ |
| 1231 | dlgwidth += DLG_ICON_WIDTH + 3 * dlgPaddingX; |
| 1232 | |
| 1233 | if (msgheight < DLG_ICON_HEIGHT) |
| 1234 | msgheight = DLG_ICON_HEIGHT; |
| 1235 | |
| 1236 | /* |
| 1237 | * Check button names. A long one will make the dialog wider. |
| 1238 | */ |
| 1239 | vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL); |
| 1240 | if (!vertical) |
| 1241 | { |
| 1242 | // Place buttons horizontally if they fit. |
| 1243 | horizWidth = dlgPaddingX; |
| 1244 | pstart = tbuffer; |
| 1245 | i = 0; |
| 1246 | do |
| 1247 | { |
| 1248 | pend = vim_strchr(pstart, DLG_BUTTON_SEP); |
| 1249 | if (pend == NULL) |
| 1250 | pend = pstart + STRLEN(pstart); // Last button name. |
| 1251 | textWidth = GetTextWidth(hdc, pstart, pend - pstart); |
| 1252 | if (textWidth < minButtonWidth) |
| 1253 | textWidth = minButtonWidth; |
| 1254 | textWidth += dlgPaddingX; /* Padding within button */ |
| 1255 | buttonWidths[i] = textWidth; |
| 1256 | buttonPositions[i++] = horizWidth; |
| 1257 | horizWidth += textWidth + dlgPaddingX; /* Pad between buttons */ |
| 1258 | pstart = pend + 1; |
| 1259 | } while (*pend != NUL); |
| 1260 | |
| 1261 | if (horizWidth > maxDialogWidth) |
| 1262 | vertical = TRUE; // Too wide to fit on the screen. |
| 1263 | else if (horizWidth > dlgwidth) |
| 1264 | dlgwidth = horizWidth; |
| 1265 | } |
| 1266 | |
| 1267 | if (vertical) |
| 1268 | { |
| 1269 | // Stack buttons vertically. |
| 1270 | pstart = tbuffer; |
| 1271 | do |
| 1272 | { |
| 1273 | pend = vim_strchr(pstart, DLG_BUTTON_SEP); |
| 1274 | if (pend == NULL) |
| 1275 | pend = pstart + STRLEN(pstart); // Last button name. |
| 1276 | textWidth = GetTextWidth(hdc, pstart, pend - pstart); |
| 1277 | textWidth += dlgPaddingX; /* Padding within button */ |
| 1278 | textWidth += DLG_VERT_PADDING_X * 2; /* Padding around button */ |
| 1279 | if (textWidth > dlgwidth) |
| 1280 | dlgwidth = textWidth; |
| 1281 | pstart = pend + 1; |
| 1282 | } while (*pend != NUL); |
| 1283 | } |
| 1284 | |
| 1285 | if (dlgwidth < DLG_MIN_WIDTH) |
| 1286 | dlgwidth = DLG_MIN_WIDTH; /* Don't allow a really thin dialog!*/ |
| 1287 | |
| 1288 | /* start to fill in the dlgtemplate information. addressing by WORDs */ |
| 1289 | lStyle = DS_MODALFRAME | WS_CAPTION | WS_VISIBLE ; |
| 1290 | |
| 1291 | add_long(lStyle); |
| 1292 | pnumitems = p; /*save where the number of items must be stored*/ |
| 1293 | add_byte(0); // NumberOfItems(will change later) |
| 1294 | add_word(10); // x |
| 1295 | add_word(10); // y |
| 1296 | add_word(PixelToDialogX(dlgwidth)); |
| 1297 | |
| 1298 | // Dialog height. |
| 1299 | if (vertical) |
| 1300 | dlgheight = msgheight + 2 * dlgPaddingY + |
| 1301 | DLG_VERT_PADDING_Y + 2 * fontHeight * numButtons; |
| 1302 | else |
| 1303 | dlgheight = msgheight + 3 * dlgPaddingY + 2 * fontHeight; |
| 1304 | |
| 1305 | // Dialog needs to be taller if contains an edit box. |
| 1306 | editboxheight = fontHeight + dlgPaddingY + 4 * DLG_VERT_PADDING_Y; |
| 1307 | if (textfield != NULL) |
| 1308 | dlgheight += editboxheight; |
| 1309 | |
| 1310 | add_word(PixelToDialogY(dlgheight)); |
| 1311 | |
| 1312 | add_byte(0); //menu |
| 1313 | add_byte(0); //class |
| 1314 | |
| 1315 | /* copy the title of the dialog */ |
| 1316 | add_string(title ? title : ("Vim"VIM_VERSION_MEDIUM)); |
| 1317 | |
| 1318 | buttonYpos = msgheight + 2 * dlgPaddingY; |
| 1319 | |
| 1320 | if (textfield != NULL) |
| 1321 | buttonYpos += editboxheight; |
| 1322 | |
| 1323 | pstart = tbuffer; //dflt_text |
| 1324 | horizWidth = (dlgwidth - horizWidth) / 2; /* Now it's X offset */ |
| 1325 | for (i = 0; i < numButtons; i++) |
| 1326 | { |
| 1327 | /* get end of this button. */ |
| 1328 | for ( pend = pstart; |
| 1329 | *pend && (*pend != DLG_BUTTON_SEP); |
| 1330 | pend++) |
| 1331 | ; |
| 1332 | |
| 1333 | if (*pend) |
| 1334 | *pend = '\0'; |
| 1335 | |
| 1336 | /* |
| 1337 | * NOTE: |
| 1338 | * setting the BS_DEFPUSHBUTTON style doesn't work because Windows sets |
| 1339 | * the focus to the first tab-able button and in so doing makes that |
| 1340 | * the default!! Grrr. Workaround: Make the default button the only |
| 1341 | * one with WS_TABSTOP style. Means user can't tab between buttons, but |
| 1342 | * he/she can use arrow keys. |
| 1343 | * |
| 1344 | * NOTE (Thore): Setting BS_DEFPUSHBUTTON works fine when it's the |
| 1345 | * first one, so I changed the correct button to be this style. This |
| 1346 | * is necessary because when an edit box is added, we need a button to |
| 1347 | * be default. The edit box will be the default control, and when the |
| 1348 | * user presses enter from the edit box we want the default button to |
| 1349 | * be pressed. |
| 1350 | */ |
| 1351 | if (vertical) |
| 1352 | { |
| 1353 | p = add_dialog_element(p, |
| 1354 | ((i == dfltbutton || dfltbutton < 0) && textfield != NULL |
| 1355 | ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, |
| 1356 | PixelToDialogX(DLG_VERT_PADDING_X), |
| 1357 | PixelToDialogY(buttonYpos /* TBK */ |
| 1358 | + 2 * fontHeight * i), |
| 1359 | PixelToDialogX(dlgwidth - 2 * DLG_VERT_PADDING_X), |
| 1360 | (WORD)(PixelToDialogY(2 * fontHeight) - 1), |
| 1361 | (WORD)(IDCANCEL + 1 + i), (BYTE)0x80, pstart); |
| 1362 | } |
| 1363 | else |
| 1364 | { |
| 1365 | p = add_dialog_element(p, |
| 1366 | ((i == dfltbutton || dfltbutton < 0) && textfield != NULL |
| 1367 | ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, |
| 1368 | PixelToDialogX(horizWidth + buttonPositions[i]), |
| 1369 | PixelToDialogY(buttonYpos), /* TBK */ |
| 1370 | PixelToDialogX(buttonWidths[i]), |
| 1371 | (WORD)(PixelToDialogY(2 * fontHeight) - 1), |
| 1372 | (WORD)(IDCANCEL + 1 + i), (BYTE)0x80, pstart); |
| 1373 | } |
| 1374 | |
| 1375 | pstart = pend + 1; /*next button*/ |
| 1376 | |
| 1377 | } |
| 1378 | *pnumitems += numButtons; |
| 1379 | |
| 1380 | /* Vim icon */ |
| 1381 | p = add_dialog_element(p, SS_ICON, |
| 1382 | PixelToDialogX(dlgPaddingX), |
| 1383 | PixelToDialogY(dlgPaddingY), |
| 1384 | PixelToDialogX(DLG_ICON_WIDTH), |
| 1385 | PixelToDialogY(DLG_ICON_HEIGHT), |
| 1386 | DLG_NONBUTTON_CONTROL + 0, (BYTE)0x82, |
| 1387 | &dlg_icons[type]); |
| 1388 | |
| 1389 | |
| 1390 | /* Dialog message */ |
| 1391 | p = add_dialog_element(p, SS_LEFT, |
| 1392 | PixelToDialogX(2 * dlgPaddingX + DLG_ICON_WIDTH), |
| 1393 | PixelToDialogY(dlgPaddingY), |
| 1394 | (WORD)(PixelToDialogX(messageWidth) + 1), |
| 1395 | PixelToDialogY(msgheight), |
| 1396 | DLG_NONBUTTON_CONTROL + 1, (BYTE)0x82, message); |
| 1397 | |
| 1398 | /* Edit box */ |
| 1399 | if (textfield != NULL) |
| 1400 | { |
| 1401 | p = add_dialog_element(p, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP | WS_BORDER, |
| 1402 | PixelToDialogX(2 * dlgPaddingX), |
| 1403 | PixelToDialogY(2 * dlgPaddingY + msgheight), |
| 1404 | PixelToDialogX(dlgwidth - 4 * dlgPaddingX), |
| 1405 | PixelToDialogY(fontHeight + dlgPaddingY), |
| 1406 | DLG_NONBUTTON_CONTROL + 2, (BYTE)0x81, textfield); |
| 1407 | *pnumitems += 1; |
| 1408 | } |
| 1409 | |
| 1410 | *pnumitems += 2; |
| 1411 | |
| 1412 | SelectFont(hdc, oldFont); |
| 1413 | ReleaseDC(hwnd, hdc); |
| 1414 | dp = MakeProcInstance((FARPROC)dialog_callback, s_hinst); |
| 1415 | |
| 1416 | |
| 1417 | /* Let the dialog_callback() function know which button to make default |
| 1418 | * If we have an edit box, make that the default. We also need to tell |
| 1419 | * dialog_callback() if this dialog contains an edit box or not. We do |
| 1420 | * this by setting s_textfield if it does. |
| 1421 | */ |
| 1422 | if (textfield != NULL) |
| 1423 | { |
| 1424 | dialog_default_button = DLG_NONBUTTON_CONTROL + 2; |
| 1425 | s_textfield = textfield; |
| 1426 | } |
| 1427 | else |
| 1428 | { |
| 1429 | dialog_default_button = IDCANCEL + 1 + dfltbutton; |
| 1430 | s_textfield = NULL; |
| 1431 | } |
| 1432 | |
| 1433 | /*show the dialog box modally and get a return value*/ |
| 1434 | nchar = DialogBoxIndirect( |
| 1435 | s_hinst, |
| 1436 | (HGLOBAL) hglbDlgTemp, |
| 1437 | s_hwnd, |
| 1438 | (DLGPROC)dp); |
| 1439 | |
| 1440 | FreeProcInstance( dp ); |
| 1441 | GlobalUnlock(hglbDlgTemp); |
| 1442 | GlobalFree(hglbDlgTemp); |
| 1443 | vim_free(tbuffer); |
| 1444 | vim_free(buttonWidths); |
| 1445 | vim_free(buttonPositions); |
| 1446 | |
| 1447 | |
| 1448 | return nchar; |
| 1449 | } |
| 1450 | |
| 1451 | /* |
| 1452 | * Put a simple element (basic class) onto a dialog template in memory. |
| 1453 | * return a pointer to where the next item should be added. |
| 1454 | * |
| 1455 | * parameters: |
| 1456 | * lStyle = additional style flags |
| 1457 | * x,y = x & y positions IN DIALOG UNITS |
| 1458 | * w,h = width and height IN DIALOG UNITS |
| 1459 | * Id = ID used in messages |
| 1460 | * clss = class ID, e.g 0x80 for a button, 0x82 for a static |
| 1461 | * caption = usually text or resource name |
| 1462 | * |
| 1463 | * TODO: use the length information noted here to enable the dialog creation |
| 1464 | * routines to work out more exactly how much memory they need to alloc. |
| 1465 | */ |
| 1466 | static LPWORD |
| 1467 | add_dialog_element( |
| 1468 | LPWORD p, |
| 1469 | DWORD lStyle, |
| 1470 | WORD x, |
| 1471 | WORD y, |
| 1472 | WORD w, |
| 1473 | WORD h, |
| 1474 | WORD Id, |
| 1475 | BYTE clss, |
| 1476 | const char *caption) |
| 1477 | { |
| 1478 | |
| 1479 | lStyle = lStyle | WS_VISIBLE | WS_CHILD; |
| 1480 | |
| 1481 | add_word(x); |
| 1482 | add_word(y); |
| 1483 | add_word(w); |
| 1484 | add_word(h); |
| 1485 | add_word(Id); |
| 1486 | add_long(lStyle); |
| 1487 | add_byte(clss); |
| 1488 | if (((lStyle & SS_ICON) != 0) && (clss == 0x82)) |
| 1489 | { |
| 1490 | /* Use resource ID */ |
| 1491 | add_byte(0xff); |
| 1492 | add_byte(*caption); |
| 1493 | } |
| 1494 | else |
| 1495 | add_string(caption); |
| 1496 | |
| 1497 | add_byte(0); //# of extra bytes following |
| 1498 | |
| 1499 | |
| 1500 | return p; |
| 1501 | } |
| 1502 | |
| 1503 | #undef add_byte |
| 1504 | #undef add_string |
| 1505 | #undef add_long |
| 1506 | #undef add_word |
| 1507 | |
| 1508 | #endif /* FEAT_GUI_DIALOG */ |
| 1509 | |
| 1510 | static void |
| 1511 | get_dialog_font_metrics(void) |
| 1512 | { |
| 1513 | DWORD dlgFontSize; |
| 1514 | dlgFontSize = GetDialogBaseUnits(); /* fall back to big old system*/ |
| 1515 | s_dlgfntwidth = LOWORD(dlgFontSize); |
| 1516 | s_dlgfntheight = HIWORD(dlgFontSize); |
| 1517 | } |
| 1518 | |
| 1519 | |
| 1520 | #if defined(FEAT_TOOLBAR) || defined(PROTO) |
| 1521 | #include "gui_w3~1.h" |
| 1522 | /* |
| 1523 | * Create the toolbar, initially unpopulated. |
| 1524 | * (just like the menu, there are no defaults, it's all |
| 1525 | * set up through menu.vim) |
| 1526 | */ |
| 1527 | static void |
| 1528 | initialise_toolbar(void) |
| 1529 | { |
| 1530 | s_toolbarhwnd = CreateToolbar( |
| 1531 | s_hwnd, |
| 1532 | WS_CHILD | WS_VISIBLE, |
| 1533 | CMD_TB_BASE, /*<vn>*/ |
Bram Moolenaar | 7263a77 | 2007-05-10 17:35:54 +0000 | [diff] [blame^] | 1534 | 31, //number of images in initial bitmap |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 1535 | s_hinst, |
| 1536 | IDR_TOOLBAR1, // id of initial bitmap |
| 1537 | NULL, |
| 1538 | 0 // initial number of buttons |
| 1539 | ); |
| 1540 | |
| 1541 | gui_mch_show_toolbar(vim_strchr(p_go, GO_TOOLBAR) != NULL); |
| 1542 | } |
| 1543 | #endif |
| 1544 | |
| 1545 | #if defined(FEAT_OLE) || defined(FEAT_EVAL) || defined(PROTO) |
| 1546 | /* |
| 1547 | * Make the GUI window come to the foreground. |
| 1548 | */ |
| 1549 | void |
| 1550 | gui_mch_set_foreground(void) |
| 1551 | { |
| 1552 | if (IsIconic(s_hwnd)) |
| 1553 | SendMessage(s_hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); |
| 1554 | SetActiveWindow(s_hwnd); |
| 1555 | } |
| 1556 | #endif |