Added code base.
[weightgraph] / weightgraph / weightdata.cpp
diff --git a/weightgraph/weightdata.cpp b/weightgraph/weightdata.cpp
new file mode 100644 (file)
index 0000000..36b286c
--- /dev/null
@@ -0,0 +1,230 @@
+#include "weightdata.h"
+#include "settings.h"
+#include <QTextStream>
+#include <QStringList>
+#include <QtAlgorithms>
+#include <assert.h>
+#include <limits>
+
+#include <QDebug>
+
+WeightDataModel::WeightDataModel(QString &datafilename, QObject *parent) :
+    QAbstractTableModel(parent), datafile(datafilename)
+{
+  clear();
+  readFromDisk();
+}
+
+void WeightDataModel::clear()
+{
+  weights.clear();
+}
+
+Qt::ItemFlags WeightDataModel::flags(const QModelIndex &index) const
+{
+  if(!index.isValid())
+    return Qt::ItemIsEnabled;
+  switch(index.column()) {
+    case 0: return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+    case 1: return Qt::ItemIsEnabled | Qt::ItemIsEditable;
+    default: return Qt::NoItemFlags;
+  }
+}
+
+QVariant WeightDataModel::data(const QModelIndex &index, int role) const
+{
+  if (!index.isValid() || index.row() >= rowCount(QModelIndex())
+      || index.column() >= columnCount(QModelIndex()))
+    return QVariant();
+
+  if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::SizeHintRole)
+    return QVariant();
+
+  if (role == Qt::SizeHintRole) {
+    if (index.column() == 0)
+      return QSize(230,70);
+    else
+      return QSize(280,70);
+  }
+
+  if (index.column() == 0) {
+    return weights.at(index.row()).date.toString(Qt::ISODate);
+  }
+  else {
+    if (role == Qt::DisplayRole)
+      return QString("%1 %2").arg(weights.at(index.row()).weight,5,'f',1)
+                             .arg(Settings::weightUnit());
+    else
+      return weights.at(index.row()).weight;
+  }
+
+  return QVariant();
+}
+
+bool WeightDataModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+  if (!index.isValid() || role != Qt::EditRole)
+    return false;
+  switch (index.column()) {
+  case 0:
+    weights[index.row()].date = value.toDate();
+    break;
+  case 1: {
+    double weight = value.toDouble();
+    weights[index.row()].weight = weight;
+    break;
+  }
+  default: return false;
+  }
+
+  emit(dataChanged(index, index));
+
+  writeToDisk();
+  return true;
+}
+
+bool WeightDataModel::setDataForRow(int row, const DateWeight &dw)
+{
+  if (row < 0 || row >= weights.size())
+    return false;
+
+  weights[row] = dw;
+  QModelIndex a = index(row, 0);
+  QModelIndex b = index(row, 1);
+
+  emit(dataChanged(a, b));
+  writeToDisk();
+  return true;
+}
+// Sets data for a date. Adds a new row for the date if it doesn't exist.
+void WeightDataModel::setWeightForDate(const QDate &date, double weight)
+{
+  int row = rowOfDate(date);
+  if (row == -1) {
+    row = rowForNewDate(date);
+    insertRows(row, 1);
+  }
+  DateWeight dw = {date, weight};
+  setDataForRow(row, dw);
+}
+
+void WeightDataModel::setWeightForDate(const WeightDataModel::DateWeight &dw)
+{
+  setWeightForDate(dw.date, dw.weight);
+}
+
+QVariant WeightDataModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+  if (role != Qt::DisplayRole)
+    return QVariant();
+  if (orientation == Qt::Horizontal) {
+    return section == 0 ? tr("Date") : tr("Weight");
+  }
+  else {
+    return QString("%1").arg(section);
+  }
+}
+
+// Insert count "empty" rows starting from row.
+// DOESN'T WRITE DATA BACK TO DISK to allow inserter
+// to modify the row, then write it.
+bool WeightDataModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
+{
+  beginInsertRows(QModelIndex(), row, row+count-1);
+  DateWeight empty;
+  //empty.date.setDate(2000,1,1);
+  empty.weight = 0.0;
+  while(count--)
+    weights.insert(row, empty);
+  endInsertRows();
+  return true;
+}
+
+bool WeightDataModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
+{
+  beginRemoveRows(QModelIndex(), row, row+count-1);
+  while(count--)
+    weights.removeAt(row);
+  endRemoveRows();
+  writeToDisk();
+  return true;
+}
+
+//Returns the row of the date or -1 if it doesn't exist.
+int WeightDataModel::rowOfDate(const QDate &date) const
+{
+  for(int i=0; i<weights.size(); i++)
+    if (weights[i].date == date)
+      return i;
+  return -1;
+}
+
+bool WeightDataModel::dateExists(const QDate &date) const
+{
+  return rowOfDate(date) != -1;
+}
+
+int WeightDataModel::rowForNewDate(const QDate &date) const
+{
+  if (weights.size() == 0)
+    return 0;
+  if (date < weights.first().date)
+    return 0;
+  for(int i=1; i<weights.size(); i++) {
+    if (weights.at(i-1).date < date
+        && weights.at(i).date > date)
+      return i;
+  }
+  if (date > weights.last().date)
+    return weights.size();
+  assert(0 && "UNREACHABLE");
+  return -1;
+}
+
+void WeightDataModel::writeToDisk()
+{
+  if (QFile::exists(datafile.fileName())) {
+    QFile::copy(datafile.fileName(), datafile.fileName()+".bak");
+  }
+  if (datafile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+    QTextStream stream(&datafile);
+    foreach(DateWeight w, weights)
+      stream << w.date.toString(Qt::ISODate) << ';' << w.weight << "\n";
+    datafile.close();
+  }
+}
+
+void WeightDataModel::readFromDisk()
+{
+  if (!datafile.exists()) {
+    clear();
+    return;
+  }
+  if (datafile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+    clear();
+    QTextStream stream(&datafile);
+    QString line = stream.readLine();
+    while (!line.isNull()) {
+      QStringList parts = line.split(';');
+      DateWeight w;
+      w.date = QDate::fromString(parts[0], Qt::ISODate);
+      if (!w.date.isValid()) {
+        throw(QString("Invalid date in file: '%1'").arg(parts[0]));
+      }
+      bool ok;
+      w.weight = parts[1].toDouble(&ok);
+      if(!ok) {
+        throw(QString("Invalid weight in file: '%1'").arg(parts[1]));
+      }
+      weights.push_back(w);
+      line = stream.readLine();
+    }
+    datafile.close();
+    qSort(weights);
+  }
+  else {
+    clear();
+    throw(QString("Could not read file '%1'").arg(datafile.fileName()));
+    return;
+  }
+}