KSeExpr 6.0.0.0
ExprCurve.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2011-2019 Disney Enterprises, Inc.
2// SPDX-License-Identifier: LicenseRef-Apache-2.0
3// SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
4// SPDX-License-Identifier: GPL-3.0-or-later
5/*
6 * @file ExprCurve.cpp
7 * @brief Contains PyQt4 Ramp Widget to emulate Maya's ramp widget
8 * @author Arthur Shek
9 * @version ashek 05/04/09 Initial Version
10 */
11
12#include <QDialog>
13#include <QDialogButtonBox>
14#include <QDoubleValidator>
15#include <QFormLayout>
16#include <QGraphicsSceneMouseEvent>
17#include <QHBoxLayout>
18#include <QLabel>
19#include <QMenu>
20#include <QPushButton>
21#include <QResizeEvent>
22#include <QToolButton>
23#include <QVBoxLayout>
24
26
27#include "ExprCurve.h"
28
30{
31 _cvs.clear();
32}
33
34void CurveGraphicsView::resizeEvent(QResizeEvent *event)
35{
36 emit resizeSignal(event->size().width(), event->size().height());
37}
38
40 : _curve(new T_CURVE)
41 , _width(320)
42 , _height(50)
43 , _interp(T_CURVE::kMonotoneSpline)
44 , _selectedItem(-1)
45 , _lmb(false)
46{
49}
50
52{
53 delete _curve;
54}
55
56void CurveScene::resize(const int width, const int height)
57{
58 // width and height already have the 8 px padding factored in
59 _width = width - 16;
60 _height = height - 16;
61 setSceneRect(-9, -7, width, height);
62 drawRect();
63 drawPoly();
64 drawPoints();
65}
66
68{
69 delete _curve;
70 _curve = new T_CURVE;
71 for (auto & _cv : _cvs)
72 _curve->addPoint(_cv._pos, _cv._val, _cv._interp);
73 _curve->preparePoints();
74}
75
76void CurveScene::addPoint(double x, double y, const T_INTERP interp, const bool select)
77{
78 x = KSeExpr::clamp(x, 0, 1);
79 y = KSeExpr::clamp(y, 0, 1);
80
81 _cvs.emplace_back(x, y, T_INTERP(interp));
82 auto newIndex = _cvs.size() - 1;
83
85
86 if (select)
87 _selectedItem = newIndex;
88 drawPoly();
89 drawPoints();
90}
91
92void CurveScene::removePoint(const int index)
93{
94 _cvs.erase(_cvs.begin() + index);
95 _selectedItem = -1;
97
98 drawPoly();
99 drawPoints();
101}
102
103void CurveScene::keyPressEvent(QKeyEvent *event)
104{
105 if (((event->key() == Qt::Key_Backspace) || (event->key() == Qt::Key_Delete)) && (_selectedItem >= 0)) {
106 // user hit delete with cv selected
108 }
109}
110
111void CurveScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
112{
113 _lmb = true;
114 QPointF pos = mouseEvent->scenePos();
115 // get items under mouse click
116 QList<QGraphicsItem *> itemList = items(pos);
117 if (itemList.empty()) {
118 _selectedItem = -1;
119 emit cvSelected(-1, -1, _interp);
120 drawPoints();
121 } else if (itemList[0]->zValue() == 2) {
122 // getting here means we've selected a current point
123 const int numCircle = _circleObjects.size();
124 for (int i = 0; i < numCircle; i++) {
125 QGraphicsItem *obj = _circleObjects[i];
126 if (obj == itemList[0]) {
127 _selectedItem = i;
128 _interp = _cvs[i]._interp;
129 emit cvSelected(_cvs[i]._pos, _cvs[i]._val, _cvs[i]._interp);
130 }
131 }
132 drawPoints();
133 } else {
134 if (mouseEvent->buttons() == Qt::LeftButton) {
135 // getting here means we want to create a new point
136 double myx = pos.x() / _width;
137 T_INTERP interpFromNearby = _curve->getLowerBoundCV(KSeExpr::clamp(myx, 0, 1))._interp;
138 if (interpFromNearby == T_CURVE::kNone)
139 interpFromNearby = T_CURVE::kMonotoneSpline;
140 addPoint(myx, pos.y() / _height, interpFromNearby);
142 } else {
143 _selectedItem = -1;
144 drawPoints();
145 }
146 }
147}
148
149void CurveScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
150{
151 if (_selectedItem >= 0) {
152 auto *menu = new QMenu(event->widget());
153 QAction *deleteAction = menu->addAction(tr("Delete Point"));
154 // menu->addAction("Cancel");
155 QAction *action = menu->exec(event->screenPos());
156 if (action == deleteAction)
158 }
159}
160
161void CurveScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
162{
163 if (_lmb) {
164 QPointF point = mouseEvent->scenePos();
165 if (_selectedItem >= 0) {
166 // clamp motion to inside curve area
167 double pos = KSeExpr::clamp(point.x() / _width, 0, 1);
168 double val = KSeExpr::clamp(point.y() / _height, 0, 1);
169 _cvs[_selectedItem]._pos = pos;
170 _cvs[_selectedItem]._val = val;
171 rebuildCurve();
172 emit cvSelected(pos, val, _cvs[_selectedItem]._interp);
173 drawPoly();
174 drawPoints();
176 }
177 }
178}
179
180void CurveScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *)
181{
182 _lmb = false;
183}
184
185// user selected a different interpolation type, redraw
186void CurveScene::interpChanged(const int interp)
187{
188 _interp = T_INTERP(interp);
189 if (_selectedItem >= 0) {
190 _cvs[_selectedItem]._interp = _interp;
191 rebuildCurve();
192 drawPoly();
194 }
195}
196
197// user entered a different point position, redraw
198void CurveScene::selPosChanged(double posInput)
199{
200 if (_selectedItem >= 0) {
201 double pos = KSeExpr::clamp(posInput, 0, 1);
202 _cvs[_selectedItem]._pos = pos;
203 rebuildCurve();
204 drawPoly();
205 drawPoints();
207 }
208}
209
210// user entered a different point value, redraw
212{
213 if (_selectedItem >= 0) {
214 val = KSeExpr::clamp(val, 0, 1);
215 _cvs[_selectedItem]._val = val;
216 rebuildCurve();
217 drawPoly();
218 drawPoints();
220 }
221}
222
223// return points in reverse order in order to use same parsing in editor
225{
226 emit curveChanged();
227}
228
229// draws the base gray outline rectangle
231{
232 if (_baseRect == nullptr) {
233 _baseRect = addRect(0, 0, _width, _height, QPen(Qt::black, 1.0), QBrush(Qt::gray));
234 }
235 _baseRect->setRect(0, 0, _width, _height);
236 _baseRect->setZValue(0);
237}
238
239// draws the poly curve representation
241{
242 if (_curvePoly == nullptr) {
243 _curvePoly = addPolygon(QPolygonF(), QPen(Qt::black, 1.0), QBrush(Qt::darkGray));
244 }
245
246 QPolygonF poly;
247 poly.append(QPointF(_width, 0));
248 poly.append(QPointF(0, 0));
249 for (int i = 0; i < 1000; i++) {
250 double x = i / 1000.0;
251 poly.append(QPointF(_width * x, _height * _curve->getValue(x)));
252 }
253 poly.append(QPointF(_width, 0));
254 _curvePoly->setPolygon(poly);
255 _curvePoly->setZValue(1);
256}
257
258// draws the cv points
260{
261 for(const auto *i: _circleObjects) {
262 delete i;
263 }
264 _circleObjects.clear();
265 int numCV = _cvs.size();
266 for (int i = 0; i < numCV; i++) {
267 const T_CURVE::CV &pt = _cvs[i];
268 QPen pen;
269 if (i == _selectedItem) {
270 pen = QPen(Qt::white, 1.0);
271 } else {
272 pen = QPen(Qt::black, 1.0);
273 }
274 _circleObjects.push_back(addEllipse(pt._pos * _width - 4, pt._val * _height - 4, 8, 8, pen, QBrush()));
275 QGraphicsEllipseItem *circle = _circleObjects.back();
276 circle->setFlag(QGraphicsItem::ItemIsMovable, true);
277 circle->setZValue(2);
278 }
279}
280
281ExprCurve::ExprCurve(QWidget *parent, QString pLabel, QString vLabel, QString iLabel, bool expandable)
282 : QWidget(parent)
283 , _scene(nullptr)
284{
285 auto *mainLayout = new QHBoxLayout();
286 mainLayout->setContentsMargins({});
287
288 auto *edits = new QWidget;
289 auto *editsLayout = new QFormLayout;
290 editsLayout->setContentsMargins({});
291 edits->setLayout(editsLayout);
292
293 _selPosEdit = new QLineEdit;
294 auto *posValidator = new QDoubleValidator(0.0, 1.0, 6, _selPosEdit);
295 _selPosEdit->setValidator(posValidator);
296 QString posLabel;
297 if (pLabel.isEmpty()) {
298 posLabel = tr("Selected Position:");
299 } else {
300 posLabel = pLabel;
301 }
302 editsLayout->addRow(posLabel, _selPosEdit);
303
304 _selValEdit = new QLineEdit;
305 auto *valValidator = new QDoubleValidator(0.0, 1.0, 6, _selValEdit);
306 _selValEdit->setValidator(valValidator);
307 QString valLabel;
308 if (vLabel.isEmpty()) {
309 valLabel = tr("Selected Value:");
310 } else {
311 valLabel = vLabel;
312 }
313 editsLayout->addRow(valLabel, _selValEdit);
314
315 QString interpLabel;
316 if (iLabel.isEmpty()) {
317 interpLabel = tr("Interp:");
318 } else {
319 interpLabel = iLabel;
320 }
321 _interpComboBox = new QComboBox;
322 _interpComboBox->addItem(tr("None"));
323 _interpComboBox->addItem(tr("Linear"));
324 _interpComboBox->addItem(tr("Smooth"));
325 _interpComboBox->addItem(tr("Spline"));
326 _interpComboBox->addItem(tr("MSpline"));
327 _interpComboBox->setCurrentIndex(4);
328 editsLayout->addRow(interpLabel, _interpComboBox);
329
330 auto *curveView = new CurveGraphicsView;
331 curveView->setFrameShape(QFrame::StyledPanel);
332 curveView->setFrameShadow(QFrame::Sunken);
333 curveView->setLineWidth(1);
334 curveView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
335 curveView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
336 _scene = new CurveScene;
337 curveView->setScene(_scene);
338 curveView->setTransform(QTransform().scale(1, -1));
339 curveView->setRenderHints(QPainter::Antialiasing);
340
341 mainLayout->addWidget(edits);
342 mainLayout->addWidget(curveView);
343 if (expandable) {
344 auto *expandButton = new QToolButton(this);
345 expandButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
346 QIcon expandIcon = QIcon::fromTheme("arrow-right", QIcon::fromTheme("go-next"));
347 auto *detailAction = new QAction(expandIcon, tr("&Expand..."));
348 expandButton->setDefaultAction(detailAction);
349 mainLayout->addWidget(expandButton);
350 // open a the detail widget when clicked
351 connect(expandButton, SIGNAL(triggered(QAction *)), this, SLOT(openDetail()));
352 }
353 mainLayout->setStretchFactor(curveView, 100);
354 setLayout(mainLayout);
355
356 // SIGNALS
357
358 // when a user selects a cv, update the fields on left
359 connect(_scene, SIGNAL(cvSelected(double, double, T_INTERP)), this, SLOT(cvSelectedSlot(double, double, T_INTERP)));
360 // when a user selects a different interp, the curve has to redraw
361 connect(_interpComboBox, SIGNAL(activated(int)), _scene, SLOT(interpChanged(int)));
362 // when a user types a different position, the curve has to redraw
363 connect(_selPosEdit, SIGNAL(returnPressed()), this, SLOT(selPosChanged()));
364 connect(this, SIGNAL(selPosChangedSignal(double)), _scene, SLOT(selPosChanged(double)));
365 // when a user types a different value, the curve has to redraw
366 connect(_selValEdit, SIGNAL(returnPressed()), this, SLOT(selValChanged()));
367 connect(this, SIGNAL(selValChangedSignal(double)), _scene, SLOT(selValChanged(double)));
368 // when the widget is resized, resize the curve widget
369 connect(curveView, SIGNAL(resizeSignal(int, int)), _scene, SLOT(resize(int, int)));
370}
371
372// CV selected, update the user interface fields.
373void ExprCurve::cvSelectedSlot(double pos, double val, T_INTERP interp)
374{
375 QString posStr;
376 if (pos >= 0.0)
377 posStr.setNum(pos, 'f', 3);
378 _selPosEdit->setText(posStr);
379 QString valStr;
380 if (val >= 0.0)
381 valStr.setNum(val, 'f', 3);
382 _selValEdit->setText(valStr);
383 _interpComboBox->setCurrentIndex(interp);
384}
385
386// User entered new position, round and send signal to redraw curve.
388{
389 double pos = QString(_selPosEdit->text()).toDouble();
390 _selPosEdit->setText(QString(tr("%1")).arg(pos, 0, 'f', 3));
391 emit selPosChangedSignal(pos);
392}
393
394// User entered new value, round and send signal to redraw curve.
396{
397 double val = QString(_selValEdit->text()).toDouble();
398 val = KSeExpr::clamp(val, 0, 1);
399 _selValEdit->setText(QString(tr("%1")).arg(val, 0, 'f', 3));
400 emit selValChangedSignal(val);
401}
402
404{
405 auto *dialog = new QDialog();
406 dialog->setMinimumWidth(1024);
407 dialog->setMinimumHeight(400);
408 auto *curve = new ExprCurve(nullptr, QString(), QString(), QString(), false);
409
410 // copy points into new data
411 const std::vector<T_CURVE::CV> &data = _scene->_cvs;
412 for (const auto & i : data)
413 curve->addPoint(i._pos, i._val, i._interp);
414
415 auto *layout = new QVBoxLayout();
416 dialog->setLayout(layout);
417 layout->addWidget(curve);
418 auto *buttonbar = new QDialogButtonBox();
419 buttonbar->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
420 connect(buttonbar, SIGNAL(accepted()), dialog, SLOT(accept()));
421 connect(buttonbar, SIGNAL(rejected()), dialog, SLOT(reject()));
422 layout->addWidget(buttonbar);
423
424 if (dialog->exec() == QDialog::Accepted) {
425 // copy points back from child
426 _scene->removeAll();
427 const auto &dataNew = curve->_scene->_cvs;
428 for (const auto & i : dataNew)
429 addPoint(i._pos, i._val, i._interp);
431 }
432
433 if (dialog->exec() == QDialog::Accepted) {
434 // copy points back from child
435 _scene->removeAll();
436 const auto &dataNew = curve->_scene->_cvs;
437 for (const auto & i : dataNew)
438 addPoint(i._pos, i._val, i._interp);
440 }
441}
void resizeEvent(QResizeEvent *event) override
Definition ExprCurve.cpp:34
void resizeSignal(int width, int height)
void drawPoints()
void cvSelected(double x, double y, T_INTERP interp)
int _height
Definition ExprCurve.h:97
void removeAll()
Definition ExprCurve.cpp:29
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override
T_CURVE::InterpType T_INTERP
Definition ExprCurve.h:51
void removePoint(int index)
Definition ExprCurve.cpp:92
void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override
int _selectedItem
Definition ExprCurve.h:100
QGraphicsRectItem * _baseRect
Definition ExprCurve.h:102
void resize(int width, int height)
Definition ExprCurve.cpp:56
void drawRect()
void selValChanged(double val)
void rebuildCurve()
Definition ExprCurve.cpp:67
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override
std::vector< QGraphicsEllipseItem * > _circleObjects
Definition ExprCurve.h:99
void drawPoly()
void addPoint(double x, double y, T_INTERP interp, bool select=true)
Definition ExprCurve.cpp:76
void emitCurveChanged()
QGraphicsPolygonItem * _curvePoly
Definition ExprCurve.h:101
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
void interpChanged(int interp)
KSeExpr::Curve< double > T_CURVE
Definition ExprCurve.h:50
std::vector< T_CURVE::CV > _cvs
Definition ExprCurve.h:78
void curveChanged()
~CurveScene() override
Definition ExprCurve.cpp:51
void selPosChanged(double pos)
T_INTERP _interp
Definition ExprCurve.h:98
void keyPressEvent(QKeyEvent *event) override
T_CURVE * _curve
Definition ExprCurve.h:83
QLineEdit * _selValEdit
Definition ExprCurve.h:141
void cvSelectedSlot(double pos, double val, T_INTERP interp)
void addPoint(double x, double y, T_INTERP interp, bool select=false)
Definition ExprCurve.h:121
ExprCurve(QWidget *parent=nullptr, QString pLabel=QString(), QString vLabel=QString(), QString iLabel=QString(), bool expandable=true)
void selValChanged()
void selValChangedSignal(double val)
QComboBox * _interpComboBox
Definition ExprCurve.h:142
void selPosChangedSignal(double pos)
void openDetail()
CurveScene * _scene
Definition ExprCurve.h:126
QLineEdit * _selPosEdit
Definition ExprCurve.h:140
void selPosChanged()
InterpType
Supported interpolation types.
Definition Curve.h:32
double clamp(double x, double lo, double hi)