File indexing completed on 2024-04-28 04:05:20

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "selectboardwindow.h"
0008 
0009 #include <KLocalizedString>
0010 #include <QAbstractTableModel>
0011 #include <QDialogButtonBox>
0012 #include <QPushButton>
0013 #include <QVBoxLayout>
0014 #include <QtAlgorithms>
0015 #include <assert.h>
0016 
0017 #include "src/logic/elapsedtime.h"
0018 #include "src/logic/levelloader.h"
0019 
0020 static QString diffString(const int difficulty);
0021 
0022 class LevelTableModel : public QAbstractTableModel
0023 {
0024 public:
0025     LevelTableModel(QList<QSharedPointer<Level> > &levels, QObject * parent = nullptr);
0026 
0027     enum Columns {
0028         Name,
0029         LevelSet,
0030         Difficulty,
0031         Size,
0032         Solved,
0033         ColumnCount /* not a real column */
0034     };
0035 
0036     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0037     int columnCount(const QModelIndex &parent = QModelIndex()) const override;
0038     QVariant data(const QModelIndex &index, int role) const override;
0039     QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
0040     void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
0041 
0042 private:
0043     QList<QSharedPointer<Level> > &m_levels;
0044 };
0045 
0046 LevelTableModel::LevelTableModel(QList<QSharedPointer<Level> > &levels, QObject *parent) :
0047     QAbstractTableModel(parent), m_levels(levels)
0048 {
0049 
0050 }
0051 
0052 int LevelTableModel::rowCount(const QModelIndex &parent) const {
0053     Q_UNUSED(parent);
0054     return m_levels.size();
0055 }
0056 
0057 int LevelTableModel::columnCount(const QModelIndex &parent) const {
0058     Q_UNUSED(parent);
0059     return ColumnCount;
0060 }
0061 
0062 QVariant LevelTableModel::data(const QModelIndex &index, int role) const {
0063     if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::TextAlignmentRole)) {
0064              return QVariant();
0065     }
0066 
0067     if (role == Qt::TextAlignmentRole) {
0068         return Qt::AlignCenter;
0069     }
0070 
0071     QSharedPointer<Level> level= m_levels[index.row()];
0072     switch (index.column()) {
0073     case Name: return level->visibleName();
0074     case LevelSet: return level->levelset();
0075     case Difficulty: return diffString(level->difficulty());
0076     case Size: return QStringLiteral("%1x%2").arg(level->width()).arg(level->height());
0077     case Solved: return (level->solved()) ? Time(level->solvedTime()).toString() : QStringLiteral("-");
0078     default: assert(0);
0079     }
0080 
0081     return QVariant();
0082 }
0083 
0084 QVariant LevelTableModel::headerData(int section, Qt::Orientation orientation, int role) const {
0085     if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
0086         switch (section) {
0087         case Name: return QVariant(ki18n("Level").toString());
0088         case LevelSet: return QVariant(ki18n("Level Set").toString());
0089         case Difficulty: return QVariant(ki18n("Difficulty").toString());
0090         case Size: return QVariant(ki18n("Size").toString());
0091         case Solved: return QVariant(ki18nc("board solved", "Solved").toString());
0092         default: assert(0);
0093         }
0094     }
0095     return QAbstractTableModel::headerData(section, orientation, role);
0096 }
0097 
0098 typedef bool (*LevelComparator)(const QSharedPointer<Level> &,
0099                                 const QSharedPointer<Level> &);
0100 
0101 static bool levelLessThan(const QSharedPointer<Level> &lhs,
0102                           const QSharedPointer<Level> &rhs) {
0103     return lhs->name() < rhs->name();
0104 }
0105 
0106 static bool levelGreaterThan(const QSharedPointer<Level> &lhs,
0107                              const QSharedPointer<Level> &rhs) {
0108     return levelLessThan(rhs, lhs);
0109 }
0110 
0111 static bool diffLessThan(const QSharedPointer<Level> &lhs,
0112                          const QSharedPointer<Level> &rhs) {
0113     return lhs->difficulty() < rhs->difficulty();
0114 }
0115 
0116 static bool diffGreaterThan(const QSharedPointer<Level> &lhs,
0117                             const QSharedPointer<Level> &rhs) {
0118     return diffLessThan(rhs, lhs);
0119 }
0120 
0121 static bool solvedLessThan(const QSharedPointer<Level> &lhs,
0122                            const QSharedPointer<Level> &rhs) {
0123     if (!lhs->solved() && !rhs->solved()) {
0124         return false;
0125     } else if (!lhs->solved() || !rhs->solved()) {
0126         return lhs->solved();
0127     } else {
0128         return (lhs->solvedTime() < rhs->solvedTime());
0129     }
0130 }
0131 
0132 static bool solvedGreaterThan(const QSharedPointer<Level> &lhs,
0133                               const QSharedPointer<Level> &rhs) {
0134     return solvedLessThan(rhs, lhs);
0135 }
0136 
0137 void LevelTableModel::sort(int column, Qt::SortOrder order) {
0138     LevelComparator cmp = NULL;
0139     switch (column) {
0140     case Name: cmp = (order == Qt::AscendingOrder) ? levelLessThan : levelGreaterThan; break;
0141     case Difficulty: cmp = (order == Qt::AscendingOrder) ? diffLessThan : diffGreaterThan; break;
0142     case Solved: cmp = (order == Qt::AscendingOrder) ? solvedLessThan : solvedGreaterThan; break;
0143     default: assert(0);
0144     }
0145 
0146     std::stable_sort(m_levels.begin(), m_levels.end(), cmp);
0147     Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, columnCount() -1));
0148 }
0149 
0150 SelectBoardWindow::SelectBoardWindow(QWidget *parent)
0151     : QDialog(parent)
0152 {
0153     setModal(true);
0154     setWindowTitle(i18nc("@title:window", "Level Selection"));
0155     QVBoxLayout *mainLayout = new QVBoxLayout;
0156     setLayout(mainLayout);
0157     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0158     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0159     okButton->setDefault(true);
0160     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0161     connect(buttonBox, &QDialogButtonBox::accepted, this, &SelectBoardWindow::accept);
0162     connect(buttonBox, &QDialogButtonBox::rejected, this, &SelectBoardWindow::reject);
0163     mainLayout->addWidget(buttonBox);
0164 
0165     QWidget *mainWidget = new QWidget(this);
0166     ui = new Ui::LevelSelectUi;
0167     ui->setupUi(mainWidget);
0168     mainLayout->addWidget(mainWidget);
0169     mainLayout->addWidget(buttonBox);
0170 
0171     m_levels = LevelLoader::load();
0172     m_model = QSharedPointer<LevelTableModel>(new LevelTableModel(m_levels));
0173 
0174     ui->tableView->setUpdatesEnabled(false);
0175     ui->tableView->setModel(m_model.data());
0176 
0177     /* Hide all columns except Level, Difficulty and Solved. */
0178 
0179     for (int i = 0; i < m_model->columnCount(); i++) {
0180         ui->tableView->hideColumn(i);
0181     }
0182     ui->tableView->showColumn(LevelTableModel::Name);
0183     ui->tableView->showColumn(LevelTableModel::Difficulty);
0184     ui->tableView->showColumn(LevelTableModel::Solved);
0185 
0186     ui->tableView->sortByColumn(LevelTableModel::Name, Qt::AscendingOrder);
0187     ui->tableView->sortByColumn(LevelTableModel::Solved, Qt::DescendingOrder);
0188     ui->tableView->sortByColumn(LevelTableModel::Difficulty, Qt::AscendingOrder);
0189 
0190     ui->tableView->resizeColumnsToContents();
0191     ui->tableView->resizeRowsToContents();
0192 
0193     if (m_levels.empty()) {
0194         okButton->setEnabled(false);
0195     } else {
0196         resetSelection();
0197         connect(ui->tableView->selectionModel(), &QItemSelectionModel::currentRowChanged,
0198                 this, &SelectBoardWindow::selectedLevelChanged);
0199         connect(m_model.data(), &QAbstractItemModel::dataChanged,
0200                 this, &SelectBoardWindow::levelDataChanged);
0201         updateDetails(m_levels[0]);
0202     }
0203 
0204     ui->tableView->setUpdatesEnabled(true);
0205 }
0206 
0207 void SelectBoardWindow::showEvent(QShowEvent *event) {
0208     updateDetails(selectedBoard());
0209     QDialog::showEvent(event);
0210 }
0211 
0212 void SelectBoardWindow::resizeEvent(QResizeEvent *event) {
0213     updateDetails(selectedBoard());
0214     QDialog::resizeEvent(event);
0215 }
0216 
0217 SelectBoardWindow::~SelectBoardWindow() {
0218     delete ui;
0219 }
0220 
0221 void SelectBoardWindow::selectedLevelChanged(const QModelIndex &current, const QModelIndex &previous) {
0222     Q_UNUSED(previous);
0223     updateDetails(m_levels[current.row()]);
0224 }
0225 
0226 void SelectBoardWindow::levelDataChanged(const QModelIndex &topLeft, const
0227                                          QModelIndex &bottomRight) {
0228     Q_UNUSED(topLeft);
0229     Q_UNUSED(bottomRight);
0230     resetSelection();
0231     updateDetails(selectedBoard());
0232 }
0233 
0234 static QString diffString(const int difficulty) {
0235     if (difficulty < 0) {
0236         return diffString(0);
0237     } else if (difficulty > 7) {
0238         return diffString(7);
0239     }
0240 
0241     switch (difficulty) {
0242     case 0: return ki18n("Ridiculously Easy").toString();
0243     case 1: return ki18n("Very Easy").toString();
0244     case 2: return ki18n("Easy").toString();
0245     case 3: return ki18n("Medium").toString();
0246     case 4: return ki18n("Hard").toString();
0247     case 5: return ki18n("Very Hard").toString();
0248     case 6: return ki18n("Extremely Hard").toString();
0249     case 7: return ki18n("Impossible").toString();
0250     default: throw OutOfBoundsException();
0251     }
0252 }
0253 
0254 void SelectBoardWindow::updateDetails(QSharedPointer<Level> level) {
0255     ui->labelName->setText(i18n("Name: %1", level->visibleName()));
0256     ui->labelAuthor->setText(i18n("Author: %1", level->author()));
0257     ui->labelSize->setText(i18n("Size: %1x%2", level->width(), level->height()));
0258     ui->labelDifficulty->setText(i18n("Difficulty: %1", diffString(level->difficulty())));
0259     if (level->solved()) {
0260         ui->labelSolved->setText(i18nc("board solve time", "Solved: %1",
0261                                        Time(level->solvedTime()).toString()));
0262         QPixmap scaled = level->preview().scaled(ui->labelImage->size(),
0263                                                  Qt::KeepAspectRatio,
0264                                                  Qt::FastTransformation);
0265         ui->labelImage->setPixmap(scaled);
0266     } else {
0267         ui->labelSolved->setText(i18nc("board not solved yet", "Solved: -"));
0268         ui->labelImage->setText(QStringLiteral("?"));
0269     }
0270 }
0271 
0272 void SelectBoardWindow::resetSelection() {
0273     QModelIndex index = m_model->index(0, 0);
0274     QItemSelectionModel::SelectionFlags flags =
0275             QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
0276     ui->tableView->selectionModel()->select(index, flags);
0277 }
0278 
0279 QSharedPointer<Level> SelectBoardWindow::selectedBoard() const {
0280     int index = ui->tableView->selectionModel()->selectedIndexes().at(0).row();
0281     return m_levels[index];
0282 }
0283 
0284 #include "moc_selectboardwindow.cpp"