1 /* Copyright (c) 2015 Gerald Knizia
2 *
3 * This file is part of the IboView program (see: http://www.iboview.org)
4 *
5 * IboView is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * IboView is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with bfint (LICENSE). If not, see http://www.gnu.org/licenses/
16 *
17 * Please see IboView documentation in README.txt for:
18 * -- A list of included external software and their licenses. The included
19 * external software's copyright is not touched by this agreement.
20 * -- Notes on re-distribution and contributions to/further development of
21 * the IboView software
22 */
23
24 #include "IvCurveView.h"
25 #include <boost/format.hpp>
26 #include <QMouseEvent>
27 #include <QColor>
28 #include <iostream>
29 #include <QGraphicsPathItem>
30
FCurveView(QWidget * parent)31 FCurveView::FCurveView(QWidget *parent)
32 : QGraphicsView(parent)
33 {
34 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
35 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
36
37 QGraphicsScene *scene = new QGraphicsScene(this);
38 // ^- FIXME: should I delete this or does this belong to the view? I think it's mine...
39 scene->setItemIndexMethod(QGraphicsScene::NoIndex);
40 // scene->setSceneRect(0, 0, 1000, 1000);
41 // scene->setSceneRect(0, 0, width(), height());
42 setScene(scene);
43
44 // setCacheMode(CacheBackground);
45 // ^- doesn't work with widget-fixed backgrounds :(.
46 // setBackgroundBrush(QBrush(QRgb(0xff404040))); // dark gray
47 setViewportUpdateMode(BoundingRectViewportUpdate);
48 setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing |
49 QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform);
50
51 // setTransformationAnchor(AnchorUnderMouse);
52 // // scale(qreal(0.8), qreal(0.8));
53 // // setMinimumSize(400, 400);
54 // setAlignment(0);
55 // // setAlignment(0);
56 // setDragMode(ScrollHandDrag);
57 //
58 //
59 // setResizeAnchor(AnchorViewCenter);
60 // setInteractive(true);
61 // setTransformationAnchor(AnchorUnderMouse);
62 // // setDragMode(QGraphicsView::RubberBandDrag);
63 setDragMode(QGraphicsView::ScrollHandDrag);
64
65
66 // setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
67 // setMinimumHeight(100);
68 // setMaximumHeight(100);
69
70 // {
71 // TArray<float> hmpf;
72 // for (unsigned i = 0; i < 50; ++ i)
73 // hmpf.push_back(rand() % 40);
74 // setCurve(0, hmpf, CURVE_Fill | CURVE_Outline, 0xffffffff, 0xff703030);
75 // }
76 }
77
~FCurveView()78 FCurveView::~FCurveView()
79 {
80 delete scene();
81 // ^- FIXME hmhmhm...
82 }
83
84
getCurveBounds()85 QRectF FCurveView::getCurveBounds()
86 {
87 if (m_Curves.empty())
88 return QRectF(0., 0., 50, 1.);
89 unsigned
90 nPts = 0;
91 float
92 Min=1e9, Max=-1e9;
93 for (unsigned i = 0; i < m_Curves.size(); ++ i) {
94 nPts = std::max(nPts, unsigned(m_Curves[i].Data.size()));
95 Min = std::min(Min, m_Curves[i].GetMin());
96 Max = std::max(Max, m_Curves[i].GetMax());
97 }
98 return QRectF(0, Min, nPts, Max-Min);
99 }
100
101 // this converts a QRectF to a QPolygonF... I *think* QPolygonF::QPolygonF(QRectF)
102 // should do exactly the same, but for some reason the thus constructed QPolygonF
103 // objects do not work in quatToQuad on my machine.
rectToPoly(QRectF const & rect)104 static QPolygonF rectToPoly(QRectF const &rect) {
105 QPolygonF
106 poly;
107 poly << rect.topLeft()
108 << rect.topRight()
109 << rect.bottomRight()
110 << rect.bottomLeft();
111 // ^- note: this does NOT close the polygon. Apparently this is the problem
112 // with the QRectF constructor... it makes a polygon with 5 vertices.
113 return poly;
114 }
115
116 static const double Pi = 3.14159265358979323846264338327950288419717;
117 // static double TwoPi = 2.0 * Pi;
118
Orth2(QPointF & u,QPointF & v,QPointF const & dxy)119 static void Orth2(QPointF &u, QPointF &v, QPointF const &dxy)
120 {
121 float f = 1.f/std::sqrt(sqr(dxy.x()) + sqr(dxy.y()));
122 u = f * dxy;
123 v = QPointF(u.y(), -u.x());
124 }
125
AddArrowToPath(QPainterPath * path,QPointF p0,QPointF p1,float ArrowAngle,float ArrowSize)126 static void AddArrowToPath(QPainterPath *path, QPointF p0, QPointF p1, float ArrowAngle, float ArrowSize)
127 {
128 QPointF
129 u, v;
130 Orth2(u, v, p0-p1);
131
132 float
133 du = ArrowSize,
134 dv = -std::tan(ArrowAngle/2) * du;
135
136 QPolygonF
137 Arrow;
138 Arrow << p0 + du * u
139 << p0 + dv * v
140 << p0 - dv * v;
141 path->addPolygon(Arrow);
142 path->closeSubpath();
143 }
144
DrawLineWithArrows(QGraphicsScene * scene,QLineF const & line,QPen const & pen,bool bStartArrow,bool bEndArrow)145 static void DrawLineWithArrows(QGraphicsScene *scene, QLineF const &line, QPen const &pen, bool bStartArrow, bool bEndArrow)
146 {
147 float
148 // LineAngle = std::atan2(line.dy(), line.dx()),
149 ArrowAngle = Pi/3,
150 ArrowSize = 3. * pen.widthF();
151 {
152 QPainterPath
153 path;
154 path.moveTo(line.p1());
155 path.lineTo(line.p2());
156 scene->addPath(path, pen, QBrush());
157 }
158
159 if (bStartArrow || bEndArrow) {
160 QPainterPath
161 path;
162 if (bStartArrow)
163 AddArrowToPath(&path, line.p1(), line.p2(), ArrowAngle, ArrowSize);
164 if (bEndArrow)
165 AddArrowToPath(&path, line.p2(), line.p1(), ArrowAngle, ArrowSize);
166 scene->addPath(path, QPen(Qt::NoPen), pen.brush());
167 }
168 }
169
170
RebuildScene()171 void FCurveView::RebuildScene()
172 {
173 scene()->clear();
174 QRectF
175 cb = getCurveBounds();
176 float
177 // fMarginH = 0.03 * cb.width(),
178 // fMarginV = 0.08 * cb.height();
179 fMarginH = 10.f, // pixels.
180 fMarginV = 10.f;
181 bool
182 // bTransWorked = QTransform::quadToQuad(QPolygonF(getCurveBounds()), QPolygonF(QRectF(this->rect())), m_TrafoData);
183 // bTransWorked = QTransform::quadToQuad(QPolygonF(QRectF(cb.left(),0.f,cb.width(),1.f)), QPolygonF(QRectF(this->rect())), m_TrafoData);
184 // bTransWorked = QTransform::quadToQuad(rectToPoly(QRectF(cb.left()-fMarginH,1.f+fMarginV,cb.width()+2*fMarginH,-1.f-2*fMarginV)), rectToPoly(QRectF(0,0,width(),height())), m_TrafoData);
185 bTransWorked = QTransform::quadToQuad(rectToPoly(QRectF(cb.left(),1.f,cb.width(),-1.f)), rectToPoly(QRectF(fMarginH,fMarginV,width()-2*fMarginH,height()-2*fMarginV)), m_TrafoData);
186 if (!bTransWorked) {
187 std::cerr << "\nWARNING: Failed to set up data transformation in FCurveView.\n" << std::endl;
188 }
189
190 #define DMAP(x,y) m_TrafoData.map(QPointF((x),(y)))
191 for (uint iLine = 0; iLine < m_HvLines.size(); ++ iLine) {
192 FHvLineData
193 &HvLine = m_HvLines[iLine];
194 QPainterPath path;
195 QLineF line;
196 if ((HvLine.Flags & HVCURVE_TypeMask) == HVCURVE_Hline) {
197 line = QLineF(QPointF(DMAP(cb.left(), HvLine.fPos)),
198 QPointF(DMAP(cb.right(), HvLine.fPos)));
199 } else {
200 line = QLineF(QPointF(DMAP(HvLine.fPos, 0.f)),
201 QPointF(DMAP(HvLine.fPos, 1.f)));
202 }
203 // scene()->addPath(path, HvLine.Pen, QBrush());
204 DrawLineWithArrows(scene(), line, HvLine.Pen, bool(HvLine.Flags & HVCURVE_StartArrow), bool(HvLine.Flags & HVCURVE_EndArrow));
205 }
206
207 for (uint iCurveId = 0; iCurveId < m_Curves.size(); ++ iCurveId)
208 {
209 FCurveData
210 &Curve = m_Curves[iCurveId];
211
212 QPen &pen = Curve.Stroke;
213 QBrush &brush = Curve.Fill;
214
215 if (Curve.isBold())
216 pen.setWidthF(3.00);
217
218 // see also: /opt/prog/qt-everywhere-opensource-src-4.8.5/examples/graphicsview/elasticnodes
219 {
220 QPainterPath path;
221
222 if (Curve.Flags & CURVE_Ellipses) {
223 for (unsigned i = 0; i < Curve.Data.size(); ++ i)
224 path.addEllipse(DMAP(i, Curve.Data[i]), 2., 2.);
225 }
226
227 for (unsigned i = 0; i < Curve.Data.size(); ++ i)
228 if (i == 0)
229 path.moveTo(DMAP(i, Curve.Data[i]));
230 else
231 path.lineTo(DMAP(i, Curve.Data[i]));
232 if (Curve.isOutlined())
233 scene()->addPath(path, pen, QBrush())->setZValue(Curve.zOrder);
234 if (Curve.isFilled()) {
235 path.lineTo(DMAP(Curve.Data.size()-1, 0.));
236 path.lineTo(DMAP(0, 0.));
237 scene()->addPath(path, QPen(), brush)->setZValue(Curve.zOrder);
238 }
239 }
240 }
241 #undef DMAP
242 }
243
clearCurves()244 void FCurveView::clearCurves()
245 {
246 m_HvLines.clear();
247 m_Curves.clear();
248 RebuildScene();
249 }
250
addHline(float fPos,QPen const & Pen,uint32_t Flags)251 void FCurveView::addHline(float fPos, QPen const &Pen, uint32_t Flags)
252 {
253 m_HvLines.push_back(FHvLineData(fPos, HVCURVE_Hline | Flags, Pen));
254 }
255
addVline(float fPos,QPen const & Pen,uint32_t Flags)256 void FCurveView::addVline(float fPos, QPen const &Pen, uint32_t Flags)
257 {
258 // std::cout << "\n!vline at " << fPos << "\n" << std::endl;
259 m_HvLines.push_back(FHvLineData(fPos, HVCURVE_Vline | Flags, Pen));
260 }
261
getCurve(TArray<float> & Data,QString & Title,uint32_t & Flags,QColor & Color,unsigned iCurveId)262 void FCurveView::getCurve(TArray<float> &Data, QString &Title, uint32_t &Flags, QColor &Color, unsigned iCurveId)
263 {
264 if (iCurveId >= m_Curves.size())
265 throw std::runtime_error("requested data of non-existent curve.");
266 FCurveData
267 &Curve = m_Curves[iCurveId];
268 Data = Curve.Data;
269 Flags = Curve.Flags;
270 Color = Curve.Stroke.color();
271 Title = Curve.Title;
272 }
273
274
275
setCurve(unsigned iCurveId,TArray<float> const & Data,QString Title,uint32_t Flags,int zOrder,QPen const & Stroke,QBrush const & Fill)276 void FCurveView::setCurve(unsigned iCurveId, TArray<float> const &Data, QString Title, uint32_t Flags, int zOrder, QPen const &Stroke, QBrush const &Fill)
277 {
278 if (m_Curves.size() < iCurveId+1)
279 m_Curves.resize(iCurveId+1);
280 FCurveData
281 &Curve = m_Curves[iCurveId];
282 Curve = FCurveData(Data, Flags, zOrder, Stroke, Fill);
283 Curve.Title = Title;
284 RebuildScene();
285
286
287
288 // {
289 // QPainterPath path;
290 // float dy = -height()/(Max-Min);
291 // float y0 = height() - dy * Min;
292 // float dx = float(width())/nPts;
293 // #define F(x) (y0+(x)*dy)
294 // FCurveData
295 // &Curve = m_Curves[iCurveId];
296 //
297 // path.moveTo(0, F(Min));
298 // for (unsigned i = 0; i < Curve.Data.size(); ++ i)
299 // path.lineTo(dx*i, F(Curve.Data[i]));
300 // path.lineTo(dx*(Curve.Data.size()-1), F(Min));
301 // scene()->addPath(path, pen, brush);
302 // #undef F
303 // }
304 };
305
306
resizeEvent(QResizeEvent *)307 void FCurveView::resizeEvent(QResizeEvent */*event*/)
308 {
309 // scene()->setSceneRect(0, 0, width(), height());
310 //
311 // QRectF cb = getCurveBounds();
312 // fitInView(QRectF(qreal(cb.left()), 0., qreal(cb.width()),1.), Qt::IgnoreAspectRatio);
313 // fitInView(QRectF(qreal(cb.left()), cb.top(), qreal(cb.width()), cb.height()), Qt::IgnoreAspectRatio);
314 // std::cout << boost::format("l: %i t: %i w: %i h: %i") % cb.left() % cb.top() % cb.width() % cb.height() << std::endl;
315 // fitInView(QRectF(cb.left(),0.,50.,1.), Qt::IgnoreAspectRatio);
316 RebuildScene();
317 }
318
319 // FCurveData::FCurveData(TArray<float> const &Data_, uint32_t Flags_, uint32_t Stroke_, uint32_t Fill_)
320 // : Data(Data_), Flags(Flags_), Stroke(Stroke_), Fill(Fill_)
321 // {}
322
FCurveData(TArray<float> const & Data_,uint32_t Flags_,int zOrder_,QPen Stroke_,QBrush Fill_)323 FCurveData::FCurveData(TArray<float> const &Data_, uint32_t Flags_, int zOrder_, QPen Stroke_, QBrush Fill_)
324 : Data(Data_), Flags(Flags_), Stroke(Stroke_), Fill(Fill_), zOrder(zOrder_)
325 {}
326
327
GetMin()328 float FCurveData::GetMin() {
329 if (Data.empty())
330 return 0.;
331 float Min = Data[0];
332 for (size_t i = 0; i < Data.size(); ++ i)
333 Min = std::min(Data[i], Min);
334 return Min;
335 }
336
GetMax()337 float FCurveData::GetMax() {
338 if (Data.empty())
339 return 0.;
340 float Max = Data[0];
341 for (size_t i = 0; i < Data.size(); ++ i)
342 Max = std::max(Data[i], Max);
343 return Max;
344 }
345
346
keyPressEvent(QKeyEvent *)347 void FCurveView::keyPressEvent(QKeyEvent */*event*/)
348 {
349 }
350
wheelEvent(QWheelEvent * event)351 void FCurveView::wheelEvent(QWheelEvent *event)
352 {
353 scaleView(pow((double)2, event->delta() / 240.0));
354 }
355
356
mousePressEvent(QMouseEvent * event)357 void FCurveView::mousePressEvent(QMouseEvent *event)
358 {
359 LastX = event->x();
360 LastY = event->y();
361 FBase::mousePressEvent(event);
362 }
363
mouseReleaseEvent(QMouseEvent * event)364 void FCurveView::mouseReleaseEvent(QMouseEvent *event)
365 {
366 FBase::mouseReleaseEvent(event);
367 // translate(-1000, 0);
368 // std::cout << "mouse release event!" << std::endl;
369 }
370
mouseMoveEvent(QMouseEvent * event)371 void FCurveView::mouseMoveEvent(QMouseEvent *event)
372 {
373 FBase::mouseMoveEvent(event);
374 // if (event->buttons() != 0) {
375 // float fScale = 1.;
376 // float fDeltaX = fScale * (event->x() - LastX);
377 // float fDeltaY = fScale * (event->y() - LastY);
378 //
379 // // if ( event->buttons() == (Qt::LeftButton | Qt::RightButton) ) {
380 // translate(fDeltaX, fDeltaY);
381 // update();
382 // // }
383 // }
384 LastX = event->x();
385 LastY = event->y();
386 }
387
388
drawBackground(QPainter * painter,const QRectF & rect)389 void FCurveView::drawBackground(QPainter *painter, const QRectF &rect)
390 {
391 // FBase::drawBackground(painter, rect);
392 // Q_UNUSED(rect);
393
394 // QRectF
395 // sceneRect = this->sceneRect();
396
397 // QLinearGradient
398 // gradient(sceneRect.topLeft(), sceneRect.bottomRight());
399 QLinearGradient
400 gradient(rect.topLeft(), rect.bottomRight());
401 gradient.setColorAt(0, QColor(0xff606060));
402 // gradient.setColorAt(1, Qt::lightGray);
403 gradient.setColorAt(1, QColor(0xff202020));
404 painter->fillRect(rect, gradient);
405 // painter->fillRect(rect.intersect(sceneRect), gradient);
406 // painter->setBrush(Qt::NoBrush);
407 // painter->drawRect(sceneRect);
408 // ^- yes... it is stupid. I could not resist.
409 }
410
scaleView(qreal scaleFactor)411 void FCurveView::scaleView(qreal scaleFactor)
412 {
413 qreal
414 factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
415 if (factor < 0.07 || factor > 100)
416 return;
417
418 scale(scaleFactor, scaleFactor);
419 // ^- transformation anchor is under mouse... pretty cool, imo.
420 }
421