1 /***************************************************************************
2 							 BoardView - view of the current board
3 									  -------------------
4 	 begin                : Sun 21 Aug 2005
5 	 copyright            : (C) 2005-2007 Michal Rudolf <mrudolf@kdewebdev.org>
6  ***************************************************************************/
7 
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "boardview.h"
18 #include "settings.h"
19 #include "guess.h"
20 #include "move.h"
21 
22 #include <QApplication>
23 #include <QSizePolicy>
24 
25 #if defined(_MSC_VER) && defined(_DEBUG)
26 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
27 #define new DEBUG_NEW
28 #endif // _MSC_VER
29 
30 using namespace Qt;
31 using namespace chessx;
32 
33 const int CoordinateSize = 16;
34 const int MoveIndicatorSize = 12;
35 
BoardView(QWidget * parent,int flags)36 BoardView::BoardView(QWidget* parent, int flags) : QWidget(parent),
37     m_flipped(false), m_showFrame(false), m_showCurrentMove(2),
38     m_guessMove(false), m_showThreat(false), m_showTargets(false), m_brushMode(false), m_selectedSquare(InvalidSquare),
39     m_hoverSquare(InvalidSquare),
40     m_hiFrom(InvalidSquare), m_hiTo(InvalidSquare),
41     m_currentFrom(InvalidSquare), m_currentTo(InvalidSquare),
42     m_storedFrom(InvalidSquare), m_storedTo(InvalidSquare),
43     m_dragStartSquare(InvalidSquare),
44     m_atLineEnd(true),
45     m_flags(flags),
46     m_coordinates(false), m_dragged(Empty), m_clickUsed(false), m_wheelCurrentDelta(0),
47     m_minDeltaWheel(0), m_moveListCurrent(0), m_showMoveIndicator(true), m_showMoveIndicatorMode(0),
48     m_DbIndex(nullptr),
49     m_showAttacks(NoColor),
50     m_showUnderProtection(NoColor),
51     lastMoveEvent(nullptr)
52 {
53     QSizePolicy policy = sizePolicy();
54     policy.setHeightForWidth(true);
55     setSizePolicy(policy);
56     setMouseTracking(true);
57     installEventFilter(this);
58     setFocusPolicy( Qt::StrongFocus );
59 
60     m_bestGuess.setNullMove();
61     m_threatGuess.setThinkTime(500);
62 
63     connect(&m_threatGuess, SIGNAL(guessFoundForBoard(Guess::Result, BoardX)),
64             this, SLOT(showThreat(Guess::Result,BoardX)), Qt::QueuedConnection);
65 }
66 
~BoardView()67 BoardView::~BoardView()
68 {
69     removeEventFilter(this);
70     m_threatGuess.cancel();
71     delete lastMoveEvent;
72 }
73 
heightForWidth(int width) const74 int BoardView::heightForWidth(int width) const
75 {
76     int coord = m_coordinates ? CoordinateSize : 0;
77     int moveIndicator = (m_showMoveIndicatorMode==Always) ? MoveIndicatorSize:0;
78     int xsize = (width - 1 - coord - moveIndicator) / 8;
79 
80     int square = moveIndicatorWidth(width, xsize);
81     return (width - square);
82 }
83 
eventFilter(QObject * obj,QEvent * ev)84 bool BoardView::eventFilter(QObject *obj, QEvent *ev)
85 {
86     if(ev->type() == QEvent::Leave || ev->type() == QEvent::WindowDeactivate)
87     {
88         removeGuess();
89     }
90     return QWidget::eventFilter(obj, ev);
91 }
92 
setFlags(int flags)93 void BoardView::setFlags(int flags)
94 {
95     m_flags = flags;
96 }
97 
flags() const98 int BoardView::flags() const
99 {
100     return m_flags;
101 }
102 
setBoard(const BoardX & value,Square from,Square to,bool atLineEnd)103 void BoardView::setBoard(const BoardX& value, Square from, Square to, bool atLineEnd)
104 {
105     m_clickUsed = true;
106     m_board = value;
107     m_currentFrom = from;
108     m_currentTo = to;
109     m_atLineEnd = atLineEnd;
110     m_hiFrom = m_hiTo = InvalidSquare;
111     m_alertSquare = value.kingInCheck();
112     m_targets.clear();
113     m_bestGuess.setNullMove();
114     if(underMouse())
115     {
116         updateGuess(m_hoverSquare);
117     }
118     updateThreat();
119     repaint();
120 }
121 
setStoredMove(Square from,Square to)122 void BoardView::setStoredMove(Square from, Square to)
123 {
124     m_storedFrom = from;
125     m_storedTo = to;
126     update();
127 }
128 
getStoredMove(Square & from,Square & to)129 void BoardView::getStoredMove(Square& from, Square& to)
130 {
131     from = m_storedFrom;
132     to = m_storedTo;
133     m_storedFrom = InvalidSquare;
134     m_storedTo = InvalidSquare;
135     update();
136 }
137 
board() const138 BoardX BoardView::board() const
139 {
140     return m_board;
141 }
142 
theme() const143 const BoardTheme& BoardView::theme() const
144 {
145     return m_theme;
146 }
147 
themeSize() const148 QSize BoardView::themeSize() const
149 {
150     return m_theme.size();
151 }
152 
showMoveIndicator(bool visible)153 void BoardView::showMoveIndicator(bool visible)
154 {
155     m_showMoveIndicator = visible;
156 }
157 
showMoveIndicator() const158 bool BoardView::showMoveIndicator() const
159 {
160     return m_showMoveIndicator;
161 }
162 
showCoordinates(bool visible)163 void BoardView::showCoordinates(bool visible)
164 {
165     m_coordinates = visible;
166 }
167 
showCoordinates() const168 bool BoardView::showCoordinates() const
169 {
170     return m_coordinates;
171 }
172 
drawSquares(QPaintEvent * event)173 void BoardView::drawSquares(QPaintEvent* event)
174 {
175     QPainter p(this);
176     for (Square square=a1; square<NumSquares; ++square)
177     {
178         QRect rect = squareRect(square);
179         if(!event->region().intersects(rect))
180         {
181             continue;
182         }
183         int coord =  m_coordinates ? CoordinateSize : 0;
184         int x = isFlipped() ? 7 - square % 8 : square % 8;
185         int y = isFlipped() ? square / 8 : 7 - square / 8;
186         QPoint pos(coord + x * m_theme.size().width(), y * m_theme.size().height());
187         p.drawPixmap(pos, m_theme.square((x + y) % 2));
188     }
189 }
190 
totalRect() const191 QRect BoardView::totalRect() const
192 {
193     QRect rect1 = squareRect(isFlipped() ? h1 : a8);
194     QRect rect2;
195     if (m_coordinates)
196     {
197         rect2 = coordinateRectHorizontal(0);
198     }
199     else
200     {
201         rect2 = squareRect(isFlipped() ? a8 : h1);
202     }
203     QRect totalRect = QRect(0,rect1.top(), width(), rect2.bottom()+2);
204     return totalRect;
205 }
206 
drawCoordinates(QPaintEvent * event)207 void BoardView::drawCoordinates(QPaintEvent* event)
208 {
209     if(m_coordinates)
210     {
211         QPainter p(this);
212         p.save();
213         p.setPen(m_theme.color(BoardTheme::Frame));
214         for(int i = 0; i<8; ++i)
215         {
216             QRect rect = coordinateRectVertical(i);
217             if(!event->region().intersects(rect))
218             {
219                 continue;
220             }
221             p.drawText(rect, Qt::AlignCenter, QString("%1").arg(i + 1));
222         }
223         for(int i = 0; i<8; ++i)
224         {
225             QRect rect = coordinateRectHorizontal(i);
226             if(!event->region().intersects(rect))
227             {
228                 continue;
229             }
230             p.drawText(rect, Qt::AlignCenter, QString("%1").arg(QChar('a' + i)));
231         }
232         p.restore();
233     }
234 }
235 
drawDraggedPieces(QPaintEvent *)236 void BoardView::drawDraggedPieces(QPaintEvent* /*event*/)
237 {
238     // Draw dragged piece
239     if(m_dragged != Empty)
240     {
241         QPainter p(this);
242         p.drawPixmap(m_dragPoint, m_theme.piece(m_dragged));
243     }
244 }
245 
drawMoveIndicator(QPaintEvent * event)246 void BoardView::drawMoveIndicator(QPaintEvent* event)
247 {
248     if(m_showMoveIndicator && m_showMoveIndicatorMode!=Never)
249     {
250         QPainter p(this);
251         // Draw side to move indicator
252         int coord =  m_coordinates ? CoordinateSize : 0;
253         bool white = m_board.toMove() == White;
254         int square = moveIndicatorWidth(width(), m_theme.size().width());
255         if(square)
256         {
257             int posy = (white == m_flipped) ? 1 : 8 * m_theme.size().width() - square;
258             QRect rect(QPoint(8 * m_theme.size().width() + 2 + coord, posy),
259                        QSize(square, square));
260             if(!event->region().intersects(rect))
261             {
262                 return;
263             }
264             QColor color = white ? Qt::white : Qt::black;
265             QColor border = white ? Qt::black : Qt::gray;
266             p.setPen(border);
267             p.setBrush(QColor(color));
268             p.drawRect(rect);
269         }
270     }
271 }
272 
drawHiliteSquare(QPoint pos,BoardTheme::ColorRole role)273 void BoardView::drawHiliteSquare(QPoint pos, BoardTheme::ColorRole role)
274 {
275     QPainter p(this);
276 
277     QPen pen;
278     pen.setColor(m_theme.color(role));
279     pen.setWidth(2);
280     pen.setJoinStyle(Qt::MiterJoin);
281     p.setPen(pen);
282     p.drawRect(pos.x() + 1 + m_showFrame, pos.y() + 1 + m_showFrame,
283                m_theme.size().width() - 2 - m_showFrame, m_theme.size().height() - 2 - m_showFrame);
284 }
285 
drawHiliting(QPaintEvent * event)286 void BoardView::drawHiliting(QPaintEvent* event)
287 {
288     for (Square square=a1; square<NumSquares; ++square)
289     {
290         QRect rect = squareRect(square);
291         if(!event->region().intersects(rect))
292         {
293             continue;
294         }
295 
296         QPoint pos = posFromSquare(square);
297 
298         if(square == m_storedFrom || square == m_storedTo)
299         {
300             drawHiliteSquare(pos, BoardTheme::StoredMove);
301         }
302 
303         if(m_showCurrentMove==1)
304         {
305             if(square == m_currentFrom || square == m_currentTo)
306             {
307                 drawHiliteSquare(pos, BoardTheme::CurrentMove);
308             }
309         }
310 
311         if(square == m_selectedSquare || square == m_hiFrom || square == m_hiTo)
312         {
313             drawHiliteSquare(pos, BoardTheme::Highlight);
314         }
315     }
316 
317     if ((m_showCurrentMove==2) && m_currentFrom != InvalidSquare && m_currentTo != InvalidSquare)
318     {
319         QRect rect1 = squareRect(m_currentFrom);
320         QRect rect2 = squareRect(m_currentTo);
321         QRect u = rect1.united(rect2);
322         if(event->region().intersects(u))
323         {
324             drawArrow(m_currentFrom, m_currentTo, m_theme.color(BoardTheme::CurrentMove));
325         }
326     }
327 
328     Square threatFrom = m_threatGuess.getFrom();
329     Square threatTo = m_threatGuess.getTo();
330     if (threatFrom != InvalidSquare && threatTo != InvalidSquare)
331     {
332         QRect rect1 = squareRect(threatFrom);
333         QRect rect2 = squareRect(threatTo);
334         QRect u = rect1.united(rect2);
335         if(event->region().intersects(u))
336         {
337             drawArrow(threatFrom, threatTo, m_theme.color(BoardTheme::Threat));
338         }
339     }
340 
341     if (!m_bestGuess.isNullMove() && m_showThreat)
342     {
343         Square guessFrom = m_bestGuess.from();
344         Square guessTo = m_bestGuess.to();
345         if (guessFrom != InvalidSquare && guessTo != InvalidSquare)
346         {
347             QRect rect1 = squareRect(guessFrom);
348             QRect rect2 = squareRect(guessTo);
349             QRect u = rect1.united(rect2);
350             if(event->region().intersects(u))
351             {
352                 QColor c(m_theme.color(BoardTheme::Engine));
353                 drawArrow(guessFrom, guessTo, c);
354             }
355         }
356     }
357 
358     // draw variation arrows (whether they are really displayed depends on the contents of m_variations)
359     int thin = 0;
360     foreach(Move move, m_variations)
361     {
362         Square moveFrom = move.from();
363         Square moveTo = move.to();
364         QRect rect1 = squareRect(moveFrom);
365         QRect rect2 = squareRect(moveTo);
366         QRect u = rect1.united(rect2);
367         if(event->region().intersects(u))
368         {
369             QColor c(m_theme.color(BoardTheme::VariationMove));
370             drawArrow(moveFrom, moveTo, c, thin);
371         }
372         thin++;    // all but the first variation are drawn as thin
373     }
374 }
375 
drawTargets(QPaintEvent * event)376 void BoardView::drawTargets(QPaintEvent* event)
377 {
378     for (Square square=a1; square<NumSquares; ++square)
379     {
380         if (m_targets.contains(square))
381         {
382             drawColorRect(event, square, m_theme.color(BoardTheme::Target));
383         }
384     }
385 }
386 
drawCheck(QPaintEvent * event)387 void BoardView::drawCheck(QPaintEvent* event)
388 {
389     if (isEnabled())
390     {
391         QPainter p(this);
392         drawColorRect(event, m_alertSquare, m_theme.color(BoardTheme::Check));
393     }
394 }
395 
drawAttacks(QPaintEvent * event)396 void BoardView::drawAttacks(QPaintEvent* event)
397 {
398     if (isEnabled() && Guess::guessAllowed())
399     {
400         if (m_showAttacks != NoColor)
401         {
402             QPainter p(this);
403             for (Square square=a1; square<NumSquares; ++square)
404             {
405                 if (m_board.isAttackedBy(m_showAttacks, square))
406                 {
407                     drawColorRect(event, square, m_theme.color(BoardTheme::Wall), true);
408                 }
409             }
410         }
411     }
412 }
413 
drawUnderProtection(QPaintEvent * event)414 void BoardView::drawUnderProtection(QPaintEvent* event)
415 {
416     if (isEnabled() && Guess::guessAllowed())
417     {
418         if (m_showUnderProtection != NoColor)
419         {
420             QPainter p(this);
421             for (Square square=a1; square<NumSquares; ++square)
422             {
423                 if (m_board.colorAt(square) == m_showUnderProtection)
424                 {
425                     if (pieceType(m_board.pieceAt(square)) != King)
426                     {
427                         int numDefenders = m_board.DefendersOfSquare(square);
428                         if ((m_showUnderProtection == White && numDefenders < 0) ||
429                             (m_showUnderProtection == Black && numDefenders > 0))
430                         {
431                             drawColorRect(event, square, m_theme.color(BoardTheme::UnderProtected), true);
432                         }
433                     }
434                 }
435             }
436         }
437     }
438 }
439 
440 
441 
drawPieces(QPaintEvent * event)442 void BoardView::drawPieces(QPaintEvent* event)
443 {
444     QPainter p(this);
445     p.setRenderHint(QPainter::SmoothPixmapTransform);
446     for (Square square=a1; square<NumSquares; ++square)
447     {
448         QRect rect = squareRect(square);
449         if(!event->region().intersects(rect))
450         {
451             continue;
452         }
453 
454         QPoint pos = posFromSquare(square);
455 
456         if(m_showFrame)
457         {
458             p.setPen(m_theme.color(BoardTheme::Frame));
459             p.drawRect(QRect(pos, m_theme.size()));
460         }
461 
462         if(m_dragged != Empty)
463         {
464             // Do not paint piece that is dragged
465             if (m_dragStartSquare == square)
466             {
467                 continue;
468             }
469         }
470 
471         p.drawPixmap(pos, m_theme.piece(m_board.pieceAt(square)));
472     }
473 
474     // Always draw outer rectangle
475     QRect rect1 = squareRect(a8);
476     QRect rect2 = squareRect(h1);
477     p.setPen(m_theme.color(BoardTheme::Frame));
478     p.drawRect(!isFlipped() ? QRect(rect1.topLeft(),rect2.bottomRight()) :
479                               QRect(rect2.topLeft(),rect1.bottomRight()));
480 }
481 
paintEvent(QPaintEvent * event)482 void BoardView::paintEvent(QPaintEvent* event)
483 {
484     drawSquares(event);
485     drawCoordinates(event);
486     drawAttacks(event);
487     drawSquareAnnotations(event);
488     drawTargets(event);
489     drawCheck(event);
490     drawUnderProtection(event);
491     drawPieces(event);
492     drawHiliting(event);
493     drawMoveIndicator(event);
494     drawArrowAnnotations(event);
495     drawDraggedPieces(event);
496 }
497 
resizeBoard(QSize sz)498 void BoardView::resizeBoard(QSize sz)
499 {
500     // subtract move indicator from width
501     int coord = m_coordinates ? CoordinateSize : 0;
502     int moveIndicator = (m_showMoveIndicatorMode==Always) ? MoveIndicatorSize:0;
503     int xsize = (sz.width() - 1 - coord - moveIndicator) / 8;
504     int ysize = (sz.height() - 1 - coord) / 8;
505     int size = std::min(xsize, ysize);
506     m_theme.setSize(QSize(size, size));
507 
508     // calculate the translation vector to center the widget inside its parent
509     int square = moveIndicatorWidth(width(), m_theme.size().width());
510 
511     QSize widgetSize(size * 8 + 1 + coord + square, size * 8 + 1 + coord);
512     QPoint widgetCenter(widgetSize.width() / 2, widgetSize.height() / 2);
513 }
514 
resizeEvent(QResizeEvent * e)515 void BoardView::resizeEvent(QResizeEvent* e)
516 {
517     resizeBoard(e->size());
518     QWidget::resizeEvent(e);
519 }
520 
sizeHint() const521 QSize BoardView::sizeHint() const
522 {
523     int w = geometry().width();
524     int h = heightForWidth(w);
525     return QSize(w,h);
526 }
527 
moveIndicatorWidth(int width,int themeWidth) const528 int BoardView::moveIndicatorWidth(int width, int themeWidth) const
529 {
530     if(m_showMoveIndicator && m_showMoveIndicatorMode!=Never)
531     {
532         int coord = m_coordinates ? CoordinateSize : 0;
533         int square = width - coord - 8 * themeWidth - 4;
534         int maxsquare = themeWidth / 2;
535         if (square > maxsquare)
536         {
537             square = maxsquare;
538         }
539         if (square <= 8)
540         {
541             square = 0;
542         }
543         return square;
544     }
545     return 0;
546 }
547 
548 
squareAt(const QPoint & p) const549 Square BoardView::squareAt(const QPoint& p) const
550 {
551     int x = p.x();
552     int y = p.y();
553     int width = m_theme.size().width();
554     int height = m_theme.size().height();
555     x -= m_coordinates ? CoordinateSize : 0;
556     if(x <= 0 || y <= 0 || x >= width * 8 || y >= height * 8)
557     {
558         return InvalidSquare;
559     }
560     x /= width;
561     y /= height;
562     return Square(isFlipped() ? (8 * y + 7 - x) : (8 * (7 - y) + x));
563 }
564 
mousePressEvent(QMouseEvent * event)565 void BoardView::mousePressEvent(QMouseEvent* event)
566 {
567     m_dragStart = event->pos();
568     setStoredMove(InvalidSquare,InvalidSquare);
569 }
570 
showGuess(Square s)571 bool BoardView::showGuess(Square s)
572 {
573     // Don't want to constantly recalculate guess, so remember which square
574     // the mouse is hovering over, and only show new guess when it changes
575     if((m_guessMove || m_showTargets) && s != InvalidSquare && s != m_hoverSquare && !(m_flags & SuppressGuessMove))
576     {
577         removeGuess();
578         m_hoverSquare = s;
579 
580         if (s != InvalidSquare)
581         {
582             Guess::Result sm = Guess::guessMove(qPrintable(m_board.toFen()), m_board.chess960(), m_board.castlingRooks(),
583                                                 static_cast<Guess::squareT>(s), m_moveList);
584             if(!sm.error)
585             {
586                 if (m_guessMove)
587                 {
588                     m_hiFrom = Square(sm.from);
589                     m_hiTo = Square(sm.to);
590                     update(squareRect(m_hiFrom));
591                     update(squareRect(m_hiTo));
592                 }
593 
594                 if (m_showTargets)
595                 {
596                     foreach(Guess::simpleMoveT sm, m_moveList)
597                     {
598                         if (s == Square(sm.from))
599                         {
600                             Square target = Square(sm.visualTo());
601                             m_targets.append(target);
602                             update(squareRect(target));
603                         }
604                     }
605                 }
606             }
607         }
608         return true;
609     }
610     return false;
611 }
612 
updateGuess(Square s)613 void BoardView::updateGuess(Square s)
614 {
615     // Invalidate any currently displayed guess to allow new guess to show
616     m_hoverSquare = InvalidSquare;
617     showGuess(s);
618 }
619 
updateThreat()620 void BoardView::updateThreat()
621 {
622     m_threatGuess.clear();
623     if(m_showThreat && !(m_flags & SuppressGuessMove) && Guess::guessAllowed())
624     {
625         if (board() != BoardX::standardStartBoard)
626         {
627             m_threatGuess.guessMove(board());
628         }
629     }
630 }
631 
showThreat(Guess::Result sm,BoardX b)632 void BoardView::showThreat(Guess::Result sm, BoardX b)
633 {
634     if (board() == b)
635     {
636         if(!sm.error)
637         {
638             update();
639         }
640     }
641     else
642     {
643         ThreadedGuess* guessEngine = qobject_cast<ThreadedGuess*>(sender());
644         if (guessEngine)
645         {
646             guessEngine->guessMove(board());
647         }
648     }
649 }
650 
removeGuess()651 void BoardView::removeGuess()
652 {
653     if(m_hiFrom != InvalidSquare)
654     {
655         update(squareRect(m_hiFrom));
656         update(squareRect(m_hiTo));
657         m_hiFrom = m_hiTo = InvalidSquare;
658     }
659     m_hoverSquare = InvalidSquare;
660     m_moveListCurrent = 0;
661     m_moveList.clear();
662     foreach(Square s, m_targets)
663     {
664         update(squareRect(s));
665     }
666     m_targets.clear();
667 }
668 
nextGuess(Square s)669 void BoardView::nextGuess(Square s)
670 {
671     if(!showGuess(s))
672     {
673         if(m_moveList.size() && (int) m_moveListCurrent < m_moveList.size())
674         {
675             Guess::simpleMoveT * sold = m_moveList.Get(m_moveListCurrent);
676             update(squareRect(Square(sold->from)));
677             update(squareRect(Square(sold->visualTo())));
678 
679             if((int)m_moveListCurrent < m_moveList.size() - 1)
680             {
681                 ++m_moveListCurrent;
682             }
683             else
684             {
685                 m_moveListCurrent = 0;
686             }
687 
688             Guess::simpleMoveT * sm = m_moveList.Get(m_moveListCurrent);
689             m_hiFrom = Square(sm->from);
690             m_hiTo = Square(sm->visualTo());
691             update(squareRect(m_hiFrom));
692             update(squareRect(m_hiTo));
693         }
694     }
695 }
696 
moveActionFromModifier(Qt::KeyboardModifiers modifiers) const697 BoardView::BoardViewAction BoardView::moveActionFromModifier(Qt::KeyboardModifiers modifiers) const
698 {
699     switch (modifiers & 0x7e000000)
700     {
701     case ShiftModifier:
702         return ActionPen;
703     case ControlModifier:
704         return m_atLineEnd ? ActionQuery : ActionReplace;
705     case AltModifier:
706         return m_atLineEnd ? ActionStandard : ActionAdd;
707     case (unsigned int)ControlModifier | (unsigned int)AltModifier:
708         return m_atLineEnd ? ActionStandard : ActionInsert;
709     case (unsigned int)ControlModifier | (unsigned int)ShiftModifier:
710         return ActionAskEngine;
711     case (unsigned int)ShiftModifier | (unsigned int)AltModifier:
712         return ActionEvalMove;
713     default:
714         return ActionStandard;
715     }
716 }
717 
checkCursor(Qt::KeyboardModifiers modifiers)718 void BoardView::checkCursor(Qt::KeyboardModifiers modifiers)
719 {
720     const char* file = nullptr;
721     QString text;
722 
723     if (underMouse())
724     {
725         if (m_brushMode)
726         {
727             if (modifiers & AltModifier)
728             {
729                 file = ":/images/spray.png";
730                 text = tr("Spray color annotations");
731             }
732             else
733             {
734                 file = ":/images/pen.png";
735                 text = tr("Draw a square or arrow annotation");
736             }
737         }
738         else switch (moveActionFromModifier(modifiers))
739         {
740         case ActionStandard:
741             break;
742         case ActionQuery:
743             file = ":/images/query_move.png";
744             text = tr("Query for piece in case of promotion");
745             break;
746         case ActionReplace:
747             file = ":/images/replace_move.png";
748             text = tr("Replace remainder of game with new move");
749             break;
750         case ActionInsert:
751             file = ":/images/insert_move.png";
752             text = tr("Insert new move and keep as much as possible of remaining moves");
753             break;
754         case ActionAdd:
755             file = ":/images/plus.png";
756             text = tr("Force adding a variation");
757             break;
758         case ActionPen:
759             file = ":/images/pen.png";
760             text = tr("Draw a square or arrow annotation");
761             break;
762         case ActionAskEngine:
763             file = ":/images/engine.png";
764             text = tr("Query the engine as if piece was located at target");
765             break;
766         case ActionEvalMove:
767             file = ":/images/engine.png";
768             text = tr("Query the engine for the best reply");
769             break;
770         }
771     }
772 
773     if(file)
774     {
775         setCursor(QCursor(QPixmap(file)));
776     }
777     else
778     {
779         setCursor(QCursor(Qt::ArrowCursor));
780     }
781 
782     emit actionHint(text);
783 
784     if ((m_dragged != Empty) && (lastMoveEvent))
785     {
786         lastMoveEvent->setModifiers(modifiers);
787         handleMouseMoveEvent(lastMoveEvent);
788     }
789 }
790 
keyPressEvent(QKeyEvent * event)791 void BoardView::keyPressEvent(QKeyEvent *event)
792 {
793     checkCursor(QApplication::queryKeyboardModifiers());
794     QWidget::keyPressEvent(event);
795 }
796 
keyReleaseEvent(QKeyEvent * event)797 void BoardView::keyReleaseEvent(QKeyEvent *event)
798 {
799     checkCursor(QApplication::queryKeyboardModifiers());
800     QWidget::keyReleaseEvent(event);
801 }
802 
enterEvent(QEvent * event)803 void BoardView::enterEvent(QEvent *event)
804 {
805     setFocus();
806     raise();
807     checkCursor(QApplication::queryKeyboardModifiers());
808     QWidget::enterEvent(event);
809 }
810 
leaveEvent(QEvent * event)811 void BoardView::leaveEvent(QEvent *event)
812 {
813     emit actionHint("");
814     QWidget::leaveEvent(event);
815 }
816 
handleMouseMoveEvent(QMouseEvent * event)817 void BoardView::handleMouseMoveEvent(QMouseEvent *event)
818 {
819     setFocus();
820 
821     Qt::KeyboardModifiers mdf = event->modifiers();
822     auto b = event->buttons();
823     if(!(b & Qt::LeftButton))
824     {
825         if (!m_brushMode)
826         {
827             if(!(mdf & Qt::ShiftModifier))
828             {
829                 showGuess(squareAt(event->pos()));
830             }
831             else
832             {
833                 removeGuess();
834             }
835         }
836         return;
837     }
838 
839     if (m_brushMode && (b & Qt::LeftButton) && (mdf & Qt::AltModifier))
840     {
841         Square s = squareAt(event->pos());
842         if ((m_dragStartSquare == InvalidSquare) || (m_dragStartSquare != s))
843         {
844             emit clicked(s, b, mapToGlobal(event->pos()), s);
845             m_dragStart = event->pos();
846             m_dragStartSquare = s;
847             return;
848         }
849     }
850 
851     if(m_dragged != Empty)
852     {
853         QRect old = QRect(m_dragPoint, m_theme.size());
854         m_dragPoint = event->pos() - m_theme.pieceCenter();
855         update(old);
856         update(QRect(m_dragPoint, m_theme.size()));
857         if (moveActionFromModifier(mdf) == ActionAskEngine)
858         {
859             emit evalRequest(m_dragStartSquare, squareAt(event->pos()));
860         }
861         else if (moveActionFromModifier(mdf) == ActionEvalMove)
862         {
863             emit evalMove(m_dragStartSquare, squareAt(event->pos()));
864         }
865         return;
866     }
867 
868     if(mdf & Qt::ShiftModifier && (moveActionFromModifier(mdf) != ActionAskEngine) && (moveActionFromModifier(mdf) != ActionEvalMove))
869     {
870         return;
871     }
872 
873     if((event->pos() - m_dragStart).manhattanLength()
874             < QApplication::startDragDistance())
875     {
876         // Click and move - start dragging
877         return;
878     }
879 
880     Square s = squareAt(m_dragStart);
881     if(!canDrag(s, mdf))
882     {
883         return;
884     }
885 
886     startToDrag(event, s);
887 }
888 
mouseMoveEvent(QMouseEvent * event)889 void BoardView::mouseMoveEvent(QMouseEvent *event)
890 {
891     delete lastMoveEvent;
892     lastMoveEvent = new QMouseEvent(*event);
893 
894     handleMouseMoveEvent(event);
895     QWidget::mouseMoveEvent(event);
896 }
897 
startToDrag(QMouseEvent * event,Square s)898 void BoardView::startToDrag(QMouseEvent *event, Square s)
899 {
900     removeGuess();
901     if (!m_brushMode) m_dragged = m_board.pieceAt(s);
902     m_dragPoint = event->pos() - m_theme.pieceCenter();
903     m_dragStartSquare = s;
904     update(squareRect(s));
905     update(QRect(m_dragPoint, m_theme.size()));
906     unselectSquare();
907 }
908 
getBestGuess() const909 Move BoardView::getBestGuess() const
910 {
911     return m_bestGuess;
912 }
913 
setBestGuess(const Move & bestGuess)914 void BoardView::setBestGuess(const Move &bestGuess)
915 {
916     m_bestGuess = bestGuess;
917     update();
918 }
919 
setVariations(const QList<Move> & variations)920 void BoardView::setVariations(const QList<Move> &variations)
921 {
922     m_variations = variations;
923     update();
924 }
925 
setShowUnderProtection(const Color & showUnderProtection)926 void BoardView::setShowUnderProtection(const Color &showUnderProtection)
927 {
928     m_showUnderProtection = showUnderProtection;
929     update();
930 }
931 
setShowAttacks(const Color & showAttacks)932 void BoardView::setShowAttacks(const Color &showAttacks)
933 {
934     m_showAttacks = showAttacks;
935     update();
936 }
937 
getBrushMode() const938 bool BoardView::getBrushMode() const
939 {
940     return m_brushMode;
941 }
942 
setBrushMode(bool brushMode)943 void BoardView::setBrushMode(bool brushMode)
944 {
945     m_brushMode = brushMode;
946 }
947 
mouseReleaseEvent(QMouseEvent * event)948 void BoardView::mouseReleaseEvent(QMouseEvent* event)
949 {
950     delete lastMoveEvent;
951     lastMoveEvent = nullptr;
952     int button = event->button() + event->modifiers();
953     Square s = squareAt(event->pos());
954     m_clickUsed = false;
955     Square from = squareAt(m_dragStart);
956 
957     if (!canDrop(from))
958     {
959         if(m_dragged != Empty)
960         {
961             m_dragStartSquare = InvalidSquare;
962             QRect oldr = QRect(m_dragPoint, m_theme.size());
963             m_dragged = Empty;
964             update(squareRect(from));
965             update(oldr);
966             emit evalModeDone();
967             return;
968         }
969     }
970 
971     if(!(event->button() & Qt::LeftButton))
972     {
973         if(s == from)
974         {
975             from = InvalidSquare;
976         }
977         if(s != InvalidSquare)
978         {
979             emit clicked(s, button, mapToGlobal(event->pos()), from);
980         }
981         else
982         {
983             Square from = squareAt(m_dragStart);
984             emit invalidMove(from);
985         }
986         m_dragged = Empty;
987         return;
988     }
989     else
990     {
991         if ((moveActionFromModifier(event->modifiers()) == ActionAskEngine) || (moveActionFromModifier(event->modifiers()) == ActionEvalMove))
992         {
993             m_dragStartSquare = InvalidSquare;
994             QRect oldr = QRect(m_dragPoint, m_theme.size());
995             m_dragged = Empty;
996             update(squareRect(from));
997             update(oldr);
998             emit evalModeDone();
999             return;
1000         }
1001         if(event->modifiers() & Qt::ShiftModifier)
1002         {
1003             if(s != InvalidSquare)
1004             {
1005                 emit clicked(s, button, mapToGlobal(event->pos()), from);
1006             }
1007             if (m_dragged != Empty)
1008             {
1009                 update(squareRect(from));
1010                 QRect oldr = QRect(m_dragPoint, m_theme.size());
1011                 update(oldr);
1012                 m_dragged = Empty;
1013             }
1014             return;
1015         }
1016     }
1017 
1018     if(m_brushMode)
1019     {
1020         m_dragStartSquare = InvalidSquare;
1021         if ((s != InvalidSquare) && !(event->modifiers() & Qt::AltModifier))
1022         {
1023             emit clicked(s, button, mapToGlobal(event->pos()), from);
1024             m_dragged = Empty;
1025         }
1026     }
1027     else if(m_dragged != Empty)
1028     {
1029         m_dragStartSquare = InvalidSquare;
1030         QRect oldr = QRect(m_dragPoint, m_theme.size());
1031         m_dragged = Empty;
1032         update(squareRect(from));
1033         update(oldr);
1034         if(s != InvalidSquare)
1035         {
1036             if((m_flags & AllowCopyPiece) && (event->modifiers() & Qt::AltModifier))
1037             {
1038                 if(m_board.pieceAt(from) != Empty)
1039                 {
1040                     emit copyPiece(from, s);
1041                 }
1042             }
1043             else
1044             {
1045                 updateGuess(s);
1046                 emit moveMade(from, s, button);
1047             }
1048         }
1049         else
1050         {
1051             emit invalidMove(from);
1052         }
1053     }
1054     else if(m_selectedSquare != InvalidSquare)
1055     {
1056         Square from = m_selectedSquare;
1057         unselectSquare();
1058         if(s != InvalidSquare)
1059         {
1060             emit moveMade(from, s, button);
1061         }
1062     }
1063     else if(m_hiFrom != InvalidSquare)
1064     {
1065         if(s == m_hiFrom || s == m_hiTo)
1066         {
1067             emit moveMade(m_hiFrom, m_hiTo, button);
1068         }
1069         m_hoverSquare = InvalidSquare;
1070         // Only update guess if "emit moveMade()" did not pop up a window (eg. promotion)
1071         if(m_hiFrom != InvalidSquare)
1072         {
1073             updateGuess(s);
1074         }
1075     }
1076     else
1077     {
1078         if(s != InvalidSquare)
1079         {
1080             emit clicked(s, button, mapToGlobal(event->pos()), InvalidSquare);
1081             if(!m_clickUsed && m_board.isMovable(s))
1082             {
1083                 selectSquare(s);
1084             }
1085         }
1086     }
1087     m_dragged = Empty;
1088     QWidget::mouseReleaseEvent(event);
1089 }
1090 
wheelEvent(QWheelEvent * e)1091 void BoardView::wheelEvent(QWheelEvent* e)
1092 {
1093     m_wheelCurrentDelta += e->delta();
1094     if(abs(m_wheelCurrentDelta) > m_minDeltaWheel)
1095     {
1096         int change = m_wheelCurrentDelta < 0 ? WheelDown : WheelUp;
1097         emit wheelScrolled(change + e->modifiers());
1098         m_wheelCurrentDelta = 0;
1099     }
1100     QWidget::wheelEvent(e);
1101 }
1102 
setFlipped(bool flipped)1103 void BoardView::setFlipped(bool flipped)
1104 {
1105     bool wasFlipped = m_flipped;
1106     m_flipped = flipped;
1107     repaint(); // Workaround Bug in Qt at least up to version 5.12
1108     emit signalFlipped(wasFlipped, m_flipped);
1109 }
1110 
flip()1111 void BoardView::flip()
1112 {
1113     setFlipped(!m_flipped);
1114 }
1115 
isFlipped() const1116 bool BoardView::isFlipped() const
1117 {
1118     return m_flipped;
1119 }
1120 
configure()1121 void BoardView::configure()
1122 {
1123     AppSettings->beginGroup("/Board/");
1124     m_showFrame = AppSettings->getValue("showFrame").toBool();
1125     m_coordinates = AppSettings->getValue("showCoordinates").toBool();
1126     m_showCurrentMove = AppSettings->getValue("showCurrentMove").toInt();
1127     m_guessMove = AppSettings->getValue("guessMove").toBool();
1128     m_showTargets = AppSettings->getValue("showTargets").toBool();
1129     m_showThreat = AppSettings->getValue("showThreat").toBool();
1130     m_minDeltaWheel = AppSettings->getValue("minWheelCount").toInt();
1131     m_showMoveIndicatorMode = AppSettings->getValue("showMoveIndicator").toInt();
1132     AppSettings->endGroup();
1133     m_theme.configure();
1134     m_theme.setEnabled(isEnabled());
1135     removeGuess();
1136     unselectSquare();
1137     if(size().height() >= minimumSize().height())
1138     {
1139         resizeBoard(size());
1140     }
1141     update();
1142 }
1143 
selectSquare(Square s)1144 void BoardView::selectSquare(Square s)
1145 {
1146     // You can't select a square when guess move is enabled
1147     if(m_guessMove && !(m_flags & SuppressGuessMove))
1148     {
1149         return;
1150     }
1151     if(m_selectedSquare == s)
1152     {
1153         return;
1154     }
1155     unselectSquare();
1156     m_selectedSquare = s;
1157     update(squareRect(s));
1158 }
1159 
unselectSquare()1160 void BoardView::unselectSquare()
1161 {
1162     Square prev = m_selectedSquare;
1163     m_selectedSquare = InvalidSquare;
1164     if(prev != InvalidSquare)
1165     {
1166         update(squareRect(prev));
1167     }
1168 }
1169 
posFromSquare(int square) const1170 QPoint BoardView::posFromSquare(int square) const
1171 {
1172     int coord =  m_coordinates ? CoordinateSize : 0;
1173     int x = isFlipped() ? 7 - square % 8 : square % 8;
1174     int y = isFlipped() ? square / 8 : 7 - square / 8;
1175     QPoint pos(coord + x * m_theme.size().width(), y * m_theme.size().height());
1176     return pos;
1177 }
1178 
squareRect(Square square) const1179 QRect BoardView::squareRect(Square square) const
1180 {
1181     return QRect(posFromSquare(square), m_theme.size());
1182 }
1183 
coordinateRectVertical(int n) const1184 QRect BoardView::coordinateRectVertical(int n) const
1185 {
1186     Q_ASSERT(m_coordinates);
1187     int x = isFlipped() ? n % 8 : 7 - n % 8;
1188     return QRect(QPoint(0, x * m_theme.size().height() + (m_theme.size().height() - CoordinateSize) / 2),
1189                  QSize(CoordinateSize, CoordinateSize));
1190 }
1191 
coordinateRectHorizontal(int n) const1192 QRect BoardView::coordinateRectHorizontal(int n) const
1193 {
1194     Q_ASSERT(m_coordinates);
1195     int y = isFlipped() ? 7 - n % 8 : n % 8;
1196     return QRect(QPoint(CoordinateSize + (y * (m_theme.size().width())) + (m_theme.size().width() - CoordinateSize) / 2,
1197                         8 * m_theme.size().height()),
1198                  QSize(CoordinateSize, CoordinateSize));
1199 }
1200 
canDrop(Square s) const1201 bool BoardView::canDrop(Square s) const
1202 {
1203     if(m_dragged == Empty)  // not dragging
1204     {
1205         return false;
1206     }
1207     if(s == InvalidSquare)
1208     {
1209         return false;
1210     }
1211     if(!(m_flags & IgnoreSideToMove))
1212     {
1213         return m_board.isMovable(s);
1214     }
1215     return true;
1216 }
1217 
canDrag(Square s,Qt::KeyboardModifiers mdf) const1218 bool BoardView::canDrag(Square s, Qt::KeyboardModifiers mdf) const
1219 {
1220     if(m_dragged != Empty)  // already dragging
1221     {
1222         return false;
1223     }
1224     if(s == InvalidSquare)
1225     {
1226         return false;
1227     }
1228     else if( (m_flags & IgnoreSideToMove) ||
1229              (moveActionFromModifier(mdf) == ActionAskEngine) ||
1230              (moveActionFromModifier(mdf) == ActionEvalMove))
1231     {
1232         return m_board.pieceAt(s) != Empty;
1233     }
1234     else
1235     {
1236         return m_board.isMovable(s);
1237     }
1238 }
1239 
dragEnterEvent(QDragEnterEvent * event)1240 void BoardView::dragEnterEvent(QDragEnterEvent *event)
1241 {
1242     const BoardViewMimeData *mimeData = qobject_cast<const BoardViewMimeData *>(event->mimeData());
1243     if(mimeData)
1244     {
1245         event->acceptProposedAction();
1246     }
1247 }
1248 
dragMoveEvent(QDragMoveEvent * event)1249 void BoardView::dragMoveEvent(QDragMoveEvent *event)
1250 {
1251     event->acceptProposedAction();
1252 }
1253 
dragLeaveEvent(QDragLeaveEvent * event)1254 void BoardView::dragLeaveEvent(QDragLeaveEvent *event)
1255 {
1256     event->accept();
1257 }
1258 
dropEvent(QDropEvent * event)1259 void BoardView::dropEvent(QDropEvent *event)
1260 {
1261     const BoardViewMimeData *mimeData = qobject_cast<const BoardViewMimeData *>(event->mimeData());
1262     if(mimeData)
1263     {
1264         Square s = squareAt(event->pos());
1265         if (s != InvalidSquare)
1266         {
1267             emit pieceDropped(s, mimeData->m_piece);
1268         }
1269         event->acceptProposedAction();
1270     }
1271 }
1272 
drawSquareAnnotations(QPaintEvent * event)1273 void BoardView::drawSquareAnnotations(QPaintEvent* event)
1274 {
1275     QString annotation = m_board.squareAnnotation();
1276 
1277     if(!annotation.isEmpty() && !annotation.isNull())
1278     {
1279         QStringList list = annotation.split(",");
1280 
1281         for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); it++)
1282         {
1283             if(*it != "")
1284             {
1285                 drawSquareAnnotation(event, *it);
1286             }
1287         }
1288     }
1289 }
1290 
drawArrowAnnotations(QPaintEvent * event)1291 void BoardView::drawArrowAnnotations(QPaintEvent* event)
1292 {
1293     QString annotation = m_board.arrowAnnotation();
1294 
1295     if(!annotation.isEmpty() && !annotation.isNull())
1296     {
1297         QStringList list = annotation.split(",");
1298 
1299         for(QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); it++)
1300         {
1301             if(*it != "")
1302             {
1303                 drawArrowAnnotation(event, *it);
1304             }
1305         }
1306     }
1307 }
1308 
drawColorRect(QPaintEvent * event,Square square,QColor color,bool plain)1309 void BoardView::drawColorRect(QPaintEvent* event, Square square, QColor color, bool plain)
1310 {
1311     QPainter p(this);
1312 
1313     QRect rect = squareRect(square);
1314     if(!event->region().intersects(rect))
1315     {
1316         return;
1317     }
1318 
1319     int x = isFlipped() ? 7 - square % 8 : square % 8;
1320     int y = isFlipped() ? square / 8 : 7 - square / 8;
1321     QPoint pos(x * m_theme.size().width(), y * m_theme.size().height());
1322 
1323     p.save();
1324     QPen pen(color);
1325     QBrush brush(color);
1326     p.setPen(pen);
1327     p.setBrush(brush);
1328     p.setOpacity(plain ? 0.7 : 0.35);
1329 
1330     int coord =  m_coordinates ? CoordinateSize : 0;
1331 
1332     if (plain)
1333     {
1334         p.fillRect(coord + pos.x(), pos.y(), m_theme.size().width(), m_theme.size().height(), color);
1335     }
1336     else
1337     {
1338         QRadialGradient radialGrad(QPointF(coord + pos.x()+m_theme.size().width()/2, pos.y()+m_theme.size().height()/2), m_theme.size().width());
1339         radialGrad.setColorAt(0, color.lighter());
1340         radialGrad.setColorAt(0.5, color);
1341         radialGrad.setColorAt(1, color);
1342 
1343         p.fillRect(coord + pos.x(), pos.y(), m_theme.size().width(), m_theme.size().height(), radialGrad);
1344     }
1345 
1346     p.restore();
1347 }
1348 
drawSquareAnnotation(QPaintEvent * event,QString annotation)1349 void BoardView::drawSquareAnnotation(QPaintEvent* event, QString annotation)
1350 {
1351     QString trimmed = annotation.simplified();
1352     QChar colorChar = trimmed[0];
1353     QChar fileChar = trimmed[1];
1354     QChar rankChar = trimmed[2];
1355     QString files = "abcdefgh";
1356     QString ranks = "12345678";
1357     unsigned char file = files.indexOf(fileChar);
1358     int rank = ranks.indexOf(rankChar);
1359 
1360     QColor color = Qt::red;
1361     if(colorChar == 'Y')
1362     {
1363         color = Qt::yellow;
1364     }
1365     else if(colorChar == 'G')
1366     {
1367         color = Qt::green;
1368     }
1369     else if(colorChar == 'B')
1370     {
1371         color = Qt::blue;
1372     }
1373 
1374     Square square = SquareFromRankAndFile(rank, file);
1375     drawColorRect(event, square, color);
1376 }
1377 
drawArrow(int square1,int square2,QColor color,int thin)1378 void BoardView::drawArrow(int square1, int square2, QColor color, int thin)
1379 {
1380     QPainter p(this);
1381     thin = std::min(thin,3);
1382     if (thin) thin++;
1383 
1384     float x1 = isFlipped() ? 7 - square1 % 8 : square1 % 8;
1385     float y1 = isFlipped() ? square1 / 8 : 7 - square1 / 8;
1386     float x2 = isFlipped() ? 7 - square2 % 8 : square2 % 8;
1387     float y2 = isFlipped() ? square2 / 8 : 7 - square2 / 8;
1388     float w = m_theme.size().width();
1389     float h = m_theme.size().height();
1390     float coord =  m_coordinates ? CoordinateSize : 0;
1391     QPointF pos1(coord + (x1 * w) + (w / 2), (y1 * h) + (h / 2));
1392     QPointF pos2(coord + (x2 * w) + (w / 2), (y2 * h) + (h / 2));
1393 
1394     // Now to Draw Arrow Head
1395     qreal headWidth = m_theme.size().width() / 4;
1396     qreal headLength = headWidth;
1397     qreal headIndent = headWidth / 4;
1398     qreal netIndent = headLength - headIndent;
1399 
1400     qreal halfHead = headWidth / 2;
1401     float px1 = pos1.x();
1402     float px2 = pos2.x();
1403     float py1 = pos1.y();
1404     float py2 = pos2.y();
1405     float dX = px2 - px1;
1406     float dY = py2 - py1;
1407 
1408     qreal  arrowLength = qSqrt(dX * dX + dY * dY);
1409 
1410     QPointF arrowPts[7];
1411 
1412     // we will shorten the line somewhat to avoid arrows all colliding in the center of the square
1413     float adjust = ((w + h) / 8);
1414 
1415     px1 = px1 + ((adjust * dX) / arrowLength);
1416     px2 = px2 - ((adjust * dX) / arrowLength);
1417     py1 = py1 + ((adjust * dY) / arrowLength);
1418     py2 = py2 - ((adjust * dY) / arrowLength);
1419 
1420     // calculate the points that form the arrow
1421     arrowPts[0].setX(px2 - ((netIndent * dX) / arrowLength));
1422     arrowPts[0].setY(py2 - ((netIndent * dY) / arrowLength));
1423     arrowPts[4].setX(px2 - ((headLength * dX) / arrowLength));
1424     arrowPts[4].setY(py2 - ((headLength * dY) / arrowLength));
1425     arrowPts[1].setX(arrowPts[4].x() - ((halfHead * (dY)) / arrowLength));
1426     arrowPts[1].setY(arrowPts[4].y() - ((halfHead * (-dX)) / arrowLength));
1427     arrowPts[3].setX(arrowPts[4].x() + ((halfHead * (dY)) / arrowLength));
1428     arrowPts[3].setY(arrowPts[4].y() + ((halfHead * (-dX)) / arrowLength));
1429     arrowPts[2].setX(px2);
1430     arrowPts[2].setY(py2);
1431 
1432     QPointF pos3(px1, py1);
1433     QPointF pos4(px2- ((adjust * dX) / arrowLength), py2- ((adjust * dY) / arrowLength));
1434 
1435     p.save();
1436     p.setRenderHint(QPainter::SmoothPixmapTransform);
1437 
1438     color.setAlpha(176-28*thin);
1439     int penWidth = std::max(2, (int)(headWidth / 3));
1440     QPen pen(color, penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
1441     p.setPen(pen);
1442     p.drawLine(pos3, pos4);
1443 
1444     QBrush brush(color);
1445     p.setBrush(brush);
1446     pen.setWidth(2);
1447     p.setPen(pen);
1448     p.drawPolygon(arrowPts, 4);
1449 
1450     p.restore();
1451 }
1452 
drawArrowAnnotation(QPaintEvent * event,QString annotation)1453 void BoardView::drawArrowAnnotation(QPaintEvent* event, QString annotation)
1454 {
1455     static const QString letters = "abcdefgh";
1456     static const QString numbers = "12345678";
1457 
1458     QString trimmed = annotation.simplified();
1459 
1460     QChar colorChar = trimmed[0];
1461     QChar fileChar1 = trimmed[1];
1462     QChar rankChar1 = trimmed[2];
1463     QChar fileChar2 = trimmed[3];
1464     QChar rankChar2 = trimmed[4];
1465     unsigned char file1 = letters.indexOf(fileChar1);
1466     int rank1 = numbers.indexOf(rankChar1);
1467     unsigned char file2 = letters.indexOf(fileChar2);
1468     int rank2 = numbers.indexOf(rankChar2);
1469 
1470     if(file1 > sizeof(letters) || file2 > sizeof(letters) || rank1 < 0 || rank2 < 0)
1471     {
1472         return;
1473     }
1474     Square square1 = SquareFromRankAndFile(rank1, file1);
1475     Square square2 = SquareFromRankAndFile(rank2, file2);
1476 
1477     QRect rect1 = squareRect(square1);
1478     QRect rect2 = squareRect(square2);
1479     QRect u = rect1.united(rect2);
1480     if(!event->region().intersects(u))
1481     {
1482         return;
1483     }
1484 
1485     QColor color = Qt::red;
1486     if(colorChar == 'Y')
1487     {
1488         color = Qt::yellow;
1489     }
1490     else if(colorChar == 'G')
1491     {
1492         color = Qt::green;
1493     }
1494     else if(colorChar == 'B')
1495     {
1496         color = Qt::blue;
1497     }
1498 
1499     drawArrow(square1, square2, color);
1500 }
1501 
dragged() const1502 Piece BoardView::dragged() const
1503 {
1504     return m_dragged;
1505 }
1506 
setDragged(const Piece & dragged)1507 void BoardView::setDragged(const Piece &dragged)
1508 {
1509     m_dragged = dragged;
1510 }
1511 
setEnabled(bool enabled)1512 void BoardView::setEnabled(bool enabled)
1513 {
1514     QWidget::setEnabled(enabled);
1515 }
1516 
setDisabled(bool disabled)1517 void BoardView::setDisabled(bool disabled)
1518 {
1519     QWidget::setDisabled(disabled);
1520 }
1521 
setDbIndex(QObject * dbIndex)1522 void BoardView::setDbIndex(QObject* dbIndex)
1523 {
1524     m_DbIndex = dbIndex;
1525 }
1526 
dbIndex() const1527 QObject* BoardView::dbIndex() const
1528 {
1529     return m_DbIndex;
1530 }
1531 
renderImage(QImage & image,double scaling) const1532 void BoardView::renderImage(QImage &image, double scaling) const
1533 {
1534     BoardView boardView(nullptr, BoardView::IgnoreSideToMove | BoardView::SuppressGuessMove);
1535     QSize s;
1536     if (scaling < 0)
1537     {
1538         if (AppSettings->getValue("/Board/fixedImageSize").toBool())
1539         {
1540             int n = AppSettings->getValue("/Board/copyImageSize").toInt();
1541             s = QSize(n, n*height()/width());
1542         }
1543         else
1544         {
1545             scaling = -scaling;
1546             s = size()*scaling;
1547         }
1548     }
1549     else
1550     {
1551         s = size()*scaling;
1552     }
1553 
1554     boardView.setMinimumSize(s);
1555     boardView.setEnabled(isEnabled() && AppSettings->getValue("/Board/colorCopy").toBool());
1556     boardView.setFlipped(isFlipped());
1557     boardView.showMoveIndicator(showMoveIndicator());
1558     boardView.showCoordinates(showCoordinates());
1559     boardView.resize(s);
1560     boardView.configure();
1561     boardView.setBoard(board());
1562 
1563     QPalette Pal(palette());
1564     Pal.setColor(QPalette::Background, Qt::transparent);
1565     boardView.setAutoFillBackground(true);
1566     boardView.setPalette(Pal);
1567     QRect sourceRect = boardView.totalRect();
1568     QPixmap pixmap(sourceRect.size());
1569     pixmap.fill(Qt::transparent);
1570 
1571     boardView.render(&pixmap, QPoint(), sourceRect, QWidget::DrawChildren);
1572     image = pixmap.toImage();
1573 }
1574 
renderImageForBoard(const BoardX & b,QSize size)1575 QImage BoardView::renderImageForBoard(const BoardX &b, QSize size)
1576 {
1577     QImage image;
1578     BoardView boardView(nullptr, BoardView::IgnoreSideToMove | BoardView::SuppressGuessMove);
1579     boardView.setBoard(b);
1580     boardView.setMinimumSize(size);
1581     boardView.resize(size);
1582     boardView.setEnabled(false);
1583     boardView.renderImage(image, 1.0);
1584     return image;
1585 }
1586