File indexing completed on 2024-04-28 04:04:46

0001 /***************************************************************************
0002  *   Copyright 2007      Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
0003  *   Copyright 2015      Ian Wadham <iandw.au@gmail.com>                   *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0019  ***************************************************************************/
0020 
0021 #include "gamevariants.h"
0022 #include "ksudoku_logging.h"
0023 #include "ksudokugame.h"
0024 #include "serializer.h"
0025 
0026 #include <QEvent>
0027 #include <QIcon>
0028 #include <QPainter>
0029 
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 
0033 
0034 
0035 #include "puzzle.h"
0036 
0037 
0038 namespace ksudoku {
0039 
0040 ///////////////////////////////////////////////////////////////////////////////
0041 // class GameVariant
0042 ///////////////////////////////////////////////////////////////////////////////
0043 
0044 GameVariant::GameVariant(const QString& name, GameVariantCollection* collection)
0045     : m_name(name)
0046 {
0047     if(collection)
0048         collection->addVariant(this);
0049 }
0050 
0051 void GameVariant::setDescription(const QString& descr) {
0052     m_description = descr;
0053 }
0054 
0055 void GameVariant::setIcon(const QString& icon) {
0056     m_icon = icon;
0057 }
0058 
0059 ///////////////////////////////////////////////////////////////////////////////
0060 // class GameVariantCollection
0061 ///////////////////////////////////////////////////////////////////////////////
0062 
0063 GameVariantCollection::GameVariantCollection(QObject* parent, bool autoDel)
0064     : QAbstractListModel(parent), m_autoDelete(autoDel)
0065 {
0066 }
0067 
0068 GameVariantCollection::~GameVariantCollection() {
0069     if(m_autoDelete)
0070         qDeleteAll(m_variants);
0071     m_variants.clear();
0072 }
0073 
0074 void GameVariantCollection::addVariant(GameVariant* variant) {
0075     int count = m_variants.count();
0076     beginInsertRows(QModelIndex(), count, count);
0077     m_variants.append(variant);
0078     endInsertRows();
0079     Q_EMIT newVariant(variant);
0080 }
0081 
0082 int GameVariantCollection::rowCount(const QModelIndex& parent) const {
0083     Q_UNUSED(parent);
0084     return m_variants.count();
0085 }
0086 
0087 QModelIndex GameVariantCollection::index(int row, int column, const QModelIndex &parent) const {
0088     Q_UNUSED(parent);
0089     if ((row < 0) || (row >= m_variants.count()))
0090         return QModelIndex();
0091     return createIndex(row, column, m_variants[row]);
0092 }
0093 
0094 QVariant GameVariantCollection::data(const QModelIndex &index, int role) const {
0095     if (!index.isValid() || index.row() >= m_variants.count())
0096         return QVariant();
0097 
0098     if (!index.internalPointer())
0099         return QVariant();
0100 
0101     auto* gameVariant = static_cast<GameVariant*>(index.internalPointer());
0102 
0103     switch(role) {
0104         case Qt::DisplayRole:
0105             return gameVariant->name();
0106         case Qt::DecorationRole:
0107             return gameVariant->icon();
0108         case GameVariantDelegate::Description:
0109             return gameVariant->description();
0110     }
0111 
0112     return QVariant();
0113 }
0114 
0115 GameVariant* GameVariantCollection::variant(const QModelIndex& index) const {
0116     return static_cast<GameVariant*>(index.internalPointer());
0117 }
0118 
0119 ///////////////////////////////////////////////////////////////////////////////
0120 // class GameVariantDelegate
0121 ////////////77/////////////////////////////////////////////////////////////////
0122 
0123 GameVariantDelegate::GameVariantDelegate(QObject* parent, QWidget * viewport)
0124     : QItemDelegate(parent),
0125       m_viewport(viewport)
0126 {
0127 }
0128 
0129 QSize GameVariantDelegate::sizeHint(const QStyleOptionViewItem& option,
0130                     const QModelIndex& index) const
0131 {
0132     Q_UNUSED(index);
0133 
0134     // Fit a varying number of columns into the list-view.
0135     int viewportWidth = m_viewport->width();
0136     int hintWidth     = m_minWidth;
0137     int nItemsPerRow  = viewportWidth / m_minWidth;
0138 
0139     // Expand the hinted width, so that the columns will fill the viewport.
0140     if (nItemsPerRow > 0) {
0141         int adjustment = (viewportWidth % nItemsPerRow) ? 0 : 1;
0142         // The adjustment works around a graphics glitch, when nItemsPerRow
0143         // exactly divides viewportWidth and instead of nItemsPerRow columns
0144         // we suddenly get one column less, plus a large empty space, even
0145         // though QListView::spacing() is zero.
0146         hintWidth      = (viewportWidth - adjustment) / nItemsPerRow;
0147     }
0148 
0149     // Set the height to contain the icon or 4 lines of text.
0150     QSize size (hintWidth, 0);
0151     int fontHeight = option.fontMetrics.height();
0152     if (m_iconHeight < fontHeight*4) {
0153         size.setHeight (fontHeight*4 + m_separatorPixels*3);
0154     }
0155     else {
0156         size.setHeight (m_iconHeight + m_separatorPixels*2);
0157     }
0158     return size;
0159 }
0160 
0161 void GameVariantDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
0162 
0163     QColor bkgColor;
0164 
0165     // Calculate item's column num in list-view. Add 1 in odd-numbered rows.
0166     int nItemsPerRow = qMax (m_viewport->width() / option.rect.width(), 1);
0167     int oddColumn = index.row() % nItemsPerRow;
0168     oddColumn = oddColumn + ((index.row() / nItemsPerRow) % 2);
0169 
0170     if (option.state & QStyle::State_Selected) {
0171         bkgColor = option.palette.color(QPalette::Highlight);
0172     } else if (oddColumn % 2) {
0173         // The shading alternates along each row in the list view and
0174         // each odd-numbered row starts with a shaded item, so we have
0175         // a checkerboard pattern, regardless of whether nItemsPerRow
0176         // is odd or even (see the above calculation).
0177         bkgColor = option.palette.color(QPalette::AlternateBase);
0178     } else {
0179         bkgColor = option.palette.color(QPalette::Base);
0180     }
0181     painter->fillRect(option.rect, bkgColor);
0182 
0183     QRect contentRect = option.rect.adjusted(m_leftMargin, m_separatorPixels, -m_rightMargin, -m_separatorPixels);
0184 
0185     // Show icon
0186 
0187     QPixmap iconPixmap = QIcon::fromTheme(icon(index)).pixmap(m_iconWidth, m_iconHeight);
0188     painter->drawPixmap(contentRect.left(), (contentRect.height() - iconPixmap.height()) / 2 + contentRect.top(), iconPixmap);
0189     contentRect.adjust(iconPixmap.width() + m_separatorPixels*2, 0, 0, 0);
0190 
0191 //  // Show configuration icon
0192 //  if(configurable(index)) {
0193 //      QPixmap configPixmap = QIcon::fromTheme( QLatin1String( "configure" ) ).pixmap(32, 32);
0194 //      painter->drawPixmap(contentRect.right() - configPixmap.width(), (contentRect.height() - configPixmap.height()) / 2 + contentRect.top(), configPixmap);
0195 //      contentRect.adjust(0, 0, -(configPixmap.width() + separatorPixels), 0);
0196 //  }
0197 
0198     // Show Title
0199     QFont titleFont(painter->font());
0200     titleFont.setPointSize(titleFont.pointSize() + 2);
0201     titleFont.setWeight(QFont::Bold);
0202 
0203     QPen previousPen(painter->pen());
0204     // TODO: don't we have a function for 'color1 similar color2'
0205     if(previousPen.color() == bkgColor) {
0206         QPen p(previousPen);
0207         int color = bkgColor.rgb();
0208         color = ~color | 0xff000000;
0209         p.setColor(color);
0210         painter->setPen(p);
0211     }
0212 
0213     QFont previousFont(painter->font());
0214     painter->setFont(titleFont);
0215     QString titleStr = painter->fontMetrics().elidedText(title(index), Qt::ElideRight, contentRect.width());
0216     painter->drawText(contentRect, titleStr);
0217     contentRect.adjust(0, m_separatorPixels + painter->fontMetrics().height(), 0, 0);
0218     painter->setFont(previousFont);
0219 
0220     // Show Description
0221     QTextOption wrap(Qt::AlignLeft);
0222     wrap.setWrapMode(QTextOption::WordWrap);
0223     QString descrStr = description(index);
0224     painter->drawText(contentRect, descrStr, wrap);
0225 
0226     painter->setPen(previousPen);
0227 }
0228 
0229 QString GameVariantDelegate::title(const QModelIndex& index) const {
0230     return index.model()->data(index, Qt::DisplayRole).toString();
0231 }
0232 
0233 QString GameVariantDelegate::description(const QModelIndex& index) const {
0234     return index.model()->data(index, Description).toString();
0235 }
0236 
0237 QString GameVariantDelegate::icon(const QModelIndex& index) const {
0238     return index.model()->data(index, Qt::DecorationRole).toString();
0239 }
0240 
0241 bool GameVariantDelegate::configurable(const QModelIndex& index) const {
0242     const auto* collection = dynamic_cast<const GameVariantCollection*>(index.model());
0243     if(!collection) return false;
0244 
0245     return collection->variant(index)->canConfigure();
0246 }
0247 
0248 bool GameVariantDelegate::eventFilter(QObject* watched, QEvent* event) {
0249     if(event->type() == QEvent::MouseButtonPress) {
0250         return true;
0251     }
0252 
0253     // TODO insert code for handling clicks on buttons in items.
0254 
0255     return QItemDelegate::eventFilter(watched, event);
0256 }
0257 
0258 ///////////////////////////////////////////////////////////////////////////////
0259 // class SudokuGame
0260 ///////////////////////////////////////////////////////////////////////////////
0261 
0262 SudokuGame::SudokuGame(const QString& name, uint order, GameVariantCollection* collection)
0263     : GameVariant(name, collection), m_order(order), m_graph(nullptr)
0264 {
0265     // TODO load from settings
0266     m_symmetry = 0;
0267 }
0268 
0269 SudokuGame::~SudokuGame()
0270 {
0271     delete m_graph;
0272 }
0273 
0274 bool SudokuGame::canConfigure() const {
0275     return true;
0276 }
0277 
0278 bool SudokuGame::configure() {
0279     KMessageBox::information(nullptr, i18n("Configuration not yet implemented"), QLatin1String(""));
0280     return false;
0281 }
0282 
0283 bool SudokuGame::canStartEmpty() const {
0284     return true;
0285 }
0286 
0287 Game SudokuGame::startEmpty() {
0288     if(!m_graph) {
0289         m_graph = new SKGraph(m_order, TypeSudoku);
0290         m_graph->initSudoku();
0291     }
0292 
0293     auto* puzzle = new Puzzle(m_graph, false);
0294     puzzle->init();
0295 
0296     return Game(puzzle);
0297 }
0298 
0299 Game SudokuGame::createGame(int difficulty, int symmetry) {
0300     if(!m_graph) {
0301         m_graph = new SKGraph(m_order, TypeSudoku);
0302         m_graph->initSudoku();
0303     }
0304     
0305     auto* puzzle = new Puzzle(m_graph, true);
0306     puzzle->init(difficulty, symmetry);
0307 
0308     return Game(puzzle);
0309 }
0310 
0311 KsView* SudokuGame::createView(const Game& /*game*/) const {
0312     qCDebug(KSudokuLog) << "KsView* ksudoku::SudokuGame::createView()";
0313     return nullptr;
0314 }
0315 
0316 ///////////////////////////////////////////////////////////////////////////////
0317 // class RoxdokuGame
0318 ///////////////////////////////////////////////////////////////////////////////
0319 
0320 RoxdokuGame::RoxdokuGame(const QString& name, uint order, GameVariantCollection* collection)
0321     : GameVariant(name, collection), m_order(order), m_graph(nullptr)
0322 {
0323     m_symmetry = 0;
0324 }
0325 
0326 RoxdokuGame::~RoxdokuGame()
0327 {
0328     delete m_graph;
0329 }
0330 
0331 bool RoxdokuGame::canConfigure() const {
0332     return true;
0333 }
0334 
0335 bool RoxdokuGame::configure() {
0336     KMessageBox::information(nullptr, i18n("Configuration not yet implemented"), QLatin1String(""));
0337     return false;
0338 }
0339 
0340 bool RoxdokuGame::canStartEmpty() const {
0341     return true;
0342 }
0343 
0344 Game RoxdokuGame::startEmpty() {
0345     if(!m_graph) {
0346         m_graph = new SKGraph(m_order, TypeRoxdoku);
0347         m_graph->initRoxdoku();
0348     }
0349 
0350     auto* puzzle = new Puzzle(m_graph, false);
0351     puzzle->init();
0352 
0353     return Game(puzzle);
0354 }
0355 
0356 Game RoxdokuGame::createGame(int difficulty, int symmetry) {
0357     if(!m_graph) {
0358         m_graph = new SKGraph(m_order, TypeRoxdoku);
0359         m_graph->initRoxdoku();
0360     }
0361 
0362     auto* puzzle = new Puzzle(m_graph, true);
0363     puzzle->init(difficulty, symmetry);
0364 
0365     return Game(puzzle);
0366 }
0367 
0368 KsView* RoxdokuGame::createView(const Game& /*game*/) const {
0369     qCDebug(KSudokuLog) << "KsView* ksudoku::RoxdokuGame::createView()";
0370     return nullptr;
0371 }
0372 
0373 ///////////////////////////////////////////////////////////////////////////////
0374 // class CustomGame
0375 ///////////////////////////////////////////////////////////////////////////////
0376 
0377 CustomGame::CustomGame(const QString& name, const QUrl& url,
0378                        GameVariantCollection* collection)
0379     : GameVariant(name, collection), m_url(url), m_graph(nullptr)
0380 {
0381     m_symmetry = 0;
0382 }
0383 
0384 CustomGame::~CustomGame()
0385 {
0386     delete m_graph;
0387 }
0388 
0389 bool CustomGame::canConfigure() const {
0390     return false;
0391 }
0392 
0393 bool CustomGame::configure() {
0394     return false;
0395 }
0396 
0397 bool CustomGame::canStartEmpty() const {
0398     return true;
0399 }
0400 
0401 Game CustomGame::startEmpty() {
0402     if (! createSKGraphObject()) {
0403         return Game();
0404     }
0405     auto* puzzle = new Puzzle(m_graph, false);
0406     puzzle->init();
0407 
0408     return Game(puzzle);
0409 }
0410 
0411 Game CustomGame::createGame(int difficulty, int symmetry) {
0412     if (! createSKGraphObject()) {
0413         return Game();
0414     }
0415     auto* puzzle = new Puzzle(m_graph, true);
0416     puzzle->init(difficulty, symmetry);
0417 
0418     return Game(puzzle);
0419 }
0420 
0421 KsView* CustomGame::createView(const Game& /*game*/) const {
0422     qCDebug(KSudokuLog) << "KsView* ksudoku::CustomGame::createView()";
0423     return nullptr;
0424 }
0425 
0426 bool CustomGame::createSKGraphObject()
0427 {
0428     if ((m_graph != nullptr) && ((m_graph->specificType() == Mathdoku) ||
0429             (m_graph->specificType() == KillerSudoku))) {
0430         delete m_graph; // Re-create SKGraph for every Mathdoku or
0431         m_graph = nullptr;  // Killer Sudoku game (re-inits cages and size).
0432     }
0433     if (m_graph == nullptr) {
0434         QString errorMsg;
0435         m_graph = ksudoku::Serializer::loadCustomShape(m_url, nullptr, errorMsg);
0436     }
0437     return (m_graph != nullptr);    // True if the shapes/*.xml file loaded OK.
0438 }
0439 
0440 }
0441 
0442 #include "moc_gamevariants.cpp"