/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* A wrapper class to OS dependent event and window interfaces, WIN32 version */ #include "C4Include.h" #include "C4ForbidLibraryCompilation.h" #include "platform/C4Window.h" #include "C4Version.h" #include "editor/C4Console.h" #include "editor/C4ViewportWindow.h" #include "game/C4Application.h" #include "game/C4FullScreen.h" #include "game/C4GraphicsSystem.h" #include "game/C4Viewport.h" #include "graphics/C4DrawGL.h" #include "gui/C4MouseControl.h" #include "lib/C4Rect.h" #include "platform/C4AppWin32Impl.h" #include "platform/StdRegistry.h" #include "res/resource.h" #include "platform/C4windowswrapper.h" #include #include #define C4ViewportClassName L"C4Viewport" #define C4FullScreenClassName L"C4FullScreen" #define ConsoleDlgClassName L"C4GUIdlg" #define ConsoleDlgWindowStyle (WS_VISIBLE | WS_POPUP | WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX) /** Convert certain keys to unix scancodes (those that differ from unix scancodes) */ static void ConvertToUnixScancode(WPARAM wParam, C4KeyCode *scancode, bool extended) { C4KeyCode &s = *scancode; switch(wParam) { case VK_HOME: s = K_HOME; break; case VK_END: s = K_END; break; case VK_PRIOR: s = K_PAGEUP; break; case VK_NEXT: s = K_PAGEDOWN; break; case VK_UP: s = K_UP; break; case VK_DOWN: s = K_DOWN; break; case VK_LEFT: s = K_LEFT; break; case VK_RIGHT: s = K_RIGHT; break; case VK_CLEAR: s = K_CENTER; break; case VK_INSERT: s = K_INSERT; break; case VK_DELETE: s = K_DELETE; break; case VK_LWIN: s = K_WIN_L; break; case VK_RWIN: s = K_WIN_R; break; case VK_MENU: s = K_MENU; break; case VK_PAUSE: s = K_PAUSE; break; case VK_PRINT: s = K_PRINT; break; case VK_RCONTROL: s = K_CONTROL_R; break; case VK_CONTROL: s = (extended ? K_CONTROL_R : K_CONTROL_L); break; case VK_NUMLOCK: s = K_NUM; break; case VK_NUMPAD1: s = K_NUM1; break; case VK_NUMPAD2: s = K_NUM2; break; case VK_NUMPAD3: s = K_NUM3; break; case VK_NUMPAD4: s = K_NUM4; break; case VK_NUMPAD5: s = K_NUM5; break; case VK_NUMPAD6: s = K_NUM6; break; case VK_NUMPAD7: s = K_NUM7; break; case VK_NUMPAD8: s = K_NUM8; break; case VK_NUMPAD9: s = K_NUM9; break; case VK_NUMPAD0: s = K_NUM0; break; } } LRESULT APIENTRY FullScreenWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static bool NativeCursorShown = true; POINT p; p.x = GET_X_LPARAM(lParam); p.y = GET_Y_LPARAM(lParam); // compute scancode C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF; bool extended = ((lParam & 0x01000000) != 0); ConvertToUnixScancode(wParam, &scancode, extended); // Process message switch (uMsg) { case WM_ACTIVATE: wParam = (LOWORD(wParam)==WA_ACTIVE || LOWORD(wParam)==WA_CLICKACTIVE); // fall through to next case case WM_ACTIVATEAPP: Application.Active = wParam != 0; #ifndef USE_CONSOLE if (pGL) { if (Application.Active) { // restore textures if (Application.FullScreenMode()) { Application.SetVideoMode(Application.GetConfigWidth(), Application.GetConfigHeight(), Config.Graphics.RefreshRate, Config.Graphics.Monitor, true); } } else { if (Application.FullScreenMode()) { ::ChangeDisplaySettings(nullptr, 0); ::ShowWindow(hwnd, SW_MINIMIZE); } } } #endif // redraw background ::GraphicsSystem.InvalidateBg(); // Redraw after task switch if (Application.Active) ::GraphicsSystem.Execute(); // update cursor clip ::MouseControl.UpdateClip(); return false; case WM_PAINT: // Redraw after task switch if (Application.Active) ::GraphicsSystem.Execute(); break; case WM_DESTROY: Application.Quit(); return 0; case WM_CLOSE: FullScreen.Close(); return 0; case MM_MCINOTIFY: if (wParam == MCI_NOTIFY_SUCCESSFUL) Application.MusicSystem.NotifySuccess(); return true; case WM_KEYUP: if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr)) return 0; break; case WM_KEYDOWN: if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) return 0; break; case WM_SYSKEYDOWN: if (wParam == 18) break; if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) { // Remove handled message from queue to prevent Windows "standard" sound for unprocessed system message MSG msg; PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE); return 0; } break; case WM_CHAR: { // UTF-8 has 1 to 4 data bytes, and we need a terminating \0 char c[5] = {0,0,0,0,0}; if(!WideCharToMultiByte(CP_UTF8, 0L, reinterpret_cast(&wParam), 1, c, 4, nullptr, nullptr)) return 0; // GUI: forward if (::pGUI->CharIn(c)) return 0; return false; } case WM_USER_LOG: if (SEqual2((const char *)lParam, "IDS_")) Log(LoadResStr((const char *)lParam)); else Log((const char *)lParam); return false; case WM_LBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_LeftDown,p.x,p.y,wParam, nullptr); break; case WM_LBUTTONUP: C4GUI::MouseMove(C4MC_Button_LeftUp, p.x, p.y, wParam, nullptr); break; case WM_RBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_RightDown, p.x, p.y, wParam, nullptr); break; case WM_RBUTTONUP: C4GUI::MouseMove(C4MC_Button_RightUp, p.x, p.y, wParam, nullptr); break; case WM_LBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_LeftDouble, p.x, p.y, wParam, nullptr); break; case WM_RBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_RightDouble, p.x, p.y, wParam, nullptr); break; case WM_MOUSEWHEEL: // the coordinates are screen-coordinates here (but only on this uMsg), // we need to convert them to client area coordinates ScreenToClient(hwnd, &p); C4GUI::MouseMove(C4MC_Button_Wheel, p.x, p.y, wParam, nullptr); break; case WM_MOUSEMOVE: C4GUI::MouseMove(C4MC_Button_None, p.x, p.y, wParam, nullptr); // Hide cursor in client area if (NativeCursorShown) { NativeCursorShown = false; ShowCursor(FALSE); } break; case WM_NCMOUSEMOVE: // Show cursor on window frame if (!NativeCursorShown) { NativeCursorShown = true; ShowCursor(TRUE); } break; case WM_SIZE: // Notify app about render window size change switch (wParam) { case SIZE_RESTORED: case SIZE_MAXIMIZED: ::Application.OnResolutionChanged(p.x, p.y); if(Application.pWindow) // this might be called from C4Window::Init in which case Application.pWindow is not yet set ::SetWindowPos(Application.pWindow->renderwnd, nullptr, 0, 0, p.x, p.y, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER); break; } break; case WM_INPUTLANGCHANGE: ::Application.OnKeyboardLayoutChanged(); break; case WM_SYSCOMMAND: // The user pressed Alt to open the system menu. This enters a modal // loop which stops us from event processing, so prevent it. Users // can still open the system menu by clicking the window's icon. if ((wParam & 0xFFF0) == SC_KEYMENU && lParam == 0) return 0; break; } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } static C4KeyCode msg2scancode(MSG *msg) { // compute scancode C4KeyCode scancode = (((unsigned int)msg->lParam) >> 16) & 0xFF; bool extended = ((msg->lParam & 0x01000000) != 0); ConvertToUnixScancode(msg->wParam, &scancode, extended); return scancode; } bool ConsoleHandleWin32KeyboardMessage(MSG *msg) { switch (msg->message) { case WM_KEYDOWN: if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Down, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(msg->lParam & 0x40000000), nullptr)) return true; break; case WM_KEYUP: if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Up, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr)) return false; break; case WM_SYSKEYDOWN: if (msg->wParam == 18) break; // VK_MENU (Alt) if (Game.DoKeyboardInput(msg2scancode(msg), KEYEV_Down, !!(msg->lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(msg->lParam & 0x40000000), nullptr)) return false; break; } return false; } LRESULT APIENTRY ViewportWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // Determine viewport C4Viewport *cvp; if (!(cvp=::Viewports.GetViewport(hwnd))) return DefWindowProcW(hwnd, uMsg, wParam, lParam); // compute scancode C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF; bool extended = ((lParam & 0x01000000) != 0); ConvertToUnixScancode(wParam, &scancode, extended); // Process message switch (uMsg) { //--------------------------------------------------------------------------------------------------------------------------- case WM_KEYDOWN: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - switch (wParam) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case VK_SCROLL: // key bound to this specific viewport. Don't want to pass this through C4Game... cvp->TogglePlayerLock(); break; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - default: if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) return 0; break; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } break; //--------------------------------------------------------------------------------------------------------------------------- case WM_KEYUP: if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, nullptr)) return 0; break; //------------------------------------------------------------------------------------------------------------ case WM_SYSKEYDOWN: if (wParam == 18) break; if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), nullptr)) return 0; break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_DESTROY: StoreWindowPosition(hwnd, FormatString("Viewport%i",cvp->Player+1).getData(), Config.GetSubkeyPath("Console")); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_CREATE: DragAcceptFiles(hwnd, TRUE); break; case WM_CLOSE: cvp->pWindow->Close(); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_DROPFILES: { HDROP hDrop = (HDROP)(HANDLE) wParam; if (!Console.Editing) { Console.Message(LoadResStr("IDS_CNS_NONETEDIT")); return false; } int32_t iFileNum = DragQueryFile(hDrop,0xFFFFFFFF,nullptr,0); POINT pntPoint; DragQueryPoint(hDrop,&pntPoint); wchar_t szFilename[500+1]; for (int32_t cnt=0; cntDropFile(StdStrBuf(szFilename).getData(), (float)pntPoint.x, (float)pntPoint.y); } DragFinish(hDrop); break; } //---------------------------------------------------------------------------------------------------------------------------------- case WM_USER_DROPDEF: Game.DropDef(C4ID(lParam),cvp->GetViewX()+float(LOWORD(wParam))/cvp->Zoom,cvp->GetViewY()+float(HIWORD(wParam)/cvp->Zoom)); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_SIZE: case WM_SIZING: cvp->UpdateOutputSize(); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_PAINT: ::GraphicsSystem.Execute(); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_HSCROLL: switch (LOWORD(wParam)) { case SB_THUMBTRACK: case SB_THUMBPOSITION: cvp->SetViewX(float(HIWORD(wParam))/cvp->Zoom); break; case SB_LINELEFT: cvp->ScrollView(-ViewportScrollSpeed, 0.0f); break; case SB_LINERIGHT: cvp->ScrollView(+ViewportScrollSpeed, 0.0f); break; case SB_PAGELEFT: cvp->ScrollView(-cvp->ViewWdt/cvp->Zoom, 0.0f); break; case SB_PAGERIGHT: cvp->ScrollView(+cvp->ViewWdt/cvp->Zoom, 0.0f); break; } cvp->Execute(); cvp->ScrollBarsByViewPosition(); return 0; //---------------------------------------------------------------------------------------------------------------------------------- case WM_VSCROLL: switch (LOWORD(wParam)) { case SB_THUMBTRACK: case SB_THUMBPOSITION: cvp->SetViewY(float(HIWORD(wParam))/cvp->Zoom); break; case SB_LINEUP: cvp->ScrollView(0.0f,-ViewportScrollSpeed); break; case SB_LINEDOWN: cvp->ScrollView(0.0f,+ViewportScrollSpeed); break; case SB_PAGEUP: cvp->ScrollView(0.0f,-cvp->ViewWdt/cvp->Zoom); break; case SB_PAGEDOWN: cvp->ScrollView(0.0f,+cvp->ViewWdt/cvp->Zoom); break; } cvp->Execute(); cvp->ScrollBarsByViewPosition(); return 0; //---------------------------------------------------------------------------------------------------------------------------------- case WM_ACTIVATE: // Keep editing dialogs on top of the current viewport, but don't make them // float on other windows (i.e., no HWND_TOPMOST). // Also, don't use SetParent, since that activates the window, which in turn // posts a new WM_ACTIVATE to us, and so on, ultimately leading to a hang. if (LOWORD(wParam) == WA_INACTIVE) { Console.Win32KeepDialogsFloating(); } else { // FALLTHROUGH case WM_MOUSEACTIVATE: // WM_MOUSEACTIVATE is emitted when the user hovers over a window and pushes a mouse button. // Setting the window owner here avoids z-order flickering. Console.Win32KeepDialogsFloating(hwnd); } break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_SYSCOMMAND: // The user pressed Alt to open the system menu. This enters a modal // loop which stops us from event processing, so prevent it. Users // can still open the system menu by clicking the window's icon. if ((wParam & 0xFFF0) == SC_KEYMENU && lParam == 0) return 0; break; } POINT p; p.x = GET_X_LPARAM(lParam); p.y = GET_Y_LPARAM(lParam); // Viewport mouse control if (::MouseControl.IsViewport(cvp) && (Console.EditCursor.GetMode()==C4CNS_ModePlay)) { switch (uMsg) { //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_LeftDown, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONUP: C4GUI::MouseMove(C4MC_Button_LeftUp, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONDOWN: C4GUI::MouseMove(C4MC_Button_RightDown, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONUP: C4GUI::MouseMove(C4MC_Button_RightUp, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_LeftDouble, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONDBLCLK: C4GUI::MouseMove(C4MC_Button_RightDouble, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_MOUSEMOVE: if ( Inside(p.x-cvp->DrawX,0,cvp->ViewWdt-1) && Inside(p.y-cvp->DrawY,0,cvp->ViewHgt-1) ) SetCursor(nullptr); C4GUI::MouseMove(C4MC_Button_None, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_MOUSEWHEEL: ScreenToClient(hwnd, &p); C4GUI::MouseMove(C4MC_Button_Wheel, p.x, p.y, wParam, cvp); break; //---------------------------------------------------------------------------------------------------------------------------------- } } // Console edit cursor control else { // The state of the ALT key is not reported in wParam for mouse messages, // and for keyboard messages the key states are hidden in lParam. It's a mess. Let's just // query GetKeyState(). DWORD dwKeyState = 0; if(GetKeyState(VK_CONTROL) < 0) dwKeyState |= MK_CONTROL; if(GetKeyState(VK_SHIFT) < 0) dwKeyState |= MK_SHIFT; if(GetKeyState(VK_MENU) < 0) dwKeyState |= MK_ALT; switch (uMsg) { case WM_KEYDOWN: Console.EditCursor.KeyDown(scancode, dwKeyState); break; case WM_KEYUP: Console.EditCursor.KeyUp(scancode, dwKeyState); break; case WM_SYSKEYDOWN: Console.EditCursor.KeyDown(scancode, dwKeyState); break; case WM_SYSKEYUP: Console.EditCursor.KeyUp(scancode, dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONDOWN: // movement update needed before, so target is always up-to-date cvp->pWindow->EditCursorMove(p.x, p.y, dwKeyState); Console.EditCursor.LeftButtonDown(dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONUP: Console.EditCursor.LeftButtonUp(dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONDOWN: Console.EditCursor.RightButtonDown(dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONUP: Console.EditCursor.RightButtonUp(dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_MOUSEMOVE: cvp->pWindow->EditCursorMove(p.x, p.y, dwKeyState); break; //---------------------------------------------------------------------------------------------------------------------------------- } } return DefWindowProcW(hwnd, uMsg, wParam, lParam); } LRESULT APIENTRY DialogWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { // Determine dialog C4GUI::Dialog *pDlg = ::pGUI->GetDialog(hwnd); if (!pDlg) return DefWindowProc(hwnd, uMsg, wParam, lParam); POINT p; p.x = GET_X_LPARAM(lParam); p.y = GET_Y_LPARAM(lParam); // compute scancode C4KeyCode scancode = (((unsigned int)lParam) >> 16) & 0xFF; bool extended = ((lParam & 0x01000000) != 0); ConvertToUnixScancode(wParam, &scancode, extended); // Process message switch (uMsg) { //--------------------------------------------------------------------------------------------------------------------------- case WM_KEYDOWN: if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), pDlg)) return 0; break; //--------------------------------------------------------------------------------------------------------------------------- case WM_KEYUP: if (Game.DoKeyboardInput(scancode, KEYEV_Up, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, false, pDlg)) return 0; break; //------------------------------------------------------------------------------------------------------------ case WM_SYSKEYDOWN: if (wParam == 18) break; if (Game.DoKeyboardInput(scancode, KEYEV_Down, !!(lParam & 0x20000000), GetKeyState(VK_CONTROL) < 0, GetKeyState(VK_SHIFT) < 0, !!(lParam & 0x40000000), pDlg)) return 0; break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_DESTROY: { const char *szID = pDlg->GetID(); if (szID && *szID) StoreWindowPosition(hwnd, FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false); } break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_CLOSE: pDlg->Close(false); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_SIZE: // UpdateOutputSize break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_PAINT: // 2do: only draw specific dlg? break; return 0; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONDOWN: ::pGUI->MouseInput(C4MC_Button_LeftDown, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONUP: ::pGUI->MouseInput(C4MC_Button_LeftUp, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONDOWN: ::pGUI->MouseInput(C4MC_Button_RightDown, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONUP: ::pGUI->MouseInput(C4MC_Button_RightUp, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_LBUTTONDBLCLK: ::pGUI->MouseInput(C4MC_Button_LeftDouble, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_RBUTTONDBLCLK: ::pGUI->MouseInput(C4MC_Button_RightDouble, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_MOUSEMOVE: ::pGUI->MouseInput(C4MC_Button_None, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- case WM_MOUSEWHEEL: ScreenToClient(hwnd, &p); ::pGUI->MouseInput(C4MC_Button_Wheel, p.x, p.y, wParam, pDlg, nullptr); break; //---------------------------------------------------------------------------------------------------------------------------------- } return DefWindowProc(hwnd, uMsg, wParam, lParam); } C4Window::C4Window () = default; C4Window::~C4Window () = default; C4Window * C4Window::Init(C4Window::WindowKind windowKind, C4AbstractApp * pApp, const char * Title, const C4Rect * size) { Active = true; eKind = windowKind; if (windowKind == W_Viewport) { #ifdef WITH_QT_EDITOR // embed into editor: Viewport widget creation handled by C4ConsoleQt ::Console.AddViewport(static_cast(this)); return this; #else static bool fViewportClassRegistered = false; if (!fViewportClassRegistered) { // Register viewport class WNDCLASSEXW WndClass; WndClass.cbSize=sizeof(WNDCLASSEX); WndClass.style = CS_DBLCLKS | CS_BYTEALIGNCLIENT; WndClass.lpfnWndProc = ViewportWinProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = pApp->GetInstance(); WndClass.hCursor = LoadCursor (nullptr, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND; WndClass.lpszMenuName = nullptr; WndClass.lpszClassName = C4ViewportClassName; WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_01_OCS) ); WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_01_OCS) ); if (!RegisterClassExW(&WndClass)) return nullptr; fViewportClassRegistered = true; } // Create window hWindow = CreateWindowExW ( WS_EX_ACCEPTFILES, C4ViewportClassName, GetWideChar(Title), C4ViewportWindowStyle, CW_USEDEFAULT,CW_USEDEFAULT, size->Wdt, size->Hgt, Console.hWindow,nullptr,pApp->GetInstance(),nullptr); if(!hWindow) return nullptr; #endif // We don't re-init viewport windows currently, so we don't need a child window // for now: Render into main window. renderwnd = hWindow; } else if (windowKind == W_Fullscreen) { // Register window class auto WndClass = WNDCLASSEXW(); WndClass.cbSize = sizeof(WNDCLASSEX); WndClass.style = CS_DBLCLKS; WndClass.lpfnWndProc = FullScreenWinProc; WndClass.hInstance = pApp->GetInstance(); WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND; WndClass.lpszClassName = C4FullScreenClassName; WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) ); WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) ); if (!RegisterClassExW(&WndClass)) return nullptr; // Create window hWindow = CreateWindowExW ( 0, C4FullScreenClassName, GetWideChar(Title), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, size->Wdt, size->Hgt, nullptr,nullptr,pApp->GetInstance(),nullptr); if(!hWindow) return nullptr; RECT rc; GetClientRect(hWindow, &rc); renderwnd = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWindow, nullptr, pApp->GetInstance(), nullptr); if(!renderwnd) { DestroyWindow(hWindow); return nullptr; } ShowWindow(renderwnd, SW_SHOW); #ifndef USE_CONSOLE // Show & focus ShowWindow(hWindow,SW_SHOWNORMAL); SetFocus(hWindow); #endif } else if (windowKind == W_GuiWindow) { static bool fDialogClassRegistered = false; if (!fDialogClassRegistered) { // register landscape viewport class WNDCLASSEXW WndClass; WndClass.cbSize=sizeof(WNDCLASSEX); WndClass.style = CS_DBLCLKS | CS_BYTEALIGNCLIENT; WndClass.lpfnWndProc = DialogWinProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = pApp->GetInstance(); WndClass.hCursor = LoadCursor (nullptr, IDC_ARROW); // - always use normal hw cursor WndClass.hbrBackground = (HBRUSH) COLOR_BACKGROUND; WndClass.lpszMenuName = nullptr; WndClass.lpszClassName = ConsoleDlgClassName; WndClass.hIcon = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) ); WndClass.hIconSm = LoadIcon (pApp->GetInstance(), MAKEINTRESOURCE (IDI_00_C4X) ); if (!RegisterClassExW(&WndClass)) return nullptr; } Active = true; // calculate required size RECT rtSize; rtSize.left = 0; rtSize.top = 0; rtSize.right = size->Wdt; rtSize.bottom = size->Hgt; if (!::AdjustWindowRectEx(&rtSize, ConsoleDlgWindowStyle, false, 0)) return nullptr; // create it! if (!Title || !*Title) Title = "???"; hWindow = ::CreateWindowExW( 0, ConsoleDlgClassName, GetWideChar(Title), ConsoleDlgWindowStyle, CW_USEDEFAULT,CW_USEDEFAULT,rtSize.right-rtSize.left,rtSize.bottom-rtSize.top, ::Console.hWindow,nullptr,pApp->GetInstance(),nullptr); renderwnd = hWindow; return hWindow ? this : nullptr; } else if (windowKind == W_Control) { // controlled externally hWindow = renderwnd = nullptr; } return this; } bool C4Window::ReInit(C4AbstractApp* pApp) { // We don't need to change anything with the window for any // configuration option changes on Windows. // However, re-create the render window so that another pixel format can // be chosen for it. The pixel format is chosen in CStdGLCtx::Init. RECT rc; GetClientRect(hWindow, &rc); HWND hNewRenderWindow = CreateWindowExW(0, L"STATIC", nullptr, WS_CHILD, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWindow, nullptr, pApp->hInstance, nullptr); if(!hNewRenderWindow) return false; ShowWindow(hNewRenderWindow, SW_SHOW); DestroyWindow(renderwnd); renderwnd = hNewRenderWindow; return true; } void C4Window::Clear() { // Destroy window if we own it if (eKind != W_Control) { if (renderwnd) DestroyWindow(renderwnd); if (hWindow && hWindow != renderwnd) DestroyWindow(hWindow); } #ifdef WITH_QT_EDITOR if (eKind == W_Viewport) { // embed into editor: Viewport widget creation handled by C4ConsoleQt ::Console.RemoveViewport(static_cast(this)); } #endif renderwnd = nullptr; hWindow = nullptr; } bool C4Window::StorePosition(const char *szWindowName, const char *szSubKey, bool fStoreSize) { return StoreWindowPosition(hWindow, szWindowName, szSubKey, fStoreSize) != 0; } bool C4Window::RestorePosition(const char *szWindowName, const char *szSubKey, bool fHidden) { if (!RestoreWindowPosition(hWindow, szWindowName, szSubKey, fHidden)) ShowWindow(hWindow,SW_SHOWNORMAL); return true; } void C4Window::SetTitle(const char *szToTitle) { if (hWindow) SetWindowTextW(hWindow, GetWideChar(szToTitle ? szToTitle : "")); } bool C4Window::GetSize(C4Rect * pRect) { RECT r; if (!(hWindow && GetClientRect(hWindow,&r))) return false; pRect->x = r.left; pRect->y = r.top; pRect->Wdt = r.right - r.left; pRect->Hgt = r.bottom - r.top; return true; } void C4Window::SetSize(unsigned int cx, unsigned int cy) { if (hWindow) { // If bordered, add border size RECT rect = { 0, 0, static_cast(cx), static_cast(cy) }; ::AdjustWindowRectEx(&rect, GetWindowLong(hWindow, GWL_STYLE), FALSE, GetWindowLong(hWindow, GWL_EXSTYLE)); cx = rect.right - rect.left; cy = rect.bottom - rect.top; ::SetWindowPos(hWindow, nullptr, 0, 0, cx, cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER); // Also resize child window GetClientRect(hWindow, &rect); ::SetWindowPos(renderwnd, nullptr, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOZORDER); } } void C4Window::FlashWindow() { // please activate me! if (hWindow) ::FlashWindow(hWindow, FLASHW_ALL | FLASHW_TIMERNOFG); } void C4Window::GrabMouse(bool grab) { // TODO } void C4Window::EnumerateMultiSamples(std::vector& samples) const { #ifndef USE_CONSOLE if(pGL && pGL->pMainCtx) samples = pGL->pMainCtx->EnumerateMultiSamples(); #endif } /* CStdMessageProc */ bool CStdMessageProc::Execute(int iTimeout, pollfd *) { // Peek messages MSG msg; while (PeekMessage(&msg,nullptr,0,0,PM_REMOVE)) { // quit? if (msg.message == WM_QUIT) { pApp->fQuitMsgReceived = true; return false; } // Dialog message transfer if (!pApp->pWindow || !pApp->pWindow->Win32DialogMessageHandling(&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return true; } /* C4AbstractApp */ C4AbstractApp::C4AbstractApp() { ZeroMemory(&dspMode, sizeof(dspMode)); dspMode.dmSize = sizeof(dspMode); ZeroMemory(&OldDspMode, sizeof(OldDspMode)); OldDspMode.dmSize = sizeof(OldDspMode); idMainThread = 0; #ifdef _WIN32 MessageProc.SetApp(this); Add(&MessageProc); #endif } C4AbstractApp::~C4AbstractApp() = default; bool C4AbstractApp::Init(int argc, char * argv[]) { // Set instance vars idMainThread = ::GetCurrentThreadId(); // Custom initialization return DoInit (argc, argv); } void C4AbstractApp::Clear() { idMainThread = 0; } void C4AbstractApp::Quit() { PostQuitMessage(0); } bool C4AbstractApp::FlushMessages() { // Always fail after quit message if (fQuitMsgReceived) return false; return MessageProc.Execute(0); } void C4AbstractApp::SetLastErrorFromOS() { LPWSTR buffer = nullptr; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&buffer), 0, nullptr); sLastError = WStrToString(buffer); LocalFree(buffer); } int GLMonitorInfoEnumCount; static BOOL CALLBACK GLMonitorInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { // get to indexed monitor if (GLMonitorInfoEnumCount--) return true; // store it C4AbstractApp *pApp = (C4AbstractApp *) dwData; pApp->hMon = hMonitor; pApp->MonitorRect = *lprcMonitor; return true; } bool C4AbstractApp::GetIndexedDisplayMode(int32_t iIndex, int32_t *piXRes, int32_t *piYRes, int32_t *piBitDepth, int32_t *piRefreshRate, uint32_t iMonitor) { // prepare search struct DEVMODEW dmode; ZeroMemory(&dmode, sizeof(dmode)); dmode.dmSize = sizeof(dmode); StdStrBuf Mon; if (iMonitor) Mon.Format(R"(\\.\Display%d)", iMonitor+1); // check if indexed mode exists if (!EnumDisplaySettingsW(Mon.GetWideChar(), iIndex, &dmode)) { SetLastErrorFromOS(); return false; } // mode exists; return it if (piXRes) *piXRes = dmode.dmPelsWidth; if (piYRes) *piYRes = dmode.dmPelsHeight; if (piBitDepth) *piBitDepth = dmode.dmBitsPerPel; if (piRefreshRate) *piRefreshRate = dmode.dmDisplayFrequency; return true; } void C4AbstractApp::RestoreVideoMode() { } bool C4AbstractApp::SetVideoMode(int iXRes, int iYRes, unsigned int iRefreshRate, unsigned int iMonitor, bool fFullScreen) { #ifndef USE_CONSOLE SetWindowLong(pWindow->hWindow, GWL_EXSTYLE, GetWindowLong(pWindow->hWindow, GWL_EXSTYLE) | WS_EX_APPWINDOW); // change mode if (!fFullScreen) { ChangeDisplaySettings(nullptr, 0); SetWindowLong(pWindow->hWindow, GWL_STYLE, GetWindowLong(pWindow->hWindow, GWL_STYLE) | (WS_CAPTION|WS_THICKFRAME|WS_BORDER)); if(iXRes != -1 && iYRes != -1) { pWindow->SetSize(iXRes, iYRes); OnResolutionChanged(iXRes, iYRes); } ::SetWindowPos(pWindow->hWindow, nullptr, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREDRAW|SWP_FRAMECHANGED); } else { bool fFound=false; DEVMODEW dmode; // if a monitor is given, search on that instead // get monitor infos GLMonitorInfoEnumCount = iMonitor; hMon = nullptr; EnumDisplayMonitors(nullptr, nullptr, GLMonitorInfoEnumProc, (LPARAM) this); // no monitor assigned? if (!hMon) { // Okay for primary; then just use a default if (!iMonitor) { MonitorRect.left = MonitorRect.top = 0; MonitorRect.right = iXRes; MonitorRect.bottom = iYRes; } else return false; } StdStrBuf Mon; if (iMonitor) Mon.Format(R"(\\.\Display%d)", iMonitor+1); ZeroMemory(&dmode, sizeof(dmode)); dmode.dmSize = sizeof(dmode); // Get current display settings if (!EnumDisplaySettingsW(Mon.GetWideChar(), ENUM_CURRENT_SETTINGS, &dmode)) { SetLastErrorFromOS(); return false; } unsigned long orientation = dmode.dmDisplayOrientation; if (iXRes == -1 && iYRes == -1) { dspMode=dmode; fFound = true; } // enumerate modes int i=0; if (!fFound) while (EnumDisplaySettingsW(Mon.GetWideChar(), i++, &dmode)) // compare enumerated mode with requested settings if (static_cast(dmode.dmPelsWidth) == iXRes && static_cast(dmode.dmPelsHeight) == iYRes && dmode.dmBitsPerPel == C4Draw::COLOR_DEPTH && dmode.dmDisplayOrientation == orientation && (iRefreshRate == 0 || dmode.dmDisplayFrequency == iRefreshRate)) { fFound=true; dspMode=dmode; break; } if (!fFound) return false; dspMode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; if (iRefreshRate != 0) dspMode.dmFields |= DM_DISPLAYFREQUENCY; LONG rv = ChangeDisplaySettingsExW(iMonitor ? Mon.GetWideChar() : nullptr, &dspMode, nullptr, CDS_FULLSCREEN, nullptr); if (rv != DISP_CHANGE_SUCCESSFUL) { switch (rv) { #define CDSE_ERROR(error) case error: sLastError = LoadResStr("IDS_ERR_" #error); break CDSE_ERROR(DISP_CHANGE_BADFLAGS); CDSE_ERROR(DISP_CHANGE_BADMODE); CDSE_ERROR(DISP_CHANGE_BADPARAM); CDSE_ERROR(DISP_CHANGE_RESTART); CDSE_ERROR(DISP_CHANGE_FAILED); #undef CDSE_ERROR default: sLastError = LoadResStr("IDS_ERR_FAILURE"); break; } return false; } SetWindowLong(pWindow->hWindow, GWL_STYLE, GetWindowLong(pWindow->hWindow, GWL_STYLE) & ~ (WS_CAPTION|WS_THICKFRAME|WS_BORDER)); pWindow->SetSize(dspMode.dmPelsWidth, dspMode.dmPelsHeight); OnResolutionChanged(dspMode.dmPelsWidth, dspMode.dmPelsHeight); ::SetWindowPos(pWindow->hWindow, nullptr, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREDRAW|SWP_FRAMECHANGED); } return true; #endif } void C4AbstractApp::MessageDialog(const char * message) { MessageBoxW(nullptr, GetWideChar(message), ADDL(C4ENGINECAPTION), MB_ICONERROR); } // Clipboard functions bool C4AbstractApp::Copy(const std::string &text, bool fClipboard) { if (!fClipboard) return false; bool fSuccess = true; // gain clipboard ownership if (!OpenClipboard(pWindow ? pWindow->hWindow : nullptr)) return false; // must empty the global clipboard, so the application clipboard equals the Windows clipboard EmptyClipboard(); int size = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), text.size() + 1, nullptr, 0); HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, size * sizeof(wchar_t)); if (hglbCopy == nullptr) { CloseClipboard(); return false; } // lock the handle and copy the text to the buffer. wchar_t *szCopyChar = (wchar_t *) GlobalLock(hglbCopy); fSuccess = !!MultiByteToWideChar(CP_UTF8, 0, text.c_str(), text.size() + 1, szCopyChar, size); GlobalUnlock(hglbCopy); // place the handle on the clipboard. fSuccess = fSuccess && !!SetClipboardData(CF_UNICODETEXT, hglbCopy); // close clipboard CloseClipboard(); // return whether copying was successful return fSuccess; } std::string C4AbstractApp::Paste(bool fClipboard) { if (!fClipboard) return std::string(); // open clipboard if (!OpenClipboard(nullptr)) return std::string(); // get text from clipboard HANDLE hglb = GetClipboardData(CF_UNICODETEXT); if (!hglb) return std::string(); std::string text{ WStrToString((wchar_t*)GlobalLock(hglb)) }; // unlock mem GlobalUnlock(hglb); // close clipboard CloseClipboard(); return text; } bool C4AbstractApp::IsClipboardFull(bool fClipboard) { if (!fClipboard) return false; return !!IsClipboardFormatAvailable(CF_UNICODETEXT); } void C4Window::RequestUpdate() { // just invoke directly PerformUpdate(); }