openclonk/src/gui/C4Scoreboard.cpp

424 lines
12 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2005-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.
*/
// script-controlled InGame dialog to show player infos
#include "C4Include.h"
#include "gui/C4Scoreboard.h"
#include "gui/C4GameOverDlg.h"
#include "gui/C4Gui.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
class C4ScoreboardDlg : public C4GUI::Dialog
{
private:
int32_t *piColWidths;
C4Scoreboard *pBrd;
enum { XIndent = 4, YIndent = 4, XMargin = 3, YMargin = 3 };
public:
C4ScoreboardDlg(C4Scoreboard *pForScoreboard);
~C4ScoreboardDlg() override;
protected:
void InvalidateRows() { delete [] piColWidths; piColWidths = nullptr; }
void Update(); // update row widths and own size and caption
bool DoPlacement(C4GUI::Screen *pOnScreen, const C4Rect &rPreferredDlgRect) override;
void Draw(C4TargetFacet &cgo) override;
void DrawElement(C4TargetFacet &cgo) override;
const char *GetID() override { return "Scoreboard"; }
bool IsMouseControlled() override { return false; }
friend class C4Scoreboard;
};
// ************************************************
// *** C4Scoreboard
void C4Scoreboard::Entry::SwapWith(Entry *pSwap)
{
Entry swp; swp.Text.Take(std::move(Text)); swp.iVal = iVal;
Text.Take(std::move(pSwap->Text)); iVal = pSwap->iVal;
pSwap->Text.Take(std::move(swp.Text)); pSwap->iVal = swp.iVal;
}
void C4Scoreboard::Clear()
{
// del all cells
delete [] pEntries;
pEntries = nullptr; iRows=iCols=0;
// del dialog
iDlgShow = 0;
if (pDlg) { delete pDlg; pDlg = nullptr; }
}
void C4Scoreboard::AddRow(int32_t iInsertBefore)
{
// counts
int32_t iNewEntryCount = (iRows+1) * iCols;
if (!iNewEntryCount) { ++iRows; return; }
// realloc and copy array
Entry *pNewEntries = new Entry[iNewEntryCount];
for (int32_t iRow = 0, iFromRow = 0; iFromRow < iRows; ++iRow,++iFromRow)
{
if (iFromRow == iInsertBefore) ++iRow;
for (int32_t iCol = 0; iCol < iCols; ++iCol)
pNewEntries[iRow*iCols+iCol].GrabFrom(GetCell(iCol, iFromRow));
}
++iRows;
delete [] pEntries; pEntries = pNewEntries;
}
void C4Scoreboard::AddCol(int32_t iInsertBefore)
{
// counts
int32_t iNewEntryCount = iRows * (iCols + 1);
if (!iNewEntryCount) { ++iCols; return; }
// realloc and copy array
Entry *pNewEntries = new Entry[iNewEntryCount];
for (int32_t iRow = 0; iRow < iRows; ++iRow)
for (int32_t iCol = 0, iFromCol = 0; iFromCol < iCols; ++iCol, ++iFromCol)
{
if (iFromCol == iInsertBefore) ++iCol;
pNewEntries[iRow*(iCols+1)+iCol].GrabFrom(GetCell(iFromCol, iRow));
}
++iCols; // CAUTION: must inc after add, so GetCell won't point into bogus!
delete [] pEntries; pEntries = pNewEntries;
}
void C4Scoreboard::DelRow(int32_t iDelIndex)
{
// counts
int32_t iNewEntryCount = (iRows-1) * iCols;
if (!iNewEntryCount) { --iRows; delete [] pEntries; pEntries = nullptr; return; }
// realloc and copy array
Entry *pNewEntries = new Entry[iNewEntryCount]; Entry *pCpy = pNewEntries;
for (int32_t iRow = 0, iFromRow = 0; iRow < (iRows-1); ++iRow,++iFromRow)
{
if (iFromRow == iDelIndex) ++iFromRow;
for (int32_t iCol = 0; iCol < iCols; ++iCol)
pCpy++->GrabFrom(GetCell(iCol, iFromRow));
}
--iRows;
delete [] pEntries; pEntries = pNewEntries;
}
void C4Scoreboard::DelCol(int32_t iDelIndex)
{
// counts
int32_t iNewEntryCount = iRows * (iCols-1);
if (!iNewEntryCount) { --iCols; delete [] pEntries; pEntries = nullptr; return; }
// realloc and copy array
Entry *pNewEntries = new Entry[iNewEntryCount]; Entry *pCpy = pNewEntries;
for (int32_t iRow = 0; iRow < iRows; ++iRow)
for (int32_t iCol = 0, iFromCol = 0; iCol < (iCols-1); ++iCol,++iFromCol)
{
if (iFromCol == iDelIndex) ++iFromCol;
pCpy++->GrabFrom(GetCell(iFromCol, iRow));
}
--iCols; // CAUTION: must dec after add, so GetCell won't point into bogus!
delete [] pEntries; pEntries = pNewEntries;
}
void C4Scoreboard::SwapRows(int32_t iRow1, int32_t iRow2)
{
Entry *pXChg1 = pEntries+iRow1*iCols;
Entry *pXChg2 = pEntries+iRow2*iCols;
int32_t i = iCols; while (i--) pXChg1++->SwapWith(pXChg2++);
}
int32_t C4Scoreboard::GetColByKey(int32_t iKey) const
{
// safety
if (!iRows) return -1;
// check all col headers
Entry *pCheck = pEntries;
for (int32_t i = 0; i < iCols; ++i)
if (pCheck++->iVal == iKey) return i;
return -1;
}
int32_t C4Scoreboard::GetRowByKey(int32_t iKey) const
{
// safety
if (!iCols) return -1;
// check all row headers
Entry *pCheck = pEntries;
for (int32_t i = 0; i < iRows; ++i,(pCheck+=iCols))
if (pCheck->iVal == iKey) return i;
return -1;
}
void C4Scoreboard::SetCell(int32_t iColKey, int32_t iRowKey, const char *szValue, int32_t iValue)
{
// ensure primary row/col exists
if (!iCols || !iRows)
{
if (!iCols) AddCol(0);
if (!iRows) AddRow(0);
GetCell(0, 0)->iVal = TitleKey;
}
// get row/col; create new if not yet existing
int32_t iCol = GetColByKey(iColKey);
if (iCol<0) { AddCol(iCol=iCols); GetCell(iCol, 0)->iVal = iColKey; }
int32_t iRow = GetRowByKey(iRowKey);
if (iRow<0) { AddRow(iRow=iRows); GetCell(0, iRow)->iVal = iRowKey; }
// now set values
Entry *pCell = GetCell(iCol, iRow);
pCell->Text.Copy(szValue);
if (iCol && iRow) pCell->iVal = iValue; // do NOT overwrite index keys!
// if an empty value was set, prune empty
if (!szValue || !*szValue)
{
// prune empty row (not label row)
int32_t i;
if (iRow)
{
for (i=1; i<iCols; ++i) if (GetCell(i, iRow)->Text) break;
if (i == iCols) DelRow(iRow);
}
// prune empty col (not label col)
if (iCol)
{
for (i=1; i<iRows; ++i) if (GetCell(iCol, i)->Text) break;
if (i == iRows) DelCol(iCol);
}
}
/* // prune empty board? but this would prevent boards that just sort
if (iRows == 1 && iCols == 1)
Clear(); // must not do this, because it will del pDlg
else*/
// recalc row widths in display (else done by clear)
InvalidateRows();
}
const char *C4Scoreboard::GetCellString(int32_t iColKey, int32_t iRowKey)
{
// get row/col
int32_t iCol = GetColByKey(iColKey);
int32_t iRow = GetRowByKey(iRowKey);
if (iCol<0 || iRow<0) return nullptr;
// now get value
Entry *pCell = GetCell(iCol, iRow);
return pCell->Text.getData();
}
int32_t C4Scoreboard::GetCellData(int32_t iColKey, int32_t iRowKey)
{
// get row/col
int32_t iCol = GetColByKey(iColKey);
int32_t iRow = GetRowByKey(iRowKey);
if (iCol<0 || iRow<0) return 0;
// now get value
Entry *pCell = GetCell(iCol, iRow);
return pCell->iVal;
}
void C4Scoreboard::RemoveCol(int32_t iColKey)
{
int32_t iCol = GetColByKey(iColKey);
if (iCol>=0) DelCol(iCol);
InvalidateRows(); // recalc row widths in display
}
void C4Scoreboard::RemoveRow(int32_t iRowKey)
{
int32_t iRow = GetRowByKey(iRowKey);
if (iRow>=0) DelRow(iRow);
InvalidateRows(); // recalc row widths in display
}
bool C4Scoreboard::SortBy(int32_t iColKey, bool fReverse)
{
// get sort col
int32_t iCol = GetColByKey(iColKey);
if (iCol<0) return false;
// sort
int32_t iSortDir = fReverse ? -1 : +1;
int32_t iSortBegin=1, iSortEnd=iRows-1;
while (iSortBegin < iSortEnd)
{
int32_t iNewBorder = iSortBegin; int32_t i;
for (i = iSortBegin; i < iSortEnd; ++i)
if (GetCell(iCol, i)->iVal * iSortDir > GetCell(iCol, i+1)->iVal * iSortDir)
{
SwapRows(i, i+1);
iNewBorder = i;
}
iSortEnd = iNewBorder;
for (i = iSortEnd; i > iSortBegin; --i)
if (GetCell(iCol, i-1)->iVal * iSortDir > GetCell(iCol, i)->iVal * iSortDir)
{
SwapRows(i-1, i);
iNewBorder = i;
}
iSortBegin = iNewBorder;
}
return true;
}
void C4Scoreboard::InvalidateRows()
{
// recalculate row sizes
if (pDlg) pDlg->InvalidateRows();
}
void C4Scoreboard::DoDlgShow(int32_t iChange, bool fUserToggle)
{
if (::pGUI->IsExclusive()) return;
// update dlg show
iDlgShow += iChange;
if (!fUserToggle)
// script update: Dlg on off if iDlgShow variable passed zero
fUserToggle = (ShouldBeShown() == !pDlg);
else
// user pressed Tab: Always toggle except if the scoreboard cannot be shown at all
if (!CanBeShown() && !pDlg) fUserToggle = false;
if (fUserToggle)
{
if (!pDlg)
{
if (!C4GameOverDlg::IsShown()) // never show during game over dlg
::pGUI->ShowRemoveDlg(pDlg = new C4ScoreboardDlg(this));
}
else
pDlg->Close(false);
}
}
void C4Scoreboard::HideDlg()
{
if (::pGUI->IsExclusive()) return;
// hide scoreboard if it was active
if (pDlg) pDlg->Close(false);
}
void C4Scoreboard::CompileFunc(StdCompiler *pComp)
{
bool deserializing = pComp->isDeserializer();
if (deserializing) Clear();
pComp->Value(mkNamingAdapt(iRows, "Rows", 0));
pComp->Value(mkNamingAdapt(iCols, "Cols", 0));
pComp->Value(mkNamingAdapt(iDlgShow, "DlgShow", 0));
if (iRows * iCols)
{
if (deserializing) pEntries = new Entry[iRows * iCols];
for (int32_t iRow = 0; iRow < iRows; ++iRow)
for (int32_t iCol = 0; iCol < iCols; ++iCol)
{
Entry *pEnt = GetCell(iCol, iRow);
pComp->Value(mkNamingAdapt(pEnt->Text, FormatString("Cell%i_%iString", iCol, iRow).getData()));
pComp->Value(mkNamingAdapt(pEnt->iVal, FormatString("Cell%i_%iValue", iCol, iRow).getData()));
}
// recheck dlg show in read mode
// will usually not do anything, because reading is done before enetering shared mode
if (pComp->isDeserializer()) DoDlgShow(0, false);
}
}
// ************************************************
// *** C4ScoreboardDlg
C4ScoreboardDlg::C4ScoreboardDlg(C4Scoreboard *pForScoreboard)
: C4GUI::Dialog(100, 100, "nops", false), piColWidths(nullptr), pBrd(pForScoreboard)
{
Update();
}
C4ScoreboardDlg::~C4ScoreboardDlg()
{
delete [] piColWidths;
pBrd->pDlg = nullptr;
}
void C4ScoreboardDlg::Update()
{
// counts
int32_t iRowCount = pBrd->iRows; int32_t iColCount = pBrd->iCols;
delete [] piColWidths; piColWidths = nullptr;
// invalid board - scipters can create those, but there's no reason why the engine
// should display something pretty then; just keep dialog defaults
if (!iRowCount || !iColCount) return;
// calc sizes as max col widths plus some indent pixels
piColWidths = new int32_t[iColCount];
int32_t iWdt=XMargin*2, iHgt=YMargin*2;
for (int32_t iCol = 0; iCol < iColCount; ++iCol)
{
piColWidths[iCol] = XIndent;
for (int32_t iRow = 0; iRow < iRowCount; ++iRow)
{
C4Scoreboard::Entry *pCell = pBrd->GetCell(iCol, iRow);
if ((iRow || iCol) && !pCell->Text.isNull()) piColWidths[iCol] = std::max<int32_t>(piColWidths[iCol], ::GraphicsResource.FontRegular.GetTextWidth(pCell->Text.getData()) + XIndent);
}
iWdt += piColWidths[iCol];
}
iHgt += iRowCount * (::GraphicsResource.FontRegular.GetLineHeight() + YIndent);
const char *szTitle = pBrd->GetCell(0,0)->Text.getData();
if (szTitle) iWdt = std::max<int32_t>(iWdt, ::GraphicsResource.FontRegular.GetTextWidth(szTitle) + 40);
if (!pTitle != !szTitle) SetTitle(szTitle); // needed for title margin...
iWdt += GetMarginLeft() + GetMarginRight();
iHgt += GetMarginTop() + GetMarginBottom();
// update dialog
SetBounds(C4Rect(rcBounds.x, rcBounds.y, iWdt, iHgt));
SetTitle(szTitle);
if (szTitle && pTitle) pTitle->SetIcon(C4GUI::Icon::GetIconFacet(C4GUI::Ico_Player));
// realign
C4GUI::Screen *pScr = GetScreen();
if (pScr) DoPlacement(pScr, pScr->GetPreferredDlgRect());
}
bool C4ScoreboardDlg::DoPlacement(C4GUI::Screen *pOnScreen, const C4Rect &rPreferredDlgRect)
{
// align topright
SetPos(rPreferredDlgRect.x + rPreferredDlgRect.Wdt - rcBounds.Wdt - 20, rPreferredDlgRect.y + 38);
return true;
}
void C4ScoreboardDlg::Draw(C4TargetFacet &cgo)
{
if (!piColWidths) Update();
typedef C4GUI::Dialog ParentClass;
ParentClass::Draw(cgo);
}
void C4ScoreboardDlg::DrawElement(C4TargetFacet &cgo)
{
typedef C4GUI::Dialog ParentClass;
ParentClass::DrawElement(cgo);
// draw spreadsheet
int32_t iRowCount = pBrd->iRows; int32_t iColCount = pBrd->iCols;
int32_t iY = YMargin + int32_t(cgo.TargetY) + rcClientRect.y;
for (int32_t iRow = 0; iRow < iRowCount; ++iRow)
{
int32_t iX = XMargin + int32_t(cgo.TargetX) + rcClientRect.x;
for (int32_t iCol = 0; iCol < iColCount; ++iCol)
{
const char *szText = pBrd->GetCell(iCol, iRow)->Text.getData();
if (szText && *szText && (iRow || iCol))
pDraw->TextOut(szText, ::GraphicsResource.FontRegular, 1.0, cgo.Surface, iCol ? iX + piColWidths[iCol]/2 : iX, iY, 0xffffffff, iCol ? ACenter : ALeft);
iX += piColWidths[iCol];
}
iY += ::GraphicsResource.FontRegular.GetLineHeight() + YIndent;
}
}