File indexing completed on 2024-05-12 04:05:53

0001 /***************************************************************************
0002  *   Copyright 2008      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 "renderer.h"
0022 #include "ksudoku_logging.h"
0023 
0024 #include <QPainter>
0025 #include <QPixmap>
0026 #include <QSvgRenderer>
0027 
0028 #include <KGameTheme>
0029 #include <KGameThemeProvider>
0030 
0031 #include "settings.h"
0032 
0033 namespace ksudoku {
0034 
0035 Renderer* Renderer::instance() {
0036     static Renderer instance;
0037     return &instance;
0038 }
0039     
0040 Renderer::Renderer() {
0041     m_themeProvider = new KGameThemeProvider(QByteArray()); // empty config key to disable internal config storage
0042     m_renderer = new QSvgRenderer();
0043     m_cache = new KImageCache(QStringLiteral("ksudoku-cache"), 3*1024);
0044     m_mathdokuStyle = false;
0045     
0046     m_themeProvider->discoverThemes(
0047         QStringLiteral("themes"), // theme file location
0048         QStringLiteral("default") // default theme file name
0049     );
0050     const QByteArray themeIdentifier = Settings::theme().toUtf8();
0051     KGameThemeProvider *provider = themeProvider();
0052     const QList<const KGameTheme *> themes = provider->themes();
0053     for (auto* theme : themes) {
0054         if (theme->identifier() == themeIdentifier) {
0055             provider->setCurrentTheme(theme);
0056             break;
0057         }
0058     }
0059     loadTheme(provider->currentTheme());
0060     QObject::connect(m_themeProvider, &KGameThemeProvider::currentThemeChanged, [this](const KGameTheme* theme) {
0061         loadTheme(theme);
0062     });
0063 
0064 }
0065 
0066 Renderer::~Renderer() {
0067     delete m_themeProvider;
0068     delete m_cache;
0069     delete m_renderer;
0070 }
0071 
0072 KGameThemeProvider * Renderer::themeProvider() const
0073 {
0074     return m_themeProvider;
0075 }
0076 
0077 bool Renderer::loadTheme(const KGameTheme* theme) {
0078     bool res = m_renderer->load(theme->graphicsPath());
0079     qCDebug(KSudokuLog) << "loading" << theme->graphicsPath();
0080     if(!res)
0081         return false;
0082 
0083     Settings::setTheme(QString::fromUtf8(theme->identifier()));
0084     Settings::self()->save();
0085     
0086     qCDebug(KSudokuLog) << "discarding cache";
0087     m_cache->clear();
0088     
0089     fillNameHashes();
0090     return true;
0091 }
0092 
0093 void Renderer::fillNameHashes() {
0094     m_borderNames = QList<QString>();
0095     m_borderNames << QStringLiteral("");
0096     m_borderNames << QStringLiteral("1");
0097     m_borderNames << QStringLiteral("2");
0098     m_borderNames << QStringLiteral("12");
0099     m_borderNames << QStringLiteral("3");
0100     m_borderNames << QStringLiteral("13");
0101     m_borderNames << QStringLiteral("23");
0102     m_borderNames << QStringLiteral("123");
0103     m_borderNames << QStringLiteral("4");
0104     m_borderNames << QStringLiteral("14");
0105     m_borderNames << QStringLiteral("24");
0106     m_borderNames << QStringLiteral("124");
0107     m_borderNames << QStringLiteral("34");
0108     m_borderNames << QStringLiteral("134");
0109     m_borderNames << QStringLiteral("234");
0110     m_borderNames << QStringLiteral("1234");
0111     m_borderTypes << QString();
0112     m_borderTypes << QStringLiteral("row");
0113     m_borderTypes << QStringLiteral("column");
0114     m_borderTypes << QStringLiteral("block");
0115     m_borderTypes << QStringLiteral("special");
0116     m_borderTypes << QStringLiteral("block");   // Use block-type borders for cages.
0117     m_borderTypes << QStringLiteral("special");
0118     m_borderTypes << QStringLiteral("special");
0119     m_borderTypes << QString();
0120     m_borderTypes << QStringLiteral("row_h");
0121     m_borderTypes << QStringLiteral("column_h");
0122     m_borderTypes << QStringLiteral("block_h");
0123     m_borderTypes << QStringLiteral("special_h");
0124     m_borderTypes << QStringLiteral("block_h"); // Use block-type borders for cages.
0125     m_borderTypes << QStringLiteral("special_h");
0126     m_borderTypes << QStringLiteral("special_h");
0127     m_specialNames << QStringLiteral("cell");
0128     m_specialNames << QStringLiteral("cell_preset");
0129     m_specialNames << QStringLiteral("cell");
0130     m_specialNames << QStringLiteral("cell_mistake");
0131     m_specialNames << QStringLiteral("cursor");
0132     m_specialNames << QStringLiteral("valuelist_item");
0133     m_specialNames << QStringLiteral("valuelist_selector");
0134     m_special3dNames << QStringLiteral("cell3d");
0135     m_special3dNames << QStringLiteral("cell3d_preset");
0136     m_special3dNames << QStringLiteral("cell3d");
0137     m_special3dNames << QStringLiteral("cell3d_mistake");
0138     m_special3dNames << QStringLiteral("cursor");
0139     m_special3dNames << QStringLiteral("valuelist_item");
0140     m_special3dNames << QStringLiteral("valuelist_selector");
0141     // TODO get this hardcoded values from the SVG file
0142 //  m_markerName << "markers9" << "markers9" //...
0143 }
0144 
0145 QPixmap Renderer::renderBackground(const QSize& size) const {
0146     if(!m_renderer->isValid() || size.isEmpty()) return QPixmap();
0147 
0148     QPixmap pix;
0149     QString cacheName = QStringLiteral("background_%1x%2").arg(size.width()).arg(size.height());
0150     if(!m_cache->findPixmap(cacheName, &pix))
0151     {
0152         pix = QPixmap(size);
0153         pix.fill(Qt::transparent);
0154         QPainter p(&pix);
0155         m_renderer->render(&p, QStringLiteral("background"));
0156         p.end();
0157         m_cache->insertPixmap(cacheName, pix);
0158     }
0159     return pix;
0160 }
0161 
0162 /** Moves a point from its relative position to the base rect (0,0,1,1) to a relative position to rect @p to */
0163 QPointF fromBasetoRect(const QPointF& p, const QRectF& to) {
0164     return QPointF(p.x()*to.width()+to.left(), p.y()*to.height()+to.top());
0165 }
0166 
0167 /** Moves a point from its relative position to rect @p from to a relative position to the base rect (0,0,1,1) */
0168 QPointF fromRectToBase(const QRectF& p, const QRectF& from) {
0169     return QPointF((p.x()-from.left())/from.width(), (p.y()-from.top())/from.height());
0170 }
0171 
0172 /** Moves a point from its relative position to rect @p from to a relative position to rect @p to */
0173 QPointF fromRectToRect(const QPointF& p, const QRectF& from, const QRectF& to) {
0174     return QPointF((p.x()-from.left())*to.width()/from.width()+to.left(),
0175                    (p.y()-from.top())*to.height()/from.height()+to.top());
0176 }
0177 
0178 QPixmap Renderer::renderSpecial(SpecialType type, int size) const {
0179     if(!m_renderer->isValid() || size == 0) return QPixmap();
0180     
0181     //only show the errors if the option has been set
0182     if(!Settings::showErrors() && type == SpecialCellMistake ) type = SpecialCell;
0183     
0184     QString cacheName = QStringLiteral("special_%1_%2").arg(m_specialNames[type]).arg(size);
0185     QPixmap pix;
0186     if(!m_cache->findPixmap(cacheName, &pix)) {
0187         pix = QPixmap(size, size);
0188         pix.fill(Qt::transparent);
0189         QPainter p(&pix);
0190         
0191         // NOTE fix for Qt's QSvgRenderer size reporting bug
0192         QRectF r(m_renderer->boundsOnElement(m_specialNames[type]));
0193         QRectF from(r.adjusted(+0.5,+0.5,-0.5,-0.5));
0194         QRectF to(QRectF(0,0,size,size));
0195         r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
0196         r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
0197         
0198         m_renderer->render(&p, m_specialNames[type], r);
0199         p.end();
0200         m_cache->insertPixmap(cacheName, pix);
0201     }
0202 
0203     return pix;
0204 }
0205 
0206 QPixmap Renderer::renderSymbol(int symbol, int size, int max, SymbolType type) const {
0207     if(!m_renderer->isValid() || size == 0) return QPixmap();
0208 
0209     QString set;
0210     if(max <= 9) {
0211         set = QStringLiteral("symbol");
0212     } else {
0213         set = QStringLiteral("symbol25");
0214     }
0215 
0216     QString cacheName = QStringLiteral("%1_%2_%3_%4").arg(set).arg(symbol).arg(size).arg(type);
0217     QPixmap pix;
0218     if(!m_cache->findPixmap(cacheName, &pix)) {
0219         pix = QPixmap(size, size);
0220         pix.fill(Qt::transparent);
0221         QPainter p(&pix);
0222         
0223         // NOTE fix for Qt's QSvgRenderer size reporting bug
0224         QRectF r(m_renderer->boundsOnElement(QStringLiteral("symbol_1")));
0225         QRectF from(m_renderer->boundsOnElement(QStringLiteral("cell_symbol")));
0226         from.adjust(+0.5,+0.5,-0.5,-0.5); // << this is the fix
0227         QRectF to(QRectF(0,0,size,size));
0228         
0229         r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
0230         r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
0231         
0232         switch(type) {
0233             case SymbolPreset:
0234                 if(m_renderer->elementExists(QStringLiteral("%1_%2_preset").arg(set).arg(symbol))) {
0235                     m_renderer->render(&p, QStringLiteral("%1_%2_preset").arg(set).arg(symbol), r);
0236                 } else {
0237                     m_renderer->render(&p, QStringLiteral("%1_%2").arg(set).arg(symbol), r);
0238                 }
0239                 break;
0240             case SymbolEdited:
0241                 if(m_renderer->elementExists(QStringLiteral("%1_%2_edited").arg(set).arg(symbol))) {
0242                     m_renderer->render(&p, QStringLiteral("%1_%2_edited").arg(set).arg(symbol), r);
0243                 } else {
0244                     m_renderer->render(&p, QStringLiteral("%1_%2").arg(set).arg(symbol), r);
0245                 }
0246                 break;
0247         }
0248         p.end();
0249         m_cache->insertPixmap(cacheName, pix);
0250     }
0251 
0252     return pix;
0253 }
0254 
0255 QPixmap Renderer::renderSymbolOn(QPixmap pixmap, int symbol, int color, int max, SymbolType type) const {
0256     // We use a smaller size of symbol in Mathdoku and Killer
0257     // Sudoku, to allow space for the cage labels.
0258     int size = m_mathdokuStyle ? (pixmap.width()+1)*3/4 : pixmap.width();
0259     int offset = m_mathdokuStyle ? (pixmap.width()+7)/8 : 0;
0260     QPixmap symbolPixmap = renderSymbol(symbol, size, max, type);
0261     if(color) {
0262         // TODO this does not work, need some other way, maybe hardcode color into NumberType
0263         QPainter p(&symbolPixmap);
0264         p.setCompositionMode(QPainter::CompositionMode_Multiply);
0265         p.setBrush(QBrush(QColor(128,128,128,255)));
0266         p.drawRect(0, 0, size, size);
0267         p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
0268         p.drawPixmap(0, 0, pixmap);
0269         p.end();
0270         return symbolPixmap;
0271     } else {
0272         QPainter p(&pixmap);
0273         p.drawPixmap(offset, offset, symbolPixmap);
0274         p.end();
0275         return pixmap;
0276     }
0277 }
0278 
0279 QPixmap Renderer::renderMarker(int symbol, int range, int size) const {
0280     if(!m_renderer->isValid() || size == 0) return QPixmap();
0281     
0282     QString set;
0283     if(range <= 9) {
0284         set = QStringLiteral("symbol");
0285     } else {
0286         set = QStringLiteral("symbol25");
0287     }
0288     
0289     // TODO this is a hardcoded list of possible marker-groupings
0290     // replace it with a test for possible markers
0291     if(range <= 9) {
0292         range = 9;
0293     } else if(range <= 16) {
0294         range = 16;
0295     } else {
0296         range = 25;
0297     }
0298 
0299     QString groupName = QStringLiteral("markers%1").arg(range);
0300     QString cacheName = QStringLiteral("%1_%2_%3").arg(groupName).arg(symbol).arg(size);
0301     QPixmap pix;
0302     if(!m_cache->findPixmap(cacheName, &pix)) {
0303         pix = QPixmap(size, size);
0304         pix.fill(Qt::transparent);
0305         QPainter p(&pix);
0306         
0307         // NOTE fix for Qt's QSvgRenderer size reporting bug
0308         QRectF r(m_renderer->boundsOnElement(QStringLiteral("%1_%2").arg(groupName).arg(symbol)));
0309         QRectF from(m_renderer->boundsOnElement(QStringLiteral("cell_%1").arg(groupName)));
0310         from.adjust(+0.5,+0.5,-0.5,-0.5); // << this is the fix
0311         QRectF to(QRectF(0,0,size,size));
0312 
0313         r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
0314         r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
0315 
0316         m_renderer->render(&p, QStringLiteral("%1_%2").arg(set).arg(symbol), r);
0317         p.end();
0318         m_cache->insertPixmap(cacheName, pix);
0319     }
0320 
0321     return pix;
0322 }
0323 
0324 QPixmap Renderer::renderMarkerOn(QPixmap pixmap, int symbol, int range, int color) const {
0325     // TODO maybe it would be good to directly integrate the renderMarker implementation and
0326     // make renderMarker be based on this method. (same for renderSymbol and renderSymbolOn)
0327 
0328     // We use a smaller size of marker in Mathdoku and Killer
0329     // Sudoku, to allow space for the cage labels.
0330     int size = m_mathdokuStyle ? (pixmap.width()+1)*3/4 : pixmap.width();
0331     QPixmap symbolPixmap = renderMarker(symbol, range, size);
0332     if(color) {
0333         QPainter p(&symbolPixmap);
0334         p.setCompositionMode(QPainter::CompositionMode_Multiply);
0335         p.setBrush(QBrush(QColor(128,128,128,255)));
0336         p.drawRect(0, 0, size, size);
0337         p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
0338         p.drawPixmap(0, 0, pixmap);
0339         p.end();
0340         return symbolPixmap;
0341     } else {
0342         QPainter p(&pixmap);
0343         // Offset the marker from 0,0 in Mathdoku and Killer Sudoku.
0344         int offset = m_mathdokuStyle ? (size + 7)/8 : 0;
0345         p.drawPixmap(offset, 2*offset, symbolPixmap);
0346         p.end();
0347         return pixmap;
0348     }
0349 }
0350 
0351 QPixmap Renderer::renderCageLabelOn(QPixmap pixmap, const QString & cageLabel)
0352 {
0353     // TODO - Do font setup once during resize? Put 0+-x/ in themes?
0354     int size = pixmap.width();
0355     QPainter p(&pixmap);
0356     p.setPen(QStringLiteral("white"));  // Text is white on a dark rectangle.
0357     p.setBrush(Qt::SolidPattern);
0358 
0359     // Cage label uses top 1/4 of pixmap and text is 1/6 height of pixmap.
0360     QFont f = p.font();
0361     f.setBold(true);
0362     f.setPixelSize((size+5)/6);
0363     p.setFont(f);
0364 
0365     QFontMetrics fm(f);
0366         int w = fm.boundingRect(cageLabel).width(); // Width of text.
0367     int h = fm.height();        // Total height of font.
0368     int a = fm.ascent();        // Height from baseline of font.
0369         int m = fm.boundingRect(QLatin1Char('1')).width()/2;    // Left-right margin = 1/2 width of '1'.
0370 
0371     // Paint background rect: text must be visible in light and dark themes.
0372     p.fillRect(size/6 - m, (size + 3)/4 - a, w + 2*m, h, Qt::darkGray);
0373     // Note: Origin of text is on baseline to left of first character.
0374     p.drawText(size/6, (size+3)/4, cageLabel);
0375 
0376     p.end();
0377     return pixmap;
0378 }
0379 
0380 QPixmap Renderer::renderBorder(int border, GroupTypes type, int size) const {
0381     if(!m_renderer->isValid() || size == 0) return QPixmap();
0382     
0383     QString cacheName = QStringLiteral("contour_%1_%2_%3").arg(m_borderTypes[type]).arg(m_borderNames[border]).arg(size);
0384     QPixmap pix;
0385     if(!m_cache->findPixmap(cacheName, &pix)) {
0386         pix = QPixmap(size, size);
0387         pix.fill(Qt::transparent);
0388         QPainter p(&pix);
0389         
0390         // NOTE fix for Qt's QSvgRenderer size reporting bug
0391         QRectF r(m_renderer->boundsOnElement(QStringLiteral("%1_%2").arg(m_borderTypes[type]).arg(m_borderNames[border])));
0392         QRectF from(r.adjusted(+0.5,+0.5,-0.5,-0.5));
0393         QRectF to(QRectF(0,0,size,size));
0394         r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
0395         r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
0396         
0397         m_renderer->render(&p, QStringLiteral("%1_%2").arg(m_borderTypes[type]).arg(m_borderNames[border]), r);
0398         p.end();
0399         m_cache->insertPixmap(cacheName, pix);
0400     }
0401 
0402     return pix;
0403 }
0404 
0405 QPixmap Renderer::renderSpecial3D(SpecialType type, int size) const {
0406     if(!m_renderer->isValid() || size == 0) return QPixmap();
0407 
0408     QString cacheName = QStringLiteral("special_%1_%2").arg(m_special3dNames[type]).arg(size);
0409     QPixmap pix;
0410     if(!m_cache->findPixmap(cacheName, &pix)) {
0411         pix = QPixmap(size, size);
0412         pix.fill(Qt::transparent);
0413         QPainter p(&pix);
0414         
0415         // NOTE fix for Qt's QSvgRenderer size reporting bug
0416         QRectF r(m_renderer->boundsOnElement(m_special3dNames[type]));
0417         QRectF from(r.adjusted(+0.5,+0.5,-0.5,-0.5));
0418         QRectF to(QRectF(0,0,size,size));
0419         r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
0420         r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
0421         
0422         m_renderer->render(&p, m_special3dNames[type], r);
0423         p.end();
0424         m_cache->insertPixmap(cacheName, pix);
0425     }
0426 
0427     return pix;
0428 }
0429 
0430 }