1 /*
2 Copyright (c) 2020, Lukas Holecek <hluk@email.cz>
3
4 This file is part of CopyQ.
5
6 CopyQ is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 CopyQ is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "actiontablemodel.h"
21
22 #include "common/action.h"
23 #include "common/actionhandlerenums.h"
24
25 #include <QColor>
26
27 #include <algorithm>
28
29 namespace {
30
31 constexpr auto dateTimeFormat = "yyyy-MM-dd HH:mm:ss.zzz";
32
actionStateToString(ActionState state)33 QString actionStateToString(ActionState state)
34 {
35 switch (state) {
36 case ActionState::Error: return "Error";
37 case ActionState::Finished: return "Finished";
38 case ActionState::Running: return "Running";
39 case ActionState::Starting: return "Starting";
40 }
41 return QString();
42 }
43
actionStateOrder(ActionState state)44 int actionStateOrder(ActionState state)
45 {
46 switch (state) {
47 case ActionState::Error: return 1;
48 case ActionState::Finished: return 0;
49 case ActionState::Running: return 2;
50 case ActionState::Starting: return 3;
51 }
52 return -1;
53 }
54
55 } // namespace
56
ActionTableModel(uint maxRowCount,QObject * parent)57 ActionTableModel::ActionTableModel(uint maxRowCount, QObject *parent)
58 : QAbstractTableModel(parent)
59 , m_maxRowCount(maxRowCount)
60 {
61 }
62
actionAboutToStart(Action * action)63 int ActionTableModel::actionAboutToStart(Action *action)
64 {
65 ActionData actionData;
66 actionData.id = m_actions.empty() ? 1 : m_actions[m_actions.size() - 1].id + 1;
67 actionData.name = action->name();
68 if ( actionData.name.isEmpty() )
69 actionData.name = action->commandLine();
70 actionData.finished = -1;
71
72 limitItems();
73
74 beginInsertRows(QModelIndex(), actionCount(), actionCount());
75 m_actions.push_back(actionData);
76 endInsertRows();
77
78 return actionData.id;
79 }
80
actionStarted(Action * action)81 void ActionTableModel::actionStarted(Action *action)
82 {
83 const int row = rowFor(action);
84 actionData(row).started = QDateTime::currentDateTime();
85 for (const int column : { ActionHandlerColumn::started, ActionHandlerColumn::status }) {
86 const auto index = this->index(row, column);
87 emit dataChanged(index, index);
88 }
89 }
90
actionFailed(Action * action,const QString & error)91 void ActionTableModel::actionFailed(Action *action, const QString &error)
92 {
93 const int row = rowFor(action);
94 actionData(row).error = error;
95 for (const int column : { ActionHandlerColumn::error, ActionHandlerColumn::status }) {
96 const auto index = this->index(row, column);
97 emit dataChanged(index, index);
98 }
99 }
100
actionFinished(Action * action)101 void ActionTableModel::actionFinished(Action *action)
102 {
103 const int row = rowFor(action);
104 ActionData &data = actionData(row);
105 data.finished = data.started.msecsTo(QDateTime::currentDateTime());
106 for (const int column : { ActionHandlerColumn::finished, ActionHandlerColumn::status }) {
107 const auto index = this->index(row, column);
108 emit dataChanged(index, index);
109 }
110 }
111
actionFinished(const QString & name)112 void ActionTableModel::actionFinished(const QString &name)
113 {
114 ActionData actionData;
115 actionData.id = m_actions.empty() ? 1 : m_actions.end()->id + 1;
116 actionData.name = name;
117 actionData.started = QDateTime::currentDateTime();
118 actionData.finished = 0;
119
120 limitItems();
121
122 beginInsertRows(QModelIndex(), actionCount(), actionCount());
123 m_actions.push_back(actionData);
124 endInsertRows();
125 }
126
headerData(int section,Qt::Orientation orientation,int role) const127 QVariant ActionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
128 {
129 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
130 switch (section) {
131 case ActionHandlerColumn::id:
132 return "ID";
133 case ActionHandlerColumn::name:
134 return "Name";
135 case ActionHandlerColumn::status:
136 return "Status";
137 case ActionHandlerColumn::started:
138 return "Started";
139 case ActionHandlerColumn::finished:
140 return "Finished";
141 case ActionHandlerColumn::error:
142 return "Error";
143 }
144 }
145
146 return QVariant();
147 }
148
rowCount(const QModelIndex & parent) const149 int ActionTableModel::rowCount(const QModelIndex &parent) const
150 {
151 if (parent.isValid())
152 return 0;
153
154 return actionCount();
155 }
156
columnCount(const QModelIndex & parent) const157 int ActionTableModel::columnCount(const QModelIndex &parent) const
158 {
159 if (parent.isValid())
160 return 0;
161
162 return ActionHandlerColumn::count;
163 }
164
data(const QModelIndex & index,int role) const165 QVariant ActionTableModel::data(const QModelIndex &index, int role) const
166 {
167 if (!index.isValid())
168 return QVariant();
169
170 if (role == Qt::DisplayRole || role == Qt::EditRole) {
171 const int row = index.row();
172 const int column = index.column();
173 const ActionData &data = actionData(row);
174 switch (column) {
175 case ActionHandlerColumn::id:
176 return row;
177 case ActionHandlerColumn::name:
178 return data.name;
179 case ActionHandlerColumn::status:
180 return actionStateToString(actionState(data));
181 case ActionHandlerColumn::started:
182 return data.started.toString(dateTimeFormat);
183 case ActionHandlerColumn::finished:
184 return data.finished == -1 ? QString() : data.started.addMSecs(data.finished).toString(dateTimeFormat);
185 case ActionHandlerColumn::error:
186 return data.error;
187 }
188 } else if (role == Qt::ToolTipRole) {
189 const int row = index.row();
190 const int column = index.column();
191 const ActionData &data = actionData(row);
192 if (column == ActionHandlerColumn::name)
193 return data.name;
194 if (column == ActionHandlerColumn::error)
195 return data.error;
196 } else if (role == Qt::DecorationRole) {
197 const int column = index.column();
198 if (column == ActionHandlerColumn::status) {
199 const int row = index.row();
200 const ActionData &data = actionData(row);
201 switch (actionState(data)) {
202 case ActionState::Error: return QColor(0xe56950);
203 case ActionState::Running: return QColor(0xcce550);
204 case ActionState::Starting: return QColor(0xe5b350);
205 case ActionState::Finished: break;
206 }
207 }
208 } else if (role >= Qt::UserRole) {
209 if (role == ActionHandlerRole::sort) {
210 const int row = index.row();
211 const int column = index.column();
212 const ActionData &data = actionData(row);
213 switch (column) {
214 case ActionHandlerColumn::id:
215 return data.id;
216 case ActionHandlerColumn::name:
217 return data.name;
218 case ActionHandlerColumn::status:
219 return actionStateOrder(actionState(data));
220 case ActionHandlerColumn::started:
221 return data.started;
222 case ActionHandlerColumn::finished:
223 return data.finished;
224 case ActionHandlerColumn::error:
225 return data.error;
226 }
227 } else if (role == ActionHandlerRole::status) {
228 const int row = index.row();
229 const ActionData &data = actionData(row);
230 return static_cast<int>(actionState(data));
231 } else if (role == ActionHandlerRole::id) {
232 const int row = index.row();
233 const ActionData &data = actionData(row);
234 return data.id;
235 }
236 }
237
238 return QVariant();
239 }
240
actionState(const ActionTableModel::ActionData & data)241 ActionState ActionTableModel::actionState(const ActionTableModel::ActionData &data)
242 {
243 if ( !data.error.isEmpty() )
244 return ActionState::Error;
245 if ( data.finished != -1 )
246 return ActionState::Finished;
247 if ( data.started.isValid() )
248 return ActionState::Running;
249 return ActionState::Starting;
250 }
251
rowFor(const Action * action) const252 int ActionTableModel::rowFor(const Action *action) const
253 {
254 const auto found = std::lower_bound(
255 std::begin(m_actions), std::end(m_actions), action->id(),
256 [](const ActionData &data, int id) {
257 return data.id < id;
258 });
259 const auto row = std::distance(std::begin(m_actions), found);
260 return static_cast<int>(row);
261 }
262
limitItems()263 void ActionTableModel::limitItems()
264 {
265 if (m_actions.size() < m_maxRowCount * 4 / 3)
266 return;
267
268 while (m_actions.size() >= m_maxRowCount) {
269 const auto found = std::find_if(
270 std::begin(m_actions), std::end(m_actions),
271 [](const ActionData &data) {
272 const auto state = actionState(data);
273 return state == ActionState::Finished || state == ActionState::Error;
274 });
275
276 if ( found == std::end(m_actions) )
277 break;
278
279 const auto row = static_cast<int>( std::distance(std::begin(m_actions), found) );
280 beginRemoveRows(QModelIndex(), row, row);
281 m_actions.erase(found);
282 endRemoveRows();
283 }
284 }
285