1 /*
2  * issuesmodel.cpp
3  * Copyright 2019, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "issuesmodel.h"
22 
23 namespace Tiled {
24 
IssuesModel(QObject * parent)25 IssuesModel::IssuesModel(QObject *parent)
26     : QAbstractListModel(parent)
27 {
28     mErrorIcon.addFile(QLatin1String("://images/16/dialog-error.png"));
29     mErrorIcon.addFile(QLatin1String("://images/24/dialog-error.png"));
30     mErrorIcon.addFile(QLatin1String("://images/32/dialog-error.png"));
31 
32     mWarningIcon.addFile(QLatin1String("://images/16/dialog-warning.png"));
33     mWarningIcon.addFile(QLatin1String("://images/24/dialog-warning.png"));
34     mWarningIcon.addFile(QLatin1String("://images/32/dialog-warning.png"));
35 
36     connect(&LoggingInterface::instance(), &LoggingInterface::issue,
37             this, &IssuesModel::addIssue);
38     connect(&LoggingInterface::instance(), &LoggingInterface::removeIssuesWithContext,
39             this, &IssuesModel::removeIssuesWithContext);
40 }
41 
instance()42 IssuesModel &IssuesModel::instance()
43 {
44     static IssuesModel issuesModel;
45     return issuesModel;
46 }
47 
addIssue(const Issue & issue)48 void IssuesModel::addIssue(const Issue &issue)
49 {
50     int i = mIssues.indexOf(issue);
51     if (i != -1) {
52         auto &existingIssue = mIssues[i];
53         existingIssue.addOccurrence(issue);
54 
55         QModelIndex modelIndex = index(i);
56         emit dataChanged(modelIndex, modelIndex);
57         return;
58     }
59 
60     switch (issue.severity()) {
61     case Issue::Error: ++mErrorCount; break;
62     case Issue::Warning: ++mWarningCount; break;
63     }
64 
65     beginInsertRows(QModelIndex(), mIssues.size(), mIssues.size());
66     mIssues.append(issue);
67     endInsertRows();
68 }
69 
removeIssues(const QList<unsigned> & issueIds)70 void IssuesModel::removeIssues(const QList<unsigned> &issueIds)
71 {
72     RangeSet<int> indexes;
73 
74     for (unsigned id : issueIds) {
75         auto it = std::find_if(mIssues.cbegin(), mIssues.cend(),
76                                [id] (const Issue &issue) { return issue.id() == id; });
77 
78         if (it != mIssues.cend())
79             indexes.insert(std::distance(mIssues.cbegin(), it));
80     }
81 
82     removeIssues(indexes);
83 }
84 
removeIssuesWithContext(const void * context)85 void IssuesModel::removeIssuesWithContext(const void *context)
86 {
87     RangeSet<int> indexes;
88 
89     for (int i = 0, size = mIssues.size(); i < size; ++i)
90         if (mIssues.at(i).context() == context)
91             indexes.insert(i);
92 
93     removeIssues(indexes);
94 }
95 
removeIssues(const RangeSet<int> & indexes)96 void IssuesModel::removeIssues(const RangeSet<int> &indexes)
97 {
98     if (indexes.isEmpty())
99         return;
100 
101     // Remove back to front to keep the indexes valid
102     RangeSet<int>::Range it = indexes.end();
103     RangeSet<int>::Range begin = indexes.begin();
104     // assert: end != begin, since there is at least one entry
105     do {
106         --it;
107 
108         std::for_each(mIssues.begin() + it.first(),
109                       mIssues.begin() + it.last() + 1,
110                       [this] (const Issue &issue) {
111             switch (issue.severity()) {
112             case Issue::Error: --mErrorCount; break;
113             case Issue::Warning: --mWarningCount; break;
114             }
115         });
116 
117         beginRemoveRows(QModelIndex(), it.first(), it.last());
118         mIssues.remove(it.first(), it.length());
119         endRemoveRows();
120     } while (it != begin);
121 }
122 
clear()123 void IssuesModel::clear()
124 {
125     beginResetModel();
126 
127     mErrorCount = 0;
128     mWarningCount = 0;
129     mIssues.clear();
130 
131     endResetModel();
132 }
133 
rowCount(const QModelIndex & parent) const134 int IssuesModel::rowCount(const QModelIndex &parent) const
135 {
136     return parent.isValid() ? 0 : mIssues.size();
137 }
138 
data(const QModelIndex & index,int role) const139 QVariant IssuesModel::data(const QModelIndex &index, int role) const
140 {
141     switch (role) {
142     case Qt::DisplayRole:
143         return mIssues.at(index.row()).text();
144     case Qt::DecorationRole:
145         switch (mIssues.at(index.row()).severity()) {
146         case Issue::Error:
147             return mErrorIcon;
148         case Issue::Warning:
149             return mWarningIcon;
150         }
151         break;
152     case Qt::BackgroundRole: {
153         switch (mIssues.at(index.row()).severity()) {
154         case Issue::Error:
155             return QColor(253, 0, 69, 32);
156         case Issue::Warning:
157             return QColor(255, 230, 0, 32);
158         }
159         break;
160     }
161     case IssueRole:
162         return QVariant::fromValue(mIssues.at(index.row()));
163     }
164 
165     return QVariant();
166 }
167 
168 } // namespace Tiled
169 
170 #include "moc_issuesmodel.cpp"
171