Fixed a locale bug in stats view, improved edit window.
[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 QModelIndex WeightDataModel::indexOfDate(const QDate &date) const
162 {
163   return index(rowOfDate(date), 0);
164 }
165
166 bool WeightDataModel::dateExists(const QDate &date) const
167 {
168   return rowOfDate(date) != -1;
169 }
170
171 int WeightDataModel::rowForNewDate(const QDate &date) const
172 {
173   if (weights.size() == 0)
174     return 0;
175   if (date < weights.first().date)
176     return 0;
177   for(int i=1; i<weights.size(); i++) {
178     if (weights.at(i-1).date < date
179         && weights.at(i).date > date)
180       return i;
181   }
182   if (date > weights.last().date)
183     return weights.size();
184   assert(0 && "UNREACHABLE");
185   return -1;
186 }
187
188 void WeightDataModel::writeToDisk()
189 {
190   if (QFile::exists(datafile.fileName())) {
191     QFile::copy(datafile.fileName(), datafile.fileName()+".bak");
192   }
193   if (datafile.open(QIODevice::WriteOnly | QIODevice::Text)) {
194     QTextStream stream(&datafile);
195     foreach(DateWeight w, weights)
196       stream << w.date.toString(Qt::ISODate) << ';' << w.weight << "\n";
197     datafile.close();
198   }
199 }
200
201 void WeightDataModel::readFromDisk()
202 {
203   if (!datafile.exists()) {
204     clear();
205     return;
206   }
207   if (datafile.open(QIODevice::ReadOnly | QIODevice::Text)) {
208     clear();
209     QTextStream stream(&datafile);
210     QString line = stream.readLine();
211     while (!line.isNull()) {
212       QStringList parts = line.split(';');
213       DateWeight w;
214       w.date = QDate::fromString(parts[0], Qt::ISODate);
215       if (!w.date.isValid()) {
216         throw(QString("Invalid date in file: '%1'").arg(parts[0]));
217       }
218       bool ok;
219       w.weight = parts[1].toDouble(&ok);
220       if(!ok) {
221         throw(QString("Invalid weight in file: '%1'").arg(parts[1]));
222       }
223       weights.push_back(w);
224       line = stream.readLine();
225     }
226     datafile.close();
227     qSort(weights);
228   }
229   else {
230     clear();
231     throw(QString("Could not read file '%1'").arg(datafile.fileName()));
232     return;
233   }
234 }