parameter_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 #include <corbo-core/console.h>
29 #include <corbo-core/utilities.h>
36 #include <corbo-gui/utilities.h>
37 
38 #include <QCheckBox>
39 #include <QLabel> // For testing only
40 #include <QMessageBox>
41 #include <QStringList>
42 #include <QVBoxLayout>
43 
44 #include <google/protobuf/reflection.h>
45 
46 #include <functional>
47 #include <memory>
48 
49 namespace corbo {
50 namespace gui {
51 
52 ParameterWidget::ParameterWidget(ParameterCache* cache, QWidget* parent) : QWidget(parent), _param_cache(cache)
53 {
54  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
55 
56  _layout = new QVBoxLayout(this);
57  _layout->setContentsMargins(5, 5, 5, 5);
58  _layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
59 }
60 
61 ParameterWidget::~ParameterWidget() {}
62 
63 QSize ParameterWidget::sizeHint() const { return QSize(300, 50); }
64 
65 void ParameterWidget::addOneOfField(const MessageParser::FieldInformation& info)
66 {
67  if (!info.oneof_descriptor) return; // no one of element;
68 
69  _has_parameters = true; // even if we do not have real parameters, one-ofs should be displayed (they can also be selected)
70 
71  // create oneof widget if not yet created for a previous parameter
72  OneOfParamWidget* oneof_widget;
73 
74  // QString oneof_widget_key = QString::fromStdString(info.oneof_descriptor->full_name());
75  QString oneof_widget_key;
76  auto it = info.nested_names.begin();
77  oneof_widget_key = QString::fromStdString(*it);
78  std::advance(it, 1);
79  for (; it != std::prev(info.nested_names.end()); ++it) oneof_widget_key += "." + QString::fromStdString(*it);
80 
81  // get one-of field name in parent message if available in order to allow multiple one-of of the
82  // same type in a single message
83  // QString one_of_field_name;
84  // auto nested_name_it = info.nested_names.end();
85  // take the entry before the last, since the last is already a field of the given one-of type.
86  // if (info.nested_names.size() > 1)
87  //{
88  // std::advance(nested_name_it, -2);
89  // one_of_field_name = QString::fromStdString(*nested_name_it);
90  //}
91  // oneof_widget_key += "." + one_of_field_name; // for top-level-one-of, one_of_field_name is empty.
92 
93  // check if key is already present
94  auto it_oneof = _oneof_widgets.find(oneof_widget_key);
95  if (it_oneof != _oneof_widgets.end())
96  {
97  // widget found
98  oneof_widget = it_oneof.value();
99  }
100  else
101  {
102  // create new oneof widget
103  QString widget_label = QString::fromStdString(info.oneof_descriptor->name()); // TODO(roesmann): separate label in protobuf
104  oneof_widget = new OneOfParamWidget(widget_label, _param_cache);
105  oneof_widget->nestedFieldName() = info.nested_names;
106 
107  oneof_widget->connect(oneof_widget, &OneOfParamWidget::currentParameterChanged,
108  [this](const QString& label) { emit updatedOneOfField(label); });
109  oneof_widget->connect(oneof_widget, &OneOfParamWidget::signalUpdateRequested, [this]() { emit signalUpdateRequested(); });
110 
111  _oneof_widgets.insert(oneof_widget_key, oneof_widget);
112 
113  addSubWidget(oneof_widget);
114  }
115  // now register current field
116  QString field_label = QString::fromStdString(info.field_raw->name()); // TODO(roesmann) actual label
117  oneof_widget->registerParameter(field_label, info);
118  return;
119 }
120 
121 void ParameterWidget::addParameterInt32(int value, const MessageParser::FieldInformation& info)
122 {
123  _has_parameters = true;
124 
125  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::TEXTEDIT;
126  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
127  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
128  int min = info.min_value.first ? static_cast<int>(info.min_value.second) : CORBO_MIN_INT;
129  int max = info.max_value.first ? static_cast<int>(info.max_value.second) : CORBO_MAX_INT;
130 
131  // add field description label if specified
132  if (info.print_description) addSubWidget(new QLabel(description));
133 
134  // check if we need to load a default value
135  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
136  {
137  // overwrite value
138  bool ok = false;
139  value = util::qstring_to_int(QString::fromStdString(info.default_value.second), &ok);
140  if (!ok)
141  {
142  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to int.");
143  value = 0;
144  }
145  info.message_raw->GetReflection()->SetInt32(info.message_raw, info.field_raw, value);
146  }
147 
148  switch (type)
149  {
150  case messages::GuiType::SLIDER:
151  {
152  LabelSliderWidget* param_widget = new LabelSliderWidget(label, value, 1, 0);
153  param_widget->setToolTip(description);
154  // if min and max are not specified, set some reasonable default limits:
155  if (!info.min_value.first) min = 0;
156  if (!info.max_value.first) max = 100;
157  param_widget->setMinMax(min, max);
158  addSubWidget(param_widget);
159 
160  // set up modifer signal
161  // message and field pointers must be valid for the lifetime of this object!
162  auto fun_write_param = [info, this](double value) {
163  int value_int = static_cast<int>(value);
164  info.message_raw->GetReflection()->SetInt32(info.message_raw, info.field_raw, value_int);
165  emit parameterInt32Updated(QString::fromStdString(MessageParser::nestedNamesToString(info.nested_names)), value_int);
166  };
167  connect(param_widget, &LabelSliderWidget::valueChanged, fun_write_param);
168  break;
169  }
170  case messages::GuiType::TEXTEDIT:
171  default:
172  {
173  LabelEditWidget* param_widget = new LabelEditWidget(label, util::int_to_qstring(value));
174  param_widget->setToolTip(description);
175  QRegExp regexp_no_decimal("^((?!\\.).)*$");
176  param_widget->widgetLineEdit()->setValidator(new QRegExpValidator(regexp_no_decimal, param_widget->widgetLineEdit()));
177  addSubWidget(param_widget);
178 
179  // set up modifer signal
180  // message and field pointers must be valid for the lifetime of this object!
181  auto fun_write_param = [info, param_widget, min, max, this]() {
182  // get values from line edit
183  bool ok;
184  int value = util::qstring_to_int(param_widget->widgetLineEdit()->text(), &ok);
185  bool out_of_range = false;
186 
187  // check limits
188  if (ok) out_of_range = !corbo::util::is_in_bounds(value, min, max);
189 
190  // set values if ok or revert if not
191  if (ok && !out_of_range)
192  {
193  info.message_raw->GetReflection()->SetInt32(info.message_raw, info.field_raw, value);
194  emit parameterInt32Updated(
195  parentFieldNames() + "." + QString::fromStdString(MessageParser::nestedNamesToString(info.nested_names)), value);
196  }
197  else
198  {
199  // revert to original value
200  param_widget->widgetLineEdit()->setText(
201  util::double_to_qstring(info.message_raw->GetReflection()->GetInt32(*info.message_raw, info.field_raw)));
202  }
203 
204  // We need to check this after resetting to the previous value in order to prevent displaying the warning twice.
205  if (out_of_range)
206  QMessageBox::warning(this, tr("Out of Range"),
207  "Parameter must be in the interval [" + util::int_to_qstring(min) + ", " + util::int_to_qstring(max) + "]");
208  };
209  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
210  break;
211  }
212  }
213 }
214 
215 void ParameterWidget::addParameterInt32Array(const std::vector<int>& values, const MessageParser::FieldInformation& info)
216 {
217  _has_parameters = true;
218 
219  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::TEXTEDIT;
220  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
221  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
222  int min = info.min_value.first ? info.min_value.second : CORBO_MIN_INT;
223  int max = info.max_value.first ? info.max_value.second : CORBO_MAX_INT;
224 
225  std::vector<int> values_mod = values;
226 
227  // add field description label if specified
228  if (info.print_description) addSubWidget(new QLabel(description));
229 
230  // check if we need to load default values
231  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
232  {
233  // overwrite value
234  bool ok = false;
235  util::qstring_to_container(QString::fromStdString(info.default_value.second), values_mod, &ok);
236  if (ok)
237  {
238  info.message_raw->GetReflection()->GetMutableRepeatedFieldRef<int>(info.message_raw, info.field_raw).CopyFrom(values_mod);
239  }
240  else
241  {
242  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to int array.");
243  values_mod = values;
244  }
245  }
246 
247  switch (type)
248  {
249  case messages::GuiType::TEXTEDIT:
250  default:
251  {
252  LabelEditWidget* param_widget = new LabelEditWidget(label, util::int_container_to_qstring(values_mod.begin(), values_mod.end()));
253  param_widget->setToolTip(description);
254  QRegExp regexp_no_decimal("^((?!\\.).)*$");
255  param_widget->widgetLineEdit()->setValidator(new QRegExpValidator(regexp_no_decimal, param_widget->widgetLineEdit()));
256  addSubWidget(param_widget);
257 
258  // set up modifer signal
259  // message and field pointers must be valid for the lifetime of this object!
260  auto fun_write_param = [info, param_widget, min, max, this]() {
261  // get values from line edit
262  bool ok;
263  std::vector<int> values;
264  util::qstring_to_container(param_widget->widgetLineEdit()->text(), values, &ok);
265 
266  // check dimension
267  bool dimension_range_exceeded = false;
268  int field_size = -1;
269  if (info.dynamic_size)
270  {
271  field_size = (int)values.size();
272  if (info.min_size.first && field_size < info.min_size.second)
273  {
274  dimension_range_exceeded = true;
275  ok = false;
276  }
277  else if (info.max_size.first && field_size < info.max_size.second)
278  {
279  dimension_range_exceeded = true;
280  ok = false;
281  }
282  }
283  else
284  {
285  int field_size = info.message_raw->GetReflection()->FieldSize(*info.message_raw, info.field_raw);
286  if (ok && (int)values.size() != field_size) ok = false;
287  }
288 
289  // check limits
290  bool out_of_range = false;
291  if (ok) out_of_range = !corbo::util::is_in_bounds_all(values.begin(), values.end(), min, max);
292 
293  // set values if ok or revert if not
294  if (ok && !out_of_range)
295  {
296  if (info.dynamic_size)
297  {
298  info.message_raw->GetReflection()
299  ->GetMutableRepeatedFieldRef<int>(info.message_raw, info.field_raw)
300  .CopyFrom(values); // this method resizes the field to the correct size
301  }
302  else
303  {
304  for (int idx = 0; idx < (int)values.size(); ++idx)
305  {
306  info.message_raw->GetReflection()->SetRepeatedInt32(info.message_raw, info.field_raw, idx, values[idx]);
307  }
308  }
309  }
310  else
311  {
312  // revert to original value
313  auto orig_values = info.message_raw->GetReflection()->GetRepeatedFieldRef<int>(*info.message_raw, info.field_raw);
314  QString test = util::int_container_to_qstring(orig_values.begin(), orig_values.end());
315  param_widget->widgetLineEdit()->setText(test);
316  }
317 
318  // We need to check this after resetting to the previous value in order to prevent displaying the warning twice.
319  if ((int)values.size() != field_size)
320  {
321  QMessageBox::warning(this, tr("Invalid Number of Elements"),
322  "Number of elements must be " + QString::number(field_size) + ", but " + QString::number(values.size()) +
323  (values.size() == 1 ? " is" : " are") + " provided.");
324  }
325  else if (dimension_range_exceeded)
326  {
327  QMessageBox::warning(this, tr("Invalid Number of Elements"),
328  "Dimension must be between " + util::int_to_qstring(info.min_size.second) + " and " +
329  util::int_to_qstring(info.max_size.second) + ".");
330  }
331  else if (out_of_range)
332  {
333  QMessageBox::warning(this, tr("Out of Range"),
334  "Parameters must be in the interval [" + util::int_to_qstring(min) + ", " + util::int_to_qstring(max) + "]");
335  }
336  };
337  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
338  break;
339  }
340  }
341 }
342 
343 void ParameterWidget::addParameterDouble(double value, const MessageParser::FieldInformation& info)
344 {
345  _has_parameters = true;
346 
347  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::TEXTEDIT;
348  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
349  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
350  double min = info.min_value.first ? info.min_value.second : CORBO_MIN_DBL;
351  double max = info.max_value.first ? info.max_value.second : CORBO_MAX_DBL;
352 
353  // add field description label if specified
354  if (info.print_description) addSubWidget(new QLabel(description));
355 
356  // check if we need to load a default value
357  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
358  {
359  // overwrite value
360  bool ok = false;
361  value = util::qstring_to_double(QString::fromStdString(info.default_value.second), &ok);
362  if (!ok)
363  {
364  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to double.");
365  value = 0;
366  }
367  info.message_raw->GetReflection()->SetDouble(info.message_raw, info.field_raw, value);
368  }
369 
370  switch (type)
371  {
372  case messages::GuiType::SLIDER:
373  {
374  LabelSliderWidget* param_widget = new LabelSliderWidget(label, value, 1);
375  param_widget->setToolTip(description);
376  // if min and max are not specified, set some reasonable default limits:
377  if (!info.min_value.first) min = 0;
378  if (!info.max_value.first) max = 100;
379  param_widget->setMinMax(min, max);
380  addSubWidget(param_widget);
381 
382  // set up modifer signal
383  // message and field pointers must be valid for the lifetime of this object!
384  auto fun_write_param = [this, info](double value) {
385  info.message_raw->GetReflection()->SetDouble(info.message_raw, info.field_raw, value);
386  if (info.update_signals) emit signalUpdateRequested();
387  };
388  connect(param_widget, &LabelSliderWidget::valueChanged, fun_write_param);
389  break;
390  }
391  case messages::GuiType::TEXTEDIT:
392  default:
393  {
394  LabelEditWidget* param_widget = new LabelEditWidget(label, util::double_to_qstring(value));
395  param_widget->setToolTip(description);
396  addSubWidget(param_widget);
397 
398  // set up modifer signal
399  // message and field pointers must be valid for the lifetime of this object!
400  auto fun_write_param = [info, param_widget, min, max, this]() {
401  // get values from line edit
402  bool ok;
403  double value = util::qstring_to_double(param_widget->widgetLineEdit()->text(), &ok);
404  bool out_of_range = false;
405 
406  // check limits
407  if (ok) out_of_range = !corbo::util::is_in_bounds(value, min, max);
408 
409  // set values if ok or revert if not
410  if (ok && !out_of_range)
411  info.message_raw->GetReflection()->SetDouble(info.message_raw, info.field_raw, value);
412  else
413  {
414  // revert to original value
415  param_widget->widgetLineEdit()->setText(
416  util::double_to_qstring(info.message_raw->GetReflection()->GetDouble(*info.message_raw, info.field_raw)));
417  }
418 
419  // We need to check this after resetting to the previous value in order to prevent displaying the warning twice.
420  if (out_of_range)
421  QMessageBox::warning(
422  this, tr("Out of Range"),
423  "Parameter must be in the interval [" + util::double_to_qstring(min) + ", " + util::double_to_qstring(max) + "]");
424 
425  if (info.update_signals) emit signalUpdateRequested();
426  };
427  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
428  break;
429  }
430  }
431 }
432 
433 void ParameterWidget::addParameterDoubleArray(const std::vector<double>& values, const MessageParser::FieldInformation& info)
434 {
435  _has_parameters = true;
436 
437  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::TEXTEDIT;
438  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
439  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
440  double min = info.min_value.first ? info.min_value.second : CORBO_MIN_DBL;
441  double max = info.max_value.first ? info.max_value.second : CORBO_MAX_DBL;
442 
443  std::vector<double> values_mod = values;
444 
445  // add field description label if specified
446  if (info.print_description) addSubWidget(new QLabel(description));
447 
448  // check if we need to load default values
449  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
450  {
451  // overwrite value
452  bool ok = false;
453  util::qstring_to_container(QString::fromStdString(info.default_value.second), values_mod, &ok);
454  if (ok)
455  {
456  info.message_raw->GetReflection()->GetMutableRepeatedFieldRef<double>(info.message_raw, info.field_raw).CopyFrom(values_mod);
457  }
458  else
459  {
460  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to double array.");
461  values_mod = values;
462  }
463  }
464 
465  switch (type)
466  {
467  case messages::GuiType::TEXTEDIT:
468  default:
469  {
470  LabelEditWidget* param_widget = new LabelEditWidget(label, util::double_container_to_qstring(values_mod.begin(), values_mod.end()));
471  param_widget->setToolTip(description);
472  addSubWidget(param_widget);
473 
474  // set up modifer signal
475  // message and field pointers must be valid for the lifetime of this object!
476  auto fun_write_param = [info, param_widget, min, max, this]() {
477  // get values from line edit
478  bool ok;
479  std::vector<double> values;
480  util::qstring_to_container(param_widget->widgetLineEdit()->text(), values, &ok);
481 
482  // check dimension
483  int field_size = -1;
484  bool dimension_range_exceeded = false;
485  if (info.dynamic_size)
486  {
487  field_size = (int)values.size();
488  if (info.min_size.first && field_size < info.min_size.second)
489  {
490  dimension_range_exceeded = true;
491  ok = false;
492  }
493  else if (info.max_size.first && field_size < info.max_size.second)
494  {
495  dimension_range_exceeded = true;
496  ok = false;
497  }
498  }
499  else
500  {
501  field_size = info.message_raw->GetReflection()->FieldSize(*info.message_raw, info.field_raw);
502  if (ok && (int)values.size() != field_size) ok = false;
503  }
504 
505  // check limits
506  bool out_of_range = false;
507  if (ok) out_of_range = !corbo::util::is_in_bounds_all(values.begin(), values.end(), min, max);
508 
509  // set values if ok or revert if not
510  if (ok && !out_of_range)
511  {
512  if (info.dynamic_size)
513  {
514  info.message_raw->GetReflection()
515  ->GetMutableRepeatedFieldRef<double>(info.message_raw, info.field_raw)
516  .CopyFrom(values); // this method resizes the field to the correct size
517  }
518  else
519  {
520  for (int idx = 0; idx < (int)values.size(); ++idx)
521  {
522  info.message_raw->GetReflection()->SetRepeatedDouble(info.message_raw, info.field_raw, idx, values[idx]);
523  }
524  }
525  }
526  else
527  {
528  // revert to original value
529  auto orig_values = info.message_raw->GetReflection()->GetRepeatedFieldRef<double>(*info.message_raw, info.field_raw);
530  QString text = util::double_container_to_qstring(orig_values.begin(), orig_values.end());
531  param_widget->widgetLineEdit()->setText(text);
532  }
533 
534  // We need to check this after resetting to the previous value in order to prevent displaying the warning twice.
535  if ((int)values.size() != field_size)
536  {
537  QMessageBox::warning(this, tr("Invalid Number of Elements"),
538  "Number of elements must be " + QString::number(field_size) + ", but " + QString::number(values.size()) +
539  (values.size() == 1 ? " is" : " are") + " provided.");
540  }
541  else if (dimension_range_exceeded)
542  {
543  QMessageBox::warning(this, tr("Invalid Number of Elements"),
544  "Dimension must be between " + util::int_to_qstring(info.min_size.second) + " and " +
545  util::int_to_qstring(info.max_size.second) + ".");
546  }
547  else if (out_of_range)
548  {
549  QMessageBox::warning(
550  this, tr("Out of Range"),
551  "Parameters must be in the interval [" + util::double_to_qstring(min) + ", " + util::double_to_qstring(max) + "]");
552  }
553  if (info.update_signals) emit signalUpdateRequested();
554  };
555  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
556  break;
557  }
558  }
559 }
560 
561 void ParameterWidget::addParameterBool(bool value, const MessageParser::FieldInformation& info)
562 {
563  _has_parameters = true;
564 
565  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::CHECKBOX;
566  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
567  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
568 
569  // add field description label if specified
570  if (info.print_description) addSubWidget(new QLabel(description));
571 
572  // check if we need to load a default value
573  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
574  {
575  // overwrite value
576  bool ok = false;
577  value = util::qstring_to_bool(QString::fromStdString(info.default_value.second), &ok);
578  if (!ok)
579  {
580  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to bool.");
581  value = 0;
582  }
583  info.message_raw->GetReflection()->SetBool(info.message_raw, info.field_raw, value);
584  }
585 
586  switch (type)
587  {
588  case messages::GuiType::CHECKBOX:
589  default:
590  {
591  QCheckBox* param_widget = new QCheckBox(label);
592  param_widget->setToolTip(description);
593  param_widget->setChecked(value);
594  addSubWidget(param_widget);
595 
596  // set up modifer signal
597  // message and field pointers must be valid for the lifetime of this object!
598  auto fun_write_param = [info, this](bool toggled) {
599  info.message_raw->GetReflection()->SetBool(info.message_raw, info.field_raw, toggled);
600  if (info.update_signals) emit signalUpdateRequested();
601  };
602  connect(param_widget, &QCheckBox::toggled, fun_write_param);
603 
604  break;
605  }
606  }
607 }
608 
609 void ParameterWidget::addParameterBoolArray(const std::vector<bool>& values, const MessageParser::FieldInformation& info)
610 {
611  _has_parameters = true;
612 
613  messages::GuiType type = info.gui_type.first ? info.gui_type.second : messages::GuiType::CHECKBOX;
614  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
615  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
616 
617  std::vector<bool> values_mod = values;
618 
619  // add field description label if specified
620  if (info.print_description) addSubWidget(new QLabel(description));
621 
622  // check if we need to load default values
623  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
624  {
625  // overwrite value
626  bool ok = false;
627  util::qstring_to_container(QString::fromStdString(info.default_value.second), values_mod, &ok);
628  if (ok)
629  {
630  info.message_raw->GetReflection()->GetMutableRepeatedFieldRef<bool>(info.message_raw, info.field_raw).CopyFrom(values_mod);
631  }
632  else
633  {
634  PRINT_ERROR_NAMED("Cannot convert default parameter: " << info.default_value.second << " to bool array.");
635  values_mod = values;
636  }
637  }
638 
639  // check if we have multiple labels separated by comma
640  QStringList label_list = label.split(',');
641 
642  QString group_name = label_list.empty() ? label : label_list.first();
643 
644  if (type == messages::GuiType::TEXTEDIT)
645  {
646  // text edit version
647  LabelEditWidget* param_widget = new LabelEditWidget(label, util::bool_container_to_qstring(values_mod.begin(), values_mod.end()));
648  param_widget->setToolTip(description);
649  addSubWidget(param_widget);
650 
651  // set up modifer signal
652  // message and field pointers must be valid for the lifetime of this object!
653  auto fun_write_param = [info, param_widget, this]() {
654  // get values from line edit
655  bool ok;
656  std::vector<bool> values;
657  util::qstring_to_container(param_widget->widgetLineEdit()->text(), values, &ok);
658 
659  // check dimension
660  int field_size = -1;
661  bool dimension_range_exceeded = false;
662  if (info.dynamic_size)
663  {
664  field_size = (int)values.size();
665  if (info.min_size.first && field_size < info.min_size.second)
666  {
667  dimension_range_exceeded = true;
668  ok = false;
669  }
670  else if (info.max_size.first && field_size < info.max_size.second)
671  {
672  dimension_range_exceeded = true;
673  ok = false;
674  }
675  }
676  else
677  {
678  field_size = info.message_raw->GetReflection()->FieldSize(*info.message_raw, info.field_raw);
679  if (ok && (int)values.size() != field_size) ok = false;
680  }
681 
682  // set values if ok or revert if not
683  if (ok)
684  {
685  if (info.dynamic_size)
686  {
687  info.message_raw->GetReflection()
688  ->GetMutableRepeatedFieldRef<bool>(info.message_raw, info.field_raw)
689  .CopyFrom(values); // this method resizes the field to the correct size
690  }
691  else
692  {
693  for (int idx = 0; idx < (int)values.size(); ++idx)
694  {
695  info.message_raw->GetReflection()->SetRepeatedBool(info.message_raw, info.field_raw, idx, values[idx]);
696  }
697  }
698  }
699  else
700  {
701  // revert to original value
702  auto orig_values = info.message_raw->GetReflection()->GetRepeatedFieldRef<bool>(*info.message_raw, info.field_raw);
703  QString text = util::bool_container_to_qstring(orig_values.begin(), orig_values.end());
704  param_widget->widgetLineEdit()->setText(text);
705  }
706 
707  // We need to check this after resetting to the previous value in order to prevent displaying the warning twice.
708  if ((int)values.size() != field_size)
709  {
710  QMessageBox::warning(this, tr("Invalid Number of Elements"),
711  "Number of elements must be " + QString::number(field_size) + ", but " + QString::number(values.size()) +
712  (values.size() == 1 ? " is" : " are") + " provided.");
713  }
714  else if (dimension_range_exceeded)
715  {
716  QMessageBox::warning(this, tr("Invalid Number of Elements"),
717  "Dimension must be between " + util::int_to_qstring(info.min_size.second) + " and " +
718  util::int_to_qstring(info.max_size.second) + ".");
719  }
720  if (info.update_signals) emit signalUpdateRequested();
721  };
722  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
723  }
724  else
725  {
726  // horizontal button group
727  bool exclusive = type != messages::GuiType::CHECKBOX; // only if we have a checkbox, multiple checked booleans are allowed
728 
729  HoriontalButtonGroup* param_widget = new HoriontalButtonGroup(group_name, exclusive);
730  param_widget->setToolTip(description);
731 
732  for (int idx = 0; idx < (int)values_mod.size(); ++idx)
733  {
734  QString field_label = (idx + 1 < label_list.size()) ? label_list[idx + 1] : ""; // first element is the group name!
735  QAbstractButton* btn = param_widget->addButton(values_mod[idx], field_label);
736 
737  // set up modifer signal
738  // message and field pointers must be valid for the lifetime of this object!
739  auto fun_write_param = [info, idx, this](bool toggled) {
740  info.message_raw->GetReflection()->SetRepeatedBool(info.message_raw, info.field_raw, idx, toggled);
741  if (info.update_signals) emit signalUpdateRequested();
742  };
743  connect(btn, &QAbstractButton::toggled, fun_write_param);
744  }
745  addSubWidget(param_widget);
746  }
747 }
748 
749 void ParameterWidget::addParameterEnum(const std::string& value, const MessageParser::FieldInformation& info)
750 {
751  _has_parameters = true;
752 
753  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
754  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
755 
756  // add field description label if specified
757  if (info.print_description) addSubWidget(new QLabel(description));
758 
759  LabelComboBoxWidget* param_widget = new LabelComboBoxWidget(label);
760  param_widget->setToolTip(description);
761  addSubWidget(param_widget);
762 
763  // search for all enum types
764  const google::protobuf::EnumDescriptor* enum_descr = info.field_raw->enum_type();
765  if (!enum_descr)
766  {
767  PRINT_WARNING("ParameterWidget::addParameterEnum(): provided parameter '" << info.field_name << "' is no enumerator!");
768  return;
769  }
770  for (int idx = 0; idx < enum_descr->value_count(); ++idx)
771  {
772  param_widget->widgetComboBox()->addItem(QString::fromStdString(enum_descr->value(idx)->name()));
773  }
774  // select current value
775  param_widget->widgetComboBox()->setCurrentText(QString::fromStdString(value));
776 
777  // set up modifer signal
778  // message and field pointers must be valid for the lifetime of this object!
779  auto fun_write_param = [info, this](int idx) {
780  info.message_raw->GetReflection()->SetEnumValue(info.message_raw, info.field_raw, idx);
781  if (info.update_signals) emit signalUpdateRequested();
782  };
783  connect(param_widget->widgetComboBox(), static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), fun_write_param);
784 }
785 
786 void ParameterWidget::addParameterString(std::string value, const MessageParser::FieldInformation& info)
787 {
788  _has_parameters = true;
789 
790  QString label = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
791  QString description = info.description.first ? QString::fromStdString(info.description.second) : "";
792 
793  // add field description label if specified
794  if (info.print_description) addSubWidget(new QLabel(description));
795 
796  // check if we need to load a default value
797  if (info.default_value.first && !_groups.empty() && std::get<2>(_groups.back()))
798  {
799  // overwrite value
800  value = info.default_value.second;
801 
802  info.message_raw->GetReflection()->SetString(info.message_raw, info.field_raw, value);
803  }
804 
805  LabelEditWidget* param_widget = new LabelEditWidget(label, QString::fromStdString(value));
806  param_widget->setToolTip(description);
807  addSubWidget(param_widget);
808 
809  // set up modifer signal
810  // message and field pointers must be valid for the lifetime of this object!
811  auto fun_write_param = [param_widget, info, this]() {
812  std::string value = param_widget->widgetLineEdit()->text().toUtf8().constData();
813  info.message_raw->GetReflection()->SetString(info.message_raw, info.field_raw, value);
814  if (info.update_signals) emit signalUpdateRequested();
815  };
816  connect(param_widget->widgetLineEdit(), &QLineEdit::editingFinished, fun_write_param);
817 }
818 
819 void ParameterWidget::addInfoText(const std::string& text, const MessageParser::FieldInformation& info)
820 {
821  _has_parameters = true;
822 
823  QString text_q = QString::fromStdString(text);
824 
825  if (info.default_value.first && text.empty()) text_q = QString::fromStdString(info.default_value.second);
826 
827  addSubWidget(new QLabel(text_q));
828 }
829 
830 void ParameterWidget::startGroup(const MessageParser::FieldInformation& info)
831 {
832  // start group
833  QString name = info.label.first ? QString::fromStdString(info.label.second) : QString::fromStdString(info.field_name);
834  CollapsableGroupBox* group = new CollapsableGroupBox(name);
835  group->setCollapsed(info.collapsed);
836 
837  QVBoxLayout* layout = new QVBoxLayout;
838  layout->setContentsMargins(5, 15, 5, 0);
839  layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
840  _groups.push(std::make_tuple(group, layout, info.new_message));
841 }
842 void ParameterWidget::endGroup()
843 {
844  if (_groups.empty()) return;
845 
846  std::tuple<CollapsableGroupBox*, QLayout*, bool> top = _groups.pop();
847 
848  // apply layout to group
849  std::get<0>(top)->groupBox()->setLayout(std::get<1>(top));
850  std::get<0>(top)->setCollapsed(false);
851 
852  // append to parent group or widget
853  if (_groups.empty())
854  _layout->addWidget(std::get<0>(top));
855  else
856  std::get<1>(_groups.top())->addWidget(std::get<0>(top));
857 }
858 
859 void ParameterWidget::addSubWidget(QWidget* widget)
860 {
861  // set font size for parameters
862  // QFont font = widget->font();
863  // font.setPointSize(11);
864  // widget->setFont(font);
865  widget->setStyleSheet("font-size: 11px"); // hereby, we also set the font size of child widgets!
866 
867  if (_groups.empty())
868  {
869  _layout->addWidget(widget);
870  // TODO(roesmann) this command is called quite often with the same widget!!!
871  // _layout->addWidget(new QLabel("test", this));
872  }
873  else if (std::get<1>(_groups.top()))
874  {
875  std::get<1>(_groups.top())->addWidget(widget);
876  // _layout->addWidget(new QLabel("test2", this));
877  }
878 }
879 
880 void ParameterWidget::generateFromMessage(std::shared_ptr<google::protobuf::Message> message)
881 {
882  clearElements();
883  _param_message = message;
884  generateElements();
885 }
886 
887 void ParameterWidget::generateFromAllocatedField(google::protobuf::Message* message, const google::protobuf::FieldDescriptor* field)
888 {
889  clearElements();
890  parse(message, field);
891 }
892 
893 void ParameterWidget::generateElements()
894 {
895  if (!_param_message) return;
896  parse(_param_message.get());
897 }
898 
899 void ParameterWidget::parse(google::protobuf::Message* message, const google::protobuf::FieldDescriptor* field)
900 {
901  MessageParser parser;
902 
903  using std::placeholders::_1;
904  using std::placeholders::_2;
905 
906  parser.setCallbackValueInt32(std::bind(&ParameterWidget::addParameterInt32, this, _1, _2));
907  parser.setCallbackValueInt32Array(std::bind(&ParameterWidget::addParameterInt32Array, this, _1, _2));
908  parser.setCallbackValueDouble(std::bind(&ParameterWidget::addParameterDouble, this, _1, _2));
909  parser.setCallbackValueDoubleArray(std::bind(&ParameterWidget::addParameterDoubleArray, this, _1, _2));
910  parser.setCallbackValueBool(std::bind(&ParameterWidget::addParameterBool, this, _1, _2));
911  parser.setCallbackValueBoolArray(std::bind(&ParameterWidget::addParameterBoolArray, this, _1, _2));
912  parser.setCallbackValueEnum(std::bind(&ParameterWidget::addParameterEnum, this, _1, _2));
913  parser.setCallbackValueString(std::bind(&ParameterWidget::addParameterString, this, _1, _2));
914  parser.setCallbackValueStringInfo(std::bind(&ParameterWidget::addInfoText, this, _1, _2));
915 
916  parser.setCallbackMessageEvent([this, field](corbo::MessageParser::MessageEvent ev, const corbo::MessageParser::FieldInformation& info) {
917  if (ev == corbo::MessageParser::MessageEvent::MessageStart)
918  startGroup(info);
919  else if (ev == corbo::MessageParser::MessageEvent::MessageEnd)
920  endGroup();
921  else if (ev == corbo::MessageParser::MessageEvent::OneOf)
922  {
923  if (info.field_raw != field) addOneOfField(info);
924  }
925  });
926 
927  std::list<std::string> nested_field_name; // required for storing nested field names while parsing
928 
929  // prepend any previous parent field names
930  nested_field_name = _nested_parent_fields;
931  if (!nested_field_name.empty()) nested_field_name.pop_back(); // erase last to avoid duplicate
932 
933  if (field)
934  {
935  parser.parseField(message, field, false, true, false,
936  &nested_field_name); // options: parse first oneof even if not active since we want to force parse the provided field
937  }
938  else
939  parser.parse(message, false, true, &nested_field_name);
940 }
941 
942 bool ParameterWidget::hasParameters() const
943 {
944  if (_has_parameters) return true;
945 
946  // also check child widgets (subgroups)
947  for (auto widget : this->findChildren<ParameterWidget*>(QString(), Qt::FindChildrenRecursively))
948  {
949  if (static_cast<ParameterWidget*>(widget)->hasParameters()) return true;
950  }
951  return false;
952 }
953 
954 void ParameterWidget::clearElements()
955 {
956  for (auto widget : this->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly)) delete widget;
957 
958  _groups.clear();
959  _oneof_widgets.clear();
960 }
961 
962 } // namespace gui
963 } // namespace corbo
PRINT_WARNING
#define PRINT_WARNING(msg)
Print msg-stream.
Definition: console.h:145
PRINT_ERROR_NAMED
#define PRINT_ERROR_NAMED(msg)
Definition: console.h:260
corbo::gui::util::double_to_qstring
QString double_to_qstring(double value)
Definition: gui/include/corbo-gui/utilities.h:175
corbo
Definition: communication/include/corbo-communication/utilities.h:37
label_combobox_widget.h
corbo::CORBO_MAX_INT
constexpr const int CORBO_MAX_INT
Maximum integer value.
Definition: core/include/corbo-core/types.h:70
corbo::gui::LabelEditWidget
Definition: label_edit_widget.h:78
label_slider_widget.h
corbo::gui::ParameterWidget
Definition: parameter_widget.h:92
corbo::gui::util::double_container_to_qstring
QString double_container_to_qstring(Iterator first, Iterator last)
Definition: gui/include/corbo-gui/utilities.h:198
console.h
corbo::gui::util::int_to_qstring
QString int_to_qstring(int value)
Definition: gui/include/corbo-gui/utilities.h:190
utilities.h
info
else if n * info
Definition: cholesky.cpp:18
corbo::gui::LabelEditWidget::widgetLineEdit
QLineEdit * widgetLineEdit()
Definition: label_edit_widget.h:114
utility::tuple::make_tuple
Tuple< Args... > make_tuple(Args... args)
Creates a tuple object, deducing the target type from the types of arguments.
Definition: TensorSyclTuple.h:133
corbo::gui::CollapsableGroupBox
Definition: collapsable_groupbox.h:82
corbo::gui::CollapsableGroupBox::setCollapsed
void setCollapsed(bool collapsed)
Definition: collapsable_groupbox.cpp:134
corbo::gui::util::qstring_to_bool
bool qstring_to_bool(const QString &string, bool *ok=nullptr)
Definition: gui/include/corbo-gui/utilities.h:121
corbo::CORBO_MIN_INT
constexpr const int CORBO_MIN_INT
Minimum (negative) integer value.
Definition: core/include/corbo-core/types.h:72
corbo::util::is_in_bounds
constexpr const bool is_in_bounds(const T &v, const T &lo, const T &hi)
Check if a value is inside the interval [lo, hi].
Definition: core/include/corbo-core/utilities.h:108
corbo::gui::util::int_container_to_qstring
QString int_container_to_qstring(Iterator first, Iterator last)
Definition: gui/include/corbo-gui/utilities.h:215
int
return int(ret)+1
one_of_param_widget.h
collapsable_groupbox.h
parameter_widget.h
corbo::util::is_in_bounds_all
const bool is_in_bounds_all(Iterator first, Iterator last, const T &lo, const T &hi)
Check if all components of a container are inside the interval [lo, hi].
Definition: core/include/corbo-core/utilities.h:125
corbo::gui::ParameterWidget::ParameterWidget
ParameterWidget(ParameterCache *cache=nullptr, QWidget *parent=nullptr)
Definition: parameter_widget.cpp:96
label_edit_widget.h
corbo::gui::util::bool_container_to_qstring
QString bool_container_to_qstring(Iterator first, Iterator last)
Definition: gui/include/corbo-gui/utilities.h:232
corbo::gui::util::qstring_to_int
int qstring_to_int(const QString &string, bool *ok=nullptr)
Definition: gui/include/corbo-gui/utilities.h:113
min
#define min(a, b)
Definition: datatypes.h:19
corbo::CORBO_MIN_DBL
constexpr const double CORBO_MIN_DBL
Minimum (negative) double value.
Definition: core/include/corbo-core/types.h:68
corbo::CORBO_MAX_DBL
constexpr const double CORBO_MAX_DBL
Maximum double value.
Definition: core/include/corbo-core/types.h:66
corbo::gui::util::qstring_to_double
double qstring_to_double(const QString &string, bool *ok=nullptr)
Definition: gui/include/corbo-gui/utilities.h:96
corbo::gui::util::qstring_to_container
void qstring_to_container(const QString &string, std::vector< double > &values, bool *ok=nullptr)
Definition: gui/include/corbo-gui/utilities.h:133
corbo::ok
bool ok()
global method to check whether to proceed or cancel the current action
Definition: global.cpp:54
max
#define max(a, b)
Definition: datatypes.h:20
relicense.text
text
Definition: relicense.py:59
corbo::gui::OneOfParamWidget
Definition: one_of_param_widget.h:89
utilities.h
horizontal_button_group.h
message_parser.h


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