1 /** @file textcanvas.cpp Text-based drawing surface.
2  *
3  * @authors Copyright © 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * LGPL: http://www.gnu.org/licenses/lgpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14  * General Public License for more details. You should have received a copy of
15  * the GNU Lesser General Public License along with this program; if not, see:
16  * http://www.gnu.org/licenses</small>
17  */
18 
19 #include "de/shell/TextCanvas"
20 #include <QList>
21 #include <QDebug>
22 
23 namespace de { namespace shell {
24 
DENG2_PIMPL_NOREF(TextCanvas)25 DENG2_PIMPL_NOREF(TextCanvas)
26 {
27     Size          size;
28     QList<Char *> lines;
29 
30     struct RichFormat {
31         Char::Attribs attrib;
32         Rangei        range;
33     };
34     QList<RichFormat> richFormats;
35 
36     Impl(Size const &initialSize) : size(initialSize)
37     {
38         // Allocate lines based on supplied initial size.
39         for (duint row = 0; row < size.y; ++row)
40         {
41             lines.append(makeLine());
42         }
43     }
44 
45     ~Impl()
46     {
47         for (int i = 0; i < lines.size(); ++i)
48         {
49             delete [] lines[i];
50         }
51     }
52 
53     dsize lineCount() const
54     {
55         return lines.size();
56     }
57 
58     Char *makeLine()
59     {
60         return new Char[size.x];
61     }
62 
63     void resize(Size const &newSize)
64     {
65         if (newSize == size) return;
66 
67         // Allocate or free lines.
68         while (newSize.y < lineCount())
69         {
70             lines.removeLast();
71         }
72         while (newSize.y > lineCount())
73         {
74             lines.append(makeLine());
75         }
76 
77         Q_ASSERT(lineCount() == newSize.y);
78         size.y = newSize.y;
79 
80         // Make sure all lines are the correct width.
81         for (int row = 0; row < lines.size(); ++row)
82         {
83             Char *newLine = new Char[newSize.x];
84             memcpy(newLine, lines[row], sizeof(Char) * de::min(size.x, newSize.x));
85             delete [] lines[row];
86             lines[row] = newLine;
87         }
88 
89         size.x = newSize.x;
90     }
91 
92     void markAllAsDirty(bool markDirty)
93     {
94         for (int row = 0; row < lines.size(); ++row)
95         {
96             Char *line = lines[row];
97             for (duint col = 0; col < size.x; ++col)
98             {
99                 Char &c = line[col];
100                 if (markDirty)
101                     c.attribs |= Char::Dirty;
102                 else
103                     c.attribs &= ~Char::Dirty;
104             }
105         }
106     }
107 
108     Char::Attribs richAttribsForTextIndex(int pos, int offset = 0) const
109     {
110         Char::Attribs attr;
111         foreach (RichFormat const &rf, richFormats)
112         {
113             if (rf.range.contains(offset + pos))
114             {
115                 attr |= rf.attrib;
116             }
117         }
118         return attr;
119     }
120 };
121 
TextCanvas(Size const & size)122 TextCanvas::TextCanvas(Size const &size) : d(new Impl(size))
123 {
124     d->size = size;
125 }
126 
~TextCanvas()127 TextCanvas::~TextCanvas()
128 {}
129 
size() const130 TextCanvas::Size TextCanvas::size() const
131 {
132     return d->size;
133 }
134 
width() const135 int TextCanvas::width() const
136 {
137     return d->size.x;
138 }
139 
height() const140 int TextCanvas::height() const
141 {
142     return d->size.y;
143 }
144 
rect() const145 Rectanglei TextCanvas::rect() const
146 {
147     return Rectanglei(0, 0, size().x, size().y);
148 }
149 
resize(Size const & newSize)150 void TextCanvas::resize(Size const &newSize)
151 {
152     d->resize(newSize);
153 }
154 
at(Coord const & pos)155 TextCanvas::Char &TextCanvas::at(Coord const &pos)
156 {
157     Q_ASSERT(isValid(pos));
158     return d->lines[pos.y][pos.x];
159 }
160 
at(Coord const & pos) const161 TextCanvas::Char const &TextCanvas::at(Coord const &pos) const
162 {
163     Q_ASSERT(isValid(pos));
164     return d->lines[pos.y][pos.x];
165 }
166 
isValid(Coord const & pos) const167 bool TextCanvas::isValid(Coord const &pos) const
168 {
169     return (pos.x >= 0 && pos.y >= 0 && pos.x < int(d->size.x) && pos.y < int(d->size.y));
170 }
171 
markDirty()172 void TextCanvas::markDirty()
173 {
174     d->markAllAsDirty(true);
175 }
176 
clear(Char const & ch)177 void TextCanvas::clear(Char const &ch)
178 {
179     fill(Rectanglei(0, 0, d->size.x, d->size.y), ch);
180 }
181 
fill(Rectanglei const & rect,Char const & ch)182 void TextCanvas::fill(Rectanglei const &rect, Char const &ch)
183 {
184     for (int y = rect.top(); y < rect.bottom(); ++y)
185     {
186         for (int x = rect.left(); x < rect.right(); ++x)
187         {
188             Coord const xy(x, y);
189             if (isValid(xy)) at(xy) = ch;
190         }
191     }
192 }
193 
put(Vector2i const & pos,Char const & ch)194 void TextCanvas::put(Vector2i const &pos, Char const &ch)
195 {
196     if (isValid(pos))
197     {
198         at(pos) = ch;
199     }
200 }
201 
clearRichFormat()202 void TextCanvas::clearRichFormat()
203 {
204     d->richFormats.clear();
205 }
206 
setRichFormatRange(Char::Attribs const & attribs,Rangei const & range)207 void TextCanvas::setRichFormatRange(Char::Attribs const &attribs, Rangei const &range)
208 {
209     Impl::RichFormat rf;
210     rf.attrib = attribs;
211     rf.range  = range;
212     d->richFormats.append(rf);
213 }
214 
drawText(Vector2i const & pos,String const & text,Char::Attribs const & attribs,int richOffset)215 void TextCanvas::drawText(Vector2i const &pos, String const &text,
216                           Char::Attribs const &attribs, int richOffset)
217 {
218     Vector2i p = pos;
219     for (int i = 0; i < text.size(); ++i)
220     {
221         if (isValid(p))
222         {
223             at(p) = Char(text[i], attribs | d->richAttribsForTextIndex(i, richOffset));
224         }
225         p.x++;
226     }
227 }
228 
drawWrappedText(Vector2i const & pos,String const & text,ILineWrapping const & wraps,Char::Attribs const & attribs,Alignment lineAlignment)229 void TextCanvas::drawWrappedText(Vector2i const &pos, String const &text,
230                                  ILineWrapping const &wraps, Char::Attribs const &attribs,
231                                  Alignment lineAlignment)
232 {
233     int const width = wraps.width();
234 
235     for (int y = 0; y < wraps.height(); ++y)
236     {
237         WrappedLine const &span = wraps.line(y);
238         String part = text.substr(span.range);
239         int x = 0;
240         if (lineAlignment.testFlag(AlignRight))
241         {
242             x = width - part.size();
243         }
244         else if (!lineAlignment.testFlag(AlignLeft))
245         {
246             x = width/2 - part.size()/2;
247         }
248         drawText(pos + Vector2i(x, y), part, attribs, span.range.start);
249     }
250 }
251 
drawLineRect(Rectanglei const & rect,Char::Attribs const & attribs)252 void TextCanvas::drawLineRect(Rectanglei const &rect, Char::Attribs const &attribs)
253 {
254     Char const corner('+', attribs);
255     Char const hEdge ('-', attribs);
256     Char const vEdge ('|', attribs);
257 
258     // Horizontal edges.
259     for (duint x = 1; x < rect.width() - 1; ++x)
260     {
261         put(rect.topLeft + Vector2i(x, 0), hEdge);
262         put(rect.bottomLeft() + Vector2i(x, -1), hEdge);
263     }
264 
265     // Vertical edges.
266     for (duint y = 1; y < rect.height() - 1; ++y)
267     {
268         put(rect.topLeft + Vector2i(0, y), vEdge);
269         put(rect.topRight() + Vector2i(-1, y), vEdge);
270     }
271 
272     put(rect.topLeft, corner);
273     put(rect.topRight() - Vector2i(1, 0), corner);
274     put(rect.bottomRight - Vector2i(1, 1), corner);
275     put(rect.bottomLeft() - Vector2i(0, 1), corner);
276 }
277 
draw(TextCanvas const & canvas,Coord const & topLeft)278 void TextCanvas::draw(TextCanvas const &canvas, Coord const &topLeft)
279 {
280     for (duint y = 0; y < canvas.d->size.y; ++y)
281     {
282         for (duint x = 0; x < canvas.d->size.x; ++x)
283         {
284             Coord const xy(x, y);
285             Coord const p = topLeft + xy;
286             if (isValid(p))
287             {
288                 at(p) = canvas.at(xy);
289             }
290         }
291     }
292 }
293 
show()294 void TextCanvas::show()
295 {
296     d->markAllAsDirty(false);
297 }
298 
setCursorPosition(Vector2i const &)299 void TextCanvas::setCursorPosition(Vector2i const &) {}
300 
301 }} // namespace de::shell
302