Fix a crash when viewing statistics including a score on a course which is not found.
[scorecard] / src / stat-model.cpp
1 /*
2  * Copyright (C) 2009 Sakari Poussa
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, version 2.
7  */
8
9 #include <QVariant>
10 #include <QFont>
11
12 #include "score-common.h"
13 #include "stat-model.h"
14
15 StatModel::StatModel(QList<Club *> &cList, QList<Score *> &sList) : clubList(cList), scoreList(sList)
16 {
17     TRACE;
18     update();
19 }
20
21 int StatModel::rowCount(const QModelIndex & parent) const
22 {
23     Q_UNUSED(parent);
24     return ROWS;
25 }
26
27 int StatModel::columnCount(const QModelIndex & parent) const
28 {
29     Q_UNUSED(parent);
30     return COLS;
31 }
32
33 QVariant StatModel::data(const QModelIndex & index, int role) const
34 {
35     if (!index.isValid())
36         return QVariant();
37
38     int row = index.row();
39     int col = index.column();
40
41     if (col >= stat.size())
42         return QVariant();
43     
44     //
45     // ALIGNMENT
46     //
47     if (role == Qt::TextAlignmentRole ) {
48         return Qt::AlignCenter;
49     }
50
51     //
52     // FONT
53     //
54     if (role == Qt::FontRole) {
55         QFont font;
56         font.setPointSize(fontSize);
57         return font;
58     }
59     //
60     // NUMBERS
61     //
62     if (role == Qt::DisplayRole) {
63         switch (row) {
64         case ROW_ROUNDS: 
65             return stat.at(col)->rounds();
66         case ROW_AVERAGE: 
67             return stat.at(col)->average();
68         case ROW_MIN: 
69             return stat.at(col)->min();
70         case ROW_MAX: 
71             return stat.at(col)->max();
72         case ROW_BIRDIE: 
73             return stat.at(col)->birdies();
74         case ROW_PAR: 
75             return stat.at(col)->pars();
76         case ROW_BOGEY: 
77             return stat.at(col)->bogeys();
78         case ROW_MORE:
79             return stat.at(col)->more();
80         }
81     }
82     return QVariant();
83 }
84
85 QVariant StatModel::headerData(int section, Qt::Orientation orientation, int role) const
86 {
87     // Only vertical header -- horizontal is hidden
88     if (role != Qt::DisplayRole)
89         return QVariant();
90
91     if (orientation == Qt::Horizontal) {
92         // TODO: check when no or less data than cols
93         // HERE CRASH
94
95         if (section < stat.size())
96             return stat.at(section)->year();
97     }
98
99     if (orientation == Qt::Vertical) {
100         switch(section) {
101         case ROW_ROUNDS: 
102             return QString("Rounds");
103         case ROW_AVERAGE: 
104             return QString("Average");
105         case ROW_MIN: 
106             return QString("Best");
107         case ROW_MAX: 
108             return QString("Worst");
109         case ROW_BIRDIE: 
110             return QString("Birdies");
111         case ROW_PAR: 
112             return QString("Pars");
113         case ROW_BOGEY: 
114             return QString("Bogeys");
115         case ROW_MORE:
116             return QString("Double+");
117         }
118     }
119
120     return QVariant();
121 }
122
123 // TODO: dup code from table-model.cpp
124 Course *StatModel::findCourse(const QString &clubName, 
125                               const QString &courseName)
126 {
127     QListIterator<Club *> i(clubList);
128     Club *c;
129
130     while (i.hasNext()) {
131         c = i.next();
132         if (c->getName() == clubName) {
133             return c->getCourse(courseName);
134         }
135     }
136     return 0;
137 }
138
139 void StatModel::update(void)
140 {
141     TRACE;
142     QListIterator<Score *> iScore(scoreList);
143     QMultiMap<QString, Score *> yearMap;
144
145     // Create multi map with years as keys, scores as values
146     while (iScore.hasNext()) {
147         Score *score = iScore.next();
148         QString year = score->getDate().split("-").at(0);
149         yearMap.insert(year, score);
150     }
151     // Create uniq list of years
152     QList<QString> yearList = yearMap.uniqueKeys();
153
154     // For each year collect the statistics
155     QListIterator<QString> iYear(yearList);
156     while (iYear.hasNext()) {
157         QString year = iYear.next();
158
159         StatItem *item = new StatItem;
160         item->setYear(year);
161
162         QList<Score *> scoresPerYear = yearMap.values(year);
163         QListIterator<Score *> iScoresPerYear(scoresPerYear);
164     
165         item->setRounds(scoresPerYear.count());
166
167         // for each year, add score
168         int sum = 0;
169         int min = 200;
170         int max = 0;
171         int pars = 0;
172         int birdies = 0;
173         int bogeys = 0;
174         int more = 0;
175         while (iScoresPerYear.hasNext()) {
176             Score *s = iScoresPerYear.next();
177             int tot = s->getTotal(Total).toInt();
178             sum += tot;
179
180             if (tot > max)
181                 max = tot;
182
183             if (tot < min)
184                 min = tot;
185
186             Course *c = findCourse(s->getClubName(), s->getCourseName());
187             if (!c)
188                 // We have a score on a course which we don't have...
189                 continue;
190
191             for (int i = 0; i < 18; i++) {
192                 int par = c->getPar(i).toInt();
193                 int shots = s->getScore(i).toInt();
194         
195                 if (shots == (par - 1))
196                     birdies++;
197                 else if (shots == par)
198                     pars++;
199                 else if (shots == (par + 1))
200                     bogeys++;
201                 else if (shots >= (par + 2))
202                     more++;
203             }
204         }
205         item->setBirdies(birdies);
206         item->setPars(pars);
207         item->setBogeys(bogeys);
208         item->setMore(more);
209
210         int avg = sum / scoresPerYear.count();
211         item->setAverage(avg);
212         item->setMin(min);
213         item->setMax(max);
214
215         stat << item;
216     }
217 }