1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the demonstration applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include <qevent.h>
43 #include <QPainter>
44 #include <QTextStream>
45 #include <QUndoStack>
46 #include "document.h"
47 #include "commands.h"
48 
49 static const int resizeHandleWidth = 6;
50 
51 /******************************************************************************
52 ** Shape
53 */
54 
55 const QSize Shape::minSize(80, 50);
56 
Shape(Type type,const QColor & color,const QRect & rect)57 Shape::Shape(Type type, const QColor &color, const QRect &rect)
58     : m_type(type), m_rect(rect), m_color(color)
59 {
60 }
61 
type() const62 Shape::Type Shape::type() const
63 {
64     return m_type;
65 }
66 
rect() const67 QRect Shape::rect() const
68 {
69     return m_rect;
70 }
71 
color() const72 QColor Shape::color() const
73 {
74     return m_color;
75 }
76 
name() const77 QString Shape::name() const
78 {
79     return m_name;
80 }
81 
resizeHandle() const82 QRect Shape::resizeHandle() const
83 {
84     QPoint br = m_rect.bottomRight();
85     return QRect(br - QPoint(resizeHandleWidth, resizeHandleWidth), br);
86 }
87 
typeToString(Type type)88 QString Shape::typeToString(Type type)
89 {
90     QString result;
91 
92     switch (type) {
93         case Rectangle:
94             result = QLatin1String("Rectangle");
95             break;
96         case Circle:
97             result = QLatin1String("Circle");
98             break;
99         case Triangle:
100             result = QLatin1String("Triangle");
101             break;
102     }
103 
104     return result;
105 }
106 
stringToType(const QString & s,bool * ok)107 Shape::Type Shape::stringToType(const QString &s, bool *ok)
108 {
109     if (ok != 0)
110         *ok = true;
111 
112     if (s == QLatin1String("Rectangle"))
113         return Rectangle;
114     if (s == QLatin1String("Circle"))
115         return Circle;
116     if (s == QLatin1String("Triangle"))
117         return Triangle;
118 
119     if (ok != 0)
120         *ok = false;
121     return Rectangle;
122 }
123 
124 /******************************************************************************
125 ** Document
126 */
127 
Document(QWidget * parent)128 Document::Document(QWidget *parent)
129     : QWidget(parent), m_currentIndex(-1), m_mousePressIndex(-1), m_resizeHandlePressed(false)
130 {
131     m_undoStack = new QUndoStack(this);
132 
133     setAutoFillBackground(true);
134     setBackgroundRole(QPalette::Base);
135 
136     QPalette pal = palette();
137     pal.setBrush(QPalette::Base, QPixmap(":/icons/background.png"));
138     pal.setColor(QPalette::HighlightedText, Qt::red);
139     setPalette(pal);
140 }
141 
addShape(const Shape & shape)142 QString Document::addShape(const Shape &shape)
143 {
144     QString name = Shape::typeToString(shape.type());
145     name = uniqueName(name);
146 
147     m_shapeList.append(shape);
148     m_shapeList[m_shapeList.count() - 1].m_name = name;
149     setCurrentShape(m_shapeList.count() - 1);
150 
151     return name;
152 }
153 
deleteShape(const QString & shapeName)154 void Document::deleteShape(const QString &shapeName)
155 {
156     int index = indexOf(shapeName);
157     if (index == -1)
158         return;
159 
160     update(m_shapeList.at(index).rect());
161 
162     m_shapeList.removeAt(index);
163 
164     if (index <= m_currentIndex) {
165         m_currentIndex = -1;
166         if (index == m_shapeList.count())
167             --index;
168         setCurrentShape(index);
169     }
170 }
171 
shape(const QString & shapeName) const172 Shape Document::shape(const QString &shapeName) const
173 {
174     int index = indexOf(shapeName);
175     if (index == -1)
176         return Shape();
177     return m_shapeList.at(index);
178 }
179 
setShapeRect(const QString & shapeName,const QRect & rect)180 void Document::setShapeRect(const QString &shapeName, const QRect &rect)
181 {
182     int index = indexOf(shapeName);
183     if (index == -1)
184         return;
185 
186     Shape &shape = m_shapeList[index];
187 
188     update(shape.rect());
189     update(rect);
190 
191     shape.m_rect = rect;
192 }
193 
setShapeColor(const QString & shapeName,const QColor & color)194 void Document::setShapeColor(const QString &shapeName, const QColor &color)
195 {
196 
197     int index = indexOf(shapeName);
198     if (index == -1)
199         return;
200 
201     Shape &shape = m_shapeList[index];
202     shape.m_color = color;
203 
204     update(shape.rect());
205 }
206 
undoStack() const207 QUndoStack *Document::undoStack() const
208 {
209     return m_undoStack;
210 }
211 
load(QTextStream & stream)212 bool Document::load(QTextStream &stream)
213 {
214     m_shapeList.clear();
215 
216     while (!stream.atEnd()) {
217         QString shapeType, shapeName, colorName;
218         int left, top, width, height;
219         stream >> shapeType >> shapeName >> colorName >> left >> top >> width >> height;
220         if (stream.status() != QTextStream::Ok)
221             return false;
222         bool ok;
223         Shape::Type type = Shape::stringToType(shapeType, &ok);
224         if (!ok)
225             return false;
226         QColor color(colorName);
227         if (!color.isValid())
228             return false;
229 
230         Shape shape(type);
231         shape.m_name = shapeName;
232         shape.m_color = color;
233         shape.m_rect = QRect(left, top, width, height);
234 
235         m_shapeList.append(shape);
236     }
237 
238     m_currentIndex = m_shapeList.isEmpty() ? -1 : 0;
239 
240     return true;
241 }
242 
save(QTextStream & stream)243 void Document::save(QTextStream &stream)
244 {
245     for (int i = 0; i < m_shapeList.count(); ++i) {
246         const Shape &shape = m_shapeList.at(i);
247         QRect r = shape.rect();
248         stream << Shape::typeToString(shape.type()) << QLatin1Char(' ')
249                 << shape.name() << QLatin1Char(' ')
250                 << shape.color().name() << QLatin1Char(' ')
251                 << r.left() << QLatin1Char(' ')
252                 << r.top() << QLatin1Char(' ')
253                 << r.width() << QLatin1Char(' ')
254                 << r.height();
255         if (i != m_shapeList.count() - 1)
256             stream << QLatin1Char('\n');
257     }
258     m_undoStack->setClean();
259 }
260 
fileName() const261 QString Document::fileName() const
262 {
263     return m_fileName;
264 }
265 
setFileName(const QString & fileName)266 void Document::setFileName(const QString &fileName)
267 {
268     m_fileName = fileName;
269 }
270 
indexAt(const QPoint & pos) const271 int Document::indexAt(const QPoint &pos) const
272 {
273     for (int i = m_shapeList.count() - 1; i >= 0; --i) {
274         if (m_shapeList.at(i).rect().contains(pos))
275             return i;
276     }
277     return -1;
278 }
279 
mousePressEvent(QMouseEvent * event)280 void Document::mousePressEvent(QMouseEvent *event)
281 {
282     event->accept();
283     int index = indexAt(event->pos());;
284     if (index != -1) {
285         setCurrentShape(index);
286 
287         const Shape &shape = m_shapeList.at(index);
288         m_resizeHandlePressed = shape.resizeHandle().contains(event->pos());
289 
290         if (m_resizeHandlePressed)
291             m_mousePressOffset = shape.rect().bottomRight() - event->pos();
292         else
293             m_mousePressOffset = event->pos() - shape.rect().topLeft();
294     }
295     m_mousePressIndex = index;
296 }
297 
mouseReleaseEvent(QMouseEvent * event)298 void Document::mouseReleaseEvent(QMouseEvent *event)
299 {
300     event->accept();
301     m_mousePressIndex = -1;
302 }
303 
mouseMoveEvent(QMouseEvent * event)304 void Document::mouseMoveEvent(QMouseEvent *event)
305 {
306     event->accept();
307 
308     if (m_mousePressIndex == -1)
309         return;
310 
311     const Shape &shape = m_shapeList.at(m_mousePressIndex);
312 
313     QRect rect;
314     if (m_resizeHandlePressed) {
315         rect = QRect(shape.rect().topLeft(), event->pos() + m_mousePressOffset);
316     } else {
317         rect = shape.rect();
318         rect.moveTopLeft(event->pos() - m_mousePressOffset);
319     }
320 
321     QSize size = rect.size().expandedTo(Shape::minSize);
322     rect.setSize(size);
323 
324     m_undoStack->push(new SetShapeRectCommand(this, shape.name(), rect));
325 }
326 
gradient(const QColor & color,const QRect & rect)327 static QGradient gradient(const QColor &color, const QRect &rect)
328 {
329     QColor c = color;
330     c.setAlpha(160);
331     QLinearGradient result(rect.topLeft(), rect.bottomRight());
332     result.setColorAt(0, c.dark(150));
333     result.setColorAt(0.5, c.light(200));
334     result.setColorAt(1, c.dark(150));
335     return result;
336 }
337 
triangle(const QRect & rect)338 static QPolygon triangle(const QRect &rect)
339 {
340     QPolygon result(3);
341     result.setPoint(0, rect.center().x(), rect.top());
342     result.setPoint(1, rect.right(), rect.bottom());
343     result.setPoint(2, rect.left(), rect.bottom());
344     return result;
345 }
346 
paintEvent(QPaintEvent * event)347 void Document::paintEvent(QPaintEvent *event)
348 {
349     QRegion paintRegion = event->region();
350     QPainter painter(this);
351     QPalette pal = palette();
352 
353     for (int i = 0; i < m_shapeList.count(); ++i) {
354         const Shape &shape = m_shapeList.at(i);
355 
356         if (!paintRegion.contains(shape.rect()))
357             continue;
358 
359         QPen pen = pal.text().color();
360         pen.setWidth(i == m_currentIndex ? 2 : 1);
361         painter.setPen(pen);
362         painter.setBrush(gradient(shape.color(), shape.rect()));
363 
364         QRect rect = shape.rect();
365         rect.adjust(1, 1, -resizeHandleWidth/2, -resizeHandleWidth/2);
366 
367         // paint the shape
368         switch (shape.type()) {
369             case Shape::Rectangle:
370                 painter.drawRect(rect);
371                 break;
372             case Shape::Circle:
373                 painter.setRenderHint(QPainter::Antialiasing);
374                 painter.drawEllipse(rect);
375                 painter.setRenderHint(QPainter::Antialiasing, false);
376                 break;
377             case Shape::Triangle:
378                 painter.setRenderHint(QPainter::Antialiasing);
379                 painter.drawPolygon(triangle(rect));
380                 painter.setRenderHint(QPainter::Antialiasing, false);
381                 break;
382         }
383 
384         // paint the resize handle
385         painter.setPen(pal.text().color());
386         painter.setBrush(Qt::white);
387         painter.drawRect(shape.resizeHandle().adjusted(0, 0, -1, -1));
388 
389         // paint the shape name
390         painter.setBrush(pal.text());
391         if (shape.type() == Shape::Triangle)
392             rect.adjust(0, rect.height()/2, 0, 0);
393         painter.drawText(rect, Qt::AlignCenter, shape.name());
394     }
395 }
396 
setCurrentShape(int index)397 void Document::setCurrentShape(int index)
398 {
399     QString currentName;
400 
401     if (m_currentIndex != -1)
402         update(m_shapeList.at(m_currentIndex).rect());
403 
404     m_currentIndex = index;
405 
406     if (m_currentIndex != -1) {
407         const Shape &current = m_shapeList.at(m_currentIndex);
408         update(current.rect());
409         currentName = current.name();
410     }
411 
412     emit currentShapeChanged(currentName);
413 }
414 
indexOf(const QString & shapeName) const415 int Document::indexOf(const QString &shapeName) const
416 {
417     for (int i = 0; i < m_shapeList.count(); ++i) {
418         if (m_shapeList.at(i).name() == shapeName)
419             return i;
420     }
421     return -1;
422 }
423 
uniqueName(const QString & name) const424 QString Document::uniqueName(const QString &name) const
425 {
426     QString unique;
427 
428     for (int i = 0; ; ++i) {
429         unique = name;
430         if (i > 0)
431             unique += QString::number(i);
432         if (indexOf(unique) == -1)
433             break;
434     }
435 
436     return unique;
437 }
438 
currentShapeName() const439 QString Document::currentShapeName() const
440 {
441     if (m_currentIndex == -1)
442         return QString();
443     return m_shapeList.at(m_currentIndex).name();
444 }
445 
446