Content of file QSofaListView.cpp
/******************************************************************************
* SOFA, Simulation Open-Framework Architecture *
* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the Free *
* Software Foundation; either version 2 of the License, or (at your option) *
* any later version. *
* *
* This program 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 General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt) *
* *
* Contact information: contact@sofa-framework.org *
******************************************************************************/
#include "QSofaListView.h"
#include "QDisplayPropertyWidget.h"
#include "GraphListenerQListView.h"
#include "ModifyObject.h"
#include "GenGraphForm.h"
#include "RealGUI.h"
#include <sofa/simulation/Simulation.h>
#include <sofa/simulation/DeleteVisitor.h>
#include <sofa/simulation/common/TransformationVisitor.h>
#include <sofa/simulation/common/xml/BaseElement.h>
#include <sofa/simulation/common/xml/XML.h>
#include <sofa/helper/cast.h>
#include <QMenu>
#include <QtGlobal> // version macro
#include <QMessageBox>
#include <QApplication>
#include <QDesktopServices>
#include <QFileInfo>
#include <QUrl>
#include <QClipboard>
#include <QSettings>
using namespace sofa::simulation;
using namespace sofa::core::objectmodel;
using namespace sofa::gui::common;
namespace sofa::gui::qt
{
QSofaListView::QSofaListView(const SofaListViewAttribute& attribute,
QWidget* parent,
const char* name,
Qt::WindowFlags f):
SofaSceneGraphWidget(parent),
graphListener_(nullptr),
AddObjectDialog_(nullptr),
attribute_(attribute),
propertyWidget(nullptr)
{
this->setObjectName(name);
this->setWindowFlags(f);
//List of objects
//Read the object.txt that contains the information about the objects which can be added to the scenes whithin a given BoundingBox and scale range
std::string object ( "config/object.txt" );
if( sofa::helper::system::DataRepository.findFile ( object ) )
{
list_object.clear();
std::ifstream end(object.c_str());
std::string s;
while( end >> s )
{
list_object.push_back(s);
}
end.close();
}
this->setColumnCount(2);
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
header()->setResizeMode(0, QHeaderView::Interactive);
header()->setResizeMode(1, QHeaderView::Stretch);
#else
header()->setSectionResizeMode(0, QHeaderView::Interactive);
header()->setSectionResizeMode(1, QHeaderView::Stretch);
#endif // SOFA_QT5
QStringList headerLabels;
headerLabels << "Name" << "Class";
this->setHeaderLabels(headerLabels);
setRootIsDecorated(true);
setIndentation(8);
graphListener_ = new GraphListenerQListView(this);
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QSofaListView::customContextMenuRequested ,this, &QSofaListView::RunSofaRightClicked);
connect(this, &QSofaListView::itemDoubleClicked, this, &QSofaListView::RunSofaDoubleClicked);
connect(this, &QSofaListView::itemClicked, this, [&](QTreeWidgetItem *item, int){ updateMatchingObjectmodel(item); });
}
QSofaListView::~QSofaListView()
{
delete graphListener_;
}
void QSofaListView::Clear(Node* /*rootNode*/)
{
/*
if(graphListener_ != nullptr)
{
delete graphListener_;
}
CloseAllDialogs();
clear();
graphListener_ = new GraphListenerQListView(this);
this->setSortingEnabled(false);
rootNode->addListener(graphListener_);
update();
std::map<Base*, QTreeWidgetItem* >::iterator graph_iterator;
for (graph_iterator = graphListener_->items.begin();
graph_iterator != graphListener_->items.end();
++graph_iterator)
{
Node* node = dynamic_cast< Node* >(graph_iterator->first);
if (node!=nullptr && !node->isActive())
{
object_.ptr.Node = node;
object_.type = typeNode;
emit RequestActivation(object_.ptr.Node, node->isActive());
}
}
*/
}
void QSofaListView::CloseAllDialogs()
{
emit Close();
assert( map_modifyObjectWindow.empty() );
assert( map_modifyDialogOpened.empty() );
}
void QSofaListView::modifyUnlock(void* Id)
{
map_modifyDialogOpened.erase( Id );
map_modifyObjectWindow.erase( Id );
}
/// Traverse the item tree and retrive the item that are expanded. The path of the node
/// that are expanded are stored in the pathes std::vector::std::string>.
void QSofaListView::getExpandedNodes(QTreeWidgetItem* item, std::vector<std::string>& pathes)
{
if(!item)
return;
/// We have reached a leaf of the hierarchy or it is closed...so we save the path
if( !item->isExpanded() && graphListener_->findObject(item)->toBaseNode() != nullptr )
return;
const BaseNode* parentNode = graphListener_->findObject(item)->toBaseNode() ;
if(parentNode == nullptr)
return;
const std::string path = parentNode->getPathName();
pathes.push_back(path);
for(int i=0 ; i<item->childCount() ; i++)
{
QTreeWidgetItem* child = item->child(i);
const BaseNode* childNode = graphListener_->findObject(child)->toBaseNode() ;
if(childNode==nullptr)
continue;
if( childNode->getParents()[0] == parentNode )
getExpandedNodes(child, pathes) ;
}
return ;
}
void QSofaListView::getExpandedNodes(std::vector<std::string>& pathes)
{
LockContextManager lock(this, true);
QTreeWidgetItem* rootitem = this->topLevelItem(0) ;
getExpandedNodes(rootitem,pathes) ;
}
void QSofaListView::collapseNode()
{
collapseNode(currentItem());
}
void QSofaListView::collapseNode(QTreeWidgetItem* item)
{
if (!item) return;
LockContextManager lock(this, true);
for(int i=0 ; i<item->childCount() ; i++)
{
QTreeWidgetItem* child = item->child(i);
child->setExpanded(false);
}
item->setExpanded ( true );
}
void QSofaListView::expandPath(const std::string& path)
{
if(path.empty())
return;
if(path.data()[0] != '/')
return;
Node* match = down_cast<Node>( graphListener_->findObject(this->topLevelItem(0))->toBaseNode() );
QStringList tokens = QString::fromStdString(path).split('/') ;
for(int i=1;i<tokens.size();i++)
{
match = match->getChild(tokens[i].toStdString());
if(match == nullptr)
return;
if(graphListener_->items.find(match) != graphListener_->items.end())
{
QTreeWidgetItem* item = graphListener_->items[match] ;
item->setExpanded ( true );
}
}
}
void QSofaListView::expandPathFrom(const std::vector<std::string>& pathes)
{
LockContextManager lock(this, true);
for(auto& path : pathes)
{
expandPath(path) ;
}
}
void QSofaListView::expandNode()
{
expandNode(currentItem());
}
void QSofaListView::expandNode(QTreeWidgetItem* item)
{
if (!item)
return;
LockContextManager lock(this, true);
item->setExpanded ( true );
for(int i=0 ; i<item->childCount() ; i++)
{
QTreeWidgetItem* child = item->child(i);
child->setExpanded(true);
expandNode(child);
}
}
void QSofaListView::setRoot(Node* root)
{
if(!root)
return;
CloseAllDialogs();
clear();
if(graphListener_)
delete graphListener_;
graphListener_ = new GraphListenerQListView(this);
setSortingEnabled(false);
const bool lockStatus = m_isLocked;
m_isLocked=false;
root->addListener(graphListener_);
graphListener_->onBeginAddChild(nullptr, root);
m_isLocked=lockStatus;
m_isDirty=false;
emit dirtynessChanged(m_isDirty);
}
void QSofaListView::update()
{
if(!m_isDirty)
return;
m_isDirty=false;
if(!graphListener_ || !this->topLevelItem(0))
{
emit dirtynessChanged(m_isDirty);
return;
}
emit dirtynessChanged(m_isDirty);
}
void QSofaListView::updateMatchingObjectmodel(QTreeWidgetItem* item, int)
{
updateMatchingObjectmodel(item);
}
void QSofaListView::updateMatchingObjectmodel(QTreeWidgetItem* item)
{
BaseData* data = nullptr;
Base* base = nullptr;
BaseObject* object = nullptr;
BaseNode* basenode = nullptr;
if(item == nullptr)
{
object_.ptr.Node = nullptr;
}
else
{
base = graphListener_->findObject(item);
if(base == nullptr)
{
data = graphListener_->findData(item);
assert(data);
object_.ptr.Data = data;
object_.type = typeData;
return;
}
basenode = base->toBaseNode();
if( basenode == nullptr)
{
object = dynamic_cast<BaseObject*>(base);
object_.ptr.Object = object;
object_.type = typeObject;
}
else
{
object_.ptr.Node = down_cast<Node>(basenode);
object_.type = typeNode;
}
}
addInPropertyWidget(item, true);
}
void QSofaListView::addInPropertyWidget(QTreeWidgetItem *item, bool clear)
{
if(!item)
return;
Base* object = graphListener_->findObject(item);
if(object == nullptr)
return;
if(propertyWidget)
{
propertyWidget->addComponent(object->getName().c_str(), object, item, clear);
propertyWidget->show();
}
}
void QSofaListView::contextMenuEvent(QContextMenuEvent *event)
{
event->accept();
}
void QSofaListView::focusObject()
{
if( object_.isObject())
emit( focusChanged(object_.ptr.Object));
}
void QSofaListView::focusNode()
{
if( object_.isNode())
emit( focusChanged(object_.ptr.Node));
}
/*****************************************************************************************************************/
void QSofaListView::RunSofaRightClicked( const QPoint& point)
{
QTreeWidgetItem *item = this->itemAt( point );
if( item == nullptr) return;
updateMatchingObjectmodel(item);
QAction* act;
bool object_hasData = false;
![](/sofa-ci-dev/static/86773a34/plugin/warnings-ng/icons/analysis.svg) | variable 'object_hasData' set but not used | |
if(object_.type == typeObject)
{
object_hasData = object_.ptr.Object->getDataFields().size() > 0 ? true : false;
}
QMenu *contextMenu = new QMenu ( this );
contextMenu->setObjectName( "ContextMenu");
if( object_.isNode() )
{
act = contextMenu->addAction("Focus", this,SLOT(focusNode()));
const bool enable = object_.ptr.Node->f_bbox.getValue().isValid() && !object_.ptr.Node->f_bbox.getValue().isFlat();
act->setEnabled(enable);
}
if( object_.isObject() )
{
act = contextMenu->addAction("Focus", this,SLOT(focusObject()));
const bool enable = object_.ptr.Object->f_bbox.getValue().isValid() && !object_.ptr.Object->f_bbox.getValue().isFlat() ;
act->setEnabled(enable);
}
contextMenu->addSeparator();
//Creation of the context Menu
if ( object_.type == typeNode)
{
act = contextMenu->addAction("Collapse", this,SLOT(collapseNode()));
act = contextMenu->addAction("Expand", this,SLOT(expandNode()));
contextMenu->addSeparator();
/*****************************************************************************************************************/
if (object_.ptr.Node->isActive())
{
act = contextMenu->addAction("Deactivate", this,SLOT(DeactivateNode()));
}
else
{
act = contextMenu->addAction("Activate", this,SLOT(ActivateNode()));
}
if (object_.ptr.Node->isSleeping())
{
act = contextMenu->addAction("Wake up", this,SLOT(WakeUpNode()));
}
else
{
act = contextMenu->addAction("Put to sleep", this,SLOT(PutNodeToSleep()));
}
contextMenu->addSeparator();
/*****************************************************************************************************************/
act = contextMenu->addAction("Save Node", this,SLOT(SaveNode()));
act = contextMenu->addAction("Export OBJ", this,SLOT(exportOBJ()));
if ( attribute_ == SIMULATION)
{
act = contextMenu->addAction("Remove Node", this,SLOT(RemoveNode()));
//If one of the elements or child of the current node is beeing modified, you cannot allow the user to erase the node
if ( !isNodeErasable ( object_.ptr.Node ) )
{
act->setEnabled(false);
}
}
}
act = contextMenu->addAction("Modify", this,SLOT(Modify()));
if( object_.isBase() )
{
contextMenu->addSeparator();
act = contextMenu->addAction("Go to Scene...", this, SLOT(openInstanciation()));
act->setEnabled(object_.asBase()->getInstanciationSourceFileName() != "");
act = contextMenu->addAction("Go to Implementation...", this, SLOT(openImplementation()));
act->setEnabled(object_.asBase()->getDefinitionSourceFileName() != "");
}
contextMenu->addSeparator();
act = contextMenu->addAction("Copy file path", this,SLOT(copyFilePathToClipBoard()));
act = contextMenu->addAction("Open file in editor", this,SLOT(openInEditor()));
contextMenu->exec ( this->mapToGlobal(point) /*, index */);
}
void QSofaListView::RunSofaDoubleClicked(QTreeWidgetItem* item, int /*index*/)
{
if(item == nullptr)
{
return;
}
item->setExpanded( !item->isExpanded());
Modify();
}
/*****************************************************************************************************************/
void QSofaListView::nodeNameModification(simulation::Node* node)
{
QTreeWidgetItem *item=graphListener_->items[node];
const QString nameToUse(node->getName().c_str());
item->setText(0,nameToUse);
typedef std::multimap<QTreeWidgetItem *, QTreeWidgetItem*>::iterator ItemIterator;
const std::pair<ItemIterator,ItemIterator> range=graphListener_->nodeWithMultipleParents.equal_range(item);
for (ItemIterator it=range.first; it!=range.second; ++it) it->second->setText(0,nameToUse);
}
void QSofaListView::DeactivateNode()
{
emit RequestActivation(object_.ptr.Node,false);
currentItem()->setExpanded(false);
}
void QSofaListView::ActivateNode()
{
emit RequestActivation(object_.ptr.Node,true);
}
void QSofaListView::PutNodeToSleep()
{
emit RequestSleeping(object_.ptr.Node, true);
}
void QSofaListView::WakeUpNode()
{
emit RequestSleeping(object_.ptr.Node, false);
}
void QSofaListView::SaveNode()
{
if( object_.ptr.Node != nullptr)
{
LockContextManager lock(this, true);
Node * node = object_.ptr.Node;
emit RequestSaving(node);
}
}
void QSofaListView::exportOBJ()
{
if( object_.ptr.Node != nullptr)
{
LockContextManager lock(this, true);
Node * node = object_.ptr.Node;
emit RequestExportOBJ(node,true);
}
}
void QSofaListView::RemoveNode()
{
if( object_.type == typeNode)
{
LockContextManager lock(this, true);
const Node::SPtr node = object_.ptr.Node;
if ( node == node->getRoot() )
{
if ( QMessageBox::warning ( this, "Removing root", "root node cannot be removed" ) )
return;
}
else
{
node->detachFromGraph();
node->execute<simulation::DeleteVisitor>(sofa::core::execparams::defaultInstance());
emit NodeRemoved();
}
}
}
void QSofaListView::Modify()
{
void *current_Id_modifyDialog = nullptr;
LockContextManager lock(this, true);
if ( currentItem() != nullptr )
{
ModifyObjectFlags dialogFlags = ModifyObjectFlags();
dialogFlags.setFlagsForSofa();
ModifyObject* dialogModifyObject = nullptr;
if (object_.type == typeData) //user clicked on a data
{
current_Id_modifyDialog = object_.ptr.Data;
}
if (object_.type == typeNode)
{
current_Id_modifyDialog = object_.ptr.Node;
}
if(object_.type == typeObject)
{
current_Id_modifyDialog = object_.ptr.Object;
}
assert(current_Id_modifyDialog != nullptr);
//Opening of a dialog window automatically created
const std::map< void*, QDialog* >::iterator testWindow = map_modifyObjectWindow.find( current_Id_modifyDialog);
if ( testWindow != map_modifyObjectWindow.end())
{
//Object already being modified: no need to open a new window
(*testWindow).second->raise();
return;
}
dialogModifyObject = new ModifyObject(current_Id_modifyDialog,currentItem(),this,dialogFlags,currentItem()->text(0).toStdString().c_str());
if(object_.type == typeData)
dialogModifyObject->createDialog(object_.ptr.Data);
if(object_.type == typeNode)
dialogModifyObject->createDialog(dynamic_cast<Base*>(object_.ptr.Node));
if(object_.type == typeObject)
dialogModifyObject->createDialog(dynamic_cast<Base*>(object_.ptr.Object));
map_modifyDialogOpened.insert( std::make_pair ( current_Id_modifyDialog, currentItem()) );
map_modifyObjectWindow.insert( std::make_pair(current_Id_modifyDialog, dialogModifyObject));
connect ( dialogModifyObject, &ModifyObject::objectUpdated, this, &QSofaListView::Updated );
connect ( this, &QSofaListView::Close, dialogModifyObject, &ModifyObject::closeNow );
connect ( dialogModifyObject, &ModifyObject::dialogClosed, this, &QSofaListView::modifyUnlock );
connect ( dialogModifyObject, &ModifyObject::nodeNameModification, this, &QSofaListView::nodeNameModification );
connect ( dialogModifyObject, &ModifyObject::dataModified, this, &QSofaListView::dataModified );
dialogModifyObject->show();
dialogModifyObject->raise();
}
}
void QSofaListView::UpdateOpenedDialogs()
{
std::map<void*,QDialog*>::const_iterator iter;
for(iter = map_modifyObjectWindow.begin(); iter != map_modifyObjectWindow.end() ; ++iter)
{
ModifyObject* modify = reinterpret_cast<ModifyObject*>(iter->second);
modify->updateTables();
}
}
void QSofaListView::ExpandRootNodeOnly()
{
this->expandToDepth(0);
}
/// @brief Open a file at given path and line number using an external editor.
///
/// The external editor is defined in a QSettings with the following entries:
/// [General]
/// ExternalEditor=qtcreator
/// ExternalEditorParams=-client ${filename}:${fileno}
/// where ${filename} is expanded with the full path to the file
/// where ${fileno} is expanded with the line number to open at.
void openInExternalEditor(const std::string filename, const int fileloc)
{
const QFileInfo f(filename.c_str());
const std::string settingsFile = BaseGUI::getConfigDirectoryPath() + "/QSettings.ini";
QSettings settings(settingsFile.c_str(), QSettings::IniFormat);
/// In case the setting file does not contains the needed entries, let's put default ones
/// based on qtcreator.
if(!settings.contains("ExternalEditor"))
settings.setValue("ExternalEditor", "qtcreator");
if(!settings.contains("ExternalEditorParams"))
settings.setValue("ExternalEditorParams", "-client ${filename}:${fileno}");
const QString editor = settings.value("ExternalEditor").toString();
QString params = settings.value("ExternalEditorParams").toString();
params.replace("${filename}", f.absoluteFilePath());
params.replace("${fileno}", QString::number(fileloc));
const QStringList paramsAsList = params.split(QRegularExpression("(\\ )"));
if ( QProcess::execute(editor, paramsAsList) != 0 )
{
msg_warning("QSofaListView") << "Unable to execute \"" << editor.toStdString() << " "
<< params.toStdString() << "\"" << msgendl
<< " The file will NOT be opened at the right line." << msgendl
<< " Set your preferred editor in: " << settingsFile << msgendl
<< " Falling back to your system default editor.";
QDesktopServices::openUrl(QUrl::fromLocalFile( f.absoluteFilePath() ));
}
}
void QSofaListView::openInstanciation()
{
if(object_.isBase())
{
openInExternalEditor(object_.asBase()->getInstanciationSourceFileName(),
object_.asBase()->getInstanciationSourceFilePos());
}
}
void QSofaListView::openImplementation()
{
if(object_.isBase())
{
openInExternalEditor(object_.asBase()->getDefinitionSourceFileName(),
object_.asBase()->getDefinitionSourceFilePos());
}
}
void QSofaListView::openInEditor()
{
const QFileInfo finfo(QApplication::activeWindow()->windowFilePath());
QDesktopServices::openUrl(QUrl::fromLocalFile(finfo.absoluteFilePath()));
}
void QSofaListView::copyFilePathToClipBoard()
{
const QFileInfo finfo(QApplication::activeWindow()->windowFilePath());
QApplication::clipboard()->setText(finfo.absoluteFilePath()) ;
}
/*****************************************************************************************************************/
// Test if a node can be erased in the graph : the condition is that none of its children has a menu modify opened
bool QSofaListView::isNodeErasable ( BaseNode* node)
{
const QTreeWidgetItem* item = graphListener_->items[node];
if(item == nullptr)
{
return false;
}
// check if there is already a dialog opened for that item in the graph
std::map< void*, QTreeWidgetItem*>::iterator it;
for (it = map_modifyDialogOpened.begin(); it != map_modifyDialogOpened.end(); ++it)
{
if (it->second == item) return false;
}
//check the item childs
for(int i=0 ; i<item->childCount() ; i++)
{
const QTreeWidgetItem *child = item->child(i);
for( it = map_modifyDialogOpened.begin(); it != map_modifyDialogOpened.end(); ++it)
{
if( it->second == child) return false;
}
}
return true;
}
void QSofaListView::Export()
{
Node* root = down_cast<Node>( graphListener_->findObject(this->topLevelItem(0))->toBaseNode() );
GenGraphForm* form = new sofa::gui::qt::GenGraphForm(this);
form->setScene ( root );
std::string gname(((RealGUI*) (QApplication::topLevelWidgets()[0]))->windowFilePath().toStdString());
const std::size_t gpath = gname.find_last_of("/\\");
const std::size_t gext = gname.rfind('.');
if (gext != std::string::npos && (gpath == std::string::npos || gext > gpath))
gname = gname.substr(0,gext);
form->filename->setText(gname.c_str());
form->show();
}
} //namespace sofa::gui::qt