1 /***************************************************************************
2  *      Mechanized Assault and Exploration Reloaded Projectfile            *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19 
20 #include "ui/graphical/menu/widgets/label.h"
21 #include "utility/drawing.h"
22 
23 //------------------------------------------------------------------------------
cLabel(const cBox<cPosition> & area,const std::string & text_,eUnicodeFontType fontType_,AlignmentFlags alignment_)24 cLabel::cLabel (const cBox<cPosition>& area, const std::string& text_, eUnicodeFontType fontType_, AlignmentFlags alignment_) :
25 	cWidget (area),
26 	fontType (fontType_),
27 	alignment (alignment_),
28 	wordWrap (false)
29 {
30 	if (getSize().x() < 0 || getSize().y() < 0)
31 	{
32 		surface = nullptr;
33 	}
34 	else
35 	{
36 		surface = AutoSurface (SDL_CreateRGBSurface (0, getSize().x(), getSize().y(), 32, 0, 0, 0, 0));
37 		SDL_FillRect (surface.get(), nullptr, 0xFF00FF);
38 		SDL_SetColorKey (surface.get(), SDL_TRUE, 0xFF00FF);
39 	}
40 
41 	setText (text_);
42 }
43 
44 //------------------------------------------------------------------------------
setText(const std::string & text_)45 void cLabel::setText (const std::string& text_)
46 {
47 	text = text_;
48 
49 	// NOTE: do we really want to do this here?
50 	//       we replace the character sequence "\n" by the escape sequence '\n'
51 	//       because e.g. when reading translation strings, this is used to
52 	//       indicate line breaks.
53 	//       May move this directly to @ref cLanguage and make it more robust
54 	//       (i.e. really parsing escape sequence, so that "\\n" will result in
55 	//       "\n" and not in "\" followed by '\n')
56 	size_t pos = 0;
57 	while ((pos = text.find ("\\n", pos)) != std::string::npos)
58 	{
59 		text.replace (pos, 2, "\n");
60 		pos += 1;
61 	}
62 
63 	updateDisplayInformation();
64 }
65 
66 //------------------------------------------------------------------------------
getText() const67 const std::string& cLabel::getText() const
68 {
69 	return text;
70 }
71 
72 //------------------------------------------------------------------------------
setFont(eUnicodeFontType fontType_)73 void cLabel::setFont (eUnicodeFontType fontType_)
74 {
75 	std::swap (fontType, fontType_);
76 	if (fontType != fontType_) updateDisplayInformation();
77 }
78 
79 //------------------------------------------------------------------------------
setAlignment(AlignmentFlags alignment_)80 void cLabel::setAlignment (AlignmentFlags alignment_)
81 {
82 	std::swap (alignment, alignment_);
83 	if (alignment != alignment_) updateDisplayInformation();
84 }
85 
86 //------------------------------------------------------------------------------
setWordWrap(bool wordWrap_)87 void cLabel::setWordWrap (bool wordWrap_)
88 {
89 	std::swap (wordWrap, wordWrap_);
90 	if (wordWrap != wordWrap_) updateDisplayInformation();
91 }
92 
93 //------------------------------------------------------------------------------
resizeToTextHeight()94 void cLabel::resizeToTextHeight()
95 {
96 	const auto textHeight = drawLines.size() * font->getFontHeight (fontType);
97 	resize (cPosition (getSize().x(), textHeight));
98 }
99 
100 //------------------------------------------------------------------------------
breakText(const std::string & text,std::vector<std::string> & lines,int maximalWidth,eUnicodeFontType fontType) const101 void cLabel::breakText (const std::string& text, std::vector<std::string>& lines, int maximalWidth, eUnicodeFontType fontType) const
102 {
103 	// NOTE: better would be not to copy each line into the vector
104 	//       but use something like "string_view". We could simulate this by using
105 	//       a pair of iterators (like a range) but non of other methods would support such a
106 	//       rand and therefore the construction of a new string object would be necessary anyway.
107 
108 	int currentLineLength = 0;
109 	int currentWordLength = 0;
110 
111 	lines.push_back ("");
112 
113 	auto it = text.begin();
114 	auto nextWordBegin = it;
115 	while (true)
116 	{
117 		auto& currentLine = lines.back();
118 
119 		if (it == text.end() || font->isUtf8Space (& (*it)))
120 		{
121 			if (currentLineLength + currentWordLength >= maximalWidth || (it != text.end() && *it == '\n'))
122 			{
123 				if (currentLineLength + currentWordLength >= maximalWidth)
124 				{
125 					// Remove all leading white spaces
126 					while (nextWordBegin != it && font->isUtf8Space (& (*nextWordBegin)))
127 					{
128 						int increase;
129 						auto unicodeCharacter = font->encodeUTF8Char (& (*nextWordBegin), increase);
130 						currentWordLength -= font->getUnicodeCharacterWidth (unicodeCharacter, fontType);
131 						nextWordBegin += increase;
132 					}
133 
134 					// TODO: may break the word when the single word is to long for the line
135 					// put the word into the next line
136 					lines.push_back (std::string (nextWordBegin, it));
137 					currentLineLength = currentWordLength;
138 				}
139 				else
140 				{
141 					currentLine.append (nextWordBegin, it);
142 					lines.push_back ("");
143 					currentLineLength = 0;
144 				}
145 			}
146 			else
147 			{
148 				if (currentLine.empty())
149 				{
150 					// Remove all leading white spaces if we are at the beginning of a new line
151 					while (nextWordBegin != it && font->isUtf8Space (& (*nextWordBegin)))
152 					{
153 						int increase;
154 						auto unicodeCharacter = font->encodeUTF8Char (& (*nextWordBegin), increase);
155 						currentWordLength -= font->getUnicodeCharacterWidth (unicodeCharacter, fontType);
156 						nextWordBegin += increase;
157 					}
158 				}
159 				currentLine.append (nextWordBegin, it);
160 				currentLineLength += currentWordLength;
161 			}
162 
163 			if (it == text.end()) break;
164 
165 			nextWordBegin = it;
166 			currentWordLength = 0;
167 		}
168 
169 		int increase;
170 		auto unicodeCharacter = font->encodeUTF8Char (& (*it), increase);
171 		currentWordLength += font->getUnicodeCharacterWidth (unicodeCharacter, fontType);
172 
173 		it += increase;
174 	}
175 }
176 
177 //------------------------------------------------------------------------------
updateDisplayInformation()178 void cLabel::updateDisplayInformation()
179 {
180 	if (surface == nullptr) return;
181 
182 	drawLines.clear();
183 
184 	if (wordWrap)
185 	{
186 		breakText (text, drawLines, getSize().x(), fontType);
187 	}
188 	else
189 	{
190 		drawLines.push_back (text);
191 	}
192 
193 	SDL_FillRect (surface.get(), nullptr, 0xFF00FF);
194 
195 	const auto height = font->getFontHeight (fontType) * drawLines.size();
196 
197 	int drawPositionY;
198 	if (alignment & eAlignmentType::Bottom)
199 	{
200 		drawPositionY = getSize().y() - height;
201 	}
202 	else if (alignment & eAlignmentType::CenterVerical)
203 	{
204 		drawPositionY = getSize().y() / 2 - height / 2;
205 	}
206 	else
207 	{
208 		drawPositionY = 0;
209 	}
210 
211 	auto originalTargetSurface = font->getTargetSurface();
212 	auto fontTargetSurfaceResetter = makeScopedOperation ([originalTargetSurface]() { font->setTargetSurface (originalTargetSurface); });
213 	font->setTargetSurface (surface.get());
214 	for (size_t i = 0; i < drawLines.size(); ++i)
215 	{
216 		const auto& line = drawLines[i];
217 
218 		const auto width = font->getTextWide (line, fontType);
219 
220 		int drawPositionX;
221 		if (alignment & eAlignmentType::Right)
222 		{
223 			drawPositionX = getSize().x() - width;
224 		}
225 		else if (alignment & eAlignmentType::CenterHorizontal)
226 		{
227 			drawPositionX = getSize().x() / 2 - width / 2;
228 		}
229 		else
230 		{
231 			drawPositionX = 0;
232 		}
233 
234 		font->showText (drawPositionX, drawPositionY, line, fontType);
235 
236 		drawPositionY += font->getFontHeight (fontType);
237 	}
238 }
239 
240 //------------------------------------------------------------------------------
draw(SDL_Surface & destination,const cBox<cPosition> & clipRect)241 void cLabel::draw (SDL_Surface& destination, const cBox<cPosition>& clipRect)
242 {
243 	if (surface && getArea().intersects (clipRect))
244 	{
245 		blitClipped (*surface, getArea(), destination, clipRect);
246 	}
247 
248 	cWidget::draw (destination, clipRect);
249 }
250 
251 //------------------------------------------------------------------------------
handleResized(const cPosition & oldSize)252 void cLabel::handleResized (const cPosition& oldSize)
253 {
254 	cWidget::handleResized (oldSize);
255 
256 	if (getSize().x() < 0 || getSize().y() < 0)
257 	{
258 		surface = nullptr;
259 		return;
260 	}
261 
262 	surface = AutoSurface (SDL_CreateRGBSurface (0, getSize().x(), getSize().y(), 32, 0, 0, 0, 0));
263 	SDL_FillRect (surface.get(), nullptr, 0xFF00FF);
264 	SDL_SetColorKey (surface.get(), SDL_TRUE, 0xFF00FF);
265 
266 	updateDisplayInformation();
267 }
268