1/*
2 * Copyright (c) 2010 Ni Hui <shuizhuyuanluo@126.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "gamescene.h"
20
21#include "piece.h"
22#include "settings.h"
23#include "undo.h"
24
25#include <KConfigGroup>
26#include <KGamePopupItem>
27#include <KGameRenderedObjectItem>
28#include <KgTheme>
29#include <KgThemeProvider>
30#include <KNotification>
31#include <KRandomSequence>
32#include <KLocale>
33
34#include <KDebug>
35
36#include <QEasingCurve>
37#include <QGraphicsColorizeEffect>
38#include <QPainter>
39#include <QParallelAnimationGroup>
40#include <QPropertyAnimation>
41#include <QSequentialAnimationGroup>
42
43static KgThemeProvider* provider()
44{
45 //TODO: Do we want to store separate theme choices for Klickety and KSame?
46 const QLatin1String defaultTheme =
47 Settings::self()->config()->name() == QLatin1String("ksamerc")
48 ? QLatin1String("ksame") : QLatin1String("default");
49 KgThemeProvider* prov = new KgThemeProvider;
50 prov->discoverThemes("appdata", QLatin1String("themes"), defaultTheme);
51 return prov;
52}
53
54GameScene::GameScene( QObject* parent )
55: QGraphicsScene(parent),
56m_renderer(provider()),
57m_messenger(new KGamePopupItem),
58m_showBoundLines(true),
59m_enableAnimation(true),
60m_enableHighlight(true),
61m_backgroundType(0),
62PWC(0),
63PHC(0),
64m_colorCount(0),
65m_gameId(qrand()),
66m_isPaused(false),
67m_isFinished(false),
68m_animation(new QSequentialAnimationGroup)
69{
70 connect( &m_undoStack, SIGNAL(canUndoChanged(bool)), this, SIGNAL(canUndoChanged(bool)) );
71 connect( &m_undoStack, SIGNAL(canRedoChanged(bool)), this, SIGNAL(canRedoChanged(bool)) );
72 connect( this, SIGNAL(sceneRectChanged(QRectF)), SLOT(resize(QRectF)) );
73
74 connect( m_animation, SIGNAL(finished()), this, SLOT(updateScene()) );
75 connect( m_animation, SIGNAL(finished()), this, SLOT(checkGameFinished()) );
76
77 // init messenger
78 m_messenger->setMessageOpacity( 0.8 );
79 m_messenger->setMessageTimeout( 0 );
80 m_messenger->setHideOnMouseClick( false );
81 addItem( m_messenger );
82 m_messenger->forceHide();
83}
84
85GameScene::~GameScene()
86{
87 qDeleteAll( m_pieces );
88 delete m_animation;
89}
90
91void GameScene::startNewGame( int pwc, int phc, int colorCount, int gameId )
92{
93 PWC = pwc;
94 PHC = phc;
95 m_colorCount = colorCount;
96 m_gameId = gameId;
97 m_isPaused = false;
98 m_isFinished = false;
99 m_undoStack.clear();
100
101 for ( int i = 0; i < m_pieces.size(); ++i ) {
102 removeItem( m_pieces[i] );
103 }
104 qDeleteAll( m_pieces );
105 m_pieces.clear();
106
107 // hide messenger if any
108 m_messenger->forceHide();
109
110 // create a default pen
111 QPen pen;
112 pen.setStyle( Qt::SolidLine );
113 pen.setWidth( 1 );
114 pen.setBrush( Qt::black );
115 pen.setCapStyle( Qt::RoundCap );
116 pen.setJoinStyle( Qt::RoundJoin );
117
118 KRandomSequence s( gameId );
119 for ( int j = 0; j < PHC; ++j ) {
120 for ( int i = 0; i < PWC; ++i ) {
121 // piece item
122 Piece* item = new Piece( &m_renderer, i, j, s.getLong( m_colorCount ) );
123 connect( item, SIGNAL(pieceClicked(int,int)), this, SLOT(removePieces(int,int)) );
124 connect( item, SIGNAL(pieceHovered(int,int)), this, SLOT(highlightPieces(int,int)) );
125 connect( item, SIGNAL(pieceUnhovered(int,int)), this, SLOT(unhighlightPieces(int,int)) );
126 m_pieces << item;
127 addItem( item );
128
129 // set up highlight effects
130 item->m_highlighter->setParentItem( item );
131 item->m_highlighter->hide();
132
133 // bound line item
134 item->m_rightLine->setPen( pen );
135 item->m_bottomLine->setPen( pen );
136 item->m_rightLine->setParentItem( item );
137 item->m_bottomLine->setParentItem( item );
138 item->m_rightLine->hide();
139 item->m_bottomLine->hide();
140 }
141 }
142
143 emit remainCountChanged( pwc * phc );
144
145 updateScene();
146}
147
148void GameScene::loadGame( const KConfigGroup& config )
149{
150 int pwc = config.readEntry( "PWC", -1 );
151 int phc = config.readEntry( "PHC", -1 );
152 int colorCount = config.readEntry( "ColorCount", -1 );
153 int gameId = config.readEntry( "GameId", -1 );
154 if ( pwc == -1 || phc == -1 || colorCount == -1 || gameId == -1 ) {
155 qWarning() << "Unexpected game parameters.";
156 return;
157 }
158 startNewGame( pwc, phc, colorCount, gameId );
159
160 int moveCount = config.readEntry( "MoveCount", 0 );
161 int undoIndex = config.readEntry( "UndoIndex", 0 );
162 if ( undoIndex > moveCount ) {
163 qWarning() << "Unexpected undo history structure.";
164 return;
165 }
166
167 // disable animation temporarily
168 bool enableAnimationOld = m_enableAnimation;
169 setEnableAnimation( false );
170
171 // execute the history
172 for ( int i = 0; i < moveCount; ++i ) {
173 QList<int> move = config.readEntry( QString( QLatin1String( "Move_%1" ) ).arg( i ), QList<int>() );
174 if ( move.count() != 2 ) {
175 qWarning() << "Unexpected undo command structure.";
176 return;
177 }
178 int x = move.at( 0 );
179 int y = move.at( 1 );
180 if ( x < 0 || x >= pwc || y < 0 || y >= phc ) {
181 qWarning() << "Unexpected undo command logic. Skip it.";
182 continue;
183 }
184
185 removePieces( x, y );
186 }
187
188 // undo
189 for ( int i = 0; i < moveCount - undoIndex; ++i ) {
190 undoMove();
191 }
192
193 setEnableAnimation( enableAnimationOld );
194
195 emit remainCountChanged( currentRemainCount() );
196}
197
198void GameScene::saveGame( KConfigGroup& config ) const
199{
200 config.writeEntry( "PWC", PWC );
201 config.writeEntry( "PHC", PHC );
202 config.writeEntry( "ColorCount", m_colorCount );
203 config.writeEntry( "GameId", m_gameId );
204
205 // save undostack
206 int moveCount = m_undoStack.count();
207 int undoIndex = m_undoStack.index();
208 config.writeEntry( "MoveCount", moveCount );
209 config.writeEntry( "UndoIndex", undoIndex );
210 for ( int i = 0; i < moveCount; ++i ) {
211 const QUndoCommand* cmd = m_undoStack.command( i );
212 // the first child should be the user click
213 const QUndoCommand* click = cmd->child( 0 );
214 if ( click->id() != ID_HIDEPIECE ) {
215 qWarning() << "Unexpected command id.";
216 return;
217 }
218 Piece* clickPiece = static_cast<const HidePiece*>(click)->m_piece;
219 QList<int> move;
220 move << clickPiece->m_x << clickPiece->m_y;
221 config.writeEntry( QString( QLatin1String( "Move_%1" ) ).arg( i ), move );
222 }
223}
224
225void GameScene::restartGame()
226{
227 startNewGame( PWC, PHC, m_colorCount, m_gameId );
228}
229
230void GameScene::setPaused( bool isPaused )
231{
232 if ( m_isPaused == isPaused )
233 return;
234
235 m_isPaused = isPaused;
236
237 // hide or unhide all the enabled pieces
238 for ( int j = 0; j < PHC; ++j ) {
239 for ( int i = 0; i < PWC; ++i ) {
240 Piece* item = m_pieces[j*PWC+i];
241 if ( item->isEnabled() )
242 item->setVisible( !m_isPaused );
243 }
244 }
245
246 if ( m_isPaused ) {
247 m_messenger->showMessage( i18n( "paused" ), KGamePopupItem::Center );
248 emit canUndoChanged( false );
249 emit canRedoChanged( false );
250 }
251 else {
252 m_messenger->forceHide();
253 emit canUndoChanged( m_undoStack.canUndo() );
254 emit canRedoChanged( m_undoStack.canRedo() );
255 }
256}
257
258bool GameScene::isGameFinished() const
259{
260 if ( m_pieces.isEmpty() || m_undoStack.isClean() )
261 return true;
262
263 for ( int j = 0; j < PHC; ++j ) {
264 for ( int i = 0; i < PWC; ++i ) {
265 Piece* item = m_pieces[j*PWC+i];
266 // check same color neighbors, rightside and downside
267 if ( !item->isEnabled() )
268 continue;
269 int rightX = i + 1;
270 int downY = j + 1;
271 if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled()
272 && m_pieces[j*PWC+rightX]->m_color == item->m_color ) {
273 return false;
274 }
275 if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled()
276 && m_pieces[downY*PWC+i]->m_color == item->m_color ) {
277 return false;
278 }
279 }
280 }
281 return true;
282}
283
284KgThemeProvider* GameScene::themeProvider() const
285{
286 return m_renderer.themeProvider();
287}
288
289void GameScene::setBackgroundType( int type )
290{
291 m_backgroundType = type;
292 // update background immediately
293 invalidate( sceneRect(), QGraphicsScene::BackgroundLayer );
294}
295
296void GameScene::setShowBoundLines( bool isShowing )
297{
298 if ( m_showBoundLines != isShowing ) {
299 m_showBoundLines = isShowing;
300 // update bound lines immediately
301 updateBoundLines();
302 }
303}
304
305void GameScene::setEnableAnimation( bool isEnabled )
306{
307 m_enableAnimation = isEnabled;
308}
309
310void GameScene::setEnableHighlight( bool isEnabled )
311{
312 m_enableHighlight = isEnabled;
313}
314
315void GameScene::undoMove()
316{
317 unhighlightPieces( m_currentlyHoveredPieceX, m_currentlyHoveredPieceY );
318 m_undoStack.undo();
319 emit remainCountChanged( currentRemainCount() );
320 updateScene();
321}
322
323void GameScene::redoMove()
324{
325 m_undoStack.redo();
326 emit remainCountChanged( currentRemainCount() );
327 updateScene();
328}
329
330void GameScene::undoAllMove()
331{
332 while ( m_undoStack.canUndo() )
333 m_undoStack.undo();
334 emit remainCountChanged( currentRemainCount() );
335 updateScene();
336}
337
338void GameScene::redoAllMove()
339{
340 while ( m_undoStack.canRedo() )
341 m_undoStack.redo();
342 emit remainCountChanged( currentRemainCount() );
343 updateScene();
344}
345
346void GameScene::checkGameFinished()
347{
348 int remain = currentRemainCount();
349 emit remainCountChanged( remain );
350 bool finished = isGameFinished();
351 if ( finished && m_isFinished != finished ) {
352 KNotification::event( QLatin1String( "gamefinished" ) );
353 m_messenger->showMessage( i18n( "game finished" ) , KGamePopupItem::Center );
354 emit canUndoChanged( false );
355 emit canRedoChanged( false );
356 emit gameFinished( remain );
357 }
358 m_isFinished = finished;
359}
360
361void GameScene::traverseNeighbors( int x, int y, int color, bool (GameScene::*func)(Piece*) )
362{
363 if ( x < 0 || x >= PWC || y < 0 || y >= PHC )
364 return;
365
366 int index = y * PWC + x;
367 if ( m_pieces[index]->m_color == color ) {
368 if ( (this->*func)( m_pieces[index] ) ) {
369 traverseNeighbors( x-1, y, color, func );// check left neighbor
370 traverseNeighbors( x, y-1, color, func );// check up neighbor
371 traverseNeighbors( x+1, y, color, func );// check right neighbor
372 traverseNeighbors( x, y+1, color, func );// check down neighbor
373 }
374 }
375}
376
377bool GameScene::highlightPiece( Piece* p )
378{
379 if ( !p->isEnabled() || p->m_highlighter->isVisible() )
380 return false;
381 p->m_highlighter->show();
382 return true;
383}
384
385bool GameScene::unhighlightPiece( Piece* p )
386{
387 if ( !p->isEnabled() || !p->m_highlighter->isVisible() )
388 return false;
389 p->m_highlighter->hide();
390 return true;
391}
392
393bool GameScene::removePiece( Piece* p )
394{
395 if ( !p->isEnabled() )
396 return false;
397 m_undoStack.push( new HidePiece( p ) );
398 return true;
399}
400
401bool GameScene::canRemovePiece( int x, int y )
402{
403 int index = y * PWC + x;
404 int color = m_pieces[index]->m_color;
405
406 int leftX = x - 1;
407 int rightX = x + 1;
408 int upY = y - 1;
409 int downY = y + 1;
410 return ( leftX >= 0 && m_pieces[y*PWC+leftX]->m_color == color && m_pieces[y*PWC+leftX]->isEnabled() )
411 || ( rightX < PWC && m_pieces[y*PWC+rightX]->m_color == color && m_pieces[y*PWC+rightX]->isEnabled() )
412 || ( upY >= 0 && m_pieces[upY*PWC+x]->m_color == color && m_pieces[upY*PWC+x]->isEnabled() )
413 || ( downY < PHC && m_pieces[downY*PWC+x]->m_color == color && m_pieces[downY*PWC+x]->isEnabled() );
414}
415
416void GameScene::highlightPieces( int x, int y )
417{
418 m_currentlyHoveredPieceX = x;
419 m_currentlyHoveredPieceY = y;
420 if ( !m_enableHighlight )
421 return;
422
423 if ( x < 0 || x >= PWC || y < 0 || y >= PHC )
424 return;
425
426 if ( !canRemovePiece( x, y ) )
427 return;
428
429 int index = y * PWC + x;
430 m_pieces[index]->m_highlighter->show();
431 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check left neighbor
432 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check up neighbor
433 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check right neighbor
434 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check down neighbor
435
436 emit markedCountChanged( currentMarkedCount() );
437}
438
439void GameScene::unhighlightPieces( int x, int y )
440{
441 if ( x < 0 || x >= PWC || y < 0 || y >= PHC )
442 return;
443
444 if ( !canRemovePiece( x, y ) )
445 return;
446
447 int index = y * PWC + x;
448 m_pieces[index]->m_highlighter->hide();
449
450 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check left neighbor
451 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check up neighbor
452 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check right neighbor
453 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check down neighbor
454
455 emit markedCountChanged( currentMarkedCount() );
456}
457
458void GameScene::removePieces( int x, int y )
459{
460 if ( x < 0 || x >= PWC || y < 0 || y >= PHC )
461 return;
462
463 if ( !canRemovePiece( x, y ) )
464 return;
465
466 // unhighlight pieces
467 unhighlightPieces( x, y );
468
469 int index = y * PWC + x;
470 m_undoStack.beginMacro( QLatin1String( "Remove pieces" ) );
471 m_undoStack.push( new HidePiece( m_pieces[index] ) );
472
473 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check left neighbor
474 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::removePiece );// check up neighbor
475 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check right neighbor
476 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::removePiece );// check down neighbor
477
478 const int elementsSize1 = sceneRect().width() / PWC;
479 const int elementsSize2 = sceneRect().height() / PHC;
480 const int elementsSize = qMin( elementsSize1, elementsSize2 );
481
482 // horizontal center
483 int gameAreaWidth = PWC * elementsSize;
484 int xShift = ( sceneRect().width() - gameAreaWidth ) / 2;
485 // vertical center
486 int gameAreaHeight = PHC * elementsSize;
487 int yShift = ( sceneRect().height() - gameAreaHeight ) / 2;
488
489 QParallelAnimationGroup* gravityAnimationGroup = NULL;
490 QParallelAnimationGroup* removeColumnsAnimationGroup = NULL;
491 if ( m_enableAnimation ) {
492 // remove previous animations if any
493 m_animation->clear();
494 gravityAnimationGroup = new QParallelAnimationGroup;
495 removeColumnsAnimationGroup = new QParallelAnimationGroup;
496 }
497
498 // gravity
499 for ( int i = 0; i < PWC; ++i ) {
500 bool mayNeedMove = false;
501 int floorRow = PHC - 1;
502 for ( int j = PHC-1; j >= 0; --j ) {
503 if ( !m_pieces[ j * PWC + i ]->isEnabled() && !mayNeedMove ) {
504 // found the hidden piece
505 mayNeedMove = true;
506 floorRow = j;
507 }
508 else if ( m_pieces[ j * PWC + i ]->isEnabled() && mayNeedMove ) {
509 // swap the visible one down to floorRow
510 Piece* visiblePiece = m_pieces[ j * PWC + i ];
511 Piece* hiddenPiece = m_pieces[ floorRow * PWC + i ];
512 const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize );
513 const QPointF newpos = hiddenPiece->pos();
514
515 m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[floorRow*PWC+i], oldpos, newpos ) );
516
517 if ( m_enableAnimation ) {
518 // restore old position for proper animation beginning state
519 visiblePiece->setPos( oldpos );
520
521 // 300ms animation is fast enough to prevent a user's resizing during it
522 QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this );
523 animation->setStartValue( oldpos );
524 animation->setEndValue( newpos );
525 animation->setDuration( 250 );
526 animation->setEasingCurve( QEasingCurve::InQuad );
527 gravityAnimationGroup->addAnimation( animation );
528 }
529
530 --floorRow;
531 }
532 }
533 }
534
535 // remove empty columns
536 bool mayNeedMove = false;
537 int floorCol = 0;
538 for ( int i = 0; i < PWC; ++i ) {
539 if ( !m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && !mayNeedMove ) {
540 // found the empty column
541 mayNeedMove = true;
542 floorCol = i;
543 }
544 else if ( m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && mayNeedMove ) {
545 // swap the visible column down to floorCol
546 for ( int j = PHC-1; j >= 0; --j ) {
547 if ( m_pieces[ j * PWC + i ]->isEnabled() ) {
548 Piece* visiblePiece = m_pieces[ j * PWC + i ];
549 Piece* hiddenPiece = m_pieces[ j * PWC + floorCol ];
550 const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize );
551 const QPointF newpos = hiddenPiece->pos();
552
553 m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[j*PWC+floorCol], oldpos, newpos ) );
554
555 if ( m_enableAnimation ) {
556 // restore old position for proper animation beginning state
557 visiblePiece->setPos( oldpos );
558
559 // 300ms animation is fast enough to prevent a user's resizing during it
560 QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this );
561 animation->setStartValue( oldpos );
562 animation->setEndValue( newpos );
563 animation->setDuration( 250 );
564 animation->setEasingCurve( QEasingCurve::InOutQuad );
565 removeColumnsAnimationGroup->addAnimation( animation );
566 }
567 }
568 }
569
570 ++floorCol;
571 }
572 }
573
574 m_undoStack.endMacro();
575 KNotification::event( QLatin1String( "remove" ) );
576
577 if ( m_enableAnimation ) {
578 // add new animations
579 m_animation->addAnimation( gravityAnimationGroup );
580 m_animation->addAnimation( removeColumnsAnimationGroup );
581 m_animation->start( QAbstractAnimation::KeepWhenStopped );
582 // update bound lines if there are no animations
583 if ( m_animation->totalDuration() == 0 ) {
584 updateBoundLines();
585 checkGameFinished();
586 }
587 }
588 else {
589 updateBoundLines();
590 checkGameFinished();
591 }
592}
593
594int GameScene::currentMarkedCount() const
595{
596 int marked = 0;
597 foreach ( const Piece* p, m_pieces ) {
598 if ( p->m_highlighter->isVisible() )
599 ++marked;
600 }
601 return marked;
602}
603
604int GameScene::currentRemainCount() const
605{
606 int remain = 0;
607 foreach ( const Piece* p, m_pieces ) {
608 if ( p->isEnabled() )
609 ++remain;
610 }
611 return remain;
612}
613
614void GameScene::resize( const QRectF& size )
615{
616 const int elementsSize1 = size.width() / PWC;
617 const int elementsSize2 = size.height() / PHC;
618 const int elementsSize = qMin( elementsSize1, elementsSize2 );
619
620 // horizontal center pieces
621 int gameAreaWidth = PWC * elementsSize;
622 int xShift = ( size.width() - gameAreaWidth ) / 2;
623 // vertical center pieces
624 int gameAreaHeight = PHC * elementsSize;
625 int yShift = ( size.height() - gameAreaHeight ) / 2;
626
627 for ( int j = 0; j < PHC; ++j ) {
628 for ( int i = 0; i < PWC; ++i ) {
629 Piece* item = m_pieces[j*PWC+i];
630 item->setRenderSize( QSize(elementsSize,elementsSize) );
631 const QPoint pos( xShift + item->m_x * elementsSize, yShift + item->m_y * elementsSize );
632 item->setPos( pos );
633 item->m_highlighter->setRenderSize( QSize(elementsSize,elementsSize) );
634 item->m_highlighter->setPos( 0, 0 );
635 }
636 }
637
638 if ( m_showBoundLines )
639 updateBoundLines();
640
641 // center the messenger
642 m_messenger->setPos( size.width() / 2 - m_messenger->boundingRect().width() / 2,
643 size.height() / 2 - m_messenger->boundingRect().height() / 2 );
644}
645
646void GameScene::updateScene()
647{
648 resize( sceneRect() );
649}
650
651void GameScene::updateBoundLines()
652{
653 const int elementsSize1 = sceneRect().width() / PWC;
654 const int elementsSize2 = sceneRect().height() / PHC;
655 const int elementsSize = qMin( elementsSize1, elementsSize2 );
656
657 for ( int j = 0; j < PHC; ++j ) {
658 for ( int i = 0; i < PWC; ++i ) {
659 Piece* item = m_pieces[j*PWC+i];
660 QGraphicsLineItem* rightLine = item->m_rightLine;
661 QGraphicsLineItem* bottomLine = item->m_bottomLine;
662 // draw boarder if necessary, rightside and downside
663 if ( !item->isEnabled() || !m_showBoundLines ) {
664 rightLine->hide();
665 bottomLine->hide();
666 continue;
667 }
668
669 // shift one pixel, otherwise the next piece will overlap our lines
670 int rightX = i + 1;
671 int downY = j + 1;
672 if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled()
673 && m_pieces[j*PWC+rightX]->m_color != item->m_color ) {
674 rightLine->setLine( elementsSize-1, 0-1, elementsSize-1, elementsSize-1 );
675 rightLine->show();
676 }
677 else
678 rightLine->hide();
679 if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled()
680 && m_pieces[downY*PWC+i]->m_color != item->m_color ) {
681 bottomLine->setLine( 0-1, elementsSize-1, elementsSize-1, elementsSize-1 );
682 bottomLine->show();
683 }
684 else
685 bottomLine->hide();
686 }
687 }
688}
689
690void GameScene::drawBackground( QPainter* painter, const QRectF& rect )
691{
692 switch ( m_backgroundType ) {
693 case Settings::EnumBgType::theme: {
694 // NOTE: the following is a workaround for https://bugs.kde.org/show_bug.cgi?id=243573
695 // cache the background pixmap locally in order to reduce the spritePixmap traffic when resizing
696 static QByteArray theme_pre( m_renderer.theme()->identifier() );
697 static QSize size_pre( rect.toRect().size() );
698 static QPixmap pix( m_renderer.spritePixmap( QLatin1String( "BACKGROUND" ), size_pre ) );
699 QSize size_offset = size_pre - rect.toRect().size();
700 if ( size_offset.width() < -100 || size_offset.height() < -100 || theme_pre != m_renderer.theme()->identifier() ) {
701 qWarning() << "export";
702 theme_pre = m_renderer.theme()->identifier();
703 size_pre = rect.toRect().size();
704 pix = m_renderer.spritePixmap( QLatin1String( "BACKGROUND" ), size_pre );
705 painter->drawPixmap( rect.topLeft(), pix );
706 }
707 else {
708 painter->drawPixmap( rect.topLeft(), pix.scaled( rect.toRect().size() ) );
709 }
710 break;
711 }
712 case 3: // the color button
713 case Settings::EnumBgType::color: {
714 painter->fillRect( rect, Settings::bgColor() );
715 break;
716 }
717 case 4: // the image url requester
718 case Settings::EnumBgType::image: {
719 // cache the background image locally in order to reduce the file opening traffic when resizing
720 static QString img_filepath( Settings::bgImage().path() );
721 static QImage img( img_filepath );
722 if ( img_filepath != Settings::bgImage().path() ) {
723 img_filepath = Settings::bgImage().path();
724 img = QImage( img_filepath );
725 }
726 if ( !img.isNull() )
727 painter->drawImage( rect, img );
728 else
729 qWarning() << "Null background image " << Settings::bgImage();
730 break;
731 }
732 default: {
733 // Unexpected background type, no action
734 break;
735 }
736 }
737}
738