source: SMSSender/gateways/Schoolnet/gui/ui/models/questiontreemodel.cpp @ 315:e6c8bb244ecd

separation-frontend-backend
Last change on this file since 315:e6c8bb244ecd was 315:e6c8bb244ecd, checked in by Sämy Zehnder <saemy.zehnder@…>, 6 years ago
  • Separates the frontend from the backend code.
  • At this point the schoolnet gateway is converted only.
  • Removes bc & da prefixes from the filenames.
File size: 14.5 KB
Line 
1/*
2 Schoolnet gateway plugin - The smssender plugin for the Schoolnet sms platform.
3 Copyright (C) 2010-2012, gorrión. See http://smssender.gorrion.ch
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18#include "questiontreemodel.h"
19
20#include <QDebug>
21#include <QIcon>
22#include <QMovie>
23
24#include "common/business/iaccount.h"
25#include "gateways/Schoolnet/business/deepthought/deepthoughthelper.h"
26#include "gateways/Schoolnet/business/deepthought/questionmanager.h"
27#include "gateways/Schoolnet/business/account.h"
28#include "gateways/Schoolnet/business/gateway.h"
29
30namespace Schoolnet {
31namespace DeepThought {
32namespace UI {
33
34QuestionTreeModel::QuestionTreeModel(const IAccountIntegrationHelper *accountHelper,
35                                     DeepThoughtHelper *deepThoughtHelper)
36    : QAbstractItemModel()
37    , m_accountHelper(accountHelper)
38    , m_deepThoughtHelper(deepThoughtHelper)
39{
40    hasLoggedInAccounts_ = getLoggedInSchoolnetAccounts().size() > 0;
41    reloadList();
42
43    connect(&m_deepThoughtHelper->questionManager(), SIGNAL(answerCorrectnessProvided(Answer*)),
44            this, SLOT(reloadList()));
45    connect(accountHelper, SIGNAL(initStateChanged(IAccount*)),
46            this, SLOT(onAccountInitStateChanged(IAccount*)));
47}
48
49void QuestionTreeModel::reloadList() {
50    beginResetModel(); {
51        items_.clear();
52        foreach (Question *question, m_deepThoughtHelper->questionManager().questions()) {
53            if (isQuestionStatusAtSchoolnetCheckedAtSomeLoggedInAccount(*question)) {
54                items_.append(question);
55            }
56        }
57    } endResetModel();
58}
59
60void QuestionTreeModel::onAnswerProvidingTaskFinished(STask *task) {
61    Q_ASSERT(loadingAnswers_.contains(task));
62
63    beginResetModel(); {
64        loadingAnswers_.remove(task);
65    } endResetModel();
66}
67
68void QuestionTreeModel::onAccountInitStateChanged(IAccount *account) {
69    Q_UNUSED(account);
70
71    bool newHasLoggedInAccounts = getLoggedInSchoolnetAccounts().size() > 0;
72    if (hasLoggedInAccounts_ != newHasLoggedInAccounts) {
73        beginResetModel(); {
74            hasLoggedInAccounts_ = newHasLoggedInAccounts;
75        } endResetModel();
76    }
77
78    if (hasLoggedInAccounts_) {
79        reloadList(); // Data depends on the logged in accounts
80    }
81}
82
83QVariant QuestionTreeModel::data(const QModelIndex& index, int role) const {
84    if (!index.isValid())
85        return QVariant();
86
87    TreeNode* node = dataObject(index);
88    Q_ASSERT(node);
89    if (node->isQuestion()) {
90        // It's a question
91        const Question *question = node->question();
92
93        switch (role) {
94            case Qt::DisplayRole:
95            case Qt::EditRole:
96                switch (index.column()) {
97                    case ColQuestionText:
98                        return question->text();
99                }
100                break;
101
102            case Qt::DecorationRole:
103                switch (index.column()) {
104                    case ColStatus: {
105                        switch (questionStatus(*question)) {
106                            case qsCanBeAnswered: return QIcon(":/images/question_open.png");
107                            case qsCanNotBeAnsweredAnymore: return QIcon(":/images/question_open_disabled.png");
108                            case qsAnsweredCorrect: return QIcon(":/images/question_correct_disabled.png");
109                            case qsAnsweredWrong: return QIcon(":/images/question_wrong_disabled.png");
110                            case qsLoading: return QIcon(":/images/question_loading.png");
111                        }
112                    }
113                }
114                break;
115
116            case Qt::ToolTipRole:
117                switch (questionStatus(*question)) {
118                    case qsCanBeAnswered: return tr("Unanswered question");
119                    case qsCanNotBeAnsweredAnymore: return tr("Can not be answered anymore (already answered at Schoolnet?)");
120                    case qsAnsweredCorrect: return tr("Correctly answered question");
121                    case qsAnsweredWrong: return tr("Incorrectly answered question");
122                    case qsLoading: return tr("Processing answer proposal");
123                }
124                break;
125
126            case Qt::TextColorRole:
127                if (questionStatus(*question) != qsCanBeAnswered) {
128                    return Qt::gray;
129                }
130                break;
131        }
132
133    } else {
134        // It's an answer
135        const Answer *answer = node->answer();
136
137        switch (role) {
138            case Qt::DisplayRole:
139                switch (index.column()) {
140                    case ColAnswerText:
141                        return answer->text();
142                }
143                break;
144
145            case Qt::CheckStateRole:
146                switch (index.column()) {
147                    case ColAnswerText:
148                        if (loadingAnswers_.values().contains(answer)) {
149                            return Qt::PartiallyChecked;
150                        } else {
151                            return (answer->isCorrect()) ? Qt::Checked : Qt::Unchecked;
152                        }
153                }
154                break;
155
156            case Qt::ToolTipRole:
157                switch (answerStatus(*answer)) {
158                    case asCanBeAnswered: return tr("Unanswered");
159                    case asLoading: return tr("Processing answer proposal");
160                    case asAnsweredWrong: return tr("Incorrect answer");
161                    case asAnsweredCorrect: return tr("Correct answer");
162                    case asCanNotBeAnsweredAnymore: return tr("Question already answered");
163                }
164                break;
165
166            case Qt::TextColorRole:
167                if (answerStatus(*answer) != asCanBeAnswered) {
168                    return Qt::gray;
169                }
170                break;
171
172            case Qt::FontRole:
173                QFont font;
174                if ((index.column() == ColAnswerText) && (answerStatus(*answer) == asAnsweredWrong)) {
175                    font.setStrikeOut(true);
176                }
177                return font;
178        }
179    }
180
181    return QVariant();
182}
183
184QuestionTreeModel::TreeNode* QuestionTreeModel::dataObject(const QModelIndex& index) const {
185    if (!index.isValid() || (index.row() >= items_.size())) {
186        return NULL;
187    } else {
188        return static_cast<TreeNode*>(index.internalPointer());
189    }
190}
191
192Qt::ItemFlags QuestionTreeModel::flags(const QModelIndex& index) const {
193    if (!index.isValid())
194        return Qt::NoItemFlags;
195
196    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
197
198    TreeNode* node = static_cast<TreeNode*>(index.internalPointer());
199    if (node->isQuestion()) {
200        // It's a question
201        const Question &question = *node->question();
202        if ((index.column() == ColQuestionText) && (questionStatus(question) != qsLoading)) {
203            flags |= Qt::ItemIsEditable;
204        }
205    } else {
206        // It's an answer
207        const Answer &answer = *node->answer();
208
209        switch (index.column()) {
210            case ColAnswerText:
211                if (answerStatus(answer) == asCanBeAnswered) {
212                    flags |= Qt::ItemIsUserCheckable;
213                }
214                if (questionStatus(*answer.question()) == qsLoading) {
215                    flags &= !Qt::ItemIsEnabled; // Disable all if one answer is loading
216                }
217                break;
218        }
219    }
220
221    return flags;
222}
223
224bool QuestionTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) {
225    if (!index.isValid())
226        return false;
227
228    TreeNode* node = static_cast<TreeNode*>(index.internalPointer());
229    if (node->isAnswer()) {
230        // It's an answer
231        Answer *answer = node->answer();
232
233        if (role == Qt::CheckStateRole) {
234            if (index.column() == ColAnswerText) {
235                const Qt::CheckState cs = static_cast<Qt::CheckState>(value.toInt());
236                if (cs == Qt::Checked) {
237                    const Account* account = getAccountWhereUnanswered(*answer->question());
238                    if (account) {
239                        beginResetModel(); {
240                            STask* task = m_deepThoughtHelper->proposeAnswer(account, answer);
241                            loadingAnswers_.insert(task, answer);
242                            // Reload the list after the task finished, so that the list gets reloaded even if no new
243                            // knowledge about the answer correctness is gained (Already answered questions).
244                            disconnect(task, SIGNAL(finished(STask*)), this, SLOT(onAnswerProvidingTaskFinished(STask*))); // Remove existing connections
245                            connect(task, SIGNAL(finished(STask*)), this, SLOT(onAnswerProvidingTaskFinished(STask*)), Qt::DirectConnection); // Add new connection
246                        } endResetModel();
247                    }
248                }
249
250                return true;
251            }
252        }
253    }
254
255    return false;
256}
257
258QModelIndex QuestionTreeModel::index(int row, int column, const QModelIndex& parent) const {
259    if (!hasIndex(row, column, parent))
260        return QModelIndex();
261
262    if (!parent.isValid()) {
263        // It's a question
264        return createIndex(row, column, new TreeNode(items_.at(row)));
265    } else {
266        // It's an answer
267        const Question *question = static_cast<TreeNode*>(parent.internalPointer())->question();
268        return createIndex(row, column, new TreeNode(question->answers().at(row)));
269    }
270}
271
272QModelIndex QuestionTreeModel::parent(const QModelIndex& index) const {
273    if (!index.isValid())
274        return QModelIndex();
275
276    TreeNode* node = static_cast<TreeNode*>(index.internalPointer());
277    if (node->isQuestion()) {
278        // It's a question
279        return QModelIndex();
280    } else {
281        // It's an answer
282        const Answer *answer = node->answer();
283        return this->index(items_.indexOf(answer->question()), 0);
284    }
285}
286
287int QuestionTreeModel::columnCount(const QModelIndex& parent) const {
288    if (!parent.isValid()) {
289        return MaxQuestionCol+1;
290    } else {
291        TreeNode* node = static_cast<TreeNode*>(parent.internalPointer());
292        if (node->isQuestion()) {
293            return MaxAnswerCol+1;
294        } else {
295            return 0;
296        }
297    }
298}
299
300int QuestionTreeModel::rowCount(const QModelIndex& parent) const {
301    if (!hasLoggedInAccounts_) {
302        return 0;
303    }
304
305    if (!parent.isValid()) {
306        return items_.size();
307    } else {
308        TreeNode* node = static_cast<TreeNode*>(parent.internalPointer());
309        if (node->isQuestion()) {
310            return node->question()->answers().size();
311        } else {
312            return 0;
313        }
314    }
315}
316
317QuestionTreeModel::QuestionStatus QuestionTreeModel::questionStatus(const Question& question) const {
318    if (question.hasCorrectAnswer()) {
319        return qsAnsweredCorrect;
320    } else if (!isQuestionUnansweredAtSomeLoggedInAccount(question)) {
321        foreach (const Answer *answer, question.answers()) {
322            if (answer->isAnswered() && !answer->isCorrect()) {
323                return qsAnsweredWrong;
324            }
325        }
326        return qsCanNotBeAnsweredAnymore;
327    } else {
328        foreach (const Answer *answer, question.answers()) {
329            if (loadingAnswers_.values().contains(answer)) {
330                return qsLoading;
331            }
332        }
333
334        return qsCanBeAnswered;
335    }
336}
337
338QuestionTreeModel::AnswerStatus QuestionTreeModel::answerStatus(const Answer& answer) const {
339    if (answer.isAnswered()) {
340        return answer.isCorrect() ? asAnsweredCorrect : asAnsweredWrong;
341    } else if (loadingAnswers_.values().contains(&answer)) {
342        return asLoading;
343    } else if (!isQuestionUnansweredAtSomeLoggedInAccount(*answer.question())) {
344        return asCanNotBeAnsweredAnymore;
345    } else if (questionStatus(*answer.question()) == qsLoading) {
346        return asCanNotBeAnsweredAnymore; // an other answer is loading
347    } else {
348        return asCanBeAnswered;
349    }
350}
351
352bool QuestionTreeModel::isQuestionStatusAtSchoolnetCheckedAtSomeLoggedInAccount(const Question &question) const {
353    foreach (Account* account, m_deepThoughtHelper->questionManager().getAccountsWhereQuestionStatusAtSchoolnetChecked(question)) {
354        if (account->isLoggedIn()) {
355            return true;
356        }
357    }
358    return false;
359}
360
361bool QuestionTreeModel::isQuestionUnansweredAtSomeLoggedInAccount(const Question &question) const {
362    foreach (Account* account, m_deepThoughtHelper->questionManager().getAccountsWhereQuestionUnanswered(question)) {
363        if (account->isLoggedIn()) {
364            return true;
365        }
366    }
367    return false;
368}
369
370QSet<Account *> QuestionTreeModel::getLoggedInSchoolnetAccounts() const {
371    QSet<Account *> ret;
372
373    foreach (IAccount* account, m_accountHelper->accounts()) {
374        Account *acc = static_cast<Account*>(account);
375        if (acc->isLoggedIn()) {
376            ret.insert(acc);
377        }
378    }
379    return ret;
380}
381
382/**
383  * Returns an account where the question of the given question is still unanswered.
384  */
385Account *QuestionTreeModel::getAccountWhereUnanswered(const Question &question) {
386    foreach (Account *account, getLoggedInSchoolnetAccounts()) {
387        if (!m_deepThoughtHelper->questionManager().isQuestionAnsweredAtSchoolnet(*account, question)) {
388            return account;
389        }
390    }
391    return NULL;
392}
393
394/***********************************************/
395
396QuestionTreeModel::TreeNode::TreeNode(Question *question)
397    : m_question(question)
398    , m_answer(NULL)
399{}
400QuestionTreeModel::TreeNode::TreeNode(Answer *answer)
401    : m_question(NULL)
402    , m_answer(answer)
403{}
404QuestionTreeModel::TreeNode::TreeNode(const TreeNode& other)
405    : m_question(other.m_question)
406    , m_answer(other.m_answer)
407{}
408
409
410Question *QuestionTreeModel::TreeNode::question() const {
411    Q_ASSERT(m_question);
412    return m_question;
413}
414Answer *QuestionTreeModel::TreeNode::answer() const {
415    Q_ASSERT(m_answer);
416    return m_answer;
417}
418
419bool QuestionTreeModel::TreeNode::isQuestion() const {
420    return m_question;
421}
422bool QuestionTreeModel::TreeNode::isAnswer() const {
423    return m_answer;
424}
425
426} // namespace UI
427} // namespace DeepThought
428} // namespace Schoolnet
Note: See TracBrowser for help on using the repository browser.