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