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"