1 /* GG is a GUI for OpenGL.
2    Copyright (C) 2003-2008 T. Zachary Laine
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1
7    of the License, or (at your option) any later version.
8 
9    This library 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 GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA
18 
19    If you do not wish to comply with the terms of the LGPL please
20    contact the author as other terms are available for a fee.
21 
22    Zach Laine
23    whatwasthataddress@gmail.com */
24 
25 #include <GG/TextControl.h>
26 
27 #include <GG/DrawUtil.h>
28 #include <GG/utf8/checked.h>
29 
30 
31 using namespace GG;
32 
33 namespace {
34     const Pt INVALID_USABLE_SIZE(-X1, -Y1);
35 }
36 
37 ////////////////////////////////////////////////
38 // GG::TextControl
39 ////////////////////////////////////////////////
TextControl(X x,Y y,X w,Y h,const std::string & str,const std::shared_ptr<Font> & font,Clr color,Flags<TextFormat> format,Flags<WndFlag> flags)40 TextControl::TextControl(X x, Y y, X w, Y h, const std::string& str,
41                          const std::shared_ptr<Font>& font, Clr color/* = CLR_BLACK*/,
42                          Flags<TextFormat> format/* = FORMAT_NONE*/,
43                          Flags<WndFlag> flags/* = NO_WND_FLAGS*/) :
44     Control(x, y, w, h, flags),
45     m_format(format),
46     m_text_color(color),
47     m_font(font)
48 {
49     ValidateFormat();
50     SetText(str);
51 }
52 
TextControl(X x,Y y,X w,Y h,const std::string & str,const std::vector<std::shared_ptr<Font::TextElement>> & text_elements,const std::shared_ptr<Font> & font,Clr color,Flags<TextFormat> format,Flags<WndFlag> flags)53 TextControl::TextControl(X x, Y y, X w, Y h, const std::string& str,
54                          const std::vector<std::shared_ptr<Font::TextElement>>& text_elements,
55                          const std::shared_ptr<Font>& font,
56                          Clr color /*= CLR_BLACK*/, Flags<TextFormat> format /*= FORMAT_NONE*/,
57                          Flags<WndFlag> flags /*= NO_WND_FLAGS*/) :
58     Control(x, y, w, h, flags),
59     m_format(format),
60     m_text_color(color),
61     m_font(font)
62 {
63     ValidateFormat();
64     SetText(str, text_elements);
65 }
66 
TextControl(const TextControl & that)67 TextControl::TextControl(const TextControl& that) :
68     Control(that.Left(), that.Top(), that.Width(), that.Height()),
69     m_text(that.m_text),
70     m_format(that.m_format),
71     m_text_color(that.m_text_color),
72     m_clip_text(that.m_clip_text),
73     m_set_min_size(that.m_set_min_size),
74     m_text_elements(that.m_text_elements),
75     m_code_points(that.m_code_points),
76     m_font(that.m_font),
77     m_cached_minusable_size_width(that.m_cached_minusable_size_width),
78     m_cached_minusable_size(that.m_cached_minusable_size)
79 {
80     for (auto& elem : m_text_elements) {
81         elem->Bind(m_text);
82     }
83 }
84 
~TextControl()85 TextControl::~TextControl()
86 {}
87 
operator =(const TextControl & that)88 TextControl& TextControl::operator=(const TextControl& that)
89 {
90     m_text = that.m_text;
91     m_format = that.m_format;
92     m_text_color = that.m_text_color;
93     m_clip_text = that.m_clip_text;
94     m_set_min_size = that.m_set_min_size;
95     m_text_elements = that.m_text_elements;
96     m_code_points = that.m_code_points;
97     m_font = that.m_font;
98     m_render_cache.reset();
99     m_cached_minusable_size_width = that.m_cached_minusable_size_width;
100     m_cached_minusable_size = that.m_cached_minusable_size;
101 
102     for (auto& elem : m_text_elements) {
103         elem->Bind(m_text);
104     }
105 
106     return *this;
107 }
108 
MinUsableSize() const109 Pt TextControl::MinUsableSize() const
110 { return m_text_lr - m_text_ul; }
111 
MinUsableSize(X width) const112 Pt TextControl::MinUsableSize(X width) const
113 {
114     // If the requested width is within one space width of the cached width
115     // don't recalculate the size
116     X min_delta = m_font->SpaceWidth();
117     X abs_delta_w = X(std::abs(Value(m_cached_minusable_size_width - width)));
118     if (m_cached_minusable_size_width != X0 &&  abs_delta_w < min_delta)
119         return m_cached_minusable_size;
120 
121     // Calculate and cache the minimum usable size when m_cached_minusable_size is equal to width.
122     // Create dummy line data with line breaks added so that lines are not wider than width.
123     Flags<TextFormat> dummy_format(m_format);
124     auto dummy_line_data =
125         m_font->DetermineLines(m_text, dummy_format, width, m_text_elements);
126     m_cached_minusable_size = m_font->TextExtent(dummy_line_data)
127         + (ClientUpperLeft() - UpperLeft()) + (LowerRight() - ClientLowerRight());
128     m_cached_minusable_size_width = width;
129     return m_cached_minusable_size;
130 }
131 
Text() const132 const std::string& TextControl::Text() const
133 { return m_text; }
134 
Text(CPSize from,CPSize to) const135 std::string TextControl::Text(CPSize from, CPSize to) const
136 {
137     if (from == INVALID_CP_SIZE || to == INVALID_CP_SIZE)
138         return "";
139     CPSize low = std::max(CP0, std::min(from, to));
140     CPSize high = std::min(Length(), std::max(from, to));
141 
142     //std::cout << "low: " << low << "  high: " << high << std::endl;
143 
144     auto low_pos = LinePositionOf(low, m_line_data);
145     auto high_pos = LinePositionOf(high, m_line_data);
146 
147     StrSize low_string_idx = StringIndexOf(low_pos.first, low_pos.second, m_line_data);
148     StrSize high_string_idx = StringIndexOf(high_pos.first, high_pos.second, m_line_data);
149 
150     auto low_it = m_text.begin() + Value(low_string_idx);
151     auto high_it = m_text.begin() + Value(high_string_idx);
152 
153     try {
154         //std::cout << "dist begin to low: " << std::distance(m_text.begin(), low_it) << std::endl;
155         //std::cout << "dist low to high: " << std::distance(low_it, high_it) << std::endl;
156         //std::cout << "dist high to end: " << std::distance(high_it, m_text.end()) << std::endl;
157 
158         return std::string(low_it, high_it);
159     } catch (...) {
160         return "";
161     }
162 }
163 
GetTextFormat() const164 Flags<TextFormat> TextControl::GetTextFormat() const
165 { return m_format; }
166 
TextColor() const167 Clr TextControl::TextColor() const
168 { return m_text_color; }
169 
ClipText() const170 bool TextControl::ClipText() const
171 { return m_clip_text; }
172 
IsResetMinSize() const173 bool TextControl::IsResetMinSize() const
174 { return m_set_min_size; }
175 
operator const std::string&() const176 TextControl::operator const std::string&() const
177 { return m_text; }
178 
Empty() const179 bool TextControl::Empty() const
180 { return m_text.empty(); }
181 
Length() const182 CPSize TextControl::Length() const
183 { return m_code_points; }
184 
TextUpperLeft() const185 Pt TextControl::TextUpperLeft() const
186 { return UpperLeft() + m_text_ul; }
187 
TextLowerRight() const188 Pt TextControl::TextLowerRight() const
189 { return UpperLeft() + m_text_lr; }
190 
Render()191 void TextControl::Render()
192 {
193     Clr clr_to_use = Disabled() ? DisabledColor(TextColor()) : TextColor();
194     glColor(clr_to_use);
195     if (m_font) {
196         if (!m_render_cache) {
197             RefreshCache();
198         }
199         if (m_clip_text)
200             BeginClipping();
201         glPushMatrix();
202         Pt ul = ClientUpperLeft();
203         glTranslated(Value(ul.x), Value(ul.y), 0);
204         m_font->RenderCachedText(*m_render_cache);
205         glPopMatrix();
206         if (m_clip_text)
207             EndClipping();
208     }
209 }
210 
RefreshCache()211 void TextControl::RefreshCache() {
212     PurgeCache();
213     m_render_cache.reset(new Font::RenderCache());
214     if (m_font)
215         m_font->PreRenderText(Pt(X0, Y0), Size(), m_text, m_format, *m_render_cache, m_line_data);
216 }
217 
PurgeCache()218 void TextControl::PurgeCache()
219 { m_render_cache.reset(); }
220 
SetText(const std::string & str)221 void TextControl::SetText(const std::string& str)
222 {
223     if (!utf8::is_valid(str.begin(), str.end()))
224         return;
225     m_text = str;
226 
227     if (!m_font)
228         return;
229 
230     m_text_elements = m_font->ExpensiveParseFromTextToTextElements(m_text, m_format);
231     RecomputeLineData();
232 }
233 
SetText(const std::string & str,const std::vector<std::shared_ptr<Font::TextElement>> & text_elements)234 void TextControl::SetText(const std::string& str,
235                           const std::vector<std::shared_ptr<Font::TextElement>>& text_elements)
236 {
237     if (!utf8::is_valid(str.begin(), str.end()))
238         return;
239 
240     std::size_t expected_length(0);
241     for (auto& elem : text_elements) {
242         expected_length += elem->text.size();
243     }
244 
245     if (expected_length > str.size())
246         return;
247 
248     m_text = str;
249 
250     m_text_elements = text_elements;
251     for (auto& elem : m_text_elements) {
252         elem->Bind(m_text);
253     }
254 
255     RecomputeLineData();
256 }
257 
ChangeTemplatedText(const std::string & new_text,size_t targ_offset)258 void TextControl::ChangeTemplatedText(const std::string& new_text, size_t targ_offset) {
259     m_font->ChangeTemplatedText(m_text, m_text_elements, new_text, targ_offset);
260     RecomputeLineData();
261 }
262 
RecomputeLineData()263 void TextControl::RecomputeLineData() {
264     if (!m_font)
265         return;
266 
267     m_code_points = CPSize(utf8::distance(m_text.begin(), m_text.end()));
268 
269     m_line_data = m_font->DetermineLines(m_text, m_format, ClientSize().x, m_text_elements);
270     Pt text_sz = m_font->TextExtent(m_line_data);
271     m_text_ul = Pt();
272     m_text_lr = text_sz;
273     PurgeCache();
274     if (m_format & FORMAT_NOWRAP) {
275         Resize(text_sz);
276     } else {
277         RecomputeTextBounds();
278     }
279 
280     m_cached_minusable_size_width = X0;
281 }
282 
GetFont() const283 const std::shared_ptr<Font>& TextControl::GetFont() const
284 { return m_font; }
285 
SetFont(std::shared_ptr<Font> font)286 void TextControl::SetFont(std::shared_ptr<Font> font)
287 {
288     m_font = font;
289     SetText(m_text);
290 }
291 
SizeMove(const Pt & ul,const Pt & lr)292 void TextControl::SizeMove(const Pt& ul, const Pt& lr)
293 {
294     GG::Pt old_size = Size();
295     Wnd::SizeMove(ul, lr);
296     bool resized = old_size != Size();
297     bool redo_determine_lines = false;
298     X client_width = ClientSize().x;
299 
300     if (m_text.empty()) {
301         // don't redo lines
302     } else if (resized && m_format != FORMAT_LEFT && m_format != FORMAT_NONE) {
303         // for text with non-trivial alignment, be that centred, justified,
304         // right, or multi-line, or vertical alignments, need to redo for any
305         // resize
306         redo_determine_lines = true;
307     } else if (resized && !(m_format & FORMAT_NOWRAP) && (m_format & FORMAT_WORDBREAK || m_format & FORMAT_LINEWRAP)) {
308         // if breaking text across lines, need to redo layout when the available
309         // width is less than that needed to fit the text on one line
310         X text_width = m_text_lr.x - m_text_ul.x;
311         redo_determine_lines = client_width < text_width ||
312                               (text_width < client_width && 1u < m_line_data.size());
313     }
314 
315     if (redo_determine_lines) {
316         if (m_text_elements.empty())
317             m_text_elements = m_font->ExpensiveParseFromTextToTextElements(m_text, m_format);
318         m_line_data = m_font->DetermineLines(m_text, m_format, client_width, m_text_elements);
319         Pt text_sz = m_font->TextExtent(m_line_data);
320         m_text_ul = Pt();
321         m_text_lr = text_sz;
322         PurgeCache();
323     }
324     RecomputeTextBounds();
325 }
326 
SetTextFormat(Flags<TextFormat> format)327 void TextControl::SetTextFormat(Flags<TextFormat> format)
328 {
329     m_format = format;
330     ValidateFormat();
331     if (m_format != format)
332         SetText(m_text);
333 }
334 
SetTextColor(Clr color)335 void TextControl::SetTextColor(Clr color)
336 {
337     m_text_color = color;
338     PurgeCache();
339 }
340 
SetColor(Clr c)341 void TextControl::SetColor(Clr c)
342 {
343     Control::SetColor(c);
344     m_text_color = c;
345     PurgeCache();
346 }
347 
ClipText(bool b)348 void TextControl::ClipText(bool b)
349 { m_clip_text = b; }
350 
SetResetMinSize(bool b)351 void TextControl::SetResetMinSize(bool b)
352 {
353     m_set_min_size = b;
354     AdjustMinimumSize();
355 }
356 
operator +=(const std::string & s)357 void TextControl::operator+=(const std::string& s)
358 { SetText(m_text + s); }
359 
operator +=(char c)360 void TextControl::operator+=(char c)
361 {
362     if (!detail::ValidUTFChar<char>()(c))
363         throw utf8::invalid_utf8(c);
364     SetText(m_text + c);
365 }
366 
Clear()367 void TextControl::Clear()
368 { SetText(""); }
369 
Insert(CPSize pos,char c)370 void TextControl::Insert(CPSize pos, char c)
371 {
372     std::size_t line;
373     std::tie(line, pos) = LinePositionOf(pos, m_line_data);
374     Insert(line, pos, c);
375 }
376 
Insert(CPSize pos,const std::string & s)377 void TextControl::Insert(CPSize pos, const std::string& s)
378 {
379     std::size_t line;
380     std::tie(line, pos) = LinePositionOf(pos, m_line_data);
381     Insert(line, pos, s);
382 }
383 
Erase(CPSize pos,CPSize num)384 void TextControl::Erase(CPSize pos, CPSize num/* = CP1*/)
385 {
386     std::size_t line;
387     std::tie(line, pos) = LinePositionOf(pos, m_line_data);
388     Erase(line, pos, num);
389 }
390 
Insert(std::size_t line,CPSize pos,char c)391 void TextControl::Insert(std::size_t line, CPSize pos, char c)
392 {
393     if (!detail::ValidUTFChar<char>()(c))
394         return;
395     m_text.insert(Value(StringIndexOf(line, pos, m_line_data)), 1, c);
396     SetText(m_text);
397 }
398 
Insert(std::size_t line,CPSize pos,const std::string & s)399 void TextControl::Insert(std::size_t line, CPSize pos, const std::string& s)
400 {
401     if (!utf8::is_valid(s.begin(), s.end()))
402         return;
403     m_text.insert(Value(StringIndexOf(line, pos, m_line_data)), s);
404     SetText(m_text);
405 }
406 
Erase(std::size_t line,CPSize pos,CPSize num)407 void TextControl::Erase(std::size_t line, CPSize pos, CPSize num/* = CP1*/)
408 {
409     auto it = m_text.begin() + Value(StringIndexOf(line, pos, m_line_data));
410     auto end_it = m_text.begin() + Value(StringIndexOf(line, pos + num, m_line_data));
411     if (it == end_it)
412         return;
413     m_text.erase(it, end_it);
414     SetText(m_text);
415 }
416 
Erase(std::size_t line1,CPSize pos1,std::size_t line2,CPSize pos2)417 void TextControl::Erase(std::size_t line1, CPSize pos1, std::size_t line2, CPSize pos2)
418 {
419     //std::cout << "TextControl::Erase(" << line1 << ", " << pos1 << "," << line2 << ", " << pos2 << ")" << std::endl;
420 
421     size_t offset1 = Value(StringIndexOf(line1, pos1, m_line_data));
422     size_t offset2 = Value(StringIndexOf(line2, pos2, m_line_data));
423     if (offset1 == offset2)
424         return;
425     //std::cout << "TextControl::Erase offsets: " << offset1 << " // " << offset2 << std::endl;
426     auto it = m_text.begin() + std::min(offset1, offset2);
427     auto end_it = m_text.begin() + std::max(offset1, offset2);
428     m_text.erase(it, end_it);
429     SetText(m_text);
430 }
431 
GetLineData() const432 const std::vector<Font::LineData>& TextControl::GetLineData() const
433 { return m_line_data; }
434 
ValidateFormat()435 void TextControl::ValidateFormat()
436 {
437     int dup_ct = 0;   // duplication count
438     if (m_format & FORMAT_LEFT) ++dup_ct;
439     if (m_format & FORMAT_RIGHT) ++dup_ct;
440     if (m_format & FORMAT_CENTER) ++dup_ct;
441     if (dup_ct != 1) {   // exactly one must be picked; when none or multiples are picked, use FORMAT_CENTER by default
442         m_format &= ~(FORMAT_RIGHT | FORMAT_LEFT);
443         m_format |= FORMAT_CENTER;
444     }
445     dup_ct = 0;
446     if (m_format & FORMAT_TOP) ++dup_ct;
447     if (m_format & FORMAT_BOTTOM) ++dup_ct;
448     if (m_format & FORMAT_VCENTER) ++dup_ct;
449     if (dup_ct != 1) {   // exactly one must be picked; when none or multiples are picked, use FORMAT_VCENTER by default
450         m_format &= ~(FORMAT_TOP | FORMAT_BOTTOM);
451         m_format |= FORMAT_VCENTER;
452     }
453     if ((m_format & FORMAT_WORDBREAK) && (m_format & FORMAT_LINEWRAP))   // only one of these can be picked; FORMAT_WORDBREAK overrides FORMAT_LINEWRAP
454         m_format &= ~FORMAT_LINEWRAP;
455 }
456 
AdjustMinimumSize()457 void TextControl::AdjustMinimumSize()
458 {
459     if (m_set_min_size)
460         SetMinSize(m_text_lr - m_text_ul);
461 }
462 
RecomputeTextBounds()463 void TextControl::RecomputeTextBounds()
464 {
465     Pt text_sz = TextLowerRight() - TextUpperLeft();
466     m_text_ul.y = Y0; // default value for FORMAT_TOP
467     if (m_format & FORMAT_BOTTOM)
468         m_text_ul.y = Size().y - text_sz.y;
469     else if (m_format & FORMAT_VCENTER)
470         m_text_ul.y = (Size().y - text_sz.y) / 2.0;
471     m_text_ul.x = X0; // default for FORMAT_LEFT
472     if (m_format & FORMAT_RIGHT)
473         m_text_ul.x = Size().x - text_sz.x;
474     else if (m_format & FORMAT_CENTER)
475         m_text_ul.x = (Size().x - text_sz.x) / 2.0;
476     m_text_lr = m_text_ul + text_sz;
477     AdjustMinimumSize();
478 }
479