From db1469f2194a9d466e963d511bd4be80ef4acf27 Mon Sep 17 00:00:00 2001 From: theo-vt Date: Sun, 8 Mar 2026 11:16:01 -0400 Subject: [PATCH] Add dialog for link insertion when selection buffer is empty --- src/Gui/CMakeLists.txt | 3 + src/Gui/CommandLink.cpp | 94 ++++++++++++++----------- src/Gui/TaskCommandLink.cpp | 135 ++++++++++++++++++++++++++++++++++++ src/Gui/TaskCommandLink.h | 80 +++++++++++++++++++++ src/Gui/TaskCommandLink.ui | 36 ++++++++++ 5 files changed, 307 insertions(+), 41 deletions(-) create mode 100644 src/Gui/TaskCommandLink.cpp create mode 100644 src/Gui/TaskCommandLink.h create mode 100644 src/Gui/TaskCommandLink.ui diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 9eceb4bfe8..953b68bd82 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -490,6 +490,7 @@ SET(Gui_UIC_SRCS InputVector.ui Placement.ui TaskTransform.ui + TaskCommandLink.ui TextureMapping.ui TaskView/TaskAppearance.ui TaskView/TaskOrientation.ui @@ -578,6 +579,7 @@ SET(Dialog_CPP_SRCS TaskDlgRelocation.cpp Dialogs/DlgCheckableMessageBox.cpp TaskTransform.cpp + TaskCommandLink.cpp Dialogs/DlgUndoRedo.cpp InputVector.cpp Placement.cpp @@ -620,6 +622,7 @@ SET(Dialog_HPP_SRCS Dialogs/DlgVersionMigrator.h TaskDlgRelocation.h TaskTransform.h + TaskCommandLink.h Dialogs/DlgUndoRedo.h InputVector.h Placement.h diff --git a/src/Gui/CommandLink.cpp b/src/Gui/CommandLink.cpp index 71ac668969..8d75ea0501 100644 --- a/src/Gui/CommandLink.cpp +++ b/src/Gui/CommandLink.cpp @@ -34,9 +34,11 @@ #include "Action.h" #include "Application.h" #include "Command.h" +#include "Control.h" #include "Document.h" #include "MainWindow.h" #include "Selection.h" +#include "TaskCommandLink.h" #include "Tree.h" #include "ViewProviderDocumentObject.h" #include "WaitCursor.h" @@ -280,6 +282,51 @@ void StdCmdLinkMake::activated(int) return; } + auto exec = [=](std::vector objs) { + doc->openTransaction(QT_TRANSLATE_NOOP("Command", "Make link")); + try { + if (objs.empty()) { + std::string name = doc->getUniqueObjectName("Link"); + Command::doCommand( + Command::Doc, + "App.getDocument('%s').addObject('App::Link','%s')", + doc->getName(), + name.c_str() + ); + Selection().addSelection(doc->getName(), name.c_str()); + } + else { + for (auto obj : objs) { + std::string name = doc->getUniqueObjectName("Link"); + Command::doCommand( + Command::Doc, + "App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument(" + "'%s'" + ").%s)", + doc->getName(), + name.c_str(), + obj->getDocument()->getName(), + obj->getNameInDocument() + ); + setLinkLabel(obj, doc->getName(), name.c_str()); + Selection().addSelection(doc->getName(), name.c_str()); + } + } + Selection().selStackPush(); + doc->commitTransaction(); + } + catch (const Base::Exception& e) { + doc->abortTransaction(); + QMessageBox::critical( + getMainWindow(), + QObject::tr("Create link failed"), + QString::fromLatin1(e.what()) + ); + e.reportException(); + } + }; + + std::set objs; for (auto& sel : Selection().getCompleteSelection()) { if (sel.pObject && sel.pObject->isAttachedToDocument()) { @@ -287,48 +334,13 @@ void StdCmdLinkMake::activated(int) } } - Selection().selStackPush(); - Selection().clearCompleteSelection(); - - Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make link")); - try { - if (objs.empty()) { - std::string name = doc->getUniqueObjectName("Link"); - Command::doCommand( - Command::Doc, - "App.getDocument('%s').addObject('App::Link','%s')", - doc->getName(), - name.c_str() - ); - Selection().addSelection(doc->getName(), name.c_str()); - } - else { - for (auto obj : objs) { - std::string name = doc->getUniqueObjectName("Link"); - Command::doCommand( - Command::Doc, - "App.getDocument('%s').addObject('App::Link','%s').setLink(App.getDocument('%s'" - ").%s)", - doc->getName(), - name.c_str(), - obj->getDocument()->getName(), - obj->getNameInDocument() - ); - setLinkLabel(obj, doc->getName(), name.c_str()); - Selection().addSelection(doc->getName(), name.c_str()); - } - } - Selection().selStackPush(); - Command::commitCommand(); + if (objs.empty()) { + Gui::Control().showDialog(new TaskCommandLinkDialog(exec)); } - catch (const Base::Exception& e) { - Command::abortCommand(); - QMessageBox::critical( - getMainWindow(), - QObject::tr("Create link failed"), - QString::fromLatin1(e.what()) - ); - e.reportException(); + else { + Selection().selStackPush(); + Selection().clearCompleteSelection(); + exec(std::vector(objs.begin(), objs.end())); } } diff --git a/src/Gui/TaskCommandLink.cpp b/src/Gui/TaskCommandLink.cpp new file mode 100644 index 0000000000..665858aacd --- /dev/null +++ b/src/Gui/TaskCommandLink.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (c) 2026 Théo Veilleux-Trinh * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "TaskCommandLink.h" + +#include "ui_TaskCommandLink.h" + +#include "Application.h" +#include "Document.h" +#include "ViewProvider.h" + +#include +#include +#include + +namespace Gui +{ +TaskCommandLink::TaskCommandLink() + : ui(new Ui_TaskCommandLinkDialog()) +{ + proxy = new QWidget(this); + ui->setupUi(proxy); + ui->objectsList->header()->hide(); + ui->objectsList->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection); + + this->groupLayout()->addWidget(proxy); + + buildObjectsList(); +} +TaskCommandLink::~TaskCommandLink() +{ + delete proxy; + delete ui; +} +std::vector TaskCommandLink::selectedObjects() +{ + auto selected = ui->objectsList->selectedItems(); + std::vector dst; + dst.reserve(selected.size()); + + for (auto sel : selected) { + dst.push_back(sel->data(0, Qt::UserRole).value()); + } + return dst; +} +void processObjectsHelper(std::vector objs, QTreeWidgetItem* item) +{ + for (auto obj : objs) { + auto objItem = new QTreeWidgetItem(item); + + objItem->setText(0, obj->Label.getValue()); + objItem->setData(0, Qt::UserRole, QVariant::fromValue(obj)); + + Gui::ViewProvider* vp = nullptr; + if (auto doc = Application::Instance->getDocument(obj->getDocument())) { + vp = doc->getViewProvider(obj); + } + if (vp) { + objItem->setIcon(0, vp->getIcon()); + processObjectsHelper(vp->claimChildren(), objItem); + } + else { + objItem->setIcon(0, QIcon()); + } + } +} +void TaskCommandLink::buildObjectsList() +{ + ui->objectsList->clear(); + + auto allDocuments = App::GetApplication().getDocuments(); + bool collapse = true; + std::map docItemMap; + + for (auto doc : allDocuments) { + auto docItem = new QTreeWidgetItem(); + std::string itemName = doc->Label.getValue(); + + docItem->setText(0, QString::fromStdString(itemName)); + docItem->setIcon(0, QIcon(QStringLiteral(":/icons/Document.svg"))); + docItem->setFlags(docItem->flags() & ~Qt::ItemIsSelectable); // Can't link a whole document + + docItemMap[docItem] = doc; + + ui->objectsList->addTopLevelItem(docItem); + + processObjectsHelper(Application::Instance->getDocument(doc)->getTreeRootObjects(), docItem); + + if (collapse) { + ui->objectsList->collapseAll(); + } + else { + ui->objectsList->expandToDepth(0); + } + } + ui->objectsList->selectedItems(); +} + +// dialog + +TaskCommandLinkDialog::TaskCommandLinkDialog( + std::function)> executor_ +) + : executor(executor_) +{ + commandLink = new TaskCommandLink(); + Content.push_back(commandLink); +} +void TaskCommandLinkDialog::open() +{} +bool TaskCommandLinkDialog::accept() +{ + executor(commandLink->selectedObjects()); + return true; +} +} // namespace Gui diff --git a/src/Gui/TaskCommandLink.h b/src/Gui/TaskCommandLink.h new file mode 100644 index 0000000000..7b1d0bba5a --- /dev/null +++ b/src/Gui/TaskCommandLink.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (c) 2026 Théo Veilleux-Trinh * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#pragma once + +#include "TaskView/TaskDialog.h" +#include "TaskView/TaskView.h" + +#include + +#include +#include + +namespace App +{ +class DocumentObject; +} + +namespace Gui +{ +class Document; +class Ui_TaskCommandLinkDialog; + +class TaskCommandLink: public Gui::TaskView::TaskBox +{ +public: + TaskCommandLink(); + ~TaskCommandLink(); + + std::vector selectedObjects(); + +private: + void buildObjectsList(); + +private: + Ui_TaskCommandLinkDialog* ui {nullptr}; + QWidget* proxy {nullptr}; +}; + +class TaskCommandLinkDialog: public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskCommandLinkDialog(std::function)> executor_); + ~TaskCommandLinkDialog() override = default; + + QDialogButtonBox::StandardButtons getStandardButtons() const override + { + return QDialogButtonBox::Ok | QDialogButtonBox::Cancel; + } + + void open() override; + bool accept() override; + +private: + TaskCommandLink* commandLink {nullptr}; + Gui::Document* document {nullptr}; + std::function)> executor; +}; +} // namespace Gui diff --git a/src/Gui/TaskCommandLink.ui b/src/Gui/TaskCommandLink.ui new file mode 100644 index 0000000000..21026ad7cc --- /dev/null +++ b/src/Gui/TaskCommandLink.ui @@ -0,0 +1,36 @@ + + + Gui::TaskCommandLinkDialog + + + + 0 + 0 + 340 + 212 + + + + Insert + + + + + + + 0 + 0 + + + + + 1 + + + + + + + + +