File indexing completed on 2024-05-19 05:42:22
0001 // ct_lvtqtw_tool_add_component.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_tool_add_component.h> 0021 0022 #include <ct_lvtldr_lakosiannode.h> 0023 #include <ct_lvtldr_nodestorage.h> 0024 #include <ct_lvtqtc_graphicsscene.h> 0025 #include <ct_lvtqtc_graphicsview.h> 0026 #include <ct_lvtqtc_iconhelpers.h> 0027 #include <ct_lvtqtc_inputdialog.h> 0028 #include <ct_lvtqtc_packageentity.h> 0029 #include <ct_lvtqtc_undo_add_component.h> 0030 #include <ct_lvtqtc_util.h> 0031 #include <ct_lvtshr_functional.h> 0032 0033 #include <QApplication> 0034 #include <QDebug> 0035 #include <QInputDialog> 0036 #include <QMessageBox> 0037 0038 #include <preferences.h> 0039 0040 using namespace Codethink::lvtldr; 0041 0042 namespace Codethink::lvtqtc { 0043 0044 struct ToolAddComponent::Private { 0045 NodeStorage& nodeStorage; 0046 0047 explicit Private(NodeStorage& nodeStorage): nodeStorage(nodeStorage) 0048 { 0049 } 0050 }; 0051 0052 ToolAddComponent::ToolAddComponent(GraphicsView *gv, NodeStorage& nodeStorage): 0053 BaseAddEntityTool(tr("Component"), tr("Creates a Component"), IconHelpers::iconFrom(":/icons/component"), gv), 0054 d(std::make_unique<Private>(nodeStorage)) 0055 { 0056 auto *inputDataDialog = inputDialog(); 0057 inputDataDialog->addTextField("name", tr("Name:")); 0058 inputDataDialog->finish(); 0059 } 0060 0061 ToolAddComponent::~ToolAddComponent() = default; 0062 0063 template<typename T> 0064 cpp::result<void, InvalidComponentError> 0065 checkNameError(bool hasParent, const std::string& name, const std::string& parentName) 0066 { 0067 return T::checkName(hasParent, name, parentName); 0068 } 0069 0070 void ToolAddComponent::activate() 0071 { 0072 Q_EMIT sendMessage(tr("Click on a Package or Package Group to add a new Component"), KMessageWidget::Information); 0073 BaseAddEntityTool::activate(); 0074 } 0075 0076 void ToolAddComponent::mousePressEvent(QMouseEvent *event) 0077 { 0078 qCDebug(LogTool) << name() << "Mouse Press Event"; 0079 0080 using Codethink::lvtshr::ScopeExit; 0081 ScopeExit _([&]() { 0082 deactivate(); 0083 }); 0084 0085 auto *parent = graphicsView()->itemByTypeAt<PackageEntity>(event->pos()); 0086 if (!parent) { 0087 Q_EMIT sendMessage(tr("You can't create a component outside of packages."), KMessageWidget::Error); 0088 return; 0089 } 0090 0091 m_nameDialog->setTextFieldValue("name", QString::fromStdString(parent->name()) + "_"); 0092 if (m_nameDialog->exec() == QDialog::Rejected) { 0093 qCDebug(LogTool) << "Add component rejected by the user"; 0094 return; 0095 } 0096 0097 const std::string name = std::any_cast<QString>(m_nameDialog->fieldValue("name")).toStdString(); 0098 0099 // Verify if the name meets Lakosian rules 0100 if (Preferences::useLakosianRules()) { 0101 const auto result = checkNameError<LakosianComponentNameRules>(true, name, parent->name()); 0102 if (result.has_error()) { 0103 Q_EMIT sendMessage(result.error().what, KMessageWidget::Error); 0104 return; 0105 } 0106 } 0107 0108 auto qualifiedName = parent->qualifiedName() + "/" + name; 0109 auto result = d->nodeStorage.addComponent(name, qualifiedName, parent->internalNode()); 0110 if (result.has_error()) { 0111 using Kind = ErrorAddComponent::Kind; 0112 switch (result.error().kind) { 0113 case Kind::MissingParent: { 0114 assert(false && "Unexpected missing parent"); 0115 } 0116 0117 case Kind::QualifiedNameAlreadyRegistered: { 0118 Q_EMIT sendMessage(tr("You can't create a new package with the same name of an existing package"), 0119 KMessageWidget::Error); 0120 return; 0121 } 0122 case Kind::CannotAddComponentToPkgGroup: { 0123 Q_EMIT sendMessage(tr("Cannot add a component to a package group"), KMessageWidget::Error); 0124 return; 0125 } 0126 } 0127 } 0128 0129 Q_EMIT sendMessage(QString(), KMessageWidget::Information); 0130 0131 auto *newNode = result.value(); 0132 const QPointF scenePos = graphicsView()->mapToScene(event->pos()); 0133 const QPointF itemPos = parent->mapFromScene(scenePos); 0134 auto *scene = qobject_cast<GraphicsScene *>(graphicsView()->scene()); 0135 scene->setEntityPos(newNode->uid(), itemPos); 0136 scene->updateBoundingRect(); 0137 Q_EMIT undoCommandCreated(new UndoAddComponent(scene, 0138 itemPos, 0139 name, 0140 qualifiedName, 0141 parent->qualifiedName(), 0142 QtcUtil::UndoActionType::e_Add, 0143 d->nodeStorage)); 0144 } 0145 0146 cpp::result<void, InvalidComponentError> 0147 LakosianComponentNameRules::checkName(bool hasParent, const std::string& name, const std::string& parentName) 0148 { 0149 const auto header = QObject::tr("BDE Guidelines Enforced: "); 0150 if (!hasParent) { 0151 return cpp::fail(InvalidComponentError{header + QObject::tr("Components must be added within packages.")}); 0152 } 0153 0154 // rule: the company can add a few letters to the filename to identify that the 0155 // file is from the company, but that's not required. 0156 // and the package name *must* be written before the component name. 0157 auto numUnderscore = std::count_if(std::begin(name), std::end(name), [](char a) { 0158 return a == '_'; 0159 }); 0160 if (numUnderscore == 0) { 0161 return cpp::fail(InvalidComponentError{header 0162 + QObject::tr("Invalid name. name must be in the form %1_component") 0163 .arg(QString::fromStdString(parentName))}); 0164 } 0165 0166 // here we know that the start of the string *needs* to be the parent package. 0167 if (numUnderscore == 1) { 0168 if (!QString::fromStdString(name).startsWith(QString::fromStdString(parentName))) { 0169 return cpp::fail(InvalidComponentError{header 0170 + QObject::tr("Invalid name. name must be in the form %1_component") 0171 .arg(QString::fromStdString(parentName))}); 0172 } 0173 } 0174 0175 // the first, or the second values must be the name of the parent package. 0176 if (numUnderscore >= 2) { 0177 std::vector<std::string> split_tmp; 0178 auto qtVector = QString::fromStdString(name).split(QString::fromStdString("_")); 0179 auto stdVector = std::vector<std::string>{}; 0180 std::transform(qtVector.begin(), qtVector.end(), std::back_inserter(split_tmp), [](auto&& qtString) { 0181 return qtString.toStdString(); 0182 }); 0183 0184 if (!QString::fromStdString(split_tmp[0]).startsWith(QString::fromStdString(parentName)) 0185 && !QString::fromStdString(split_tmp[1]).startsWith(QString::fromStdString(parentName))) { 0186 return cpp::fail(InvalidComponentError{header 0187 + QObject::tr("Invalid name. name must be in the form %1_component") 0188 .arg(QString::fromStdString(parentName))}); 0189 } 0190 } 0191 0192 return {}; 0193 } 0194 0195 } // namespace Codethink::lvtqtc