/* vi:set ts=8 sts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * GUI support by Robert Webb * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * gui_w16.c * * GUI support for Microsoft Windows 3.1x * * George V. Reilly wrote the original Win32 GUI. * Robert Webb reworked it to use the existing GUI stuff and added menu, * scrollbars, etc. * * Vince Negri then butchered the code to get it compiling for * 16-bit windows. * */ /* * Include the common stuff for MS-Windows GUI. */ #include "gui_w48.c" #include "guiw16rc.h" /* Undocumented Windows Message - not even defined in some SDK headers */ #define WM_EXITSIZEMOVE 0x0232 #ifdef FEAT_TOOLBAR # define CMD_TB_BASE (99) # include #endif #ifdef PROTO # define WINAPI #endif #define HANDLE_WM_DROPFILES(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (HDROP)(wParam)), 0L) /* Local variables: */ #ifdef FEAT_MENU static UINT s_menu_id = 100; #endif #define VIM_NAME "vim" #define VIM_CLASS "Vim" #define DLG_ALLOC_SIZE 16 * 1024 /* * stuff for dialogs, menus, tearoffs etc. */ #if defined(FEAT_GUI_DIALOG) || defined(PROTO) static BOOL CALLBACK dialog_callback(HWND, UINT, WPARAM, LPARAM); static LPWORD add_dialog_element( LPWORD p, DWORD lStyle, WORD x, WORD y, WORD w, WORD h, WORD Id, BYTE clss, const char *caption); static int dialog_default_button = -1; #endif static void get_dialog_font_metrics(void); #ifdef FEAT_TOOLBAR static void initialise_toolbar(void); #endif #ifdef FEAT_MENU /* * Figure out how high the menu bar is at the moment. */ static int gui_mswin_get_menu_height( int fix_window) /* If TRUE, resize window if menu height changed */ { static int old_menu_height = -1; int num; int menu_height; if (gui.menu_is_active) num = GetMenuItemCount(s_menuBar); else num = 0; if (num == 0) menu_height = 0; else if (gui.starting) menu_height = GetSystemMetrics(SM_CYMENU); else { RECT r1, r2; int frameht = GetSystemMetrics(SM_CYFRAME); int capht = GetSystemMetrics(SM_CYCAPTION); /* get window rect of s_hwnd * get client rect of s_hwnd * get cap height * subtract from window rect, the sum of client height, * (if not maximized)frame thickness, and caption height. */ GetWindowRect(s_hwnd, &r1); GetClientRect(s_hwnd, &r2); menu_height = r1.bottom - r1.top - (r2.bottom-r2.top + 2 * frameht * (!IsZoomed(s_hwnd)) + capht); } if (fix_window && menu_height != old_menu_height) { old_menu_height = menu_height; gui_set_shellsize(FALSE, FALSE, RESIZE_VERT); } return menu_height; } #endif /*FEAT_MENU*/ /* * Even though we have _DuringSizing() which makes the rubber band a valid * size, we need this for when the user maximises the window. * TODO: Doesn't seem to adjust the width though for some reason. */ static BOOL _OnWindowPosChanging( HWND hwnd, LPWINDOWPOS lpwpos) { if (!IsIconic(hwnd) && !(lpwpos->flags & SWP_NOSIZE)) { gui_mswin_get_valid_dimensions(lpwpos->cx, lpwpos->cy, &lpwpos->cx, &lpwpos->cy); } return 0; } static LRESULT CALLBACK _WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* TRACE("WndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n", hwnd, uMsg, wParam, lParam); */ HandleMouseHide(uMsg, lParam); s_uMsg = uMsg; s_wParam = wParam; s_lParam = lParam; switch (uMsg) { HANDLE_MSG(hwnd, WM_DEADCHAR, _OnDeadChar); HANDLE_MSG(hwnd, WM_SYSDEADCHAR, _OnDeadChar); /* HANDLE_MSG(hwnd, WM_ACTIVATE, _OnActivate); */ HANDLE_MSG(hwnd, WM_CHAR, _OnChar); HANDLE_MSG(hwnd, WM_CLOSE, _OnClose); /* HANDLE_MSG(hwnd, WM_COMMAND, _OnCommand); */ HANDLE_MSG(hwnd, WM_DESTROY, _OnDestroy); HANDLE_MSG(hwnd, WM_DROPFILES, _OnDropFiles); HANDLE_MSG(hwnd, WM_HSCROLL, _OnScroll); HANDLE_MSG(hwnd, WM_KILLFOCUS, _OnKillFocus); #ifdef FEAT_MENU HANDLE_MSG(hwnd, WM_COMMAND, _OnMenu); #endif /* HANDLE_MSG(hwnd, WM_MOVE, _OnMove); */ /* HANDLE_MSG(hwnd, WM_NCACTIVATE, _OnNCActivate); */ HANDLE_MSG(hwnd, WM_SETFOCUS, _OnSetFocus); HANDLE_MSG(hwnd, WM_SIZE, _OnSize); /* HANDLE_MSG(hwnd, WM_SYSCOMMAND, _OnSysCommand); */ /* HANDLE_MSG(hwnd, WM_SYSKEYDOWN, _OnAltKey); */ HANDLE_MSG(hwnd, WM_VSCROLL, _OnScroll); HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING, _OnWindowPosChanging); HANDLE_MSG(hwnd, WM_ACTIVATEAPP, _OnActivateApp); case WM_QUERYENDSESSION: /* System wants to go down. */ gui_shell_closed(); /* Will exit when no changed buffers. */ return FALSE; /* Do NOT allow system to go down. */ case WM_ENDSESSION: if (wParam) /* system only really goes down when wParam is TRUE */ _OnEndSession(); break; case WM_SYSCHAR: /* * if 'winaltkeys' is "no", or it's "menu" and it's not a menu * shortcut key, handle like a typed ALT key, otherwise call Windows * ALT key handling. */ #ifdef FEAT_MENU if ( !gui.menu_is_active || p_wak[0] == 'n' || (p_wak[0] == 'm' && !gui_is_menu_shortcut((int)wParam)) ) #endif return HANDLE_WM_SYSCHAR((hwnd), (wParam), (lParam), (_OnSysChar)); #ifdef FEAT_MENU else return MyWindowProc(hwnd, uMsg, wParam, lParam); #endif case WM_SYSKEYUP: #ifdef FEAT_MENU /* Only when menu is active, ALT key is used for that. */ if (gui.menu_is_active) { return MyWindowProc(hwnd, uMsg, wParam, lParam); } else #endif return 0; #if defined(MENUHINTS) && defined(FEAT_MENU) case WM_MENUSELECT: if (((UINT) LOWORD(lParam) & (0xffff ^ (MF_MOUSESELECT + MF_BITMAP + MF_POPUP))) == MF_HILITE && (State & CMDLINE) == 0) { UINT idButton; int idx; vimmenu_T *pMenu; idButton = (UINT)LOWORD(wParam); pMenu = gui_mswin_find_menu(root_menu, idButton); if (pMenu) { idx = MENU_INDEX_TIP; msg_clr_cmdline(); if (pMenu->strings[idx]) msg(pMenu->strings[idx]); else msg(""); setcursor(); out_flush(); } } break; #endif case WM_NCHITTEST: { LRESULT result; int x, y; int xPos = GET_X_LPARAM(lParam); result = MyWindowProc(hwnd, uMsg, wParam, lParam); if (result == HTCLIENT) { gui_mch_get_winpos(&x, &y); xPos -= x; if (xPos < 48) /* TODO should use system metric?*/ return HTBOTTOMLEFT; else return HTBOTTOMRIGHT; } else return result; } /* break; */ default: #ifdef MSWIN_FIND_REPLACE if (uMsg == s_findrep_msg && s_findrep_msg != 0) { _OnFindRepl(); } #endif return MyWindowProc(hwnd, uMsg, wParam, lParam); } return 1; } /* * End of call-back routines */ /* * Parse the GUI related command-line arguments. Any arguments used are * deleted from argv, and *argc is decremented accordingly. This is called * when vim is started, whether or not the GUI has been started. */ void gui_mch_prepare(int *argc, char **argv) { /* No special args for win16 GUI at the moment. */ } /* * Initialise the GUI. Create all the windows, set up all the call-backs * etc. */ int gui_mch_init(void) { const char szVimWndClass[] = VIM_CLASS; const char szTextAreaClass[] = "VimTextArea"; WNDCLASS wndclass; #ifdef WIN16_3DLOOK Ctl3dRegister(s_hinst); Ctl3dAutoSubclass(s_hinst); #endif /* Display any pending error messages */ display_errors(); gui.scrollbar_width = GetSystemMetrics(SM_CXVSCROLL); gui.scrollbar_height = GetSystemMetrics(SM_CYHSCROLL); #ifdef FEAT_MENU gui.menu_height = 0; /* Windows takes care of this */ #endif gui.border_width = 0; gui.currBgColor = INVALCOLOR; s_brush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)); if (GetClassInfo(s_hinst, szVimWndClass, &wndclass) == 0) { wndclass.style = 0; wndclass.lpfnWndProc = _WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = s_hinst; wndclass.hIcon = LoadIcon(wndclass.hInstance, MAKEINTRESOURCE(IDR_VIM)); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = s_brush; wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szVimWndClass; if (( #ifdef GLOBAL_IME atom = #endif RegisterClass(&wndclass)) == 0) return FAIL; } s_hwnd = CreateWindow( szVimWndClass, "Vim MSWindows GUI", WS_OVERLAPPEDWINDOW, gui_win_x == -1 ? CW_USEDEFAULT : gui_win_x, gui_win_y == -1 ? CW_USEDEFAULT : gui_win_y, 100, /* Any value will do */ 100, /* Any value will do */ NULL, NULL, s_hinst, NULL); if (s_hwnd == NULL) return FAIL; #ifdef GLOBAL_IME global_ime_init(atom, s_hwnd); #endif /* Create the text area window */ if (GetClassInfo(s_hinst, szTextAreaClass, &wndclass) == 0) { wndclass.style = CS_OWNDC; wndclass.lpfnWndProc = _TextAreaWndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = s_hinst; wndclass.hIcon = NULL; wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = NULL; wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szTextAreaClass; if (RegisterClass(&wndclass) == 0) return FAIL; } s_textArea = CreateWindow( szTextAreaClass, "Vim text area", WS_CHILD | WS_VISIBLE, 0, 0, 100, /* Any value will do for now */ 100, /* Any value will do for now */ s_hwnd, NULL, s_hinst, NULL); if (s_textArea == NULL) return FAIL; #ifdef FEAT_MENU s_menuBar = CreateMenu(); #endif s_hdc = GetDC(s_textArea); #ifdef MSWIN16_FASTTEXT SetBkMode(s_hdc, OPAQUE); #endif DragAcceptFiles(s_hwnd, TRUE); /* Do we need to bother with this? */ /* m_fMouseAvail = GetSystemMetrics(SM_MOUSEPRESENT); */ /* Get background/foreground colors from the system */ gui_mch_def_colors(); /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc * file) */ set_normal_colors(); /* * Check that none of the colors are the same as the background color. * Then store the current values as the defaults. */ gui_check_colors(); gui.def_norm_pixel = gui.norm_pixel; gui.def_back_pixel = gui.back_pixel; /* Get the colors for the highlight groups (gui_check_colors() might have * changed them) */ highlight_gui_started(); /* * Start out by adding the configured border width into the border offset */ gui.border_offset = gui.border_width; /* * compute a couple of metrics used for the dialogs */ get_dialog_font_metrics(); #ifdef FEAT_TOOLBAR /* * Create the toolbar */ initialise_toolbar(); #endif #ifdef MSWIN_FIND_REPLACE /* * Initialise the dialog box stuff */ s_findrep_msg = RegisterWindowMessage(FINDMSGSTRING); /* Initialise the struct */ s_findrep_struct.lStructSize = sizeof(s_findrep_struct); s_findrep_struct.lpstrFindWhat = alloc(MSWIN_FR_BUFSIZE); s_findrep_struct.lpstrFindWhat[0] = NUL; s_findrep_struct.lpstrReplaceWith = alloc(MSWIN_FR_BUFSIZE); s_findrep_struct.lpstrReplaceWith[0] = NUL; s_findrep_struct.wFindWhatLen = MSWIN_FR_BUFSIZE; s_findrep_struct.wReplaceWithLen = MSWIN_FR_BUFSIZE; #endif return OK; } /* * Set the size of the window to the given width and height in pixels. */ void gui_mch_set_shellsize(int width, int height, int min_width, int min_height, int base_width, int base_height, int direction) { RECT workarea_rect; int win_width, win_height; int win_xpos, win_ypos; WINDOWPLACEMENT wndpl; /* try to keep window completely on screen */ /* get size of the screen work area - use SM_CYFULLSCREEN * instead of SM_CYSCREEN so that we don't overlap the * taskbar if someone fires us up on Win95/NT */ workarea_rect.left = 0; workarea_rect.top = 0; workarea_rect.right = GetSystemMetrics(SM_CXSCREEN); workarea_rect.bottom = GetSystemMetrics(SM_CYFULLSCREEN); /* get current posision of our window */ wndpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(s_hwnd, &wndpl); if (wndpl.showCmd == SW_SHOWNORMAL) { win_xpos = wndpl.rcNormalPosition.left; win_ypos = wndpl.rcNormalPosition.top; } else { win_xpos = workarea_rect.left; win_ypos = workarea_rect.top; } /* compute the size of the outside of the window */ win_width = width + GetSystemMetrics(SM_CXFRAME) * 2; win_height = height + GetSystemMetrics(SM_CYFRAME) * 2 + GetSystemMetrics(SM_CYCAPTION) #ifdef FEAT_MENU + gui_mswin_get_menu_height(FALSE) #endif ; /* if the window is going off the screen, move it on to the screen */ if ((direction & RESIZE_HOR) && win_xpos + win_width > workarea_rect.right) win_xpos = workarea_rect.right - win_width; if ((direction & RESIZE_HOR) && win_xpos < workarea_rect.left) win_xpos = workarea_rect.left; if ((direction & RESIZE_VERT) && win_ypos + win_height > workarea_rect.bottom) win_ypos = workarea_rect.bottom - win_height; if ((direction & RESIZE_VERT) && win_ypos < workarea_rect.top) win_ypos = workarea_rect.top; /* set window position */ SetWindowPos(s_hwnd, NULL, win_xpos, win_ypos, win_width, win_height, SWP_NOZORDER | SWP_NOACTIVATE); #ifdef FEAT_MENU /* Menu may wrap differently now */ gui_mswin_get_menu_height(!gui.starting); #endif } void gui_mch_set_scrollbar_thumb( scrollbar_T *sb, long val, long size, long max) { sb->scroll_shift = 0; while (max > 32767) { max = (max + 1) >> 1; val >>= 1; size >>= 1; ++sb->scroll_shift; } if (sb->scroll_shift > 0) ++size; SetScrollRange(sb->id, SB_CTL, 0, (int) max, FALSE); SetScrollPos(sb->id, SB_CTL, (int) val, TRUE); } /* * Set the current text font. */ void gui_mch_set_font(GuiFont font) { gui.currFont = font; SelectFont(s_hdc, gui.currFont); } /* * Set the current text foreground color. */ void gui_mch_set_fg_color(guicolor_T color) { gui.currFgColor = color; SetTextColor(s_hdc, gui.currFgColor); } /* * Set the current text background color. */ void gui_mch_set_bg_color(guicolor_T color) { if (gui.currBgColor == color) return; gui.currBgColor = color; SetBkColor(s_hdc, gui.currBgColor); } /* * Set the current text special color. */ void gui_mch_set_sp_color(guicolor_T color) { /* TODO */ } void gui_mch_draw_string( int row, int col, char_u *text, int len, int flags) { #ifndef MSWIN16_FASTTEXT static int *padding = NULL; static int pad_size = 0; int i; #endif HPEN hpen, old_pen; int y; #ifndef MSWIN16_FASTTEXT /* * Italic and bold text seems to have an extra row of pixels at the bottom * (below where the bottom of the character should be). If we draw the * characters with a solid background, the top row of pixels in the * character below will be overwritten. We can fix this by filling in the * background ourselves, to the correct character proportions, and then * writing the character in transparent mode. Still have a problem when * the character is "_", which gets written on to the character below. * New fix: set gui.char_ascent to -1. This shifts all characters up one * pixel in their slots, which fixes the problem with the bottom row of * pixels. We still need this code because otherwise the top row of pixels * becomes a problem. - webb. */ HBRUSH hbr; RECT rc; if (!(flags & DRAW_TRANSP)) { /* * Clear background first. * Note: FillRect() excludes right and bottom of rectangle. */ rc.left = FILL_X(col); rc.top = FILL_Y(row); #ifdef FEAT_MBYTE if (has_mbyte) { /* Compute the length in display cells. */ rc.right = FILL_X(col + mb_string2cells(text, len)); } else #endif rc.right = FILL_X(col + len); rc.bottom = FILL_Y(row + 1); hbr = CreateSolidBrush(gui.currBgColor); FillRect(s_hdc, &rc, hbr); DeleteBrush(hbr); SetBkMode(s_hdc, TRANSPARENT); /* * When drawing block cursor, prevent inverted character spilling * over character cell (can happen with bold/italic) */ if (flags & DRAW_CURSOR) { pcliprect = &rc; foptions = ETO_CLIPPED; } } #else /* * Alternative: write the characters in opaque mode, since we have blocked * bold or italic fonts. */ /* The OPAQUE mode and backcolour have already been set */ #endif /* The forecolor and font have already been set */ #ifndef MSWIN16_FASTTEXT if (pad_size != Columns || padding == NULL || padding[0] != gui.char_width) { vim_free(padding); pad_size = Columns; padding = (int *)alloc(pad_size * sizeof(int)); if (padding != NULL) for (i = 0; i < pad_size; i++) padding[i] = gui.char_width; } #endif /* * We have to provide the padding argument because italic and bold versions * of fixed-width fonts are often one pixel or so wider than their normal * versions. * No check for DRAW_BOLD, Windows will have done it already. */ #ifndef MSWIN16_FASTTEXT ExtTextOut(s_hdc, TEXT_X(col), TEXT_Y(row), 0, NULL, (char *)text, len, padding); #else TextOut(s_hdc, TEXT_X(col), TEXT_Y(row), (char *)text, len); #endif if (flags & DRAW_UNDERL) { hpen = CreatePen(PS_SOLID, 1, gui.currFgColor); old_pen = SelectObject(s_hdc, hpen); /* When p_linespace is 0, overwrite the bottom row of pixels. * Otherwise put the line just below the character. */ y = FILL_Y(row + 1) - 1; #ifndef MSWIN16_FASTTEXT if (p_linespace > 1) y -= p_linespace - 1; #endif MoveToEx(s_hdc, FILL_X(col), y, NULL); /* Note: LineTo() excludes the last pixel in the line. */ LineTo(s_hdc, FILL_X(col + len), y); DeleteObject(SelectObject(s_hdc, old_pen)); } } /* * Output routines. */ /* Flush any output to the screen */ void gui_mch_flush(void) { /* Is anything needed here? */ } static void clear_rect(RECT *rcp) { /* Use trick for fast rect clear */ gui_mch_set_bg_color(gui.back_pixel); ExtTextOut(s_hdc, 0, 0, ETO_CLIPPED | ETO_OPAQUE, rcp, NULL, 0, NULL); } void gui_mch_get_screen_dimensions(int *screen_w, int *screen_h) { *screen_w = GetSystemMetrics(SM_CXFULLSCREEN) - GetSystemMetrics(SM_CXFRAME) * 2; /* FIXME: dirty trick: Because the gui_get_base_height() doesn't include * the menubar for MSwin, we subtract it from the screen height, so that * the window size can be made to fit on the screen. */ *screen_h = GetSystemMetrics(SM_CYFULLSCREEN) - GetSystemMetrics(SM_CYFRAME) * 2 #ifdef FEAT_MENU - gui_mswin_get_menu_height(FALSE) #endif ; } #if defined(FEAT_MENU) || defined(PROTO) /* * Add a sub menu to the menu bar. */ void gui_mch_add_menu( vimmenu_T *menu, int pos) { vimmenu_T *parent = menu->parent; menu->submenu_id = CreatePopupMenu(); menu->id = s_menu_id++; if (menu_is_menubar(menu->name)) { InsertMenu((parent == NULL) ? s_menuBar : parent->submenu_id, (UINT)pos, MF_POPUP | MF_STRING | MF_BYPOSITION, (UINT)menu->submenu_id, menu->name); } /* Fix window size if menu may have wrapped */ if (parent == NULL) gui_mswin_get_menu_height(!gui.starting); } void gui_mch_show_popupmenu(vimmenu_T *menu) { POINT mp; (void)GetCursorPos((LPPOINT)&mp); gui_mch_show_popupmenu_at(menu, (int)mp.x, (int)mp.y); } void gui_make_popup(char_u *path_name, int mouse_pos) { vimmenu_T *menu = gui_find_menu(path_name); if (menu != NULL) { /* Find the position of the current cursor */ DWORD temp_p; POINT p; temp_p = GetDCOrg(s_hdc); p.x = LOWORD(temp_p); p.y = HIWORD(temp_p); if (mouse_pos) { int mx, my; gui_mch_getmouse(&mx, &my); p.x += mx; p.y += my; } else if (curwin != NULL) { p.x += TEXT_X(W_WINCOL(curwin) + curwin->w_wcol + 1); p.y += TEXT_Y(W_WINROW(curwin) + curwin->w_wrow + 1); } msg_scroll = FALSE; gui_mch_show_popupmenu_at(menu, (int)p.x, (int)p.y); } } /* * Add a menu item to a menu */ void gui_mch_add_menu_item( vimmenu_T *menu, int idx) { vimmenu_T *parent = menu->parent; menu->id = s_menu_id++; menu->submenu_id = NULL; #ifdef FEAT_TOOLBAR if (menu_is_toolbar(parent->name)) { TBBUTTON newtb; vim_memset(&newtb, 0, sizeof(newtb)); if (menu_is_separator(menu->name)) { newtb.iBitmap = 0; newtb.fsStyle = TBSTYLE_SEP; } else { if (menu->iconidx >= TOOLBAR_BITMAP_COUNT) newtb.iBitmap = -1; else newtb.iBitmap = menu->iconidx; newtb.fsStyle = TBSTYLE_BUTTON; } newtb.idCommand = menu->id; newtb.fsState = TBSTATE_ENABLED; SendMessage(s_toolbarhwnd, TB_INSERTBUTTON, (WPARAM)idx, (LPARAM)&newtb); menu->submenu_id = (HMENU)-1; } else #endif { InsertMenu(parent->submenu_id, (UINT)idx, (menu_is_separator(menu->name) ? MF_SEPARATOR : MF_STRING) | MF_BYPOSITION, (UINT)menu->id, menu->name); } } /* * Destroy the machine specific menu widget. */ void gui_mch_destroy_menu(vimmenu_T *menu) { UINT i, j; char pants[80]; /* hack*/ #ifdef FEAT_TOOLBAR /* * is this a toolbar button? */ if (menu->submenu_id == (HMENU)-1) { int iButton; iButton = SendMessage(s_toolbarhwnd, TB_COMMANDTOINDEX, (WPARAM)menu->id, 0); SendMessage(s_toolbarhwnd, TB_DELETEBUTTON, (WPARAM)iButton, 0); } else #endif { /* * negri: horrible API bug when running 16-bit programs under Win9x or * NT means that we can't use MF_BYCOMMAND for menu items which have * submenus, including the top-level headings. We have to find the menu * item and use MF_BYPOSITION instead. :-p */ if (menu->parent != NULL && menu_is_popup(menu->parent->dname) && menu->parent->submenu_id != NULL) RemoveMenu(menu->parent->submenu_id, menu->id, MF_BYCOMMAND); else if (menu->submenu_id == NULL) RemoveMenu(s_menuBar, menu->id, MF_BYCOMMAND); else if (menu->parent != NULL) { i = GetMenuItemCount(menu->parent->submenu_id); for (j = 0; j < i; ++j) { GetMenuString(menu->parent->submenu_id, j, pants, 80, MF_BYPOSITION); if (strcmp(pants, menu->name) == 0) { RemoveMenu(menu->parent->submenu_id, j, MF_BYPOSITION); break; } } } else { i = GetMenuItemCount(s_menuBar); for (j = 0; j < i; ++j) { GetMenuString(s_menuBar, j, pants, 80, MF_BYPOSITION); if (strcmp(pants, menu->name) == 0) { RemoveMenu(s_menuBar, j, MF_BYPOSITION); break; } } } if (menu->submenu_id != NULL) DestroyMenu(menu->submenu_id); } DrawMenuBar(s_hwnd); } /* * Make a menu either grey or not grey. */ void gui_mch_menu_grey( vimmenu_T *menu, int grey) { #ifdef FEAT_TOOLBAR /* * is this a toolbar button? */ if (menu->submenu_id == (HMENU)-1) { SendMessage(s_toolbarhwnd, TB_ENABLEBUTTON, (WPARAM)menu->id, (LPARAM) MAKELONG((grey ? FALSE : TRUE), 0) ); } else #endif if (grey) EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_GRAYED); else EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED); } #endif /*FEAT_MENU*/ /* define some macros used to make the dialogue creation more readable */ #define add_string(s) strcpy((LPSTR)p, s); (LPSTR)p += (strlen((LPSTR)p) + 1) #define add_word(x) *p++ = (x) #define add_byte(x) *((LPSTR)p)++ = (x) #define add_long(x) *((LPDWORD)p)++ = (x) #if defined(FEAT_GUI_DIALOG) || defined(PROTO) /* * stuff for dialogs */ /* * The callback routine used by all the dialogs. Very simple. First, * acknowledges the INITDIALOG message so that Windows knows to do standard * dialog stuff (Return = default, Esc = cancel....) Second, if a button is * pressed, return that button's ID - IDCANCEL (2), which is the button's * number. */ static BOOL CALLBACK dialog_callback( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_INITDIALOG) { CenterWindow(hwnd, GetWindow(hwnd, GW_OWNER)); /* Set focus to the dialog. Set the default button, if specified. */ (void)SetFocus(hwnd); if (dialog_default_button > IDCANCEL) (void)SetFocus(GetDlgItem(hwnd, dialog_default_button)); // if (dialog_default_button > 0) // (void)SetFocus(GetDlgItem(hwnd, dialog_default_button + IDCANCEL)); return FALSE; } if (message == WM_COMMAND) { int button = LOWORD(wParam); /* Don't end the dialog if something was selected that was * not a button. */ if (button >= DLG_NONBUTTON_CONTROL) return TRUE; /* If the edit box exists, copy the string. */ if (s_textfield != NULL) GetDlgItemText(hwnd, DLG_NONBUTTON_CONTROL + 2, s_textfield, IOSIZE); /* * Need to check for IDOK because if the user just hits Return to * accept the default value, some reason this is what we get. */ if (button == IDOK) EndDialog(hwnd, dialog_default_button); else EndDialog(hwnd, button - IDCANCEL); return TRUE; } if ((message == WM_SYSCOMMAND) && (wParam == SC_CLOSE)) { EndDialog(hwnd, 0); return TRUE; } return FALSE; } /* * Create a dialog dynamically from the parameter strings. * type = type of dialog (question, alert, etc.) * title = dialog title. may be NULL for default title. * message = text to display. Dialog sizes to accommodate it. * buttons = '\n' separated list of button captions, default first. * dfltbutton = number of default button. * * This routine returns 1 if the first button is pressed, * 2 for the second, etc. * * 0 indicates Esc was pressed. * -1 for unexpected error * * If stubbing out this fn, return 1. */ static const char_u dlg_icons[] = /* must match names in resource file */ { IDR_VIM, IDR_VIM_ERROR, IDR_VIM_ALERT, IDR_VIM_INFO, IDR_VIM_QUESTION }; int gui_mch_dialog( int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd) { FARPROC dp; LPWORD p, pnumitems; int numButtons; int *buttonWidths, *buttonPositions; int buttonYpos; int nchar, i; DWORD lStyle; int dlgwidth = 0; int dlgheight; int editboxheight; int horizWidth; int msgheight; char_u *pstart; char_u *pend; char_u *tbuffer; RECT rect; HWND hwnd; HDC hdc; HFONT oldFont; TEXTMETRIC fontInfo; int fontHeight; int textWidth, minButtonWidth, messageWidth; int maxDialogWidth; int vertical; int dlgPaddingX; int dlgPaddingY; HGLOBAL hglbDlgTemp; #ifndef NO_CONSOLE /* Don't output anything in silent mode ("ex -s") */ if (silent_mode) return dfltbutton; /* return default option */ #endif /* If there is no window yet, open it. */ if (s_hwnd == NULL && gui_mch_init() == FAIL) return dfltbutton; if ((type < 0) || (type > VIM_LAST_TYPE)) type = 0; /* allocate some memory for dialog template */ /* TODO should compute this really*/ hglbDlgTemp = GlobalAlloc(GHND, DLG_ALLOC_SIZE); if (hglbDlgTemp == NULL) return -1; p = (LPWORD) GlobalLock(hglbDlgTemp); if (p == NULL) return -1; /* * make a copy of 'buttons' to fiddle with it. complier grizzles because * vim_strsave() doesn't take a const arg (why not?), so cast away the * const. */ tbuffer = vim_strsave(buttons); if (tbuffer == NULL) return -1; --dfltbutton; /* Change from one-based to zero-based */ /* Count buttons */ numButtons = 1; for (i = 0; tbuffer[i] != '\0'; i++) { if (tbuffer[i] == DLG_BUTTON_SEP) numButtons++; } if (dfltbutton >= numButtons) dfltbutton = 0; /* Allocate array to hold the width of each button */ buttonWidths = (int *) lalloc(numButtons * sizeof(int), TRUE); if (buttonWidths == NULL) return -1; /* Allocate array to hold the X position of each button */ buttonPositions = (int *) lalloc(numButtons * sizeof(int), TRUE); if (buttonPositions == NULL) return -1; /* * Calculate how big the dialog must be. */ hwnd = GetDesktopWindow(); hdc = GetWindowDC(hwnd); oldFont = SelectFont(hdc, GetStockObject(SYSTEM_FONT)); dlgPaddingX = DLG_OLD_STYLE_PADDING_X; dlgPaddingY = DLG_OLD_STYLE_PADDING_Y; GetTextMetrics(hdc, &fontInfo); fontHeight = fontInfo.tmHeight; /* Minimum width for horizontal button */ minButtonWidth = GetTextWidth(hdc, "Cancel", 6); /* Maximum width of a dialog, if possible */ GetWindowRect(s_hwnd, &rect); maxDialogWidth = rect.right - rect.left - GetSystemMetrics(SM_CXFRAME) * 2; if (maxDialogWidth < DLG_MIN_MAX_WIDTH) maxDialogWidth = DLG_MIN_MAX_WIDTH; /* Set dlgwidth to width of message */ pstart = message; messageWidth = 0; msgheight = 0; do { pend = vim_strchr(pstart, DLG_BUTTON_SEP); if (pend == NULL) pend = pstart + STRLEN(pstart); /* Last line of message. */ msgheight += fontHeight; textWidth = GetTextWidth(hdc, pstart, pend - pstart); if (textWidth > messageWidth) messageWidth = textWidth; pstart = pend + 1; } while (*pend != NUL); dlgwidth = messageWidth; /* Add width of icon to dlgwidth, and some space */ dlgwidth += DLG_ICON_WIDTH + 3 * dlgPaddingX; if (msgheight < DLG_ICON_HEIGHT) msgheight = DLG_ICON_HEIGHT; /* * Check button names. A long one will make the dialog wider. */ vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL); if (!vertical) { // Place buttons horizontally if they fit. horizWidth = dlgPaddingX; pstart = tbuffer; i = 0; do { pend = vim_strchr(pstart, DLG_BUTTON_SEP); if (pend == NULL) pend = pstart + STRLEN(pstart); // Last button name. textWidth = GetTextWidth(hdc, pstart, pend - pstart); if (textWidth < minButtonWidth) textWidth = minButtonWidth; textWidth += dlgPaddingX; /* Padding within button */ buttonWidths[i] = textWidth; buttonPositions[i++] = horizWidth; horizWidth += textWidth + dlgPaddingX; /* Pad between buttons */ pstart = pend + 1; } while (*pend != NUL); if (horizWidth > maxDialogWidth) vertical = TRUE; // Too wide to fit on the screen. else if (horizWidth > dlgwidth) dlgwidth = horizWidth; } if (vertical) { // Stack buttons vertically. pstart = tbuffer; do { pend = vim_strchr(pstart, DLG_BUTTON_SEP); if (pend == NULL) pend = pstart + STRLEN(pstart); // Last button name. textWidth = GetTextWidth(hdc, pstart, pend - pstart); textWidth += dlgPaddingX; /* Padding within button */ textWidth += DLG_VERT_PADDING_X * 2; /* Padding around button */ if (textWidth > dlgwidth) dlgwidth = textWidth; pstart = pend + 1; } while (*pend != NUL); } if (dlgwidth < DLG_MIN_WIDTH) dlgwidth = DLG_MIN_WIDTH; /* Don't allow a really thin dialog!*/ /* start to fill in the dlgtemplate information. addressing by WORDs */ lStyle = DS_MODALFRAME | WS_CAPTION | WS_VISIBLE ; add_long(lStyle); pnumitems = p; /*save where the number of items must be stored*/ add_byte(0); // NumberOfItems(will change later) add_word(10); // x add_word(10); // y add_word(PixelToDialogX(dlgwidth)); // Dialog height. if (vertical) dlgheight = msgheight + 2 * dlgPaddingY + DLG_VERT_PADDING_Y + 2 * fontHeight * numButtons; else dlgheight = msgheight + 3 * dlgPaddingY + 2 * fontHeight; // Dialog needs to be taller if contains an edit box. editboxheight = fontHeight + dlgPaddingY + 4 * DLG_VERT_PADDING_Y; if (textfield != NULL) dlgheight += editboxheight; add_word(PixelToDialogY(dlgheight)); add_byte(0); //menu add_byte(0); //class /* copy the title of the dialog */ add_string(title ? title : ("Vim"VIM_VERSION_MEDIUM)); buttonYpos = msgheight + 2 * dlgPaddingY; if (textfield != NULL) buttonYpos += editboxheight; pstart = tbuffer; //dflt_text horizWidth = (dlgwidth - horizWidth) / 2; /* Now it's X offset */ for (i = 0; i < numButtons; i++) { /* get end of this button. */ for ( pend = pstart; *pend && (*pend != DLG_BUTTON_SEP); pend++) ; if (*pend) *pend = '\0'; /* * NOTE: * setting the BS_DEFPUSHBUTTON style doesn't work because Windows sets * the focus to the first tab-able button and in so doing makes that * the default!! Grrr. Workaround: Make the default button the only * one with WS_TABSTOP style. Means user can't tab between buttons, but * he/she can use arrow keys. * * NOTE (Thore): Setting BS_DEFPUSHBUTTON works fine when it's the * first one, so I changed the correct button to be this style. This * is necessary because when an edit box is added, we need a button to * be default. The edit box will be the default control, and when the * user presses enter from the edit box we want the default button to * be pressed. */ if (vertical) { p = add_dialog_element(p, ((i == dfltbutton || dfltbutton < 0) && textfield != NULL ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, PixelToDialogX(DLG_VERT_PADDING_X), PixelToDialogY(buttonYpos /* TBK */ + 2 * fontHeight * i), PixelToDialogX(dlgwidth - 2 * DLG_VERT_PADDING_X), (WORD)(PixelToDialogY(2 * fontHeight) - 1), (WORD)(IDCANCEL + 1 + i), (BYTE)0x80, pstart); } else { p = add_dialog_element(p, ((i == dfltbutton || dfltbutton < 0) && textfield != NULL ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON) | WS_TABSTOP, PixelToDialogX(horizWidth + buttonPositions[i]), PixelToDialogY(buttonYpos), /* TBK */ PixelToDialogX(buttonWidths[i]), (WORD)(PixelToDialogY(2 * fontHeight) - 1), (WORD)(IDCANCEL + 1 + i), (BYTE)0x80, pstart); } pstart = pend + 1; /*next button*/ } *pnumitems += numButtons; /* Vim icon */ p = add_dialog_element(p, SS_ICON, PixelToDialogX(dlgPaddingX), PixelToDialogY(dlgPaddingY), PixelToDialogX(DLG_ICON_WIDTH), PixelToDialogY(DLG_ICON_HEIGHT), DLG_NONBUTTON_CONTROL + 0, (BYTE)0x82, &dlg_icons[type]); /* Dialog message */ p = add_dialog_element(p, SS_LEFT, PixelToDialogX(2 * dlgPaddingX + DLG_ICON_WIDTH), PixelToDialogY(dlgPaddingY), (WORD)(PixelToDialogX(messageWidth) + 1), PixelToDialogY(msgheight), DLG_NONBUTTON_CONTROL + 1, (BYTE)0x82, message); /* Edit box */ if (textfield != NULL) { p = add_dialog_element(p, ES_LEFT | ES_AUTOHSCROLL | WS_TABSTOP | WS_BORDER, PixelToDialogX(2 * dlgPaddingX), PixelToDialogY(2 * dlgPaddingY + msgheight), PixelToDialogX(dlgwidth - 4 * dlgPaddingX), PixelToDialogY(fontHeight + dlgPaddingY), DLG_NONBUTTON_CONTROL + 2, (BYTE)0x81, textfield); *pnumitems += 1; } *pnumitems += 2; SelectFont(hdc, oldFont); ReleaseDC(hwnd, hdc); dp = MakeProcInstance((FARPROC)dialog_callback, s_hinst); /* Let the dialog_callback() function know which button to make default * If we have an edit box, make that the default. We also need to tell * dialog_callback() if this dialog contains an edit box or not. We do * this by setting s_textfield if it does. */ if (textfield != NULL) { dialog_default_button = DLG_NONBUTTON_CONTROL + 2; s_textfield = textfield; } else { dialog_default_button = IDCANCEL + 1 + dfltbutton; s_textfield = NULL; } /*show the dialog box modally and get a return value*/ nchar = DialogBoxIndirect( s_hinst, (HGLOBAL) hglbDlgTemp, s_hwnd, (DLGPROC)dp); FreeProcInstance( dp ); GlobalUnlock(hglbDlgTemp); GlobalFree(hglbDlgTemp); vim_free(tbuffer); vim_free(buttonWidths); vim_free(buttonPositions); return nchar; } /* * Put a simple element (basic class) onto a dialog template in memory. * return a pointer to where the next item should be added. * * parameters: * lStyle = additional style flags * x,y = x & y positions IN DIALOG UNITS * w,h = width and height IN DIALOG UNITS * Id = ID used in messages * clss = class ID, e.g 0x80 for a button, 0x82 for a static * caption = usually text or resource name * * TODO: use the length information noted here to enable the dialog creation * routines to work out more exactly how much memory they need to alloc. */ static LPWORD add_dialog_element( LPWORD p, DWORD lStyle, WORD x, WORD y, WORD w, WORD h, WORD Id, BYTE clss, const char *caption) { lStyle = lStyle | WS_VISIBLE | WS_CHILD; add_word(x); add_word(y); add_word(w); add_word(h); add_word(Id); add_long(lStyle); add_byte(clss); if (((lStyle & SS_ICON) != 0) && (clss == 0x82)) { /* Use resource ID */ add_byte(0xff); add_byte(*caption); } else add_string(caption); add_byte(0); //# of extra bytes following return p; } #undef add_byte #undef add_string #undef add_long #undef add_word #endif /* FEAT_GUI_DIALOG */ static void get_dialog_font_metrics(void) { DWORD dlgFontSize; dlgFontSize = GetDialogBaseUnits(); /* fall back to big old system*/ s_dlgfntwidth = LOWORD(dlgFontSize); s_dlgfntheight = HIWORD(dlgFontSize); } #if defined(FEAT_TOOLBAR) || defined(PROTO) #include "gui_w3~1.h" /* * Create the toolbar, initially unpopulated. * (just like the menu, there are no defaults, it's all * set up through menu.vim) */ static void initialise_toolbar(void) { s_toolbarhwnd = CreateToolbar( s_hwnd, WS_CHILD | WS_VISIBLE, CMD_TB_BASE, /**/ 31, //number of images in initial bitmap s_hinst, IDR_TOOLBAR1, // id of initial bitmap NULL, 0 // initial number of buttons ); gui_mch_show_toolbar(vim_strchr(p_go, GO_TOOLBAR) != NULL); } #endif #if defined(FEAT_OLE) || defined(FEAT_EVAL) || defined(PROTO) /* * Make the GUI window come to the foreground. */ void gui_mch_set_foreground(void) { if (IsIconic(s_hwnd)) SendMessage(s_hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); SetActiveWindow(s_hwnd); } #endif