Files
create/src/Gui/CommandLink.cpp
2025-11-11 13:49:01 +01:00

1074 lines
34 KiB
C++

/****************************************************************************
* Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* 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 <sstream>
#include <QMessageBox>
#include <App/Application.h>
#include <App/ElementNamingUtils.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/Link.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
#include "Action.h"
#include "Application.h"
#include "Command.h"
#include "Document.h"
#include "MainWindow.h"
#include "Selection.h"
#include "Tree.h"
#include "ViewProviderDocumentObject.h"
#include "WaitCursor.h"
FC_LOG_LEVEL_INIT("CommandLink", true, true)
using namespace Gui;
static void setLinkLabel(App::DocumentObject* obj, const char* doc, const char* name)
{
std::string label = obj->Label.getValue();
label = Base::Tools::escapeEncodeString(label);
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').Label='%s'",
doc,
name,
label.c_str()
);
}
////////////////////////////////////////////////////////////////////////////////////////////
class StdCmdLinkMakeGroup: public Gui::Command
{
public:
StdCmdLinkMakeGroup();
const char* className() const override
{
return "StdCmdLinkMakeGroup";
}
protected:
void activated(int iMsg) override;
bool isActive() override;
Action* createAction() override;
void languageChange() override;
};
StdCmdLinkMakeGroup::StdCmdLinkMakeGroup()
: Command("Std_LinkMakeGroup")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Link Group");
sToolTipText = QT_TR_NOOP("Creates a group of links");
sWhatsThis = "Std_LinkMakeGroup";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "LinkGroup";
}
bool StdCmdLinkMakeGroup::isActive()
{
return !!App::GetApplication().getActiveDocument();
}
Action* StdCmdLinkMakeGroup::createAction()
{
auto pcAction = new ActionGroup(this, getMainWindow());
pcAction->setDropDownMenu(true);
applyCommandData(this->className(), pcAction);
// add the action items
QAction* action = nullptr;
action = pcAction->addAction(QObject::tr("Simple Group"));
action->setWhatsThis(QString::fromLatin1(getWhatsThis()));
action = pcAction->addAction(QObject::tr("Group With Links"));
action->setWhatsThis(QString::fromLatin1(getWhatsThis()));
action = pcAction->addAction(QObject::tr("Group With Transform Links"));
action->setWhatsThis(QString::fromLatin1(getWhatsThis()));
return pcAction;
}
void StdCmdLinkMakeGroup::languageChange()
{
Command::languageChange();
if (!_pcAction) {
return;
}
auto pcAction = qobject_cast<ActionGroup*>(_pcAction);
QList<QAction*> acts = pcAction->actions();
acts[0]->setText(QObject::tr("Simple Group"));
acts[1]->setText(QObject::tr("Group With Links"));
acts[2]->setText(QObject::tr("Group With Transform Links"));
}
void StdCmdLinkMakeGroup::activated(int option)
{
std::vector<App::DocumentObject*> objs;
std::set<App::DocumentObject*> objset;
auto doc = App::GetApplication().getActiveDocument();
if (!doc) {
FC_ERR("no active document");
return;
}
for (auto& sel : Selection().getCompleteSelection()) {
if (sel.pObject && sel.pObject->isAttachedToDocument() && objset.insert(sel.pObject).second) {
objs.push_back(sel.pObject);
}
}
Selection().selStackPush();
Selection().clearCompleteSelection();
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make link group"));
try {
std::string groupName = doc->getUniqueObjectName("LinkGroup");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::LinkGroup','%s')",
doc->getName(),
groupName.c_str()
);
if (objs.empty()) {
Selection().addSelection(doc->getName(), groupName.c_str());
Selection().selStackPush();
}
else {
Command::doCommand(Command::Doc, "__objs__ = []");
for (auto obj : objs) {
std::string name;
if (option != 0 || doc != obj->getDocument()) {
name = doc->getUniqueObjectName("Link");
Command::doCommand(
Command::Doc,
"App.getDocument('%s').addObject('App::Link','%s').setLink("
"App.getDocument('%s').getObject('%s'))",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
setLinkLabel(obj, doc->getName(), name.c_str());
if (option == 2) {
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').LinkTransform = True",
doc->getName(),
name.c_str()
);
}
else if (obj->getPropertyByName("Placement")) {
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').Placement = "
"App.getDocument('%s').getObject('%s').Placement",
doc->getName(),
name.c_str(),
obj->getDocument()->getName(),
obj->getNameInDocument()
);
}
}
else {
name = obj->getNameInDocument();
}
Command::doCommand(
Command::Doc,
"__objs__.append(App.getDocument('%s').getObject('%s'))",
doc->getName(),
name.c_str()
);
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').ViewObject.Visibility=False",
doc->getName(),
name.c_str()
);
}
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').setLink(__objs__)",
doc->getName(),
groupName.c_str()
);
Command::doCommand(Command::Doc, "del __objs__");
for (size_t i = 0; i < objs.size(); ++i) {
auto name = std::to_string(i) + ".";
Selection().addSelection(doc->getName(), groupName.c_str(), name.c_str());
}
Selection().selStackPush();
}
if (option != 0) {
Command::doCommand(
Command::Doc,
"App.getDocument('%s').getObject('%s').LinkMode = 'Auto Delete'",
doc->getName(),
groupName.c_str()
);
}
Command::commitCommand();
}
catch (const Base::Exception& e) {
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link group failed"),
QString::fromLatin1(e.what())
);
Command::abortCommand();
e.reportException();
}
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkMake)
StdCmdLinkMake::StdCmdLinkMake()
: Command("Std_LinkMake")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Make Link");
sToolTipText = QT_TR_NOOP(
"A link is an object that references another object, either within the same "
"or in another document. Unlike clones, links reference the original shape directly, "
"making them more memory-efficient, which helps with the creation of complex assemblies."
);
sWhatsThis = "Std_LinkMake";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "Link";
}
bool StdCmdLinkMake::isActive()
{
return App::GetApplication().getActiveDocument();
}
void StdCmdLinkMake::activated(int)
{
auto doc = App::GetApplication().getActiveDocument();
if (!doc) {
FC_ERR("no active document");
return;
}
std::set<App::DocumentObject*> objs;
for (auto& sel : Selection().getCompleteSelection()) {
if (sel.pObject && sel.pObject->isAttachedToDocument()) {
objs.insert(sel.pObject);
}
}
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();
}
catch (const Base::Exception& e) {
Command::abortCommand();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Create link failed"),
QString::fromLatin1(e.what())
);
e.reportException();
}
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkMakeRelative)
StdCmdLinkMakeRelative::StdCmdLinkMakeRelative()
: Command("Std_LinkMakeRelative")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Make Sub-Link");
sToolTipText = QT_TR_NOOP("Creates a sub-object or sub-element link");
sWhatsThis = "Std_LinkMakeRelative";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "LinkSub";
}
bool StdCmdLinkMakeRelative::isActive()
{
return Selection().hasSubSelection(nullptr, true);
}
void StdCmdLinkMakeRelative::activated(int)
{
auto doc = App::GetApplication().getActiveDocument();
if (!doc) {
FC_ERR("no active document");
return;
}
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Make sub-link"));
try {
std::map<
std::pair<App::DocumentObject*, std::string>,
std::pair<App::DocumentObject*, std::vector<std::string>>>
linkInfo;
for (auto& sel : Selection().getCompleteSelection(ResolveMode::NoResolve)) {
if (!sel.pObject || !sel.pObject->isAttachedToDocument()) {
continue;
}
auto key = std::make_pair(sel.pObject, Data::noElementName(sel.SubName));
auto element = Data::findElementName(sel.SubName);
auto& info = linkInfo[key];
info.first = sel.pResolvedObject;
if (!Base::Tools::isNullOrEmpty(element)) {
info.second.emplace_back(element);
}
}
Selection().selStackPush();
Selection().clearCompleteSelection();
for (auto& v : linkInfo) {
auto& key = v.first;
auto& info = v.second;
std::string name = doc->getUniqueObjectName("Link");
std::ostringstream ss;
ss << '[';
for (auto& s : info.second) {
ss << "'" << s << "',";
}
ss << ']';
FCMD_DOC_CMD(
doc,
"addObject('App::Link','" << name << "').setLink(" << getObjectCmd(key.first)
<< ",'" << key.second << "'," << ss.str() << ")"
);
auto link = doc->getObject(name.c_str());
FCMD_OBJ_CMD(link, "LinkTransform = True");
setLinkLabel(info.first, doc->getName(), name.c_str());
Selection().addSelection(doc->getName(), name.c_str());
}
Selection().selStackPush();
Command::commitCommand();
}
catch (const Base::Exception& e) {
Command::abortCommand();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Failed to create relative link"),
QString::fromLatin1(e.what())
);
e.reportException();
}
return;
}
/////////////////////////////////////////////////////////////////////////////////////
struct Info
{
bool inited = false;
App::DocumentObjectT topParent;
std::string subname;
App::DocumentObjectT parent;
App::DocumentObjectT obj;
};
static void linkConvert(bool unlink)
{
// We are trying to replace an object with a link (App::Link), or replace a
// link back to its linked object (i.e. unlink). This is a very complex
// operation. It works by reassign the link property of the parent of the
// selected object(s) to a newly created link to the original object.
// Everything should remain the same. This complexity is now largely handled
// by ViewProviderDocumentObject::replaceObject(), which in turn relies on
// PropertyLinkBase::CopyOnLinkReplace().
std::map<std::pair<App::DocumentObject*, App::DocumentObject*>, Info> infos;
for (const auto& sel : TreeWidget::getSelection()) {
auto obj = sel.vp->getObject();
auto parent = sel.parentVp;
if (!parent) {
FC_WARN("skip '" << obj->getFullName() << "' with no parent");
continue;
}
auto parentObj = parent->getObject();
auto& info = infos[std::make_pair(parentObj, obj)];
if (info.inited) {
continue;
}
info.inited = true;
if (unlink) {
auto linked = obj->getLinkedObject(false);
if (!linked || !linked->isAttachedToDocument() || linked == obj) {
FC_WARN("skip non link");
continue;
}
}
info.topParent = sel.topParent;
info.parent = parentObj;
info.obj = obj;
}
if (infos.empty()) {
return;
}
Selection().selStackPush();
Selection().clearCompleteSelection();
// now, do actual operation
const char* transactionName = unlink ? "Unlink" : "Replace with link";
Command::openCommand(transactionName);
try {
std::unordered_map<App::DocumentObject*, App::DocumentObjectT> recomputeSet;
for (auto& v : infos) {
auto& info = v.second;
auto parent = info.parent.getObject();
auto parentVp = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(parent)
);
auto obj = info.obj.getObject();
if (!parent || !obj || !parentVp) {
continue;
}
if (!recomputeSet.contains(parent)) {
recomputeSet.emplace(parent, parent);
}
auto doc = parent->getDocument();
App::DocumentObject* replaceObj;
if (unlink) {
replaceObj = obj->getLinkedObject(false);
if (!replaceObj || !replaceObj->isAttachedToDocument() || replaceObj == obj) {
continue;
}
}
else {
auto name = doc->getUniqueObjectName("Link");
auto link = doc->addObject<App::Link>(name.c_str());
if (!link) {
FC_THROWM(Base::RuntimeError, "Failed to create link");
}
link->setLink(-1, obj);
link->Label.setValue(obj->Label.getValue());
auto pla = freecad_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
if (pla) {
link->Placement.setValue(pla->getValue());
}
else {
link->LinkTransform.setValue(true);
}
replaceObj = link;
}
// adjust subname for the new object
auto pos = info.subname.rfind('.');
if (pos == std::string::npos && pos) {
info.subname.clear();
}
else {
pos = info.subname.rfind('.', pos - 1);
if (pos == std::string::npos) {
info.subname.clear();
}
else {
info.subname.resize(pos + 1);
info.subname += replaceObj->getNameInDocument();
info.subname += ".";
}
}
// do the replacement operation
if (parentVp->replaceObject(obj, replaceObj) <= 0) {
FC_THROWM(Base::RuntimeError, "Failed to change link for " << parent->getFullName());
}
}
std::vector<App::DocumentObject*> recomputes;
for (auto& v : recomputeSet) {
auto obj = v.second.getObject();
if (obj) {
recomputes.push_back(obj);
}
}
if (!recomputes.empty()) {
recomputes.front()->getDocument()->recompute(recomputes);
}
Command::commitCommand();
}
catch (const Base::Exception& e) {
Command::abortCommand();
auto title = unlink ? QObject::tr("Unlink failed") : QObject::tr("Replace link failed");
QMessageBox::critical(getMainWindow(), title, QString::fromLatin1(e.what()));
e.reportException();
return;
}
}
static bool linkConvertible(bool unlink)
{
int count = 0;
for (auto& sel : TreeWidget::getSelection()) {
auto parent = sel.parentVp;
if (!parent) {
return false;
}
auto obj = sel.vp->getObject();
if (unlink) {
auto linked = obj->getLinkedObject(false);
if (!linked || linked == obj) {
return false;
}
}
++count;
}
return count != 0;
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkReplace)
StdCmdLinkReplace::StdCmdLinkReplace()
: Command("Std_LinkReplace")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Replace With Link");
sToolTipText = QT_TR_NOOP("Replaces the selected objects with links");
sWhatsThis = "Std_LinkReplace";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "LinkReplace";
}
bool StdCmdLinkReplace::isActive()
{
return linkConvertible(false);
}
void StdCmdLinkReplace::activated(int)
{
linkConvert(false);
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkUnlink)
StdCmdLinkUnlink::StdCmdLinkUnlink()
: Command("Std_LinkUnlink")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Unlink");
sToolTipText = QT_TR_NOOP("Unlinks the object by placing it directly in the container");
sWhatsThis = "Std_LinkUnlink";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "Unlink";
}
bool StdCmdLinkUnlink::isActive()
{
return linkConvertible(true);
}
void StdCmdLinkUnlink::activated(int)
{
linkConvert(true);
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkImport)
StdCmdLinkImport::StdCmdLinkImport()
: Command("Std_LinkImport")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Import Links");
sToolTipText = QT_TR_NOOP("Imports selected external links");
sWhatsThis = "Std_LinkImport";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "LinkImport";
}
static std::map<App::Document*, std::vector<App::DocumentObject*>> getLinkImportSelections()
{
std::map<App::Document*, std::vector<App::DocumentObject*>> objMap;
for (auto& sel : Selection().getCompleteSelection(ResolveMode::NoResolve)) {
auto obj = sel.pObject->resolve(sel.SubName);
if (!obj || !obj->isAttachedToDocument()) {
continue;
}
for (auto o : obj->getOutList()) {
if (o && o->isAttachedToDocument() && o->getDocument() != obj->getDocument()) {
objMap[obj->getDocument()].push_back(obj);
break;
}
}
}
return objMap;
}
bool StdCmdLinkImport::isActive()
{
auto links = getLinkImportSelections();
if (links.empty()) {
return false;
}
for (auto& v : links) {
if (v.first->testStatus(App::Document::PartialDoc)) {
return false;
}
}
return true;
}
void StdCmdLinkImport::activated(int)
{
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Import links"));
try {
WaitCursor wc;
wc.setIgnoreEvents(WaitCursor::NoEvents);
for (auto& v : getLinkImportSelections()) {
auto doc = v.first;
// TODO: Is it possible to do this using interpreter?
for (auto obj : doc->importLinks(v.second)) {
obj->Visibility.setValue(false);
}
}
Command::commitCommand();
}
catch (const Base::Exception& e) {
Command::abortCommand();
QMessageBox::critical(
getMainWindow(),
QObject::tr("Failed to import links"),
QString::fromLatin1(e.what())
);
e.reportException();
}
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkImportAll)
StdCmdLinkImportAll::StdCmdLinkImportAll()
: Command("Std_LinkImportAll")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Import All Links");
sToolTipText = QT_TR_NOOP("Imports all links of the active document");
sWhatsThis = "Std_LinkImportAll";
sStatusTip = sToolTipText;
eType = AlterDoc;
sPixmap = "LinkImportAll";
}
bool StdCmdLinkImportAll::isActive()
{
auto doc = App::GetApplication().getActiveDocument();
return doc && !doc->testStatus(App::Document::PartialDoc) && App::PropertyXLink::hasXLink(doc);
}
void StdCmdLinkImportAll::activated(int)
{
Command::openCommand(QT_TRANSLATE_NOOP("Command", "Import all links"));
try {
WaitCursor wc;
wc.setIgnoreEvents(WaitCursor::NoEvents);
auto doc = App::GetApplication().getActiveDocument();
if (doc) {
for (auto obj : doc->importLinks()) {
obj->Visibility.setValue(false);
}
}
Command::commitCommand();
}
catch (const Base::Exception& e) {
QMessageBox::critical(
getMainWindow(),
QObject::tr("Failed to import all links"),
QString::fromLatin1(e.what())
);
Command::abortCommand();
e.reportException();
}
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkSelectLinked)
StdCmdLinkSelectLinked::StdCmdLinkSelectLinked()
: Command("Std_LinkSelectLinked")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("&Go to Linked Object");
sToolTipText = QT_TR_NOOP("Selects the linked object and switches to its original document");
sWhatsThis = "Std_LinkSelectLinked";
sStatusTip = sToolTipText;
eType = AlterSelection;
sPixmap = "LinkSelect";
sAccel = "S, G";
}
static App::DocumentObject* getSelectedLink(bool finalLink, std::string* subname = nullptr)
{
const auto& sels = Selection().getSelection("*", ResolveMode::NoResolve, true);
if (sels.empty()) {
return nullptr;
}
auto sobj = sels[0].pObject->getSubObject(sels[0].SubName);
if (!sobj) {
return nullptr;
}
auto vp = freecad_cast<ViewProviderDocumentObject*>(Application::Instance->getViewProvider(sobj));
if (!vp) {
return nullptr;
}
auto linkedVp = vp->getLinkedViewProvider(subname, finalLink);
if (!linkedVp || linkedVp == vp) {
if (sobj->getDocument() == sels[0].pObject->getDocument()) {
return nullptr;
}
for (const char* dot = strchr(sels[0].SubName, '.'); dot; dot = strchr(dot + 1, '.')) {
std::string sub(sels[0].SubName, dot + 1 - sels[0].SubName);
auto obj = sels[0].pObject->getSubObject(sub.c_str());
if (!obj) {
break;
}
obj = obj->getLinkedObject(true);
if (obj->getDocument() != sels[0].pObject->getDocument()) {
if (finalLink) {
return sobj == obj ? nullptr : sobj;
}
if (subname) {
*subname = std::string(dot + 1);
}
return obj;
}
}
return finalLink ? nullptr : sobj;
}
if (finalLink && linkedVp == vp->getLinkedViewProvider()) {
return nullptr;
}
auto linked = linkedVp->getObject();
if (!linked || !linked->isAttachedToDocument()) {
return nullptr;
}
if (subname && sels[0].pObject != sobj && sels[0].SubName) {
bool found = false;
int pre_len = 0;
std::size_t post_len = 0;
std::string prefix;
std::string prefix2;
// An object can be claimed by multiple objects. Let's try select one
// that causes minimum jump in tree view, and prefer upper over lower
// hierarchy (because of less depth/complexity of tree expansion)
for (auto& v : linked->getParents()) {
if (v.first != sels[0].pObject) {
continue;
}
const char* sub = v.second.c_str();
const char* dot = sub;
for (const char* s = sels[0].SubName; *s && *sub == *s; ++s, ++sub) {
if (*sub == '.') {
dot = sub;
}
}
found = true;
if (dot - v.second.c_str() > pre_len
|| (dot - v.second.c_str() == pre_len && v.second.size() < post_len)) {
pre_len = dot - v.second.c_str();
prefix = std::string(sels[0].SubName, pre_len) + (v.second.c_str() + pre_len);
post_len = v.second.size();
}
else if (!pre_len) {
if (prefix2.empty() || prefix2.size() > v.second.size()) {
prefix2 = v.second;
}
}
}
if (found) {
linked = sels[0].pObject;
*subname = !prefix.empty() ? prefix : prefix2 + *subname;
}
}
return linked;
}
bool StdCmdLinkSelectLinked::isActive()
{
return getSelectedLink(false) != nullptr;
}
void StdCmdLinkSelectLinked::activated(int)
{
std::string subname;
auto linked = getSelectedLink(false, &subname);
if (!linked) {
FC_WARN("invalid selection");
return;
}
Selection().selStackPush();
Selection().clearCompleteSelection();
if (!subname.empty()) {
Selection().addSelection(
linked->getDocument()->getName(),
linked->getNameInDocument(),
subname.c_str()
);
auto doc = Application::Instance->getDocument(linked->getDocument());
if (doc) {
auto vp = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(linked)
);
doc->setActiveView(vp);
}
}
else {
const auto trees = getMainWindow()->findChildren<TreeWidget*>();
for (auto tree : trees) {
tree->selectLinkedObject(linked);
}
}
Selection().selStackPush();
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkSelectLinkedFinal)
StdCmdLinkSelectLinkedFinal::StdCmdLinkSelectLinkedFinal()
: Command("Std_LinkSelectLinkedFinal")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Go to &Deepest Linked Object");
sToolTipText = QT_TR_NOOP(
"Selects the deepest linked object and switches to its original document"
);
sWhatsThis = "Std_LinkSelectLinkedFinal";
sStatusTip = sToolTipText;
eType = AlterSelection;
sPixmap = "LinkSelectFinal";
sAccel = "S, D";
}
bool StdCmdLinkSelectLinkedFinal::isActive()
{
return getSelectedLink(true) != nullptr;
}
void StdCmdLinkSelectLinkedFinal::activated(int)
{
auto linked = getSelectedLink(true);
if (!linked) {
FC_WARN("invalid selection");
return;
}
Selection().selStackPush();
Selection().clearCompleteSelection();
const auto trees = getMainWindow()->findChildren<TreeWidget*>();
for (auto tree : trees) {
tree->selectLinkedObject(linked);
}
Selection().selStackPush();
}
////////////////////////////////////////////////////////////////////////////////////////////
DEF_STD_CMD_A(StdCmdLinkSelectAllLinks)
StdCmdLinkSelectAllLinks::StdCmdLinkSelectAllLinks()
: Command("Std_LinkSelectAllLinks")
{
sGroup = "Link";
sMenuText = QT_TR_NOOP("Select &All Links");
sToolTipText = QT_TR_NOOP("Selects all links to the current selected object");
sWhatsThis = "Std_LinkSelectAllLinks";
sStatusTip = sToolTipText;
eType = AlterSelection;
sPixmap = "LinkSelectAll";
}
bool StdCmdLinkSelectAllLinks::isActive()
{
const auto& sels = Selection().getSelection("*", ResolveMode::OldStyleElement, true);
if (sels.empty()) {
return false;
}
return App::GetApplication().hasLinksTo(sels[0].pObject);
}
void StdCmdLinkSelectAllLinks::activated(int)
{
auto sels = Selection().getSelection("*", ResolveMode::OldStyleElement, true);
if (sels.empty()) {
return;
}
Selection().selStackPush();
Selection().clearCompleteSelection();
const auto trees = getMainWindow()->findChildren<TreeWidget*>();
for (auto tree : trees) {
tree->selectAllLinks(sels[0].pObject);
}
Selection().selStackPush();
}
//======================================================================
// Std_LinkSelectActions
//===========================================================================
class StdCmdLinkSelectActions: public GroupCommand
{
public:
StdCmdLinkSelectActions()
: GroupCommand("Std_LinkSelectActions")
{
sGroup = "View";
sMenuText = QT_TR_NOOP("&Link Navigation");
sToolTipText = QT_TR_NOOP("Link navigation actions");
sWhatsThis = "Std_LinkSelectActions";
sStatusTip = sToolTipText;
eType = AlterSelection;
bCanLog = false;
addCommand(new StdCmdLinkSelectLinked());
addCommand(new StdCmdLinkSelectLinkedFinal());
addCommand(new StdCmdLinkSelectAllLinks());
}
const char* className() const override
{
return "StdCmdLinkSelectActions";
}
};
//======================================================================
// Std_LinkActions
//===========================================================================
class StdCmdLinkActions: public GroupCommand
{
public:
StdCmdLinkActions()
: GroupCommand("Std_LinkActions")
{
sGroup = "View";
sMenuText = QT_TR_NOOP("Link Actions");
sToolTipText = QT_TR_NOOP("Commands that operate on link objects");
sWhatsThis = "Std_LinkActions";
sStatusTip = sToolTipText;
eType = AlterDoc;
bCanLog = false;
setCheckable(false);
addCommand(new StdCmdLinkMake());
addCommand(new StdCmdLinkMakeRelative());
addCommand(new StdCmdLinkReplace());
addCommand(new StdCmdLinkUnlink());
addCommand(new StdCmdLinkImport());
addCommand(new StdCmdLinkImportAll());
}
const char* className() const override
{
return "StdCmdLinkActions";
}
bool isActive() override
{
return hasActiveDocument();
}
};
//===========================================================================
// Instantiation
//===========================================================================
namespace Gui
{
void CreateLinkCommands()
{
CommandManager& rcCmdMgr = Application::Instance->commandManager();
rcCmdMgr.addCommand(new StdCmdLinkActions());
rcCmdMgr.addCommand(new StdCmdLinkMakeGroup());
rcCmdMgr.addCommand(new StdCmdLinkSelectActions());
}
} // namespace Gui