File indexing completed on 2024-05-19 05:42:13

0001 // ct_lvtmdl_modelhelpers.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 <QApplication>
0021 #include <QStyledItemDelegate>
0022 #include <ct_lvtldr_lakosiannode.h>
0023 #include <ct_lvtldr_packagenode.h>
0024 #include <ct_lvtmdl_modelhelpers.h>
0025 
0026 namespace Codethink::lvtmdl {
0027 
0028 using namespace Codethink::lvtldr;
0029 using namespace Codethink::lvtmdl;
0030 using lvtshr::DiagramType;
0031 
0032 NodeType::Enum NodeType::fromDiagramType(lvtshr::DiagramType type)
0033 {
0034     using lvtshr::DiagramType;
0035     switch (type) {
0036     case DiagramType::ClassType:
0037         return NodeType::e_Class;
0038     case DiagramType::ComponentType:
0039         return NodeType::e_Component;
0040     case DiagramType::PackageType:
0041         return NodeType::e_Package;
0042     case DiagramType::RepositoryType:
0043         return NodeType::e_Repository;
0044     case DiagramType::FreeFunctionType:
0045         return NodeType::e_FreeFunction;
0046     case DiagramType::NoneType:
0047         break;
0048     }
0049     return NodeType::e_Invalid;
0050 }
0051 
0052 lvtshr::DiagramType NodeType::toDiagramType(NodeType::Enum type)
0053 {
0054     switch (type) {
0055     case NodeType::e_Class:
0056         return DiagramType::ClassType;
0057     case NodeType::e_Component:
0058         return DiagramType::ComponentType;
0059     case NodeType::e_Package:
0060         return DiagramType::PackageType;
0061     case e_Namespace:
0062         break;
0063     case e_Repository:
0064         return DiagramType::RepositoryType;
0065     case NodeType::e_FreeFunction:
0066         return DiagramType::FreeFunctionType;
0067     case e_Invalid:
0068         break;
0069     }
0070     return DiagramType::NoneType;
0071 }
0072 
0073 std::tuple<LakosianNode::IsLakosianResult, std::optional<QString>> nodeIsRecursivelyLakosian(LakosianNode& node)
0074 {
0075     auto isLakosianResult = node.isLakosian();
0076     auto notLakosianMessage = [&]() -> std::optional<QString> {
0077         switch (isLakosianResult) {
0078         case LakosianNode::IsLakosianResult::IsLakosian: {
0079             return std::nullopt;
0080         }
0081         case LakosianNode::IsLakosianResult::ComponentHasNoPackage: {
0082             return QObject::tr("Component %1 has no package.").arg(QString::fromStdString(node.name()));
0083         }
0084         case LakosianNode::IsLakosianResult::ComponentDoesntStartWithParentName: {
0085             return QObject::tr("Component %1 does not starts with the parent name %2")
0086                 .arg(QString::fromStdString(node.name()),
0087                      QString::fromStdString(dynamic_cast<PackageNode *>(node.parent())->canonicalName()));
0088         }
0089         case LakosianNode::IsLakosianResult::PackageParentIsNotGroup: {
0090             return QObject::tr("Parent package is not a package group");
0091         }
0092         case LakosianNode::IsLakosianResult::PackagePrefixDiffersFromGroup: {
0093             return QObject::tr("The package %1 prefix differs from the package group")
0094                 .arg(QString::fromStdString(node.name()));
0095         }
0096         case LakosianNode::IsLakosianResult::PackageNameInvalidNumberOfChars: {
0097             return QObject::tr("The package %1 name is not between 3 and 6 characters")
0098                 .arg(QString::fromStdString(node.name()));
0099         }
0100         case LakosianNode::IsLakosianResult::PackageGroupNameInvalidNumberOfChars: {
0101             return QObject::tr("Package groups must be three letter long, but %1 doesn't")
0102                 .arg(QString::fromStdString(node.name()));
0103         }
0104         }
0105         return std::nullopt;
0106     }();
0107 
0108     if (isLakosianResult != LakosianNode::IsLakosianResult::IsLakosian) {
0109         return {isLakosianResult, notLakosianMessage};
0110     }
0111 
0112     if (node.type() == DiagramType::PackageType) {
0113         // Only check for children for packages and package groups, since we assume that all classes inside any
0114         // Lakosian Component are also Lakosian.
0115         for (auto *child : node.children()) {
0116             auto isChildLakosianResultAndMessage = nodeIsRecursivelyLakosian(*child);
0117             if (std::get<0>(isChildLakosianResultAndMessage) != LakosianNode::IsLakosianResult::IsLakosian) {
0118                 return isChildLakosianResultAndMessage;
0119             }
0120         }
0121     }
0122 
0123     return {LakosianNode::IsLakosianResult::IsLakosian, std::nullopt};
0124 }
0125 
0126 QIcon getIconFor(LakosianNode& node)
0127 {
0128     if (node.type() == DiagramType::RepositoryType) {
0129         static const auto iconRepo = QIcon(":/icons/repository");
0130         return iconRepo;
0131     }
0132 
0133     if (node.type() == DiagramType::ClassType) {
0134         static const auto iconClass = QIcon(":/icons/class");
0135         return iconClass;
0136     }
0137 
0138     if (node.isLakosian() == LakosianNode::IsLakosianResult::IsLakosian) {
0139         if (node.type() == DiagramType::PackageType) {
0140             static const auto iconPkg = QIcon(":/icons/package");
0141             return iconPkg;
0142         }
0143         if (node.type() == DiagramType::ComponentType) {
0144             static const auto iconComponent = QIcon(":/icons/component");
0145             return iconComponent;
0146         }
0147     } else {
0148         if (node.type() == DiagramType::PackageType) {
0149             static const auto iconFolder = QIcon(":/icons/folder");
0150             return iconFolder;
0151         }
0152         if (node.type() == DiagramType::ComponentType) {
0153             static const auto iconFile = QIcon(":/icons/file");
0154             return iconFile;
0155         }
0156     }
0157 
0158     static const auto iconHelp = QIcon(":/icons/help");
0159     return iconHelp;
0160 }
0161 
0162 QStandardItem *
0163 ModelUtil::createTreeItemFromLakosianNode(LakosianNode& node,
0164                                           std::optional<ShouldPopulateChildren_f> const& shouldPopulateChildren)
0165 // Creates an element that can hold inner elements
0166 // like a package or a component.
0167 {
0168     const auto [isLakosianResult, notLakosianMessage] = nodeIsRecursivelyLakosian(node);
0169     auto *item = new QStandardItem();
0170     item->setText(QString::fromStdString(node.name()));
0171     item->setIcon(getIconFor(node));
0172     item->setData(node.id(), ModelRoles::e_Id);
0173     item->setData(true, ModelRoles::e_IsBranch);
0174     item->setData((int) NodeType::fromDiagramType(node.type()), ModelRoles::e_NodeType);
0175     item->setData(QString::fromStdString(node.qualifiedName()), ModelRoles::e_QualifiedName);
0176     item->setData(isLakosianResult == LakosianNode::IsLakosianResult::IsLakosian, ModelRoles::e_RecursiveLakosian);
0177     if (notLakosianMessage) {
0178         item->setData(*notLakosianMessage, Qt::ToolTipRole);
0179     }
0180 
0181     if (shouldPopulateChildren.has_value() && (*shouldPopulateChildren)(node)) {
0182         ModelUtil::populateTreeItemChildren(node, *item, shouldPopulateChildren);
0183         item->setData(true, ModelRoles::e_ChildItemsLoaded);
0184     } else {
0185         // Dummy item so that Qt creates an "expandable" parent
0186         item->appendRow(new QStandardItem());
0187         item->setData(false, ModelRoles::e_ChildItemsLoaded);
0188     }
0189 
0190     return item;
0191 }
0192 
0193 void ModelUtil::populateTreeItemChildren(lvtldr::LakosianNode& node,
0194                                          QStandardItem& item,
0195                                          std::optional<ShouldPopulateChildren_f> const& shouldPopulateChildren)
0196 {
0197     std::vector<LakosianNode *> children = node.children();
0198     if (children.empty()) {
0199         item.setData(false, ModelRoles::e_IsBranch);
0200         return;
0201     }
0202 
0203     std::sort(children.begin(), children.end(), [](LakosianNode *l, LakosianNode *r) {
0204         if (l->name() == "non-lakosian group") {
0205             return false;
0206         }
0207         if (r->name() == "non-lakosian group") {
0208             return true;
0209         }
0210         return l->name() < r->name();
0211     });
0212     if (node.type() == DiagramType::RepositoryType) {
0213         // In case of a repository tree item, we do not want to show the inner packages as children of the repository,
0214         // we only want the toplevel items to be children of the repository, so we need to unconsider everything else.
0215         children.erase(std::remove_if(children.begin(),
0216                                       children.end(),
0217                                       [](auto&& child) {
0218                                           if (!child->parent()) {
0219                                               return true;
0220                                           }
0221 
0222                                           if (child->parent()->type() != lvtshr::DiagramType::RepositoryType) {
0223                                               return true;
0224                                           }
0225 
0226                                           return false;
0227                                       }),
0228                        children.end());
0229     }
0230     QList<QStandardItem *> childItems;
0231     for (auto *child : children) {
0232         childItems.push_back(ModelUtil::createTreeItemFromLakosianNode(*child, shouldPopulateChildren));
0233     }
0234     item.appendRows(childItems);
0235 }
0236 
0237 } // namespace Codethink::lvtmdl