// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdmisc.h" #include "nel/misc/win_displayer.h" #ifdef NL_OS_WINDOWS #include #include #include #include #include #include #include #include "nel/misc/app_context.h" #include "nel/misc/path.h" #include "nel/misc/command.h" #include "nel/misc/thread.h" #include "nel/misc/ucstring.h" using namespace std; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NLMISC { static CHARFORMAT2A CharFormat; CWinDisplayer::CWinDisplayer(const char *displayerName) : CWindowDisplayer(displayerName), Exit(false) { needSlashR = true; createLabel("@Clear|CLEAR"); INelContext::getInstance().setWindowedApplication(true); } CWinDisplayer::~CWinDisplayer () { } LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { MSGFILTER *pmf; switch (message) { case WM_ACTIVATE: { if (LOWORD(wParam) != WA_INACTIVE) { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); if (cwd != NULL) SetFocus(cwd->_HInputEdit); return 0; } } break; case WM_SIZE: { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); if (cwd != NULL) { int w = lParam & 0xFFFF; int h = (lParam >> 16) & 0xFFFF; // SetWindowPos (cwd->_HEdit, NULL, 0, cwd->_ToolBarHeight, w, h-cwd->_ToolBarHeight-cwd->_InputEditHeight, SWP_NOZORDER | SWP_NOACTIVATE ); SetWindowPos (cwd->_HInputEdit, NULL, 0, h-cwd->_InputEditHeight, w, cwd->_InputEditHeight, SWP_NOZORDER | SWP_NOACTIVATE ); cwd->resizeLabels (); } } break; case WM_DESTROY: PostQuitMessage (0); break; case WM_CLOSE: { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); if (cwd != NULL) cwd->_Continue = false; } break; case WM_COMMAND: { if (HIWORD(wParam) == BN_CLICKED) { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); // find the button and execute the command CSynchronized >::CAccessor access (&cwd->_Labels); for (uint i = 0; i < access.value().size(); i++) { if (access.value()[i].Hwnd == (HWND)lParam) { if(access.value()[i].Value == "@Clear|CLEAR") { // special commands because the clear must be called by the display thread and not main thread cwd->clear (); } else { // the button was found, add the command in the command stack CSynchronized >::CAccessor accessCommands (&cwd->_CommandsToExecute); string str; nlassert (!access.value()[i].Value.empty()); nlassert (access.value()[i].Value[0] == '@'); string::size_type pos = access.value()[i].Value.find ("|"); if (pos != string::npos) { str = access.value()[i].Value.substr(pos+1); } else { str = access.value()[i].Value.substr(1); } if (!str.empty()) accessCommands.value().push_back(str); } break; } } } } break; case WM_NOTIFY: switch (((NMHDR*)lParam)->code) { case EN_MSGFILTER: pmf = (MSGFILTER *)lParam; if (pmf->msg == WM_CHAR) { if (pmf->wParam == VK_RETURN) { WCHAR wText[20000]; string TextSend; CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtr (hWnd, GWLP_USERDATA); // get the text as unicode string GetWindowTextW(cwd->_HInputEdit, wText, 20000); // and convert it to UTF-8 encoding. TextSend = wideToUtf8(wText); SendMessageA (cwd->_HInputEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)""); const char *pos2 = TextSend.c_str(); string str; while (*pos2 != '\0') { str.clear(); // get the string while (*pos2 != '\0' && *pos2 != '\n') { if (*pos2 != '\r') { str += *pos2; } *pos2++; } // eat the \n if (*pos2 == '\n') *pos2++; if (!str.empty()) { { CSynchronized >::CAccessor access (&cwd->_CommandsToExecute); access.value().push_back(str); } cwd->_History.push_back(str); cwd->_PosInHistory = (uint)cwd->_History.size(); } } } else if (pmf->wParam == VK_TAB) { WCHAR wText[20000]; CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); // get the text as unicode string GetWindowTextW(cwd->_HInputEdit, wText, 20000); // and convert it to UTF-8 encoding string str = wideToUtf8(wText); nlassert (cwd->Log != NULL); ICommand::expand (str, *cwd->Log); SendMessageW (cwd->_HInputEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)wText); SendMessageA (cwd->_HInputEdit, EM_SETSEL, wcslen(wText), wcslen(wText)); return 1; } } else if (pmf->msg == WM_KEYUP) { if (pmf->wParam == VK_UP) { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrA (hWnd, GWLP_USERDATA); if (cwd->_PosInHistory > 0) cwd->_PosInHistory--; if (!cwd->_History.empty()) { ucstring ucs; // convert the text from UTF-8 to unicode ucs.fromUtf8(cwd->_History[cwd->_PosInHistory]); // set the text as unicode string if (!SetWindowTextW(cwd->_HInputEdit, (LPCWSTR)ucs.c_str())) { nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str()); } SendMessageA (cwd->_HInputEdit, EM_SETSEL, (WPARAM)ucs.size(), (LPARAM)ucs.size()); } } else if (pmf->wParam == VK_DOWN) { CWinDisplayer *cwd=(CWinDisplayer *)GetWindowLongPtrW (hWnd, GWLP_USERDATA); if (cwd->_PosInHistory < cwd->_History.size()-1) cwd->_PosInHistory++; if (!cwd->_History.empty() && cwd->_PosInHistory < cwd->_History.size()) { ucstring ucs; // convert the text from UTF-8 to unicode ucs.fromUtf8(cwd->_History[cwd->_PosInHistory]); // set the text as unicode string if (!SetWindowTextW(cwd->_HInputEdit, (LPCWSTR)ucs.c_str())) { nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str()); } SendMessageA (cwd->_HInputEdit, EM_SETSEL, (WPARAM)ucs.size(), (LPARAM)ucs.size()); } } } } } return DefWindowProcW (hWnd, message, wParam, lParam); } void CWinDisplayer::updateLabels () { bool needResize = false; { CSynchronized >::CAccessor access (&_Labels); for (uint i = 0; i < access.value().size(); i++) { if (access.value()[i].NeedUpdate && !access.value()[i].Value.empty()) { if (access.value()[i].Hwnd == NULL) { // create a button for command and label for variables if (access.value()[i].Value[0] == '@') { access.value()[i].Hwnd = CreateWindowA ("BUTTON", "", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 0, 0, 0, 0, _HWnd, (HMENU) NULL, (HINSTANCE) GetWindowLongPtrA(_HWnd, GWLP_HINSTANCE), NULL); } else { access.value()[i].Hwnd = CreateWindowA ("STATIC", "", WS_CHILD | WS_VISIBLE | SS_SIMPLE, 0, 0, 0, 0, _HWnd, (HMENU) NULL, (HINSTANCE) GetWindowLongPtrA(_HWnd, GWLP_HINSTANCE), NULL); } SendMessageA ((HWND)access.value()[i].Hwnd, WM_SETFONT, (WPARAM)_HFont, TRUE); needResize = true; } string n; // do this tricks to be sure that windows will clear what is after the number if (access.value()[i].Value[0] != '@') n = access.value()[i].Value + " "; else { string::size_type pos = access.value()[i].Value.find ('|'); if (pos != string::npos) { n = access.value()[i].Value.substr (1, pos - 1); } else { n = access.value()[i].Value.substr (1); } } SendMessageW ((HWND)access.value()[i].Hwnd, WM_SETTEXT, 0, (LPARAM) utf8ToWide(n)); access.value()[i].NeedUpdate = false; } } } if (needResize) resizeLabels(); } void CWinDisplayer::resizeLabels () { { CSynchronized >::CAccessor access (&_Labels); RECT Rect; GetClientRect (_HWnd, &Rect); uint i = 0, nb; uint y = 0, nby = 1; for (i = 0; i < access.value().size (); i++) if (access.value()[i].Value.empty()) nby++; i = 0; for(;;) { nb = 0; while (i+nb != access.value().size () && !access.value()[i+nb].Value.empty()) nb++; sint delta; if (nb == 0) delta = 0; else delta = Rect.right / nb; for (uint j = 0; j< nb; j++) { if ((HWND)access.value()[i+j].Hwnd != NULL) SetWindowPos ((HWND)access.value()[i+j].Hwnd, NULL, j*delta, y*_ToolBarHeight, delta, _ToolBarHeight, SWP_NOZORDER | SWP_NOACTIVATE ); } i += nb + 1; y++; if (i >= access.value().size()) break; } SetWindowPos (_HEdit, NULL, 0, nby*_ToolBarHeight, Rect.right, Rect.bottom-nby*_ToolBarHeight-_InputEditHeight, SWP_NOZORDER | SWP_NOACTIVATE ); } } void CWinDisplayer::setTitleBar (const string &titleBar) { string wn; if (!titleBar.empty()) { wn += titleBar; wn += ": "; } wn += "Nel Service Console (compiled " __DATE__ " " __TIME__ " in " + nlMode + " mode)"; nldebug("SERVICE: Set title bar to '%s'", wn.c_str()); if (!SetWindowTextW(_HWnd, (LPWSTR)ucstring::makeFromUtf8(wn).c_str())) { nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str()); } } void CWinDisplayer::open (string titleBar, bool iconified, sint x, sint y, sint w, sint h, sint hs, sint fs, const std::string &fn, bool ww, CLog *log) { if (w == -1) w = 700; if (h == -1) h = 300; if (hs == -1) hs = 1000; Log = log; _HistorySize = hs; WNDCLASSW wc; memset (&wc,0,sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW ;//| CS_DBLCLKS; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = GetModuleHandleW(NULL); wc.hIcon = NULL; wc.hCursor = LoadCursorW(NULL,(LPWSTR)IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszClassName = L"NLDisplayerClass"; wc.lpszMenuName = NULL; if ( !RegisterClassW(&wc) ) return; ULONG WndFlags; RECT WndRect; WndFlags = WS_OVERLAPPEDWINDOW /*| WS_CLIPCHILDREN | WS_CLIPSIBLINGS*/; WndRect.left = 0; WndRect.top = 0; WndRect.right = w; WndRect.bottom = h; AdjustWindowRect(&WndRect,WndFlags,FALSE); // create the window _HWnd = CreateWindowW (L"NLDisplayerClass", L"", WndFlags, CW_USEDEFAULT,CW_USEDEFAULT, WndRect.right,WndRect.bottom, NULL, NULL, GetModuleHandle(NULL), NULL); SetWindowLongPtrW (_HWnd, GWLP_USERDATA, (LONG_PTR)this); _HLibModule = LoadLibraryW(L"RICHED20.DLL"); if (_HLibModule == NULL) { nlerror ("RichEdit 2.0 library not found!"); } string rfn; if (fn.empty()) rfn = "courier"; else rfn = fn; sint rfs; if (fs == 0) rfs = 10; else rfs = fs; _HFont = CreateFontW (-MulDiv(rfs, GetDeviceCaps(GetDC(0),LOGPIXELSY), 72), 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, (LPCWSTR)ucstring::makeFromUtf8(rfn).c_str()); // create the edit control DWORD dwStyle = WS_HSCROLL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_READONLY | ES_LEFT | ES_MULTILINE /*| ES_AUTOVSCROLL*/; if(ww) dwStyle &= ~WS_HSCROLL; else dwStyle |= WS_HSCROLL; _HEdit = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, RICHEDIT_CLASSW, L"", dwStyle, 0, _ToolBarHeight, w, h-_ToolBarHeight-_InputEditHeight, _HWnd, (HMENU) NULL, (HINSTANCE) GetWindowLongPtr(_HWnd, GWLP_HINSTANCE), NULL); SendMessageA (_HEdit, WM_SETFONT, (WPARAM)_HFont, TRUE); // set the edit text limit to lot of :) SendMessageA (_HEdit, EM_LIMITTEXT, -1, 0); CharFormat.cbSize = sizeof(CharFormat); CharFormat.dwMask = CFM_COLOR; SendMessageA(_HEdit,EM_GETCHARFORMAT,(WPARAM)0,(LPARAM)&CharFormat); CharFormat.dwEffects &= ~CFE_AUTOCOLOR; // create the input edit control _HInputEdit = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW, RICHEDIT_CLASSW, L"", WS_CHILD | WS_VISIBLE /*| ES_MULTILINE*/ | ES_WANTRETURN | ES_NOHIDESEL | ES_AUTOHSCROLL, 0, h-_InputEditHeight, w, _InputEditHeight, _HWnd, NULL, (HINSTANCE) GetWindowLongPtr(_HWnd, GWLP_HINSTANCE), NULL); SendMessageW (_HInputEdit, WM_SETFONT, (WPARAM)_HFont, TRUE); LRESULT dwEvent = SendMessageW(_HInputEdit, EM_GETEVENTMASK, (WPARAM)0, (LPARAM)0); dwEvent |= ENM_MOUSEEVENTS | ENM_KEYEVENTS | ENM_CHANGE; SendMessageA(_HInputEdit, EM_SETEVENTMASK, (WPARAM)0, (LPARAM)dwEvent); // resize the window RECT rc; SetRect (&rc, 0, 0, w, h); AdjustWindowRectEx (&rc, GetWindowStyle (_HWnd), GetMenu (_HWnd) != NULL, GetWindowExStyle (_HWnd)); LONG flag = SWP_NOZORDER | SWP_NOACTIVATE; if (x == -1 && y == -1) flag |= SWP_NOMOVE; if (w == -1 && h == -1) flag |= SWP_NOSIZE; SetWindowPos (_HWnd, NULL, x, y, w, h, flag); SetWindowPos (_HWnd, NULL, x, y, rc.right - rc.left, rc.bottom - rc.top, flag); setTitleBar (titleBar); if (iconified) ShowWindow(_HWnd,SW_MINIMIZE); else ShowWindow(_HWnd,SW_SHOW); SetFocus (_HInputEdit); _Init = true; } void CWinDisplayer::clear () { bool focus = (GetFocus() == _HEdit); if (focus) { SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_AND,(LPARAM)~ECO_AUTOVSCROLL); SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_AND,(LPARAM)~ECO_AUTOHSCROLL); } // get number of line LRESULT nLine = SendMessageW (_HEdit, EM_GETLINECOUNT, 0, 0) - 1; // get size of the last line LRESULT nIndex = SendMessageW (_HEdit, EM_LINEINDEX, nLine, 0); // select all the text SendMessageW (_HEdit, EM_SETSEL, 0, nIndex); // clear all the text SendMessageW (_HEdit, EM_REPLACESEL, FALSE, (LPARAM) ""); SendMessageW(_HEdit,EM_SETMODIFY,(WPARAM)TRUE,(LPARAM)0); if ( focus ) { SendMessageW(_HEdit,EM_SETOPTIONS,ECOOP_OR,(LPARAM)ECO_AUTOVSCROLL); SendMessageW(_HEdit,EM_SETOPTIONS,ECOOP_OR,(LPARAM)ECO_AUTOHSCROLL); } } void CWinDisplayer::display_main () { nlassert (_Init); while (_Continue) { // // Display the bufferized string // { CSynchronized > >::CAccessor access (&_Buffer); std::list >::iterator it; sint vecSize = (sint)access.value().size(); //nlassert (vecSize <= _HistorySize); if (vecSize > 0) { // look if we are at the bottom of the edit SCROLLINFO info; info.cbSize = sizeof(info); info.fMask = SIF_ALL; bool bottom = true; if (GetScrollInfo(_HEdit,SB_VERT,&info) != 0) bottom = (info.nPage == 0) || (info.nMax<=(info.nPos+(int)info.nPage)); // look if we have the focus bool focus = (GetFocus() == _HEdit); if (focus) { SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_AND,(LPARAM)~ECO_AUTOVSCROLL); SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_AND,(LPARAM)~ECO_AUTOHSCROLL); } // store old selection DWORD startSel, endSel; SendMessageA (_HEdit, EM_GETSEL, (WPARAM)&startSel, (LPARAM)&endSel); // find how many lines we have to remove in the current output to add new lines // get number of line LRESULT nLine = SendMessageW (_HEdit, EM_GETLINECOUNT, 0, 0) - 1; if (_HistorySize > 0 && nLine+vecSize > _HistorySize) { int nblineremove = vecSize; //nlassert (nblineremove>0 && nblineremove <= _HistorySize); if (nblineremove == _HistorySize) { SendMessageA (_HEdit, WM_SETTEXT, 0, (LPARAM) ""); startSel = endSel = -1; } else { LRESULT oldIndex1 = SendMessageW (_HEdit, EM_LINEINDEX, 0, 0); //nlassert (oldIndex1 != -1); LRESULT oldIndex2 = SendMessageW (_HEdit, EM_LINEINDEX, nblineremove, 0); //nlassert (oldIndex2 != -1); SendMessageW (_HEdit, EM_SETSEL, oldIndex1, oldIndex2); SendMessageW (_HEdit, EM_REPLACESEL, FALSE, (LPARAM) ""); // update the selection due to the erasing sint dt = (sint)(oldIndex2 - oldIndex1); if (startSel < 65000) { if ((sint)startSel-dt < 0) startSel = -1; else startSel -= dt; } else startSel = -1; if(endSel < 65000) { if ((sint)endSel-dt < 0) startSel = endSel = -1; else endSel -= dt; } else startSel = endSel = -1; } } for (it = access.value().begin(); it != access.value().end(); ) { ucstring str = ucstring::makeFromUtf8((*it).second); uint32 col = (*it).first; // get all string that have the same color for (it++; it != access.value().end() && (*it).first == col; it++) { str += ucstring::makeFromUtf8((*it).second); } SendMessageA(_HEdit, EM_SETSEL, -1, -1); if ((col>>24) == 0) { // there s a specific color CharFormat.crTextColor = RGB ((col>>16)&0xFF, (col>>8)&0xFF, col&0xFF); SendMessageA(_HEdit, EM_SETCHARFORMAT, (WPARAM) SCF_SELECTION, (LPARAM) &CharFormat); } // add the string to the edit control SendMessageW(_HEdit, EM_REPLACESEL, FALSE, (LPARAM) str.c_str()); } // restore old selection SendMessageA(_HEdit, EM_SETSEL, startSel, endSel); SendMessageA(_HEdit,EM_SETMODIFY,(WPARAM)TRUE,(LPARAM)0); if (bottom) SendMessageA(_HEdit,WM_VSCROLL,(WPARAM)SB_BOTTOM,(LPARAM)0L); if (focus) { SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_OR,(LPARAM)ECO_AUTOVSCROLL); SendMessageA(_HEdit,EM_SETOPTIONS,ECOOP_OR,(LPARAM)ECO_AUTOHSCROLL); } } // clear the log access.value().clear (); } // // Update labels // updateLabels (); // // Manage windows message // MSG msg; while (PeekMessageW(&msg,NULL,0,0,PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } // // Wait // ////////////////////////////////////////////////////////////////// // WARNING: READ THIS !!!!!!!!!!!!!!!! /////////////////////////// // If at the release time, it freezes here, it's a microsoft bug: // http://support.microsoft.com/support/kb/articles/q173/2/60.asp nlSleep (1); } DeleteObject (_HFont); _HFont = NULL; DestroyWindow (_HWnd); _HWnd = NULL; DestroyWindow (_HEdit); _HEdit = NULL; FreeLibrary (_HLibModule); _HLibModule = NULL; } void CWinDisplayer::getWindowPos (uint32 &x, uint32 &y, uint32 &w, uint32 &h) { RECT rect; // get the w and h of the client array GetClientRect (_HWnd, &rect); w = rect.right - rect.left; h = rect.bottom - rect.top; // get the x and y of the window (not the client array) GetWindowRect (_HWnd, &rect); x = rect.left; y = rect.top; } } // NLMISC #endif // NL_OS_WINDOWS