pxmlw6n2f/Gazebo_Distributed_TCP/gazebo/gui/InsertModelWidget.cc

407 lines
13 KiB
C++

/*
* Copyright (C) 2012 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifdef _WIN32
// Ensure that Winsock2.h is included before Windows.h, which can get
// pulled in by anybody (e.g., Boost).
#include <Winsock2.h>
#endif
#include <fstream>
#include <boost/bind.hpp>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <sdf/sdf.hh>
#include <tinyxml.h>
#include "gazebo/common/SystemPaths.hh"
#include "gazebo/common/Console.hh"
#include "gazebo/common/ModelDatabase.hh"
#include "gazebo/rendering/RenderingIface.hh"
#include "gazebo/rendering/Scene.hh"
#include "gazebo/rendering/UserCamera.hh"
#include "gazebo/rendering/Visual.hh"
#include "gazebo/gui/GuiIface.hh"
#include "gazebo/gui/GuiEvents.hh"
#include "gazebo/transport/Node.hh"
#include "gazebo/transport/Publisher.hh"
#include "gazebo/gui/InsertModelWidgetPrivate.hh"
#include "gazebo/gui/InsertModelWidget.hh"
using namespace gazebo;
using namespace gui;
/////////////////////////////////////////////////
InsertModelWidget::InsertModelWidget(QWidget *_parent)
: QWidget(_parent), dataPtr(new InsertModelWidgetPrivate)
{
this->setObjectName("insertModel");
this->dataPtr->modelDatabaseItem = NULL;
QVBoxLayout *mainLayout = new QVBoxLayout;
this->dataPtr->fileTreeWidget = new QTreeWidget();
this->dataPtr->fileTreeWidget->setColumnCount(1);
this->dataPtr->fileTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
this->dataPtr->fileTreeWidget->header()->hide();
connect(this->dataPtr->fileTreeWidget,
SIGNAL(itemClicked(QTreeWidgetItem *, int)),
this, SLOT(OnModelSelection(QTreeWidgetItem *, int)));
QFrame *frame = new QFrame;
QVBoxLayout *frameLayout = new QVBoxLayout;
frameLayout->addWidget(this->dataPtr->fileTreeWidget, 0);
frameLayout->setContentsMargins(0, 0, 0, 0);
frame->setLayout(frameLayout);
mainLayout->addWidget(frame);
this->setLayout(mainLayout);
this->layout()->setContentsMargins(0, 0, 0, 0);
// Create a system path watcher
this->dataPtr->watcher = new QFileSystemWatcher();
// Update the list of models on the local system.
this->UpdateAllLocalPaths();
// Create a top-level tree item for the path
this->dataPtr->modelDatabaseItem =
new QTreeWidgetItem(static_cast<QTreeWidgetItem*>(0),
QStringList(QString("Connecting to model database...")));
this->dataPtr->fileTreeWidget->addTopLevelItem(
this->dataPtr->modelDatabaseItem);
// Also insert additional paths from gui.ini
std::string additionalPaths =
gui::getINIProperty<std::string>("model_paths.filenames", "");
if (!additionalPaths.empty())
{
common::SystemPaths::Instance()->AddModelPaths(additionalPaths);
// Get each path in the : separated list
std::string delim(":");
size_t pos1 = 0;
size_t pos2 = additionalPaths.find(delim);
while (pos2 != std::string::npos)
{
this->UpdateLocalPath(additionalPaths.substr(pos1, pos2-pos1));
pos1 = pos2+1;
pos2 = additionalPaths.find(delim, pos2+1);
}
this->UpdateLocalPath(additionalPaths.substr(pos1,
additionalPaths.size()-pos1));
}
// Connect callbacks now that everything else is initialized
// Connect a callback that is triggered whenever a directory is changed.
connect(this->dataPtr->watcher, SIGNAL(directoryChanged(const QString &)),
this, SLOT(OnDirectoryChanged(const QString &)));
// Connect a callback to trigger when the model paths are updated.
this->connections.push_back(
common::SystemPaths::Instance()->updateModelRequest.Connect(
boost::bind(&InsertModelWidget::OnModelUpdateRequest, this, _1)));
/// Non-blocking call to get all the models in the database.
this->dataPtr->getModelsConnection =
common::ModelDatabase::Instance()->GetModels(
boost::bind(&InsertModelWidget::OnModels, this, _1));
// Start a timer to check for the results from the ModelDatabase. We need
// to do this so that the QT elements get added in the main thread.
QTimer::singleShot(1000, this, SLOT(Update()));
}
/////////////////////////////////////////////////
InsertModelWidget::~InsertModelWidget()
{
delete this->dataPtr->watcher;
delete this->dataPtr;
this->dataPtr = NULL;
}
/////////////////////////////////////////////////
bool InsertModelWidget::LocalPathInFileWidget(const std::string &_path)
{
return this->dataPtr->localFilenameCache.find(_path) !=
this->dataPtr->localFilenameCache.end();
}
/////////////////////////////////////////////////
void InsertModelWidget::Update()
{
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
// If the model database has call the OnModels callback function, then
// add all the models from the database.
if (!this->dataPtr->modelBuffer.empty())
{
std::string uri = common::ModelDatabase::Instance()->GetURI();
this->dataPtr->modelDatabaseItem->setText(0,
QString("%1").arg(QString::fromStdString(uri)));
if (!this->dataPtr->modelBuffer.empty())
{
for (std::map<std::string, std::string>::const_iterator iter =
this->dataPtr->modelBuffer.begin();
iter != this->dataPtr->modelBuffer.end(); ++iter)
{
// Add a child item for the model
QTreeWidgetItem *childItem = new QTreeWidgetItem(
this->dataPtr->modelDatabaseItem,
QStringList(QString("%1").arg(
QString::fromStdString(iter->second))));
childItem->setData(0, Qt::UserRole, QVariant(iter->first.c_str()));
this->dataPtr->fileTreeWidget->addTopLevelItem(childItem);
}
}
this->dataPtr->modelBuffer.clear();
this->dataPtr->getModelsConnection.reset();
}
else
QTimer::singleShot(1000, this, SLOT(Update()));
}
/////////////////////////////////////////////////
void InsertModelWidget::OnModels(
const std::map<std::string, std::string> &_models)
{
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
this->dataPtr->modelBuffer = _models;
}
/////////////////////////////////////////////////
void InsertModelWidget::OnModelSelection(QTreeWidgetItem *_item,
int /*_column*/)
{
if (_item)
{
std::string path, filename;
if (_item->parent())
path = _item->parent()->text(0).toStdString() + "/";
path = _item->data(0, Qt::UserRole).toString().toStdString();
if (!path.empty())
{
QApplication::setOverrideCursor(Qt::BusyCursor);
filename = common::ModelDatabase::Instance()->GetModelFile(path);
gui::Events::createEntity("model", filename);
{
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
this->dataPtr->fileTreeWidget->clearSelection();
}
QApplication::setOverrideCursor(Qt::ArrowCursor);
}
}
}
/////////////////////////////////////////////////
void InsertModelWidget::UpdateLocalPath(const std::string &_path)
{
if (_path.empty())
return;
boost::filesystem::path dir(_path);
bool pathExists = this->IsPathAccessible(dir);
QString qpath = QString::fromStdString(_path);
QTreeWidgetItem *topItem = NULL;
QList<QTreeWidgetItem *> matchList =
this->dataPtr->fileTreeWidget->findItems(qpath, Qt::MatchExactly);
// Create a top-level tree item for the path
if (matchList.empty())
{
topItem = new QTreeWidgetItem(
static_cast<QTreeWidgetItem*>(0), QStringList(qpath));
this->dataPtr->fileTreeWidget->addTopLevelItem(topItem);
this->dataPtr->localFilenameCache.insert(_path);
// Add the new path to the directory watcher
if (pathExists)
{
this->dataPtr->watcher->addPath(qpath);
}
}
else
topItem = matchList.first();
// Remove current items.
topItem->takeChildren();
if (pathExists && boost::filesystem::is_directory(dir))
{
std::vector<boost::filesystem::path> paths;
// Get all the paths in alphabetical order
try
{
std::copy(boost::filesystem::directory_iterator(dir),
boost::filesystem::directory_iterator(),
std::back_inserter(paths));
}
catch(boost::filesystem::filesystem_error & e)
{
gzerr << "Not loading models in: " << _path << " ("
<< e.what() << ")" << std::endl;
return;
}
std::sort(paths.begin(), paths.end());
// Iterate over all the models in the current gazebo path
for (std::vector<boost::filesystem::path>::iterator dIter = paths.begin();
dIter != paths.end(); ++dIter)
{
std::string modelName;
boost::filesystem::path fullPath = _path / dIter->filename();
boost::filesystem::path manifest = fullPath;
if (!boost::filesystem::is_directory(fullPath))
{
if (dIter->filename() != "database.config")
{
gzlog << "Invalid filename or directory[" << fullPath
<< "] in GAZEBO_MODEL_PATH. It's not a good idea to put extra "
<< "files in a GAZEBO_MODEL_PATH because the file structure may"
<< " be modified by Gazebo.\n";
}
continue;
}
manifest /= GZ_MODEL_MANIFEST_FILENAME;
// Check if the manifest does not exists
if (!this->IsPathAccessible(manifest))
{
gzerr << "Missing " << GZ_MODEL_MANIFEST_FILENAME << " for model "
<< (*dIter) << "\n";
manifest = manifest / "manifest.xml";
}
if (!this->IsPathAccessible(manifest) || manifest == fullPath)
{
gzlog << "model.config file is missing in directory["
<< fullPath << "]\n";
continue;
}
TiXmlDocument xmlDoc;
if (xmlDoc.LoadFile(manifest.string()))
{
TiXmlElement *modelXML = xmlDoc.FirstChildElement("model");
if (!modelXML || !modelXML->FirstChildElement("name"))
gzerr << "No model name in manifest[" << manifest << "]\n";
else
modelName = modelXML->FirstChildElement("name")->GetText();
// Add a child item for the model
QTreeWidgetItem *childItem = new QTreeWidgetItem(topItem,
QStringList(QString::fromStdString(modelName)));
childItem->setData(0, Qt::UserRole,
QVariant((std::string("file://") + fullPath.string()).c_str()));
this->dataPtr->fileTreeWidget->addTopLevelItem(childItem);
this->dataPtr->localFilenameCache.insert(fullPath.string());
}
}
}
// Make all top-level items expanded. Trying to reduce mouse clicks.
this->dataPtr->fileTreeWidget->expandItem(topItem);
}
/////////////////////////////////////////////////
void InsertModelWidget::UpdateAllLocalPaths()
{
std::list<std::string> gazeboPaths =
common::SystemPaths::Instance()->GetModelPaths();
// Iterate over all the gazebo paths
for (std::list<std::string>::iterator iter = gazeboPaths.begin();
iter != gazeboPaths.end(); ++iter)
{
// This is the full model path
this->UpdateLocalPath((*iter));
}
}
/////////////////////////////////////////////////
void InsertModelWidget::OnDirectoryChanged(const QString &_path)
{
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
this->UpdateLocalPath(_path.toStdString());
}
/////////////////////////////////////////////////
void InsertModelWidget::OnModelUpdateRequest(const std::string &_path)
{
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
this->UpdateLocalPath(_path);
}
/////////////////////////////////////////////////
bool InsertModelWidget::IsPathAccessible(const boost::filesystem::path &_path)
{
try
{
if (!boost::filesystem::exists(_path))
return false;
if (boost::filesystem::is_directory(_path))
{
// Try to retrieve a pointer to the first entry in this directory.
// If permission denied to the directory, will throw filesystem_error
boost::filesystem::directory_iterator iter(_path);
return true;
}
else
{
std::ifstream ifs(_path.string().c_str(), std::ifstream::in);
if (ifs.fail() || ifs.bad())
{
gzerr << "File unreadable: " << _path << std::endl;
return false;
}
return true;
}
}
catch(boost::filesystem::filesystem_error & e)
{
gzerr << "Permission denied for directory: " << _path << std::endl;
}
catch(std::exception & e)
{
gzerr << "Unexpected error while accessing to: " << _path << "."
<< "Error reported: " << e.what() << std::endl;
}
return false;
}