Bläddra i källkod

Some refactoring of text display widgets:
- renamed some methods/classes with better names + some consistency (no more print, setTxt and setText that do exactly same thing)
- TextBox now contains label widget instead of inheriting it
- smooth scrolling support (up/down buttons still scroll one line)

Ivan Savenko 12 år sedan
förälder
incheckning
6bee105d7d

+ 3 - 3
client/AdventureMapClasses.cpp

@@ -71,7 +71,7 @@ void CList::CListItem::clickLeft(tribool down, bool previousState)
 void CList::CListItem::hover(bool on)
 {
 	if (on)
-		GH.statusbar->print(getHoverText());
+		GH.statusbar->setText(getHoverText());
 	else
 		GH.statusbar->clear();
 }
@@ -561,7 +561,7 @@ void CMinimap::clickRight(tribool down, bool previousState)
 void CMinimap::hover(bool on)
 {
 	if (on)
-		GH.statusbar->print(CGI->generaltexth->zelp[291].first);
+		GH.statusbar->setText(CGI->generaltexth->zelp[291].first);
 	else
 		GH.statusbar->clear();
 }
@@ -872,7 +872,7 @@ void CInfoBar::clickRight(tribool down, bool previousState)
 void CInfoBar::hover(bool on)
 {
 	if (on)
-		GH.statusbar->print(CGI->generaltexth->zelp[292].first);
+		GH.statusbar->setText(CGI->generaltexth->zelp[292].first);
 	else
 		GH.statusbar->clear();
 }

+ 2 - 2
client/CAdvmapInterface.cpp

@@ -1231,13 +1231,13 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
 	{
 		std::string text = objAtTile->getHoverText();
 		boost::replace_all(text,"\n"," ");
-		statusbar.print(text);
+		statusbar.setText(text);
 	}
 	else
 	{
 		std::string hlp;
 		CGI->mh->getTerrainDescr(mapPos, hlp, false);
-		statusbar.print(hlp);
+		statusbar.setText(hlp);
 	}
 
 	const CGPathNode *pnode = LOCPLINT->cb->getPathInfo(mapPos);

+ 12 - 12
client/CCastleInterface.cpp

@@ -247,7 +247,7 @@ void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
 			  || (*parent->selectedBuilding)<(*this)) //or we are on top
 			{
 				parent->selectedBuilding = this;
-				GH.statusbar->print(getSubtitle());
+				GH.statusbar->setText(getSubtitle());
 			}
 		}
 	}
@@ -335,7 +335,7 @@ void CHeroGSlot::hover (bool on)
 		}
 	}
 	if(temp.size())
-		GH.statusbar->print(temp);
+		GH.statusbar->setText(temp);
 }
 
 void CHeroGSlot::clickLeft(tribool down, bool previousState)
@@ -961,7 +961,7 @@ void CCastleInterface::recreateIcons()
 	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->modh->settings.MAX_BUILDING_PER_TURN];
 
 	icon->setFrame(iconIndex);
-	income->setTxt(boost::lexical_cast<std::string>(town->dailyIncome()));
+	income->setText(boost::lexical_cast<std::string>(town->dailyIncome()));
 
 	hall = new CTownInfo( 80, 413, town, true);
 	fort = new CTownInfo(122, 413, town, false);
@@ -1031,7 +1031,7 @@ void CCreaInfo::update()
 			value = boost::lexical_cast<std::string>(town->creatureGrowth(level));
 
 		if (value != label->text)
-			label->setTxt(value);
+			label->setText(value);
 	}
 }
 
@@ -1042,9 +1042,9 @@ void CCreaInfo::hover(bool on)
 
 	if(on)
 	{
-		GH.statusbar->print(message);
+		GH.statusbar->setText(message);
 	}
-	else if (message == GH.statusbar->getCurrent())
+	else if (message == GH.statusbar->getText())
 		GH.statusbar->clear();
 }
 
@@ -1122,7 +1122,7 @@ void CTownInfo::hover(bool on)
 	if(on)
 	{
 		if ( building )
-			GH.statusbar->print(building->Name());
+			GH.statusbar->setText(building->Name());
 	}
 	else
 		GH.statusbar->clear();
@@ -1244,7 +1244,7 @@ void CHallInterface::CBuildingBox::hover(bool on)
 		else
 			toPrint = CGI->generaltexth->hcommands[state];
 		boost::algorithm::replace_first(toPrint,"%s",building->Name());
-		GH.statusbar->print(toPrint);
+		GH.statusbar->setText(toPrint);
 	}
 	else
 		GH.statusbar->clear();
@@ -1508,7 +1508,7 @@ void LabeledValue::init(std::string nameText, std::string descr, int min, int ma
 void LabeledValue::hover(bool on)
 {
 	if(on)
-		GH.statusbar->print(hoverText);
+		GH.statusbar->setText(hoverText);
 	else
 	{
 		GH.statusbar->clear();
@@ -1570,7 +1570,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 void CFortScreen::RecruitArea::hover(bool on)
 {
 	if(on)
-		GH.statusbar->print(hoverText);
+		GH.statusbar->setText(hoverText);
 	else
 		GH.statusbar->clear();
 }
@@ -1581,7 +1581,7 @@ void CFortScreen::RecruitArea::creaturesChanged()
 	{
 		std::string availableText = CGI->generaltexth->allTexts[217] +
 		            boost::lexical_cast<std::string>(town->creatures[level].first);
-		availableCount->setTxt(availableText);
+		availableCount->setText(availableText);
 	}
 }
 
@@ -1659,7 +1659,7 @@ void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
 void CMageGuildScreen::Scroll::hover(bool on)
 {
 	if(on)
-		GH.statusbar->print(spell->name);
+		GH.statusbar->setText(spell->name);
 	else
 		GH.statusbar->clear();
 

+ 1 - 1
client/CKingdomInterface.cpp

@@ -812,7 +812,7 @@ void CTownItem::update()
 {
 	std::string incomeVal = boost::lexical_cast<std::string>(town->dailyIncome());
 	if (incomeVal != income->text)
-		income->setTxt(incomeVal);
+		income->setText(incomeVal);
 
 	heroes->update();
 

+ 1 - 1
client/CMT.cpp

@@ -443,7 +443,7 @@ void printInfoAboutIntObject(const CIntObject *obj, int level)
         sbuffer << "inactive";
     sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
     sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
-    logGlobal->debugStream() << sbuffer.str();
+    logGlobal->infoStream() << sbuffer.str();
 
 	for(const CIntObject *child : obj->children)
 		printInfoAboutIntObject(child, level+1);

+ 7 - 1
client/CMessage.cpp

@@ -232,11 +232,17 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
 			&& ret->text->slider;
 		i++)
 	{
-		ret->text->setBounds(sizes[i][0], sizes[i][1]);
+		ret->text->resize(Point(sizes[i][0], sizes[i][1]));
 	}
 
 	if(ret->text->slider)
+	{
 		ret->text->slider->addUsedEvents(CIntObject::WHEEL | CIntObject::KEYBOARD);
+	}
+	else
+	{
+		ret->text->resize(ret->text->label->textSize + Point(10, 10));
+	}
 
 	std::pair<int,int> winSize(ret->text->pos.w, ret->text->pos.h); //start with text size
 

+ 16 - 23
client/CPreGame.cpp

@@ -408,7 +408,7 @@ CreditsScreen::CreditsScreen()
 	size_t firstQuote = text.find('\"')+1;
 	text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote );
 	credits = new CTextBox(text, Rect(pos.w - 350, 600, 350, 32000), 0, FONT_CREDITS, CENTER, Colors::WHITE);
-	credits->pos.h = credits->maxH;
+	credits->pos.h = credits->label->textSize.y;
 }
 
 void CreditsScreen::showAll(SDL_Surface * to)
@@ -1291,7 +1291,7 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CM
 	case CMenuScreen::saveGame:;
 		if(saveGameName.empty())
 		{
-			txt->setTxt("NEWGAME");
+			txt->setText("NEWGAME");
 		}
 		else
 		{
@@ -1353,7 +1353,7 @@ void SelectionTab::select( int position )
 	{
 		std::string filename = *CResourceHandler::get()->getResourceName(
 								   ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
-		txt->setTxt(CFileInfo(filename).getBaseName());
+		txt->setText(CFileInfo(filename).getBaseName());
 	}
 
 	onSelect(curItems[py]);
@@ -1901,7 +1901,7 @@ CChatBox::CChatBox(const Rect &rect)
 	inputBox->removeUsedEvents(KEYBOARD);
 	chatHistory = new CTextBox("", Rect(0, 0, rect.w, rect.h - height), 1);
 
-	chatHistory->color = Colors::GREEN;
+	chatHistory->label->color = Colors::GREEN;
 }
 
 void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
@@ -1909,7 +1909,7 @@ void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
 	if(key.keysym.sym == SDLK_RETURN  &&  key.state == SDL_PRESSED  &&  inputBox->text.size())
 	{
 		SEL->postChatMessage(inputBox->text);
-		inputBox->setTxt("");
+		inputBox->setText("");
 	}
 	else
 		inputBox->keyPressed(key);
@@ -1917,7 +1917,7 @@ void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
 
 void CChatBox::addNewMessage(const std::string &text)
 {
-	chatHistory->setTxt(chatHistory->text + text + "\n");
+	chatHistory->setText(chatHistory->label->text + text + "\n");
 	if(chatHistory->slider)
 		chatHistory->slider->moveToMax();
 }
@@ -1964,8 +1964,6 @@ InfoCard::InfoCard( bool Network )
 		if(SEL->screenType != CMenuScreen::newGame)
 			difficulty->block(true);
 
-		//description needs bg
-		mapDescription->addChild(new CPicture(*bg, descriptionRect), true); //move subpicture bg to our description control (by default it's our (Infocard) child)
 
 		if(network)
 		{
@@ -2152,9 +2150,9 @@ void InfoCard::changeSelection( const CMapInfo *to )
 	if(to && mapDescription)
 	{
 		if (SEL->screenType == CMenuScreen::campaignList)
-			mapDescription->setTxt(to->campaignHeader->description);
+			mapDescription->setText(to->campaignHeader->description);
 		else
-			mapDescription->setTxt(to->mapHeader->description);
+			mapDescription->setText(to->mapHeader->description);
 
 		if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) {
 			difficulty->block(true);
@@ -2926,7 +2924,7 @@ OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings,
 void OptionsTab::SelectedBox::update()
 {
 	image->setFrame(getImageIndex());
-	subtitle->setTxt(getName());
+	subtitle->setText(getName());
 }
 
 void OptionsTab::SelectedBox::clickRight( tribool down, bool previousState )
@@ -3048,7 +3046,7 @@ CMultiMode::CMultiMode()
 
 	bar = new CGStatusBar(new CPicture(Rect(7, 465, 440, 18), 0));//226, 472
 	txt = new CTextInput(Rect(19, 436, 334, 16), *bg);
-	txt->setTxt(settings["general"]["playerName"].String()); //Player
+	txt->setText(settings["general"]["playerName"].String()); //Player
 
 	btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[266], boost::bind(&CMultiMode::openHotseat, this), 373, 78, "MUBHOT.DEF");
 	btns[1] = new CAdventureMapButton("Host TCP/IP game", "", boost::bind(&CMultiMode::hostTCP, this), 373, 78 + 57*1, "MUBHOST.DEF");
@@ -3097,7 +3095,7 @@ CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer)
 	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), 205, 338, "MUBCANC.DEF", SDLK_ESCAPE);
 	bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472
 
-	txt[0]->setTxt(firstPlayer, true);
+	txt[0]->setText(firstPlayer, true);
 	txt[0]->giveFocus();
 }
 
@@ -3331,7 +3329,7 @@ void CBonusSelection::selectMap( int whichOne, bool initialSelect )
 		sInfo.turnTime = 0;
 		sInfo.difficulty = ourCampaign->camp->scenarios[whichOne].difficulty;
 
-		mapDesc->setTxt(ourHeader->description);
+		mapDesc->setText(ourHeader->description);
 
 		updateBonusSelection();
 	}
@@ -3602,13 +3600,9 @@ void CBonusSelection::startMap()
 	if (scenario.prolog.hasPrologEpilog)
 	{
 		GH.pushInt(new CPrologEpilogVideo(scenario.prolog, exitCb));
-        logGlobal->infoStream() << "Video: " << scenario.prolog.prologVideo;
-        logGlobal->infoStream() << "Audio: " << scenario.prolog.prologMusic;
-        logGlobal->infoStream() << "Text:  " << scenario.prolog.prologText;
 	}
 	else
 	{
-        logGlobal->infoStream() << "Without prolog";
 		exitCb();
 	}
 }
@@ -3991,9 +3985,9 @@ void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousStat
 void CCampaignScreen::CCampaignButton::hover(bool on)
 {
 	if (on)
-		hoverLabel->setTxt(hoverText); // Shows the name of the campaign when you get into the bounds of the button
+		hoverLabel->setText(hoverText); // Shows the name of the campaign when you get into the bounds of the button
 	else
-		hoverLabel->setTxt(" ");
+		hoverLabel->setText(" ");
 }
 
 void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
@@ -4111,7 +4105,6 @@ CPrologEpilogVideo::CPrologEpilogVideo( CCampaignScenario::SScenarioPrologEpilog
 	txt = CSDL_Ext::newSurface(500, 20 * lines.size() + 10);
 	curTxtH = screen->h;
 	graphics->fonts[FONT_BIG]->renderTextLinesCenter(txt, lines, Colors::METALLIC_GOLD, Point(txt->w/2, txt->h/2 + 5));
-	//SDL_SaveBMP(txt, "txtsrfc.bmp");
 }
 
 void CPrologEpilogVideo::show( SDL_Surface * to )
@@ -4160,8 +4153,8 @@ CSimpleJoinScreen::CSimpleJoinScreen()
 	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), 142, 142, "MUBCANC.DEF", SDLK_ESCAPE);
 	bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0));
 
-	port->setTxt(boost::lexical_cast<std::string>(settings["server"]["port"].Float()), true);
-	address->setTxt(settings["server"]["server"].String(), true);
+	port->setText(boost::lexical_cast<std::string>(settings["server"]["port"].Float()), true);
+	address->setText(settings["server"]["server"].String(), true);
 	address->giveFocus();
 }
 

+ 3 - 3
client/CQuestLog.cpp

@@ -43,7 +43,7 @@ void CQuestLabel::clickLeft(tribool down, bool previousState)
 void CQuestLabel::showAll(SDL_Surface * to)
 {
 	if (active)
-		CBoundedLabel::showAll (to);
+		CMultiLineLabel::showAll (to);
 }
 
 CQuestIcon::CQuestIcon (const std::string &defname, int index, int x, int y) :
@@ -144,7 +144,7 @@ void CQuestLog::init()
 			text.addReplacement (quests[i].obj->getHoverText()); //get name of the object
 		CQuestLabel * label = new CQuestLabel (28, 199 + i * 24, FONT_SMALL, TOPLEFT, Colors::WHITE, text.toString());
 		label->callback = boost::bind(&CQuestLog::selectQuest, this, i);
-		label->setBounds (172, 30);
+		label->setVisibleSize (Rect(0, 0, 172, 30));
 		labels.push_back(label);
 	}
 
@@ -192,7 +192,7 @@ void CQuestLog::selectQuest (int which)
 	MetaString text;
 	std::vector<Component> components; //TODO: display them
 	currentQuest->quest->getVisitText (text, components , currentQuest->quest->isCustomFirst, true);
-	description->setTxt (text.toString()); //TODO: use special log entry text
+	description->setText (text.toString()); //TODO: use special log entry text
 	redraw();
 }
 

+ 2 - 2
client/CQuestLog.h

@@ -33,13 +33,13 @@ extern CAdvMapInt *adventureInt;
 
 const int QUEST_COUNT = 9;
 
-class CQuestLabel : public LRClickableAreaWText, public CBoundedLabel
+class CQuestLabel : public LRClickableAreaWText, public CMultiLineLabel
 {
 public:
 	std::function<void()> callback;
 
 	CQuestLabel (int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "")
-		: CBoundedLabel (x, y, FONT_SMALL, TOPLEFT, Colors::WHITE, Text){};
+		: CMultiLineLabel (x, y, FONT_SMALL, TOPLEFT, Colors::WHITE, Text){};
 	void clickLeft(tribool down, bool previousState);
 	void showAll(SDL_Surface * to);
 };

+ 12 - 12
client/CSpellWindow.cpp

@@ -159,29 +159,29 @@ CSpellWindow::CSpellWindow(const SDL_Rect &, const CGHeroInstance * _myHero, CPl
 
 	statusBar = new CGStatusBar(7 + pos.x, 569 + pos.y, "Spelroll.bmp");
 	SDL_Rect temp_rect = genRect(45, 35, 479 + pos.x, 405 + pos.y);
-	exitBtn = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fexitb, this), CGI->generaltexth->zelp[460].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[460].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	exitBtn = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fexitb, this), CGI->generaltexth->zelp[460].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[460].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(45, 35, 221 + pos.x, 405 + pos.y);
-	battleSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fbattleSpellsb, this), CGI->generaltexth->zelp[453].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[453].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	battleSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fbattleSpellsb, this), CGI->generaltexth->zelp[453].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[453].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(45, 35, 355 + pos.x, 405 + pos.y);
-	adventureSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fadvSpellsb, this), CGI->generaltexth->zelp[452].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[452].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	adventureSpells = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fadvSpellsb, this), CGI->generaltexth->zelp[452].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[452].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(45, 35, 418 + pos.x, 405 + pos.y);
-	manaPoints = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fmanaPtsb, this), CGI->generaltexth->zelp[459].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[459].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	manaPoints = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fmanaPtsb, this), CGI->generaltexth->zelp[459].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[459].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 
 	temp_rect = genRect(36, 56, 549 + pos.x, 94 + pos.y);
-	selectSpellsA = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 0), CGI->generaltexth->zelp[454].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[454].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	selectSpellsA = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 0), CGI->generaltexth->zelp[454].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[454].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(36, 56, 549 + pos.x, 151 + pos.y);
-	selectSpellsE = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 3), CGI->generaltexth->zelp[457].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[457].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	selectSpellsE = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 3), CGI->generaltexth->zelp[457].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[457].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(36, 56, 549 + pos.x, 210 + pos.y);
-	selectSpellsF = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 1), CGI->generaltexth->zelp[455].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[455].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	selectSpellsF = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 1), CGI->generaltexth->zelp[455].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[455].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(36, 56, 549 + pos.x, 270 + pos.y);
-	selectSpellsW = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 2), CGI->generaltexth->zelp[456].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[456].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	selectSpellsW = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 2), CGI->generaltexth->zelp[456].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[456].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(36, 56, 549 + pos.x, 330 + pos.y);
-	selectSpellsAll = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 4), CGI->generaltexth->zelp[458].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[458].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	selectSpellsAll = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::selectSchool, this, 4), CGI->generaltexth->zelp[458].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[458].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 
 	temp_rect = genRect(leftCorner->h, leftCorner->w, 97 + pos.x, 77 + pos.y);
-	lCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fLcornerb, this), CGI->generaltexth->zelp[450].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[450].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	lCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fLcornerb, this), CGI->generaltexth->zelp[450].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[450].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 	temp_rect = genRect(rightCorner->h, rightCorner->w, 487 + pos.x, 72 + pos.y);
-	rCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fRcornerb, this), CGI->generaltexth->zelp[451].second, boost::bind(&CGStatusBar::print, statusBar, (CGI->generaltexth->zelp[451].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
+	rCorner = new SpellbookInteractiveArea(temp_rect, boost::bind(&CSpellWindow::fRcornerb, this), CGI->generaltexth->zelp[451].second, boost::bind(&CGStatusBar::setText, statusBar, (CGI->generaltexth->zelp[451].first)), boost::bind(&CGStatusBar::clear, statusBar), myInt);
 
 	//areas for spells
 	int xpos = 117 + pos.x, ypos = 90 + pos.y;
@@ -813,7 +813,7 @@ void CSpellWindow::SpellArea::hover(bool on)
 		{
 			std::ostringstream ss;
 			ss<<CGI->spellh->spells[mySpell]->name<<" ("<<CGI->generaltexth->allTexts[171+CGI->spellh->spells[mySpell]->level]<<")";
-			owner->statusBar->print(ss.str());
+			owner->statusBar->setText(ss.str());
 		}
 		else
 		{

+ 38 - 41
client/GUIClasses.cpp

@@ -282,7 +282,7 @@ void CGarrisonSlot::hover (bool on)
 				temp = CGI->generaltexth->tcommands[11]; //Empty
 			}
 		}
-		GH.statusbar->print(temp);
+		GH.statusbar->setText(temp);
 	}
 	else
 	{
@@ -464,7 +464,7 @@ void CGarrisonSlot::update()
 		creatureImage->setFrame(creature->iconIndex);
 
 		stackCount->enable();
-		stackCount->setTxt(boost::lexical_cast<std::string>(myStack->count));
+		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
 	}
 	else
 	{
@@ -510,7 +510,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int
 	if (!creature)
 		stackCount->disable();
 	else
-		stackCount->setTxt(boost::lexical_cast<std::string>(myStack->count));
+		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
 }
 
 void CGarrisonInt::addSplitBtn(CAdventureMapButton * button)
@@ -667,8 +667,7 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
 	text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE);
 	if(!text->slider)
 	{
-		text->pos.w = text->maxW;
-		text->pos.h = text->maxH;
+		text->resize(text->label->textSize);
 	}
 
 	if(buttons.size())
@@ -1397,7 +1396,7 @@ void CRecruitmentWindow::CCostBox::set(TResources res)
 	//just update values
 	for(auto & item : resources)
 	{
-		item.second.first->setTxt(boost::lexical_cast<std::string>(res[item.first]));
+		item.second.first->setText(boost::lexical_cast<std::string>(res[item.first]));
 	}
 }
 
@@ -1469,7 +1468,7 @@ void CRecruitmentWindow::select(CCreatureCard *card)
 		totalCostValue->set(card->creature->cost * maxAmount);
 
 		//Recruit %s
-		title->setTxt(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl));
+		title->setText(boost::str(boost::format(CGI->generaltexth->tcommands[21]) % card->creature->namePl));
 
 		maxButton->block(maxAmount == 0);
 		slider->block(maxAmount == 0);
@@ -1626,8 +1625,8 @@ void CRecruitmentWindow::sliderMoved(int to)
 		return;
 
 	buyButton->block(!to);
-	availableValue->setTxt(boost::lexical_cast<std::string>(selected->amount - to));
-	toRecruitValue->setTxt(boost::lexical_cast<std::string>(to));
+	availableValue->setText(boost::lexical_cast<std::string>(selected->amount - to));
+	toRecruitValue->setText(boost::lexical_cast<std::string>(to));
 
 	totalCostValue->set(selected->creature->cost * to);
 }
@@ -1660,8 +1659,8 @@ CSplitWindow::CSplitWindow(const CCreature * creature, std::function<void(int, i
 	leftInput->filters.add(boost::bind(&CTextInput::numberFilter, _1, _2, leftMin, leftMax));
 	rightInput->filters.add(boost::bind(&CTextInput::numberFilter, _1, _2, rightMin, rightMax));
 
-	leftInput->setTxt(boost::lexical_cast<std::string>(leftAmount), false);
-	rightInput->setTxt(boost::lexical_cast<std::string>(rightAmount), false);
+	leftInput->setText(boost::lexical_cast<std::string>(leftAmount), false);
+	rightInput->setText(boost::lexical_cast<std::string>(rightAmount), false);
 
 	animLeft = new CCreaturePic(20, 54, creature, true, false);
 	animRight = new CCreaturePic(177, 54,creature, true, false);
@@ -1691,8 +1690,8 @@ void CSplitWindow::setAmount(int value, bool left)
 	leftAmount  = left ? value : total - value;
 	rightAmount = left ? total - value : value;
 
-	leftInput->setTxt(boost::lexical_cast<std::string>(leftAmount));
-	rightInput->setTxt(boost::lexical_cast<std::string>(rightAmount));
+	leftInput->setText(boost::lexical_cast<std::string>(leftAmount));
+	rightInput->setText(boost::lexical_cast<std::string>(rightAmount));
 }
 
 void CSplitWindow::apply()
@@ -2157,13 +2156,13 @@ void CTradeWindow::CTradeableItem::hover(bool on)
 	{
 	case CREATURE:
 	case CREATURE_PLACEHOLDER:
-		GH.statusbar->print(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl));
+		GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->creatures[id]->namePl));
 		break;
 	case ARTIFACT_PLACEHOLDER:
 		if(id < 0)
-			GH.statusbar->print(CGI->generaltexth->zelp[582].first);
+			GH.statusbar->setText(CGI->generaltexth->zelp[582].first);
 		else
-			GH.statusbar->print(CGI->arth->artifacts[id]->Name());
+			GH.statusbar->setText(CGI->arth->artifacts[id]->Name());
 		break;
 	}
 }
@@ -2964,27 +2963,27 @@ void CMarketplaceWindow::updateTraderText()
 		if(mode == EMarketMode::RESOURCE_PLAYER)
 		{
 			//I can give %s to the %s player.
-			traderText->setTxt(boost::str(boost::format(CGI->generaltexth->allTexts[165]) % hLeft->getName() % hRight->getName()));
+			traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[165]) % hLeft->getName() % hRight->getName()));
 		}
 		else if(mode == EMarketMode::RESOURCE_ARTIFACT)
 		{
 			//I can offer you the %s for %d %s of %s.
-			traderText->setTxt(boost::str(boost::format(CGI->generaltexth->allTexts[267]) % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName()));
+			traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[267]) % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName()));
 		}
 		else if(mode == EMarketMode::RESOURCE_RESOURCE)
 		{
 			//I can offer you %d %s of %s for %d %s of %s.
-			traderText->setTxt(boost::str(boost::format(CGI->generaltexth->allTexts[157]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName()));
+			traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[157]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % CGI->generaltexth->allTexts[160 + (r1==1)] % hLeft->getName()));
 		}
 		else if(mode == EMarketMode::CREATURE_RESOURCE)
 		{
 			//I can offer you %d %s of %s for %d %s.
-			traderText->setTxt(boost::str(boost::format(CGI->generaltexth->allTexts[269]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % hLeft->getName(r1)));
+			traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[269]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % r1 % hLeft->getName(r1)));
 		}
 		else if(mode == EMarketMode::ARTIFACT_RESOURCE)
 		{
 			//I can offer you %d %s of %s for your %s.
-			traderText->setTxt(boost::str(boost::format(CGI->generaltexth->allTexts[268]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % hLeft->getName(r1)));
+			traderText->setText(boost::str(boost::format(CGI->generaltexth->allTexts[268]) % r2 % CGI->generaltexth->allTexts[160 + (r2==1)] % hRight->getName() % hLeft->getName(r1)));
 		}
 		return;
 	}
@@ -3004,7 +3003,7 @@ void CMarketplaceWindow::updateTraderText()
 		else
 			gnrtxtnr = 163; //Please inspect our fine wares.  If you feel like offering a trade, click on the items you wish to trade with and for.
 	}
-	traderText->setTxt(CGI->generaltexth->allTexts[gnrtxtnr]);
+	traderText->setText(CGI->generaltexth->allTexts[gnrtxtnr]);
 }
 
 CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*= nullptr*/, EMarketMode::EMarketMode Mode)
@@ -3297,12 +3296,12 @@ void CAltarWindow::calcTotalExp()
 		}
 	}
 	val = hero->calculateXp(val);
-	expOnAltar->setTxt(boost::lexical_cast<std::string>(val));
+	expOnAltar->setText(boost::lexical_cast<std::string>(val));
 }
 
 void CAltarWindow::setExpToLevel()
 {
-	expToLevel->setTxt(boost::lexical_cast<std::string>(CGI->heroh->reqExp(CGI->heroh->level(hero->exp)+1) - hero->exp));
+	expToLevel->setText(boost::lexical_cast<std::string>(CGI->heroh->reqExp(CGI->heroh->level(hero->exp)+1) - hero->exp));
 }
 
 void CAltarWindow::blockTrade()
@@ -3609,7 +3608,7 @@ void CSystemOptionsWindow::setGameRes(int index)
 	resText += boost::lexical_cast<std::string>(iter->first.first);
 	resText += "x";
 	resText += boost::lexical_cast<std::string>(iter->first.second);
-	gameResLabel->setTxt(resText);
+	gameResLabel->setText(resText);
 }
 
 void CSystemOptionsWindow::toggleReminder(bool on)
@@ -3814,7 +3813,7 @@ void CTavernWindow::HeroPortrait::hover( bool on )
 {
 	//Hoverable::hover(on);
 	if(on)
-		GH.statusbar->print(hoverName);
+		GH.statusbar->setText(hoverName);
 	else
 		GH.statusbar->clear();
 }
@@ -3977,12 +3976,11 @@ void CInGameConsole::startEnteringText()
 	enteredText = "_";
 	if(GH.topInt() == adventureInt)
 	{
-        GH.statusbar->alignment = TOPLEFT;
-        GH.statusbar->calcOffset();
-		GH.statusbar->print(enteredText);
+		GH.statusbar->alignment = TOPLEFT;
+		GH.statusbar->setText(enteredText);
 
-        //Prevent changes to the text from mouse interaction with the adventure map
-        GH.statusbar->lock(true);
+		//Prevent changes to the text from mouse interaction with the adventure map
+		GH.statusbar->lock(true);
 	}
 	else if(LOCPLINT->battleInt)
 	{
@@ -4003,10 +4001,9 @@ void CInGameConsole::endEnteringText(bool printEnteredText)
 	enteredText = "";
 	if(GH.topInt() == adventureInt)
 	{
-        GH.statusbar->alignment = CENTER;
-        GH.statusbar->calcOffset();
-        GH.statusbar->lock(false);
-        GH.statusbar->clear();
+		GH.statusbar->alignment = CENTER;
+		GH.statusbar->lock(false);
+		GH.statusbar->clear();
 	}
 	else if(LOCPLINT->battleInt)
 	{
@@ -4018,10 +4015,10 @@ void CInGameConsole::refreshEnteredText()
 {
 	if(GH.topInt() == adventureInt)
 	{
-        GH.statusbar->lock(false);
-        GH.statusbar->clear();
-		GH.statusbar->print(enteredText);
-        GH.statusbar->lock(true);
+		GH.statusbar->lock(false);
+		GH.statusbar->clear();
+		GH.statusbar->setText(enteredText);
+		GH.statusbar->lock(true);
 	}
 	else if(LOCPLINT->battleInt)
 	{
@@ -4521,7 +4518,7 @@ void CHeroArea::clickRight(tribool down, bool previousState)
 void CHeroArea::hover(bool on)
 {
 	if (on && hero)
-		GH.statusbar->print(hero->hoverName);
+		GH.statusbar->setText(hero->hoverName);
 	else
 		GH.statusbar->clear();
 }
@@ -5438,7 +5435,7 @@ void CUniversityWindow::CItem::clickRight(tribool down, bool previousState)
 void CUniversityWindow::CItem::hover(bool on)
 {
 	if (on)
-		GH.statusbar->print(CGI->generaltexth->skillName[ID]);
+		GH.statusbar->setText(CGI->generaltexth->skillName[ID]);
 	else
 		GH.statusbar->clear();
 }

+ 163 - 150
client/gui/CIntObjectClasses.cpp

@@ -376,8 +376,8 @@ void CAdventureMapButton::hover (bool on)
 		else if(GH.statusbar) //for other buttons
 		{
 			if (on)
-				GH.statusbar->print(*name);
-			else if ( GH.statusbar->getCurrent()==(*name) )
+				GH.statusbar->setText(*name);
+			else if ( GH.statusbar->getText()==(*name) )
 				GH.statusbar->clear();
 		}
 	}
@@ -725,8 +725,12 @@ CSlider::~CSlider()
 
 }
 
-CSlider::CSlider(int x, int y, int totalw, std::function<void(int)> Moved, int Capacity, int Amount, int Value, bool Horizontal, int style)
-:capacity(Capacity),amount(Amount),horizontal(Horizontal), moved(Moved)
+CSlider::CSlider(int x, int y, int totalw, std::function<void(int)> Moved, int Capacity, int Amount, int Value, bool Horizontal, int style):
+    capacity(Capacity),
+    amount(Amount),
+    scrollStep(1),
+    horizontal(Horizontal),
+    moved(Moved)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	setAmount(amount);
@@ -827,7 +831,7 @@ void CSlider::showAll(SDL_Surface * to)
 
 void CSlider::wheelScrolled(bool down, bool in)
 {
-	moveTo(value + 3 * (down ? +1 : -1));
+	moveTo(value + 3 * (down ? +scrollStep : -scrollStep));
 }
 
 void CSlider::keyPressed(const SDL_KeyboardEvent & key)
@@ -839,17 +843,17 @@ void CSlider::keyPressed(const SDL_KeyboardEvent & key)
 	{
 	case SDLK_UP:
 	case SDLK_LEFT:
-		moveDest = value - 1;
+		moveDest = value - scrollStep;
 		break;
 	case SDLK_DOWN:
 	case SDLK_RIGHT:
-		moveDest = value + 1;
+		moveDest = value + scrollStep;
 		break;
 	case SDLK_PAGEUP:
-		moveDest = value - capacity + 1;
+		moveDest = value - capacity + scrollStep;
 		break;
 	case SDLK_PAGEDOWN:
-		moveDest = value + capacity - 1;
+		moveDest = value + capacity - scrollStep;
 		break;
 	case SDLK_HOME:
 		moveDest = 0;
@@ -1104,8 +1108,8 @@ CSimpleWindow::~CSimpleWindow()
 void CHoverableArea::hover (bool on)
 {
 	if (on)
-		GH.statusbar->print(hoverText);
-	else if (GH.statusbar->getCurrent()==hoverText)
+		GH.statusbar->setText(hoverText);
+	else if (GH.statusbar->getText()==hoverText)
 		GH.statusbar->clear();
 }
 
@@ -1156,24 +1160,20 @@ void CLabel::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
 
-	std::string toPrint = visibleText();
-	if(!toPrint.length())
-		return;
-
-	//blitLine(to, pos.topLeft()/2 + pos.bottomRight()/2, toPrint);
-    blitLine(to, pos.topLeft() + textOffset, toPrint);
+	if(!text.empty())
+		blitLine(to, pos, text);
 
 }
 
 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;
-	ignoreLeadingWhitespace = false;
 
 	if (alignment == TOPLEFT) // causes issues for MIDDLE
 	{
@@ -1182,15 +1182,17 @@ CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, con
 	}
 }
 
-std::string CLabel::visibleText()
+Point CLabel::getBorderSize()
 {
-	std::string ret = text;
-	if(ignoreLeadingWhitespace)
-		boost::trim_left(ret);
-	return ret;
+	return Point(0, 0);
 }
 
-void CLabel::setTxt(const std::string &Txt)
+std::string CLabel::getText()
+{
+	return text;
+}
+
+void CLabel::setText(const std::string &Txt)
 {
 	text = Txt;
 	if(autoRedraw)
@@ -1202,33 +1204,60 @@ void CLabel::setTxt(const std::string &Txt)
 	}
 }
 
-void CBoundedLabel::setBounds(int limitW, int limitH)
+CMultiLineLabel::CMultiLineLabel(int x, int y, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text):
+    CLabel(x, y, Font, Align, Color, Text),
+    visibleSize(0, 0, 0, 0)
 {
-	pos.h = limitH;
-	pos.w = limitW;
-	recalculateLines(text);
+	splitText(Text);
+}
+
+void CMultiLineLabel::setVisibleSize(Rect visibleSize)
+{
+	this->visibleSize = visibleSize;
+	redraw();
 }
 
-void CBoundedLabel::setTxt(const std::string &Txt)
+void CMultiLineLabel::scrollTextBy(int distance)
 {
-	recalculateLines(Txt);
-	CLabel::setTxt(Txt);
+	scrollTextTo(visibleSize.y + distance);
 }
 
-void CTextContainer::blitLine(SDL_Surface *to, Point where, std::string what)
+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 -= f->getStringWidth(what) / 2;
-		where.y -= f->getLineHeight() / 2;
+		where.x += (destRect.w - f->getStringWidth(what)) / 2;
+		where.y += (destRect.h - f->getLineHeight()) / 2;
 	}
 
 	if (alignment == BOTTOMRIGHT)
 	{
-		where.x -= f->getStringWidth(what);
-		where.y -= f->getLineHeight();
+		where.x += getBorderSize().x + destRect.w - f->getStringWidth(what);
+		where.y += getBorderSize().y + destRect.h - f->getLineHeight();
 	}
 
 	size_t begin = 0;
@@ -1245,11 +1274,11 @@ void CTextContainer::blitLine(SDL_Surface *to, Point where, std::string what)
 
 			if (currDelimeter % 2) // Enclosed in {} text - set to yellow
 				f->renderTextLeft(to, toPrint, Colors::YELLOW, where);
-			else // Non-enclosed text
+			else // Non-enclosed text, use default color
 				f->renderTextLeft(to, toPrint, color, where);
 			begin = end;
 
-			where.x += f->getStringWidth(toPrint);
+			destRect.x += f->getStringWidth(toPrint);
 		}
 		currDelimeter++;
 	}
@@ -1262,36 +1291,33 @@ CTextContainer::CTextContainer(EAlignment alignment, EFonts font, SDL_Color colo
 	color(color)
 {}
 
-void CBoundedLabel::showAll(SDL_Surface * to)
+void CMultiLineLabel::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
 
 	const IFont * f = graphics->fonts[font];
-	int lineHeight =  f->getLineHeight();
-	int lineCapacity = pos.h / lineHeight;
 
-	int dy = f->getLineHeight(); //line height
-	int base_y = pos.y;
-	if(alignment == CENTER)
-		base_y += std::max((pos.h - maxH)/2,0);
+	// calculate which lines should be visible
+	size_t totalLines = lines.size();
+	size_t beginLine  = visibleSize.y / f->getLineHeight();
+	size_t endLine    = (getTextLocation().h + visibleSize.y) / f->getLineHeight() + 1;
 
-	for (int i = 0; i < lineCapacity; i++)
-	{
-		const std::string &line = lines[i];
-		if ( !(lines.size() && line.size())) //empty message or empty line
-			continue;
+	// and where they should be displayed
+	Point lineStart = getTextLocation().topLeft() - visibleSize + Point(0, beginLine * f->getLineHeight());
+	Point lineSize  = Point(getTextLocation().w, f->getLineHeight());
 
-		int x = pos.x;
-		if(alignment == CENTER)
-		{
-			x += pos.w - f->getStringWidth(line.c_str()) / 2;
-		}
+	CSDL_Ext::CClipRectGuard guard(to, getTextLocation()); // to properly trim text that is too big to fit
+
+	for (size_t i = beginLine; i < std::min(totalLines, endLine); i++)
+	{
+		if (!lines[i].empty()) //non-empty line
+			blitLine(to, Rect(lineStart, lineSize), lines[i]);
 
-		blitLine(to, Point(x, base_y + i * dy), line);
+		lineStart.y += f->getLineHeight();
 	}
 }
 
-void CBoundedLabel::recalculateLines(const std::string &Txt)
+void CMultiLineLabel::splitText(const std::string &Txt)
 {
 	lines.clear();
 
@@ -1300,10 +1326,32 @@ void CBoundedLabel::recalculateLines(const std::string &Txt)
 
 	lines = CMessage::breakText(Txt, pos.w, font);
 
-	maxH = lineHeight * lines.size();
-	maxW = 0;
+	 textSize.y = lineHeight * lines.size();
+	 textSize.x = 0;
 	for(const std::string &line : lines)
-		vstd::amax(maxW, f->getStringWidth(line.c_str()));
+		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):
@@ -1316,104 +1364,72 @@ void CLabelGroup::add(int x, int y, const std::string &text)
 	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*/)
-:CBoundedLabel(rect.x, rect.y, Font, Align, Color, Text), sliderStyle(SliderStyle), slider(nullptr)
+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.x, rect.y, Font, Align, Color);
+
 	type |= REDRAW_PARENT;
-	autoRedraw = false;
-	pos.h = rect.h;
-	pos.w = rect.w;
-	assert(Align == TOPLEFT || Align == CENTER); //TODO: support for other alignments
+	label->pos.h = pos.h = rect.h;
+	label->pos.w = pos.w = rect.w;
+
 	assert(pos.w >= 40); //we need some space
-	setTxt(Text);
+	setText(Text);
 }
 
-void CTextBox::recalculateLines(const std::string &Txt)
+void CTextBox::sliderMoved(int to)
 {
-	//TODO: merge with CBoundedlabel::recalculateLines
-
-	vstd::clear_pointer(slider);
-	lines.clear();
-	const IFont * f = graphics->fonts[font];
-	int lineHeight =  f->getLineHeight();
-	int lineCapacity = pos.h / lineHeight;
-
-	lines = CMessage::breakText(Txt, pos.w, font);
-	if (lines.size() > lineCapacity) //we need to add a slider
-	{
-		lines = CMessage::breakText(Txt, pos.w - 32 - 10, font);
-		OBJ_CONSTRUCTION_CAPTURING_ALL;
-		slider = new CSlider(pos.w - 32, 0, pos.h, boost::bind(&CTextBox::sliderMoved, this, _1), lineCapacity, lines.size(), 0, false, sliderStyle);
-		if(active)
-			slider->activate();
-	}
-
-	maxH = lineHeight * lines.size();
-	maxW = 0;
-	for(const std::string &line : lines)
-		vstd::amax(maxW, f->getStringWidth(line));
+	label->scrollTextTo(to);
 }
 
-void CTextBox::showAll(SDL_Surface * to)
+void CTextBox::resize(Point newSize)
 {
-	CIntObject::showAll(to);
-
-	const IFont * f = graphics->fonts[font];
-	int dy = f->getLineHeight(); //line height
-	int base_y = pos.y;
-
-	if (alignment == CENTER)
-		base_y += std::max((pos.h - maxH)/2,0);
-
-	int howManyLinesToPrint = slider ? slider->capacity : lines.size();
-	int firstLineToPrint = slider ? slider->value : 0;
-
-	for (int i = 0; i < howManyLinesToPrint; i++)
-	{
-		const std::string &line = lines[i + firstLineToPrint];
-		if(!line.size()) continue;
-
-		int width = pos.w + (slider ? (slider->pos.w) : 0);
-		int x = pos.x + int(alignment) * width / 2;
-
-		blitLine(to, Point(x, base_y + i * dy), line);
-	}
+	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::sliderMoved(int to)
+void CTextBox::setText(const std::string &text)
 {
-	if(!slider)
-		return;
+	label->setText(text);
+	if (label->textSize.y <= label->pos.h && slider)
+	{
+		// slider is no longer needed
+		vstd::clear_pointer(slider);
+		label->pos.w = pos.w;
+		label->setText(text);
+	}
+	else if (label->textSize.y > label->pos.h && !slider)
+	{
+		// create slider and update widget
+		label->pos.w = pos.w - 32;
+		label->setText(text);
 
-	redraw();
+		OBJ_CONSTRUCTION_CAPTURING_ALL;
+		slider = new CSlider(pos.w - 32, 0, pos.h, boost::bind(&CTextBox::sliderMoved, this, _1),
+		                     label->pos.h, label->textSize.y, 0, false, sliderStyle);
+		slider->scrollStep = graphics->fonts[label->font]->getLineHeight();
+	}
 }
 
-const Point CGStatusBar::edgeOffset = Point(5, 1);
-
-void CGStatusBar::print(const std::string & Text)
+void CGStatusBar::setText(const std::string & Text)
 {
-    if(!textLock)
-	    setTxt(Text);
+	if(!textLock)
+		CLabel::setText(Text);
 }
 
 void CGStatusBar::clear()
 {
-    if(!textLock)
-	    setTxt("");
-}
-
-std::string CGStatusBar::getCurrent()
-{
-	return text;
+	setText("");
 }
 
-//CGStatusBar::CGStatusBar(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, const SDL_Color &Color /*= Colors::WHITE*/, const std::string &Text /*= ""*/)
-//: CLabel(x, y, Font, Align, Color, Text)
-//{
-//	init();
-//}
-
 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, "")
 {
@@ -1421,7 +1437,7 @@ CGStatusBar::CGStatusBar(CPicture *BG, EFonts Font /*= FONT_SMALL*/, EAlignment
 	bg = BG;
 	addChild(bg);
 	pos = bg->pos;
-	calcOffset();
+	getBorderSize();
     textLock = false;
 }
 
@@ -1437,7 +1453,6 @@ CGStatusBar::CGStatusBar(int x, int y, std::string name/*="ADROLLVR.bmp"*/, int
 		vstd::amin(pos.w, maxw);
 		bg->srcRect = new Rect(0, 0, maxw, pos.h);
 	}
-	calcOffset();
     textLock = false;
 }
 
@@ -1457,20 +1472,19 @@ void CGStatusBar::init()
 	GH.statusbar = this;
 }
 
-void CGStatusBar::calcOffset()
+Point CGStatusBar::getBorderSize()
 {
+	//Width of borders where text should not be printed
+	static const Point borderSize(5,1);
+
 	switch(alignment)
 	{
-	case TOPLEFT:
-		textOffset = Point(edgeOffset.x, edgeOffset.y);
-		break;
-	case CENTER:
-		textOffset = Point(pos.w/2, pos.h/2);
-		break;
-	case BOTTOMRIGHT:
-		textOffset = Point(pos.w - edgeOffset.x, pos.h - edgeOffset.y);
-		break;
+	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)
@@ -1486,7 +1500,6 @@ CTextInput::CTextInput(const Rect &Pos, EFonts font, const CFunctionList<void(co
 	focus = false;
 	pos.h = Pos.h;
 	pos.w = Pos.w;
-	textOffset = Point(pos.w/2, pos.h/2);
 	captureAllKeys = true;
 	bg = nullptr;
 	addUsedEvents(LCLICK | KEYBOARD);
@@ -1570,9 +1583,9 @@ void CTextInput::keyPressed( const SDL_KeyboardEvent & key )
 	}
 }
 
-void CTextInput::setTxt( const std::string &nText, bool callCb )
+void CTextInput::setText( const std::string &nText, bool callCb )
 {
-	CLabel::setTxt(nText);
+	CLabel::setText(nText);
 	if(callCb)
 		cb(text);
 }

+ 72 - 59
client/gui/CIntObjectClasses.h

@@ -192,10 +192,11 @@ class CSlider : public CIntObject
 {
 public:
 	CAdventureMapButton *left, *right, *slider; //if vertical then left=up
-	int capacity,//how many elements can be active at same time
-		amount, //how many elements
-		positions, //number of highest position (0 if there is only one)
-		value; //first active element
+	int capacity;//how many elements can be active at same time (e.g. hero list = 5)
+	int amount; //total amount of elements (e.g. hero list = 0-8)
+	int positions; //number of highest position (0 if there is only one)
+	int value; //first active element
+	int scrollStep; // how many elements will be scrolled via one click, default = 1
 	bool horizontal;
 	bool wheelScrolling;
 	bool keyScrolling;
@@ -279,7 +280,7 @@ public:
 	//Pos - position of first item
 	//ItemOffset - distance between items in the list
 	//VisibleSize - maximal number of displayable at once items
-	//TotalSize 
+	//TotalSize
 	//Slider - slider style, bit field: 1 = present(disabled), 2=horisontal(vertical), 4=blue(brown)
 	//SliderPos - position of slider, if present
 	CListBox(CreateFunc create, DestroyFunc destroy, Point Pos, Point ItemOffset, size_t VisibleSize,
@@ -312,109 +313,121 @@ public:
 	size_t getPos();
 };
 
+/// Small helper class to manage group of similar labels
+class CLabelGroup : public CIntObject
+{
+	std::list<CLabel*> labels;
+	EFonts font;
+	EAlignment align;
+	SDL_Color color;
+public:
+	CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
+	void add(int x=0, int y=0, const std::string &text =  "");
+};
+
+/// Base class for all text-related widgets.
+/// Controls text blitting-related options
 class CTextContainer : public virtual CIntObject
 {
 protected:
-	void blitLine(SDL_Surface * to, Point where, std::string what);
+	/// returns size of border, for left- or right-aligned text
+	virtual Point getBorderSize() = 0;
+	/// do actual blitting of line. Text "what" will be placed at "where" and aligned according to alignment
+	void blitLine(SDL_Surface * to, Rect where, std::string what);
 
 	CTextContainer(EAlignment alignment, EFonts font, SDL_Color color);
-	//CTextContainer() {};
 
 public:
 	EAlignment alignment;
 	EFonts font;
-	SDL_Color color;
+	SDL_Color color; // default font color. Can be overriden by placing "{}" into the string
 };
 
 /// Label which shows text
 class CLabel : public CTextContainer
 {
 protected:
-	virtual std::string visibleText();
+	Point getBorderSize() override;
 
+	CPicture *bg;
 public:
+
 	std::string text;
-	CPicture *bg;
 	bool autoRedraw;  //whether control will redraw itself on setTxt
-	Point textOffset; //text will be blitted at pos + textOffset with appropriate alignment
-	bool ignoreLeadingWhitespace; 
 
-	virtual void setTxt(const std::string &Txt);
+	std::string getText();
+	virtual void setText(const std::string &Txt);
+
+	CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT,
+	       const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
 	void showAll(SDL_Surface * to); //shows statusbar (with current text)
-    CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
 };
 
-class CBoundedLabel : public CLabel
+/// Multi-line label that can display multiple lines of text
+/// If text is too big to fit into requested area remaining part will not be visible
+class CMultiLineLabel : public CLabel
 {
-public:
+	// text to blit, split into lines that are no longer than widget width
+	std::vector<std::string> lines;
 
-	int maxW; //longest line of text in px
-	int maxH; //total height needed to print all lines
+	// area of text that actually will be printed, default is widget size
+	Rect visibleSize;
 
-	std::vector<std::string> lines;
+	void splitText(const std::string &Txt);
+	Rect getTextLocation();
+public:
+	// total size of text, x = longest line of text, y = total height of lines
+	Point textSize;
 
-    CBoundedLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "")
-		: CLabel (x, y, Font, Align, Color, Text){};
-	void setTxt(const std::string &Txt);
-	void setBounds(int limitW, int limitH);
-	virtual void recalculateLines(const std::string &Txt);
+	CMultiLineLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
+
+	void setText(const std::string &Txt);
 	void showAll(SDL_Surface * to);
-};
 
-//Small helper class to manage group of similar labels 
-class CLabelGroup : public CIntObject
-{
-	std::list<CLabel*> labels;
-	EFonts font;
-	EAlignment align;
-	const SDL_Color &color;
-public:
-    CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
-	void add(int x=0, int y=0, const std::string &text =  "");
+	void setVisibleSize(Rect visibleSize);
+	// scrolls text visible in widget. Positive value will move text up
+	void scrollTextTo(int distance);
+	void scrollTextBy(int distance);
 };
 
-/// a multi-line label that tries to fit text with given available width and height; if not possible, it creates a slider for scrolling text
-class CTextBox : public CBoundedLabel
+/// a multi-line label that tries to fit text with given available width and height;
+/// if not possible, it creates a slider for scrolling text
+class CTextBox : public CIntObject
 {
-public:
-
 	int sliderStyle;
-
-	std::vector<CAnimImage* > effects;
+public:
+	CMultiLineLabel * label;
 	CSlider *slider;
 
-    //CTextBox( std::string Text, const Point &Pos, int w, int h, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
-    CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
-	void showAll(SDL_Surface * to); //shows statusbar (with current text)
-	void recalculateLines(const std::string &Txt);
+	CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
 
+	void resize(Point newSize);
+	void setText(const std::string &Txt);
 	void sliderMoved(int to);
 };
 
 /// Status bar which is shown at the bottom of the in-game screens
 class CGStatusBar : public CLabel
 {
-    bool textLock; //Used for blocking changes to the text
+	bool textLock; //Used for blocking changes to the text
 	void init();
-public:
+
 	CGStatusBar *oldStatusBar;
+protected:
+	Point getBorderSize() override;
+
+public:
 
-	//statusbar interface overloads
-	void print(const std::string & Text); //prints text and refreshes statusbar
 	void clear();//clears statusbar and refreshes
-	std::string getCurrent(); //returns currently displayed text
-	void show(SDL_Surface * to); //shows statusbar (with current text)
+	void setText(const std::string & Text) override; //prints text and refreshes statusbar
 
-    //CGStatusBar(int x, int y, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color &Color = Colors::WHITE, const std::string &Text =  "");
-    CGStatusBar(CPicture *BG, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color &Color = Colors::WHITE); //given CPicture will be captured by created sbar and it's pos will be used as pos for sbar
-	CGStatusBar(int x, int y, std::string name, int maxw=-1); 
+	void show(SDL_Surface * to); //shows statusbar (with current text)
 
+	CGStatusBar(CPicture *BG, EFonts Font = FONT_SMALL, EAlignment Align = CENTER, const SDL_Color &Color = Colors::WHITE); //given CPicture will be captured by created sbar and it's pos will be used as pos for sbar
+	CGStatusBar(int x, int y, std::string name, int maxw=-1);
 	~CGStatusBar();
-	void calcOffset();
-
-    void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called
 
-    const static Point edgeOffset; //Amount to move text from side when alignment is left or right
+	void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called
 };
 
 /// UIElement which can get input focus
@@ -441,7 +454,7 @@ protected:
 public:
 	CFunctionList<void(const std::string &)> cb;
 	CFunctionList<void(std::string &, const std::string &)> filters;
-	void setTxt(const std::string &nText, bool callCb = false);
+	void setText(const std::string &nText, bool callCb = false);
 
 	CTextInput(const Rect &Pos, EFonts font, const CFunctionList<void(const std::string &)> &CB);
 	CTextInput(const Rect &Pos, const Point &bgOffset, const std::string &bgName, const CFunctionList<void(const std::string &)> &CB);

+ 3 - 3
config/campaignMedia.json

@@ -128,7 +128,7 @@
 	"music" : [
 		
 	],
-	"voice" : {
+	"voice" : [
 	//Restoration of Erathia
 		"G1A", //Long live the Queen 1 
 		"G1B", //Long live the Queen 2 
@@ -233,5 +233,5 @@
 		"H3x2UAk", //Unholy alliance 11 
 		"H3x2UAl", //Unholy alliance 12 
 		"H3x2UAm" //Unholy alliance 12end
-	}
-}
+	]
+}