File indexing completed on 2024-05-19 05:42:16
0001 // ct_lvtqtc_edgecollection.cpp -*-C++-*- 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <ct_lvtqtc_edgecollection.h> 0021 #include <ct_lvtqtc_lakosentity.h> 0022 0023 // TODO: circular component dependency 0024 #include <ct_lvtqtc_lakosrelation.h> 0025 0026 #include <QDebug> 0027 0028 #include <cmath> 0029 0030 namespace Codethink::lvtqtc { 0031 0032 static constexpr double INTER_RELATION_SPACING = 8.0; 0033 0034 struct EdgeCollection::Private { 0035 /*! \brief weight of the Edge 0036 */ 0037 double weight = 1.0; 0038 0039 /*! \brief A list of LakosRelation instances 0040 */ 0041 std::vector<LakosRelation *> relations; 0042 // All of the relations on this collection. 0043 0044 LakosEntity *from = nullptr; 0045 LakosEntity *to = nullptr; 0046 0047 PointFrom pointFrom = PointFrom::SOURCE; 0048 PointTo pointTo = PointTo::TARGET; 0049 0050 bool isRedundant = false; 0051 }; 0052 0053 EdgeCollection::EdgeCollection(): d(std::make_unique<EdgeCollection::Private>()) 0054 { 0055 } 0056 0057 EdgeCollection::~EdgeCollection() = default; 0058 0059 // TODO: This is a hack to make the filter correctly delete the memory if needed. 0060 // try to come with a better algorithm for this. 0061 void deleter(LakosRelation *relation) 0062 { 0063 delete relation; 0064 } 0065 0066 LakosEntity *EdgeCollection::from() const 0067 { 0068 return d->from; 0069 } 0070 0071 LakosEntity *EdgeCollection::to() const 0072 { 0073 return d->to; 0074 } 0075 0076 std::vector<LakosRelation *> EdgeCollection::relations() const 0077 { 0078 return d->relations; 0079 } 0080 0081 void EdgeCollection::setFrom(LakosEntity *from) 0082 { 0083 d->from = from; 0084 } 0085 0086 void EdgeCollection::setTo(LakosEntity *to) 0087 { 0088 d->to = to; 0089 } 0090 0091 LakosRelation *EdgeCollection::addRelation(LakosRelation *relation) 0092 { 0093 auto *filtered_relation = lvtshr::filterEdgeFromCollection<LakosRelation *>(d->relations, relation, deleter); 0094 if (filtered_relation) { 0095 return filtered_relation; 0096 } 0097 0098 d->relations.push_back(relation); 0099 // The edges with the most relations should be the shortest, 0100 // so reduce the weight each time a relation is added to the 0101 // edge. 0102 d->weight -= 0.15; 0103 return relation; 0104 } 0105 0106 void EdgeCollection::layoutSingleEdge(LakosRelation *relation, double dx, double dy) 0107 { 0108 if (relation->parentItem()) { 0109 // We need the item center point in scene coordinates. so first we get 0110 // the point, then we map to the view. 0111 // TODO: Check if we can do less transformations. 0112 auto relationSource = d->from->boundingRect().translated(d->from->pos()).translated(dx, dy).center(); 0113 relationSource = d->from->parentItem()->mapToScene(relationSource); 0114 relationSource = relation->parentItem()->mapFromScene(relationSource); 0115 0116 auto relationTarget = d->to->boundingRect().translated(d->to->pos()).translated(dx, dy).center(); 0117 relationTarget = d->to->parentItem()->mapToScene(relationTarget); 0118 relationTarget = relation->parentItem()->mapFromScene(relationTarget); 0119 0120 // TODO: Maybe we should set the lines always in scene coordinates? 0121 // This is in parent coordinates, *or* scene when parent is null. 0122 relation->setLine(QLineF(relationSource, relationTarget)); 0123 return; 0124 } 0125 0126 // No special case, we need to figure out if we can get the information 0127 // from the node or the parent, and we use scenePos instead of localPos 0128 QGraphicsItem *sourceEntity = nullptr; 0129 if (d->pointFrom == PointFrom::PARENT && d->from->parentItem()) { 0130 sourceEntity = d->from->parentItem(); 0131 } else { 0132 sourceEntity = d->from; 0133 } 0134 0135 QGraphicsItem *targetEntity = nullptr; 0136 if (d->pointTo == PointTo::PARENT && d->to->parentItem()) { 0137 targetEntity = d->to->parentItem(); 0138 } else { 0139 targetEntity = d->to; 0140 } 0141 0142 const QPointF relationSource = sourceEntity->mapToScene(sourceEntity->boundingRect().center()); 0143 const QPointF relationTarget = targetEntity->mapToScene(targetEntity->boundingRect().center()); 0144 0145 relation->setLine(QLineF(relationSource, relationTarget)); 0146 } 0147 0148 void EdgeCollection::layoutRelations() 0149 { 0150 if (d->relations.size() == 1) { 0151 layoutSingleEdge(d->relations[0], 0, 0); 0152 return; 0153 } 0154 0155 QPointF sourcePoint = d->from->scenePos(); 0156 QPointF targetPoint = d->to->scenePos(); 0157 0158 double x = sourcePoint.x() - targetPoint.x(); 0159 double y = sourcePoint.y() - targetPoint.y(); 0160 double length = std::sqrt((x * x) + (y * y)); 0161 0162 // Space out relations in the edge. 0163 // The spacings are at 90 degrees to the arrow, 0164 // and so the x and y coordinates are interchanged 0165 double ySpacing = (x / length) * INTER_RELATION_SPACING; 0166 double xSpacing = (y / length) * INTER_RELATION_SPACING; 0167 0168 // assume that d->relations.size() is a smallish integer. Theoretically it 0169 // could be ULLONG_MAX, but in practice we shouldn't see that many relations 0170 // If for some reason it was a very large unsigned integer, then the double 0171 // approximation will only be approximate 0172 const auto num_relations = static_cast<double>(d->relations.size()); 0173 0174 double dy = -(((num_relations - 1) * ySpacing) / 2.0); 0175 double dx = -(((num_relations - 1) * xSpacing) / 2.0); 0176 0177 for (auto *relation : d->relations) { 0178 layoutSingleEdge(relation, dx, dy); 0179 dy += ySpacing; 0180 dx += xSpacing; 0181 } 0182 } 0183 0184 void EdgeCollection::setHighlighted(bool highlighted) 0185 { 0186 toggleRelationFlags(RelationFlags::RelationIsParentHovered, highlighted); 0187 } 0188 0189 void EdgeCollection::setVisible(bool v) 0190 { 0191 for (auto *relation : d->relations) { 0192 relation->setVisible(v); 0193 } 0194 } 0195 0196 void EdgeCollection::setPointFrom(PointFrom entity) 0197 { 0198 d->pointFrom = entity; 0199 layoutRelations(); 0200 } 0201 0202 void EdgeCollection::setPointTo(PointTo entity) 0203 { 0204 d->pointTo = entity; 0205 layoutRelations(); 0206 } 0207 0208 void EdgeCollection::toggleRelationFlags(RelationFlags flags, bool toggle) 0209 { 0210 for (LakosRelation *relation : d->relations) { 0211 relation->toggleRelationFlags(flags, toggle); 0212 } 0213 } 0214 0215 void EdgeCollection::removeEdge(LakosRelation *edge) 0216 { 0217 auto it = std::find(std::begin(d->relations), std::end(d->relations), edge); 0218 if (it != std::end(d->relations)) { 0219 d->relations.erase(it); 0220 } 0221 } 0222 0223 void EdgeCollection::setRedundant(bool redundant) 0224 { 0225 d->isRedundant = redundant; 0226 0227 // TODO: Change the visibility status of the redundant edges? 0228 // maybe a semi-translucent color so they are not as agressive as 0229 // the normal edges? 0230 for (auto *edge : d->relations) { 0231 edge->update(); 0232 } 0233 } 0234 0235 bool EdgeCollection::isRedundant() const 0236 { 0237 return d->isRedundant; 0238 } 0239 0240 } // end namespace Codethink::lvtqtc