Update debian/control to point to garage.
[weightgraph] / weightgraph / weightdata.cpp
1 #include "weightdata.h"
2 #include "settings.h"
3 #include <QTextStream>
4 #include <QStringList>
5 #include <QtAlgorithms>
6 #include <assert.h>
7 #include <limits>
8
9 #include <QDebug>
10
11 WeightDataModel::WeightDataModel(QString &datafilename, QObject *parent) :
12     QAbstractTableModel(parent), datafile(datafilename)
13 {
14   clear();
15   readFromDisk();
16 }
17
18 void WeightDataModel::clear()
19 {
20   weights.clear();
21 }
22
23 Qt::ItemFlags WeightDataModel::flags(const QModelIndex &index) const
24 {
25   if(!index.isValid())
26     return Qt::ItemIsEnabled;
27   switch(index.column()) {
28     case 0: return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
29     case 1: return Qt::ItemIsEnabled | Qt::ItemIsEditable;
30     default: return Qt::NoItemFlags;
31   }
32 }
33
34 QVariant WeightDataModel::data(const QModelIndex &index, int role) const
35 {
36   if (!index.isValid() || index.row() >= rowCount(QModelIndex())
37       || index.column() >= columnCount(QModelIndex()))
38     return QVariant();
39
40   if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::SizeHintRole)
41     return QVariant();
42
43   if (role == Qt::SizeHintRole) {
44     if (index.column() == 0)
45       return QSize(230,70);
46     else
47       return QSize(280,70);
48   }
49
50   if (index.column() == 0) {
51     return weights.at(index.row()).date.toString(Qt::ISODate);
52   }
53   else {
54     if (role == Qt::DisplayRole)
55       return QString("%1 %2").arg(weights.at(index.row()).weight,5,'f',1)
56                              .arg(Settings::weightUnit());
57     else
58       return weights.at(index.row()).weight;
59   }
60
61   return QVariant();
62 }
63
64 bool WeightDataModel::setData(const QModelIndex &index, const QVariant &value, int role)
65 {
66   if (!index.isValid() || role != Qt::EditRole)
67     return false;
68   switch (index.column()) {
69   case 0:
70     weights[index.row()].date = value.toDate();
71     break;
72   case 1: {
73     double weight = value.toDouble();
74     weights[index.row()].weight = weight;
75     break;
76   }
77   default: return false;
78   }
79
80   emit(dataChanged(index, index));
81
82   writeToDisk();
83   return true;
84 }
85
86 bool WeightDataModel::setDataForRow(int row, const DateWeight &dw)
87 {
88   if (row < 0 || row >= weights.size())
89     return false;
90
91   weights[row] = dw;
92   QModelIndex a = index(row, 0);
93   QModelIndex b = index(row, 1);
94
95   emit(dataChanged(a, b));
96   writeToDisk();
97   return true;
98 }
99 // Sets data for a date. Adds a new row for the date if it doesn't exist.
100 void WeightDataModel::setWeightForDate(const QDate &date, double weight)
101 {
102   int row = rowOfDate(date);
103   if (row == -1) {
104     row = rowForNewDate(date);
105     insertRows(row, 1);
106   }
107   DateWeight dw = {date, weight};
108   setDataForRow(row, dw);
109 }
110
111 void WeightDataModel::setWeightForDate(const WeightDataModel::DateWeight &dw)
112 {
113   setWeightForDate(dw.date, dw.weight);
114 }
115
116 QVariant WeightDataModel::headerData(int section, Qt::Orientation orientation, int role) const
117 {
118   if (role != Qt::DisplayRole)
119     return QVariant();
120   if (orientation == Qt::Horizontal) {
121     return section == 0 ? tr("Date") : tr("Weight");
122   }
123   else {
124     return QString("%1").arg(section);
125   }
126 }
127
128 // Insert count "empty" rows starting from row.
129 // DOESN'T WRITE DATA BACK TO DISK to allow inserter
130 // to modify the row, then write it.
131 bool WeightDataModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
132 {
133   beginInsertRows(QModelIndex(), row, row+count-1);
134   DateWeight empty;
135   //empty.date.setDate(2000,1,1);
136   empty.weight = 0.0;
137   while(count--)
138     weights.insert(row, empty);
139   endInsertRows();
140   return true;
141 }
142
143 bool WeightDataModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
144 {
145   beginRemoveRows(QModelIndex(), row, row+count-1);
146   while(count--)
147     weights.removeAt(row);
148   endRemoveRows();
149   writeToDisk();
150   return true;
151 }
152
153 //Returns the row of the date or -1 if it doesn't exist.
154 int WeightDataModel::rowOfDate(const QDate &date) const
155 {
156   for(int i=0; i<weights.size(); i++)
157     if (weights[i].date == date)
158       return i;
159   return -1;
160 }
161
162 bool WeightDataModel::dateExists(const QDate &date) const
163 {
164   return rowOfDate(date) != -1;
165 }
166
167 int WeightDataModel::rowForNewDate(const QDate &date) const
168 {
169   if (weights.size() == 0)
170     return 0;
171   if (date < weights.first().date)
172     return 0;
173   for(int i=1; i<weights.size(); i++) {
174     if (weights.at(i-1).date < date
175         && weights.at(i).date > date)
176       return i;
177   }
178   if (date > weights.last().date)
179     return weights.size();
180   assert(0 && "UNREACHABLE");
181   return -1;
182 }
183
184 void WeightDataModel::writeToDisk()
185 {
186   if (QFile::exists(datafile.fileName())) {
187     QFile::copy(datafile.fileName(), datafile.fileName()+".bak");
188   }
189   if (datafile.open(QIODevice::WriteOnly | QIODevice::Text)) {
190     QTextStream stream(&datafile);
191     foreach(DateWeight w, weights)
192       stream << w.date.toString(Qt::ISODate) << ';' << w.weight << "\n";
193     datafile.close();
194   }
195 }
196
197 void WeightDataModel::readFromDisk()
198 {
199   if (!datafile.exists()) {
200     clear();
201     return;
202   }
203   if (datafile.open(QIODevice::ReadOnly | QIODevice::Text)) {
204     clear();
205     QTextStream stream(&datafile);
206     QString line = stream.readLine();
207     while (!line.isNull()) {
208       QStringList parts = line.split(';');
209       DateWeight w;
210       w.date = QDate::fromString(parts[0], Qt::ISODate);
211       if (!w.date.isValid()) {
212         throw(QString("Invalid date in file: '%1'").arg(parts[0]));
213       }
214       bool ok;
215       w.weight = parts[1].toDouble(&ok);
216       if(!ok) {
217         throw(QString("Invalid weight in file: '%1'").arg(parts[1]));
218       }
219       weights.push_back(w);
220       line = stream.readLine();
221     }
222     datafile.close();
223     qSort(weights);
224   }
225   else {
226     clear();
227     throw(QString("Could not read file '%1'").arg(datafile.fileName()));
228     return;
229   }
230 }