1 /***************************************************************************
2 * (C) 2007-2009 Michal Rudolf <mrudolf@kdewebdev.org> *
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; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
9
10 #include "database.h"
11 #include "openingtree.h"
12 #include "settings.h"
13 #include "movedata.h"
14 #include "openingtreethread.h"
15
16 #include <QIcon>
17 #include <QImage>
18 #include <QPainter>
19 #include <QtCore>
20
21 #if defined(_MSC_VER) && defined(_DEBUG)
22 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
23 #define new DEBUG_NEW
24 #endif // _MSC_VER
25
compareMove(const MoveData & m1,const MoveData & m2)26 static bool compareMove(const MoveData& m1, const MoveData& m2)
27 {
28 return m1.san < m2.san;
29 }
30
compareScore(const MoveData & m1,const MoveData & m2)31 static bool compareScore(const MoveData& m1, const MoveData& m2)
32 {
33 auto s1 = m1.results.scorePercentage();
34 auto s2 = m2.results.scorePercentage();
35 return s1 < s2 || (s1 == s2 && m1.san < m2.san);
36 }
37
compareRating(const MoveData & m1,const MoveData & m2)38 static bool compareRating(const MoveData& m1, const MoveData& m2)
39 {
40 auto r1 = m1.rating.average();
41 auto r2 = m2.rating.average();
42 return r1 < r2 || (r1 == r2 && m1.san < m2.san);
43 }
44
compareYear(const MoveData & m1,const MoveData & m2)45 static bool compareYear(const MoveData& m1, const MoveData& m2)
46 {
47 auto y1 = m1.year.average();
48 auto y2 = m2.year.average();
49 return y1 < y2 || (y1 == y2 && m1.san < m2.san);
50 }
51
52 const unsigned MinAveYear = 1;
53 const unsigned MinAveRating = 5;
54
updateFilter(FilterX & f,const BoardX & b,bool updateFilter,bool sourceIsFilter,bool bEnd)55 bool OpeningTree::updateFilter(FilterX& f, const BoardX& b, bool updateFilter, bool sourceIsFilter, bool bEnd)
56 {
57 if((&f == m_filter) && (updateFilter == m_updateFilter) && (b == m_board) && (m_bEnd == bEnd) && (m_sourceIsDatabase != sourceIsFilter))
58 {
59 return true;
60 }
61 m_bEnd = bEnd;
62 m_board = b;
63 m_filter = &f;
64 m_updateFilter = updateFilter;
65 m_sourceIsDatabase = !sourceIsFilter;
66
67 if(!oupd.isRunning())
68 {
69 emit openingTreeUpdateStarted();
70 m_bRequestPending = false;
71 connect(&oupd, SIGNAL(UpdateFinished(BoardX*)), this, SLOT(updateFinished(BoardX*)), Qt::UniqueConnection);
72 connect(&oupd, SIGNAL(MoveUpdate(BoardX*,QList<MoveData>)), this, SLOT(moveUpdated(BoardX*,QList<MoveData>)), Qt::UniqueConnection);
73 connect(&oupd, SIGNAL(UpdateTerminated(BoardX*)), this, SLOT(updateTerminated(BoardX*)), Qt::UniqueConnection);
74 connect(&oupd, SIGNAL(progress(int)), SIGNAL(progress(int)), Qt::UniqueConnection);
75 connect(&oupd, SIGNAL(requestGameFilterUpdate(int,int)), SIGNAL(requestGameFilterUpdate(int,int)), Qt::UniqueConnection);
76 return oupd.updateFilter(f, b, m_games, m_updateFilter, m_sourceIsDatabase, m_bEnd);
77 }
78 else
79 {
80 m_bRequestPending = true;
81 oupd.cancel();
82 return false;
83 }
84 }
85
cancel()86 void OpeningTree::cancel()
87 {
88 if(oupd.isRunning())
89 {
90 m_bRequestPending = false;
91 oupd.cancel();
92 oupd.wait(10000);
93 }
94 }
95
updateFinished(BoardX * b)96 void OpeningTree::updateFinished(BoardX* b)
97 {
98 emit openingTreeUpdated();
99 if(m_bRequestPending)
100 {
101 updateTerminated(b);
102 }
103 }
104
moveUpdated(BoardX * b,QList<MoveData> moveList)105 void OpeningTree::moveUpdated(BoardX* b, QList<MoveData> moveList)
106 {
107 if (*b == m_board)
108 {
109 beginResetModel();
110 {
111 m_moves = moveList;
112 doSort(m_sortcolumn, m_order);
113 }
114 endResetModel();
115 }
116 }
117
updateTerminated(BoardX *)118 void OpeningTree::updateTerminated(BoardX*)
119 {
120 if(m_bRequestPending)
121 {
122 emit openingTreeUpdateStarted();
123 m_bRequestPending = false;
124 connect(&oupd, SIGNAL(UpdateFinished(BoardX*)), this, SLOT(updateFinished(BoardX*)), Qt::UniqueConnection);
125 connect(&oupd, SIGNAL(MoveUpdate(BoardX*,QList<MoveData>)), this, SLOT(moveUpdated(BoardX*,QList<MoveData>)), Qt::UniqueConnection);
126 connect(&oupd, SIGNAL(UpdateTerminated(BoardX*)), this, SLOT(updateTerminated(BoardX*)), Qt::UniqueConnection);
127 connect(&oupd, SIGNAL(progress(int)), SIGNAL(progress(int)), Qt::UniqueConnection);
128 connect(&oupd, SIGNAL(requestGameFilterUpdate(int,int)), SIGNAL(requestGameFilterUpdate(int,int)), Qt::UniqueConnection);
129 oupd.updateFilter(*m_filter, m_board, m_games, m_updateFilter, m_sourceIsDatabase, m_bEnd);
130 }
131 }
132
rowCount(const QModelIndex & parent) const133 int OpeningTree::rowCount(const QModelIndex& parent) const
134 {
135 return parent.isValid() ? 0 : m_moves.count();
136 }
137
columnCount(const QModelIndex &) const138 int OpeningTree::columnCount(const QModelIndex&) const
139 {
140 return m_names.count();
141 }
142
OpeningTree(QObject * parent)143 OpeningTree::OpeningTree(QObject* parent) :
144 QAbstractTableModel(parent),
145 m_sortcolumn(1),
146 m_order(Qt::DescendingOrder),
147 m_filter(nullptr),
148 oupd(*new OpeningTreeThread)
149 {
150 m_names << tr("Move") << tr("Count") << tr("Score") << tr("Rating") << tr("Year");
151 }
152
~OpeningTree()153 OpeningTree::~OpeningTree()
154 {
155 delete &oupd;
156 }
157
headerData(int section,Qt::Orientation orientation,int role) const158 QVariant OpeningTree::headerData(int section, Qt::Orientation orientation, int role) const
159 {
160 if(role == Qt::DisplayRole && orientation == Qt::Horizontal)
161 {
162 return m_names[section];
163 }
164 else
165 {
166 return QVariant();
167 }
168 }
169
paintPercentage(int percentageWhite,int percentageBlack) const170 QPixmap OpeningTree::paintPercentage(int percentageWhite, int percentageBlack) const
171 {
172 QRect fullRect(QPoint(0,0),QSize(41, AppSettings->getValue("/General/ListFontSize").toInt()));
173 QImage rowImg = QImage(fullRect.size(),QImage::Format_RGB16);
174 rowImg.fill(Qt::lightGray);
175 fullRect = fullRect.adjusted(0,0,-1,-1);
176
177 QPainter p;
178 p.begin(&rowImg);
179 p.setPen(QPen(QColor(Qt::gray)));
180 p.drawRect(fullRect);
181
182 int percentWidthWhite = (percentageWhite * fullRect.width()) / 100;
183 int percentWidthBlack = (percentageBlack * fullRect.width()) / 100;
184
185 QRect blackRect(QPoint(fullRect.width()-percentWidthBlack, 0), QSize(percentWidthBlack, fullRect.height()));
186 QRect whiteRect(QPoint(0, 0), QSize(percentWidthWhite, fullRect.height()));
187
188 p.setBrush(QBrush(QColor(Qt::white), Qt::SolidPattern));
189 p.drawRect(whiteRect);
190
191 p.setBrush(QBrush(QColor(Qt::darkGray), Qt::SolidPattern));
192 p.drawRect(blackRect);
193 p.end();
194
195 return QPixmap().fromImage(rowImg);
196 }
197
bEnd() const198 bool OpeningTree::bEnd() const
199 {
200 return m_bEnd;
201 }
202
data(const QModelIndex & index,int role) const203 QVariant OpeningTree::data(const QModelIndex& index, int role) const
204 {
205 if(!index.isValid() || index.row() >= m_moves.count())
206 {
207 return QVariant();
208 }
209
210 const auto& data = m_moves[index.row()];
211 switch (role)
212 {
213 case Qt::DisplayRole:
214 {
215 switch(index.column())
216 {
217 case 0:
218 return QString("%1: %2").arg(index.row() + 1).arg(data.localsan);
219 case 1:
220 {
221 if(m_games == 0)
222 {
223 return "";
224 }
225 auto gamesCount = data.results.count();
226 unsigned int percentage = gamesCount * 1000U / m_games / 10U;
227 QString approx;
228 if(percentage == 0)
229 {
230 percentage = 1;
231 approx = "<";
232 }
233 return QString("%1: %2%3%")
234 .arg(gamesCount)
235 .arg(approx)
236 .arg(percentage);
237 }
238 case 2:
239 if (data.results)
240 return QString("%1%").arg(data.results.scorePercentage(), 0, 'f', 1);
241 break;
242 case 3:
243 return data.rating.count() >= MinAveRating ?
244 data.rating.average() : QVariant();
245 case 4:
246 return data.year.count() >= MinAveYear ?
247 data.year.average() : QVariant();
248
249 default:
250 return QVariant();
251 }
252 break;
253 }
254 case Qt::DecorationRole:
255 {
256 if ((index.column() == 2) && data.results)
257 {
258 return paintPercentage(static_cast<int>(data.results.whiteWinPercentage()),
259 static_cast<int>(data.results.blackWinPercentage()));
260 }
261 break;
262 }
263 }
264
265 return QVariant();
266 }
267
doSort(int column,Qt::SortOrder order)268 void OpeningTree::doSort(int column, Qt::SortOrder order)
269 {
270 m_sortcolumn = column;
271 m_order = order;
272
273 switch(column)
274 {
275 case 0:
276 std::sort(m_moves.begin(), m_moves.end(), compareMove);
277 break;
278 case 1:
279 std::sort(m_moves.begin(), m_moves.end());
280 break;
281 case 2:
282 std::sort(m_moves.begin(), m_moves.end(), compareScore);
283 break;
284 case 3:
285 std::sort(m_moves.begin(), m_moves.end(), compareRating);
286 break;
287 case 4:
288 std::sort(m_moves.begin(), m_moves.end(), compareYear);
289 break;
290 };
291
292 if(order == Qt::DescendingOrder)
293 {
294 for(int i = 0; i < m_moves.count() / 2; ++i)
295 {
296 qSwap(m_moves[i], m_moves[m_moves.count() - i - 1]);
297 }
298 }
299 }
300
sort(int column,Qt::SortOrder order)301 void OpeningTree::sort(int column, Qt::SortOrder order)
302 {
303 beginResetModel();
304 doSort(column, order);
305 endResetModel();
306 }
307
sort()308 void OpeningTree::sort()
309 {
310 sort(m_sortcolumn, m_order);
311 }
312
move(const QModelIndex & index) const313 QString OpeningTree::move(const QModelIndex& index) const
314 {
315 return index.isValid() ? m_moves[index.row()].san : QString();
316 }
317
board() const318 BoardX OpeningTree::board() const
319 {
320 return m_board;
321 }
322
323