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 }