toolbox_widget.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
26 
28 
29 #include <QFileDialog>
30 #include <QMessageBox>
31 
32 #include <fstream>
33 #include <memory>
34 
35 namespace corbo {
36 namespace gui {
37 
38 ToolboxWidget::ToolboxWidget(SignalHelper::Ptr signal_helper, std::shared_ptr<MasterServiceClient> rpc, QWidget* parent)
39  : QWidget(parent), _signal_helper(signal_helper), _rpc_client(rpc)
40 {
41  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
42 
43  if (rpc) _param_message = rpc->createParameterMsg();
44 
45  createToolboxContent();
46 
47  // register meta types for queued signal-slot connections
48  qRegisterMetaType<messages::Signal>("messages::Signal");
49 }
50 
51 ToolboxWidget::~ToolboxWidget()
52 {
53  _rpc_task_thread.quit();
54  _rpc_task_thread.wait();
55 }
56 
57 QSize ToolboxWidget::sizeHint() const { return QSize(350, 800); }
58 
59 void ToolboxWidget::createToolboxContent()
60 {
61  QVBoxLayout* layout = new QVBoxLayout(this);
62  // layout->setContentsMargins(0, 0, 0, 0);
63  layout->setAlignment(Qt::AlignTop);
64 
65  _toolbox = new QToolBox;
66  // _toolbox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
67 
68  // create toolbox items according to corboParameters message;
69  initializeToolboxes();
70 
71  layout->addWidget(_toolbox);
72 
73  // add hline and button:
74  layout->addSpacing(20);
75  QFrame* hline = new QFrame;
76  hline->setFrameShape(QFrame::HLine);
77  hline->setFrameShadow(QFrame::Sunken);
78  hline->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
79  layout->addWidget(hline);
80 
81  layout->addSpacing(20);
82 
83  QHBoxLayout* btn_layout = new QHBoxLayout;
84  _btn_perform_task = new QPushButton(_task_button_label_active);
85  _btn_perform_task->setMaximumWidth(200);
86  _btn_perform_task->setMaximumHeight(40);
87  connect(_btn_perform_task, &QPushButton::clicked, [this](bool) { performTask(); });
88  btn_layout->addWidget(_btn_perform_task);
89  layout->addLayout(btn_layout);
90 
91  // layout->addStretch();
92 }
93 
94 void ToolboxWidget::initializeToolboxes()
95 {
96  if (!_param_message) return;
97 
98  if (!_param_widgets.empty())
99  {
100  for (ParameterWidget* widget : _param_widgets) widget->deleteLater();
101  }
102 
103  // create toolbox items according to corboParameters message;
104  namespace p = google::protobuf;
105  const p::Descriptor* desc = _param_message->GetDescriptor();
106  int num_fields = desc->field_count();
107  for (int i = 0; i < num_fields; ++i)
108  {
109  const p::FieldDescriptor* field = desc->field(i);
110  if (field->cpp_type() != p::FieldDescriptor::CPPTYPE_MESSAGE)
111  {
112  PRINT_ERROR(
113  "ToolboxWidget::createToolboxContent(): non-message-field found in parameter message. Only message fields are allowed in top-level");
114  continue;
115  }
116 
117  QString name = QString::fromStdString(field->name());
118  name[0] = name[0].toUpper();
119 
120  ParameterWidget* param_widget = new ParameterWidget(&_param_cache, this);
121  _toolbox->addItem(param_widget, name);
122  connect(param_widget, &ParameterWidget::updatedOneOfField, [this](const QString& /*text*/) {
123  if (_signal_auto_update) getAvailableSignals();
124  });
125  connect(param_widget, &ParameterWidget::signalUpdateRequested, this, &ToolboxWidget::getAvailableSignals);
126 
127  // TODO(roesmann): We can directly modify _param_message field instead of copying each time in fromParameter and toParameters
128  _param_widgets.push_back(param_widget);
129  }
130 }
131 
132 void ToolboxWidget::updateParameterWidgets()
133 {
134  namespace p = google::protobuf;
135  const p::Descriptor* desc = _param_message->GetDescriptor();
136  int num_fields = desc->field_count();
137 
138  if (num_fields != _param_widgets.size())
139  {
140  PRINT_ERROR("ToolboxWidget::updateParameterWidgets(): Number of fields does not match number of ParameterWidgets.");
141  return;
142  }
143 
144  _signal_auto_update = false; // deactivate signal auto update since we do not want intermediate callbacks to trigger new signals
145 
146  for (int i = 0; i < num_fields; ++i)
147  {
148  const p::FieldDescriptor* field = desc->field(i);
149 
150  if (field->cpp_type() != p::FieldDescriptor::CPPTYPE_MESSAGE)
151  {
152  PRINT_ERROR("ToolboxWidget::fromMessage(): non-message-field found in parameter message. Only message fields are allowed in top-level");
153  return;
154  }
155  _param_widgets[i]->generateFromAllocatedField(_param_message.get(), field);
156  }
157 
158  _signal_auto_update = true; // reactivate signal auto update
159 
160  // now also get available signals
161  getAvailableSignals();
162 }
163 
164 void ToolboxWidget::requestParametersFromService()
165 {
166  if (!_rpc_client)
167  {
168  clearParameters();
169  return;
170  }
171 
172  if (_rpc_client->getParameters(*_param_message))
173  {
174  updateParameterWidgets();
175  }
176 }
177 
178 bool ToolboxWidget::sendParametersToService(bool suppress_warning)
179 {
180  bool ret_val = true;
181  QString status;
182 
183  if (!_rpc_client)
184  {
185  status += "Cannot connect to RPC client / connection lost\n";
186  if (!suppress_warning) QMessageBox::warning(this, tr("RPC Connection Error"), status);
187  return false;
188  }
189 
190  messages::Status status_msg;
191  if (_rpc_client->setParameters(*_param_message, status_msg))
192  {
193  if (!status_msg.ok())
194  {
195  status += QString::fromStdString(status_msg.text()) + "\n";
196  ret_val = false;
197  }
198  }
199 
200  if (ret_val == false && !suppress_warning) QMessageBox::warning(this, tr("Invalid Configuration"), status);
201  return ret_val;
202 }
203 
204 void ToolboxWidget::performTask()
205 {
206  if (!_rpc_client) return;
207 
208  if (_task_running)
209  {
210  if (_rpc_client->stopTask()) setTaskRunningFlag(false);
211  return;
212  }
213 
214  // first update parameters
215  if (!sendParametersToService()) return;
216 
217  // perform actual task
218  setTaskRunningFlag(true);
219 
220  RpcTaskWorker* rpc_worker = new RpcTaskWorker;
221  rpc_worker->setRpcClient(_rpc_client);
222  rpc_worker->moveToThread(&_rpc_task_thread);
223  connect(&_rpc_task_thread, SIGNAL(started()), rpc_worker, SLOT(performTask()));
224  connect(rpc_worker, &RpcTaskWorker::feedback, this, [this](const messages::Signal& signal) { _signal_helper->addSignal(signal); });
225  connect(rpc_worker, SIGNAL(taskFinished(bool, const QString&)), this, SLOT(rpcTaskFinished(bool, const QString&)));
226  connect(&_rpc_task_thread, SIGNAL(finished()), rpc_worker, SLOT(deleteLater()));
227 
228  _rpc_task_thread.start(QThread::NormalPriority);
229 }
230 
231 void ToolboxWidget::rpcTaskFinished(bool success, const QString& msg)
232 {
233  setTaskRunningFlag(false);
234 
235  _rpc_task_thread.quit(); // stop thread's event-loop
236 
237  if (success)
238  {
239  emit taskCompleted(QDateTime::currentDateTime());
240 
241  // add parameters to cache
242  // TODO(roesmann) avoid copying by directly writing form ParameterWidgets to _param_message
243  // toMessage(*_param_message);
244  _task_cache.toCache(std::to_string(_signal_helper->currentSeriesId()), *_param_message);
245  // TODO(roesmann) we create a copy in both toMessage() and toCache() again: place for improving efficiency
246 
247  // increase the id for the next run
248  _signal_helper->startNewSeries();
249  }
250  else
251  {
252  QMessageBox::warning(this, tr("Something went wrong..."), msg);
253  return;
254  }
255 
256  // request parameters for next task
257  if (_signal_auto_update) getAvailableSignals();
258 }
259 
260 void ToolboxWidget::getAvailableSignals()
261 {
262  if (!_signal_auto_update) return; // TODO(roesmann) quick workaround: we do not want to update signals at all if signal update is deactivated
263 
264  if (!_rpc_client) return;
265 
266  // first update parameters
267  sendParametersToService(true);
268 
269  // get current signals
270  _signal_helper->clearCurrentSeries(); // TODO(roesmann) instead of clearing everything, check which signals are updated and erase only
271  // the other ones!
272  auto signal_feedback = [this](const messages::Signal& signal) { _signal_helper->addSignal(signal); };
273  _rpc_client->getAvailableSignals(signal_feedback);
274 }
275 
276 void ToolboxWidget::restoreTaskParameters(int task_id)
277 {
278  std::unique_ptr<google::protobuf::Message> cached_msg = _task_cache.fromCache(std::to_string(task_id));
279  if (cached_msg)
280  {
281  fromMessage(*cached_msg);
282  }
283 }
284 
285 void ToolboxWidget::removeFromTaskCache(int task_id) { _task_cache.erase(std::to_string(task_id)); }
286 
287 void ToolboxWidget::saveCurrentParametersToFile()
288 {
289  if (!_param_message) return;
290  saveParametersToFile(*_param_message);
291 }
292 
293 void ToolboxWidget::saveParametersToFile(int task_id)
294 {
295  std::unique_ptr<google::protobuf::Message> cached_msg = _task_cache.fromCache(std::to_string(task_id));
296  if (cached_msg)
297  saveParametersToFile(*cached_msg);
298  else
299  QMessageBox::warning(this, "Warning", "Cannot save parameters to file, since not found in cache.");
300 }
301 
302 void ToolboxWidget::saveParametersToFile(const google::protobuf::Message& params)
303 {
304  QString default_filter = tr("Parameter file (*.cparams)");
305 #ifdef __linux__
306  QString filename = QFileDialog::getSaveFileName(this, tr("Export Parameters"), "parameters.cparams",
307  tr("Parameter file (*.cparams);;All files (*.*)"), &default_filter);
308 #else
309  QString filename =
310  QFileDialog::getSaveFileName(this, tr("Export Parameters"), "parameters", tr("Parameter file (*.cparams);;All files (*.*)"), &default_filter);
311 #endif
312  if (filename.isEmpty()) return;
313 
314  // QFile file(filename);
315  std::ofstream file(filename.toStdString(), std::ofstream::binary | std::ofstream::trunc);
316 
317  if (!file.is_open())
318  {
319  QMessageBox::warning(this, "Warning", "Cannot open file with write-permissions for exporting parameters.");
320  return;
321  }
322  params.SerializeToOstream(&file);
323 
324  emit parameterSavedToFile(filename);
325 }
326 
327 void ToolboxWidget::loadParametersFromFile()
328 {
329  QString default_filter = tr("Parameter file (*.cparams)");
330  QString filename =
331  QFileDialog::getOpenFileName(this, tr("Import Parameters"), "parameters", tr("Parameter file (*.cparams);;All files (*.*)"), &default_filter);
332  loadParametersFromFile(filename);
333 }
334 
335 void ToolboxWidget::loadParametersFromFile(const QString& filepath)
336 {
337  if (!_param_message)
338  {
339  QMessageBox::warning(this, "Warning", "Cannot load parameter before RPC client is configured.");
340  return;
341  }
342 
343  if (filepath.isEmpty()) return;
344 
345  std::ifstream file(filepath.toStdString());
346 
347  if (!file.is_open())
348  {
349  QMessageBox::warning(this, "Warning", "Cannot open file with read-permissions for importing parameters.");
350  return;
351  }
352 
353  _param_message->Clear();
354  if (_param_message->ParsePartialFromIstream(&file))
355  {
356  updateParameterWidgets();
357  emit parameterLoadedFromFile(filepath);
358  }
359 }
360 
361 void ToolboxWidget::clearParameters()
362 {
363  for (ParameterWidget* param_widget : _param_widgets) param_widget->clearElements();
364 }
365 
366 void ToolboxWidget::setRpcClient(std::shared_ptr<MasterServiceClient> rpc_client)
367 {
368  _rpc_client = rpc_client;
369  if (_rpc_client)
370  {
371  _param_message = rpc_client->createParameterMsg();
372  initializeToolboxes();
373  requestParametersFromService();
374  }
375 }
376 
377 void ToolboxWidget::fromMessage(const google::protobuf::Message& parameters)
378 {
379  if (!_param_message) return;
380 
381  _param_message->CopyFrom(parameters);
382  updateParameterWidgets();
383 }
384 
385 void ToolboxWidget::toMessage(google::protobuf::Message& parameters) const
386 {
387  if (!_param_message) return;
388 
389  parameters.CopyFrom(*_param_message);
390 }
391 
392 void ToolboxWidget::setTaskRunningFlag(bool running)
393 {
394  _task_running = running;
395  if (running)
396  _btn_perform_task->setText(_task_button_label_inactive);
397  else
398  _btn_perform_task->setText(_task_button_label_active);
399 }
400 
401 } // namespace gui
402 } // namespace corbo
corbo
Definition: communication/include/corbo-communication/utilities.h:37
corbo::gui::ToolboxWidget::ToolboxWidget
ToolboxWidget(SignalHelper::Ptr signal_helper, std::shared_ptr< MasterServiceClient > rpc, QWidget *parent=0)
Definition: toolbox_widget.cpp:82
corbo::gui::ParameterWidget
Definition: parameter_widget.h:92
relicense.filename
filename
Definition: relicense.py:57
Eigen::LevenbergMarquardtSpace::Status
Status
Definition: LevenbergMarquardt/LevenbergMarquardt.h:25
corbo::gui::RpcTaskWorker
Definition: rpc_task_worker.h:83
toolbox_widget.h
rpc_task_worker.h
corbo::gui::SignalHelper::Ptr
std::shared_ptr< SignalHelper > Ptr
Definition: signal_helper.h:111
corbo::gui::RpcTaskWorker::setRpcClient
void setRpcClient(std::shared_ptr< MasterServiceClient > client)
Definition: rpc_task_worker.h:114
PRINT_ERROR
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Wed Mar 2 2022 00:07:06