/* * 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 #endif #include #include #include #include #include #include #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(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("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::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 &_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 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(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 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::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 gazeboPaths = common::SystemPaths::Instance()->GetModelPaths(); // Iterate over all the gazebo paths for (std::list::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; }