select_service_dialog.cpp
Go to the documentation of this file.
1 // *****************************************************************************
2 //
3 // Copyright (c) 2015, Southwest Research Institute® (SwRI®)
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Southwest Research Institute® (SwRI®) nor the
14 // names of its contributors may be used to endorse or promote products
15 // derived from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE
21 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
27 // DAMAGE.
28 //
29 // *****************************************************************************
30 
32 
33 #include <QCloseEvent>
34 #include <QHBoxLayout>
35 #include <QLabel>
36 #include <QLineEdit>
37 #include <QListWidget>
38 #include <QMessageBox>
39 #include <QPushButton>
40 #include <QTimerEvent>
41 #include <QVBoxLayout>
42 
43 #include <rosapi/Services.h>
44 #include <rosapi/ServicesForType.h>
45 
46 namespace mapviz
47 {
49  {
50  ros::ServiceClient client;
51 
52  if (allowed_datatype_.empty())
53  {
54  client = nh_.serviceClient<rosapi::Services>("/rosapi/services");
55  }
56  else
57  {
58  client = nh_.serviceClient<rosapi::ServicesForType>("/rosapi/services_for_type");
59  }
60 
61  if (!client.waitForExistence(ros::Duration(1)))
62  {
63  // Check to see whether the rosapi services are actually running.
64  Q_EMIT fetchingFailed(tr("Unable to list ROS services. Is rosapi_node running?"));
65  return;
66  }
67 
68  if (allowed_datatype_.empty())
69  {
70  rosapi::Services srv;
71 
72  ROS_DEBUG("Listing all services.");
73  if (client.call(srv))
74  {
75  Q_EMIT servicesFetched(srv.response.services);
76  }
77  }
78  else {
79  rosapi::ServicesForType srv;
80  srv.request.type = allowed_datatype_;
81 
82  ROS_DEBUG("Listing services for type %s", srv.request.type.c_str());
83  if (client.call(srv))
84  {
85  Q_EMIT servicesFetched(srv.response.services);
86  }
87  else
88  {
89  // If there are any dead or unreachable nodes that provide services, even if
90  // they're not of the service type we're looking for, the services_for_type
91  // service will have an error and not return anything. Super annoying.
92  Q_EMIT fetchingFailed(tr("Unable to list ROS services. You may have " \
93  "dead nodes; try running \"rosnode cleanup\"."));
94  }
95  }
96  }
97 
98  std::string SelectServiceDialog::selectService(const std::string& datatype, QWidget* parent)
99  {
100  SelectServiceDialog dialog(datatype, parent);
101  dialog.setDatatypeFilter(datatype);
102  if (dialog.exec() == QDialog::Accepted) {
103  return dialog.selectedService();
104  } else {
105  return "";
106  }
107  }
108 
109  SelectServiceDialog::SelectServiceDialog(const std::string& datatype, QWidget* parent)
110  :
111  QDialog(parent),
112  allowed_datatype_(datatype),
113  cancel_button_(new QPushButton("&Cancel")),
114  list_widget_(new QListWidget()),
115  name_filter_(new QLineEdit()),
116  ok_button_(new QPushButton("&Ok"))
117  {
118  QHBoxLayout *filter_box = new QHBoxLayout();
119  filter_box->addWidget(new QLabel("Filter:"));
120  filter_box->addWidget(name_filter_);
121 
122  QHBoxLayout *button_box = new QHBoxLayout();
123  button_box->addStretch(1);
124  button_box->addWidget(cancel_button_);
125  button_box->addWidget(ok_button_);
126 
127  QVBoxLayout *vbox = new QVBoxLayout();
128  vbox->addWidget(list_widget_);
129  vbox->addLayout(filter_box);
130  vbox->addLayout(button_box);
131  setLayout(vbox);
132 
133  // This is ugly, but necessary in order to be able to send a std::vector<std::string>
134  // via a queued signal/slot connection.
135  qRegisterMetaType<ServiceStringVector>("ServiceStringVector");
136 
137  connect(ok_button_, SIGNAL(clicked(bool)),
138  this, SLOT(accept()));
139  connect(cancel_button_, SIGNAL(clicked(bool)),
140  this, SLOT(reject()));
141  connect(name_filter_, SIGNAL(textChanged(const QString &)),
142  this, SLOT(updateDisplayedServices()));
143 
144  ok_button_->setDefault(true);
145 
146  setWindowTitle("Select service...");
147 
148  fetch_services_timer_id_ = startTimer(5000);
149  fetchServices();
150  }
151 
153  {
154  if (worker_thread_)
155  {
156  // If the thread's parent is destroyed before the thread has finished,
157  // it will cause a segmentation fault. We'll wait a few seconds for
158  // it to finish cleanly, and if that doesn't work, try to force it to
159  // die and wait a few more.
160  worker_thread_->wait(5000);
161  if (worker_thread_->isRunning())
162  {
163  worker_thread_->terminate();
164  worker_thread_->wait(2000);
165  }
166  }
167  }
168 
170  {
171  // If we don't currently have a worker thread or the previous one has
172  // finished, start a new one.
173  if (!worker_thread_ || worker_thread_->isFinished())
174  {
176  QObject::connect(worker_thread_.get(),
177  SIGNAL(servicesFetched(ServiceStringVector)),
178  this,
180  QObject::connect(worker_thread_.get(),
181  SIGNAL(fetchingFailed(const QString)),
182  this,
183  SLOT(displayUpdateError(const QString)));
184  worker_thread_->start();
185  }
186  }
187 
189  {
190  known_services_ = services;
192  }
193 
194  void SelectServiceDialog::displayUpdateError(const QString error_msg)
195  {
196  killTimer(fetch_services_timer_id_);
197  QMessageBox mbox(this->parentWidget());
198  mbox.setIcon(QMessageBox::Warning);
199  mbox.setText(error_msg);
200  mbox.exec();
201  }
202 
203  std::vector<std::string> SelectServiceDialog::filterServices()
204  {
205  std::vector<std::string> filtered_services;
206 
207  QString filter_text = name_filter_->text();
208 
209  Q_FOREACH(const std::string& service, known_services_)
210  {
211  if (QString::fromStdString(service).contains(filter_text, Qt::CaseInsensitive))
212  {
213  filtered_services.push_back(service);
214  }
215  }
216 
217  return filtered_services;
218  }
219 
221  {
222  std::vector<std::string> next_displayed_services = filterServices();
223 
224  // It's a lot more work to keep track of the additions/removals like
225  // this compared to resetting the QListWidget's items each time, but
226  // it allows Qt to properly track the selection and current items
227  // across updates, which results in much less frustration for the user.
228 
229  std::set<std::string> prev_names;
230  for (size_t i = 0; i < displayed_services_.size(); i++) {
231  prev_names.insert(displayed_services_[i]);
232  }
233 
234  std::set<std::string> next_names;
235  for (size_t i = 0; i < next_displayed_services.size(); i++) {
236  next_names.insert(next_displayed_services[i]);
237  }
238 
239  std::set<std::string> added_names;
240  std::set_difference(next_names.begin(), next_names.end(),
241  prev_names.begin(), prev_names.end(),
242  std::inserter(added_names, added_names.end()));
243 
244  std::set<std::string> removed_names;
245  std::set_difference(prev_names.begin(), prev_names.end(),
246  next_names.begin(), next_names.end(),
247  std::inserter(removed_names, removed_names.end()));
248 
249  // Remove all the removed names
250  size_t removed = 0;
251  for (size_t i = 0; i < displayed_services_.size(); i++) {
252  if (removed_names.count(displayed_services_[i]) == 0) {
253  continue;
254  }
255 
256  QListWidgetItem *item = list_widget_->takeItem(i - removed);
257  delete item;
258  removed++;
259  }
260 
261  // Now we can add the new items.
262  for (size_t i = 0; i < next_displayed_services.size(); i++) {
263  if (added_names.count(next_displayed_services[i]) == 0) {
264  continue;
265  }
266 
267  list_widget_->insertItem(i, QString::fromStdString(next_displayed_services[i]));
268  if (list_widget_->count() == 1) {
269  list_widget_->setCurrentRow(0);
270  }
271  }
272 
273  displayed_services_.swap(next_displayed_services);
274  }
275 
276  void SelectServiceDialog::setDatatypeFilter(const std::string& datatype)
277  {
278  allowed_datatype_ = datatype;
280  }
281 
283  {
284  QModelIndex qt_selection = list_widget_->selectionModel()->currentIndex();
285 
286  if (qt_selection.isValid()) {
287  int row = qt_selection.row();
288  if (row < static_cast<int>(displayed_services_.size())) {
289  return displayed_services_[row];
290  }
291  }
292 
293  return "";
294  }
295 
296  void SelectServiceDialog::timerEvent(QTimerEvent* event)
297  {
298  if (event->timerId() == fetch_services_timer_id_) {
299  fetchServices();
300  }
301  }
302 
303  void SelectServiceDialog::closeEvent(QCloseEvent* event)
304  {
305  // We don't need to keep making requests from the ROS master.
306  killTimer(fetch_services_timer_id_);
307  QDialog::closeEvent(event);
308  }
309 }
void setDatatypeFilter(const std::string &datatype)
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
std::vector< std::string > known_services_
void servicesFetched(ServiceStringVector services)
bool call(MReq &req, MRes &res)
const std::string & allowed_datatype_
void updateKnownServices(ServiceStringVector services)
SelectServiceDialog(const std::string &datatype="", QWidget *parent=0)
std::vector< std::string > displayed_services_
boost::shared_ptr< ServiceUpdaterThread > worker_thread_
bool waitForExistence(ros::Duration timeout=ros::Duration(-1))
QT_END_NAMESPACE typedef std::vector< std::string > ServiceStringVector
std::vector< std::string > filterServices()
static std::string selectService(const std::string &datatype, QWidget *parent=0)
void fetchingFailed(const QString error_msg)
#define ROS_DEBUG(...)


mapviz
Author(s): Marc Alban
autogenerated on Fri Mar 19 2021 02:44:25