dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
outputchannelform.cpp
Go to the documentation of this file.
1 
13 /*
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful, but
20  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  * for more details.
23  *
24  * You should have received a copy of the GNU General Public License along
25  * with this program; if not, see <http://www.gnu.org/licenses/>
26  */
27 
28 #include <cmath>
29 #include <limits>
30 
31 #include "outputchannelform.h"
32 #include "configoutputwidget.h"
33 #include "actuatorsettings.h"
34 
35 OutputChannelForm::OutputChannelForm(const int index, QWidget *parent, const bool showLegend)
36  : ConfigTaskWidget(parent)
37  , ui()
38  , m_index(index)
39  , m_inChannelTest(false)
40  , limitMin(1000)
41  , limitMax(2000)
42  , minMaxFixed(false)
43 {
44  ui.setupUi(this);
45 
46  ActuatorSettings *actuatorSettings = ActuatorSettings::GetInstance(getObjectManager());
47  Q_ASSERT(actuatorSettings);
48 
49  if (!showLegend) {
50  // Remove legend
51  QGridLayout *grid_layout = dynamic_cast<QGridLayout *>(layout());
52  Q_ASSERT(grid_layout);
53  for (int col = 0; col < grid_layout->columnCount();
54  col++) { // remove every item in first row
55  QLayoutItem *item = grid_layout->itemAtPosition(0, col);
56  if (!item)
57  continue;
58  // get widget from layout item
59  QWidget *legend_widget = item->widget();
60  if (!legend_widget)
61  continue;
62  // delete widget
63  grid_layout->removeWidget(legend_widget);
64  delete legend_widget;
65  }
66  }
67 
68  // The convention is Channel 1 to Channel 10.
69  ui.actuatorNumber->setText(QString("%1:").arg(m_index + 1));
70 
71  // Register for ActuatorSettings changes:
72  connect(ui.actuatorMin, &QAbstractSpinBox::editingFinished, this,
73  &OutputChannelForm::setChannelRange);
74  connect(ui.actuatorMax, &QAbstractSpinBox::editingFinished, this,
75  &OutputChannelForm::setChannelRange);
76  connect(ui.pb_reverseActuator, &QAbstractButton::clicked, this,
77  &OutputChannelForm::reverseChannel);
78 
79  // Connect the channel out sliders to our signal in order to send updates in test mode
80  connect(ui.actuatorNeutral, &QAbstractSlider::valueChanged, this,
81  &OutputChannelForm::sendChannelTest);
82 
83  // Connect UI elements to dirty/clean (i.e. changed/unchanged) signal/slot
84  connect(ui.actuatorMin, QOverload<int>::of(&QSpinBox::valueChanged), this,
85  &OutputChannelForm::notifyFormChanged);
86  connect(ui.actuatorMax, QOverload<int>::of(&QSpinBox::valueChanged), this,
87  &OutputChannelForm::notifyFormChanged);
88  connect(ui.actuatorNeutral, &QAbstractSlider::sliderReleased, this,
89  &OutputChannelForm::notifyFormChanged);
90  ui.actuatorLink->setChecked(false);
91  connect(ui.actuatorLink, &QAbstractButton::toggled, this, &OutputChannelForm::linkToggled);
92 
94 }
95 
97 {
98  // Do nothing
99 }
100 
105 {
106  if (m_inChannelTest == state)
107  return;
108  m_inChannelTest = state;
109 
110  if (m_inChannelTest) {
111  // Prevent users from changing the minimum & maximum ranges while
112  // moving the sliders. Thanks Ivan for the tip :)
113  ui.actuatorMin->setEnabled(false);
114  ui.actuatorMax->setEnabled(false);
115  ui.pb_reverseActuator->setEnabled(false);
116  } else {
117  ui.actuatorMin->setEnabled(!minMaxFixed);
118  ui.actuatorMax->setEnabled(!minMaxFixed);
119  ui.pb_reverseActuator->setEnabled(!minMaxFixed);
120  }
121 }
122 
126 void OutputChannelForm::linkToggled(bool state)
127 {
128  Q_UNUSED(state)
129 
130  if (!m_inChannelTest)
131  return; // we are not in Test Output mode
132 
133  // find the minimum slider value for the linked ones
134  if (!parent())
135  return;
136  int min = 10000;
137  int linked_count = 0;
138  QList<OutputChannelForm *> outputChannelForms = parent()->findChildren<OutputChannelForm *>();
139  // set the linked channels of the parent widget to the same value
140  foreach (OutputChannelForm *outputChannelForm, outputChannelForms) {
141  if (!outputChannelForm->ui.actuatorLink->checkState())
142  continue;
143  if (this == outputChannelForm)
144  continue;
145  int value = outputChannelForm->ui.actuatorNeutral->value();
146  if (min > value)
147  min = value;
148  linked_count++;
149  }
150 
151  if (linked_count <= 0)
152  return; // no linked channels
153 
154  // set the linked channels to the same value
155  foreach (OutputChannelForm *outputChannelForm, outputChannelForms) {
156  if (!outputChannelForm->ui.actuatorLink->checkState())
157  continue;
158  outputChannelForm->ui.actuatorNeutral->setValue(min);
159  }
160 }
161 
165 void OutputChannelForm::setMax(int maximum)
166 {
167  setMinmax(ui.actuatorMin->value(), maximum);
168 }
169 
173 void OutputChannelForm::setMin(int minimum)
174 {
175  setMinmax(minimum, ui.actuatorMax->value());
176 }
177 
181 void OutputChannelForm::setMinmax(int minimum, int maximum)
182 {
183  inhibitRangeChanges = true;
184 
185  if (minMaxFixed) {
186  ui.actuatorMin->setValue(limitMin);
187  ui.actuatorMax->setValue(limitMax);
188  } else {
189  ui.actuatorMin->setValue(minimum);
190  ui.actuatorMax->setValue(maximum);
191  }
192 
193  inhibitRangeChanges = false;
194 
195  setChannelRange();
196 }
197 
202 {
203  ui.actuatorNeutral->setValue(value);
204 }
205 
206 void OutputChannelForm::alignFields()
207 {
208  int actuatorWidth = 0;
209 
210  foreach (OutputChannelForm *form, parent()->findChildren<OutputChannelForm *>()) {
211  actuatorWidth = fmax(actuatorWidth, form->ui.actuatorMin->minimumSize().width());
212  actuatorWidth = fmax(actuatorWidth, form->ui.actuatorMin->sizeHint().width());
213  actuatorWidth = fmax(actuatorWidth, form->ui.actuatorMax->minimumSize().width());
214  actuatorWidth = fmax(actuatorWidth, form->ui.actuatorMax->sizeHint().width());
215  }
216 
217  foreach (OutputChannelForm *form, parent()->findChildren<OutputChannelForm *>()) {
218  form->ui.actuatorMin->setMinimumSize(actuatorWidth, 0);
219  form->ui.actuatorMax->setMinimumSize(actuatorWidth, 0);
220  }
221 }
222 
226 void OutputChannelForm::setAssignment(const QString &assignment)
227 {
228  ui.actuatorName->setText(assignment);
229  QFontMetrics metrics(ui.actuatorName->font());
230  int width = metrics.width(assignment) + 1;
231  foreach (OutputChannelForm *form, parent()->findChildren<OutputChannelForm *>()) {
232  if (form == this)
233  continue;
234  if (form->ui.actuatorName->minimumSize().width() < width)
235  form->ui.actuatorName->setMinimumSize(width, 0);
236  else
237  width = form->ui.actuatorName->minimumSize().width();
238  }
239  ui.actuatorName->setMinimumSize(width, 0);
240 }
241 
249 void OutputChannelForm::setChannelRange()
250 {
251  if (inhibitRangeChanges) {
252  return;
253  }
254 
255  if (minMaxFixed) { // special case Dshot
256  ui.actuatorNeutral->setRange(limitMin, limitMax);
257  ui.actuatorNeutral->setEnabled(true);
258  } else if (ui.actuatorMin->value() < ui.actuatorMax->value()) {
259  ui.actuatorNeutral->setRange(ui.actuatorMin->value(), ui.actuatorMax->value());
260  ui.actuatorNeutral->setEnabled(true);
261  } else if (ui.actuatorMin->value() > ui.actuatorMax->value()) {
262  ui.actuatorNeutral->setRange(ui.actuatorMax->value(), ui.actuatorMin->value());
263  ui.actuatorNeutral->setEnabled(true);
264  } else {
265  // when the min and max is equal, disable this slider to prevent crash
266  // from Qt bug: https://bugreports.qt.io/browse/QTBUG-43398
267  ui.actuatorNeutral->setRange(ui.actuatorMin->value() - 1, ui.actuatorMin->value() + 1);
268  ui.actuatorNeutral->setEnabled(false);
269  setNeutral(ui.actuatorMin->value());
270  }
271 
272  // Force a full slider update
273  updateSlider();
274 }
275 
279 void OutputChannelForm::reverseChannel()
280 {
281  // can't reverse Dshot at this time
282  if (!ui.actuatorMin->isEnabled() || !ui.actuatorMax->isEnabled())
283  return;
284 
285  // Swap the min & max values
286  setMinmax(ui.actuatorMax->value(), ui.actuatorMin->value());
287 
288  setChannelRange();
289 }
290 
297 void OutputChannelForm::updateSlider()
298 {
299  // Invert the slider
300  if (ui.actuatorMin->value() > ui.actuatorMax->value()) {
301  ui.actuatorNeutral->setInvertedAppearance(true);
302 
303  // Set the QSlider groove colors so that the fill is on the side of the minimum value
304  ui.actuatorNeutral->setProperty("state", "inverted");
305  } else {
306  ui.actuatorNeutral->setInvertedAppearance(false);
307 
308  // Set the QSlider groove colors so that the fill is on the side of the minimum value
309  ui.actuatorNeutral->setProperty("state", "normal");
310  }
311 
312  // Force refresh of style sheet.
313  ui.actuatorNeutral->setStyle(QApplication::style());
314 }
315 
320 void OutputChannelForm::sendChannelTest(int value)
321 {
322  int in_value = value;
323 
324  QSlider *ob = dynamic_cast<QSlider *>(QObject::sender());
325  if (!ob)
326  return;
327 
328  if (ui.actuatorLink->checkState() && parent()) { // the channel is linked to other channels
329  QList<OutputChannelForm *> outputChannelForms =
330  parent()->findChildren<OutputChannelForm *>();
331  // set the linked channels of the parent widget to the same value
332  foreach (OutputChannelForm *outputChannelForm, outputChannelForms) {
333  if (this == outputChannelForm)
334  continue;
335  if (!outputChannelForm->ui.actuatorLink->checkState())
336  continue;
337 
338  int val = in_value;
339  if (val < outputChannelForm->ui.actuatorNeutral->minimum())
340  val = outputChannelForm->ui.actuatorNeutral->minimum();
341  if (val > outputChannelForm->ui.actuatorNeutral->maximum())
342  val = outputChannelForm->ui.actuatorNeutral->maximum();
343 
344  if (outputChannelForm->ui.actuatorNeutral->value() == val)
345  continue;
346 
347  outputChannelForm->ui.actuatorNeutral->setValue(val);
348  }
349  }
350 
351  if (!m_inChannelTest)
352  return; // we are not in Test Output mode
353 
354  emit channelChanged(index(), value);
355 }
356 
360 void OutputChannelForm::notifyFormChanged()
361 {
362  // If we are not in Test Output mode, set form as dirty
363  if (!m_inChannelTest) {
364  emit formChanged();
365  }
366 
367  setChannelRange();
368 }
369 
370 void OutputChannelForm::updateChannelLimits(int minPulse, int maxPulse, bool digitalProtocol)
371 {
372  limitMin = minPulse;
373  limitMax = maxPulse;
374  minMaxFixed = digitalProtocol;
375 
376  if (digitalProtocol) {
377  // digital protocol like Dshot, fix min to 0, max to maxPulse, and neutral range goes
378  // between minPulse and maxPulse
379  ui.actuatorMin->setRange(0, 0);
380  ui.actuatorMax->setRange(limitMax, limitMax);
381  } else {
382  ui.actuatorMin->setRange(limitMin, limitMax);
383  ui.actuatorMax->setRange(limitMin, limitMax);
384  }
385  // these are fixed with digital protocols like Dshot
386  ui.actuatorMin->setEnabled(!minMaxFixed && !m_inChannelTest);
387  ui.actuatorMax->setEnabled(!minMaxFixed && !m_inChannelTest);
388  ui.pb_reverseActuator->setEnabled(!minMaxFixed && !m_inChannelTest);
389 
390  setChannelRange();
391  alignFields();
392 }
void channelChanged(int index, int value)
void setMinmax(int minimum, int maximum)
void setAssignment(const QString &assignment)
void setMin(int minimum)
void enableChannelTest(bool state)
void updateChannelLimits(int minPulse, int maxPulse, bool digitalProtocol=false)
void setMax(int maximum)
OutputChannelForm(const int index, QWidget *parent=NULL, const bool showLegend=false)
Definition: icore.h:39
void setNeutral(int value)
UAVObjectManager * getObjectManager()
ConfigTaskWidget::getObjectManager Utility function to get a pointer to the object manager...