1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "nel/misc/win_displayer.h"
32 #include "nel/misc/app_context.h"
33 #include "nel/misc/path.h"
34 #include "nel/misc/command.h"
35 #include "nel/misc/thread.h"
36 #include "nel/misc/ucstring.h"
46 static CHARFORMAT2A CharFormat
;
48 CWinDisplayer::CWinDisplayer(const char *displayerName
) : CWindowDisplayer(displayerName
), Exit(false)
51 createLabel("@Clear|CLEAR");
53 INelContext::getInstance().setWindowedApplication(true);
56 CWinDisplayer::~CWinDisplayer ()
60 LRESULT CALLBACK
WndProc (HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
68 if (LOWORD(wParam
) != WA_INACTIVE
)
70 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
72 SetFocus(cwd
->_HInputEdit
);
79 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
82 int w
= lParam
& 0xFFFF;
83 int h
= (lParam
>> 16) & 0xFFFF;
85 // SetWindowPos (cwd->_HEdit, NULL, 0, cwd->_ToolBarHeight, w, h-cwd->_ToolBarHeight-cwd->_InputEditHeight, SWP_NOZORDER | SWP_NOACTIVATE );
86 SetWindowPos (cwd
->_HInputEdit
, NULL
, 0, h
-cwd
->_InputEditHeight
, w
, cwd
->_InputEditHeight
, SWP_NOZORDER
| SWP_NOACTIVATE
);
91 case WM_DESTROY
: PostQuitMessage (0); break;
94 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
96 cwd
->_Continue
= false;
101 if (HIWORD(wParam
) == BN_CLICKED
)
103 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
104 // find the button and execute the command
105 CSynchronized
<std::vector
<CWindowDisplayer::CLabelEntry
> >::CAccessor
access (&cwd
->_Labels
);
106 for (uint i
= 0; i
< access
.value().size(); i
++)
108 if (access
.value()[i
].Hwnd
== (HWND
)lParam
)
110 if(access
.value()[i
].Value
== "@Clear|CLEAR")
112 // special commands because the clear must be called by the display thread and not main thread
117 // the button was found, add the command in the command stack
118 CSynchronized
<std::vector
<std::string
> >::CAccessor
accessCommands (&cwd
->_CommandsToExecute
);
120 nlassert (!access
.value()[i
].Value
.empty());
121 nlassert (access
.value()[i
].Value
[0] == '@');
123 string::size_type pos
= access
.value()[i
].Value
.find ("|");
124 if (pos
!= string::npos
)
126 str
= access
.value()[i
].Value
.substr(pos
+1);
130 str
= access
.value()[i
].Value
.substr(1);
133 accessCommands
.value().push_back(str
);
142 switch (((NMHDR
*)lParam
)->code
)
145 pmf
= (MSGFILTER
*)lParam
;
146 if (pmf
->msg
== WM_CHAR
)
148 if (pmf
->wParam
== VK_RETURN
)
152 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtr (hWnd
, GWLP_USERDATA
);
153 // get the text as unicode string
154 GetWindowTextW(cwd
->_HInputEdit
, wText
, 20000);
155 // and convert it to UTF-8 encoding.
156 TextSend
= wideToUtf8(wText
);
157 SendMessageA (cwd
->_HInputEdit
, WM_SETTEXT
, (WPARAM
)0, (LPARAM
)"");
158 const char *pos2
= TextSend
.c_str();
160 while (*pos2
!= '\0')
165 while (*pos2
!= '\0' && *pos2
!= '\n')
181 CSynchronized
<std::vector
<std::string
> >::CAccessor
access (&cwd
->_CommandsToExecute
);
182 access
.value().push_back(str
);
184 cwd
->_History
.push_back(str
);
185 cwd
->_PosInHistory
= (uint
)cwd
->_History
.size();
189 else if (pmf
->wParam
== VK_TAB
)
194 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
196 // get the text as unicode string
197 GetWindowTextW(cwd
->_HInputEdit
, wText
, 20000);
198 // and convert it to UTF-8 encoding
199 string str
= wideToUtf8(wText
);
200 nlassert (cwd
->Log
!= NULL
);
201 ICommand::expand (str
, *cwd
->Log
);
202 SendMessageW (cwd
->_HInputEdit
, WM_SETTEXT
, (WPARAM
)0, (LPARAM
)wText
);
204 SendMessageA (cwd
->_HInputEdit
, EM_SETSEL
, wcslen(wText
), wcslen(wText
));
209 else if (pmf
->msg
== WM_KEYUP
)
211 if (pmf
->wParam
== VK_UP
)
213 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrA (hWnd
, GWLP_USERDATA
);
215 if (cwd
->_PosInHistory
> 0)
216 cwd
->_PosInHistory
--;
218 if (!cwd
->_History
.empty())
221 // convert the text from UTF-8 to unicode
222 ucs
.fromUtf8(cwd
->_History
[cwd
->_PosInHistory
]);
224 // set the text as unicode string
225 if (!SetWindowTextW(cwd
->_HInputEdit
, (LPCWSTR
)ucs
.c_str()))
227 nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str());
230 SendMessageA (cwd
->_HInputEdit
, EM_SETSEL
, (WPARAM
)ucs
.size(), (LPARAM
)ucs
.size());
233 else if (pmf
->wParam
== VK_DOWN
)
235 CWinDisplayer
*cwd
=(CWinDisplayer
*)GetWindowLongPtrW (hWnd
, GWLP_USERDATA
);
237 if (cwd
->_PosInHistory
< cwd
->_History
.size()-1)
238 cwd
->_PosInHistory
++;
240 if (!cwd
->_History
.empty() && cwd
->_PosInHistory
< cwd
->_History
.size())
243 // convert the text from UTF-8 to unicode
244 ucs
.fromUtf8(cwd
->_History
[cwd
->_PosInHistory
]);
246 // set the text as unicode string
247 if (!SetWindowTextW(cwd
->_HInputEdit
, (LPCWSTR
)ucs
.c_str()))
249 nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str());
252 SendMessageA (cwd
->_HInputEdit
, EM_SETSEL
, (WPARAM
)ucs
.size(), (LPARAM
)ucs
.size());
259 return DefWindowProcW (hWnd
, message
, wParam
, lParam
);
262 void CWinDisplayer::updateLabels ()
264 bool needResize
= false;
266 CSynchronized
<std::vector
<CLabelEntry
> >::CAccessor
access (&_Labels
);
267 for (uint i
= 0; i
< access
.value().size(); i
++)
269 if (access
.value()[i
].NeedUpdate
&& !access
.value()[i
].Value
.empty())
271 if (access
.value()[i
].Hwnd
== NULL
)
273 // create a button for command and label for variables
274 if (access
.value()[i
].Value
[0] == '@')
276 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
);
280 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
);
282 SendMessageA ((HWND
)access
.value()[i
].Hwnd
, WM_SETFONT
, (WPARAM
)_HFont
, TRUE
);
288 // do this tricks to be sure that windows will clear what is after the number
289 if (access
.value()[i
].Value
[0] != '@')
290 n
= access
.value()[i
].Value
+ " ";
293 string::size_type pos
= access
.value()[i
].Value
.find ('|');
294 if (pos
!= string::npos
)
296 n
= access
.value()[i
].Value
.substr (1, pos
- 1);
300 n
= access
.value()[i
].Value
.substr (1);
304 SendMessageW((HWND
)access
.value()[i
].Hwnd
, WM_SETTEXT
, 0, (LPARAM
)nlUtf8ToWide(n
));
305 access
.value()[i
].NeedUpdate
= false;
313 void CWinDisplayer::resizeLabels ()
316 CSynchronized
<std::vector
<CLabelEntry
> >::CAccessor
access (&_Labels
);
319 GetClientRect (_HWnd
, &Rect
);
324 for (i
= 0; i
< access
.value().size (); i
++)
325 if (access
.value()[i
].Value
.empty())
333 while (i
+nb
!= access
.value().size () && !access
.value()[i
+nb
].Value
.empty()) nb
++;
339 delta
= Rect
.right
/ nb
;
341 for (uint j
= 0; j
< nb
; j
++)
343 if ((HWND
)access
.value()[i
+j
].Hwnd
!= NULL
)
344 SetWindowPos ((HWND
)access
.value()[i
+j
].Hwnd
, NULL
, j
*delta
, y
*_ToolBarHeight
, delta
, _ToolBarHeight
, SWP_NOZORDER
| SWP_NOACTIVATE
);
348 if (i
>= access
.value().size())
352 SetWindowPos (_HEdit
, NULL
, 0, nby
*_ToolBarHeight
, Rect
.right
, Rect
.bottom
-nby
*_ToolBarHeight
-_InputEditHeight
, SWP_NOZORDER
| SWP_NOACTIVATE
);
356 void CWinDisplayer::setTitleBar (const string
&titleBar
)
359 if (!titleBar
.empty())
364 wn
+= "Nel Service Console (compiled " __DATE__
" " __TIME__
" in " + nlMode
+ " mode)";
366 nldebug("SERVICE: Set title bar to '%s'", wn
.c_str());
368 if (!SetWindowTextW(_HWnd
, (LPWSTR
)ucstring::makeFromUtf8(wn
).c_str()))
370 nlwarning("SetWindowText failed: %s", formatErrorMessage(getLastError()).c_str());
374 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
)
388 memset (&wc
,0,sizeof(wc
));
389 wc
.style
= CS_HREDRAW
| CS_VREDRAW
;//| CS_DBLCLKS;
390 wc
.lpfnWndProc
= (WNDPROC
)WndProc
;
393 wc
.hInstance
= GetModuleHandleW(NULL
);
395 wc
.hCursor
= LoadCursorW(NULL
,(LPWSTR
)IDC_ARROW
);
396 wc
.hbrBackground
= (HBRUSH
)COLOR_WINDOW
;
397 wc
.lpszClassName
= L
"NLDisplayerClass";
398 wc
.lpszMenuName
= NULL
;
399 if ( !RegisterClassW(&wc
) ) return;
403 WndFlags
= WS_OVERLAPPEDWINDOW
/*| WS_CLIPCHILDREN | WS_CLIPSIBLINGS*/;
408 AdjustWindowRect(&WndRect
,WndFlags
,FALSE
);
411 _HWnd
= CreateWindowW (L
"NLDisplayerClass", L
"", WndFlags
, CW_USEDEFAULT
,CW_USEDEFAULT
, WndRect
.right
,WndRect
.bottom
, NULL
, NULL
, GetModuleHandle(NULL
), NULL
);
412 SetWindowLongPtrW (_HWnd
, GWLP_USERDATA
, (LONG_PTR
)this);
414 _HLibModule
= LoadLibraryW(L
"RICHED20.DLL");
415 if (_HLibModule
== NULL
)
417 nlerror ("RichEdit 2.0 library not found!");
432 _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());
435 // create the edit control
436 DWORD dwStyle
= WS_HSCROLL
| WS_CHILD
| WS_VISIBLE
| WS_VSCROLL
| ES_READONLY
| ES_LEFT
| ES_MULTILINE
/*| ES_AUTOVSCROLL*/;
439 dwStyle
&= ~WS_HSCROLL
;
441 dwStyle
|= WS_HSCROLL
;
443 _HEdit
= CreateWindowExW(WS_EX_OVERLAPPEDWINDOW
, RICHEDIT_CLASSW
, L
"", dwStyle
, 0, _ToolBarHeight
, w
, h
-_ToolBarHeight
-_InputEditHeight
, _HWnd
, (HMENU
) NULL
, (HINSTANCE
) GetWindowLongPtr(_HWnd
, GWLP_HINSTANCE
), NULL
);
444 SendMessageA (_HEdit
, WM_SETFONT
, (WPARAM
)_HFont
, TRUE
);
446 // set the edit text limit to lot of :)
447 SendMessageA (_HEdit
, EM_LIMITTEXT
, -1, 0);
449 CharFormat
.cbSize
= sizeof(CharFormat
);
450 CharFormat
.dwMask
= CFM_COLOR
;
451 SendMessageA(_HEdit
,EM_GETCHARFORMAT
,(WPARAM
)0,(LPARAM
)&CharFormat
);
452 CharFormat
.dwEffects
&= ~CFE_AUTOCOLOR
;
454 // create the input edit control
455 _HInputEdit
= CreateWindowExW(WS_EX_OVERLAPPEDWINDOW
, RICHEDIT_CLASSW
, L
"", WS_CHILD
| WS_VISIBLE
456 /*| ES_MULTILINE*/ | ES_WANTRETURN
| ES_NOHIDESEL
| ES_AUTOHSCROLL
, 0, h
-_InputEditHeight
, w
, _InputEditHeight
,
457 _HWnd
, NULL
, (HINSTANCE
) GetWindowLongPtr(_HWnd
, GWLP_HINSTANCE
), NULL
);
458 SendMessageW (_HInputEdit
, WM_SETFONT
, (WPARAM
)_HFont
, TRUE
);
460 LRESULT dwEvent
= SendMessageW(_HInputEdit
, EM_GETEVENTMASK
, (WPARAM
)0, (LPARAM
)0);
461 dwEvent
|= ENM_MOUSEEVENTS
| ENM_KEYEVENTS
| ENM_CHANGE
;
462 SendMessageA(_HInputEdit
, EM_SETEVENTMASK
, (WPARAM
)0, (LPARAM
)dwEvent
);
466 SetRect (&rc
, 0, 0, w
, h
);
467 AdjustWindowRectEx (&rc
, GetWindowStyle (_HWnd
), GetMenu (_HWnd
) != NULL
, GetWindowExStyle (_HWnd
));
469 LONG flag
= SWP_NOZORDER
| SWP_NOACTIVATE
;
471 if (x
== -1 && y
== -1) flag
|= SWP_NOMOVE
;
472 if (w
== -1 && h
== -1) flag
|= SWP_NOSIZE
;
474 SetWindowPos (_HWnd
, NULL
, x
, y
, w
, h
, flag
);
475 SetWindowPos (_HWnd
, NULL
, x
, y
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, flag
);
477 setTitleBar (titleBar
);
480 ShowWindow(_HWnd
,SW_MINIMIZE
);
482 ShowWindow(_HWnd
,SW_SHOW
);
484 SetFocus (_HInputEdit
);
489 void CWinDisplayer::clear ()
491 bool focus
= (GetFocus() == _HEdit
);
494 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_AND
,(LPARAM
)~ECO_AUTOVSCROLL
);
495 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_AND
,(LPARAM
)~ECO_AUTOHSCROLL
);
498 // get number of line
499 LRESULT nLine
= SendMessageW (_HEdit
, EM_GETLINECOUNT
, 0, 0) - 1;
501 // get size of the last line
502 LRESULT nIndex
= SendMessageW (_HEdit
, EM_LINEINDEX
, nLine
, 0);
504 // select all the text
505 SendMessageW (_HEdit
, EM_SETSEL
, 0, nIndex
);
507 // clear all the text
508 SendMessageW (_HEdit
, EM_REPLACESEL
, FALSE
, (LPARAM
) L
"");
510 SendMessageW(_HEdit
,EM_SETMODIFY
,(WPARAM
)TRUE
,(LPARAM
)0);
514 SendMessageW(_HEdit
,EM_SETOPTIONS
,ECOOP_OR
,(LPARAM
)ECO_AUTOVSCROLL
);
515 SendMessageW(_HEdit
,EM_SETOPTIONS
,ECOOP_OR
,(LPARAM
)ECO_AUTOHSCROLL
);
519 void CWinDisplayer::display_main ()
526 // Display the bufferized string
530 CSynchronized
<std::list
<std::pair
<uint32
, std::string
> > >::CAccessor
access (&_Buffer
);
531 std::list
<std::pair
<uint32
, std::string
> >::iterator it
;
533 sint vecSize
= (sint
)access
.value().size();
534 //nlassert (vecSize <= _HistorySize);
538 // look if we are at the bottom of the edit
540 info
.cbSize
= sizeof(info
);
541 info
.fMask
= SIF_ALL
;
544 if (GetScrollInfo(_HEdit
,SB_VERT
,&info
) != 0)
545 bottom
= (info
.nPage
== 0) || (info
.nMax
<=(info
.nPos
+(int)info
.nPage
));
547 // look if we have the focus
548 bool focus
= (GetFocus() == _HEdit
);
551 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_AND
,(LPARAM
)~ECO_AUTOVSCROLL
);
552 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_AND
,(LPARAM
)~ECO_AUTOHSCROLL
);
555 // store old selection
556 DWORD startSel
, endSel
;
557 SendMessageA (_HEdit
, EM_GETSEL
, (WPARAM
)&startSel
, (LPARAM
)&endSel
);
559 // find how many lines we have to remove in the current output to add new lines
561 // get number of line
562 LRESULT nLine
= SendMessageW (_HEdit
, EM_GETLINECOUNT
, 0, 0) - 1;
564 if (_HistorySize
> 0 && nLine
+vecSize
> _HistorySize
)
566 int nblineremove
= vecSize
;
567 //nlassert (nblineremove>0 && nblineremove <= _HistorySize);
569 if (nblineremove
== _HistorySize
)
571 SendMessageA (_HEdit
, WM_SETTEXT
, 0, (LPARAM
) "");
572 startSel
= endSel
= -1;
576 LRESULT oldIndex1
= SendMessageW (_HEdit
, EM_LINEINDEX
, 0, 0);
577 //nlassert (oldIndex1 != -1);
578 LRESULT oldIndex2
= SendMessageW (_HEdit
, EM_LINEINDEX
, nblineremove
, 0);
579 //nlassert (oldIndex2 != -1);
580 SendMessageW (_HEdit
, EM_SETSEL
, oldIndex1
, oldIndex2
);
581 SendMessageW (_HEdit
, EM_REPLACESEL
, FALSE
, (LPARAM
) L
"");
583 // update the selection due to the erasing
584 sint dt
= (sint
)(oldIndex2
- oldIndex1
);
585 if (startSel
< 65000)
587 if ((sint
)startSel
-dt
< 0) startSel
= -1;
593 if ((sint
)endSel
-dt
< 0) startSel
= endSel
= -1;
596 else startSel
= endSel
= -1;
600 for (it
= access
.value().begin(); it
!= access
.value().end(); )
602 ucstring str
= ucstring::makeFromUtf8((*it
).second
);
603 uint32 col
= (*it
).first
;
605 // get all string that have the same color
606 for (it
++; it
!= access
.value().end() && (*it
).first
== col
; it
++)
608 str
+= ucstring::makeFromUtf8((*it
).second
);
611 SendMessageA(_HEdit
, EM_SETSEL
, -1, -1);
615 // there s a specific color
616 CharFormat
.crTextColor
= RGB ((col
>>16)&0xFF, (col
>>8)&0xFF, col
&0xFF);
617 SendMessageA(_HEdit
, EM_SETCHARFORMAT
, (WPARAM
) SCF_SELECTION
, (LPARAM
) &CharFormat
);
620 // add the string to the edit control
621 SendMessageW(_HEdit
, EM_REPLACESEL
, FALSE
, (LPARAM
) str
.c_str());
624 // restore old selection
625 SendMessageA(_HEdit
, EM_SETSEL
, startSel
, endSel
);
627 SendMessageA(_HEdit
,EM_SETMODIFY
,(WPARAM
)TRUE
,(LPARAM
)0);
630 SendMessageA(_HEdit
,WM_VSCROLL
,(WPARAM
)SB_BOTTOM
,(LPARAM
)0L);
634 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_OR
,(LPARAM
)ECO_AUTOVSCROLL
);
635 SendMessageA(_HEdit
,EM_SETOPTIONS
,ECOOP_OR
,(LPARAM
)ECO_AUTOHSCROLL
);
640 access
.value().clear ();
650 // Manage windows message
654 while (PeekMessageW(&msg
,NULL
,0,0,PM_REMOVE
))
656 TranslateMessage(&msg
);
657 DispatchMessageW(&msg
);
664 //////////////////////////////////////////////////////////////////
665 // WARNING: READ THIS !!!!!!!!!!!!!!!! ///////////////////////////
666 // If at the release time, it freezes here, it's a microsoft bug:
667 // http://support.microsoft.com/support/kb/articles/q173/2/60.asp
671 DeleteObject (_HFont
);
673 DestroyWindow (_HWnd
);
675 DestroyWindow (_HEdit
);
677 FreeLibrary (_HLibModule
);
681 void CWinDisplayer::getWindowPos (uint32
&x
, uint32
&y
, uint32
&w
, uint32
&h
)
684 // get the w and h of the client array
685 GetClientRect (_HWnd
, &rect
);
686 w
= rect
.right
- rect
.left
;
687 h
= rect
.bottom
- rect
.top
;
689 // get the x and y of the window (not the client array)
690 GetWindowRect (_HWnd
, &rect
);
698 #endif // NL_OS_WINDOWS