openclonk/src/gui/C4GuiDialogs.cpp

1271 lines
40 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// generic user interface
// dialog base classes and some user dialogs
#include "C4Include.h"
#include "C4ForbidLibraryCompilation.h"
#include "gui/C4Gui.h"
#include "game/C4Application.h"
#include "game/C4GameScript.h"
#include "game/C4Viewport.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "object/C4DefList.h"
#include "object/C4Def.h"
#include "graphics/C4DrawGL.h"
#include "platform/StdRegistry.h"
namespace C4GUI
{
// --------------------------------------------------
// FrameDecoration
void FrameDecoration::Clear()
{
pSourceDef = nullptr;
idSourceDef = C4ID::None;
dwBackClr = C4GUI_StandardBGColor;
iBorderTop=iBorderLeft=iBorderRight=iBorderBottom=0;
fHasGfxOutsideClientArea = false;
fctTop.Default();
fctTopRight.Default();
fctRight.Default();
fctBottomRight.Default();
fctBottom.Default();
fctBottomLeft.Default();
fctLeft.Default();
fctTopLeft.Default();
}
bool FrameDecoration::SetFacetByAction(C4Def *pOfDef, C4TargetFacet &rfctTarget, const char *szFacetName)
{
// get action
StdStrBuf sActName;
sActName.Format("FrameDeco%s", szFacetName);
C4PropList *act = pOfDef->GetActionByName(sActName.getData());
if (!act) return false;
// set facet by it
int32_t x = act->GetPropertyInt(P_X);
int32_t y = act->GetPropertyInt(P_Y);
int32_t wdt = act->GetPropertyInt(P_Wdt);
int32_t hgt = act->GetPropertyInt(P_Hgt);
int32_t tx = act->GetPropertyInt(P_OffX);
int32_t ty = act->GetPropertyInt(P_OffY);
if (!wdt || !hgt) return false;
rfctTarget.Set(pOfDef->Graphics.GetBitmap(), x, y, wdt, hgt, tx, ty);
return true;
}
bool FrameDecoration::SetByDef(C4ID idSourceDef)
{
return SetByDef(C4Id2Def(idSourceDef));
}
bool FrameDecoration::SetByDef(C4Def *pSrcDef)
{
if (!pSrcDef) return false;
// script compiled?
if (!pSrcDef->Script.IsReady()) return false;
// reset old
Clear();
this->pSourceDef = pSrcDef;
this->idSourceDef = pSrcDef->id;
// query values
dwBackClr = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BackClr" ).getData()).getInt();
iBorderTop = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderTop" ).getData()).getInt();
iBorderLeft = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderLeft" ).getData()).getInt();
iBorderRight = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderRight" ).getData()).getInt();
iBorderBottom = pSrcDef->Call(FormatString(PSF_FrameDecoration, "BorderBottom").getData()).getInt();
// get gfx
SetFacetByAction(pSrcDef, fctTop , "Top" );
SetFacetByAction(pSrcDef, fctTopRight , "TopRight" );
SetFacetByAction(pSrcDef, fctRight , "Right" );
SetFacetByAction(pSrcDef, fctBottomRight, "BottomRight");
SetFacetByAction(pSrcDef, fctBottom , "Bottom" );
SetFacetByAction(pSrcDef, fctBottomLeft , "BottomLeft" );
SetFacetByAction(pSrcDef, fctLeft , "Left" );
SetFacetByAction(pSrcDef, fctTopLeft , "TopLeft" );
// check for gfx outside main area
fHasGfxOutsideClientArea = (fctTopLeft.TargetY < 0) || (fctTop.TargetY < 0) || (fctTopRight.TargetY < 0)
|| (fctTopLeft.TargetX < 0) || (fctLeft.TargetX < 0) || (fctBottomLeft.TargetX < 0)
|| (fctTopRight.TargetX + fctTopRight.Wdt > iBorderRight) || (fctRight.TargetX + fctRight.Wdt > iBorderRight) || (fctBottomRight.TargetX + fctBottomRight.Wdt > iBorderRight)
|| (fctBottomLeft.TargetY + fctBottomLeft.Hgt > iBorderBottom) || (fctBottom.TargetY + fctBottom.Hgt > iBorderBottom) || (fctBottomRight.TargetY + fctBottomRight.Hgt > iBorderBottom);
// k, done
return true;
}
bool FrameDecoration::UpdateGfx()
{
// simply re-set by def
return SetByDef(idSourceDef);
}
void FrameDecoration::Draw(C4TargetFacet &cgo, C4Rect &rcBounds)
{
// draw BG
int ox = cgo.TargetX+rcBounds.x, oy = cgo.TargetY+rcBounds.y;
pDraw->DrawBoxDw(cgo.Surface, ox,oy,ox+rcBounds.Wdt-1,oy+rcBounds.Hgt-1,dwBackClr);
// draw borders
int x,y,Q;
// top
if ((Q=fctTop.Wdt))
{
for (x = iBorderLeft; x < rcBounds.Wdt-iBorderRight; x += fctTop.Wdt)
{
int w = std::min<int>(fctTop.Wdt, rcBounds.Wdt-iBorderRight-x);
fctTop.Wdt = w;
fctTop.Draw(cgo.Surface, ox+x, oy+fctTop.TargetY);
}
fctTop.Wdt = Q;
}
// left
if ((Q=fctLeft.Hgt))
{
for (y = iBorderTop; y < rcBounds.Hgt-iBorderBottom; y += fctLeft.Hgt)
{
int h = std::min<int>(fctLeft.Hgt, rcBounds.Hgt-iBorderBottom-y);
fctLeft.Hgt = h;
fctLeft.Draw(cgo.Surface, ox+fctLeft.TargetX, oy+y);
}
fctLeft.Hgt = Q;
}
// right
if ((Q=fctRight.Hgt))
{
for (y = iBorderTop; y < rcBounds.Hgt-iBorderBottom; y += fctRight.Hgt)
{
int h = std::min<int>(fctRight.Hgt, rcBounds.Hgt-iBorderBottom-y);
fctRight.Hgt = h;
fctRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctRight.TargetX, oy+y);
}
fctRight.Hgt = Q;
}
// bottom
if ((Q=fctBottom.Wdt))
{
for (x = iBorderLeft; x < rcBounds.Wdt-iBorderRight; x += fctBottom.Wdt)
{
int w = std::min<int>(fctBottom.Wdt, rcBounds.Wdt-iBorderRight-x);
fctBottom.Wdt = w;
fctBottom.Draw(cgo.Surface, ox+x, oy+rcBounds.Hgt-iBorderBottom+fctBottom.TargetY);
}
fctBottom.Wdt = Q;
}
// draw edges
fctTopLeft.Draw(cgo.Surface, ox+fctTopLeft.TargetX,oy+fctTopLeft.TargetY);
fctTopRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctTopRight.TargetX,oy+fctTopRight.TargetY);
fctBottomLeft.Draw(cgo.Surface, ox+fctBottomLeft.TargetX,oy+rcBounds.Hgt-iBorderBottom+fctBottomLeft.TargetY);
fctBottomRight.Draw(cgo.Surface, ox+rcBounds.Wdt-iBorderRight+fctBottomRight.TargetX,oy+rcBounds.Hgt-iBorderBottom+fctBottomRight.TargetY);
}
// --------------------------------------------------
// DialogWindow
#ifdef USE_WIN32_WINDOWS
C4Window * DialogWindow::Init(C4AbstractApp * pApp, const char * Title, const C4Rect &rcBounds, const char *szID)
{
C4Window * result = C4Window::Init(C4Window::W_GuiWindow, pApp, Title, &rcBounds);
if (result)
{
// update pos
if (szID && *szID)
RestoreWindowPosition(hWindow, FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false);
// and show
::ShowWindow(hWindow, SW_SHOW);
}
return result;
}
#else
C4Window * DialogWindow::Init(C4AbstractApp * pApp, const char * Title, const C4Rect &rcBounds, const char *szID)
{
C4Window * result = C4Window::Init(C4Window::W_GuiWindow, pApp, Title, &rcBounds);
if (result)
{
// update pos
if (szID && *szID)
RestorePosition(FormatString("ConsoleGUI_%s", szID).getData(), Config.GetSubkeyPath("Console"), false);
else
SetSize(rcBounds.Wdt, rcBounds.Hgt);
}
return result;
}
#endif // _WIN32
void DialogWindow::PerformUpdate()
{
if (!pDialog)
return; // safety
C4Rect r;
GetSize(&r);
if (pSurface)
{
pSurface->Wdt = r.Wdt;
pSurface->Hgt = r.Hgt;
#ifndef USE_CONSOLE
pGL->PrepareRendering(pSurface);
glClear(GL_COLOR_BUFFER_BIT);
#endif
}
C4TargetFacet cgo;
cgo.Set(nullptr, 0, 0, r.Wdt, r.Hgt, 0, 0);
pDialog->Draw(cgo);
}
void DialogWindow::Close()
{
// FIXME: Close the dialog of this window
}
bool Dialog::CreateConsoleWindow()
{
#ifdef WITH_QT_EDITOR
// TODO: Implement these as Qt editor windows.
// This currently creates an empty window in Windows and a segfault in Linux.
return false;
#endif
// already created?
if (pWindow) return true;
// create it!
pWindow = new DialogWindow();
if (!pWindow->Init(&Application, TitleString.getData(), rcBounds, GetID()))
{
delete pWindow;
pWindow = nullptr;
return false;
}
// create rendering context
pWindow->pSurface = new C4Surface(&Application, pWindow);
pWindow->pDialog = this;
return true;
}
void Dialog::DestroyConsoleWindow()
{
if (pWindow)
{
delete pWindow->pSurface;
pWindow->Clear();
delete pWindow;
pWindow = nullptr;
}
}
Dialog::Dialog(int32_t iWdt, int32_t iHgt, const char *szTitle, bool fViewportDlg):
Window(), pTitle(nullptr), pCloseBtn(nullptr), fDelOnClose(false), fViewportDlg(fViewportDlg), pWindow(nullptr), pFrameDeco(nullptr)
{
// zero fields
pActiveCtrl = nullptr;
fShow = fOK = false;
iFade = 100; eFade = eFadeNone;
// add title
rcBounds.Wdt = iWdt;
SetTitle(szTitle);
// set size - calcs client rect as well
SetBounds(C4Rect(0,0,iWdt,iHgt));
// create key callbacks
C4CustomKey::CodeList Keys;
Keys.emplace_back(K_TAB);
if (Config.Controls.GamepadGuiControl)
{
ControllerKeys::Right(Keys);
}
pKeyAdvanceControl = new C4KeyBinding(Keys, "GUIAdvanceFocus", KEYSCOPE_Gui,
new DlgKeyCBEx<Dialog, bool>(*this, false, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
Keys.clear();
Keys.emplace_back(K_TAB, KEYS_Shift);
if (Config.Controls.GamepadGuiControl)
{
ControllerKeys::Left(Keys);
}
pKeyAdvanceControlB = new C4KeyBinding(Keys, "GUIAdvanceFocusBack", KEYSCOPE_Gui,
new DlgKeyCBEx<Dialog, bool>(*this, true, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
Keys.clear();
Keys.emplace_back(KEY_Any, KEYS_Alt);
Keys.emplace_back(KEY_Any, C4KeyShiftState(KEYS_Alt | KEYS_Shift));
pKeyHotkey = new C4KeyBinding(Keys, "GUIHotkey", KEYSCOPE_Gui,
new DlgKeyCBPassKey<Dialog>(*this, &Dialog::KeyHotkey), C4CustomKey::PRIO_Ctrl);
Keys.clear();
Keys.emplace_back(K_RETURN);
if (Config.Controls.GamepadGuiControl)
{
ControllerKeys::Ok(Keys);
}
pKeyEnter = new C4KeyBinding(Keys, "GUIDialogOkay", KEYSCOPE_Gui,
new DlgKeyCB<Dialog>(*this, &Dialog::KeyEnter), C4CustomKey::PRIO_Dlg);
Keys.clear();
Keys.emplace_back(K_ESCAPE);
if (Config.Controls.GamepadGuiControl)
{
ControllerKeys::Cancel(Keys);
}
pKeyEscape = new C4KeyBinding(Keys, "GUIDialogAbort", KEYSCOPE_Gui,
new DlgKeyCB<Dialog>(*this, &Dialog::KeyEscape), C4CustomKey::PRIO_Dlg);
Keys.clear();
Keys.emplace_back(KEY_Any);
Keys.emplace_back(KEY_Any, KEYS_Shift);
pKeyFocusDefControl = new C4KeyBinding(Keys, "GUIFocusDefault", KEYSCOPE_Gui,
new DlgKeyCB<Dialog>(*this, &Dialog::KeyFocusDefault), C4CustomKey::PRIO_Dlg);
}
int32_t Dialog::GetDefaultTitleHeight()
{
// default title font
return std::min<int32_t>(::GraphicsResource.TextFont.GetLineHeight(), C4GUI_MinWoodBarHgt);
}
void Dialog::SetTitle(const char *szTitle, bool fShowCloseButton)
{
// always keep local copy of title
TitleString.Copy(szTitle);
// console mode dialogs: Use window bar
if (Application.isEditor && !IsViewportDialog())
{
if (pWindow) pWindow->SetTitle(szTitle ? szTitle : "");
return;
}
// set new
if (szTitle && *szTitle)
{
int32_t iTextHgt = WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
if (pTitle)
{
pTitle->GetBounds() = C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt);
// noupdate if title is same - this is necessary to prevent scrolling reset when refilling internal menus
if (SEqual(pTitle->GetText(), szTitle)) return;
pTitle->SetText(szTitle);
}
else
AddElement(pTitle = new WoodenLabel(szTitle, C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont, ALeft, false));
pTitle->SetToolTip(szTitle);
pTitle->SetDragTarget(this);
pTitle->SetAutoScrollTime(C4GUI_TitleAutoScrollTime);
if (fShowCloseButton)
{
pTitle->SetRightIndent(20); // for close button
if (!pCloseBtn)
{
AddElement(pCloseBtn = new CallbackButton<Dialog, IconButton>(Ico_Close, pTitle->GetToprightCornerRect(16,16,4,4,0), '\0', &Dialog::OnUserClose));
pCloseBtn->SetToolTip(LoadResStr("IDS_MNU_CLOSE"));
}
else
pCloseBtn->GetBounds() = pTitle->GetToprightCornerRect(16,16,4,4,0);
}
}
else
{
if (pTitle) { delete pTitle; pTitle=nullptr; }
if (pCloseBtn) { delete pCloseBtn; pCloseBtn = nullptr; }
}
}
Dialog::~Dialog()
{
// kill key bindings
delete pKeyAdvanceControl;
delete pKeyAdvanceControlB;
delete pKeyHotkey;
delete pKeyEscape;
delete pKeyEnter;
delete pKeyFocusDefControl;
// clear window
DestroyConsoleWindow();
// avoid endless delete/close-recursion
fDelOnClose = false;
// free deco
if (pFrameDeco) pFrameDeco->Deref();
}
void Dialog::UpdateSize()
{
// update title bar position
if (pTitle)
{
int32_t iTextHgt = WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
pTitle->SetBounds(C4Rect(-GetMarginLeft(), -iTextHgt, rcBounds.Wdt, iTextHgt));
if (pCloseBtn) pCloseBtn->SetBounds(pTitle->GetToprightCornerRect(16,16,4,4,0));
}
// inherited
Window::UpdateSize();
// update assigned window
if (pWindow)
{
pWindow->SetSize(rcBounds.Wdt,rcBounds.Hgt);
}
}
void Dialog::UpdatePos()
{
// Dialogs with their own windows can only be at 0/0
if (pWindow)
{
rcBounds.x = 0;
rcBounds.y = 0;
}
Window::UpdatePos();
}
void Dialog::RemoveElement(Element *pChild)
{
// inherited
Window::RemoveElement(pChild);
// clear ptr
if (pChild == pActiveCtrl) pActiveCtrl = nullptr;
}
void Dialog::Draw(C4TargetFacet &cgo0)
{
C4TargetFacet cgo; cgo.Set(cgo0);
// Dialogs with a window just ignore the cgo.
if (pWindow)
{
cgo.Surface = pWindow->pSurface;
cgo.X = 0; cgo.Y = 0; cgo.Wdt = rcBounds.Wdt; cgo.Hgt = rcBounds.Hgt;
}
Screen *pScreen;
// evaluate fading
switch (eFade)
{
case eFadeNone: break; // no fading
case eFadeIn:
// fade in
if ((iFade+=10) >= 100)
{
if ((pScreen = GetScreen()))
{
if (pScreen->GetTopDialog() == this)
pScreen->ActivateDialog(this);
}
eFade = eFadeNone;
}
break;
case eFadeOut:
// fade out
if ((iFade-=10) <= 0)
{
fVisible = fShow = false;
if ((pScreen = GetScreen()))
pScreen->RecheckActiveDialog();
eFade = eFadeNone;
}
}
// set fade
if (iFade < 100)
{
if (iFade <= 0) return;
pDraw->ActivateBlitModulation((iFade*255/100)<<24 | 0xffffff);
}
// separate window: Clear background
if (pWindow)
pDraw->DrawBoxDw(cgo.Surface, rcBounds.x, rcBounds.y, rcBounds.Wdt-1, rcBounds.Hgt-1, (0xff << 24) | (C4GUI_StandardBGColor & 0xffffff) );
// draw window + contents (evaluates IsVisible)
Window::Draw(cgo);
// reset blit modulation
if (iFade<100) pDraw->DeactivateBlitModulation();
// blit output to own window
if (pWindow)
{
// Draw context menu on editor window
ContextMenu *menu;
if ((menu = GetScreen()->pContext))
{
if (menu->GetTargetDialog() == this)
{
menu->Draw(cgo);
}
}
// Editor window: Blit to output
C4Rect rtSrc,rtDst;
rtSrc.x=rcBounds.x; rtSrc.y=rcBounds.y; rtSrc.Wdt=rcBounds.Wdt; rtSrc.Hgt=rcBounds.Hgt;
rtDst.x=0; rtDst.y=0; rtDst.Wdt=rcBounds.Wdt; rtDst.Hgt=rcBounds.Hgt;
pWindow->pSurface->PageFlip(&rtSrc, &rtDst);
}
}
void Dialog::DrawElement(C4TargetFacet &cgo)
{
// custom border?
if (pFrameDeco)
pFrameDeco->Draw(cgo, rcBounds);
else
{
// standard border/bg then
// draw background
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);
// draw frame
Draw3DFrame(cgo);
}
}
bool Dialog::CharIn(const char * c)
{
// reroute to active control
if (pActiveCtrl && pActiveCtrl->CharIn(c)) return true;
// unprocessed: Focus default control
// Except for space, which may have been processed as a key already
// (changing focus here would render buttons unusable, because they switch on KeyUp)
Control *pDefCtrl = GetDefaultControl();
if (pDefCtrl && pDefCtrl != pActiveCtrl && (!c || *c != 0x20))
{
SetFocus(pDefCtrl, false);
if (pActiveCtrl && pActiveCtrl->CharIn(c))
return true;
}
return false;
}
bool Dialog::KeyHotkey(const C4KeyCodeEx &key)
{
StdStrBuf sKey = C4KeyCodeEx::KeyCode2String(key.Key, true, true);
// do hotkey procs for standard alphanumerics only
if (sKey.getLength() != 1) return false;
WORD wKey = WORD(*sKey.getData());
if (Inside<WORD>(TOUPPERIFX11(wKey), 'A', 'Z')) if (OnHotkey(char(TOUPPERIFX11(wKey)))) return true;
if (Inside<WORD>(TOUPPERIFX11(wKey), '0', '9')) if (OnHotkey(char(TOUPPERIFX11(wKey)))) return true;
return false;
}
bool Dialog::KeyFocusDefault()
{
// unprocessed key: Focus default control
Control *pDefCtrl = GetDefaultControl();
if (pDefCtrl && pDefCtrl != pActiveCtrl)
SetFocus(pDefCtrl, false);
// never mark this as processed, so a later char message to the control may be sent (for deselected chat)
return false;
}
void Dialog::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
{
// inherited will do...
Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
}
void Dialog::SetFocus(Control *pCtrl, bool fByMouse)
{
// no change?
if (pCtrl == pActiveCtrl) return;
// leave old focus
if (pActiveCtrl)
{
Control *pC = pActiveCtrl;
pActiveCtrl = nullptr;
pC->OnLooseFocus();
// if leaving the old focus set a new one, abort here because it looks like the control didn't want to lose focus
if (pActiveCtrl) return;
}
// set new
if ((pActiveCtrl = pCtrl)) pCtrl->OnGetFocus(fByMouse);
}
void Dialog::AdvanceFocus(bool fBackwards)
{
// get element to start from
Element *pCurrElement = pActiveCtrl;
// find new control
for (;;)
{
// get next element
pCurrElement = GetNextNestedElement(pCurrElement, fBackwards);
// end reached: start from beginning
if (!pCurrElement && pActiveCtrl) if (!(pCurrElement = GetNextNestedElement(nullptr, fBackwards))) return;
// cycled?
if (pCurrElement == pActiveCtrl)
{
// but current is no longer a focus element? Then defocus it and return
if (pCurrElement && !pCurrElement->IsFocusElement())
SetFocus(nullptr, false);
return;
}
// for list elements, check whether the child can be selected
if (pCurrElement->GetParent() && !pCurrElement->GetParent()->IsSelectedChild(pCurrElement)) continue;
// check if this is a new control
Control *pFocusCtrl = pCurrElement->IsFocusElement();
if (pFocusCtrl && pFocusCtrl != pActiveCtrl && pFocusCtrl->IsVisible())
{
// set focus here...
SetFocus(pFocusCtrl, false);
// ...done!
return;
}
}
// never reached
}
bool Dialog::Show(Screen *pOnScreen, bool fCB)
{
// already shown?
if (fShow) return false;
// default screen
if (!pOnScreen) if (!(pOnScreen = Screen::GetScreenS())) return false;
// show there
pOnScreen->ShowDialog(this, false);
fVisible = true;
// developer mode: Create window
if (Application.isEditor && !IsViewportDialog())
if (!CreateConsoleWindow()) return false;
// CB
if (fCB) OnShown();
return true;
}
void Dialog::Close(bool fOK)
{
// already closed?
if (!fShow) return;
// set OK flag
this->fOK = fOK;
// get screen
Screen *pScreen = GetScreen();
if (pScreen) pScreen->CloseDialog(this, false); else fShow = false;
// developer mode: Remove window
if (pWindow) DestroyConsoleWindow();
// do callback - last call, because it might do perilous things
OnClosed(fOK);
}
void Dialog::OnClosed(bool fOK)
{
// developer mode: Remove window
if (pWindow) DestroyConsoleWindow();
// delete when closing?
if (fDelOnClose)
{
fDelOnClose=false;
delete this;
}
}
bool Dialog::DoModal()
{
// Cancel all dialogues if game is left (including e.g. league dialogues)
if (::Application.IsQuittingGame()) return false;
// main message loop
while (fShow)
{
// dialog idle proc
OnIdle();
// Modal dialogue during running game is tricky. Do not execute game!
bool fGameWasRunning = ::Game.IsRunning;
::Game.IsRunning = false;
// handle messages - this may block until the next timer
if (!Application.ScheduleProcs())
return false; // game GUI and lobby will deleted in Game::Clear()
// reset game run state
if (fGameWasRunning) ::Game.IsRunning = true;
}
// return whether dlg was OK
return fOK;
}
bool Dialog::Execute()
{
// process messages
if (!Application.ScheduleProcs(0))
return false;
// check status
if (!fShow) return false;
return true;
}
bool Dialog::Execute2()
{
// execute
if (Execute()) return true;
// delete self if closed
delete this;
return false;
}
bool Dialog::IsActive(bool fForKeyboard)
{
// must be fully visible
if (!IsShown() || IsFading()) return false;
// screen-less dialogs are always inactive (not yet added)
Screen *pScreen = GetScreen();
if (!pScreen) return false;
// no keyboard focus if screen is in context mode
if (fForKeyboard && pScreen->HasContext()) return false;
// always okay in shared mode: all dlgs accessible by mouse
if (!pScreen->IsExclusive() && !fForKeyboard) return true;
// exclusive mode or keyboard input: Only one dlg active
return pScreen->pActiveDlg == this;
}
bool Dialog::FadeIn(Screen *pOnScreen)
{
// default screen
if (!pOnScreen) pOnScreen = Screen::GetScreenS();
// fade in there
pOnScreen->ShowDialog(this, true);
iFade = 0;
eFade = eFadeIn;
fVisible = true;
OnShown();
// done, success
return true;
}
void Dialog::FadeOut(bool fCloseWithOK)
{
// only if shown, or being faded in
if (!IsShown() && (!fVisible || eFade!=eFadeIn)) return;
// set OK flag
this->fOK = fCloseWithOK;
// fade out
Screen *pOnScreen = GetScreen();
if (!pOnScreen) return;
pOnScreen->CloseDialog(this, true);
eFade = eFadeOut;
// do callback - last call, because it might do perilous things
OnClosed(fCloseWithOK);
}
void Dialog::ApplyElementOffset(int32_t &riX, int32_t &riY)
{
// inherited
Window::ApplyElementOffset(riX, riY);
// apply viewport offset, if a viewport is assigned
C4Viewport *pVP = GetViewport();
if (pVP)
{
C4Rect rcVP(pVP->GetOutputRect());
riX -= rcVP.x; riY -= rcVP.y;
}
}
void Dialog::ApplyInvElementOffset(int32_t &riX, int32_t &riY)
{
// inherited
Window::ApplyInvElementOffset(riX, riY);
// apply viewport offset, if a viewport is assigned
C4Viewport *pVP = GetViewport();
if (pVP)
{
C4Rect rcVP(pVP->GetOutputRect());
riX += rcVP.x; riY += rcVP.y;
}
}
void Dialog::SetClientSize(int32_t iToWdt, int32_t iToHgt)
{
// calc new bounds
iToWdt += GetMarginLeft()+GetMarginRight();
iToHgt += GetMarginTop()+GetMarginBottom();
rcBounds.x += (rcBounds.Wdt - iToWdt)/2;
rcBounds.y += (rcBounds.Hgt - iToHgt)/2;
rcBounds.Wdt = iToWdt; rcBounds.Hgt = iToHgt;
// reflect changes
UpdatePos();
}
// --------------------------------------------------
// FullscreenDialog
FullscreenDialog::FullscreenDialog(const char *szTitle, const char *szSubtitle)
: Dialog(Screen::GetScreenS()->GetClientRect().Wdt, Screen::GetScreenS()->GetClientRect().Hgt, nullptr /* create own title */, false), pFullscreenTitle(nullptr)
{
// set margins
int32_t iScreenX = Screen::GetScreenS()->GetClientRect().Wdt;
int32_t iScreenY = Screen::GetScreenS()->GetClientRect().Hgt;
if (iScreenX < 500) iDlgMarginX = 2; else iDlgMarginX = iScreenX/50;
if (iScreenY < 320) iDlgMarginY = 2; else iDlgMarginY = iScreenY*2/75;
// set size - calcs client rect as well
SetBounds(C4Rect(0,0,iScreenX,iScreenY));
// create title
SetTitle(szTitle);
// create subtitle (only with upperboard)
if (szSubtitle && *szSubtitle && HasUpperBoard())
{
AddElement(pSubTitle = new Label(szSubtitle, rcClientRect.Wdt, C4UpperBoardHeight-::GraphicsResource.CaptionFont.GetLineHeight()/2-25-GetMarginTop(), ARight, C4GUI_CaptionFontClr, &::GraphicsResource.TextFont));
pSubTitle->SetToolTip(szTitle);
}
else pSubTitle = nullptr;
}
void FullscreenDialog::SetTitle(const char *szTitle)
{
if (pFullscreenTitle) { delete pFullscreenTitle; pFullscreenTitle=nullptr; }
// change title text; creates or removes title bar if necessary
if (szTitle && *szTitle)
{
// not using dlg label, which is a wooden label
if (HasUpperBoard())
pFullscreenTitle = new Label(szTitle, 0, C4UpperBoardHeight/2 - ::GraphicsResource.TitleFont.GetLineHeight()/2-GetMarginTop(), ALeft, C4GUI_CaptionFontClr, &::GraphicsResource.TitleFont);
else
// non-woodbar: Title is centered and in big font
pFullscreenTitle = new Label(szTitle, GetClientRect().Wdt/2, C4UpperBoardHeight/2 - ::GraphicsResource.TitleFont.GetLineHeight()/2-GetMarginTop(), ACenter, C4GUI_FullscreenCaptionFontClr, &::GraphicsResource.TitleFont);
AddElement(pFullscreenTitle);
pFullscreenTitle->SetToolTip(szTitle);
}
}
void FullscreenDialog::DrawElement(C4TargetFacet &cgo)
{
// draw upper board
if (HasUpperBoard())
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);
}
void FullscreenDialog::UpdateOwnPos()
{
// inherited to update client rect
Dialog::UpdateOwnPos();
}
void FullscreenDialog::DrawBackground(C4TargetFacet &cgo, C4Facet &rFromFct)
{
// draw across fullscreen bounds - zoom 1px border to prevent flashing borders by blit offsets
rFromFct.DrawFullScreen(cgo);
}
// --------------------------------------------------
// MessageDialog
MessageDialog::MessageDialog(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, DlgSize eSize, int32_t *piConfigDontShowAgainSetting, bool fDefaultNo)
: Dialog(eSize, 100 /* will be resized */, szCaption, false), piConfigDontShowAgainSetting(piConfigDontShowAgainSetting), pKeyCopy(nullptr), sCopyText()
{
CStdFont &rUseFont = ::GraphicsResource.TextFont;
// get positions
ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
// place icon
C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
// centered text for small dialogs and/or dialogs w/o much text (i.e.: no linebreaks)
bool fTextCentered;
if (eSize != dsRegular)
fTextCentered = true;
else
{
int32_t iMsgWdt=0, iMsgHgt=0;
rUseFont.GetTextExtent(szMessage, iMsgWdt, iMsgHgt);
fTextCentered = ((iMsgWdt <= caMain.GetInnerWidth() - C4GUI_IconWdt - C4GUI_DefDlgIndent*2) && iMsgHgt<=rUseFont.GetLineHeight());
}
// centered text dialog: waste some icon space on the right to balance dialog
if (fTextCentered) caMain.GetFromRight(C4GUI_IconWdt);
// place message label
// use text with line breaks
StdStrBuf sMsgBroken;
int iMsgHeight = rUseFont.BreakMessage(szMessage, caMain.GetInnerWidth(), &sMsgBroken, true);
Label *pLblMessage = new Label("", caMain.GetFromTop(iMsgHeight), fTextCentered ? ACenter : ALeft, C4GUI_MessageFontClr, &rUseFont, false);
pLblMessage->SetText(sMsgBroken.getData(), false);
AddElement(pLblMessage);
// place do-not-show-again-checkbox
if (piConfigDontShowAgainSetting)
{
int w=100,h=20;
const char *szCheckText = LoadResStr("IDS_MSG_DONTSHOW");
CheckBox::GetStandardCheckBoxSize(&w, &h, szCheckText, nullptr);
CheckBox *pCheck = new C4GUI::CheckBox(caMain.GetFromTop(h, w), szCheckText, !!*piConfigDontShowAgainSetting);
pCheck->SetOnChecked(new C4GUI::CallbackHandler<MessageDialog>(this, &MessageDialog::OnDontShowAgainCheck));
AddElement(pCheck);
}
if (!fTextCentered) caMain.ExpandLeft(C4GUI_DefDlgIndent*2 + C4GUI_IconWdt);
// place button(s)
ComponentAligner caButtonArea(caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0,0);
int32_t iButtonCount = 0;
int32_t i=1; while (i) { if (dwButtons & i) ++iButtonCount; i=i<<1; }
fHasOK = !!(dwButtons & btnOK) || !!(dwButtons & btnYes);
Button *btnFocus = nullptr;
if (iButtonCount)
{
C4Rect rcBtn = caButtonArea.GetCentered(iButtonCount*C4GUI_DefButton2Wdt+(iButtonCount-1)*C4GUI_DefButton2HSpace, C4GUI_ButtonHgt);
rcBtn.Wdt = C4GUI_DefButton2Wdt;
// OK
if (dwButtons & btnOK)
{
Button *pBtnOK = new OKButton(rcBtn);
AddElement(pBtnOK);
rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
if (!fDefaultNo) btnFocus = pBtnOK;
}
// Retry
if (dwButtons & btnRetry)
{
Button *pBtnRetry = new RetryButton(rcBtn);
AddElement(pBtnRetry);
rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
if (!btnFocus) btnFocus = pBtnRetry;
}
// Cancel
if (dwButtons & btnAbort)
{
Button *pBtnAbort = new CancelButton(rcBtn);
AddElement(pBtnAbort);
rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
if (!btnFocus) btnFocus = pBtnAbort;
}
// Yes
if (dwButtons & btnYes)
{
Button *pBtnYes = new YesButton(rcBtn);
AddElement(pBtnYes);
rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
if (!btnFocus && !fDefaultNo) btnFocus = pBtnYes;
}
// No
if (dwButtons & btnNo)
{
Button *pBtnNo = new NoButton(rcBtn);
AddElement(pBtnNo);
if (!btnFocus) btnFocus = pBtnNo;
}
// Reset
if (dwButtons & btnReset)
{
Button *pBtnReset = new ResetButton(rcBtn);
AddElement(pBtnReset);
rcBtn.x += C4GUI_DefButton2Wdt+C4GUI_DefButton2HSpace;
if (!btnFocus) btnFocus = pBtnReset;
}
}
if (btnFocus) SetFocus(btnFocus, false);
// resize to actually needed size
SetClientSize(GetClientRect().Wdt, GetClientRect().Hgt - caMain.GetHeight());
// Control+C copies text to clipboard
sCopyText = strprintf("[%s] %s", szCaption ? szCaption : "", szMessage ? szMessage : "");
pKeyCopy = new C4KeyBinding(C4KeyCodeEx(K_C, KEYS_Control), "GUIEditCopy", KEYSCOPE_Gui,
new DlgKeyCB<MessageDialog>(*this, &MessageDialog::KeyCopy), C4CustomKey::PRIO_CtrlOverride);
}
MessageDialog::~MessageDialog()
{
delete pKeyCopy;
}
bool MessageDialog::KeyCopy()
{
// Copy text to clipboard
::Application.Copy(sCopyText);
return true;
}
// --------------------------------------------------
// ConfirmationDialog
ConfirmationDialog::ConfirmationDialog(const char *szMessage, const char *szCaption, BaseCallbackHandler *pCB, DWORD dwButtons, bool fSmall, Icons icoIcon)
: MessageDialog(szMessage, szCaption, dwButtons, icoIcon, fSmall ? MessageDialog::dsSmall : MessageDialog::dsRegular)
{
if ((this->pCB=pCB)) pCB->Ref();
// always log confirmation messages
LogSilentF("[Cnf] %s: %s", szCaption, szMessage);
// confirmations always get deleted on close
SetDelOnClose();
}
void ConfirmationDialog::OnClosed(bool fOK)
{
// confirmed only on OK
BaseCallbackHandler *pStackCB = fOK ? pCB : nullptr;
if (pStackCB) pStackCB->Ref();
// caution: this will usually delete the dlg (this)
// so the CB-interface is backed up
MessageDialog::OnClosed(fOK);
if (pStackCB)
{
pStackCB->DoCall(nullptr);
pStackCB->DeRef();
}
}
// --------------------------------------------------
// ProgressDialog
ProgressDialog::ProgressDialog(const char *szMessage, const char *szCaption, int32_t iMaxProgress, int32_t iInitialProgress, Icons icoIcon)
: 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)
{
// get positions
ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
C4Rect rtProgressBar = caMain.GetFromBottom(C4GUI_ProgressDlgPBHgt);
// place icon
C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
// place message label
// use text with line breaks
StdStrBuf str;
::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_ProgressDlgWdt-3*C4GUI_DefDlgIndent-C4GUI_IconWdt, &str, true);
Label *pLblMessage = new Label(str.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &::GraphicsResource.TextFont);
AddElement(pLblMessage);
// place progress bar
pBar = new ProgressBar(rtProgressBar, iMaxProgress);
pBar->SetProgress(iInitialProgress);
pBar->SetToolTip(LoadResStr("IDS_DLGTIP_PROGRESS"));
AddElement(pBar);
// place abort button
Button *pBtnAbort = new CancelButton(caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt));
AddElement(pBtnAbort);
}
// --------------------------------------------------
// Some dialog wrappers in Screen class
bool Screen::ShowMessage(const char *szMessage, const char *szCaption, Icons icoIcon, int32_t *piConfigDontShowAgainSetting)
{
// always log messages
LogSilentF("[Msg] %s: %s", szCaption, szMessage);
if (piConfigDontShowAgainSetting && *piConfigDontShowAgainSetting) return true;
#ifdef USE_CONSOLE
// skip in console mode
return true;
#endif
return ShowRemoveDlg(new MessageDialog(szMessage, szCaption, MessageDialog::btnOK, icoIcon, MessageDialog::dsRegular, piConfigDontShowAgainSetting));
}
bool Screen::ShowErrorMessage(const char *szMessage)
{
return ShowMessage(szMessage, LoadResStr("IDS_DLG_ERROR"), Ico_Error);
}
bool Screen::ShowMessageModal(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, int32_t *piConfigDontShowAgainSetting)
{
// always log messages
LogSilentF("[Modal] %s: %s", szCaption, szMessage);
// skip if user doesn't want to see it
if (piConfigDontShowAgainSetting && *piConfigDontShowAgainSetting) return true;
// create message dlg and show modal
return ShowModalDlg(new MessageDialog(szMessage, szCaption, dwButtons, icoIcon, MessageDialog::dsRegular, piConfigDontShowAgainSetting));
}
ProgressDialog *Screen::ShowProgressDlg(const char *szMessage, const char *szCaption, int32_t iMaxProgress, int32_t iInitialProgress, Icons icoIcon)
{
// create progress dlg
ProgressDialog *pDlg = new ProgressDialog(szMessage, szCaption, iMaxProgress, iInitialProgress, icoIcon);
// show it
if (!pDlg->Show(this, true)) { delete pDlg; return nullptr; }
// return dlg pointer
return pDlg;
}
bool Screen::ShowModalDlg(Dialog *pDlg, bool fDestruct)
{
#ifdef USE_CONSOLE
// no modal dialogs in console build
// (there's most likely no way to close them!)
if (fDestruct) delete pDlg;
return true;
#endif
// safety
if (!pDlg) return false;
// show it
if (!pDlg->Show(this, true)) { delete pDlg; return false; }
// wait until it is closed
bool fResult = pDlg->DoModal();
if (fDestruct) delete pDlg;
// return result
return fResult;
}
bool Screen::ShowRemoveDlg(Dialog *pDlg)
{
// safety
if (!pDlg) return false;
// mark removal when done
pDlg->SetDelOnClose();
// show it
if (!pDlg->Show(this, true)) { delete pDlg; return false; }
// done, success
return true;
}
// --------------------------------------------------
// InputDialog
InputDialog::InputDialog(const char *szMessage, const char *szCaption, Icons icoIcon, BaseInputCallback *pCB, bool fChatLayout)
: Dialog(fChatLayout ? C4GUI::GetScreenWdt()*4/5 : C4GUI_InputDlgWdt,
fChatLayout ? C4GUI::Edit::GetDefaultEditHeight() + 2 :
std::max(::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_InputDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, nullptr, 0, true),
C4GUI_IconHgt) + C4GUI_InputDlgVRoom, szCaption, false),
pEdit(nullptr), pCB(pCB), fChatLayout(fChatLayout), pChatLbl(nullptr)
{
if (fChatLayout)
{
// chat input layout
C4GUI::ComponentAligner caChat(GetContainedClientRect(), 1,1);
// normal chatbox layout: Left chat label
int32_t w=40,h;
::GraphicsResource.TextFont.GetTextExtent(szMessage, w,h, true);
pChatLbl = new C4GUI::WoodenLabel(szMessage, caChat.GetFromLeft(w+4), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont);
caChat.ExpandLeft(2); // undo margin
rcEditBounds = caChat.GetAll();
SetCustomEdit(new Edit(rcEditBounds));
pChatLbl->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT"));
AddElement(pChatLbl);
}
else
{
// regular input dialog layout
// get positions
ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
rcEditBounds = caMain.GetFromBottom(Edit::GetDefaultEditHeight());
// place icon
C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
Icon *pIcon = new Icon(rcIcon, icoIcon); AddElement(pIcon);
// place message label
// use text with line breaks
StdStrBuf str;
::GraphicsResource.TextFont.BreakMessage(szMessage, C4GUI_InputDlgWdt - 3 * C4GUI_DefDlgIndent - C4GUI_IconWdt, &str, true);
Label *pLblMessage = new Label(str.getData(), caMain.GetAll().GetMiddleX(), caMain.GetAll().y, ACenter, C4GUI_MessageFontClr, &::GraphicsResource.TextFont);
AddElement(pLblMessage);
// place input edit
SetCustomEdit(new Edit(rcEditBounds));
// place buttons
C4Rect rcBtn = caButtonArea.GetCentered(2 * C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace, C4GUI_ButtonHgt);
rcBtn.Wdt = C4GUI_DefButton2Wdt;
// OK
Button *pBtnOK = new OKButton(rcBtn);
AddElement(pBtnOK);
rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace;
// Cancel
Button *pBtnAbort = new CancelButton(rcBtn);
AddElement(pBtnAbort);
rcBtn.x += rcBtn.Wdt + C4GUI_DefButton2HSpace;
}
// input dlg always closed in the end
SetDelOnClose();
}
void InputDialog::SetInputText(const char *szToText)
{
pEdit->SelectAll(); pEdit->DeleteSelection();
if (szToText)
{
pEdit->InsertText(szToText, false);
pEdit->SelectAll();
}
}
void InputDialog::SetCustomEdit(Edit *pCustomEdit)
{
// del old
if (pEdit) delete pEdit;
// add new
pEdit = pCustomEdit;
pEdit->SetBounds(rcEditBounds);
if (fChatLayout)
{
pEdit->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT"));
pChatLbl->SetClickFocusControl(pEdit); // 2do: to all, to allies, etc.
}
AddElement(pEdit);
SetFocus(pEdit, false);
}
// --------------------------------------------------
// InfoDialog
InfoDialog::InfoDialog(const char *szCaption, int32_t iLineCount)
: Dialog(C4GUI_InfoDlgWdt, ::GraphicsResource.TextFont.GetLineHeight()*iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0)
{
// timer
Application.Add(this);
CreateSubComponents();
}
InfoDialog::InfoDialog(const char *szCaption, int iLineCount, const StdStrBuf &sText)
: Dialog(C4GUI_InfoDlgWdt, ::GraphicsResource.TextFont.GetLineHeight()*iLineCount + C4GUI_InfoDlgVRoom, szCaption, false), iScroll(0)
{
// ctor - init w/o timer
CreateSubComponents();
// fill in initial text
for (size_t i=0; i < sText.getLength(); ++i)
{
size_t i0 = i;
while (sText[i] != '|' && sText[i]) ++i;
StdStrBuf sLine = sText.copyPart(i0, i-i0);
pTextWin->AddTextLine(sLine.getData(), &::GraphicsResource.TextFont, C4GUI_MessageFontClr, false, true);
}
pTextWin->UpdateHeight();
}
InfoDialog::~InfoDialog()
{
Application.Remove(this);
}
void InfoDialog::CreateSubComponents()
{
// get positions
ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt), 0,0);
// place info box
pTextWin = new TextWindow(caMain.GetAll(), 0, 0, 0, 100, 4096, " ", true, nullptr, 0);
AddElement(pTextWin);
// place close button
Button *pBtnClose = new DlgCloseButton(caButtonArea.GetCentered(C4GUI_DefButtonWdt, C4GUI_ButtonHgt));
AddElement(pBtnClose); pBtnClose->SetToolTip(LoadResStr("IDS_MNU_CLOSE"));
}
void InfoDialog::AddLine(const char *szText)
{
// add line to text window
if (!pTextWin) return;
pTextWin->AddTextLine(szText, &::GraphicsResource.TextFont, C4GUI_MessageFontClr, false, true);
}
void InfoDialog::AddLineFmt(const char *szFmtString, ...)
{
// compose formatted line
va_list lst; va_start(lst, szFmtString);
StdStrBuf buf;
buf.FormatV(szFmtString, lst);
// add it
AddLine(buf.getData());
}
void InfoDialog::BeginUpdateText()
{
// safety
if (!pTextWin) return;
// backup scrolling
iScroll = pTextWin->GetScrollPos();
// clear text window, so new text can be added
pTextWin->ClearText(false);
}
void InfoDialog::EndUpdateText()
{
// safety
if (!pTextWin) return;
// update text height
pTextWin->UpdateHeight();
// restore scrolling
pTextWin->SetScrollPos(iScroll);
}
void InfoDialog::OnSec1Timer()
{
// always update
UpdateText();
}
} // end of namespace