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