// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "stereotypecontroller.h" #include "customrelation.h" #include "stereotypeicon.h" #include "shapepaintvisitor.h" #include "toolbar.h" #include "qmt/infrastructure/qmtassert.h" #include "qmt/style/style.h" #include #include #include #include #include #include #include using Utils::FilePath; namespace qmt { namespace { struct IconKey { IconKey(StereotypeIcon::Element element, const QList &stereotypes, const FilePath &defaultIconPath, const Uid &styleUid, const QSize &size, const QMarginsF &margins, qreal lineWidth) : m_element(element), m_stereotypes(stereotypes), m_defaultIconPath(defaultIconPath), m_styleUid(styleUid), m_size(size), m_margins(margins), m_lineWidth(lineWidth) { } friend bool operator==(const IconKey &lhs, const IconKey &rhs) { return lhs.m_element == rhs.m_element && lhs.m_stereotypes == rhs.m_stereotypes && lhs.m_defaultIconPath == rhs.m_defaultIconPath && lhs.m_styleUid == rhs.m_styleUid && lhs.m_size == rhs.m_size && lhs.m_margins == rhs.m_margins && lhs.m_lineWidth == rhs.m_lineWidth; } friend auto qHash(const IconKey &key) { return ::qHash(key.m_element) + qHash(key.m_stereotypes) + qHash(key.m_defaultIconPath) + qHash(key.m_styleUid) + ::qHash(key.m_size.width()) + ::qHash(key.m_size.height()); } const StereotypeIcon::Element m_element; const QList m_stereotypes; const FilePath m_defaultIconPath; const Uid m_styleUid; const QSize m_size; const QMarginsF m_margins; const qreal m_lineWidth; }; } class StereotypeController::StereotypeControllerPrivate { public: QHash, QString> m_stereotypeToIconIdMap; QHash m_iconIdToStereotypeIconsMap; QHash m_relationIdToCustomRelationMap; QHash m_stereotypeToCustomRelationMap; QList m_toolbars; QList m_elementToolbars; QHash m_iconMap; }; StereotypeController::StereotypeController(QObject *parent) : QObject(parent), d(new StereotypeControllerPrivate) { } StereotypeController::~StereotypeController() { delete d; } QList StereotypeController::stereotypeIcons() const { return d->m_iconIdToStereotypeIconsMap.values(); } QList StereotypeController::toolbars() const { return d->m_toolbars; } QList StereotypeController::findToolbars(const QString &elementType) const { return Utils::filtered(d->m_elementToolbars, [&elementType](const Toolbar &tb) { return tb.elementTypes().contains(elementType); }); } QList StereotypeController::knownStereotypes(StereotypeIcon::Element stereotypeElement) const { QSet stereotypes; for (const StereotypeIcon &icon : d->m_iconIdToStereotypeIconsMap) { if (icon.elements().isEmpty() || icon.elements().contains(stereotypeElement)) stereotypes += icon.stereotypes(); } QList list = Utils::toList(stereotypes); std::sort(list.begin(), list.end()); return list; } QString StereotypeController::findStereotypeIconId(StereotypeIcon::Element element, const QList &stereotypes) const { for (const QString &stereotype : stereotypes) { auto it = d->m_stereotypeToIconIdMap.constFind({element, stereotype}); if (it != d->m_stereotypeToIconIdMap.constEnd()) return it.value(); it = d->m_stereotypeToIconIdMap.constFind({StereotypeIcon::ElementAny, stereotype}); if (it != d->m_stereotypeToIconIdMap.constEnd()) return it.value(); } return {}; } QList StereotypeController::filterStereotypesByIconId(const QString &stereotypeIconId, const QList &stereotypes) const { if (!d->m_iconIdToStereotypeIconsMap.contains(stereotypeIconId)) return stereotypes; QList filteredStereotypes = stereotypes; const QSet stereotypeList = d->m_iconIdToStereotypeIconsMap.value(stereotypeIconId).stereotypes(); for (const QString &stereotype : stereotypeList) filteredStereotypes.removeAll(stereotype); return filteredStereotypes; } StereotypeIcon StereotypeController::findStereotypeIcon(const QString &stereotypeIconId) const { QMT_CHECK(d->m_iconIdToStereotypeIconsMap.contains(stereotypeIconId)); return d->m_iconIdToStereotypeIconsMap.value(stereotypeIconId); } CustomRelation StereotypeController::findCustomRelation(const QString &customRelationId) const { return d->m_relationIdToCustomRelationMap.value(customRelationId); } CustomRelation StereotypeController::findCustomRelationByStereotype(const QString &steoreotype) const { return d->m_stereotypeToCustomRelationMap.value(steoreotype); } QIcon StereotypeController::createIcon(StereotypeIcon::Element element, const QList &stereotypes, const FilePath &defaultIconPath, const Style *style, const QSize &size, const QMarginsF &margins, qreal lineWidth) { IconKey key(element, stereotypes, defaultIconPath, style->uid(), size, margins, lineWidth); QIcon icon = d->m_iconMap.value(key); if (!icon.isNull()) return icon; QString stereotypeIconId = findStereotypeIconId(element, stereotypes); if (!stereotypeIconId.isEmpty()) { StereotypeIcon stereotypeIcon = findStereotypeIcon(stereotypeIconId); // calculate bounding rectangle relativ to original icon size ShapeSizeVisitor sizeVisitor(QPointF(0.0, 0.0), QSizeF(stereotypeIcon.width(), stereotypeIcon.height()), QSizeF(stereotypeIcon.width(), stereotypeIcon.height()), QSizeF(stereotypeIcon.width(), stereotypeIcon.height())); stereotypeIcon.iconShape().visitShapes(&sizeVisitor); QRectF iconBoundingRect = sizeVisitor.boundingRect(); // calc painting space within margins qreal innerWidth = size.width() - margins.left() - margins.right(); qreal innerHeight = size.height() - margins.top() - margins.bottom(); // calculate width/height ratio from icon size qreal widthRatio = 1.0; qreal heightRatio = 1.0; qreal ratio = stereotypeIcon.width() / stereotypeIcon.height(); if (ratio > 1.0) heightRatio /= ratio; else widthRatio *= ratio; // calculate inner painting area qreal paintWidth = stereotypeIcon.width() * innerWidth / iconBoundingRect.width() * widthRatio; qreal paintHeight = stereotypeIcon.height() * innerHeight / iconBoundingRect.height() * heightRatio; // icons which renders smaller than their size should not be zoomed if (paintWidth > innerWidth) { paintHeight *= innerWidth / paintHeight; paintWidth = innerWidth; } if (paintHeight > innerHeight) { paintWidth *= innerHeight / paintHeight; paintHeight = innerHeight; } // calculate offset of top/left edge qreal paintLeft = iconBoundingRect.left() * paintWidth / stereotypeIcon.width(); qreal paintTop = iconBoundingRect.top() * paintHeight / stereotypeIcon.height(); // calculate total painting size qreal totalPaintWidth = iconBoundingRect.width() * paintWidth / stereotypeIcon.width(); qreal totalPaintHeight = iconBoundingRect.height() * paintHeight / stereotypeIcon.height(); QPixmap pixmap(size); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter.setBrush(Qt::NoBrush); // set painting origin taking margin, offset and centering into account painter.translate(QPointF(margins.left(), margins.top()) - QPointF(paintLeft, paintTop) + QPointF((innerWidth - totalPaintWidth) / 2, (innerHeight - totalPaintHeight) / 2)); QPen linePen = style->linePen(); linePen.setWidthF(lineWidth); painter.setPen(linePen); painter.setBrush(style->fillBrush()); ShapePaintVisitor visitor(&painter, QPointF(0.0, 0.0), QSizeF(stereotypeIcon.width(), stereotypeIcon.height()), QSizeF(paintWidth, paintHeight), QSizeF(paintWidth, paintHeight)); stereotypeIcon.iconShape().visitShapes(&visitor); icon = QIcon(pixmap); } if (icon.isNull() && !defaultIconPath.isEmpty()) icon = QIcon(defaultIconPath.toFSPathString()); d->m_iconMap.insert(key, icon); return icon; } void StereotypeController::addStereotypeIcon(const StereotypeIcon &stereotypeIcon) { if (stereotypeIcon.elements().isEmpty()) { const QSet stereotypes = stereotypeIcon.stereotypes(); for (const QString &stereotype : stereotypes) d->m_stereotypeToIconIdMap.insert({StereotypeIcon::ElementAny, stereotype}, stereotypeIcon.id()); } else { const QSet elements = stereotypeIcon.elements(); for (StereotypeIcon::Element element : elements) { const QSet stereotypes = stereotypeIcon.stereotypes(); for (const QString &stereotype : stereotypes) d->m_stereotypeToIconIdMap.insert({element, stereotype}, stereotypeIcon.id()); } } d->m_iconIdToStereotypeIconsMap.insert(stereotypeIcon.id(), stereotypeIcon); } void StereotypeController::addCustomRelation(const CustomRelation &customRelation) { d->m_relationIdToCustomRelationMap.insert(customRelation.id(), customRelation); QString stereotype = Utils::toList(customRelation.stereotypes()).value(0); if (!stereotype.isEmpty()) d->m_stereotypeToCustomRelationMap.insert(stereotype, customRelation); } void StereotypeController::addToolbar(const Toolbar &toolbar) { if (toolbar.elementTypes().isEmpty()) d->m_toolbars.append(toolbar); else d->m_elementToolbars.append(toolbar); } } // namespace qmt