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