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