/* * Copyright (C) 2013 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 "gazebo/common/SystemPaths.hh" #include "gazebo/common/Console.hh" #include "gazebo/gui/GuiIface.hh" #include "gazebo/gui/SaveDialogPrivate.hh" #include "gazebo/gui/SaveDialog.hh" #include "gazebo/gazebo_config.h" using namespace gazebo; using namespace gui; ///////////////////////////////////////////////// SaveDialog::SaveDialog(int _mode, QWidget *_parent) : QDialog(_parent), dataPtr(new SaveDialogPrivate) { this->setObjectName("saveDialog"); this->setWindowTitle(tr("Save Model")); this->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); this->dataPtr->messageLabel = new QLabel; this->dataPtr->messageLabel->setText( tr("Pick a name and a location for your model.\n" "All the model files will be saved in the model folder.\n")); QLabel *modelLabel = new QLabel; modelLabel->setText(tr("Model Name:")); this->dataPtr->modelNameLineEdit = new QLineEdit; connect(this->dataPtr->modelNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(ModelNameChangedOnDialog(QString))); QLabel *modelHeader = new QLabel; modelHeader->setText(tr("Model")); QLabel *modelLocation = new QLabel; modelLocation->setText(tr("Location:")); this->dataPtr->modelLocationLineEdit = new QLineEdit; this->dataPtr->modelLocationLineEdit->setMinimumWidth(300); // Try to get path to home folder if (_mode == SaveMode::BUILDING) { this->dataPtr->modelLocationLineEdit->setText(QDir::homePath()+ "/building_editor_models/Untitled"); } else if (_mode == SaveMode::MODEL) { this->dataPtr->modelLocationLineEdit->setText(QDir::homePath()+ "/model_editor_models/Untitled"); } QPushButton *browseButton = new QPushButton(tr("Browse")); connect(browseButton, SIGNAL(clicked()), this, SLOT(OnBrowse())); QLabel *authorHeader = new QLabel; authorHeader->setText(tr("Author")); QLabel *modelAuthorName = new QLabel; modelAuthorName->setText(tr(" Name:")); this->dataPtr->modelAuthorNameLineEdit = new QLineEdit; QLabel *modelAuthorEmail = new QLabel; modelAuthorEmail->setText(tr(" Email:")); this->dataPtr->modelAuthorEmailLineEdit = new QLineEdit; QLabel *modelVersion = new QLabel; modelVersion->setText(tr(" Version:")); this->dataPtr->modelVersionLineEdit = new QLineEdit; this->dataPtr->modelVersionLineEdit->setText(tr("1.0")); QLabel *modelDescription = new QLabel; modelDescription->setText(tr(" Description:")); this->dataPtr->modelDescriptionLineEdit = new QLineEdit; // TODO Propshop integration /* QString contributeText( tr("Contribute this model to the Model Database so that\n" "the entire Gazebo community can benefit!\n" "[This will open up a new tab in your browser]\n")); QCheckBox *contributeCheckBox = new QCheckBox(contributeText);*/ QHBoxLayout *buttonsLayout = new QHBoxLayout; QPushButton *cancelButton = new QPushButton(tr("&Cancel")); connect(cancelButton, SIGNAL(clicked()), this, SLOT(OnCancel())); std::string saveButtonText = "&Save"; QPushButton *saveButton = new QPushButton(tr(saveButtonText.c_str())); saveButton->setDefault(true); connect(saveButton, SIGNAL(clicked()), this, SLOT(OnAcceptSave())); buttonsLayout->addWidget(cancelButton); buttonsLayout->addWidget(saveButton); buttonsLayout->setAlignment(Qt::AlignRight); QHBoxLayout *modelNameLayout = new QHBoxLayout; modelNameLayout->addWidget(modelLabel); modelNameLayout->addWidget(this->dataPtr->modelNameLineEdit); QHBoxLayout *locationLayout = new QHBoxLayout; locationLayout->addWidget(modelLocation); locationLayout->addWidget(this->dataPtr->modelLocationLineEdit); locationLayout->addWidget(browseButton); QRadioButton *advancedOptionsCollapser = new QRadioButton(); advancedOptionsCollapser->setFocusPolicy(Qt::NoFocus); advancedOptionsCollapser->setChecked(false); advancedOptionsCollapser->setText("Advanced Options"); advancedOptionsCollapser->setStyleSheet( "QRadioButton {\ color: #d0d0d0;\ }\ QRadioButton::indicator::unchecked {\ image: url(:/images/right_arrow.png);\ }\ QRadioButton::indicator::checked {\ image: url(:/images/down_arrow.png);\ }"); // initialize as "closed" (unchecked) // Button behavior: when "open", show advancedOptionsGrid connect(advancedOptionsCollapser, SIGNAL(toggled(bool)), this, SLOT(ToggleAdvancedOptions(bool))); QHBoxLayout *advancedOptions = new QHBoxLayout(); advancedOptions->addWidget(advancedOptionsCollapser); // Advanced options QGridLayout *advancedOptionsGrid = new QGridLayout(); advancedOptionsGrid->addWidget(modelHeader, 1, 0); advancedOptionsGrid->addWidget(modelVersion, 2, 0); advancedOptionsGrid->addWidget(this->dataPtr->modelVersionLineEdit, 2, 1); advancedOptionsGrid->addWidget(modelDescription, 3, 0); advancedOptionsGrid->addWidget(this->dataPtr->modelDescriptionLineEdit, 3, 1); advancedOptionsGrid->addWidget(authorHeader, 4, 0); advancedOptionsGrid->addWidget(modelAuthorName, 5, 0); advancedOptionsGrid->addWidget(this->dataPtr->modelAuthorNameLineEdit, 5, 1); advancedOptionsGrid->addWidget(modelAuthorEmail, 6, 0); advancedOptionsGrid->addWidget(this->dataPtr->modelAuthorEmailLineEdit, 6, 1); this->dataPtr->advancedOptionsWidget = new QWidget(); this->dataPtr->advancedOptionsWidget->setLayout(advancedOptionsGrid); this->dataPtr->advancedOptionsWidget->hide(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(this->dataPtr->messageLabel); mainLayout->addLayout(modelNameLayout); mainLayout->addLayout(locationLayout); mainLayout->addLayout(advancedOptions); mainLayout->addWidget(this->dataPtr->advancedOptionsWidget); mainLayout->addLayout(buttonsLayout); mainLayout->setSizeConstraint(QLayout::SetFixedSize); this->setLayout(mainLayout); } ///////////////////////////////////////////////// SaveDialog::~SaveDialog() { delete this->dataPtr; this->dataPtr = NULL; } ///////////////////////////////////////////////// std::string SaveDialog::GetModelName() const { return this->dataPtr->modelNameLineEdit->text().toStdString(); } ///////////////////////////////////////////////// std::string SaveDialog::GetSaveLocation() const { return this->dataPtr->modelLocationLineEdit->text().toStdString(); } ///////////////////////////////////////////////// std::string SaveDialog::GetAuthorName() const { return this->dataPtr->modelAuthorNameLineEdit->text().toStdString(); } ///////////////////////////////////////////////// std::string SaveDialog::GetAuthorEmail() const { return this->dataPtr->modelAuthorEmailLineEdit->text().toStdString(); } ///////////////////////////////////////////////// std::string SaveDialog::GetDescription() const { return this->dataPtr->modelDescriptionLineEdit->text().toStdString(); } ///////////////////////////////////////////////// std::string SaveDialog::GetVersion() const { return this->dataPtr->modelVersionLineEdit->text().toStdString(); } ///////////////////////////////////////////////// void SaveDialog::SetModelName(const std::string &_name) { this->dataPtr->modelNameLineEdit->setText(tr(_name.c_str())); } ///////////////////////////////////////////////// void SaveDialog::SetSaveLocation(const std::string &_location) { this->dataPtr->modelLocationLineEdit->setText(tr(_location.c_str())); } ///////////////////////////////////////////////// void SaveDialog::OnBrowse() { QFileDialog fileDialog(this, tr("Open Directory"), QDir::homePath()); fileDialog.setFileMode(QFileDialog::Directory); fileDialog.setOptions(QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); fileDialog.setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint); if (fileDialog.exec() == QDialog::Accepted) { QStringList selected = fileDialog.selectedFiles(); if (selected.empty()) return; // Substitute everything up to the model folder std::string folder = this->GetSaveLocation(); folder = folder.substr(folder.rfind("/")+1); this->SetSaveLocation(selected[0].toStdString() + "/" + folder); } } ///////////////////////////////////////////////// void SaveDialog::OnCancel() { this->close(); } ///////////////////////////////////////////////// void SaveDialog::OnAcceptSave() { this->accept(); } ///////////////////////////////////////////////// bool SaveDialog::OnSaveAs() { if (this->exec() == QDialog::Accepted) { if (this->GetModelName().size() == 0) { QMessageBox msgBox(QMessageBox::Warning, QString("Empty Name"), QString("Please give your model a non-empty name.")); msgBox.exec(); return this->OnSaveAs(); } if (this->GetSaveLocation().size() == 0) { QMessageBox msgBox(QMessageBox::Warning, QString("Empty Location"), QString("Please give a path to where your model will be saved.")); msgBox.exec(); return this->OnSaveAs(); } boost::filesystem::path path; path = path / this->GetSaveLocation(); if (!boost::filesystem::exists(path)) { if (!boost::filesystem::create_directories(path)) { gzerr << "Couldn't create folder for model files." << std::endl; return false; } gzmsg << "Created folder " << path << " for model files." << std::endl; } boost::filesystem::path modelConfigPath = path / "model.config"; boost::filesystem::path sdfPath = path / "model.sdf"; // Before writing if (boost::filesystem::exists(sdfPath) || boost::filesystem::exists(modelConfigPath)) { std::string msg = "A model already exists in folder \n" + path.string() + ".\n\n" + "Do you wish to overwrite the existing model files?\n"; QMessageBox msgBox(QMessageBox::Warning, QString("Files Exist"), QString(msg.c_str())); QPushButton *cancelButton = msgBox.addButton("Cancel", QMessageBox::RejectRole); QPushButton *saveButton = msgBox.addButton("Save", QMessageBox::AcceptRole); msgBox.setDefaultButton(saveButton); msgBox.setEscapeButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != saveButton) { return this->OnSaveAs(); } } this->AddDirToModelPaths(this->GetSaveLocation()); return true; } return false; } ///////////////////////////////////////////////// void SaveDialog::ToggleAdvancedOptions(bool _checked) { if (_checked) { this->dataPtr->advancedOptionsWidget->show(); } else { this->dataPtr->advancedOptionsWidget->hide(); } } ///////////////////////////////////////////////// void SaveDialog::AddDirToModelPaths(const std::string &_path) { std::string parentDirectory = boost::filesystem::path(_path) .parent_path().string(); std::list modelPaths = gazebo::common::SystemPaths::Instance()->GetModelPaths(); std::list::iterator iter; for (iter = modelPaths.begin(); iter != modelPaths.end(); ++iter) { if (iter->compare(parentDirectory) == 0) { break; } } gazebo::common::SystemPaths::Instance()-> AddModelPathsUpdate(parentDirectory); std::string additionalProperties = gui::getINIProperty("model_paths.filenames", ""); if (additionalProperties.find(parentDirectory) == std::string::npos) { // Append it to gui.ini if (additionalProperties.empty()) additionalProperties = parentDirectory; else additionalProperties = additionalProperties + ":" + parentDirectory; gui::setINIProperty("model_paths.filenames", additionalProperties); // Save any changes that were made to the property tree // TODO: check gui.ini env variable char *home = getenv("HOME"); if (home) { boost::filesystem::path guiINIPath = home; guiINIPath = guiINIPath / ".gazebo" / "gui.ini"; saveINI(guiINIPath); } } } ///////////////////////////////////////////////// std::string SaveDialog::GetTemplateConfigString() { std::ostringstream newModelStr; newModelStr << "" << "" << "template_model" << "1.0" << "model.sdf" << "" << "author_name" << "author_email" << "" << "Made with Gazebo" << ""; return newModelStr.str(); } ///////////////////////////////////////////////// void SaveDialog::GenerateConfig() { // Create an xml config file this->dataPtr->modelConfig.Clear(); this->dataPtr->modelConfig.Parse(this->GetTemplateConfigString().c_str()); TiXmlElement *modelXML = this->dataPtr ->modelConfig.FirstChildElement("model"); if (!modelXML) { gzerr << "No model name in default config file" << std::endl; return; } TiXmlElement *modelNameXML = modelXML->FirstChildElement("name"); modelNameXML->FirstChild()->SetValue(this->GetModelName()); TiXmlElement *versionXML = modelXML->FirstChildElement("version"); if (!versionXML) { gzerr << "Couldn't find model version" << std::endl; versionXML->FirstChild()->SetValue("1.0"); } else { versionXML->FirstChild()->SetValue(this->GetVersion()); } TiXmlElement *descriptionXML = modelXML->FirstChildElement("description"); if (!descriptionXML) { gzerr << "Couldn't find model description" << std::endl; descriptionXML->FirstChild()->SetValue(""); } else { descriptionXML->FirstChild()->SetValue(this->GetDescription()); } // TODO: Multiple authors TiXmlElement *authorXML = modelXML->FirstChildElement("author"); if (!authorXML) { gzerr << "Couldn't find model author" << std::endl; } else { TiXmlElement *authorChild = authorXML->FirstChildElement("name"); if (!authorChild) { gzerr << "Couldn't find author name" << std::endl; authorChild->FirstChild()->SetValue(""); } else { authorChild->FirstChild()->SetValue(this->GetAuthorName()); } authorChild = authorXML->FirstChildElement("email"); if (!authorChild) { gzerr << "Couldn't find author email" << std::endl; authorChild->FirstChild()->SetValue(""); } else { authorChild->FirstChild()->SetValue(this->GetAuthorEmail()); } } } ///////////////////////////////////////////////// void SaveDialog::SaveToConfig() { boost::filesystem::path path(this->GetSaveLocation()); path = path / "model.config"; const char* modelConfigString = path.string().c_str(); this->dataPtr->modelConfig.SaveFile(modelConfigString); gzdbg << "Saved file to " << modelConfigString << std::endl; } ///////////////////////////////////////////////// void SaveDialog::SaveToSDF(sdf::SDFPtr _modelSDF) { std::ofstream savefile; boost::filesystem::path path(this->GetSaveLocation()); path = path / "model.sdf"; // FIXME savefile.open(path.string().c_str()); if (!savefile.is_open()) { gzerr << "Couldn't open file for writing: " << path.string() << std::endl; return; } savefile << _modelSDF->ToString(); savefile.close(); gzdbg << "Saved file to " << path.string() << std::endl; this->AddDirToModelPaths(this->GetSaveLocation()); } ///////////////////////////////////////////////// std::string SaveDialog::GetFolderNameFromModelName(const std::string &_modelName) { // Auto-generate folder name based on model name std::string foldername = _modelName; std::vector > replacePairs; replacePairs.push_back(std::pair(" ", "_")); for (unsigned int i = 0; i < replacePairs.size(); ++i) { std::string forbiddenChar = replacePairs[i].first; std::string replaceChar = replacePairs[i].second; size_t index = foldername.find(forbiddenChar); while (index != std::string::npos) { foldername.replace(index, forbiddenChar.size(), replaceChar); index = foldername.find(forbiddenChar); } } return foldername; } ///////////////////////////////////////////////// void SaveDialog::ModelNameChangedOnDialog(QString _modelName) { std::string folderName = this->GetFolderNameFromModelName( _modelName.toStdString()); // Use current path and change only last folder name std::string path = this->GetSaveLocation(); path = path.substr(0, path.rfind("/")+1); path += folderName; this->SetSaveLocation(path); }