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