File indexing completed on 2022-11-22 13:00:59

0001 /*
0002     SPDX-FileCopyrightText: 2014 Inge Wallin <inge@lysator.liu.se>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "dashboard.h"
0007 
0008 #include <QAction>
0009 #include <QDebug>
0010 #include <QMessageBox>
0011 #include <QStandardItemModel>
0012 #include <QTimer>
0013 
0014 //#include <KMimeType>
0015 #include <KActionCollection>
0016 #include <KNSWidgets/Button>
0017 
0018 #include "../utils.h"
0019 #include "buttondelegate.h"
0020 #include "parleydocument.h"
0021 #include "parleymainwindow.h"
0022 #include "practice/imagewidget.h"
0023 #include "practice/themedbackgroundrenderer.h"
0024 #include "statistics/statisticsmodel.h"
0025 
0026 #include "collection.h"
0027 #include "collectionwidget.h"
0028 #include "gradereferencewidget.h"
0029 
0030 // ================================================================
0031 //                         class Dashboard
0032 
0033 int ROWSIZE = 4; // Number of collection widgets (+ 1 initial spacerItem) per row
0034 
0035 Dashboard::Dashboard(ParleyMainWindow *parent)
0036     : KXmlGuiWindow(parent)
0037     , m_mainWindow(parent)
0038 {
0039     // KXmlGui
0040     setXMLFile(QStringLiteral("dashboardui.rc"));
0041     setObjectName(QStringLiteral("Dashboard"));
0042 
0043     m_widget = new Practice::ImageWidget(this);
0044 
0045     m_widget->setScalingEnabled(false, false);
0046     m_widget->setKeepAspectRatio(Qt::IgnoreAspectRatio);
0047     m_widget->setFadingEnabled(false);
0048 
0049     m_ui.setupUi(m_widget);
0050     setCentralWidget(m_widget);
0051 
0052     QFont font = m_ui.recentLabel->font();
0053     font.setBold(true);
0054     m_ui.recentLabel->setFont(font);
0055     font = m_ui.completedLabel->font();
0056     font.setBold(true);
0057     m_ui.completedLabel->setFont(font);
0058 
0059     m_ui.newButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0060     m_ui.openButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0061     m_ui.ghnsButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")));
0062 
0063     GradeReferenceWidget *gradeReferenceWidget = new GradeReferenceWidget();
0064     gradeReferenceWidget->setMinimumSize(m_widget->width(), 50);
0065     m_ui.gridLayout->addWidget(gradeReferenceWidget, 1, 0, 1, ROWSIZE, Qt::AlignCenter);
0066 
0067     m_subGridLayout = new QGridLayout();
0068     m_subGridLayout->setHorizontalSpacing(50);
0069     m_subGridLayout->setVerticalSpacing(30);
0070     m_ui.gridLayout_2->addLayout(m_subGridLayout, 2, 0, 1, 1);
0071 
0072     m_completedGridLayout = new QGridLayout();
0073     m_completedGridLayout->setHorizontalSpacing(50);
0074     m_completedGridLayout->setVerticalSpacing(30);
0075     m_ui.gridLayout_2->addLayout(m_completedGridLayout, 6, 0, 1, 1);
0076 
0077     populateMap();
0078     populateGrid();
0079 
0080     // Signals from the main buttons.
0081     ParleyDocument *doc = m_mainWindow->parleyDocument();
0082     connect(m_ui.newButton, &QAbstractButton::clicked, m_mainWindow, &ParleyMainWindow::slotFileNew);
0083     connect(m_ui.openButton, &QAbstractButton::clicked, doc, &ParleyDocument::slotFileOpen);
0084     connect(m_ui.ghnsButton, &KNSWidgets::Button::dialogFinished, doc, &ParleyDocument::slotGHNS);
0085     m_ui.ghnsButton->setConfigFile("parley.knsrc");
0086 
0087     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("parleyrc")), objectName());
0088     applyMainWindowSettings(cfg);
0089 
0090     m_themedBackgroundRenderer = new Practice::ThemedBackgroundRenderer(this, QStringLiteral("startpagethemecache.bin"));
0091 
0092     // Set theme and prepare for future theme changes.
0093     connect(Prefs::self(), &KCoreConfigSkeleton::configChanged, this, &Dashboard::setTheme);
0094     setTheme();
0095 
0096     connect(m_themedBackgroundRenderer, &Practice::ThemedBackgroundRenderer::backgroundChanged, this, &Dashboard::backgroundChanged);
0097     connect(m_widget, &Practice::ImageWidget::sizeChanged, this, &Dashboard::updateBackground);
0098 
0099     QAction *updateAction = new QAction(this);
0100     updateAction->connect(updateAction, &QAction::triggered, this, &Dashboard::updateWidgets);
0101     actionCollection()->addAction(QStringLiteral("update_dashboard"), updateAction);
0102     actionCollection()->setDefaultShortcut(updateAction, QKeySequence(Qt::Key_F5));
0103 }
0104 
0105 Dashboard::~Dashboard()
0106 {
0107     KConfigGroup cfg(KSharedConfig::openConfig(QStringLiteral("parleyrc")), objectName());
0108     saveMainWindowSettings(cfg);
0109 }
0110 
0111 void Dashboard::clearGrid()
0112 {
0113     remove(m_subGridLayout, m_subGridLayout->rowCount() - 1, m_subGridLayout->columnCount() - 1, true);
0114     remove(m_completedGridLayout, m_completedGridLayout->rowCount() - 1, m_completedGridLayout->columnCount() - 1, true);
0115 }
0116 
0117 /**
0118  * Helper function. Removes all layout items within the given @a layout
0119  * which either span the given @a row or @a column. If @a deleteWidgets
0120  * is true, all concerned child widgets become not only removed from the
0121  * layout, but also deleted.
0122  */
0123 void Dashboard::remove(QGridLayout *layout, int row, int column, bool deleteWidgets)
0124 {
0125     // We avoid usage of QGridLayout::itemAtPosition() here to improve performance.
0126     for (int i = layout->count() - 1; i >= 0; i--) {
0127         int r, c, rs, cs;
0128         layout->getItemPosition(i, &r, &c, &rs, &cs);
0129         if ((r + rs - 1 <= row) || (c + cs - 1 <= column)) {
0130             // This layout item is subject to deletion.
0131             QLayoutItem *item = layout->takeAt(i);
0132             if (deleteWidgets) {
0133                 deleteChildWidgets(item);
0134             }
0135             delete item;
0136         }
0137     }
0138 }
0139 
0140 /**
0141  * Helper function. Deletes all child widgets of the given layout @a item.
0142  */
0143 void Dashboard::deleteChildWidgets(QLayoutItem *item)
0144 {
0145     if (item->layout()) {
0146         // Process all child items recursively.
0147         for (int i = 0; i < item->layout()->count(); i++) {
0148             deleteChildWidgets(item->layout()->itemAt(i));
0149         }
0150     }
0151     delete item->widget();
0152 }
0153 
0154 void Dashboard::populateMap()
0155 {
0156     KConfig parleyConfig(QStringLiteral("parleyrc"));
0157     KConfigGroup recentFilesGroup(&parleyConfig, "Recent Files");
0158     for (int i = recentFilesGroup.keyList().count() / 2; i > 0; i--) {
0159         QString urlString = recentFilesGroup.readPathEntry("File" + QString::number(i), QString());
0160         QString nameString = recentFilesGroup.readEntry("Name" + QString::number(i), QString());
0161         m_recentFilesMap.insert(urlString, nameString);
0162     }
0163 }
0164 
0165 void Dashboard::populateGrid()
0166 {
0167     int j = 0, k = 0, jc = 0, kc = 0;
0168 
0169     // key = url, value = title
0170     QMapIterator<QString, QString> it(m_recentFilesMap);
0171     while (it.hasNext()) {
0172         it.next();
0173         QString urlString = it.key();
0174 
0175         QUrl url(QUrl::fromLocalFile(urlString));
0176         Collection *collection = new Collection(&url, this);
0177 
0178         WordCount dueWords;
0179         int percentageCompleted = dueWords.percentageCompleted();
0180 
0181         m_urlArray[k] = url;
0182         if (percentageCompleted != 100) {
0183             if (j % ROWSIZE == 0) {
0184                 m_subGridLayout->addItem(new QSpacerItem(50, 1), j / ROWSIZE, 0);
0185                 j++;
0186             }
0187         } else {
0188             if (jc % ROWSIZE == 0) {
0189                 m_completedGridLayout->addItem(new QSpacerItem(50, 1), jc / ROWSIZE, 0);
0190                 jc++;
0191             }
0192         }
0193 
0194         CollectionWidget *backWidget = new CollectionWidget(collection, &dueWords, this);
0195         m_collectionWidgets.append(backWidget);
0196         if (percentageCompleted != 100) {
0197             backWidget->setFixedSize(COLLWIDTH, COLLHEIGHT1);
0198             backWidget->setMinimumSize(COLLWIDTH, COLLHEIGHT1);
0199             m_subGridLayout->addWidget(backWidget, j / ROWSIZE, j % ROWSIZE);
0200         } else {
0201             backWidget->setFixedSize(COLLWIDTH, COLLHEIGHT2);
0202             backWidget->setMinimumSize(COLLWIDTH, COLLHEIGHT2);
0203             m_completedGridLayout->addWidget(backWidget, jc / ROWSIZE, jc % ROWSIZE);
0204         }
0205 
0206         connect(backWidget, &CollectionWidget::practiceButtonClicked, this, [=] {
0207             slotPracticeButtonClicked(urlString);
0208         });
0209         connect(backWidget, &CollectionWidget::removeButtonClicked, this, [=] {
0210             slotRemoveButtonClicked(urlString);
0211         });
0212 
0213         if (percentageCompleted != 100) {
0214             j++;
0215         } else {
0216             jc++;
0217             kc++;
0218         }
0219 
0220         k++;
0221     }
0222 
0223     m_count = k;
0224     m_completedGridLayout->addItem(new QSpacerItem(50, 1, QSizePolicy::Expanding, QSizePolicy::Fixed),
0225                                    m_completedGridLayout->rowCount() - 1,
0226                                    m_completedGridLayout->columnCount());
0227     m_subGridLayout->addItem(new QSpacerItem(50, 1, QSizePolicy::Expanding, QSizePolicy::Fixed),
0228                              m_subGridLayout->rowCount() - 1,
0229                              m_subGridLayout->columnCount());
0230     if (k - kc) {
0231         m_ui.recentLabel->setText(i18n("Active Collections"));
0232     } else {
0233         m_ui.recentLabel->clear();
0234     }
0235     if (kc) {
0236         m_ui.completedLabel->setText(i18n("Completed Collections"));
0237     } else {
0238         m_ui.completedLabel->clear();
0239     }
0240 }
0241 
0242 void Dashboard::statisticsHandler(const QUrl &url)
0243 {
0244 #if 1
0245     Q_UNUSED(url);
0246 #else
0247     if (!m_mainWindow->parleyDocument()->open(url)) {
0248         return;
0249     }
0250 
0251     // Find due words. TODO find a better way.
0252     m_mainWindow->m_sessionManager.setDocument(m_mainWindow->parleyDocument()->document());
0253 
0254     qDebug() << "Session Manager, allEntryCount=" << m_mainWindow->m_sessionManager.allDueEntryCount();
0255     statisticsWidget->setDocument(m_mainWindow->parleyDocument()->document());
0256 
0257     // Find the percentage completion, to categorize as active or completed collection.
0258     QModelIndex index = statisticsWidget->statisticsModel()->index(0, 2, QModelIndex());
0259     qDebug() << "Percentage:" << index.data(StatisticsModel::TotalPercent).toInt();
0260 #endif
0261 }
0262 
0263 void Dashboard::slotOpenUrl(const QUrl &url)
0264 {
0265     if (!m_mainWindow->parleyDocument()->open(url)) {
0266         return;
0267     }
0268     m_mainWindow->showEditor();
0269 }
0270 
0271 void Dashboard::slotPracticeButtonClicked(const QString &urlString)
0272 {
0273     // qDebug() << urlString;
0274     QUrl url(QUrl::fromLocalFile(urlString));
0275     m_openUrl = url;
0276     QTimer::singleShot(0, this, &Dashboard::slotDoubleClickOpen);
0277 }
0278 
0279 void Dashboard::slotRemoveButtonClicked(const QString &urlString)
0280 {
0281     qDebug() << urlString;
0282 
0283     QMessageBox::StandardButton reply;
0284     reply = QMessageBox::question(this, i18n("Remove"), i18n("Are you sure you want to remove this collection?"), QMessageBox::Yes | QMessageBox::No);
0285     if (reply == QMessageBox::Yes) {
0286         m_recentFilesMap.remove(urlString);
0287         m_mainWindow->removeRecentFile(QUrl::fromLocalFile(urlString));
0288         clearGrid();
0289         populateGrid();
0290     }
0291 }
0292 
0293 void Dashboard::slotDoubleClickOpen()
0294 {
0295     slotPracticeUrl(m_openUrl);
0296 }
0297 
0298 void Dashboard::slotPracticeUrl(const QUrl &url)
0299 {
0300     if (!m_mainWindow->parleyDocument()->open(url)) {
0301         return;
0302     }
0303 
0304     // This used to go to the practice configuration but both I and
0305     // some users wanted to go directly to the practice so I'm testing
0306     // out this for a while.
0307     m_mainWindow->showPracticeConfiguration();
0308     // m_mainWindow->showPractice();
0309 }
0310 
0311 void Dashboard::backgroundChanged(const QPixmap &pixmap)
0312 {
0313     m_widget->setPixmap(pixmap);
0314 }
0315 
0316 void Dashboard::setTheme()
0317 {
0318     m_themedBackgroundRenderer->setTheme(Prefs::theme());
0319     updateFontColors();
0320     updateBackground();
0321     m_widget->setContentsMargins(m_themedBackgroundRenderer->contentMargins());
0322 }
0323 
0324 void Dashboard::updateWidgets()
0325 {
0326     for (CollectionWidget *cw : qAsConst(m_collectionWidgets)) {
0327         cw->updateDue();
0328     }
0329 }
0330 void Dashboard::updateFontColors()
0331 {
0332     QPalette p(QApplication::palette());
0333     QColor c = m_themedBackgroundRenderer->fontColor(QStringLiteral("Start"), p.color(QPalette::Active, QPalette::WindowText));
0334     p.setColor(QPalette::Base, Qt::transparent);
0335     p.setColor(QPalette::Text, c);
0336     p.setColor(QPalette::WindowText, c);
0337     m_widget->setPalette(p);
0338 }
0339 
0340 void Dashboard::updateBackground()
0341 {
0342     m_themedBackgroundRenderer->clearRects();
0343     m_themedBackgroundRenderer->addRect(QStringLiteral("startbackground"), QRect(QPoint(), m_widget->size()));
0344     QPixmap pixmap = m_themedBackgroundRenderer->getScaledBackground();
0345     if (!pixmap.isNull()) {
0346         m_widget->setPixmap(pixmap);
0347     }
0348     m_themedBackgroundRenderer->updateBackground();
0349 }