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