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