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

0001 // ct_lvtqtw_tool_add_package.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_package.h>
0021 
0022 #include <ct_lvtqtc_undo_add_package.h>
0023 
0024 #include <ct_lvtqtc_graphicsscene.h>
0025 #include <ct_lvtqtc_graphicsview.h>
0026 #include <ct_lvtqtc_packageentity.h>
0027 #include <ct_lvtshr_functional.h>
0028 
0029 #include <ct_lvtqtc_iconhelpers.h>
0030 #include <ct_lvtqtc_util.h>
0031 
0032 #include <ct_lvtldr_nodestorage.h>
0033 
0034 #include <QApplication>
0035 #include <QDebug>
0036 #include <QInputDialog>
0037 #include <QMessageBox>
0038 
0039 #include <preferences.h>
0040 
0041 using namespace Codethink::lvtldr;
0042 
0043 namespace Codethink::lvtqtc {
0044 
0045 struct ToolAddPackage::Private {
0046     NodeStorage& nodeStorage;
0047 
0048     explicit Private(NodeStorage& nodeStorage): nodeStorage(nodeStorage)
0049     {
0050     }
0051 };
0052 
0053 ToolAddPackage::ToolAddPackage(GraphicsView *gv, NodeStorage& nodeStorage):
0054     BaseAddEntityTool(tr("Package"), tr("Creates a Package"), IconHelpers::iconFrom(":/icons/package"), gv),
0055     d(std::make_unique<Private>(nodeStorage))
0056 {
0057     auto *inputDataDialog = inputDialog();
0058     inputDataDialog->addTextField("name", tr("Name:"));
0059     inputDataDialog->finish();
0060 }
0061 
0062 ToolAddPackage::~ToolAddPackage() = default;
0063 
0064 template<typename T>
0065 cpp::result<void, InvalidNameError>
0066 checkNameError(bool hasParent, const std::string& name, const std::string& parentName)
0067 {
0068     return T::checkName(hasParent, name, parentName);
0069 }
0070 
0071 void ToolAddPackage::activate()
0072 {
0073     Q_EMIT sendMessage(tr("Click on an empty spot to add a new package group,"
0074                           "or on a package group to add a new package."),
0075                        KMessageWidget::Information);
0076 
0077     BaseAddEntityTool::activate();
0078 }
0079 
0080 void ToolAddPackage::mouseReleaseEvent(QMouseEvent *event)
0081 {
0082     qCDebug(LogTool) << name() << "Mouse Release Event";
0083 
0084     using Codethink::lvtshr::ScopeExit;
0085     ScopeExit _([&]() {
0086         deactivate();
0087     });
0088 
0089     event->accept();
0090 
0091     auto *parentView = graphicsView()->itemByTypeAt<PackageEntity>(event->pos());
0092     auto *parent = parentView ? parentView->internalNode() : nullptr;
0093     auto parentName = parent ? parent->name() : "";
0094     auto parentQualifiedName = parent ? parent->qualifiedName() : "";
0095 
0096     m_nameDialog->setTextFieldValue("name", QString::fromStdString(parentName));
0097     if (m_nameDialog->exec() == QDialog::Rejected) {
0098         return;
0099     }
0100 
0101     // Verify if the names are correct / sane.
0102     const std::string name = std::any_cast<QString>(m_nameDialog->fieldValue("name")).toStdString();
0103     if (Preferences::useLakosianRules()) {
0104         const auto result = checkNameError<LakosianNameRules>(parent != nullptr, name, parentName);
0105         if (result.has_error()) {
0106             Q_EMIT sendMessage(result.error().what, KMessageWidget::Error);
0107             return;
0108         }
0109     }
0110 
0111     auto qualifiedName = parent ? parentName + "/" + name : name;
0112     auto *scene = qobject_cast<GraphicsScene *>(graphicsView()->scene());
0113 
0114     auto result = d->nodeStorage.addPackage(name, qualifiedName, parent, scene);
0115     if (result.has_error()) {
0116         using Kind = ErrorAddPackage::Kind;
0117         switch (result.error().kind) {
0118         case Kind::QualifiedNameAlreadyRegistered: {
0119             Q_EMIT sendMessage(
0120                 tr("Qualified name already registered %1").arg(QString::fromStdString(result.error().what)),
0121                 KMessageWidget::Error);
0122             return;
0123         }
0124         case Kind::CannotAddPackageToStandalonePackage: {
0125             Q_EMIT sendMessage(
0126                 tr("Cannot add a package to a standalone package (because it already contains a component)"),
0127                 KMessageWidget::Error);
0128             return;
0129         }
0130         case Kind::CantAddChildren: {
0131             Q_EMIT sendMessage(tr("Cannot add Children on the package"), KMessageWidget::Error);
0132             return;
0133         }
0134         }
0135     }
0136     auto *newPackage = result.value();
0137     Q_EMIT sendMessage(QString(), KMessageWidget::Information);
0138 
0139     const QPointF scenePos = graphicsView()->mapToScene(event->pos());
0140     const QPointF itemPos = parentView ? parentView->mapFromScene(scenePos) : scenePos;
0141     scene->setEntityPos(newPackage->uid(), itemPos);
0142     scene->updateBoundingRect();
0143 
0144     Q_EMIT undoCommandCreated(new UndoAddPackage(scene,
0145                                                  itemPos,
0146                                                  name,
0147                                                  qualifiedName,
0148                                                  parentQualifiedName,
0149                                                  QtcUtil::UndoActionType::e_Add,
0150                                                  d->nodeStorage));
0151 }
0152 
0153 cpp::result<void, InvalidNameError>
0154 LakosianNameRules::checkName(bool hasParent, const std::string& name, const std::string& parentName)
0155 {
0156     const auto header = QObject::tr("BDE Guidelines Enforced: ");
0157     if (hasParent) {
0158         if (name.find('_') != std::string::npos) {
0159             return cpp::fail(InvalidNameError{header + QObject::tr("Package names should not contain underscore")});
0160         }
0161         if (name.size() < 4) {
0162             return cpp::fail(InvalidNameError{
0163                 header + QObject::tr("Name too short, must be at least four letters (parent package(3) + name(1-5))")});
0164         }
0165 
0166         if (name.size() > 8) {
0167             return cpp::fail(InvalidNameError{
0168                 header + QObject::tr("Name too long, must be at most eight letters (parent package(3) + name(1-5))")});
0169         }
0170         if (!QString::fromStdString(name).startsWith(QString::fromStdString(parentName))) {
0171             return cpp::fail(InvalidNameError{header
0172                                               + QObject::tr("the package name must start with the parent's name (%1)")
0173                                                     .arg(QString::fromStdString(parentName))});
0174         }
0175     } else if (name.size() != 3) {
0176         return cpp::fail(InvalidNameError{header + QObject::tr("top level packages must be three letters long.")});
0177     }
0178     return {};
0179 }
0180 
0181 } // namespace Codethink::lvtqtc