1 /*
2     SPDX-FileCopyrightText: 2007-2009 Frederik Gladhorn <gladhorn@kde.org>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "practicesummarycomponent.h"
7 #include "prefs.h"
8 
9 #include "parleyactions.h"
10 
11 #include <KActionCollection>
12 #include <KColorScheme>
13 #include <KConfigGroup>
14 #include <KEduVocExpression>
15 #include <KLocalizedString>
16 #include <KMessageBox>
17 #include <KToolBar>
18 #include <QFileDialog>
19 #include <QTableWidgetItem>
20 #include <QTextCursor>
21 #include <QTextDocument>
22 #include <QTextDocumentWriter>
23 #include <QTextTable>
24 
25 using namespace Practice;
26 
27 class PracticeSummaryComponent::SortedAttemptTableWidgetItem : public QTableWidgetItem
28 {
operator <(const QTableWidgetItem & other) const29     bool operator<(const QTableWidgetItem &other) const override
30     {
31         if (data(Qt::DisplayRole).toInt() == other.data(Qt::DisplayRole).toInt()) {
32             return data(Qt::UserRole).toInt() < other.data(Qt::UserRole).toInt();
33         }
34         return data(Qt::DisplayRole).toInt() < other.data(Qt::DisplayRole).toInt();
35     }
36 };
37 
PracticeSummaryComponent(SessionManagerBase * sessionManager,QWidget * parent)38 PracticeSummaryComponent::PracticeSummaryComponent(SessionManagerBase *sessionManager, QWidget *parent)
39     : KXmlGuiWindow(parent)
40     , m_sessionManager(sessionManager)
41 {
42     // KXmlGui
43     setXMLFile(QStringLiteral("practicesummaryui.rc"));
44     setObjectName(QStringLiteral("Statistics"));
45 
46     QWidget *mainWidget = new QWidget(this);
47     setupUi(mainWidget);
48     setCentralWidget(mainWidget);
49 
50     initActions(parent);
51 
52     setupDetailsTable();
53     summaryBar->setStatistics(m_sessionManager->statisticTotalCorrectFirstAttempt(),
54                               m_sessionManager->statisticTotalWrong(),
55                               m_sessionManager->statisticTotalUnanswered());
56 
57     int total = m_sessionManager->statisticTotalCorrectFirstAttempt() + m_sessionManager->statisticTotalWrong();
58     int minutes = m_sessionManager->totalTime() / 60;
59     int seconds = m_sessionManager->totalTime() % 60;
60 
61     testSummaryLabel->setText(i18nc("number of words, minutes, seconds",
62                                     "You practiced %1 in %2 and %3.",
63                                     i18np("one word", "%1 words", total),
64                                     i18np("one minute", "%1 minutes", minutes),
65                                     i18np("one second", "%1 seconds", seconds)));
66 
67     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("parleyrc")), objectName());
68     applyMainWindowSettings(cfg);
69 }
70 
~PracticeSummaryComponent()71 PracticeSummaryComponent::~PracticeSummaryComponent()
72 {
73     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("parleyrc")), objectName());
74     saveMainWindowSettings(cfg);
75 }
76 
initActions(QWidget * parleyMainWindow)77 void PracticeSummaryComponent::initActions(QWidget *parleyMainWindow)
78 {
79     ParleyActions::create(ParleyActions::EnterEditMode, parleyMainWindow, SLOT(showEditor()), actionCollection());
80     ParleyActions::create(ParleyActions::StartPractice, parleyMainWindow, SLOT(showPracticeConfiguration()), actionCollection());
81     ParleyActions::create(ParleyActions::ExportPracticeResults, this, SLOT(exportResults()), actionCollection());
82     actionCollection()->action(QStringLiteral("practice_start"))->setText(i18n("Practice Overview"));
83     actionCollection()->action(QStringLiteral("practice_start"))->setToolTip(i18n("Switch to the Practice Overview page"));
84 }
85 
setupDetailsTable()86 void PracticeSummaryComponent::setupDetailsTable()
87 {
88     tableWidget->setRowCount(m_sessionManager->allEntryCount());
89     tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
90 
91     Qt::ItemFlags flags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
92 
93     KColorScheme scheme(QPalette::Active);
94     QPalette correctPalette = QApplication::palette();
95     correctPalette.setColor(QPalette::WindowText, scheme.foreground(KColorScheme::PositiveText).color());
96     correctPalette.setColor(QPalette::Text, scheme.foreground(KColorScheme::PositiveText).color());
97 
98     QPalette wrongPalette = QApplication::palette();
99     wrongPalette.setColor(QPalette::WindowText, scheme.foreground(KColorScheme::NegativeText).color());
100     wrongPalette.setColor(QPalette::Text, scheme.foreground(KColorScheme::NegativeText).color());
101 
102     int i = 0;
103     // TODO headers with languages
104     // TODO some colors, maybe an indicator icon whether the word was right/wrong
105     const QList<TestEntry *> allTestEntries = m_sessionManager->allTestEntries();
106     for (TestEntry *entry : allTestEntries) {
107         QTableWidgetItem *itemFrom = new QTableWidgetItem(entry->entry()->translation(entry->TestEntry::languageFrom())->text());
108         QTableWidgetItem *itemTo = new QTableWidgetItem(entry->entry()->translation(entry->languageTo())->text());
109         if (entry->statisticGoodCount() > 0) {
110             itemTo->setForeground(correctPalette.windowText());
111         }
112 
113         QTableWidgetItem *itemUserAnswer = new QTableWidgetItem(entry->userAnswers().join(QStringLiteral("; ")));
114         itemUserAnswer->setForeground(wrongPalette.windowText());
115 
116         SortedAttemptTableWidgetItem *itemAttempts = new SortedAttemptTableWidgetItem();
117         itemAttempts->setData(Qt::DisplayRole, entry->statisticCount());
118         itemAttempts->setData(Qt::UserRole, entry->statisticBadCount());
119         itemAttempts->setTextAlignment(Qt::AlignRight);
120 
121         itemFrom->setFlags(flags);
122         itemTo->setFlags(flags);
123         itemUserAnswer->setFlags(flags);
124         itemAttempts->setFlags(flags);
125 
126         if (entry->correctAtFirstAttempt()) {
127             itemUserAnswer->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
128         } else if (entry->statisticGoodCount() > 0) {
129             itemUserAnswer->setIcon(QIcon::fromTheme(QStringLiteral("task-attempt")));
130         } else if (entry->statisticCount() > 0) {
131             itemUserAnswer->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error")));
132         } else {
133             itemUserAnswer->setIcon(QIcon::fromTheme(QStringLiteral("task-attempt")));
134         }
135 
136         tableWidget->setItem(i, 0, itemAttempts);
137         tableWidget->setItem(i, 1, itemFrom);
138         tableWidget->setItem(i, 2, itemTo);
139         tableWidget->setItem(i, 3, itemUserAnswer);
140         ++i;
141     }
142 
143     tableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
144     tableWidget->setSortingEnabled(true);
145     tableWidget->sortItems(0, Qt::DescendingOrder);
146 }
147 
exportResults()148 void PracticeSummaryComponent::exportResults()
149 {
150     QString filter = i18n("HTML Files") + " (*.html);;" + i18n("OpenDocument text files") + " (*.odt)";
151     QString caption;
152     QString startingdir(QStringLiteral("kfiledialog:///practice_export"));
153     QString fileName = QFileDialog::getSaveFileName(0, caption, startingdir, filter);
154 
155     if (fileName.isEmpty()) {
156         return;
157     }
158 
159     QTextDocument doc;
160     doc.setHtml("<html><head><title>" + i18n("Practice results") + "</title></body></html>");
161     QTextCursor cursor(&doc);
162 
163     cursor.insertHtml("<h1>" + m_sessionManager->title() + "</h1><br />");
164 
165     cursor.insertText(i18n("Answered questions: %1\n", m_sessionManager->allEntryCount()));
166     cursor.insertText(i18n("Correct answers: %1\n", m_sessionManager->statisticTotalCorrectFirstAttempt()));
167     cursor.insertText(i18n("Wrong answers: %1\n", m_sessionManager->statisticTotalWrong()));
168 
169     QTextTableFormat tableFormat;
170     tableFormat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
171     tableFormat.setCellPadding(1);
172     tableFormat.setAlignment(Qt::AlignLeft);
173     QTextTable *table = cursor.insertTable(1, 4, tableFormat);
174     table->cellAt(0, 0).firstCursorPosition().insertHtml(i18n("<b>Attempts</b>"));
175     table->cellAt(0, 1).firstCursorPosition().insertHtml(i18n("<b>Question</b>"));
176     table->cellAt(0, 2).firstCursorPosition().insertHtml(i18n("<b>Correct answer</b>"));
177     table->cellAt(0, 3).firstCursorPosition().insertHtml(i18n("<b>Your errors</b>"));
178 
179     const QList<TestEntry *> allTestEntries = m_sessionManager->allTestEntries();
180     for (TestEntry *entry : allTestEntries) {
181         table->appendRows(1);
182         int newRow = table->rows() - 1;
183         table->cellAt(newRow, 0).firstCursorPosition().insertText(QString::number(entry->statisticCount()));
184         table->cellAt(newRow, 1).firstCursorPosition().insertText(entry->entry()->translation(entry->languageFrom())->text());
185         table->cellAt(newRow, 2).firstCursorPosition().insertText(entry->entry()->translation(entry->languageTo())->text());
186         table->cellAt(newRow, 3).firstCursorPosition().insertText(entry->userAnswers().join(QStringLiteral("; ")));
187     }
188 
189     QTextDocumentWriter writer(fileName);
190 
191     if (!writer.write(&doc)) {
192         KMessageBox::error(this, i18n("Could not write to %1", fileName), i18n("Could not write file"));
193         return;
194     }
195 }
196