1 /*
2     SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
3     SPDX-FileCopyrightText: 2006 Mauricio Piacentini <mauricio@tabuleiro.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 // own
9 #include "editor.h"
10 
11 // Qt
12 #include <QAction>
13 #include <QFileDialog>
14 #include <QFileInfo>
15 #include <QGridLayout>
16 #include <QIcon>
17 #include <QLabel>
18 #include <QPainter>
19 #include <QResizeEvent>
20 
21 // KF
22 #include <KActionCollection>
23 #include <KLocalizedString>
24 #include <KMessageBox>
25 #include <KStandardAction>
26 #include <KToggleAction>
27 
28 // KMahjongg
29 #include "prefs.h"
30 
Editor(QWidget * parent)31 Editor::Editor(QWidget * parent)
32     : QDialog(parent)
33     , m_mode(EditMode::insert)
34     , m_borderLeft(0)
35     , m_borderTop(0)
36     , m_numTiles(0)
37     , m_clean(true)
38     , m_drawFrame(nullptr)
39     , m_tiles()
40     , m_theLabel(nullptr)
41     , m_topToolbar(nullptr)
42     , m_actionCollection(nullptr)
43 {
44     setModal(true);
45 
46     QWidget * mainWidget = new QWidget(this);
47 
48     QVBoxLayout * mainLayout = new QVBoxLayout(this);
49     mainLayout->addWidget(mainWidget);
50 
51     resize(QSize(800, 400));
52 
53     QGridLayout * gridLayout = new QGridLayout(mainWidget);
54     QVBoxLayout * layout = new QVBoxLayout();
55 
56     setupToolbar();
57     layout->addWidget(m_topToolbar);
58 
59     m_drawFrame = new FrameImage(this, QSize(0, 0));
60     m_drawFrame->setFocusPolicy(Qt::NoFocus);
61     m_drawFrame->setMouseTracking(true);
62 
63     layout->addWidget(m_drawFrame);
64     gridLayout->addLayout(layout, 0, 0, 1, 1);
65 
66     //toolbar will set our minimum height
67     setMinimumHeight(120);
68 
69     // tell the user what we do
70     setWindowTitle(i18nc("@title:window", "Edit Board Layout"));
71 
72     connect(m_drawFrame, &FrameImage::mousePressed, this, &Editor::drawFrameMousePressEvent);
73     connect(m_drawFrame, &FrameImage::mouseMoved, this, &Editor::drawFrameMouseMovedEvent);
74 
75     statusChanged();
76 
77     update();
78 }
79 
~Editor()80 Editor::~Editor()
81 {
82 }
83 
updateTileSize(const QSize size)84 void Editor::updateTileSize(const QSize size)
85 {
86     const int width = m_theBoard.getWidth();
87     const int height = m_theBoard.getHeight();
88     const QSize tileSize = m_tiles.preferredTileSize(size, width / 2, height / 2);
89 
90     m_tiles.reloadTileset(tileSize);
91     m_borderLeft = (m_drawFrame->size().width() - (width * m_tiles.qWidth())) / 2;
92     m_borderTop = (m_drawFrame->size().height() - (height * m_tiles.qHeight())) / 2;
93 }
94 
resizeEvent(QResizeEvent * event)95 void Editor::resizeEvent(QResizeEvent * event)
96 {
97     updateTileSize(event->size());
98 }
99 
setupToolbar()100 void Editor::setupToolbar()
101 {
102     m_topToolbar = new KToolBar(QStringLiteral("editToolBar"), this);
103     m_topToolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
104 
105     m_actionCollection = new KActionCollection(this);
106 
107     // new game
108     QAction * newBoard = m_actionCollection->addAction(QStringLiteral("new_board"));
109     newBoard->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
110     newBoard->setText(i18n("New board"));
111     connect(newBoard, &QAction::triggered, this, &Editor::newBoard);
112     m_topToolbar->addAction(newBoard);
113 
114     // open game
115     QAction * openBoard = m_actionCollection->addAction(QStringLiteral("open_board"));
116     openBoard->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
117     openBoard->setText(i18n("Open board"));
118     connect(openBoard, &QAction::triggered, this, &Editor::loadBoard);
119     m_topToolbar->addAction(openBoard);
120 
121     // save game
122     QAction * saveBoard = m_actionCollection->addAction(QStringLiteral("save_board"));
123     saveBoard->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
124     saveBoard->setText(i18n("Save board"));
125     connect(saveBoard, &QAction::triggered, this, &Editor::saveBoard);
126     m_topToolbar->addAction(saveBoard);
127 
128     m_topToolbar->addSeparator();
129 
130 #ifdef FUTURE_OPTIONS
131     // Select
132     QAction * select = actionCollection->addAction(QLatin1String("select"));
133     select->setIcon(QIcon::fromTheme(QLatin1String("rectangle_select")));
134     select->setText(i18n("Select"));
135     topToolbar->addAction(select);
136 
137     QAction * cut = actionCollection->addAction(QLatin1String("edit_cut"));
138     cut->setIcon(QIcon::fromTheme(QLatin1String("edit-cut")));
139     cut->setText(i18n("Cut"));
140     topToolbar->addAction(cut);
141 
142     QAction * copy = actionCollection->addAction(QLatin1String("edit_copy"));
143     copy->setIcon(QIcon::fromTheme(QLatin1String("edit-copy")));
144     copy->setText(i18n("Copy"));
145     topToolbar->addAction(copy);
146 
147     QAction * paste = actionCollection->addAction(QLatin1String("edit_paste"));
148     paste->setIcon(QIcon::fromTheme(QLatin1String("edit-paste")));
149     paste->setText(i18n("Paste"));
150     topToolbar->addAction(paste);
151 
152     topToolbar->addSeparator();
153 
154     QAction * moveTiles = actionCollection->addAction(QLatin1String("move_tiles"));
155     moveTiles->setIcon(QIcon::fromTheme(QLatin1String("move")));
156     moveTiles->setText(i18n("Move tiles"));
157     topToolbar->addAction(moveTiles);
158 #endif
159 
160     KToggleAction * addTiles = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-freehand")), i18n("Add tiles"), this);
161     m_actionCollection->addAction(QStringLiteral("add_tiles"), addTiles);
162     m_topToolbar->addAction(addTiles);
163     KToggleAction * delTiles = new KToggleAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove tiles"), this);
164     m_actionCollection->addAction(QStringLiteral("del_tiles"), delTiles);
165     m_topToolbar->addAction(delTiles);
166 
167     QActionGroup * radioGrp = new QActionGroup(this);
168     radioGrp->setExclusive(true);
169     radioGrp->addAction(addTiles);
170     addTiles->setChecked(true);
171 
172 #ifdef FUTURE_OPTIONS
173     radioGrp->addAction(moveTiles);
174 #endif
175 
176     radioGrp->addAction(delTiles);
177     connect(radioGrp, &QActionGroup::triggered, this, &Editor::slotModeChanged);
178 
179     // board shift
180 
181     m_topToolbar->addSeparator();
182 
183     // NOTE: maybe join shiftActions in QActionGroup and create one slot(QAction*) instead of 4 slots? ;)
184     // Does this makes sense? dimsuz
185     QAction * shiftLeft = m_actionCollection->addAction(QStringLiteral("shift_left"));
186     shiftLeft->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
187     shiftLeft->setText(i18n("Shift left"));
188     connect(shiftLeft, &QAction::triggered, this, &Editor::slotShiftLeft);
189     m_topToolbar->addAction(shiftLeft);
190 
191     QAction * shiftUp = m_actionCollection->addAction(QStringLiteral("shift_up"));
192     shiftUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
193     shiftUp->setText(i18n("Shift up"));
194     connect(shiftUp, &QAction::triggered, this, &Editor::slotShiftUp);
195     m_topToolbar->addAction(shiftUp);
196 
197     QAction * shiftDown = m_actionCollection->addAction(QStringLiteral("shift_down"));
198     shiftDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
199     shiftDown->setText(i18n("Shift down"));
200     connect(shiftDown, &QAction::triggered, this, &Editor::slotShiftDown);
201     m_topToolbar->addAction(shiftDown);
202 
203     QAction * shiftRight = m_actionCollection->addAction(QStringLiteral("shift_right"));
204     shiftRight->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
205     shiftRight->setText(i18n("Shift right"));
206     connect(shiftRight, &QAction::triggered, this, &Editor::slotShiftRight);
207     m_topToolbar->addAction(shiftRight);
208 
209     m_topToolbar->addSeparator();
210     QAction * quit = m_actionCollection->addAction(KStandardAction::Quit, QStringLiteral("quit"));
211     connect(quit, &QAction::triggered, this, &Editor::close);
212     m_topToolbar->addAction(quit);
213 
214     // status in the toolbar for now (ick)
215     QWidget * hbox = new QWidget(m_topToolbar);
216     QHBoxLayout * layout = new QHBoxLayout(hbox);
217     layout->setContentsMargins(0, 0, 0, 0);
218     layout->setSpacing(0);
219     layout->addStretch();
220 
221     m_theLabel = new QLabel(statusText(), hbox);
222     layout->addWidget(m_theLabel);
223     m_topToolbar->addWidget(hbox);
224 
225     m_topToolbar->adjustSize();
226     setMinimumWidth(m_topToolbar->width());
227 }
228 
statusChanged() const229 void Editor::statusChanged() const
230 {
231     const bool canSave = ((m_numTiles != 0) && ((m_numTiles & 1) == 0));
232     m_theLabel->setText(statusText());
233     m_actionCollection->action(QStringLiteral("save_board"))->setEnabled(canSave);
234 }
235 
slotShiftLeft()236 void Editor::slotShiftLeft()
237 {
238     m_theBoard.shiftLeft();
239     update();
240 }
241 
slotShiftRight()242 void Editor::slotShiftRight()
243 {
244     m_theBoard.shiftRight();
245     update();
246 }
247 
slotShiftUp()248 void Editor::slotShiftUp()
249 {
250     m_theBoard.shiftUp();
251     update();
252 }
253 
slotShiftDown()254 void Editor::slotShiftDown()
255 {
256     m_theBoard.shiftDown();
257     update();
258 }
259 
slotModeChanged(QAction * act)260 void Editor::slotModeChanged(QAction * act)
261 {
262     if (act == m_actionCollection->action(QStringLiteral("move_tiles"))) {
263         m_mode = EditMode::move;
264     } else if (act == m_actionCollection->action(QStringLiteral("del_tiles"))) {
265         m_mode = EditMode::remove;
266     } else if (act == m_actionCollection->action(QStringLiteral("add_tiles"))) {
267         m_mode = EditMode::insert;
268     }
269 }
270 
statusText() const271 QString Editor::statusText() const
272 {
273     int x = m_curPos.x;
274     int y = m_curPos.y;
275     int z = m_curPos.z;
276 
277     if (z == 100) {
278         z = 0;
279     } else {
280         z = z + 1;
281     }
282 
283     if (x >= m_theBoard.getWidth() || x < 0 || y >= m_theBoard.getHeight() || y < 0) {
284         x = y = z = 0;
285     }
286 
287     return i18n("Tiles: %1 Pos: %2,%3,%4", m_numTiles, x, y, z);
288 }
289 
loadBoard()290 void Editor::loadBoard()
291 {
292     if (!testSave()) {
293         return;
294     }
295 
296     const QString filename = QFileDialog::getOpenFileName(this, i18n("Open Board Layout"), QString(),
297                                                           i18n("Board Layout (*.layout);;All Files (*)"));
298 
299     if (filename.isEmpty()) {
300         return;
301     }
302 
303     m_theBoard.loadBoardLayout(filename);
304     update();
305 }
306 
newBoard()307 void Editor::newBoard()
308 {
309     // Clear out the contents of the board. Repaint the screen
310     // set values to their defaults.
311 
312     if (!testSave()) {
313         return;
314     }
315 
316     m_theBoard.clearBoardLayout();
317 
318     m_clean = true;
319     m_numTiles = 0;
320 
321     statusChanged();
322     update();
323 }
324 
saveBoard()325 bool Editor::saveBoard()
326 {
327     if (!((m_numTiles != 0) && ((m_numTiles & 1) == 0))) {
328         KMessageBox::sorry(this, i18n("You can only save with a even number of tiles."));
329 
330         return false;
331     }
332 
333     // get a save file name
334     const QString filename = QFileDialog::getSaveFileName(this, i18n("Save Board Layout"), QString(),
335                                                           i18n("Board Layout (*.layout);;All Files (*)"));
336 
337     if (filename.isEmpty()) {
338         return false;
339     }
340 
341     const QFileInfo f(filename);
342     if (f.exists()) {
343         // if it already exists, query the user for replacement
344         int res = KMessageBox::warningContinueCancel(this,
345                                                      i18n("A file with that name already exists. Do you wish to overwrite it?"),
346                                                      i18n("Save Board Layout"), KStandardGuiItem::save());
347 
348         if (res != KMessageBox::Continue) {
349             return false;
350         }
351     }
352 
353     bool result = m_theBoard.saveBoardLayout(filename);
354 
355     if (result == true) {
356         m_clean = true;
357 
358         return true;
359     } else {
360         return false;
361     }
362 }
363 
testSave()364 bool Editor::testSave()
365 {
366     // test if a save is required and return true if the app is to continue
367     // false if cancel is selected. (if ok then call out to save the board
368 
369     if (m_clean) {
370         return true;
371     }
372 
373     const int res = KMessageBox::warningYesNoCancel(this,
374                                                     i18n("The board has been modified. Would you like to save the changes?"),
375                                                     QString(), KStandardGuiItem::save(), KStandardGuiItem::dontSave());
376 
377     if (res == KMessageBox::Yes) {
378         // yes to save
379         if (saveBoard()) {
380             return true;
381         } else {
382             KMessageBox::sorry(this, i18n("Save failed. Aborting operation."));
383 
384             return false;
385         }
386     } else {
387         return (res != KMessageBox::Cancel);
388     }
389     return true;
390 }
391 
paintEvent(QPaintEvent *)392 void Editor::paintEvent(QPaintEvent *)
393 {
394     // The main paint event, draw in the grid and blit in
395     // the tiles as specified by the layout.
396 
397     // first we layer on a background grid
398     QPixmap buff;
399     QPixmap * dest = m_drawFrame->getPreviewPixmap();
400     buff = QPixmap(dest->width(), dest->height());
401     drawBackground(&buff);
402     drawTiles(&buff);
403     QPainter p(dest);
404     p.drawPixmap(0, 0, buff);
405     p.end();
406 
407     m_drawFrame->update();
408 }
409 
drawBackground(QPixmap * pixmap) const410 void Editor::drawBackground(QPixmap * pixmap) const
411 {
412     const int width = m_theBoard.getWidth();
413     const int height = m_theBoard.getHeight();
414     QPainter p(pixmap);
415 
416     // blast in a white background
417     p.fillRect(0, 0, pixmap->width(), pixmap->height(), Qt::white);
418 
419     // now put in a grid of tile quarter width squares
420     for (int y = 0; y <= height; ++y) {
421         int nextY = m_borderTop + (y * m_tiles.qHeight());
422         p.drawLine(m_borderLeft, nextY, m_borderLeft + (width * m_tiles.qWidth()), nextY);
423     }
424 
425     for (int x = 0; x <= width; ++x) {
426         int nextX = m_borderLeft + (x * m_tiles.qWidth());
427         p.drawLine(nextX, m_borderTop, nextX, m_borderTop + (height * m_tiles.qHeight()));
428     }
429 }
430 
drawTiles(QPixmap * dest)431 void Editor::drawTiles(QPixmap * dest)
432 {
433     QPainter p(dest);
434 
435     const int width = m_theBoard.getWidth();
436     const int height = m_theBoard.getHeight();
437     const int depth = m_theBoard.getDepth();
438     const int shadowX = m_tiles.width() - m_tiles.qWidth() * 2 - m_tiles.levelOffsetX();
439     const int shadowY = m_tiles.height() - m_tiles.qHeight() * 2 - m_tiles.levelOffsetY();
440     short tile = 0;
441 
442     int xOffset = -shadowX;
443     int yOffset = -m_tiles.levelOffsetY();
444 
445     // we iterate over the depth stacking order. Each successive level is
446     // drawn one indent up and to the right. The indent is the width
447     // of the 3d relief on the tile left (tile shadow width)
448     for (int z = 0; z < depth; ++z) {
449         // we draw down the board so the tile below over rights our border
450         for (int y = 0; y < height; ++y) {
451             // drawing right to left to prevent border overwrite
452             for (int x = width - 1; x >= 0; --x) {
453                 int sx = x * m_tiles.qWidth() + xOffset + m_borderLeft;
454                 int sy = y * m_tiles.qHeight() + yOffset + m_borderTop;
455 
456                 if (m_theBoard.getBoardData(z, y, x) != '1') {
457                     continue;
458                 }
459 
460                 QPixmap t;
461                 tile = (z * depth) + (y * height) + (x * width);
462                 t = m_tiles.unselectedTile(0);
463 
464                 // Only one complication. Since we render top to bottom , left
465                 // to right situations arise where...:
466                 // there exists a tile one q height above and to the left
467                 // in this situation we would draw our top left border over it
468                 // we simply split the tile draw so the top half is drawn
469                 // minus border
470                 if ((x > 1) && (y > 0) && m_theBoard.getBoardData(z, y - 1, x - 2) == '1') {
471                     p.drawPixmap(sx, sy, t, 0, 0, t.width(), t.height());
472 
473                     p.drawPixmap(sx - m_tiles.qWidth() + shadowX + m_tiles.levelOffsetX(), sy, t,
474                                  t.width() - m_tiles.qWidth(),
475                                  t.height() - m_tiles.qHeight() - m_tiles.levelOffsetX() - shadowY,
476                                  m_tiles.qWidth(), m_tiles.qHeight() + m_tiles.levelOffsetX());
477                 } else {
478                     p.drawPixmap(sx, sy, t, 0, 0, t.width(), t.height());
479                 }
480 
481                 ++tile;
482                 tile = tile % 143;
483             }
484         }
485 
486         xOffset += m_tiles.levelOffsetX();
487         yOffset -= m_tiles.levelOffsetY();
488     }
489 }
490 
transformPointToPosition(const QPoint & point,POSITION & mouseClickPos,bool align) const491 void Editor::transformPointToPosition(const QPoint & point, POSITION & mouseClickPos, bool align) const
492 {
493     // convert mouse position on screen to a tile z y x coord
494     // different to the one in kmahjongg.cpp since if we hit ground
495     // we return a result too.
496 
497     short z = 0;
498     short y = 0;
499     short x = 0;
500     mouseClickPos.z = 100;
501 
502     // iterate over z coordinate from top to bottom
503     for (z = m_theBoard.getDepth() - 1; z >= 0; --z) {
504         // calculate mouse coordinates --> position in game board
505         // the factor -theTiles.width()/2 must keep track with the
506         // offset for blitting in the print event (FIX ME)
507         x = ((point.x() - m_borderLeft) - (z + 1) * m_tiles.levelOffsetX()) / m_tiles.qWidth();
508         y = ((point.y() - m_borderTop) + z * m_tiles.levelOffsetX()) / m_tiles.qHeight();
509 
510         // skip when position is illegal
511         if (x < 0 || x >= m_theBoard.getWidth() || y < 0 || y >= m_theBoard.getHeight()) {
512             continue;
513         }
514 
515         switch (m_theBoard.getBoardData(z, y, x)) {
516             case static_cast<UCHAR>('3'):
517                 if (align) {
518                     --x;
519                     --y;
520                 }
521 
522                 break;
523 
524             case static_cast<UCHAR>('2'):
525                 if (align) {
526                     --x;
527                 }
528 
529                 break;
530 
531             case static_cast<UCHAR>('4'):
532                 if (align) {
533                     --y;
534                 }
535 
536                 break;
537 
538             case static_cast<UCHAR>('1'):
539                 break;
540 
541             default:
542                 continue;
543         }
544 
545         // if gameboard is empty, skip
546         if (!m_theBoard.getBoardData(z, y, x)) {
547             continue;
548         }
549 
550         // here, position is legal
551         mouseClickPos.z = z;
552         mouseClickPos.y = y;
553         mouseClickPos.x = x;
554         mouseClickPos.f = m_theBoard.getBoardData(z, y, x);
555 
556         break;
557     }
558 
559     if (mouseClickPos.z == 100) {
560         mouseClickPos.x = x;
561         mouseClickPos.y = y;
562         mouseClickPos.f = 0;
563     }
564 }
565 
drawFrameMousePressEvent(QMouseEvent * e)566 void Editor::drawFrameMousePressEvent(QMouseEvent * e)
567 {
568     // we swallow the draw frames mouse clicks and process here
569 
570     POSITION mousePos;
571     transformPointToPosition(e->pos(), mousePos, (m_mode == EditMode::remove));
572 
573     switch (m_mode) {
574         case EditMode::remove:
575             if (!m_theBoard.tileAbove(mousePos) && mousePos.z < m_theBoard.getDepth() && m_theBoard.isTileAt(mousePos)) {
576                 m_theBoard.deleteTile(mousePos);
577                 --m_numTiles;
578                 statusChanged();
579                 drawFrameMouseMovedEvent(e);
580                 update();
581             }
582 
583             break;
584         case EditMode::insert: {
585             POSITION n = mousePos;
586 
587             if (n.z == 100) {
588                 n.z = 0;
589             } else {
590                 n.z += 1;
591             }
592 
593             if (canInsert(n)) {
594                 m_theBoard.insertTile(n);
595                 m_clean = false;
596                 ++m_numTiles;
597                 statusChanged();
598                 update();
599             }
600 
601             break;
602         }
603         default:
604             break;
605     }
606 }
607 
drawCursor(POSITION & p,bool visible)608 void Editor::drawCursor(POSITION & p, bool visible)
609 {
610     int x = m_borderLeft + (p.z * m_tiles.levelOffsetX()) + (p.x * m_tiles.qWidth());
611     const int y = m_borderTop - ((p.z + 1) * m_tiles.levelOffsetY()) + (p.y * m_tiles.qHeight());
612     const int w = (m_tiles.qWidth() * 2) + m_tiles.levelOffsetX();
613     const int h = (m_tiles.qHeight() * 2) + m_tiles.levelOffsetY();
614 
615     if (p.z == 100 || !visible) {
616         x = -1;
617     }
618 
619     m_drawFrame->setRect(x, y, w, h, m_tiles.levelOffsetX(), static_cast<int>(m_mode) - static_cast<int>(EditMode::remove));
620     m_drawFrame->update();
621 }
622 
drawFrameMouseMovedEvent(QMouseEvent * e)623 void Editor::drawFrameMouseMovedEvent(QMouseEvent * e)
624 {
625     // we swallow the draw frames mouse moves and process here
626 
627     POSITION mousePos;
628     transformPointToPosition(e->pos(), mousePos, (m_mode == EditMode::remove));
629 
630     if ((mousePos.x == m_curPos.x) && (mousePos.y == m_curPos.y) && (mousePos.z == m_curPos.z)) {
631         return;
632     }
633 
634     m_curPos = mousePos;
635 
636     statusChanged();
637 
638     switch (m_mode) {
639         case EditMode::insert: {
640             POSITION next;
641             next = m_curPos;
642 
643             if (next.z == 100) {
644                 next.z = 0;
645             } else {
646                 next.z += 1;
647             }
648 
649             drawCursor(next, canInsert(next));
650 
651             break;
652         }
653         case EditMode::remove:
654             drawCursor(m_curPos, 1);
655 
656             break;
657         case EditMode::move:
658             break;
659     }
660 }
661 
canInsert(POSITION & p) const662 bool Editor::canInsert(POSITION & p) const
663 {
664     // can we inser a tile here. We can iff
665     // there are tiles in all positions below us (or we are a ground level)
666     // there are no tiles intersecting with us on this level
667 
668     if (p.z >= m_theBoard.getDepth()) {
669         return false;
670     }
671 
672     if (p.y > m_theBoard.getHeight() - 2) {
673         return false;
674     }
675 
676     if (p.x > m_theBoard.getWidth() - 2) {
677         return false;
678     }
679 
680     POSITION n = p;
681 
682     if (p.z != 0) {
683         n.z -= 1;
684         if (!m_theBoard.allFilled(n)) {
685             return false;
686         }
687     }
688 
689     return !m_theBoard.anyFilled(p);
690 }
691 
closeEvent(QCloseEvent * e)692 void Editor::closeEvent(QCloseEvent * e)
693 {
694     if (testSave()) {
695         m_theBoard.clearBoardLayout();
696         m_clean = true;
697         m_numTiles = 0;
698         statusChanged();
699         update();
700 
701         // Save the window geometry.
702         Prefs::setEditorGeometry(geometry());
703         Prefs::self()->save();
704 
705         e->accept();
706     } else {
707         e->ignore();
708     }
709 }
710 
setTilesetFromSettings()711 void Editor::setTilesetFromSettings()
712 {
713     const QString tileset(Prefs::tileSet());
714 
715     // Exit if the tileset is already set.
716     if (tileset == m_tileset) {
717         return;
718     }
719 
720     // Try to load the new tileset.
721     if (!m_tiles.loadTileset(tileset)) {
722         // Try to load the old one.
723         if (!m_tiles.loadTileset(m_tileset)) {
724             m_tiles.loadDefault();
725         }
726     } else {
727         // If loading the new tileset was ok, set the new tileset name.
728         m_tileset = tileset;
729     }
730 
731     // Must be called to load the graphics and its information.
732     m_tiles.loadGraphics();
733 
734     updateTileSize(size());
735 }
736