1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2 *
3 * @file PointParameter.cpp
4 *
5 * Copyright 2017 Sebastien Fourey
6 *
7 * This file is part of G'MIC-Qt, a generic plug-in for raster graphics
8 * editors, offering hundreds of filters thanks to the underlying G'MIC
9 * image processing framework.
10 *
11 * gmic_qt is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * gmic_qt is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with gmic_qt. If not, see <http://www.gnu.org/licenses/>.
23 *
24 */
25 #include "FilterParameters/PointParameter.h"
26 #include <QApplication>
27 #include <QColorDialog>
28 #include <QDebug>
29 #include <QFont>
30 #include <QFontMetrics>
31 #include <QGridLayout>
32 #include <QHBoxLayout>
33 #include <QLabel>
34 #include <QPainter>
35 #include <QPushButton>
36 #include <QSpacerItem>
37 #include <QWidget>
38 #include <cmath>
39 #include <cstdio>
40 #include <cstdlib>
41 #include "Common.h"
42 #include "DialogSettings.h"
43 #include "FilterTextTranslator.h"
44 #include "HtmlTranslator.h"
45 #include "KeypointList.h"
46
47 namespace GmicQt
48 {
49
50 int PointParameter::_defaultColorNextIndex = 0;
51 unsigned long PointParameter::_randomSeed = 12345;
52
PointParameter(QObject * parent)53 PointParameter::PointParameter(QObject * parent) : AbstractParameter(parent), _defaultPosition(0, 0), _position(0, 0), _removable(false), _burst(false)
54 {
55 _label = nullptr;
56 _colorLabel = nullptr;
57 _labelX = nullptr;
58 _labelY = nullptr;
59 _spinBoxX = nullptr;
60 _spinBoxY = nullptr;
61 _removeButton = nullptr;
62 _rowCell = nullptr;
63 _notificationEnabled = true;
64 _connected = false;
65 _defaultRemovedStatus = false;
66 _radius = KeypointList::Keypoint::DefaultRadius;
67 _keepOpacityWhenSelected = false;
68 _removed = false;
69 setRemoved(false);
70 }
71
~PointParameter()72 PointParameter::~PointParameter()
73 {
74 delete _label;
75 delete _rowCell;
76 }
77
size() const78 int PointParameter::size() const
79 {
80 return 2;
81 }
82
addTo(QWidget * widget,int row)83 bool PointParameter::addTo(QWidget * widget, int row)
84 {
85 _grid = dynamic_cast<QGridLayout *>(widget->layout());
86 Q_ASSERT_X(_grid, __PRETTY_FUNCTION__, "No grid layout in widget");
87 _row = row;
88 delete _label;
89 delete _rowCell;
90
91 _rowCell = new QWidget(widget);
92 auto hbox = new QHBoxLayout(_rowCell);
93 hbox->setMargin(0);
94 hbox->addWidget(_colorLabel = new QLabel(_rowCell));
95
96 QFontMetrics fm(widget->font());
97 QRect r = fm.boundingRect("CLR");
98 _colorLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
99 QPixmap pixmap(r.width(), r.height());
100 QPainter painter(&pixmap);
101 painter.setBrush(QColor(_color.red(), _color.green(), _color.blue()));
102 painter.setPen(Qt::black);
103 painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
104 _colorLabel->setPixmap(pixmap);
105
106 hbox->addWidget(_labelX = new QLabel("X", _rowCell));
107 hbox->addWidget(_spinBoxX = new QDoubleSpinBox(_rowCell));
108 hbox->addWidget(_labelY = new QLabel("Y", _rowCell));
109 hbox->addWidget(_spinBoxY = new QDoubleSpinBox(_rowCell));
110 if (_removable) {
111 hbox->addWidget(_removeButton = new QToolButton(_rowCell));
112 _removeButton->setCheckable(true);
113 _removeButton->setChecked(_removed);
114 _removeButton->setIcon(DialogSettings::RemoveIcon);
115 } else {
116 _removeButton = nullptr;
117 }
118 hbox->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed));
119 _spinBoxX->setRange(-200.0, 300.0);
120 _spinBoxY->setRange(-200.0, 300.0);
121 _spinBoxX->setValue(_position.x());
122 _spinBoxY->setValue(_position.y());
123 _grid->addWidget(_label = new QLabel(_name, widget), row, 0, 1, 1);
124 _grid->addWidget(_rowCell, row, 1, 1, 2);
125
126 #ifdef _GMIC_QT_DEBUG_
127 _label->setToolTip(QString("Burst: %1").arg(_burst ? "on" : "off"));
128 #endif
129
130 setRemoved(_removed);
131 connectSpinboxes();
132 return true;
133 }
134
addToKeypointList(KeypointList & list) const135 void PointParameter::addToKeypointList(KeypointList & list) const
136 {
137 if (_removable && _removed) {
138 list.add(KeypointList::Keypoint(_color, _removable, _burst, _radius, _keepOpacityWhenSelected));
139 } else {
140 list.add(KeypointList::Keypoint(_position.x(), _position.y(), _color, _removable, _burst, _radius, _keepOpacityWhenSelected));
141 }
142 }
143
extractPositionFromKeypointList(KeypointList & list)144 void PointParameter::extractPositionFromKeypointList(KeypointList & list)
145 {
146 Q_ASSERT_X(!list.isEmpty(), __PRETTY_FUNCTION__, "Keypoint list is empty");
147 enableNotifications(false);
148 KeypointList::Keypoint kp = list.front();
149 if (!kp.isNaN()) {
150 _position.setX(kp.x);
151 _position.setY(kp.y);
152 if (_spinBoxX) {
153 _spinBoxX->setValue(kp.x);
154 _spinBoxY->setValue(kp.y);
155 }
156 }
157 list.pop_front();
158 enableNotifications(true);
159 }
160
value() const161 QString PointParameter::value() const
162 {
163 if (_removed) {
164 return "nan,nan";
165 }
166 return QString("%1,%2").arg(_position.x()).arg(_position.y());
167 }
168
defaultValue() const169 QString PointParameter::defaultValue() const
170 {
171 return QString("%1,%2").arg(_defaultPosition.x()).arg(_defaultPosition.y());
172 }
173
setValue(const QString & value)174 void PointParameter::setValue(const QString & value)
175 {
176 QStringList list = value.split(",");
177 if (list.size() == 2) {
178 bool ok;
179 float x = list[0].toFloat(&ok);
180 bool xNaN = (list[0].toUpper() == "NAN");
181 if (ok && !xNaN) {
182 _position.setX(x);
183 }
184 float y = list[1].toFloat(&ok);
185 bool yNaN = (list[1].toUpper() == "NAN");
186 if (ok && !yNaN) {
187 _position.setY(y);
188 }
189 _removed = (_removable && xNaN && yNaN);
190
191 updateView();
192 }
193 }
194
setVisibilityState(AbstractParameter::VisibilityState state)195 void PointParameter::setVisibilityState(AbstractParameter::VisibilityState state)
196 {
197 AbstractParameter::setVisibilityState(state);
198 if (state == VisibilityState::Visible) {
199 updateView();
200 }
201 }
202
updateView()203 void PointParameter::updateView()
204 {
205 if (not _spinBoxX) {
206 return;
207 }
208 disconnectSpinboxes();
209 if (_removeButton) {
210 setRemoved(_removed);
211 _removeButton->setChecked(_removed);
212 }
213 if (!_removed) {
214 _spinBoxX->setValue(_position.x());
215 _spinBoxY->setValue(_position.y());
216 }
217 connectSpinboxes();
218 }
219
reset()220 void PointParameter::reset()
221 {
222 _position = _defaultPosition;
223 enableNotifications(false);
224 if (_spinBoxX) {
225 _spinBoxX->setValue(_defaultPosition.rx());
226 _spinBoxY->setValue(_defaultPosition.ry());
227 }
228 if (_removeButton && _removable) {
229 _removeButton->setChecked((_removed = _defaultRemovedStatus));
230 }
231 enableNotifications(true);
232 }
233
234 // P = point(x,y,removable{(0),1},burst{(0),1},r,g,b,a{negative->keepOpacityWhenSelected},radius,widget_visible{0|(1)})
initFromText(const char * text,int & textLength)235 bool PointParameter::initFromText(const char * text, int & textLength)
236 {
237 QList<QString> list = parseText("point", text, textLength);
238 if (list.isEmpty()) {
239 return false;
240 }
241 _name = HtmlTranslator::html2txt(FilterTextTranslator::translate(list[0]));
242 QList<QString> params = list[1].split(",");
243
244 bool ok = true;
245
246 _defaultPosition.setX(50.0);
247 _defaultPosition.setY(50.0);
248 _defaultPosition = _position;
249 _color.setRgb(255, 255, 255, 255);
250 _burst = false;
251 _removable = false;
252 _radius = KeypointList::Keypoint::DefaultRadius;
253 _keepOpacityWhenSelected = false;
254
255 float x = 50.0f;
256 float y = 50.0f;
257 _removed = false;
258 bool xNaN = true;
259 bool yNaN = true;
260
261 if (!params.isEmpty()) {
262 x = params[0].toFloat(&ok);
263 xNaN = (params[0].toUpper() == "NAN");
264 if (!ok) {
265 return false;
266 }
267 if (xNaN) {
268 x = 50.0;
269 }
270 }
271
272 if (params.size() >= 2) {
273 y = params[1].toFloat(&ok);
274 yNaN = (params[1].toUpper() == "NAN");
275 if (!ok) {
276 return false;
277 }
278 if (yNaN) {
279 y = 50.0;
280 }
281 }
282
283 _defaultPosition.setX(static_cast<qreal>(x));
284 _defaultPosition.setY(static_cast<qreal>(y));
285 _removed = _defaultRemovedStatus = (xNaN || yNaN);
286
287 if (params.size() >= 3) {
288 int removable = params[2].toInt(&ok);
289 if (!ok) {
290 return false;
291 }
292 switch (removable) {
293 case -1:
294 _removable = _removed = _defaultRemovedStatus = true;
295 break;
296 case 0:
297 _removable = _removed = false;
298 break;
299 case 1:
300 _removable = true;
301 _defaultRemovedStatus = _removed = (xNaN && yNaN);
302 break;
303 default:
304 return false;
305 }
306 }
307
308 if (params.size() >= 4) {
309 bool burst = params[3].toInt(&ok);
310 if (!ok) {
311 return false;
312 }
313 _burst = burst;
314 }
315
316 if (params.size() >= 5) {
317 int red = params[4].toInt(&ok);
318 if (!ok) {
319 return false;
320 }
321 _color.setRed(red);
322 _color.setGreen(red);
323 _color.setBlue(red);
324 } else {
325 pickColorFromDefaultColormap();
326 }
327
328 if (params.size() >= 6) {
329 int green = params[5].toInt(&ok);
330 if (!ok) {
331 return false;
332 }
333 _color.setGreen(green);
334 _color.setBlue(0);
335 }
336
337 if (params.size() >= 7) {
338 int blue = params[6].toInt(&ok);
339 if (!ok) {
340 return false;
341 }
342 _color.setBlue(blue);
343 }
344
345 if (params.size() >= 8) {
346 int alpha = params[7].toInt(&ok);
347 if (!ok) {
348 return false;
349 }
350 if (params[7].trimmed().startsWith("-") || (alpha < 0)) {
351 _keepOpacityWhenSelected = true;
352 }
353 _color.setAlpha(std::abs(alpha));
354 }
355
356 if (params.size() >= 9) {
357 QString s = params[8].trimmed();
358 if (s.endsWith("%")) {
359 s.chop(1);
360 _radius = -s.toFloat(&ok);
361 } else {
362 _radius = s.toFloat(&ok);
363 }
364 if (!ok) {
365 return false;
366 }
367 }
368
369 _position = _defaultPosition;
370 return true;
371 }
372
enableNotifications(bool on)373 void PointParameter::enableNotifications(bool on)
374 {
375 _notificationEnabled = on;
376 }
377
onSpinBoxChanged()378 void PointParameter::onSpinBoxChanged()
379 {
380 _position = QPointF(_spinBoxX->value(), _spinBoxY->value());
381 if (_notificationEnabled) {
382 notifyIfRelevant();
383 }
384 }
385
setRemoved(bool on)386 void PointParameter::setRemoved(bool on)
387 {
388 _removed = on;
389 if (_spinBoxX) {
390 _spinBoxX->setDisabled(on);
391 _spinBoxY->setDisabled(on);
392 _labelX->setDisabled(on);
393 _labelY->setDisabled(on);
394 if (_removeButton) {
395 _removeButton->setIcon(on ? DialogSettings::AddIcon : DialogSettings::RemoveIcon);
396 }
397 }
398 }
399
resetDefaultColorIndex()400 void PointParameter::resetDefaultColorIndex()
401 {
402 _defaultColorNextIndex = 0;
403 _randomSeed = 12345;
404 }
405
onRemoveButtonToggled(bool on)406 void PointParameter::onRemoveButtonToggled(bool on)
407 {
408 setRemoved(on);
409 notifyIfRelevant();
410 }
411
randomChannel()412 int PointParameter::randomChannel()
413 {
414 int value = (_randomSeed / 65536) % 256;
415 _randomSeed = _randomSeed * 1103515245 + 12345;
416 return value;
417 }
418
connectSpinboxes()419 void PointParameter::connectSpinboxes()
420 {
421 if (_connected || !_spinBoxX) {
422 return;
423 }
424 connect(_spinBoxX, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxChanged()));
425 connect(_spinBoxY, SIGNAL(valueChanged(double)), this, SLOT(onSpinBoxChanged()));
426 if (_removable && _removeButton) {
427 connect(_removeButton, SIGNAL(toggled(bool)), this, SLOT(onRemoveButtonToggled(bool)));
428 }
429 _connected = true;
430 }
431
disconnectSpinboxes()432 void PointParameter::disconnectSpinboxes()
433 {
434 if (!_connected || !_spinBoxX) {
435 return;
436 }
437 _spinBoxX->disconnect(this);
438 _spinBoxY->disconnect(this);
439 if (_removable && _removeButton) {
440 _removeButton->disconnect(this);
441 }
442 _connected = false;
443 }
444
pickColorFromDefaultColormap()445 void PointParameter::pickColorFromDefaultColormap()
446 {
447 switch (_defaultColorNextIndex) {
448 case 0:
449 _color.setRgb(255, 255, 255, 255);
450 break;
451 case 1:
452 _color = Qt::red;
453 break;
454 case 2:
455 _color = Qt::green;
456 break;
457 case 3:
458 _color.setRgb(64, 64, 255, 255);
459 break;
460 case 4:
461 _color = Qt::cyan;
462 break;
463 case 5:
464 _color = Qt::magenta;
465 break;
466 case 6:
467 _color = Qt::yellow;
468 break;
469 default:
470 _color.setRgb(randomChannel(), randomChannel(), randomChannel());
471 }
472 ++_defaultColorNextIndex;
473 }
474
475 } // namespace GmicQt
476