1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-30 23:18:08 +02:00
vcmi/client/widgets/TextControls.cpp
Arseniy Shestakov 10dbbead2d Fix indentation of logging code and around it
That wouldn't be as big issue if problem affected few files, but it everywhere in codebase.
Fixed it everywhere since in most files that is the only code with wrong indentation.
2016-03-12 04:46:21 +03:00

624 lines
13 KiB
C++

#include "StdInc.h"
#include "TextControls.h"
#include "Buttons.h"
#include "Images.h"
#include "../CMessage.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/CGeneralTextHandler.h" //for Unicode related stuff
/*
* TextControls.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
std::string CLabel::visibleText()
{
return text;
}
void CLabel::showAll(SDL_Surface * to)
{
CIntObject::showAll(to);
if(!visibleText().empty())
blitLine(to, pos, visibleText());
}
CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, const SDL_Color &Color /*= Colors::WHITE*/, const std::string &Text /*= ""*/)
:CTextContainer(Align, Font, Color), text(Text)
{
type |= REDRAW_PARENT;
autoRedraw = true;
pos.x += x;
pos.y += y;
pos.w = pos.h = 0;
bg = nullptr;
if (alignment == TOPLEFT) // causes issues for MIDDLE
{
pos.w = graphics->fonts[font]->getStringWidth(visibleText().c_str());
pos.h = graphics->fonts[font]->getLineHeight();
}
}
Point CLabel::getBorderSize()
{
return Point(0, 0);
}
std::string CLabel::getText()
{
return text;
}
void CLabel::setText(const std::string &Txt)
{
text = Txt;
if(autoRedraw)
{
if(bg || !parent)
redraw();
else
parent->redraw();
}
}
CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text):
CLabel(position.x, position.y, Font, Align, Color, Text),
visibleSize(0, 0, position.w, position.h)
{
pos.w = position.w;
pos.h = position.h;
splitText(Text);
}
void CMultiLineLabel::setVisibleSize(Rect visibleSize)
{
this->visibleSize = visibleSize;
redraw();
}
void CMultiLineLabel::scrollTextBy(int distance)
{
scrollTextTo(visibleSize.y + distance);
}
void CMultiLineLabel::scrollTextTo(int distance)
{
Rect size = visibleSize;
size.y = distance;
setVisibleSize(size);
}
void CMultiLineLabel::setText(const std::string &Txt)
{
splitText(Txt);
CLabel::setText(Txt);
}
void CTextContainer::blitLine(SDL_Surface *to, Rect destRect, std::string what)
{
const IFont * f = graphics->fonts[font];
Point where = destRect.topLeft();
// input is rect in which given text should be placed
// calculate proper position for top-left corner of the text
if (alignment == TOPLEFT)
{
where.x += getBorderSize().x;
where.y += getBorderSize().y;
}
if (alignment == CENTER)
{
where.x += (int(destRect.w) - int(f->getStringWidth(what))) / 2;
where.y += (int(destRect.h) - int(f->getLineHeight())) / 2;
}
if (alignment == BOTTOMRIGHT)
{
where.x += getBorderSize().x + destRect.w - f->getStringWidth(what);
where.y += getBorderSize().y + destRect.h - f->getLineHeight();
}
size_t begin = 0;
std::string delimeters = "{}";
size_t currDelimeter = 0;
do
{
size_t end = what.find_first_of(delimeters[currDelimeter % 2], begin);
if (begin != end)
{
std::string toPrint = what.substr(begin, end - begin);
if (currDelimeter % 2) // Enclosed in {} text - set to yellow
f->renderTextLeft(to, toPrint, Colors::YELLOW, where);
else // Non-enclosed text, use default color
f->renderTextLeft(to, toPrint, color, where);
begin = end;
where.x += f->getStringWidth(toPrint);
}
currDelimeter++;
}
while (begin++ != std::string::npos);
}
CTextContainer::CTextContainer(EAlignment alignment, EFonts font, SDL_Color color):
alignment(alignment),
font(font),
color(color)
{}
void CMultiLineLabel::showAll(SDL_Surface * to)
{
CIntObject::showAll(to);
const IFont * f = graphics->fonts[font];
// calculate which lines should be visible
int totalLines = lines.size();
int beginLine = visibleSize.y;
int endLine = getTextLocation().h + visibleSize.y;
if (beginLine < 0)
beginLine = 0;
else
beginLine /= f->getLineHeight();
if (endLine < 0)
endLine = 0;
else
endLine /= f->getLineHeight();
endLine++;
// and where they should be displayed
Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * f->getLineHeight());
Point lineSize = Point(getTextLocation().w, f->getLineHeight());
CSDL_Ext::CClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit
for (int i = beginLine; i < std::min(totalLines, endLine); i++)
{
if (!lines[i].empty()) //non-empty line
blitLine(to, Rect(lineStart, lineSize), lines[i]);
lineStart.y += f->getLineHeight();
}
}
void CMultiLineLabel::splitText(const std::string &Txt)
{
lines.clear();
const IFont * f = graphics->fonts[font];
int lineHeight = f->getLineHeight();
lines = CMessage::breakText(Txt, pos.w, font);
textSize.y = lineHeight * lines.size();
textSize.x = 0;
for(const std::string &line : lines)
vstd::amax( textSize.x, f->getStringWidth(line.c_str()));
redraw();
}
Rect CMultiLineLabel::getTextLocation()
{
// this method is needed for vertical alignment alignment of text
// when height of available text is smaller than height of widget
// in this case - we should add proper offset to display text at required position
if (pos.h <= textSize.y)
return pos;
Point textSize(pos.w, graphics->fonts[font]->getLineHeight() * lines.size());
Point textOffset(pos.w - textSize.x, pos.h - textSize.y);
switch(alignment)
{
case TOPLEFT: return Rect(pos.topLeft(), textSize);
case CENTER: return Rect(pos.topLeft() + textOffset / 2, textSize);
case BOTTOMRIGHT: return Rect(pos.topLeft() + textOffset, textSize);
}
assert(0);
return Rect();
}
CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color):
font(Font), align(Align), color(Color)
{}
void CLabelGroup::add(int x, int y, const std::string &text)
{
OBJ_CONSTRUCTION_CAPTURING_ALL;
new CLabel(x, y, font, align, color, text);
}
CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= TOPLEFT*/, const SDL_Color &Color /*= Colors::WHITE*/):
sliderStyle(SliderStyle),
slider(nullptr)
{
OBJ_CONSTRUCTION_CAPTURING_ALL;
label = new CMultiLineLabel(rect, Font, Align, Color);
type |= REDRAW_PARENT;
pos.x += rect.x;
pos.y += rect.y;
pos.h = rect.h;
pos.w = rect.w;
assert(pos.w >= 40); //we need some space
setText(Text);
}
void CTextBox::sliderMoved(int to)
{
label->scrollTextTo(to);
}
void CTextBox::resize(Point newSize)
{
pos.w = newSize.x;
pos.h = newSize.y;
label->pos.w = pos.w;
label->pos.h = pos.h;
if (slider)
vstd::clear_pointer(slider); // will be recreated if needed later
setText(label->getText()); // force refresh
}
void CTextBox::setText(const std::string &text)
{
label->pos.w = pos.w; // reset to default before textSize.y check
label->setText(text);
if(label->textSize.y <= label->pos.h && slider)
{
// slider is no longer needed
vstd::clear_pointer(slider);
}
else if(slider)
{
// decrease width again if slider still used
label->pos.w = pos.w - 32;
label->setText(text);
slider->setAmount(label->textSize.y);
}
else if(label->textSize.y > label->pos.h)
{
// create slider and update widget
label->pos.w = pos.w - 32;
label->setText(text);
OBJ_CONSTRUCTION_CAPTURING_ALL;
slider = new CSlider(Point(pos.w - 32, 0), pos.h, std::bind(&CTextBox::sliderMoved, this, _1),
label->pos.h, label->textSize.y, 0, false, CSlider::EStyle(sliderStyle));
slider->setScrollStep(graphics->fonts[label->font]->getLineHeight());
}
}
void CGStatusBar::setText(const std::string & Text)
{
if(!textLock)
CLabel::setText(Text);
}
void CGStatusBar::clear()
{
setText("");
}
CGStatusBar::CGStatusBar(CPicture *BG, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= CENTER*/, const SDL_Color &Color /*= Colors::WHITE*/)
: CLabel(BG->pos.x, BG->pos.y, Font, Align, Color, "")
{
init();
bg = BG;
addChild(bg);
pos = bg->pos;
getBorderSize();
textLock = false;
}
CGStatusBar::CGStatusBar(int x, int y, std::string name/*="ADROLLVR.bmp"*/, int maxw/*=-1*/)
: CLabel(x, y, FONT_SMALL, CENTER)
{
OBJ_CONSTRUCTION_CAPTURING_ALL;
init();
bg = new CPicture(name);
pos = bg->pos;
if((unsigned int)maxw < pos.w)
{
vstd::amin(pos.w, maxw);
bg->srcRect = new Rect(0, 0, maxw, pos.h);
}
textLock = false;
}
CGStatusBar::~CGStatusBar()
{
GH.statusbar = oldStatusBar;
}
void CGStatusBar::show(SDL_Surface * to)
{
showAll(to);
}
void CGStatusBar::init()
{
oldStatusBar = GH.statusbar;
GH.statusbar = this;
}
Point CGStatusBar::getBorderSize()
{
//Width of borders where text should not be printed
static const Point borderSize(5,1);
switch(alignment)
{
case TOPLEFT: return Point(borderSize.x, borderSize.y);
case CENTER: return Point(pos.w/2, pos.h/2);
case BOTTOMRIGHT: return Point(pos.w - borderSize.x, pos.h - borderSize.y);
}
assert(0);
return Point();
}
void CGStatusBar::lock(bool shouldLock)
{
textLock = shouldLock;
}
CTextInput::CTextInput(const Rect &Pos, EFonts font, const CFunctionList<void(const std::string &)> &CB):
CLabel(Pos.x, Pos.y, font, CENTER),
cb(CB)
{
type |= REDRAW_PARENT;
focus = false;
pos.h = Pos.h;
pos.w = Pos.w;
captureAllKeys = true;
bg = nullptr;
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
giveFocus();
}
CTextInput::CTextInput( const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList<void(const std::string &)> &CB )
:cb(CB)
{
focus = false;
pos += Pos;
captureAllKeys = true;
OBJ_CONSTRUCTION;
bg = new CPicture(bgName, bgOffset.x, bgOffset.y);
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
giveFocus();
}
CTextInput::CTextInput(const Rect &Pos, SDL_Surface *srf)
{
focus = false;
pos += Pos;
captureAllKeys = true;
OBJ_CONSTRUCTION;
bg = new CPicture(Pos, 0, true);
Rect hlp = Pos;
if(srf)
CSDL_Ext::blitSurface(srf, &hlp, *bg, nullptr);
else
SDL_FillRect(*bg, nullptr, 0);
pos.w = bg->pos.w;
pos.h = bg->pos.h;
bg->pos = pos;
addUsedEvents(LCLICK | KEYBOARD | TEXTINPUT);
giveFocus();
}
void CTextInput::focusGot()
{
CSDL_Ext::startTextInput(&pos);
}
void CTextInput::focusLost()
{
CSDL_Ext::stopTextInput();
}
std::string CTextInput::visibleText()
{
return focus ? text + newText + "_" : text;
}
void CTextInput::clickLeft( tribool down, bool previousState )
{
if(down && !focus)
giveFocus();
}
void CTextInput::keyPressed( const SDL_KeyboardEvent & key )
{
if(!focus || key.state != SDL_PRESSED)
return;
if(key.keysym.sym == SDLK_TAB)
{
moveFocus();
GH.breakEventHandling();
return;
}
bool redrawNeeded = false;
switch(key.keysym.sym)
{
case SDLK_DELETE: // have index > ' ' so it won't be filtered out by default section
return;
case SDLK_BACKSPACE:
if(!newText.empty())
{
Unicode::trimRight(newText);
redrawNeeded = true;
}
else if(!text.empty())
{
Unicode::trimRight(text);
redrawNeeded = true;
}
break;
default:
break;
}
if (redrawNeeded)
{
redraw();
cb(text);
}
}
void CTextInput::setText( const std::string &nText, bool callCb )
{
CLabel::setText(nText);
if(callCb)
cb(text);
}
bool CTextInput::captureThisEvent(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_RETURN || key.keysym.sym == SDLK_KP_ENTER || key.keysym.sym == SDLK_ESCAPE)
return false;
return true;
}
void CTextInput::textInputed(const SDL_TextInputEvent & event)
{
if(!focus)
return;
std::string oldText = text;
text += event.text;
filters(text,oldText);
if (text != oldText)
{
redraw();
cb(text);
}
newText = "";
}
void CTextInput::textEdited(const SDL_TextEditingEvent & event)
{
if(!focus)
return;
newText = event.text;
redraw();
cb(text+newText);
}
void CTextInput::filenameFilter(std::string & text, const std::string &)
{
static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed
size_t pos;
while ((pos = text.find_first_of(forbiddenChars)) != std::string::npos)
text.erase(pos, 1);
}
void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue)
{
assert(minValue < maxValue);
if (text.empty())
text = "0";
size_t pos = 0;
if (text[0] == '-') //allow '-' sign as first symbol only
pos++;
while (pos < text.size())
{
if (text[pos] < '0' || text[pos] > '9')
{
text = oldText;
return; //new text is not number.
}
pos++;
}
try
{
int value = boost::lexical_cast<int>(text);
if (value < minValue)
text = boost::lexical_cast<std::string>(minValue);
else if (value > maxValue)
text = boost::lexical_cast<std::string>(maxValue);
}
catch(boost::bad_lexical_cast &)
{
//Should never happen. Unless I missed some cases
logGlobal->warnStream() << "Warning: failed to convert "<< text << " to number!";
text = oldText;
}
}
CFocusable::CFocusable()
{
focusables.push_back(this);
}
CFocusable::~CFocusable()
{
if(inputWithFocus == this)
{
focusLost();
inputWithFocus = nullptr;
}
focusables -= this;
}
void CFocusable::giveFocus()
{
if(inputWithFocus)
{
inputWithFocus->focus = false;
inputWithFocus->focusLost();
inputWithFocus->redraw();
}
focus = true;
inputWithFocus = this;
focusGot();
redraw();
}
void CFocusable::moveFocus()
{
auto i = vstd::find(focusables, this),
ourIt = i;
for(i++; i != ourIt; i++)
{
if(i == focusables.end())
i = focusables.begin();
if((*i)->active)
{
(*i)->giveFocus();
break;;
}
}
}