Scaled icon's master image down.
[weightgraph] / weightgraph / weightgraphview.cpp
1 #include "weightgraphview.h"
2 #include "settings.h"
3 #include <QPainter>
4 #include <QDebug>
5 #include <QTimer>
6 #include <cmath>
7 #include <QtGui/QX11Info>
8 #include <X11/Xlib.h>
9 #include <X11/Xatom.h>
10
11 WeightGraphView::WeightGraphView(WeightDataModel *wdm,
12                                  const QString &id, QWidget *parent) :
13     QWidget(parent), id(id), wdm(wdm),
14     period(Settings::graphSettings(id).defaultTimeInterval)
15 {
16   setWindowTitle("WeightGraph");
17
18   setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
19
20   connect(wdm, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
21           this, SLOT(update()));
22   connect(wdm, SIGNAL(rowsInserted(const QModelIndex&,int,int)),
23           this, SLOT(update()));
24   connect(wdm, SIGNAL(rowsRemoved(const QModelIndex&,int,int)),
25           this, SLOT(update()));
26   connect(Settings::self(), SIGNAL(settingChanged()),
27           this, SLOT(update()));
28 }
29
30 void WeightGraphView::mousePressEvent(QMouseEvent *)
31 {
32   emit clicked();
33 }
34
35 QSize WeightGraphView::sizeHint() const
36 {
37   return QSize(300, 400);
38 }
39
40 void WeightGraphView::keyPressEvent(QKeyEvent* event)
41 {
42   //qDebug() << "Key pressed: " << event->key();
43   switch (event->key()) {
44   case Qt::Key_F7:
45       decPeriod();
46       event->accept();
47       break;
48
49   case Qt::Key_F8:
50       incPeriod();
51       event->accept();
52       break;
53   }
54   QWidget::keyPressEvent(event);
55 }
56
57 // Macros to transform dates and weights to paintdevice coords
58 #define D_X(d) ((d)*qreal(width())/days)
59 #define DW_X(dw) D_X(f.daysTo((dw).date))
60 #define W_Y(w) (height()*(maxW-(w))/weightInterval)
61 #define DW_Y(dw) W_Y((dw).weight)
62 #define DW_POINT(dw) QPointF(DW_X(dw), DW_Y(dw))
63
64 inline double weightIntervalToMult(double wi)
65 {
66   if (wi <= 0.2) return 0.01;
67   else if (wi <= 0.5) return 0.025;
68   else if (wi <= 1.0) return 0.05;
69   else if (wi <= 2.0) return 0.1;
70   else if (wi <= 5.0) return 0.25;
71   else if (wi <= 10.0) return 0.5;
72   else if (wi <= 20.0) return 1.0;
73   else if (wi <= 50.0) return 2.5;
74   else if (wi <= 100.0) return 5.0;
75   else if (wi <= 200.0) return 10.0;
76   else if (wi <= 500.0) return 25.0;
77   else if (wi <= 1000.0) return 50.0;
78   else return 100.0;
79 }
80
81 void WeightGraphView::paintEvent(QPaintEvent *)
82 {
83   QPainter painter(this);
84   painter.setRenderHint(QPainter::Antialiasing);
85
86   GraphSettings gs = Settings::graphSettings(id);
87   const QList<DW> &allWeights = wdm->getWeights();
88
89   double min, max;
90   QList<DW> weights;
91   const DW *beforeFirst = NULL;
92   if (allWeights.size() < 2) {
93     min = 0;
94     max = 100;
95   }
96   else {
97     bool firstFound = false;
98     min = 1e30;
99     max = -1e30;
100     const QDate &l = allWeights.last().date;
101     for(int i=0; i < allWeights.size(); i++) {
102       if (period == 0 || firstFound || allWeights[i].date.daysTo(l) <= period) {
103         weights.append(allWeights[i]);
104         if (allWeights[i].weight < min)
105           min = allWeights[i].weight;
106         if (allWeights[i].weight > max)
107           max = allWeights[i].weight;
108         firstFound = true;
109       }
110       if (!firstFound)
111         beforeFirst = &allWeights[i];
112     }
113   }
114
115   if (gs.weightIntervalMode == GraphSettings::AutomaticWithGoalWeight
116       && gs.goalWeightEnabled) {
117     min = qMin(min, Settings::goalWeightMin());
118     max = qMax(max, Settings::goalWeightMax());
119   }
120   else if(gs.weightIntervalMode == GraphSettings::Manual) {
121     if (gs.weightIntervalMax > gs.weightIntervalMin) {
122       min = gs.weightIntervalMin;
123       max = gs.weightIntervalMax;
124     }
125   }
126   // else default is min and max of actual data
127
128   double margin = (max - min)*0.03;
129   double minW = min - margin;
130   double maxW = max + margin;
131   double weightInterval = maxW-minW;
132   if (maxW-minW < 0.1) {
133     minW = min-0.003;
134     maxW = max+0.003;
135     weightInterval = maxW - minW;
136   }
137   QDate f, l;
138   int days;
139   if (weights.size() < 2) {
140     l = QDate::currentDate();
141     f = l.addDays(-7);
142     days = 7;
143   }
144   else if (period==0) {
145     f = weights.first().date;
146     l = weights.last().date;
147     days = f.daysTo(l);
148   } else {
149     l = weights.last().date;
150     f = l.addDays(-period);
151     days = period;
152   }
153
154   // Interpolate to fill gap in left part of the graph
155   if (weights.first().date != f && beforeFirst != NULL) {
156     DW dw;
157     dw.date = f;
158     dw.weight = (weights.first().weight - beforeFirst->weight)
159                 /beforeFirst->date.daysTo(weights.first().date)
160                 *beforeFirst->date.daysTo(f)
161                 +beforeFirst->weight;
162     weights.prepend(dw);
163   }
164
165   painter.setWindow(-50, 0, width()+85, height()+25);
166
167   if (gs.goalWeightEnabled
168       && ((Settings::goalWeightMin() > minW && Settings::goalWeightMin() < maxW)
169           ||(Settings::goalWeightMax() > minW && Settings::goalWeightMax() < maxW))) {
170     QPen oldPen = painter.pen();
171     QBrush oldBrush = painter.brush();
172     painter.setPen(Qt::NoPen);
173     painter.setBrush(QColor(0,255,0,100));
174     painter.setClipRect(QRectF(0,0,width(),height()));
175     painter.drawRect(QRectF(QPointF(0, W_Y(Settings::goalWeightMax())),
176                             QPointF(width(), W_Y(Settings::goalWeightMin()))));
177     painter.setClipping(false);
178     painter.setPen(oldPen);
179     painter.setBrush(oldBrush);
180   }
181
182   // Y-axis
183   QFont font = painter.font();
184   font.setPixelSize(16);
185   painter.setFont(font);
186   painter.drawLine(QPointF(0.0,0.0), QPointF(0,height()));
187   double mult = weightIntervalToMult(weightInterval);
188   int count = 0;
189   for(double w=ceil(minW/mult)*mult; w < maxW; w += mult, count++) {
190     double len = count%5==0 ? 7.0 : 4.0;
191     painter.drawLine(QPointF(-len,W_Y(w)),QPointF(len,W_Y(w)));
192     if (count%5 == 0) {
193       QPen p = painter.pen();
194       painter.setPen(QColor(50,50,50));
195       painter.drawLine(QPointF(len,W_Y(w)),QPointF(width(),W_Y(w)));
196       painter.setPen(p);
197       QString text = tr("%1").arg(double(w),0,'f', mult <= 0.25 ? 2 : 1);
198       QSize textSize = painter.fontMetrics().size(0, text);
199       painter.drawText(QPointF(-len-3-textSize.width(), W_Y(w)+6), text);
200     }
201   }
202
203   // X-axis
204   font.setPixelSize(13);
205   painter.setFont(font);
206   double endOfLastDate = -1e6;
207   mult = days/30+1;
208   painter.drawLine(QPointF(0.0,height()), QPointF(width(),height()));
209   for(int day=0; day <= days; day+=mult) {
210     QString text = f.addDays(day).toString(Qt::ISODate);
211     QSize textSize = painter.fontMetrics().size(0, text);
212     double tickLen;
213     if (D_X(day)-textSize.width()/2 > endOfLastDate + 10) {
214       tickLen = 5.0;
215       painter.drawText(QPointF(D_X(day)-textSize.width()/2,
216                                W_Y(minW)+18), text);
217       endOfLastDate = D_X(day)+textSize.width()/2;
218     }
219     else
220       tickLen = 3.0;
221     painter.drawLine(QPointF(D_X(day), W_Y(minW)-tickLen),
222                      QPointF(D_X(day), W_Y(minW)+tickLen));
223   }
224
225   // The weight data
226   QPolygonF linepoints;
227   foreach(DW dw, weights) {
228     linepoints << DW_POINT(dw);
229   }
230   painter.drawPolyline(linepoints);
231
232 }
233 void WeightGraphView::show()
234 {
235   QWidget::show();
236   grabZoomKeys(Settings::grabZoomKeys()); //Need to be regrabbed somewhy
237   //Work around a bug: hidden graphs don't update. Must wait for the
238   //graph to actually show up, then call update.
239   QTimer *tmp = new QTimer(this);
240   tmp->setSingleShot(true);
241   tmp->setInterval(500);
242   connect(tmp, SIGNAL(timeout()), this, SLOT(update()));
243   tmp->start();
244 }
245
246 void WeightGraphView::grabZoomKeys(bool grab)
247 {
248   if (!winId()) {
249     qWarning("Can't grab keys unless we have a window id");
250     return;
251   }
252
253   unsigned long val = (grab) ? 1 : 0;
254   Atom atom = XInternAtom(QX11Info::display(), "_HILDON_ZOOM_KEY_ATOM", False);
255   if (!atom) {
256     qWarning("Unable to obtain _HILDON_ZOOM_KEY_ATOM. This example will only work "
257              "on a Maemo 5 device!");
258     return;
259   }
260
261
262   XChangeProperty (QX11Info::display(),
263                    winId(),
264                    atom,
265                    XA_INTEGER,
266                    32,
267                    PropModeReplace,
268                    reinterpret_cast<unsigned char *>(&val),
269                    1);
270
271   //qDebug() << "Grabbed for winId " << winId();
272 }