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 ¤t = 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