1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Jochen Becher
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "stereotypecontroller.h"
27
28 #include "customrelation.h"
29 #include "stereotypeicon.h"
30 #include "shapepaintvisitor.h"
31 #include "toolbar.h"
32
33 #include "qmt/infrastructure/qmtassert.h"
34 #include "qmt/style/style.h"
35 #include <utils/algorithm.h>
36
37 #include <QHash>
38 #include <QPainter>
39 #include <QIcon>
40 #include <QPair>
41
42 #include <algorithm>
43
44 namespace qmt {
45
46 namespace {
47
48 struct IconKey {
IconKeyqmt::__anone80d65360111::IconKey49 IconKey(StereotypeIcon::Element element, const QList<QString> &stereotypes, const QString &defaultIconPath,
50 const Uid &styleUid, const QSize &size, const QMarginsF &margins, qreal lineWidth)
51 : m_element(element),
52 m_stereotypes(stereotypes),
53 m_defaultIconPath(defaultIconPath),
54 m_styleUid(styleUid),
55 m_size(size),
56 m_margins(margins),
57 m_lineWidth(lineWidth)
58 {
59 }
60
61 const StereotypeIcon::Element m_element;
62 const QList<QString> m_stereotypes;
63 const QString m_defaultIconPath;
64 const Uid m_styleUid;
65 const QSize m_size;
66 const QMarginsF m_margins;
67 const qreal m_lineWidth;
68 };
69
operator ==(const IconKey & lhs,const IconKey & rhs)70 bool operator==(const IconKey &lhs, const IconKey &rhs) {
71 return lhs.m_element == rhs.m_element
72 && lhs.m_stereotypes == rhs.m_stereotypes
73 && lhs.m_defaultIconPath == rhs.m_defaultIconPath
74 && lhs.m_styleUid == rhs.m_styleUid
75 && lhs.m_size == rhs.m_size
76 && lhs.m_margins == rhs.m_margins
77 && lhs.m_lineWidth == rhs.m_lineWidth;
78 }
79
qHash(const IconKey & key)80 auto qHash(const IconKey &key) {
81 return ::qHash(key.m_element) + qHash(key.m_stereotypes) + qHash(key.m_defaultIconPath)
82 + qHash(key.m_styleUid) + ::qHash(key.m_size.width()) + ::qHash(key.m_size.height());
83 }
84
85 }
86
87 class StereotypeController::StereotypeControllerPrivate
88 {
89 public:
90 QHash<QPair<StereotypeIcon::Element, QString>, QString> m_stereotypeToIconIdMap;
91 QHash<QString, StereotypeIcon> m_iconIdToStereotypeIconsMap;
92 QHash<QString, CustomRelation> m_relationIdToCustomRelationMap;
93 QList<Toolbar> m_toolbars;
94 QList<Toolbar> m_elementToolbars;
95 QHash<IconKey, QIcon> m_iconMap;
96 };
97
StereotypeController(QObject * parent)98 StereotypeController::StereotypeController(QObject *parent) :
99 QObject(parent),
100 d(new StereotypeControllerPrivate)
101 {
102 }
103
~StereotypeController()104 StereotypeController::~StereotypeController()
105 {
106 delete d;
107 }
108
stereotypeIcons() const109 QList<StereotypeIcon> StereotypeController::stereotypeIcons() const
110 {
111 return d->m_iconIdToStereotypeIconsMap.values();
112 }
113
toolbars() const114 QList<Toolbar> StereotypeController::toolbars() const
115 {
116 return d->m_toolbars;
117 }
118
findToolbars(const QString & elementType) const119 QList<Toolbar> StereotypeController::findToolbars(const QString &elementType) const
120 {
121 return Utils::filtered(d->m_elementToolbars, [&elementType](const Toolbar &tb) {
122 return tb.elementTypes().contains(elementType);
123 });
124 }
125
knownStereotypes(StereotypeIcon::Element stereotypeElement) const126 QList<QString> StereotypeController::knownStereotypes(StereotypeIcon::Element stereotypeElement) const
127 {
128 QSet<QString> stereotypes;
129 foreach (const StereotypeIcon &icon, d->m_iconIdToStereotypeIconsMap) {
130 if (icon.elements().isEmpty() || icon.elements().contains(stereotypeElement))
131 stereotypes += icon.stereotypes();
132 }
133 QList<QString> list = Utils::toList(stereotypes);
134 std::sort(list.begin(), list.end());
135 return list;
136 }
137
findStereotypeIconId(StereotypeIcon::Element element,const QList<QString> & stereotypes) const138 QString StereotypeController::findStereotypeIconId(StereotypeIcon::Element element,
139 const QList<QString> &stereotypes) const
140 {
141 foreach (const QString &stereotype, stereotypes) {
142 if (d->m_stereotypeToIconIdMap.contains(qMakePair(element, stereotype)))
143 return d->m_stereotypeToIconIdMap.value(qMakePair(element, stereotype));
144 else if (d->m_stereotypeToIconIdMap.contains(qMakePair(StereotypeIcon::ElementAny, stereotype)))
145 return d->m_stereotypeToIconIdMap.value(qMakePair(StereotypeIcon::ElementAny, stereotype));
146 }
147 return QString();
148 }
149
filterStereotypesByIconId(const QString & stereotypeIconId,const QList<QString> & stereotypes) const150 QList<QString> StereotypeController::filterStereotypesByIconId(const QString &stereotypeIconId,
151 const QList<QString> &stereotypes) const
152 {
153 if (!d->m_iconIdToStereotypeIconsMap.contains(stereotypeIconId))
154 return stereotypes;
155 QList<QString> filteredStereotypes = stereotypes;
156 foreach (const QString &stereotype, d->m_iconIdToStereotypeIconsMap.value(stereotypeIconId).stereotypes())
157 filteredStereotypes.removeAll(stereotype);
158 return filteredStereotypes;
159 }
160
findStereotypeIcon(const QString & stereotypeIconId) const161 StereotypeIcon StereotypeController::findStereotypeIcon(const QString &stereotypeIconId) const
162 {
163 QMT_CHECK(d->m_iconIdToStereotypeIconsMap.contains(stereotypeIconId));
164 return d->m_iconIdToStereotypeIconsMap.value(stereotypeIconId);
165 }
166
findCustomRelation(const QString & customRelationId) const167 CustomRelation StereotypeController::findCustomRelation(const QString &customRelationId) const
168 {
169 return d->m_relationIdToCustomRelationMap.value(customRelationId);
170 }
171
createIcon(StereotypeIcon::Element element,const QList<QString> & stereotypes,const QString & defaultIconPath,const Style * style,const QSize & size,const QMarginsF & margins,qreal lineWidth)172 QIcon StereotypeController::createIcon(StereotypeIcon::Element element, const QList<QString> &stereotypes,
173 const QString &defaultIconPath, const Style *style, const QSize &size,
174 const QMarginsF &margins, qreal lineWidth)
175 {
176 IconKey key(element, stereotypes, defaultIconPath, style->uid(), size, margins, lineWidth);
177 QIcon icon = d->m_iconMap.value(key);
178 if (!icon.isNull())
179 return icon;
180 QString stereotypeIconId = findStereotypeIconId(element, stereotypes);
181 if (!stereotypeIconId.isEmpty()) {
182 StereotypeIcon stereotypeIcon = findStereotypeIcon(stereotypeIconId);
183
184 // calculate bounding rectangle relativ to original icon size
185 ShapeSizeVisitor sizeVisitor(QPointF(0.0, 0.0),
186 QSizeF(stereotypeIcon.width(), stereotypeIcon.height()),
187 QSizeF(stereotypeIcon.width(), stereotypeIcon.height()),
188 QSizeF(stereotypeIcon.width(), stereotypeIcon.height()));
189 stereotypeIcon.iconShape().visitShapes(&sizeVisitor);
190 QRectF iconBoundingRect = sizeVisitor.boundingRect();
191
192 // calc painting space within margins
193 qreal innerWidth = size.width() - margins.left() - margins.right();
194 qreal innerHeight = size.height() - margins.top() - margins.bottom();
195
196 // calculate width/height ratio from icon size
197 qreal widthRatio = 1.0;
198 qreal heightRatio = 1.0;
199 qreal ratio = stereotypeIcon.width() / stereotypeIcon.height();
200 if (ratio > 1.0)
201 heightRatio /= ratio;
202 else
203 widthRatio *= ratio;
204
205 // calculate inner painting area
206 qreal paintWidth = stereotypeIcon.width() * innerWidth / iconBoundingRect.width() * widthRatio;
207 qreal paintHeight = stereotypeIcon.height() * innerHeight / iconBoundingRect.height() * heightRatio;
208 // icons which renders smaller than their size should not be zoomed
209 if (paintWidth > innerWidth) {
210 paintHeight *= innerWidth / paintHeight;
211 paintWidth = innerWidth;
212 }
213 if (paintHeight > innerHeight) {
214 paintWidth *= innerHeight / paintHeight;
215 paintHeight = innerHeight;
216 }
217
218 // calculate offset of top/left edge
219 qreal paintLeft = iconBoundingRect.left() * paintWidth / stereotypeIcon.width();
220 qreal paintTop = iconBoundingRect.top() * paintHeight / stereotypeIcon.height();
221
222 // calculate total painting size
223 qreal totalPaintWidth = iconBoundingRect.width() * paintWidth / stereotypeIcon.width();
224 qreal totalPaintHeight = iconBoundingRect.height() * paintHeight / stereotypeIcon.height();
225
226 QPixmap pixmap(size);
227 pixmap.fill(Qt::transparent);
228 QPainter painter(&pixmap);
229 painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
230 painter.setBrush(Qt::NoBrush);
231 // set painting origin taking margin, offset and centering into account
232 painter.translate(QPointF(margins.left(), margins.top()) - QPointF(paintLeft, paintTop)
233 + QPointF((innerWidth - totalPaintWidth) / 2, (innerHeight - totalPaintHeight) / 2));
234 QPen linePen = style->linePen();
235 linePen.setWidthF(lineWidth);
236 painter.setPen(linePen);
237 painter.setBrush(style->fillBrush());
238
239 ShapePaintVisitor visitor(&painter, QPointF(0.0, 0.0),
240 QSizeF(stereotypeIcon.width(), stereotypeIcon.height()),
241 QSizeF(paintWidth, paintHeight), QSizeF(paintWidth, paintHeight));
242 stereotypeIcon.iconShape().visitShapes(&visitor);
243
244 icon = QIcon(pixmap);
245 }
246 if (icon.isNull() && !defaultIconPath.isEmpty())
247 icon = QIcon(defaultIconPath);
248 d->m_iconMap.insert(key, icon);
249 return icon;
250
251 }
252
addStereotypeIcon(const StereotypeIcon & stereotypeIcon)253 void StereotypeController::addStereotypeIcon(const StereotypeIcon &stereotypeIcon)
254 {
255 if (stereotypeIcon.elements().isEmpty()) {
256 foreach (const QString &stereotype, stereotypeIcon.stereotypes())
257 d->m_stereotypeToIconIdMap.insert(qMakePair(StereotypeIcon::ElementAny, stereotype), stereotypeIcon.id());
258 } else {
259 foreach (StereotypeIcon::Element element, stereotypeIcon.elements()) {
260 foreach (const QString &stereotype, stereotypeIcon.stereotypes())
261 d->m_stereotypeToIconIdMap.insert(qMakePair(element, stereotype), stereotypeIcon.id());
262 }
263 }
264 d->m_iconIdToStereotypeIconsMap.insert(stereotypeIcon.id(), stereotypeIcon);
265 }
266
addCustomRelation(const CustomRelation & customRelation)267 void StereotypeController::addCustomRelation(const CustomRelation &customRelation)
268 {
269 d->m_relationIdToCustomRelationMap.insert(customRelation.id(), customRelation);
270 }
271
addToolbar(const Toolbar & toolbar)272 void StereotypeController::addToolbar(const Toolbar &toolbar)
273 {
274 if (toolbar.elementTypes().isEmpty())
275 d->m_toolbars.append(toolbar);
276 else
277 d->m_elementToolbars.append(toolbar);
278 }
279
280 } // namespace qmt
281