KSeExpr  4.0.4.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 
25 #include <KSeExpr/ExprBuiltins.h>
26 
27 #include "ExprCurve.h"
28 
30 {
31  _cvs.clear();
32 }
33 
34 void 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 {
47  rebuildCurve();
49 }
50 
52 {
53  delete _curve;
54 }
55 
56 void 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);
74 }
75 
76 void 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 
84  rebuildCurve();
85 
86  if (select)
87  _selectedItem = newIndex;
88  drawPoly();
89  drawPoints();
90 }
91 
92 void CurveScene::removePoint(const int index)
93 {
94  _cvs.erase(_cvs.begin() + index);
95  _selectedItem = -1;
96  rebuildCurve();
97 
98  drawPoly();
99  drawPoints();
101 }
102 
103 void 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 
111 void 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 
149 void 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 
161 void 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 
180 void CurveScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *)
181 {
182  _lmb = false;
183 }
184 
185 // user selected a different interpolation type, redraw
186 void 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
198 void 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 
281 ExprCurve::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->setMargin(0);
287 
288  auto *edits = new QWidget;
289  auto *editsLayout = new QFormLayout;
290  editsLayout->setMargin(0);
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.
373 void 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()
Definition: ExprCurve.cpp:259
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
Definition: ExprCurve.cpp:111
T_CURVE::InterpType T_INTERP
Definition: ExprCurve.h:51
void removePoint(int index)
Definition: ExprCurve.cpp:92
void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override
Definition: ExprCurve.cpp:180
int _selectedItem
Definition: ExprCurve.h:100
QGraphicsRectItem * _baseRect
Definition: ExprCurve.h:102
void resize(int width, int height)
Definition: ExprCurve.cpp:56
void drawRect()
Definition: ExprCurve.cpp:230
void selValChanged(double val)
Definition: ExprCurve.cpp:211
int _width
Definition: ExprCurve.h:96
void rebuildCurve()
Definition: ExprCurve.cpp:67
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override
Definition: ExprCurve.cpp:161
std::vector< QGraphicsEllipseItem * > _circleObjects
Definition: ExprCurve.h:99
void drawPoly()
Definition: ExprCurve.cpp:240
void addPoint(double x, double y, T_INTERP interp, bool select=true)
Definition: ExprCurve.cpp:76
void emitCurveChanged()
Definition: ExprCurve.cpp:224
bool _lmb
Definition: ExprCurve.h:103
QGraphicsPolygonItem * _curvePoly
Definition: ExprCurve.h:101
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
Definition: ExprCurve.cpp:149
void interpChanged(int interp)
Definition: ExprCurve.cpp:186
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)
Definition: ExprCurve.cpp:198
T_INTERP _interp
Definition: ExprCurve.h:98
void keyPressEvent(QKeyEvent *event) override
Definition: ExprCurve.cpp:103
T_CURVE * _curve
Definition: ExprCurve.h:83
QLineEdit * _selValEdit
Definition: ExprCurve.h:141
void cvSelectedSlot(double pos, double val, T_INTERP interp)
Definition: ExprCurve.cpp:373
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)
Definition: ExprCurve.cpp:281
void selValChanged()
Definition: ExprCurve.cpp:395
void selValChangedSignal(double val)
QComboBox * _interpComboBox
Definition: ExprCurve.h:142
void selPosChangedSignal(double pos)
void openDetail()
Definition: ExprCurve.cpp:403
CurveScene * _scene
Definition: ExprCurve.h:126
QLineEdit * _selPosEdit
Definition: ExprCurve.h:140
void selPosChanged()
Definition: ExprCurve.cpp:387
Interpolation curve class for double->double and double->Vec3D.
Definition: Curve.h:27
InterpType
Supported interpolation types.
Definition: Curve.h:32
@ kMonotoneSpline
Definition: Curve.h:32
void preparePoints()
Prepares points for evaluation (sorts and computes boundaries, clamps extrema)
Definition: Curve.cpp:46
T getValue(double param) const
Evaluates curve and returns full value.
Definition: Curve.cpp:94
void addPoint(double position, const T &val, InterpType type)
Adds a point to the curve.
Definition: Curve.cpp:40
CV getLowerBoundCV(double param) const
Definition: Curve.cpp:186
KSeExpr::CurveFuncX curve
double clamp(double x, double lo, double hi)
Definition: ExprBuiltins.h:66
double _pos
Definition: Curve.h:41
InterpType _interp
Definition: Curve.h:43