select_service_dialog.cpp
Go to the documentation of this file.
00001 // *****************************************************************************
00002 //
00003 // Copyright (c) 2015, Southwest Research Institute® (SwRI®)
00004 // All rights reserved.
00005 //
00006 // Redistribution and use in source and binary forms, with or without
00007 // modification, are permitted provided that the following conditions are met:
00008 //     * Redistributions of source code must retain the above copyright
00009 //       notice, this list of conditions and the following disclaimer.
00010 //     * Redistributions in binary form must reproduce the above copyright
00011 //       notice, this list of conditions and the following disclaimer in the
00012 //       documentation and/or other materials provided with the distribution.
00013 //     * Neither the name of Southwest Research Institute® (SwRI®) nor the
00014 //       names of its contributors may be used to endorse or promote products
00015 //       derived from this software without specific prior written permission.
00016 //
00017 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020 // ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE 
00021 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
00022 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
00023 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
00024 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
00025 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
00026 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00027 // DAMAGE.
00028 //
00029 // *****************************************************************************
00030 
00031 #include <mapviz/select_service_dialog.h>
00032 
00033 #include <QCloseEvent>
00034 #include <QHBoxLayout>
00035 #include <QLabel>
00036 #include <QLineEdit>
00037 #include <QListWidget>
00038 #include <QMessageBox>
00039 #include <QPushButton>
00040 #include <QTimerEvent>
00041 #include <QVBoxLayout>
00042 
00043 #include <rosapi/Services.h>
00044 #include <rosapi/ServicesForType.h>
00045 #if QT_VERSION >= 0x050000
00046 #include <QtWidgets/QMessageBox>
00047 #else
00048 #include <QtGui/QMessageBox>
00049 #endif
00050 
00051 namespace mapviz
00052 {
00053   void ServiceUpdaterThread::run()
00054   {
00055     ros::ServiceClient client;
00056 
00057     if (allowed_datatype_.empty())
00058     {
00059       client = nh_.serviceClient<rosapi::Services>("/rosapi/services");
00060     }
00061     else
00062     {
00063       client = nh_.serviceClient<rosapi::ServicesForType>("/rosapi/services_for_type");
00064     }
00065 
00066     if (!client.waitForExistence(ros::Duration(1)))
00067     {
00068       // Check to see whether the rosapi services are actually running.
00069       Q_EMIT fetchingFailed(tr("Unable to list ROS services.  Is rosapi_node running?"));
00070       return;
00071     }
00072 
00073     if (allowed_datatype_.empty())
00074     {
00075       rosapi::Services srv;
00076 
00077       ROS_DEBUG("Listing all services.");
00078       if (client.call(srv))
00079       {
00080         Q_EMIT servicesFetched(srv.response.services);
00081       }
00082     }
00083     else {
00084       rosapi::ServicesForType srv;
00085       srv.request.type = allowed_datatype_;
00086 
00087       ROS_DEBUG("Listing services for type %s", srv.request.type.c_str());
00088       if (client.call(srv))
00089       {
00090         Q_EMIT servicesFetched(srv.response.services);
00091       }
00092       else
00093       {
00094         // If there are any dead or unreachable nodes that provide services, even if
00095         // they're not of the service type we're looking for, the services_for_type
00096         // service will have an error and not return anything.  Super annoying.
00097         Q_EMIT fetchingFailed(tr("Unable to list ROS services.  You may have " \
00098                               "dead nodes; try running \"rosnode cleanup\"."));
00099       }
00100     }
00101   }
00102 
00103   std::string SelectServiceDialog::selectService(const std::string& datatype, QWidget* parent)
00104   {
00105     SelectServiceDialog dialog(datatype, parent);
00106     dialog.setDatatypeFilter(datatype);
00107     if (dialog.exec() == QDialog::Accepted) {
00108       return dialog.selectedService();
00109     } else {
00110       return "";
00111     }
00112   }
00113 
00114   SelectServiceDialog::SelectServiceDialog(const std::string& datatype, QWidget* parent)
00115       :
00116       QDialog(parent),
00117       allowed_datatype_(datatype),
00118       cancel_button_(new QPushButton("&Cancel")),
00119       list_widget_(new QListWidget()),
00120       name_filter_(new QLineEdit()),
00121       ok_button_(new QPushButton("&Ok"))
00122   {
00123     QHBoxLayout *filter_box = new QHBoxLayout();
00124     filter_box->addWidget(new QLabel("Filter:"));
00125     filter_box->addWidget(name_filter_);
00126 
00127     QHBoxLayout *button_box = new QHBoxLayout();
00128     button_box->addStretch(1);
00129     button_box->addWidget(cancel_button_);
00130     button_box->addWidget(ok_button_);
00131 
00132     QVBoxLayout *vbox = new QVBoxLayout();
00133     vbox->addWidget(list_widget_);
00134     vbox->addLayout(filter_box);
00135     vbox->addLayout(button_box);
00136     setLayout(vbox);
00137 
00138     // This is ugly, but necessary in order to be able to send a std::vector<std::string>
00139     // via a queued signal/slot connection.
00140     qRegisterMetaType<ServiceStringVector>("ServiceStringVector");
00141 
00142     connect(ok_button_, SIGNAL(clicked(bool)),
00143             this, SLOT(accept()));
00144     connect(cancel_button_, SIGNAL(clicked(bool)),
00145             this, SLOT(reject()));
00146     connect(name_filter_, SIGNAL(textChanged(const QString &)),
00147             this, SLOT(updateDisplayedServices()));
00148 
00149     ok_button_->setDefault(true);
00150 
00151     setWindowTitle("Select service...");
00152 
00153     fetch_services_timer_id_ = startTimer(5000);
00154     fetchServices();
00155   }
00156 
00157   SelectServiceDialog::~SelectServiceDialog()
00158   {
00159     if (worker_thread_)
00160     {
00161       // If the thread's parent is destroyed before the thread has finished,
00162       // it will cause a segmentation fault.  We'll wait a few seconds for
00163       // it to finish cleanly, and if that doesn't work, try to force it to
00164       // die and wait a few more.
00165       worker_thread_->wait(5000);
00166       if (worker_thread_->isRunning())
00167       {
00168         worker_thread_->terminate();
00169         worker_thread_->wait(2000);
00170       }
00171     }
00172   }
00173 
00174   void SelectServiceDialog::fetchServices()
00175   {
00176     // If we don't currently have a worker thread or the previous one has
00177     // finished, start a new one.
00178     if (!worker_thread_ || worker_thread_->isFinished())
00179     {
00180       worker_thread_.reset(new ServiceUpdaterThread(nh_, allowed_datatype_, this));
00181       QObject::connect(worker_thread_.get(),
00182                        SIGNAL(servicesFetched(ServiceStringVector)),
00183                        this,
00184                        SLOT(updateKnownServices(ServiceStringVector)));
00185       QObject::connect(worker_thread_.get(),
00186                        SIGNAL(fetchingFailed(const QString)),
00187                        this,
00188                        SLOT(displayUpdateError(const QString)));
00189       worker_thread_->start();
00190     }
00191   }
00192 
00193   void SelectServiceDialog::updateKnownServices(ServiceStringVector services)
00194   {
00195     known_services_ = services;
00196     updateDisplayedServices();
00197   }
00198 
00199   void SelectServiceDialog::displayUpdateError(const QString error_msg)
00200   {
00201     killTimer(fetch_services_timer_id_);
00202     QMessageBox mbox(this->parentWidget());
00203     mbox.setIcon(QMessageBox::Warning);
00204     mbox.setText(error_msg);
00205     mbox.exec();
00206   }
00207 
00208   std::vector<std::string> SelectServiceDialog::filterServices()
00209   {
00210     std::vector<std::string> filtered_services;
00211 
00212     QString filter_text = name_filter_->text();
00213 
00214     Q_FOREACH(const std::string& service, known_services_)
00215     {
00216       if (QString::fromStdString(service).contains(filter_text, Qt::CaseInsensitive))
00217       {
00218         filtered_services.push_back(service);
00219       }
00220     }
00221 
00222     return filtered_services;
00223   }
00224 
00225   void SelectServiceDialog::updateDisplayedServices()
00226   {
00227     std::vector<std::string> next_displayed_services = filterServices();
00228 
00229     // It's a lot more work to keep track of the additions/removals like
00230     // this compared to resetting the QListWidget's items each time, but
00231     // it allows Qt to properly track the selection and current items
00232     // across updates, which results in much less frustration for the user.
00233 
00234     std::set<std::string> prev_names;
00235     for (size_t i = 0; i < displayed_services_.size(); i++) {
00236       prev_names.insert(displayed_services_[i]);
00237     }
00238 
00239     std::set<std::string> next_names;
00240     for (size_t i = 0; i < next_displayed_services.size(); i++) {
00241       next_names.insert(next_displayed_services[i]);
00242     }
00243 
00244     std::set<std::string> added_names;
00245     std::set_difference(next_names.begin(), next_names.end(),
00246                         prev_names.begin(), prev_names.end(),
00247                         std::inserter(added_names, added_names.end()));
00248 
00249     std::set<std::string> removed_names;
00250     std::set_difference(prev_names.begin(), prev_names.end(),
00251                         next_names.begin(), next_names.end(),
00252                         std::inserter(removed_names, removed_names.end()));
00253 
00254     // Remove all the removed names
00255     size_t removed = 0;
00256     for (size_t i = 0; i < displayed_services_.size(); i++) {
00257       if (removed_names.count(displayed_services_[i]) == 0) {
00258         continue;
00259       }
00260 
00261       QListWidgetItem *item = list_widget_->takeItem(i - removed);
00262       delete item;
00263       removed++;
00264     }
00265 
00266     // Now we can add the new items.
00267     for (size_t i = 0; i < next_displayed_services.size(); i++) {
00268       if (added_names.count(next_displayed_services[i]) == 0) {
00269         continue;
00270       }
00271 
00272       list_widget_->insertItem(i, QString::fromStdString(next_displayed_services[i]));
00273       if (list_widget_->count() == 1) {
00274         list_widget_->setCurrentRow(0);
00275       }
00276     }
00277 
00278     displayed_services_.swap(next_displayed_services);
00279   }
00280 
00281   void SelectServiceDialog::setDatatypeFilter(const std::string& datatype)
00282   {
00283     allowed_datatype_ = datatype;
00284     updateDisplayedServices();
00285   }
00286 
00287   std::string SelectServiceDialog::selectedService() const
00288   {
00289     QModelIndex qt_selection = list_widget_->selectionModel()->currentIndex();
00290 
00291     if (qt_selection.isValid()) {
00292       int row = qt_selection.row();
00293       if (row < static_cast<int>(displayed_services_.size())) {
00294         return displayed_services_[row];
00295       }
00296     }
00297 
00298     return "";
00299   }
00300 
00301   void SelectServiceDialog::timerEvent(QTimerEvent* event)
00302   {
00303       if (event->timerId() == fetch_services_timer_id_) {
00304         fetchServices();
00305       }
00306   }
00307 
00308   void SelectServiceDialog::closeEvent(QCloseEvent* event)
00309   {
00310     // We don't need to keep making requests from the ROS master.
00311     killTimer(fetch_services_timer_id_);
00312     QDialog::closeEvent(event);
00313   }
00314 }


mapviz
Author(s): Marc Alban
autogenerated on Thu Jun 6 2019 18:50:58