// 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