1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 // generic user interface
18 // dialog base classes and some user dialogs
19 
20 #include "C4Include.h"
21 #include "C4ForbidLibraryCompilation.h"
22 #include "gui/C4Gui.h"
23 
24 #include "game/C4Application.h"
25 #include "game/C4GameScript.h"
26 #include "game/C4Viewport.h"
27 #include "graphics/C4Draw.h"
28 #include "graphics/C4GraphicsResource.h"
29 #include "object/C4DefList.h"
30 #include "object/C4Def.h"
31 
32 #include "graphics/C4DrawGL.h"
33 #include "platform/StdRegistry.h"
34 
35 namespace C4GUI
36 {
37 
38 // --------------------------------------------------
39 // FrameDecoration
40 
Clear()41 	void FrameDecoration::Clear()
42 	{
43 		pSourceDef = nullptr;
44 		idSourceDef = C4ID::None;
45 		dwBackClr = C4GUI_StandardBGColor;
46 		iBorderTop=iBorderLeft=iBorderRight=iBorderBottom=0;
47 		fHasGfxOutsideClientArea = false;
48 		fctTop.Default();
49 		fctTopRight.Default();
50 		fctRight.Default();
51 		fctBottomRight.Default();
52 		fctBottom.Default();
53 		fctBottomLeft.Default();
54 		fctLeft.Default();
55 		fctTopLeft.Default();
56 	}
57 
SetFacetByAction(C4Def * pOfDef,C4TargetFacet & rfctTarget,const char * szFacetName)58 	bool FrameDecoration::SetFacetByAction(C4Def *pOfDef, C4TargetFacet &rfctTarget, const char *szFacetName)
59 	{
60 		// get action
61 		StdStrBuf sActName;
62 		sActName.Format("FrameDeco%s", szFacetName);
63 		C4PropList *act = pOfDef->GetActionByName(sActName.getData());
64 		if (!act) return false;
65 		// set facet by it
66 		int32_t x = act->GetPropertyInt(P_X);
67 		int32_t y = act->GetPropertyInt(P_Y);
68 		int32_t wdt = act->GetPropertyInt(P_Wdt);
69 		int32_t hgt = act->GetPropertyInt(P_Hgt);
70 		int32_t tx = act->GetPropertyInt(P_OffX);
71 		int32_t ty = act->GetPropertyInt(P_OffY);
72 		if (!wdt || !hgt) return false;
73 		rfctTarget.Set(pOfDef->Graphics.GetBitmap(), x, y, wdt, hgt, tx, ty);
74 		return true;
75 	}
76 
SetByDef(C4ID idSourceDef)77 	bool FrameDecoration::SetByDef(C4ID idSourceDef)
78 	{
79 		return SetByDef(C4Id2Def(idSourceDef));
80 	}
81 
SetByDef(C4Def * pSrcDef)82 	bool FrameDecoration::SetByDef(C4Def *pSrcDef)
83 	{
84 		if (!pSrcDef) return false;
85 		// script compiled?
86 		if (!pSrcDef->Script.IsReady()) return false;
87 		// reset old
88 		Clear();
89 		this->pSourceDef = pSrcDef;
90 		this->idSourceDef = pSrcDef->id;
91 		// query values
92 		dwBackClr     = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BackClr"     ).getData()).getInt();
93 		iBorderTop    = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderTop"   ).getData()).getInt();
94 		iBorderLeft   = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderLeft"  ).getData()).getInt();
95 		iBorderRight  = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderRight" ).getData()).getInt();
96 		iBorderBottom = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderBottom").getData()).getInt();
97 		// get gfx
98 		SetFacetByAction(pSrcDef, fctTop        , "Top"        );
99 		SetFacetByAction(pSrcDef, fctTopRight   , "TopRight"   );
100 		SetFacetByAction(pSrcDef, fctRight      , "Right"      );
101 		SetFacetByAction(pSrcDef, fctBottomRight, "BottomRight");
102 		SetFacetByAction(pSrcDef, fctBottom     , "Bottom"     );
103 		SetFacetByAction(pSrcDef, fctBottomLeft , "BottomLeft" );
104 		SetFacetByAction(pSrcDef, fctLeft       , "Left"       );
105 		SetFacetByAction(pSrcDef, fctTopLeft    , "TopLeft"    );
106 		// check for gfx outside main area
107 		fHasGfxOutsideClientArea = (fctTopLeft.TargetY < 0) || (fctTop.TargetY < 0) || (fctTopRight.TargetY < 0)
108 		                           || (fctTopLeft.TargetX < 0) || (fctLeft.TargetX < 0) || (fctBottomLeft.TargetX < 0)
109 		                           || (fctTopRight.TargetX + fctTopRight.Wdt > iBorderRight) || (fctRight.TargetX + fctRight.Wdt > iBorderRight) || (fctBottomRight.TargetX + fctBottomRight.Wdt > iBorderRight)
110 		                           || (fctBottomLeft.TargetY + fctBottomLeft.Hgt > iBorderBottom) || (fctBottom.TargetY + fctBottom.Hgt > iBorderBottom) || (fctBottomRight.TargetY + fctBottomRight.Hgt > iBorderBottom);
111 		// k, done
112 		return true;
113 	}
114 
UpdateGfx()115 	bool FrameDecoration::UpdateGfx()
116 	{
117 		// simply re-set by def
118 		return SetByDef(idSourceDef);
119 	}
120 
Draw(C4TargetFacet & cgo,C4Rect & rcBounds)121 	void FrameDecoration::Draw(C4TargetFacet &cgo, C4Rect &rcBounds)
122 	{
123 		// draw BG
124 		int ox = cgo.TargetX+rcBounds.x, oy = cgo.TargetY+rcBounds.y;
125 		pDraw->DrawBoxDw(cgo.Surface, ox,oy,ox+rcBounds.Wdt-1,oy+rcBounds.Hgt-1,dwBackClr);
126 		// draw borders
127 		int x,y,Q;
128 		// top
129 		if ((Q=fctTop.Wdt))
130 		{
131 			for (x = iBorderLeft; x < rcBounds.Wdt-iBorderRight; x += fctTop.Wdt)
132 			{
133 				int w = std::min<int>(fctTop.Wdt, rcBounds.Wdt-iBorderRight-x);
134 				fctTop.Wdt = w;
135 				fctTop.Draw(cgo.Surface, ox+x, oy+fctTop.TargetY);
136 			}
137 			fctTop.Wdt = Q;
138 		}
139 		// left
140 		if ((Q=fctLeft.Hgt))
141 		{
142 			for (y = iBorderTop; y < rcBounds.Hgt-iBorderBottom; y += fctLeft.Hgt)
143 			{
144 				int h = std::min<int>(fctLeft.Hgt, rcBounds.Hgt-iBorderBottom-y);
145 				fctLeft.Hgt = h;
146 				fctLeft.Draw(cgo.Surface, ox+fctLeft.TargetX, oy+y);
147 			}
148 			fctLeft.Hgt = Q;
149 		}
150 		// right
151 		if ((Q=fctRight.Hgt))
152 		{
153 			for (y = iBorderTop; y < rcBounds.Hgt-iBorderBottom; y += fctRight.Hgt)
154 			{
155 				int h = std::min<int>(fctRight.Hgt, rcBounds.Hgt-iBorderBottom-y);
156 				fctRight.Hgt = h;
157 				fctRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctRight.TargetX, oy+y);
158 			}
159 			fctRight.Hgt = Q;
160 		}
161 		// bottom
162 		if ((Q=fctBottom.Wdt))
163 		{
164 			for (x = iBorderLeft; x < rcBounds.Wdt-iBorderRight; x += fctBottom.Wdt)
165 			{
166 				int w = std::min<int>(fctBottom.Wdt, rcBounds.Wdt-iBorderRight-x);
167 				fctBottom.Wdt = w;
168 				fctBottom.Draw(cgo.Surface, ox+x, oy+rcBounds.Hgt-iBorderBottom+fctBottom.TargetY);
169 			}
170 			fctBottom.Wdt = Q;
171 		}
172 		// draw edges
173 		fctTopLeft.Draw(cgo.Surface, ox+fctTopLeft.TargetX,oy+fctTopLeft.TargetY);
174 		fctTopRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctTopRight.TargetX,oy+fctTopRight.TargetY);
175 		fctBottomLeft.Draw(cgo.Surface, ox+fctBottomLeft.TargetX,oy+rcBounds.Hgt-iBorderBottom+fctBottomLeft.TargetY);
176 		fctBottomRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctBottomRight.TargetX,oy+rcBounds.Hgt-iBorderBottom+fctBottomRight.TargetY);
177 	}
178 
179 // --------------------------------------------------
180 // DialogWindow
181 
182 #ifdef USE_WIN32_WINDOWS
183 
Init(C4AbstractApp * pApp,const char * Title,const C4Rect & rcBounds,const char * szID)184 	C4Window * DialogWindow::Init(C4AbstractApp * pApp, const char * Title, const C4Rect &rcBounds, const char *szID)
185 	{
186 		C4Window * result = C4Window::Init(C4Window::W_GuiWindow, pApp, Title, &rcBounds);
187 		if (result)
188 		{
189 			// update pos
190 			if (szID && *szID)
191 				RestoreWindowPosition(hWindow, FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false);
192 			// and show
193 			::ShowWindow(hWindow, SW_SHOW);
194 		}
195 		return result;
196 	}
197 
198 #else
Init(C4AbstractApp * pApp,const char * Title,const C4Rect & rcBounds,const char * szID)199 	C4Window * DialogWindow::Init(C4AbstractApp * pApp, const char * Title, const C4Rect &rcBounds, const char *szID)
200 	{
201 		C4Window * result = C4Window::Init(C4Window::W_GuiWindow, pApp, Title, &rcBounds);
202 		if (result)
203 		{
204 			// update pos
205 			if (szID && *szID)
206 				RestorePosition(FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false);
207 			else
208 				SetSize(rcBounds.Wdt, rcBounds.Hgt);
209 		}
210 		return result;
211 	}
212 #endif // _WIN32
213 
PerformUpdate()214 	void DialogWindow::PerformUpdate()
215 	{
216 		if (!pDialog)
217 			return; // safety
218 		C4Rect r;
219 		GetSize(&r);
220 		if (pSurface)
221 		{
222 			pSurface->Wdt = r.Wdt;
223 			pSurface->Hgt = r.Hgt;
224 #ifndef USE_CONSOLE
225 			pGL->PrepareRendering(pSurface);
226 			glClear(GL_COLOR_BUFFER_BIT);
227 #endif
228 		}
229 		C4TargetFacet cgo;
230 		cgo.Set(nullptr, 0, 0, r.Wdt, r.Hgt, 0, 0);
231 		pDialog->Draw(cgo);
232 	}
233 
Close()234 	void DialogWindow::Close()
235 	{
236 		// FIXME: Close the dialog of this window
237 	}
238 
CreateConsoleWindow()239 	bool Dialog::CreateConsoleWindow()
240 	{
241 #ifdef WITH_QT_EDITOR
242 		// TODO: Implement these as Qt editor windows.
243 		// This currently creates an empty window in Windows and a segfault in Linux.
244 		return false;
245 #endif
246 		// already created?
247 		if (pWindow) return true;
248 		// create it!
249 		pWindow = new DialogWindow();
250 		if (!pWindow->Init(&Application, TitleString.getData(), rcBounds, GetID()))
251 		{
252 			delete pWindow;
253 			pWindow = nullptr;
254 			return false;
255 		}
256 		// create rendering context
257 		pWindow->pSurface = new C4Surface(&Application, pWindow);
258 		pWindow->pDialog = this;
259 		return true;
260 	}
261 
DestroyConsoleWindow()262 	void Dialog::DestroyConsoleWindow()
263 	{
264 		if (pWindow)
265 		{
266 			delete pWindow->pSurface;
267 			pWindow->Clear();
268 			delete pWindow;
269 			pWindow = nullptr;
270 		}
271 	}
272 
Dialog(int32_t iWdt,int32_t iHgt,const char * szTitle,bool fViewportDlg)273 	Dialog::Dialog(int32_t iWdt, int32_t iHgt, const char *szTitle, bool fViewportDlg):
274 			Window(), pTitle(nullptr), pCloseBtn(nullptr), fDelOnClose(false), fViewportDlg(fViewportDlg), pWindow(nullptr), pFrameDeco(nullptr)
275 	{
276 		// zero fields
277 		pActiveCtrl = nullptr;
278 		fShow = fOK = false;
279 		iFade = 100; eFade = eFadeNone;
280 		// add title
281 		rcBounds.Wdt = iWdt;
282 		SetTitle(szTitle);
283 		// set size - calcs client rect as well
284 		SetBounds(C4Rect(0,0,iWdt,iHgt));
285 		// create key callbacks
286 		C4CustomKey::CodeList Keys;
287 		Keys.emplace_back(K_TAB);
288 		if (Config.Controls.GamepadGuiControl)
289 		{
290 			ControllerKeys::Right(Keys);
291 		}
292 		pKeyAdvanceControl = new C4KeyBinding(Keys, "GUIAdvanceFocus", KEYSCOPE_Gui,
293 		                                      new DlgKeyCBEx<Dialog, bool>(*this, false, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
294 		Keys.clear();
295 		Keys.emplace_back(K_TAB, KEYS_Shift);
296 		if (Config.Controls.GamepadGuiControl)
297 		{
298 			ControllerKeys::Left(Keys);
299 		}
300 		pKeyAdvanceControlB = new C4KeyBinding(Keys, "GUIAdvanceFocusBack", KEYSCOPE_Gui,
301 		                                       new DlgKeyCBEx<Dialog, bool>(*this, true, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
302 		Keys.clear();
303 		Keys.emplace_back(KEY_Any, KEYS_Alt);
304 		Keys.emplace_back(KEY_Any, C4KeyShiftState(KEYS_Alt | KEYS_Shift));
305 		pKeyHotkey = new C4KeyBinding(Keys, "GUIHotkey", KEYSCOPE_Gui,
306 		                              new DlgKeyCBPassKey<Dialog>(*this, &Dialog::KeyHotkey), C4CustomKey::PRIO_Ctrl);
307 		Keys.clear();
308 		Keys.emplace_back(K_RETURN);
309 		if (Config.Controls.GamepadGuiControl)
310 		{
311 			ControllerKeys::Ok(Keys);
312 		}
313 		pKeyEnter = new C4KeyBinding(Keys, "GUIDialogOkay", KEYSCOPE_Gui,
314 		                             new DlgKeyCB<Dialog>(*this, &Dialog::KeyEnter), C4CustomKey::PRIO_Dlg);
315 		Keys.clear();
316 		Keys.emplace_back(K_ESCAPE);
317 		if (Config.Controls.GamepadGuiControl)
318 		{
319 			ControllerKeys::Cancel(Keys);
320 		}
321 		pKeyEscape = new C4KeyBinding(Keys, "GUIDialogAbort", KEYSCOPE_Gui,
322 		                              new DlgKeyCB<Dialog>(*this, &Dialog::KeyEscape), C4CustomKey::PRIO_Dlg);
323 		Keys.clear();
324 		Keys.emplace_back(KEY_Any);
325 		Keys.emplace_back(KEY_Any, KEYS_Shift);
326 		pKeyFocusDefControl = new C4KeyBinding(Keys, "GUIFocusDefault", KEYSCOPE_Gui,
327 		                                       new DlgKeyCB<Dialog>(*this, &Dialog::KeyFocusDefault), C4CustomKey::PRIO_Dlg);
328 	}
329 
GetDefaultTitleHeight()330 	int32_t Dialog::GetDefaultTitleHeight()
331 	{
332 		// default title font
333 		return std::min<int32_t>(::GraphicsResource.TextFont.GetLineHeight(), C4GUI_MinWoodBarHgt);
334 	}
335 
SetTitle(const char * szTitle,bool fShowCloseButton)336 	void Dialog::SetTitle(const char *szTitle, bool fShowCloseButton)
337 	{
338 		// always keep local copy of title
339 		TitleString.Copy(szTitle);
340 		// console mode dialogs: Use window bar
341 		if (Application.isEditor && !IsViewportDialog())
342 		{
343 			if (pWindow) pWindow->SetTitle(szTitle ? szTitle : "");
344 			return;
345 		}
346 		// set new
347 		if (szTitle && *szTitle)
348 		{
349 			int32_t iTextHgt = WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
350 			if (pTitle)
351 			{
352 				pTitle->GetBounds() = C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt);
353 				// noupdate if title is same - this is necessary to prevent scrolling reset when refilling internal menus
354 				if (SEqual(pTitle->GetText(), szTitle)) return;
355 				pTitle->SetText(szTitle);
356 			}
357 			else
358 				AddElement(pTitle = new WoodenLabel(szTitle, C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont, ALeft, false));
359 			pTitle->SetToolTip(szTitle);
360 			pTitle->SetDragTarget(this);
361 			pTitle->SetAutoScrollTime(C4GUI_TitleAutoScrollTime);
362 			if (fShowCloseButton)
363 			{
364 				pTitle->SetRightIndent(20); // for close button
365 				if (!pCloseBtn)
366 				{
367 					AddElement(pCloseBtn = new CallbackButton<Dialog, IconButton>(Ico_Close, pTitle->GetToprightCornerRect(16,16,4,4,0), '\0', &Dialog::OnUserClose));
368 					pCloseBtn->SetToolTip(LoadResStr("IDS_MNU_CLOSE"));
369 				}
370 				else
371 					pCloseBtn->GetBounds() = pTitle->GetToprightCornerRect(16,16,4,4,0);
372 			}
373 		}
374 		else
375 		{
376 			if (pTitle) { delete pTitle; pTitle=nullptr; }
377 			if (pCloseBtn) { delete pCloseBtn; pCloseBtn = nullptr; }
378 		}
379 	}
380 
~Dialog()381 	Dialog::~Dialog()
382 	{
383 		// kill key bindings
384 		delete pKeyAdvanceControl;
385 		delete pKeyAdvanceControlB;
386 		delete pKeyHotkey;
387 		delete pKeyEscape;
388 		delete pKeyEnter;
389 		delete pKeyFocusDefControl;
390 		// clear window
391 		DestroyConsoleWindow();
392 		// avoid endless delete/close-recursion
393 		fDelOnClose = false;
394 		// free deco
395 		if (pFrameDeco) pFrameDeco->Deref();
396 	}
397 
UpdateSize()398 	void Dialog::UpdateSize()
399 	{
400 		// update title bar position
401 		if (pTitle)
402 		{
403 			int32_t iTextHgt = WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
404 			pTitle->SetBounds(C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt));
405 			if (pCloseBtn) pCloseBtn->SetBounds(pTitle->GetToprightCornerRect(16,16,4,4,0));
406 		}
407 		// inherited
408 		Window::UpdateSize();
409 		// update assigned window
410 		if (pWindow)
411 		{
412 			pWindow->SetSize(rcBounds.Wdt,rcBounds.Hgt);
413 		}
414 	}
415 
UpdatePos()416 	void Dialog::UpdatePos()
417 	{
418 		// Dialogs with their own windows can only be at 0/0
419 		if (pWindow)
420 		{
421 			rcBounds.x = 0;
422 			rcBounds.y = 0;
423 		}
424 		Window::UpdatePos();
425 	}
426 
RemoveElement(Element * pChild)427 	void Dialog::RemoveElement(Element *pChild)
428 	{
429 		// inherited
430 		Window::RemoveElement(pChild);
431 		// clear ptr
432 		if (pChild == pActiveCtrl) pActiveCtrl = nullptr;
433 	}
434 
Draw(C4TargetFacet & cgo0)435 	void Dialog::Draw(C4TargetFacet &cgo0)
436 	{
437 		C4TargetFacet cgo; cgo.Set(cgo0);
438 		// Dialogs with a window just ignore the cgo.
439 		if (pWindow)
440 		{
441 			cgo.Surface = pWindow->pSurface;
442 			cgo.X = 0; cgo.Y = 0; cgo.Wdt = rcBounds.Wdt; cgo.Hgt = rcBounds.Hgt;
443 		}
444 		Screen *pScreen;
445 		// evaluate fading
446 		switch (eFade)
447 		{
448 		case eFadeNone: break; // no fading
449 		case eFadeIn:
450 			// fade in
451 			if ((iFade+=10) >= 100)
452 			{
453 				if ((pScreen = GetScreen()))
454 				{
455 					if (pScreen->GetTopDialog() == this)
456 						pScreen->ActivateDialog(this);
457 				}
458 				eFade = eFadeNone;
459 			}
460 			break;
461 		case eFadeOut:
462 			// fade out
463 			if ((iFade-=10) <= 0)
464 			{
465 				fVisible = fShow = false;
466 				if ((pScreen = GetScreen()))
467 					pScreen->RecheckActiveDialog();
468 				eFade = eFadeNone;
469 			}
470 		}
471 		// set fade
472 		if (iFade < 100)
473 		{
474 			if (iFade <= 0) return;
475 			pDraw->ActivateBlitModulation((iFade*255/100)<<24 | 0xffffff);
476 		}
477 		// separate window: Clear background
478 		if (pWindow)
479 			pDraw->DrawBoxDw(cgo.Surface, rcBounds.x, rcBounds.y, rcBounds.Wdt-1, rcBounds.Hgt-1, (0xff << 24) | (C4GUI_StandardBGColor & 0xffffff) );
480 		// draw window + contents (evaluates IsVisible)
481 		Window::Draw(cgo);
482 		// reset blit modulation
483 		if (iFade<100) pDraw->DeactivateBlitModulation();
484 		// blit output to own window
485 		if (pWindow)
486 		{
487 			// Draw context menu on editor window
488 			ContextMenu *menu;
489 			if ((menu = GetScreen()->pContext))
490 			{
491 				if (menu->GetTargetDialog() == this)
492 				{
493 					menu->Draw(cgo);
494 				}
495 			}
496 			// Editor window: Blit to output
497 			C4Rect rtSrc,rtDst;
498 			rtSrc.x=rcBounds.x; rtSrc.y=rcBounds.y;  rtSrc.Wdt=rcBounds.Wdt; rtSrc.Hgt=rcBounds.Hgt;
499 			rtDst.x=0; rtDst.y=0;    rtDst.Wdt=rcBounds.Wdt; rtDst.Hgt=rcBounds.Hgt;
500 			pWindow->pSurface->PageFlip(&rtSrc, &rtDst);
501 		}
502 	}
503 
DrawElement(C4TargetFacet & cgo)504 	void Dialog::DrawElement(C4TargetFacet &cgo)
505 	{
506 		// custom border?
507 		if (pFrameDeco)
508 			pFrameDeco->Draw(cgo, rcBounds);
509 		else
510 		{
511 			// standard border/bg then
512 			// draw background
513 			pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x,cgo.TargetY+rcBounds.y,rcBounds.x+rcBounds.Wdt-1+cgo.TargetX,rcBounds.y+rcBounds.Hgt-1+cgo.TargetY,C4GUI_StandardBGColor);
514 			// draw frame
515 			Draw3DFrame(cgo);
516 		}
517 	}
518 
CharIn(const char * c)519 	bool Dialog::CharIn(const char * c)
520 	{
521 		// reroute to active control
522 		if (pActiveCtrl && pActiveCtrl->CharIn(c)) return true;
523 		// unprocessed: Focus default control
524 		// Except for space, which may have been processed as a key already
525 		// (changing focus here would render buttons unusable, because they switch on KeyUp)
526 		Control *pDefCtrl = GetDefaultControl();
527 		if (pDefCtrl && pDefCtrl != pActiveCtrl && (!c || *c != 0x20))
528 		{
529 			SetFocus(pDefCtrl, false);
530 			if (pActiveCtrl && pActiveCtrl->CharIn(c))
531 				return true;
532 		}
533 		return false;
534 	}
535 
KeyHotkey(const C4KeyCodeEx & key)536 	bool Dialog::KeyHotkey(const C4KeyCodeEx &key)
537 	{
538 		StdStrBuf sKey = C4KeyCodeEx::KeyCode2String(key.Key, true, true);
539 		// do hotkey procs for standard alphanumerics only
540 		if (sKey.getLength() != 1) return false;
541 		WORD wKey = WORD(*sKey.getData());
542 		if (Inside<WORD>(TOUPPERIFX11(wKey), 'A', 'Z')) if (OnHotkey(char(TOUPPERIFX11(wKey)))) return true;
543 		if (Inside<WORD>(TOUPPERIFX11(wKey), '0', '9')) if (OnHotkey(char(TOUPPERIFX11(wKey)))) return true;
544 		return false;
545 	}
546 
KeyFocusDefault()547 	bool Dialog::KeyFocusDefault()
548 	{
549 		// unprocessed key: Focus default control
550 		Control *pDefCtrl = GetDefaultControl();
551 		if (pDefCtrl && pDefCtrl != pActiveCtrl)
552 			SetFocus(pDefCtrl, false);
553 		// never mark this as processed, so a later char message to the control may be sent (for deselected chat)
554 		return false;
555 	}
556 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)557 	void Dialog::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
558 	{
559 		// inherited will do...
560 		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
561 	}
562 
SetFocus(Control * pCtrl,bool fByMouse)563 	void Dialog::SetFocus(Control *pCtrl, bool fByMouse)
564 	{
565 		// no change?
566 		if (pCtrl == pActiveCtrl) return;
567 		// leave old focus
568 		if (pActiveCtrl)
569 		{
570 			Control *pC = pActiveCtrl;
571 			pActiveCtrl = nullptr;
572 			pC->OnLooseFocus();
573 			// if leaving the old focus set a new one, abort here because it looks like the control didn't want to lose focus
574 			if (pActiveCtrl) return;
575 		}
576 		// set new
577 		if ((pActiveCtrl = pCtrl)) pCtrl->OnGetFocus(fByMouse);
578 	}
579 
AdvanceFocus(bool fBackwards)580 	void Dialog::AdvanceFocus(bool fBackwards)
581 	{
582 		// get element to start from
583 		Element *pCurrElement = pActiveCtrl;
584 		// find new control
585 		for (;;)
586 		{
587 			// get next element
588 			pCurrElement = GetNextNestedElement(pCurrElement, fBackwards);
589 			// end reached: start from beginning
590 			if (!pCurrElement && pActiveCtrl) if (!(pCurrElement = GetNextNestedElement(nullptr, fBackwards))) return;
591 			// cycled?
592 			if (pCurrElement == pActiveCtrl)
593 			{
594 				// but current is no longer a focus element? Then defocus it and return
595 				if (pCurrElement && !pCurrElement->IsFocusElement())
596 					SetFocus(nullptr, false);
597 				return;
598 			}
599 			// for list elements, check whether the child can be selected
600 			if (pCurrElement->GetParent() && !pCurrElement->GetParent()->IsSelectedChild(pCurrElement)) continue;
601 			// check if this is a new control
602 			Control *pFocusCtrl = pCurrElement->IsFocusElement();
603 			if (pFocusCtrl && pFocusCtrl != pActiveCtrl && pFocusCtrl->IsVisible())
604 			{
605 				// set focus here...
606 				SetFocus(pFocusCtrl, false);
607 				// ...done!
608 				return;
609 			}
610 		}
611 		// never reached
612 	}
613 
Show(Screen * pOnScreen,bool fCB)614 	bool Dialog::Show(Screen *pOnScreen, bool fCB)
615 	{
616 		// already shown?
617 		if (fShow) return false;
618 		// default screen
619 		if (!pOnScreen) if (!(pOnScreen = Screen::GetScreenS())) return false;
620 		// show there
621 		pOnScreen->ShowDialog(this, false);
622 		fVisible = true;
623 		// developer mode: Create window
624 		if (Application.isEditor && !IsViewportDialog())
625 			if (!CreateConsoleWindow()) return false;
626 		// CB
627 		if (fCB) OnShown();
628 		return true;
629 	}
630 
Close(bool fOK)631 	void Dialog::Close(bool fOK)
632 	{
633 		// already closed?
634 		if (!fShow) return;
635 		// set OK flag
636 		this->fOK = fOK;
637 		// get screen
638 		Screen *pScreen = GetScreen();
639 		if (pScreen) pScreen->CloseDialog(this, false); else fShow = false;
640 		// developer mode: Remove window
641 		if (pWindow) DestroyConsoleWindow();
642 		// do callback - last call, because it might do perilous things
643 		OnClosed(fOK);
644 	}
645 
OnClosed(bool fOK)646 	void Dialog::OnClosed(bool fOK)
647 	{
648 		// developer mode: Remove window
649 		if (pWindow) DestroyConsoleWindow();
650 		// delete when closing?
651 		if (fDelOnClose)
652 		{
653 			fDelOnClose=false;
654 			delete this;
655 		}
656 	}
657 
DoModal()658 	bool Dialog::DoModal()
659 	{
660 		// Cancel all dialogues if game is left (including e.g. league dialogues)
661 		if (::Application.IsQuittingGame()) return false;
662 		// main message loop
663 		while (fShow)
664 		{
665 			// dialog idle proc
666 			OnIdle();
667 			// Modal dialogue during running game is tricky. Do not execute game!
668 			bool fGameWasRunning = ::Game.IsRunning;
669 			::Game.IsRunning = false;
670 			// handle messages - this may block until the next timer
671 			if (!Application.ScheduleProcs())
672 				return false; // game GUI and lobby will deleted in Game::Clear()
673 			// reset game run state
674 			if (fGameWasRunning) ::Game.IsRunning = true;
675 		}
676 		// return whether dlg was OK
677 		return fOK;
678 	}
679 
Execute()680 	bool Dialog::Execute()
681 	{
682 		// process messages
683 		if (!Application.ScheduleProcs(0))
684 			return false;
685 		// check status
686 		if (!fShow) return false;
687 		return true;
688 	}
689 
Execute2()690 	bool Dialog::Execute2()
691 	{
692 		// execute
693 		if (Execute()) return true;
694 		// delete self if closed
695 		delete this;
696 		return false;
697 	}
698 
IsActive(bool fForKeyboard)699 	bool Dialog::IsActive(bool fForKeyboard)
700 	{
701 		// must be fully visible
702 		if (!IsShown() || IsFading()) return false;
703 		// screen-less dialogs are always inactive (not yet added)
704 		Screen *pScreen = GetScreen();
705 		if (!pScreen) return false;
706 		// no keyboard focus if screen is in context mode
707 		if (fForKeyboard && pScreen->HasContext()) return false;
708 		// always okay in shared mode: all dlgs accessible by mouse
709 		if (!pScreen->IsExclusive() && !fForKeyboard) return true;
710 		// exclusive mode or keyboard input: Only one dlg active
711 		return pScreen->pActiveDlg == this;
712 	}
713 
FadeIn(Screen * pOnScreen)714 	bool Dialog::FadeIn(Screen *pOnScreen)
715 	{
716 		// default screen
717 		if (!pOnScreen) pOnScreen = Screen::GetScreenS();
718 		// fade in there
719 		pOnScreen->ShowDialog(this, true);
720 		iFade = 0;
721 		eFade = eFadeIn;
722 		fVisible = true;
723 		OnShown();
724 		// done, success
725 		return true;
726 	}
727 
FadeOut(bool fCloseWithOK)728 	void Dialog::FadeOut(bool fCloseWithOK)
729 	{
730 		// only if shown, or being faded in
731 		if (!IsShown() && (!fVisible || eFade!=eFadeIn)) return;
732 		// set OK flag
733 		this->fOK = fCloseWithOK;
734 		// fade out
735 		Screen *pOnScreen = GetScreen();
736 		if (!pOnScreen) return;
737 		pOnScreen->CloseDialog(this, true);
738 		eFade = eFadeOut;
739 		// do callback - last call, because it might do perilous things
740 		OnClosed(fCloseWithOK);
741 	}
742 
ApplyElementOffset(int32_t & riX,int32_t & riY)743 	void Dialog::ApplyElementOffset(int32_t &riX, int32_t &riY)
744 	{
745 		// inherited
746 		Window::ApplyElementOffset(riX, riY);
747 		// apply viewport offset, if a viewport is assigned
748 		C4Viewport *pVP = GetViewport();
749 		if (pVP)
750 		{
751 			C4Rect rcVP(pVP->GetOutputRect());
752 			riX -= rcVP.x; riY -= rcVP.y;
753 		}
754 	}
755 
ApplyInvElementOffset(int32_t & riX,int32_t & riY)756 	void Dialog::ApplyInvElementOffset(int32_t &riX, int32_t &riY)
757 	{
758 		// inherited
759 		Window::ApplyInvElementOffset(riX, riY);
760 		// apply viewport offset, if a viewport is assigned
761 		C4Viewport *pVP = GetViewport();
762 		if (pVP)
763 		{
764 			C4Rect rcVP(pVP->GetOutputRect());
765 			riX += rcVP.x; riY += rcVP.y;
766 		}
767 	}
768 
SetClientSize(int32_t iToWdt,int32_t iToHgt)769 	void Dialog::SetClientSize(int32_t iToWdt, int32_t iToHgt)
770 	{
771 		// calc new bounds
772 		iToWdt += GetMarginLeft()+GetMarginRight();
773 		iToHgt += GetMarginTop()+GetMarginBottom();
774 		rcBounds.x += (rcBounds.Wdt - iToWdt)/2;
775 		rcBounds.y += (rcBounds.Hgt - iToHgt)/2;
776 		rcBounds.Wdt = iToWdt; rcBounds.Hgt = iToHgt;
777 		// reflect changes
778 		UpdatePos();
779 	}
780 
781 
782 // --------------------------------------------------
783 // FullscreenDialog
784 
FullscreenDialog(const char * szTitle,const char * szSubtitle)785 	FullscreenDialog::FullscreenDialog(const char *szTitle, const char *szSubtitle)
786 			: Dialog(Screen::GetScreenS()->GetClientRect().Wdt, Screen::GetScreenS()->GetClientRect().Hgt, nullptr /* create own title */, false), pFullscreenTitle(nullptr)
787 	{
788 		// set margins
789 		int32_t iScreenX = Screen::GetScreenS()->GetClientRect().Wdt;
790 		int32_t iScreenY = Screen::GetScreenS()->GetClientRect().Hgt;
791 		if (iScreenX < 500) iDlgMarginX = 2; else iDlgMarginX = iScreenX/50;
792 		if (iScreenY < 320) iDlgMarginY = 2; else iDlgMarginY = iScreenY*2/75;
793 		// set size - calcs client rect as well
794 		SetBounds(C4Rect(0,0,iScreenX,iScreenY));
795 		// create title
796 		SetTitle(szTitle);
797 		// create subtitle (only with upperboard)
798 		if (szSubtitle && *szSubtitle && HasUpperBoard())
799 		{
800 			AddElement(pSubTitle = new Label(szSubtitle, rcClientRect.Wdt, C4UpperBoardHeight-::GraphicsResource.CaptionFont.GetLineHeight()/2-25-GetMarginTop(), ARight, C4GUI_CaptionFontClr, &::GraphicsResource.TextFont));
801 			pSubTitle->SetToolTip(szTitle);
802 		}
803 		else pSubTitle = nullptr;
804 	}
805 
SetTitle(const char * szTitle)806 	void FullscreenDialog::SetTitle(const char *szTitle)
807 	{
808 		if (pFullscreenTitle) { delete pFullscreenTitle; pFullscreenTitle=nullptr; }
809 		// change title text; creates or removes title bar if necessary
810 		if (szTitle && *szTitle)
811 		{
812 			// not using dlg label, which is a wooden label
813 			if (HasUpperBoard())
814 				pFullscreenTitle = new Label(szTitle, 0, C4UpperBoardHeight/2 - ::GraphicsResource.TitleFont.GetLineHeight()/2-GetMarginTop(), ALeft, C4GUI_CaptionFontClr, &::GraphicsResource.TitleFont);
815 			else
816 				// non-woodbar: Title is centered and in big font
817 				pFullscreenTitle = new Label(szTitle, GetClientRect().Wdt/2, C4UpperBoardHeight/2 - ::GraphicsResource.TitleFont.GetLineHeight()/2-GetMarginTop(), ACenter, C4GUI_FullscreenCaptionFontClr, &::GraphicsResource.TitleFont);
818 			AddElement(pFullscreenTitle);
819 			pFullscreenTitle->SetToolTip(szTitle);
820 		}
821 	}
822 
DrawElement(C4TargetFacet & cgo)823 	void FullscreenDialog::DrawElement(C4TargetFacet &cgo)
824 	{
825 		// draw upper board
826 		if (HasUpperBoard())
827 			pDraw->BlitSurfaceTile(::GraphicsResource.fctUpperBoard.Surface,cgo.Surface,0,std::min<int32_t>(iFade-::GraphicsResource.fctUpperBoard.Hgt, 0),cgo.Wdt,::GraphicsResource.fctUpperBoard.Hgt, 0, 0, nullptr);
828 	}
829 
UpdateOwnPos()830 	void FullscreenDialog::UpdateOwnPos()
831 	{
832 		// inherited to update client rect
833 		Dialog::UpdateOwnPos();
834 	}
835 
DrawBackground(C4TargetFacet & cgo,C4Facet & rFromFct)836 	void FullscreenDialog::DrawBackground(C4TargetFacet &cgo, C4Facet &rFromFct)
837 	{
838 		// draw across fullscreen bounds - zoom 1px border to prevent flashing borders by blit offsets
839 		rFromFct.DrawFullScreen(cgo);
840 	}
841 
842 // --------------------------------------------------
843 // MessageDialog
844 
MessageDialog(const char * szMessage,const char * szCaption,DWORD dwButtons,Icons icoIcon,DlgSize eSize,int32_t * piConfigDontShowAgainSetting,bool fDefaultNo)845 	MessageDialog::MessageDialog(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, DlgSize eSize, int32_t *piConfigDontShowAgainSetting, bool fDefaultNo)
846 			: Dialog(eSize, 100 /* will be resized */, szCaption, false), piConfigDontShowAgainSetting(piConfigDontShowAgainSetting), pKeyCopy(nullptr), sCopyText()
847 	{
848 		CStdFont &rUseFont = ::GraphicsResource.TextFont;
849 		// get positions
850 		ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
851 		// place icon
852 		C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
853 		Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
854 		// centered text for small dialogs and/or dialogs w/o much text (i.e.: no linebreaks)
855 		bool fTextCentered;
856 		if (eSize != dsRegular)
857 			fTextCentered = true;
858 		else
859 		{
860 			int32_t iMsgWdt=0, iMsgHgt=0;
861 			rUseFont.GetTextExtent(szMessage, iMsgWdt, iMsgHgt);
862 			fTextCentered = ((iMsgWdt <= caMain.GetInnerWidth() - C4GUI_IconWdt - C4GUI_DefDlgIndent*2) && iMsgHgt<=rUseFont.GetLineHeight());
863 		}
864 		// centered text dialog: waste some icon space on the right to balance dialog
865 		if (fTextCentered) caMain.GetFromRight(C4GUI_IconWdt);
866 		// place message label
867 		// use text with line breaks
868 		StdStrBuf sMsgBroken;
869 		int iMsgHeight = rUseFont.BreakMessage(szMessage, caMain.GetInnerWidth(), &sMsgBroken, true);
870 		Label *pLblMessage = new Label("", caMain.GetFromTop(iMsgHeight), fTextCentered ? ACenter : ALeft, C4GUI_MessageFontClr, &rUseFont, false);
871 		pLblMessage->SetText(sMsgBroken.getData(), false);
872 		AddElement(pLblMessage);
873 		// place do-not-show-again-checkbox
874 		if (piConfigDontShowAgainSetting)
875 		{
876 			int w=100,h=20;
877 			const char *szCheckText = LoadResStr("IDS_MSG_DONTSHOW");
878 			CheckBox::GetStandardCheckBoxSize(&w, &h, szCheckText, nullptr);
879 			CheckBox *pCheck = new C4GUI::CheckBox(caMain.GetFromTop(h, w), szCheckText, !!*piConfigDontShowAgainSetting);
880 			pCheck->SetOnChecked(new C4GUI::CallbackHandler<MessageDialog>(this, &MessageDialog::OnDontShowAgainCheck));
881 			AddElement(pCheck);
882 		}
883 		if (!fTextCentered) caMain.ExpandLeft(C4GUI_DefDlgIndent*2 + C4GUI_IconWdt);
884 		// place button(s)
885 		ComponentAligner caButtonArea(caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0,0);
886 		int32_t iButtonCount = 0;
887 		int32_t i=1; while (i) { if (dwButtons & i) ++iButtonCount; i=i<<1; }
888 		fHasOK = !!(dwButtons & btnOK) || !!(dwButtons & btnYes);
889 		Button *btnFocus = nullptr;
890 		if (iButtonCount)
891 		{
892 			C4Rect rcBtn = caButtonArea.GetCentered(iButtonCount*C4GUI_DefButton2Wdt+(iButtonCount-1)*C4GUI_DefButton2HSpace, C4GUI_ButtonHgt);
893 			rcBtn.Wdt = C4GUI_DefButton2Wdt;
894 			// OK
895 			if (dwButtons & btnOK)
896 			{
897 				Button *pBtnOK = new OKButton(rcBtn);
898 				AddElement(pBtnOK);
899 				rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
900 				if (!fDefaultNo) btnFocus = pBtnOK;
901 			}
902 			// Retry
903 			if (dwButtons & btnRetry)
904 			{
905 				Button *pBtnRetry = new RetryButton(rcBtn);
906 				AddElement(pBtnRetry);
907 				rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
908 				if (!btnFocus) btnFocus = pBtnRetry;
909 
910 			}
911 			// Cancel
912 			if (dwButtons & btnAbort)
913 			{
914 				Button *pBtnAbort = new CancelButton(rcBtn);
915 				AddElement(pBtnAbort);
916 				rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
917 				if (!btnFocus) btnFocus = pBtnAbort;
918 			}
919 			// Yes
920 			if (dwButtons & btnYes)
921 			{
922 				Button *pBtnYes = new YesButton(rcBtn);
923 				AddElement(pBtnYes);
924 				rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
925 				if (!btnFocus && !fDefaultNo) btnFocus = pBtnYes;
926 			}
927 			// No
928 			if (dwButtons & btnNo)
929 			{
930 				Button *pBtnNo = new NoButton(rcBtn);
931 				AddElement(pBtnNo);
932 				if (!btnFocus) btnFocus = pBtnNo;
933 			}
934 			// Reset
935 			if (dwButtons & btnReset)
936 			{
937 				Button *pBtnReset = new ResetButton(rcBtn);
938 				AddElement(pBtnReset);
939 				rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
940 				if (!btnFocus) btnFocus = pBtnReset;
941 
942 			}
943 		}
944 		if (btnFocus) SetFocus(btnFocus, false);
945 		// resize to actually needed size
946 		SetClientSize(GetClientRect().Wdt, GetClientRect().Hgt - caMain.GetHeight());
947 		// Control+C copies text to clipboard
948 		sCopyText = strprintf("[%s] %s", szCaption ? szCaption : "", szMessage ? szMessage : "");
949 		pKeyCopy = new C4KeyBinding(C4KeyCodeEx(K_C, KEYS_Control), "GUIEditCopy", KEYSCOPE_Gui,
950 		               new DlgKeyCB<MessageDialog>(*this, &MessageDialog::KeyCopy), C4CustomKey::PRIO_CtrlOverride);
951 	}
952 
~MessageDialog()953 MessageDialog::~MessageDialog()
954 {
955 	delete pKeyCopy;
956 }
957 
KeyCopy()958 bool MessageDialog::KeyCopy()
959 {
960 	// Copy text to clipboard
961 	::Application.Copy(sCopyText);
962 	return true;
963 }
964 
965 
966 // --------------------------------------------------
967 // ConfirmationDialog
968 
ConfirmationDialog(const char * szMessage,const char * szCaption,BaseCallbackHandler * pCB,DWORD dwButtons,bool fSmall,Icons icoIcon)969 	ConfirmationDialog::ConfirmationDialog(const char *szMessage, const char *szCaption, BaseCallbackHandler *pCB, DWORD dwButtons, bool fSmall, Icons icoIcon)
970 			: MessageDialog(szMessage, szCaption, dwButtons, icoIcon, fSmall ? MessageDialog::dsSmall : MessageDialog::dsRegular)
971 	{
972 		if ((this->pCB=pCB)) pCB->Ref();
973 		// always log confirmation messages
974 		LogSilentF("[Cnf] %s: %s", szCaption, szMessage);
975 		// confirmations always get deleted on close
976 		SetDelOnClose();
977 	}
978 
OnClosed(bool fOK)979 	void ConfirmationDialog::OnClosed(bool fOK)
980 	{
981 		// confirmed only on OK
982 		BaseCallbackHandler *pStackCB = fOK ? pCB : nullptr;
983 		if (pStackCB) pStackCB->Ref();
984 		// caution: this will usually delete the dlg (this)
985 		// so the CB-interface is backed up
986 		MessageDialog::OnClosed(fOK);
987 		if (pStackCB)
988 		{
989 			pStackCB->DoCall(nullptr);
990 			pStackCB->DeRef();
991 		}
992 	}
993 
994 
995 // --------------------------------------------------
996 // ProgressDialog
997 
ProgressDialog(const char * szMessage,const char * szCaption,int32_t iMaxProgress,int32_t iInitialProgress,Icons icoIcon)998 	ProgressDialog::ProgressDialog(const char *szMessage, const char *szCaption, int32_t iMaxProgress, int32_t iInitialProgress, Icons icoIcon)
999 			: Dialog(C4GUI_ProgressDlgWdt, std::max(::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_ProgressDlgWdt-3*C4GUI_DefDlgIndent-C4GUI_IconWdt, nullptr, 0, true), C4GUI_IconHgt) + C4GUI_ProgressDlgVRoom, szCaption, false)
1000 	{
1001 		// get positions
1002 		ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
1003 		ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
1004 		C4Rect rtProgressBar = caMain.GetFromBottom(C4GUI_ProgressDlgPBHgt);
1005 		// place icon
1006 		C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
1007 		Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
1008 		// place message label
1009 		// use text with line breaks
1010 		StdStrBuf str;
1011 		::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_ProgressDlgWdt-3*C4GUI_DefDlgIndent-C4GUI_IconWdt, &str, true);
1012 		Label *pLblMessage = new Label(str.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &::GraphicsResource.TextFont);
1013 		AddElement(pLblMessage);
1014 		// place progress bar
1015 		pBar = new ProgressBar(rtProgressBar, iMaxProgress);
1016 		pBar->SetProgress(iInitialProgress);
1017 		pBar->SetToolTip(LoadResStr("IDS_DLGTIP_PROGRESS"));
1018 		AddElement(pBar);
1019 		// place abort button
1020 		Button *pBtnAbort = new CancelButton(caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt));
1021 		AddElement(pBtnAbort);
1022 	}
1023 
1024 
1025 // --------------------------------------------------
1026 // Some dialog wrappers in Screen class
1027 
ShowMessage(const char * szMessage,const char * szCaption,Icons icoIcon,int32_t * piConfigDontShowAgainSetting)1028 	bool Screen::ShowMessage(const char *szMessage, const char *szCaption, Icons icoIcon, int32_t *piConfigDontShowAgainSetting)
1029 	{
1030 		// always log messages
1031 		LogSilentF("[Msg] %s: %s", szCaption, szMessage);
1032 		if (piConfigDontShowAgainSetting && *piConfigDontShowAgainSetting) return true;
1033 #ifdef USE_CONSOLE
1034 		// skip in console mode
1035 		return true;
1036 #endif
1037 		return ShowRemoveDlg(new MessageDialog(szMessage, szCaption, MessageDialog::btnOK, icoIcon, MessageDialog::dsRegular, piConfigDontShowAgainSetting));
1038 	}
1039 
ShowErrorMessage(const char * szMessage)1040 	bool Screen::ShowErrorMessage(const char *szMessage)
1041 	{
1042 		return ShowMessage(szMessage, LoadResStr("IDS_DLG_ERROR"), Ico_Error);
1043 	}
1044 
ShowMessageModal(const char * szMessage,const char * szCaption,DWORD dwButtons,Icons icoIcon,int32_t * piConfigDontShowAgainSetting)1045 	bool Screen::ShowMessageModal(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, int32_t *piConfigDontShowAgainSetting)
1046 	{
1047 		// always log messages
1048 		LogSilentF("[Modal] %s: %s", szCaption, szMessage);
1049 		// skip if user doesn't want to see it
1050 		if (piConfigDontShowAgainSetting && *piConfigDontShowAgainSetting) return true;
1051 		// create message dlg and show modal
1052 		return ShowModalDlg(new MessageDialog(szMessage, szCaption, dwButtons, icoIcon, MessageDialog::dsRegular, piConfigDontShowAgainSetting));
1053 	}
1054 
ShowProgressDlg(const char * szMessage,const char * szCaption,int32_t iMaxProgress,int32_t iInitialProgress,Icons icoIcon)1055 	ProgressDialog *Screen::ShowProgressDlg(const char *szMessage, const char *szCaption, int32_t iMaxProgress, int32_t iInitialProgress, Icons icoIcon)
1056 	{
1057 		// create progress dlg
1058 		ProgressDialog *pDlg = new ProgressDialog(szMessage, szCaption, iMaxProgress, iInitialProgress, icoIcon);
1059 		// show it
1060 		if (!pDlg->Show(this, true)) { delete pDlg; return nullptr; }
1061 		// return dlg pointer
1062 		return pDlg;
1063 	}
1064 
ShowModalDlg(Dialog * pDlg,bool fDestruct)1065 	bool Screen::ShowModalDlg(Dialog *pDlg, bool fDestruct)
1066 	{
1067 #ifdef USE_CONSOLE
1068 		// no modal dialogs in console build
1069 		// (there's most likely no way to close them!)
1070 		if (fDestruct) delete pDlg;
1071 		return true;
1072 #endif
1073 		// safety
1074 		if (!pDlg) return false;
1075 		// show it
1076 		if (!pDlg->Show(this, true)) { delete pDlg; return false; }
1077 		// wait until it is closed
1078 		bool fResult = pDlg->DoModal();
1079 		if (fDestruct) delete pDlg;
1080 		// return result
1081 		return fResult;
1082 	}
1083 
ShowRemoveDlg(Dialog * pDlg)1084 	bool Screen::ShowRemoveDlg(Dialog *pDlg)
1085 	{
1086 		// safety
1087 		if (!pDlg) return false;
1088 		// mark removal when done
1089 		pDlg->SetDelOnClose();
1090 		// show it
1091 		if (!pDlg->Show(this, true)) { delete pDlg; return false; }
1092 		// done, success
1093 		return true;
1094 	}
1095 
1096 
1097 // --------------------------------------------------
1098 // InputDialog
1099 
InputDialog(const char * szMessage,const char * szCaption,Icons icoIcon,BaseInputCallback * pCB,bool fChatLayout)1100 	InputDialog::InputDialog(const char *szMessage, const char *szCaption, Icons icoIcon, BaseInputCallback *pCB, bool fChatLayout)
1101 			: Dialog(fChatLayout ? C4GUI::GetScreenWdt()*4/5 : C4GUI_InputDlgWdt,
1102 			         fChatLayout ? C4GUI::Edit::GetDefaultEditHeight() + 2 :
1103 			         std::max(::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_InputDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, nullptr, 0, true),
1104 			             C4GUI_IconHgt) + C4GUI_InputDlgVRoom, szCaption, false),
1105 			pEdit(nullptr), pCB(pCB), fChatLayout(fChatLayout), pChatLbl(nullptr)
1106 	{
1107 		if (fChatLayout)
1108 		{
1109 			// chat input layout
1110 			C4GUI::ComponentAligner caChat(GetContainedClientRect(), 1,1);
1111 			// normal chatbox layout: Left chat label
1112 			int32_t w=40,h;
1113 			::GraphicsResource.TextFont.GetTextExtent(szMessage, w,h, true);
1114 			pChatLbl = new C4GUI::WoodenLabel(szMessage, caChat.GetFromLeft(w+4), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont);
1115 			caChat.ExpandLeft(2); // undo margin
1116 			rcEditBounds = caChat.GetAll();
1117 			SetCustomEdit(new Edit(rcEditBounds));
1118 			pChatLbl->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT"));
1119 			AddElement(pChatLbl);
1120 		}
1121 		else
1122 		{
1123 			// regular input dialog layout
1124 			// get positions
1125 			ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
1126 			ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
1127 			rcEditBounds = caMain.GetFromBottom(Edit::GetDefaultEditHeight());
1128 			// place icon
1129 			C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
1130 			Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
1131 			// place message label
1132 			// use text with line breaks
1133 			StdStrBuf str;
1134 			::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_InputDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, &str, true);
1135 			Label *pLblMessage = new Label(str.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &::GraphicsResource.TextFont);
1136 			AddElement(pLblMessage);
1137 			// place input edit
1138 			SetCustomEdit(new Edit(rcEditBounds));
1139 			// place buttons
1140 			C4Rect rcBtn = caButtonArea.GetCentered(2 * C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace, C4GUI_ButtonHgt);
1141 			rcBtn.Wdt = C4GUI_DefButton2Wdt;
1142 			// OK
1143 			Button *pBtnOK = new OKButton(rcBtn);
1144 			AddElement(pBtnOK);
1145 			rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace;
1146 			// Cancel
1147 			Button *pBtnAbort = new CancelButton(rcBtn);
1148 			AddElement(pBtnAbort);
1149 			rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace;
1150 		}
1151 		// input dlg always closed in the end
1152 		SetDelOnClose();
1153 	}
1154 
SetInputText(const char * szToText)1155 	void InputDialog::SetInputText(const char *szToText)
1156 	{
1157 		pEdit->SelectAll(); pEdit->DeleteSelection();
1158 		if (szToText)
1159 		{
1160 			pEdit->InsertText(szToText, false);
1161 			pEdit->SelectAll();
1162 		}
1163 	}
1164 
SetCustomEdit(Edit * pCustomEdit)1165 	void InputDialog::SetCustomEdit(Edit *pCustomEdit)
1166 	{
1167 		// del old
1168 		if (pEdit) delete pEdit;
1169 		// add new
1170 		pEdit = pCustomEdit;
1171 		pEdit->SetBounds(rcEditBounds);
1172 		if (fChatLayout)
1173 		{
1174 			pEdit->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT"));
1175 			pChatLbl->SetClickFocusControl(pEdit); // 2do: to all, to allies, etc.
1176 		}
1177 		AddElement(pEdit);
1178 		SetFocus(pEdit, false);
1179 	}
1180 
1181 
1182 // --------------------------------------------------
1183 // InfoDialog
1184 
InfoDialog(const char * szCaption,int32_t iLineCount)1185 	InfoDialog::InfoDialog(const char *szCaption, int32_t iLineCount)
1186 			: Dialog(C4GUI_InfoDlgWdt, ::GraphicsResource.TextFont.GetLineHeight()*iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0)
1187 	{
1188 		// timer
1189 		Application.Add(this);
1190 		CreateSubComponents();
1191 	}
1192 
InfoDialog(const char * szCaption,int iLineCount,const StdStrBuf & sText)1193 	InfoDialog::InfoDialog(const char *szCaption, int iLineCount, const StdStrBuf &sText)
1194 			: Dialog(C4GUI_InfoDlgWdt, ::GraphicsResource.TextFont.GetLineHeight()*iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0)
1195 	{
1196 		// ctor - init w/o timer
1197 		CreateSubComponents();
1198 		// fill in initial text
1199 		for (size_t i=0; i < sText.getLength(); ++i)
1200 		{
1201 			size_t i0 = i;
1202 			while (sText[i] != '|' && sText[i]) ++i;
1203 			StdStrBuf sLine = sText.copyPart(i0, i-i0);
1204 			pTextWin->AddTextLine(sLine.getData(), &::GraphicsResource.TextFont, C4GUI_MessageFontClr, false, true);
1205 		}
1206 		pTextWin->UpdateHeight();
1207 	}
~InfoDialog()1208 	InfoDialog::~InfoDialog()
1209 	{
1210 		Application.Remove(this);
1211 	}
1212 
CreateSubComponents()1213 	void InfoDialog::CreateSubComponents()
1214 	{
1215 		// get positions
1216 		ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
1217 		ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
1218 		// place info box
1219 		pTextWin = new TextWindow(caMain.GetAll(), 0, 0, 0, 100, 4096, "  ", true, nullptr, 0);
1220 		AddElement(pTextWin);
1221 		// place close button
1222 		Button *pBtnClose = new DlgCloseButton(caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt));
1223 		AddElement(pBtnClose); pBtnClose->SetToolTip(LoadResStr("IDS_MNU_CLOSE"));
1224 	}
1225 
AddLine(const char * szText)1226 	void InfoDialog::AddLine(const char *szText)
1227 	{
1228 		// add line to text window
1229 		if (!pTextWin) return;
1230 		pTextWin->AddTextLine(szText, &::GraphicsResource.TextFont, C4GUI_MessageFontClr, false, true);
1231 	}
1232 
AddLineFmt(const char * szFmtString,...)1233 	void InfoDialog::AddLineFmt(const char *szFmtString, ...)
1234 	{
1235 		// compose formatted line
1236 		va_list lst; va_start(lst, szFmtString);
1237 		StdStrBuf buf;
1238 		buf.FormatV(szFmtString, lst);
1239 		// add it
1240 		AddLine(buf.getData());
1241 	}
1242 
BeginUpdateText()1243 	void InfoDialog::BeginUpdateText()
1244 	{
1245 		// safety
1246 		if (!pTextWin) return;
1247 		// backup scrolling
1248 		iScroll = pTextWin->GetScrollPos();
1249 		// clear text window, so new text can be added
1250 		pTextWin->ClearText(false);
1251 	}
1252 
EndUpdateText()1253 	void InfoDialog::EndUpdateText()
1254 	{
1255 		// safety
1256 		if (!pTextWin) return;
1257 		// update text height
1258 		pTextWin->UpdateHeight();
1259 		// restore scrolling
1260 		pTextWin->SetScrollPos(iScroll);
1261 	}
1262 
OnSec1Timer()1263 	void InfoDialog::OnSec1Timer()
1264 	{
1265 		// always update
1266 		UpdateText();
1267 	}
1268 
1269 } // end of namespace
1270 
1271