forked from Mirrors/openclonk
1271 lines
40 KiB
C++
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
|
|
|