File indexing completed on 2024-05-12 04:05:54
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 "view2d.h" 0022 0023 #include "ksudoku_logging.h" 0024 0025 #include <QGraphicsPixmapItem> 0026 #include <QGraphicsSceneEvent> 0027 0028 #include "puzzle.h" 0029 #include "gameactions.h" 0030 0031 #include "settings.h" 0032 0033 namespace ksudoku { 0034 0035 0036 struct ColoredValue { 0037 ColoredValue() : value(0), color(0) { } 0038 ColoredValue(int v, int c) : value(v), color(c) { } 0039 int value; 0040 int color; 0041 }; 0042 0043 class CellGraphicsItem : public QGraphicsPixmapItem { 0044 public: 0045 CellGraphicsItem(QPoint pos, int id, View2DScene* scene); 0046 public: 0047 void resize(int gridSize); 0048 QPoint pos() const { return m_pos; } 0049 void showCursor(QGraphicsItem* cursor); 0050 void setType(SpecialType type); 0051 void setCageLabel (const QString &cageLabel); 0052 0053 void setValues(const QList<ColoredValue> &values); 0054 protected: 0055 void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; 0056 void mousePressEvent(QGraphicsSceneMouseEvent* event) override; 0057 private: 0058 void updatePixmap(); 0059 private: 0060 View2DScene* m_scene; 0061 QPoint m_pos; 0062 SpecialType m_type; 0063 QList<ColoredValue> m_values; 0064 QString m_cageLabel; 0065 int m_id; 0066 int m_size; 0067 int m_range; 0068 }; 0069 0070 CellGraphicsItem::CellGraphicsItem(QPoint pos, int id, View2DScene* scene) { 0071 setAcceptHoverEvents(true); 0072 setShapeMode(QGraphicsPixmapItem::BoundingRectShape); 0073 m_pos = pos; 0074 m_size = 0; 0075 m_scene = scene; 0076 m_id = id; 0077 m_type = SpecialCell; 0078 m_range = scene->maxValue(); 0079 } 0080 0081 void CellGraphicsItem::resize(int gridSize) { 0082 m_size = gridSize * 2; 0083 0084 setPos(m_pos.x()*m_size, m_pos.y()*m_size); 0085 updatePixmap(); 0086 } 0087 0088 void CellGraphicsItem::showCursor(QGraphicsItem* cursor) { 0089 cursor->setParentItem(this); 0090 cursor->setZValue(1); 0091 } 0092 0093 void CellGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) { 0094 Q_UNUSED(event); 0095 m_scene->hover(m_id); 0096 } 0097 0098 void CellGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* event) { 0099 switch(event->button()) { 0100 case Qt::LeftButton: 0101 m_scene->press(m_id); 0102 break; 0103 case Qt::RightButton: 0104 m_scene->press(m_id, true); 0105 break; 0106 default: 0107 break; 0108 } 0109 } 0110 0111 void CellGraphicsItem::setType(SpecialType type) { 0112 if(type == m_type) return; 0113 m_type = type; 0114 0115 updatePixmap(); 0116 } 0117 0118 void CellGraphicsItem::setValues(const QList<ColoredValue> &values) { 0119 m_values = values; 0120 0121 updatePixmap(); 0122 } 0123 0124 void CellGraphicsItem::setCageLabel(const QString &cageLabel) { 0125 m_cageLabel = cageLabel; 0126 } 0127 0128 void CellGraphicsItem::updatePixmap() { 0129 if(m_size == 0) return; 0130 0131 hide(); 0132 0133 QPixmap pic = Renderer::instance()->renderSpecial(m_type, m_size); 0134 switch(m_type) { 0135 case SpecialCell: 0136 case SpecialCellMistake: 0137 if(!m_values.isEmpty()) { 0138 pic = Renderer::instance()->renderSymbolOn(pic, m_values[0].value, m_values[0].color, m_range, SymbolEdited); 0139 } 0140 break; 0141 case SpecialCellPreset: 0142 if(!m_values.isEmpty()) { 0143 pic = Renderer::instance()->renderSymbolOn(pic, m_values[0].value, 0, m_range, SymbolPreset); 0144 } 0145 break; 0146 case SpecialCellMarkers: { 0147 for(int i = m_values.size()-1; i >= 0; --i) { 0148 pic = Renderer::instance()->renderMarkerOn(pic, m_values[i].value, m_range, 0); 0149 } 0150 } break; 0151 default: break; // TODO maybe assert as this is not allowed to occur 0152 } 0153 if (! m_cageLabel.isEmpty()) { 0154 pic = Renderer::instance()->renderCageLabelOn(pic, m_cageLabel); 0155 } 0156 setPixmap(pic); 0157 0158 show(); 0159 } 0160 0161 0162 struct GroupGraphicItemSegment { 0163 QPoint pos; 0164 int shape; 0165 QGraphicsPixmapItem* standard; 0166 QGraphicsPixmapItem* highlighted; 0167 }; 0168 0169 class GroupGraphicsItem : public QGraphicsItemGroup { 0170 public: 0171 GroupGraphicsItem(const QList<QPoint> &cells, bool isCage = false); 0172 ~GroupGraphicsItem() override; 0173 void hideBlockBorder (bool visible); 0174 public: 0175 void resize(int gridSize, bool highlight); 0176 void setHighlight(bool highlight); 0177 void setHighlight(const QPoint& pos, bool highlight); 0178 private: 0179 int border(int tl, int tr, int bl, int br, int given); 0180 void detectType(); 0181 void createContour(); 0182 void createSegment(const QPoint& pos, int shape); 0183 private: 0184 GroupTypes m_type; 0185 QList<QPoint> m_cells; 0186 QList<GroupGraphicItemSegment> m_segments; 0187 bool m_isCage; // Is the group a cage, as in Killer Sudoku or Mathdoku? 0188 bool m_borderVisible; 0189 }; 0190 0191 0192 GroupGraphicsItem::GroupGraphicsItem(const QList<QPoint> &cells, bool isCage) { 0193 m_cells = cells; 0194 m_isCage = isCage; 0195 m_borderVisible = true; 0196 0197 setEnabled(false); 0198 setAcceptedMouseButtons(Qt::NoButton); 0199 0200 detectType(); 0201 if (isCage) { 0202 // Draw border around cage, even if it is all in one row or column. 0203 m_type = GroupCage; 0204 setZValue(4); 0205 } 0206 createContour(); 0207 0208 if(!m_cells.contains(QPoint(1,1))) setHighlight(false); 0209 } 0210 0211 GroupGraphicsItem::~GroupGraphicsItem() { 0212 QList<GroupGraphicItemSegment>::iterator segment; 0213 for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) { 0214 if(segment->highlighted) delete segment->highlighted; 0215 if(segment->standard) delete segment->standard; 0216 } 0217 } 0218 0219 void GroupGraphicsItem::hideBlockBorder (bool hide) { 0220 if ((m_type == GroupBlock) && hide) { 0221 m_borderVisible = false; 0222 } 0223 } 0224 0225 void GroupGraphicsItem::detectType() { 0226 int x = m_cells[0].x(); 0227 int y = m_cells[0].y(); 0228 for(int i = 1; i < m_cells.size(); ++i) { 0229 if(x != m_cells[i].x()) x = -1; 0230 if(y != m_cells[i].y()) y = -1; 0231 } 0232 m_type = GroupNone; 0233 if(x==-1) m_type |= GroupRow; 0234 if(y==-1) m_type |= GroupColumn; 0235 0236 // Row and column highlights must go above the GroupBlock boundary-line. 0237 // It really looks better to have the block highlight on top of the row 0238 // and column highlights, especially with cages or jigsaw-type blocks. 0239 // TODO - Might also be a good idea to reduce opacity of row and column. 0240 if(m_type == GroupColumn) setZValue(3); 0241 else if(m_type == GroupRow) setZValue(3); 0242 else if(m_type == GroupBlock) setZValue(4); 0243 } 0244 0245 void GroupGraphicsItem::createContour() { 0246 for(int i = 0; i < m_cells.size(); ++i) { 0247 int x = m_cells[i].x(); 0248 int y = m_cells[i].y(); 0249 int idx[9]; 0250 // Find neighbours of cell (x, y). 0251 idx[0] = m_cells.indexOf(QPoint(x-1, y-1)); // North West. 0252 idx[1] = m_cells.indexOf(QPoint(x, y-1)); // North. 0253 idx[2] = m_cells.indexOf(QPoint(x+1, y-1)); // North East. 0254 idx[3] = m_cells.indexOf(QPoint(x-1, y )); // West. 0255 idx[4] = i; // Self. 0256 idx[5] = m_cells.indexOf(QPoint(x+1, y )); // East. 0257 idx[6] = m_cells.indexOf(QPoint(x-1, y+1)); // South West. 0258 idx[7] = m_cells.indexOf(QPoint(x, y+1)); // South. 0259 idx[8] = m_cells.indexOf(QPoint(x+1, y+1)); // South East. 0260 0261 // A cage can consist of one cell with a border. 0262 if((m_type != GroupCage) && 0263 idx[1] == -1 && idx[3] == -1 && idx[5] == -1 && idx[7] == -1) 0264 { 0265 // No adjoining neighbour to N, S, E or W. 0266 m_type = GroupSpecial; // Used in the "X" of XSudoku. 0267 setZValue(4); // Same height as row or column. 0268 } 0269 0270 int b; 0271 if((b = border(idx[0],idx[1],idx[3],idx[4],3))) { 0272 createSegment(QPoint(x,y), b); 0273 } 0274 if((b = border(idx[1],idx[2],idx[4],idx[5],2))) { 0275 createSegment(QPoint(x+1,y), b); 0276 } 0277 if((b = border(idx[3],idx[4],idx[6],idx[7],1))) { 0278 createSegment(QPoint(x,y+1), b); 0279 } 0280 if((b = border(idx[4],idx[5],idx[7],idx[8],0))) { 0281 createSegment(QPoint(x+1,y+1), b); 0282 } 0283 } 0284 } 0285 0286 void GroupGraphicsItem::createSegment(const QPoint& pos, int shape) { 0287 GroupGraphicItemSegment segment; 0288 segment.pos = pos*2 - QPoint(1,1); 0289 segment.shape = shape; 0290 0291 // Row and column groups are drawn only when highlighted. 0292 // Block groups have a boundary-line, but no middle unless highlighted. 0293 // Special groups (disjoint cells) have a special color. 0294 0295 switch(m_type & GroupUnhighlightedMask) { 0296 case GroupRow: 0297 case GroupColumn: 0298 segment.standard = nullptr; 0299 break; 0300 case GroupBlock: 0301 case GroupCage: 0302 segment.standard = (shape == 15) ? nullptr // No middle. 0303 : new QGraphicsPixmapItem(this); 0304 break; 0305 default: // Special Group 0306 segment.standard = new QGraphicsPixmapItem(this); 0307 break; 0308 } 0309 0310 // All groups have highlighting. 0311 segment.highlighted = new QGraphicsPixmapItem(this); 0312 segment.highlighted->setVisible(false); 0313 0314 m_segments << segment; 0315 } 0316 0317 int GroupGraphicsItem::border(int tl, int tr, int bl, int br, int given) { 0318 switch(given) { 0319 case 0: if(tr > tl || bl > tl || br > tl) return 0; break; 0320 case 1: if(tl > tr || bl > tr || br > tr) return 0; break; 0321 case 2: if(tl > bl || tr > bl || br > bl) return 0; break; 0322 case 3: if(tl > br || tr > br || bl > br) return 0; break; 0323 } 0324 int b = ((tl!=-1)?1:0) | ((tr!=-1)?2:0) | ((bl!=-1)?4:0) | ((br!=-1)?8:0); 0325 return b; 0326 } 0327 0328 void GroupGraphicsItem::setHighlight(bool highlight) { 0329 if(((m_type & GroupHighlight) == GroupHighlight) == highlight) return; 0330 0331 QList<GroupGraphicItemSegment>::iterator segment; 0332 for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) { 0333 if (segment->highlighted) { 0334 segment->highlighted->setVisible(highlight); 0335 if ((m_type & GroupBlock) == GroupBlock) { 0336 // Block highlight goes on top of row, column and special 0337 // highlights. Block boundary-line goes underneath them. 0338 setZValue(highlight ? 8 : 4); 0339 } 0340 if (m_type == GroupSpecial) { 0341 // Special highlight goes on top of unhighlighted special cell. 0342 setZValue(highlight ? 9 : 4); 0343 } 0344 } 0345 if(segment->standard) { 0346 segment->standard->setVisible(!highlight); 0347 } 0348 } 0349 m_type ^= GroupHighlight; 0350 } 0351 0352 void GroupGraphicsItem::setHighlight(const QPoint& pos, bool highlight) { 0353 setHighlight(m_cells.contains(pos) && highlight); 0354 } 0355 0356 void GroupGraphicsItem::resize(int gridSize, bool highlight) { 0357 int size = gridSize*2; 0358 Renderer* r = Renderer::instance(); 0359 0360 highlight = (m_type == GroupCage); // IDW test. 0361 highlight = true; // IDW test. 0362 GroupTypes standard = m_type & GroupUnhighlightedMask; 0363 GroupTypes highlighted = m_type | GroupHighlight; 0364 0365 QList<GroupGraphicItemSegment>::iterator segment; 0366 for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) { 0367 QPointF pos = segment->pos*gridSize; 0368 // Has standard pixmap item? 0369 if(m_borderVisible && segment->standard) { 0370 QPixmap pic = r->renderBorder(segment->shape, standard, size); 0371 segment->standard->setPixmap(pic); 0372 segment->standard->setOffset(pos); 0373 } 0374 // Highlights on and has highlighted pixmap item? 0375 if(m_borderVisible && highlight && segment->highlighted) { 0376 QPixmap pic = r->renderBorder(segment->shape, highlighted, size); 0377 segment->highlighted->setPixmap(pic); 0378 segment->highlighted->setOffset(pos); 0379 } 0380 } 0381 } 0382 0383 View2DScene::View2DScene(GameActions* gameActions) { 0384 m_gameActions = gameActions; 0385 m_background = nullptr; 0386 m_groupLayer = nullptr; 0387 m_cellLayer = nullptr; 0388 m_cursorPos = 0; 0389 m_cursor = nullptr; 0390 m_highlightsOn = false; 0391 } 0392 0393 View2DScene::~View2DScene() { 0394 delete m_cursor; // needs to be deleted before cells 0395 0396 // groups need to be deleted before m_groupLayer 0397 QList<GroupGraphicsItem*>::iterator group; 0398 for(group = m_groups.begin(); group != m_groups.end(); ++group) { 0399 delete *group; 0400 } 0401 0402 // cells need to be deleted before m_cellLayer 0403 QList<CellGraphicsItem*>::iterator cell; 0404 for(cell = m_cells.begin(); cell != m_cells.end(); ++cell) { 0405 delete *cell; 0406 } 0407 0408 delete m_background; 0409 delete m_groupLayer; 0410 delete m_cellLayer; 0411 } 0412 0413 void View2DScene::init(const Game& game) { 0414 m_selectedValue = 1; 0415 0416 m_game = game; 0417 0418 // Set the order of the layers. 0419 m_background = new QGraphicsPixmapItem(); 0420 m_background->setZValue(-7); // Background. 0421 addItem(m_background); 0422 m_groupLayer = new QGraphicsItemGroup(); 0423 m_groupLayer->setZValue(-1); // Boundary-lines and highlighting. 0424 addItem(m_groupLayer); 0425 m_cellLayer = new QGraphicsItemGroup(); 0426 m_cellLayer->setZValue(0); // Cell outlines and shading. 0427 m_cellLayer->setHandlesChildEvents(false); 0428 addItem(m_cellLayer); 0429 0430 SKGraph* g = m_game.puzzle()->graph(); 0431 Renderer::instance()->setMathdokuStyle((g->specificType() == Mathdoku) 0432 || (g->specificType() == KillerSudoku)); 0433 m_cells.resize(m_game.size()); 0434 m_cursorPos = -1; 0435 for(int i = 0; i < m_game.size(); ++i) { 0436 // Do not paint unusable cells (e.g. gaps in Samurai puzzles). 0437 if (game.value(i) == UNUSABLE) { 0438 m_cells[i] = nullptr; 0439 continue; 0440 } 0441 m_cells[i] = new CellGraphicsItem(QPoint(g->cellPosX(i), g->cellPosY(i)), i, this); 0442 m_cells[i]->setParentItem(m_cellLayer); 0443 if(game.given(i)) 0444 // TODO - Implement allCellsLookSame preference. 0445 m_cells[i]->setType(SpecialCellPreset); 0446 // m_cells[i]->setType(SpecialCell); // IDW test. 0447 else 0448 m_cells[i]->setType(SpecialCell); 0449 if(game.value(i)) 0450 m_cells[i]->setValues(QList<ColoredValue>() << ColoredValue(game.value(i),0)); 0451 else 0452 m_cells[i]->setValues(QList<ColoredValue>()); 0453 if(m_cursorPos < 0) m_cursorPos = i; 0454 } 0455 0456 m_groups.resize(g->cliqueCount() + g->cageCount()); 0457 // IDW TODO - Draw Killer Sudoku cages inside cell borders? 0458 // Anyway, show 3x3 and 2x2 blocks in Killer Sudoku somehow. 0459 bool hasBothBlocksAndCages = (g->specificType() == KillerSudoku); 0460 for(int i = 0; i < g->cliqueCount(); ++i) { 0461 // Set the shape of each group. 0462 QList<int> idx = g->clique(i); 0463 QList<QPoint> pts = QList<QPoint>(idx.size()); 0464 for(int j = 0; j < idx.size(); ++j) { 0465 pts[j] = QPoint(g->cellPosX(idx[j]), g->cellPosY(idx[j])); 0466 } 0467 m_groups[i] = new GroupGraphicsItem(pts); 0468 m_groups[i]->setParentItem(m_groupLayer); 0469 // Avoid ugly crossings of cages and blocks in Killer Sudoku. 0470 // Hide borders of blocks if there can be cages in the puzzle. 0471 m_groups[i]->hideBlockBorder (hasBothBlocksAndCages); 0472 } 0473 for(int i = 0; i < g->cageCount(); ++i) { 0474 // Create a cage: draw the label for all cages except size 1. 0475 initCageGroup (i, (g->cage(i).size() > 1)); 0476 } 0477 0478 m_cursor = new QGraphicsPixmapItem(); 0479 addItem(m_cursor); 0480 0481 hover(m_cursorPos); 0482 0483 connect(m_game.interface(), SIGNAL(cellChange(int)), this, SLOT(update(int))); 0484 connect(m_game.interface(), SIGNAL(fullChange()), this, SLOT(update())); 0485 connect(m_game.interface(), &GameIFace::cageChange, this, &View2DScene::updateCage); 0486 connect(m_gameActions, &GameActions::selectValue, this, &View2DScene::selectValue); 0487 connect(m_gameActions, SIGNAL(enterValue(int)), this, SLOT(enterValue(int))); 0488 connect(m_gameActions, SIGNAL(markValue(int)), this, SLOT(flipMarkValue(int))); 0489 connect(m_gameActions, &GameActions::move, this, &View2DScene::moveCursor); 0490 // Fix bug 188162 by ensuring that all markers, as well as cells, are 0491 // updated and displayed after a Load action. 0492 update(-1); 0493 } 0494 0495 void View2DScene::initCageGroup (int cageNum, bool drawLabel) { 0496 // Set the graphical shape and look of a Mathdoku or KillerSudoku cage. 0497 SKGraph* g = m_game.puzzle()->graph(); 0498 int offset = g->cliqueCount(); 0499 QList<int> idx = g->cage(cageNum); 0500 QList<QPoint> pts = QList<QPoint>(idx.size()); 0501 for(int j = 0; j < idx.size(); ++j) { 0502 pts[j] = QPoint(g->cellPosX(idx[j]), g->cellPosY(idx[j])); 0503 } 0504 m_groups[offset + cageNum] = new GroupGraphicsItem(pts, true); 0505 m_groups[offset + cageNum]->setParentItem(m_groupLayer); 0506 0507 // Set the label of the cage (value and operator), but, in a single-cell 0508 // cage, draw it only during data-entry of the first cell of a cage. 0509 if (! drawLabel) { 0510 m_cells[g->cageTopLeft(cageNum)]->setCageLabel(QString()); 0511 } 0512 else if (drawLabel || (g->cage(cageNum).size() > 1)) { 0513 QString str = QString::number(g->cageValue(cageNum)); 0514 if (g->specificType() == Mathdoku) { // No op shown in KillerSudoku. 0515 str = str + QStringLiteral(" /-x+").mid(g->cageOperator(cageNum), 1); 0516 } 0517 m_cells[g->cageTopLeft(cageNum)]->setCageLabel(str); 0518 } 0519 } 0520 0521 void View2DScene::setSceneSize(const QSize& size) { 0522 // Called from View2D::resizeEvent() and View2D::settingsChanged(). 0523 m_highlightsOn = Settings::showHighlights(); 0524 0525 m_background->setPixmap(Renderer::instance()->renderBackground(size)); 0526 0527 SKGraph* g = m_game.puzzle()->graph(); 0528 setSceneRect(QRectF(0, 0, size.width(), size.height())); 0529 0530 int width = size.width() / (g->sizeX()+1); 0531 int height = size.height() / (g->sizeY()+1); 0532 int grid = qMin(width, height) / 2; 0533 int offsetX = size.width()/2 - g->sizeX()*grid; 0534 int offsetY = size.height()/2 - g->sizeY()*grid; 0535 0536 m_groupLayer->setPos(offsetX, offsetY); 0537 m_cellLayer->setPos(offsetX, offsetY); 0538 0539 for(int i = 0; i < m_game.size(); ++i) { 0540 if(m_cells[i] == nullptr) continue; 0541 m_cells[i]->resize(grid); 0542 } 0543 0544 for (ksudoku::GroupGraphicsItem* group : std::as_const(m_groups)) { 0545 group->resize(grid, m_highlightsOn); 0546 } 0547 0548 m_cursor->setPixmap(Renderer::instance()->renderSpecial(SpecialCursor, grid*2)); 0549 } 0550 0551 void View2DScene::hover(int cell) { 0552 m_cursorPos = cell; 0553 // qCDebug(KSudokuLog) << "hover cell" << cell << m_cells[cell]; 0554 QPoint pos(m_cells[cell]->pos()); 0555 for (GroupGraphicsItem* item : std::as_const(m_groups)) { 0556 item->setHighlight(pos, m_highlightsOn); 0557 } 0558 0559 m_cells[cell]->showCursor(m_cursor); 0560 } 0561 0562 void View2DScene::press(int cell, bool rightButton) { 0563 // IDW TODO - Can we save the type and entry mode just once somewhere? 0564 if (! m_game.puzzle()->hasSolution()) { 0565 // Keying in a puzzle. Is it a Mathdoku or Killer Sudoku type? 0566 // If so, right click ==> delete cage: left click ==> add a cell. 0567 SKGraph * g = m_game.puzzle()->graph(); 0568 SudokuType t = g->specificType(); 0569 if ((t == Mathdoku) || (t == KillerSudoku)) { 0570 // If it is, delete or add a cell in the cage being entered. 0571 if (m_game.addToCage (cell, rightButton ? 32 : 30)) { 0572 return; 0573 } 0574 } 0575 } 0576 // Normal mouse actions for working on any kind of KSudoku puzzle. 0577 if(rightButton) { 0578 m_game.flipMarker(cell, m_selectedValue); 0579 } else { 0580 if(m_game.given(cell)) { 0581 selectValue(m_game.value(cell)); 0582 } else { 0583 m_game.setValue(cell, m_selectedValue); 0584 } 0585 } 0586 0587 } 0588 0589 void View2DScene::update(int cell) { 0590 if(cell < 0) { 0591 for(int i = 0; i < m_game.size(); ++i) { 0592 if(m_cells[i] == nullptr) continue; 0593 update(i); 0594 } 0595 } else { 0596 if(m_cells[cell] == nullptr) return; 0597 CellInfo cellInfo = m_game.cellInfo(cell); 0598 0599 QList<ColoredValue> values; 0600 switch(cellInfo.state()) { 0601 case GivenValue: 0602 // TODO - Implement allCellsLookSame preference. 0603 m_cells[cell]->setType(SpecialCellPreset); 0604 // m_cells[cell]->setType(SpecialCell); // IDW test. 0605 if(cellInfo.value()) values << ColoredValue(cellInfo.value(),0); 0606 m_cells[cell]->setValues(values); 0607 break; 0608 case CorrectValue: 0609 m_cells[cell]->setType(SpecialCell); 0610 if(cellInfo.value()) values << ColoredValue(cellInfo.value(),0); 0611 m_cells[cell]->setValues(values); 0612 break; 0613 case WrongValue: 0614 case ObviouslyWrong: 0615 m_cells[cell]->setType(SpecialCellMistake); 0616 if(cellInfo.value()) values << ColoredValue(cellInfo.value(),0); 0617 m_cells[cell]->setValues(values); 0618 break; 0619 case Marker: { 0620 m_cells[cell]->setType(SpecialCellMarkers); 0621 for(int i = 1; i <= m_game.size(); ++i) { 0622 if(cellInfo.marker(i)) 0623 values << ColoredValue(i,0); 0624 } 0625 m_cells[cell]->setValues(values); 0626 } break; 0627 } 0628 } 0629 } 0630 0631 void View2DScene::updateCage (int cageNumP1, bool drawLabel) { 0632 if (cageNumP1 == 0) { 0633 qCDebug(KSudokuLog) << "ERROR: View2DScene::updateCage: cageNumP1 == 0."; 0634 return; 0635 } 0636 SKGraph* g = m_game.puzzle()->graph(); 0637 int offset = g->cliqueCount(); 0638 bool deleting = (cageNumP1 < 0); 0639 int cageNum = deleting ? (-cageNumP1 - 1) : (cageNumP1 - 1); 0640 if ((cageNum >= 0) && (m_groups.count() > (offset + cageNum))){ 0641 // Remove the cage-label from its scene-cell item. 0642 m_cells[g->cageTopLeft(cageNum)]->setCageLabel(QString()); 0643 // Remove the cage-graphics from the scene. 0644 removeItem (m_groups.at (offset + cageNum)); 0645 delete m_groups.at (offset + cageNum); 0646 m_groups[offset + cageNum] = nullptr; // Re-use or remove this later. 0647 } 0648 else { 0649 // Start adding a cage-group to the scene. 0650 m_groups.resize(g->cliqueCount() + g->cageCount()); 0651 } 0652 if (!deleting && (cageNum >= 0)) { 0653 // Create or re-create the cage that is being entered in. 0654 initCageGroup (cageNum, drawLabel); 0655 // IDW TODO - Should NOT need hilite settings, NOT hilite row/col. 0656 // IDW TODO - May need to doctor LOCAL CLASS GroupGraphicsItem for 0657 // hilite, deletion and removal of cage-label to happen. 0658 m_groups[offset + cageNum]->setHighlight (true); 0659 } 0660 else { 0661 // Deleting a cage: finish removing graphics item from scene-data. 0662 m_groups.remove (offset + cageNum); 0663 } 0664 0665 // Invoke the method in View2DScene that triggers a re-draw. 0666 setSceneSize (views().constFirst()->size()); 0667 } 0668 0669 void View2DScene::selectValue(int value) { 0670 m_selectedValue = value; 0671 Q_EMIT valueSelected( value ); 0672 } 0673 0674 void View2DScene::enterValue(int value, int cell) { 0675 if(value >= 0) { 0676 if(cell >= 0) { 0677 m_game.setValue(cell, value); 0678 } else { 0679 m_game.setValue(m_cursorPos, value); 0680 } 0681 } else { 0682 if(cell >= 0) { 0683 m_game.setValue(cell, m_selectedValue); 0684 } else { 0685 m_game.setValue(m_cursorPos, m_selectedValue); 0686 } 0687 } 0688 } 0689 0690 void View2DScene::markValue(int value, int cell, bool set) { 0691 if(value >= 0) { 0692 if(cell >= 0) { 0693 m_game.setMarker(cell, value, set); 0694 } else { 0695 m_game.setMarker(m_cursorPos, value, set); 0696 } 0697 } else { 0698 if(cell >= 0) { 0699 m_game.setMarker(cell, m_selectedValue, set); 0700 } else { 0701 m_game.setMarker(m_cursorPos, m_selectedValue, set); 0702 } 0703 } 0704 } 0705 0706 void View2DScene::flipMarkValue(int value, int cell) { 0707 if(value >= 0) { 0708 if(cell >= 0) { 0709 m_game.flipMarker(cell, value); 0710 } else { 0711 m_game.flipMarker(m_cursorPos, value); 0712 } 0713 } else { 0714 if(cell >= 0) { 0715 m_game.flipMarker(cell, m_selectedValue); 0716 } else { 0717 m_game.flipMarker(m_cursorPos, m_selectedValue); 0718 } 0719 } 0720 } 0721 0722 void View2DScene::moveCursor(int dx, int dy) { 0723 SKGraph* g = m_game.puzzle()->graph(); 0724 QPoint oldPos = m_cells[m_cursorPos]->pos(); 0725 QPoint relPos; 0726 int newCursorPos = -1; 0727 if(dx < 0) relPos.setX(-1); 0728 else if(dx > 0) relPos.setX(+1); 0729 if(dy < 0) relPos.setY(-1); 0730 else if(dy > 0) relPos.setY(+1); 0731 0732 QPoint newPos = oldPos + relPos; 0733 while(newPos != oldPos && newCursorPos == -1) { 0734 if(newPos.x() < 0) newPos.setX(g->sizeX()-1); 0735 if(newPos.x() >= g->sizeX()) newPos.setX(0); 0736 if(newPos.y() < 0) newPos.setY(g->sizeY()-1); 0737 if(newPos.y() >= g->sizeY()) newPos.setY(0); 0738 0739 for(int i = 0; i < m_game.size(); ++i) { 0740 if(m_cells[i] == nullptr) continue; 0741 if(m_cells[i]->pos() == newPos) { 0742 newCursorPos = i; 0743 break; 0744 } 0745 } 0746 0747 newPos += relPos; 0748 } 0749 if(newCursorPos >= 0) 0750 hover(newCursorPos); 0751 } 0752 0753 void View2DScene::wheelEvent(QGraphicsSceneWheelEvent* event) { 0754 if(event->orientation() != Qt::Vertical) return; 0755 0756 if(event->delta() < 0) { 0757 m_selectedValue++; 0758 if(m_selectedValue > m_game.order()) 0759 m_selectedValue = 1; 0760 } else if(event->delta() > 0) { 0761 m_selectedValue--; 0762 if(m_selectedValue < 1) 0763 m_selectedValue = m_game.order(); 0764 } 0765 Q_EMIT valueSelected(m_selectedValue); 0766 } 0767 0768 0769 View2D::View2D(QWidget *parent, const Game& game, GameActions* gameActions) : QGraphicsView(parent) { 0770 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0771 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0772 setFrameStyle(QFrame::NoFrame); 0773 setAlignment( Qt::AlignLeft | Qt::AlignTop ); 0774 0775 m_scene = new View2DScene(gameActions); 0776 m_scene->init(game); 0777 setScene(m_scene); 0778 0779 gameActions->associateWidget(this); 0780 0781 connect(m_scene, &View2DScene::valueSelected, this, &View2D::valueSelected); 0782 } 0783 0784 View2D::~View2D() { 0785 delete m_scene; 0786 } 0787 0788 void View2D::resizeEvent(QResizeEvent* e) { 0789 if(e) QGraphicsView::resizeEvent(e); 0790 0791 if(m_scene) m_scene->setSceneSize(size()); 0792 } 0793 0794 void View2D::selectValue(int value) { 0795 if(!m_scene) return; 0796 0797 m_scene->selectValue(value); 0798 } 0799 0800 void View2D::settingsChanged() { 0801 m_scene->setSceneSize(size()); 0802 } 0803 0804 } 0805 0806 #include "moc_view2d.cpp"