File indexing completed on 2024-05-19 04:04:54

0001 /*
0002     This file is part of the game 'KJumpingCube'
0003 
0004     SPDX-FileCopyrightText: 1998-2000 Matthias Kiefer <matthias.kiefer@gmx.de>
0005     SPDX-FileCopyrightText: 2012-2013 Ian Wadhan <iandw.au@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kcubeboxwidget.h"
0011 
0012 #include <KGameTheme>
0013 
0014 #include <QTimer>
0015 #include <QLabel>
0016 #include <QPainter>
0017 
0018 #include <assert.h>
0019 #include <QStandardPaths>
0020 
0021 #include "prefs.h"
0022 
0023 #include "kjumpingcube_debug.h"
0024 
0025 KCubeBoxWidget::KCubeBoxWidget (const int d, QWidget *parent)
0026         : QWidget (parent),
0027       m_side          (d),
0028       m_popup         (new QLabel (this))
0029 {
0030    qCDebug(KJUMPINGCUBE_LOG) << "CONSTRUCT KCubeBoxWidget: side" << m_side;
0031    cubes.clear();
0032    init();
0033 }
0034 
0035 KCubeBoxWidget::~KCubeBoxWidget()
0036 {
0037 }
0038 
0039 bool KCubeBoxWidget::loadSettings()
0040 {
0041   qCDebug(KJUMPINGCUBE_LOG) << "LOAD VIEW SETTINGS";
0042   bool reColorCubes = ((color1 != Prefs::color1()) ||
0043                        (color2 != Prefs::color2()) ||
0044                        (color0 != Prefs::color0()));
0045 
0046   color1 = Prefs::color1();
0047   color2 = Prefs::color2();
0048   color0 = Prefs::color0();
0049 
0050   if (Prefs::animationNone()) {
0051      cascadeAnimation = None;
0052   }
0053   else if (Prefs::animationDelay() || (Prefs::animationSpeed() <= 1)) {
0054      cascadeAnimation = Darken;
0055   }
0056   else if (Prefs::animationBlink()) {
0057      cascadeAnimation = RapidBlink;
0058   }
0059   else if (Prefs::animationSpread()) {
0060      cascadeAnimation = Scatter;
0061   }
0062 
0063   animationTime = Prefs::animationSpeed() * 150;
0064 
0065   // NOTE: When the box-size (Prefs::cubeDim()) changes, Game::newGame() calls
0066   //       KCubeBoxWidget::loadSettings() first, then KCubeBoxWidget::setDim().
0067 
0068   if (reColorCubes) {
0069      makeStatusPixmaps (sWidth);        // Make new status pixmaps.
0070      makeSVGCubes (cubeSize);
0071      setColors ();
0072   }
0073   return reColorCubes;
0074 }
0075 
0076 void KCubeBoxWidget::reset()    // Called if a player wins or requests New game.
0077 {
0078    for (KCubeWidget * cube : std::as_const(cubes)) {
0079       cube->reset();
0080    }
0081 
0082    KCubeWidget::enableClicks(true);
0083    currentAnimation = None;
0084 }
0085 
0086 void KCubeBoxWidget::displayCube (int index, Player owner, int value)
0087 {
0088    cubes.at(index)->setOwner (owner);
0089    cubes.at(index)->setValue (value);
0090 }
0091 
0092 void KCubeBoxWidget::highlightCube (int index, bool highlight)
0093 {
0094    if (highlight) {
0095       cubes.at(index)->setDark();
0096    }
0097    else {
0098       cubes.at(index)->setNeutral();
0099    }
0100 }
0101 
0102 void KCubeBoxWidget::timedCubeHighlight (int index)
0103 {
0104    if (m_highlighted > 0) {
0105       highlightDone();
0106    }
0107    cubes.at(index)->setDark();
0108    m_highlighted = index;
0109    m_highlightTimer->start();
0110 }
0111 
0112 void KCubeBoxWidget::highlightDone()
0113 {
0114    cubes.at(m_highlighted)->setNeutral();
0115    m_highlightTimer->stop();
0116    m_highlighted = -1;
0117 }
0118 
0119 void KCubeBoxWidget::setColors ()
0120 {
0121    for (KCubeWidget * cube : std::as_const(cubes)) {
0122       cube->updateColors();
0123    }
0124 }
0125 
0126 void KCubeBoxWidget::setDim(int d)
0127 {
0128    if (d != m_side) {
0129       m_side  = d;
0130       initCubes();
0131       reCalculateGraphics (width(), height());
0132       reset();
0133    }
0134 }
0135 
0136 /* ***************************************************************** **
0137 **                               slots                               **
0138 ** ***************************************************************** */
0139 
0140 void KCubeBoxWidget::setWaitCursor()
0141 {
0142    setCursor (Qt::BusyCursor);
0143 }
0144 
0145 void KCubeBoxWidget::setNormalCursor()
0146 {
0147    setCursor (Qt::PointingHandCursor);
0148 }
0149 
0150 bool KCubeBoxWidget::checkClick (int x, int y)
0151 {
0152    /* IDW TODO - Remove this from the view OR rewrite it as a MouseEvent().
0153     *
0154    // IDW TODO - Write a new mouse-click event for KCubeBoxWidget? Remove the
0155    //            one that KCubeWidget has?
0156    */
0157    qCDebug(KJUMPINGCUBE_LOG) << "Emit mouseClick (" << x << y << ")";
0158    Q_EMIT mouseClick (x, y);
0159    return false;
0160 }
0161 
0162 /* ***************************************************************** **
0163 **                   initializing functions                          **
0164 ** ***************************************************************** */
0165 void KCubeBoxWidget::init()
0166 {
0167    currentAnimation = None;
0168    animationSteps = 12;
0169    animationCount = 0;
0170 
0171    setMinimumSize (200, 200);
0172    color1 = Prefs::color1();            // Set preferred colors.
0173    color2 = Prefs::color2();
0174    color0 = Prefs::color0();
0175 
0176    KGameTheme theme((QByteArray()));
0177    theme.readFromDesktopFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("themes/default.desktop")));
0178    svg.load (theme.graphicsPath());
0179 
0180    initCubes();
0181 
0182    animationTime = Prefs::animationSpeed() * 150;
0183    animationTimer = new QTimer(this);
0184 
0185    m_highlightTimer = new QTimer(this);
0186    m_highlightTimer->setInterval (1500);
0187    m_highlighted = -1;
0188 
0189    connect(animationTimer, &QTimer::timeout, this, &KCubeBoxWidget::nextAnimationStep);
0190    connect(m_highlightTimer, &QTimer::timeout, this, &KCubeBoxWidget::highlightDone);
0191    setNormalCursor();
0192    setPopup();
0193 }
0194 
0195 void KCubeBoxWidget::initCubes()
0196 {
0197    qDeleteAll (cubes);
0198    cubes.clear();
0199 
0200    int nCubes = m_side * m_side;
0201    for (int n = 0; n < nCubes; n++) {
0202       KCubeWidget * cube = new KCubeWidget (this);
0203       cubes.append (cube);
0204       cube->setCoordinates (n / m_side, n % m_side, m_side - 1);
0205       cube->setPixmaps (&elements);
0206       connect(cube, &KCubeWidget::clicked, this, &KCubeBoxWidget::checkClick);
0207       cube->show();
0208    }
0209 }
0210 
0211 void KCubeBoxWidget::makeStatusPixmaps (const int width)
0212 {
0213    qreal d, p;
0214    QImage status (width, width, QImage::Format_ARGB32_Premultiplied);
0215    QPainter s (&status);
0216    sWidth = width;
0217 
0218    d = width/4.0;
0219    p = width/2.0;
0220    status.fill (0);
0221    svg.render (&s, QStringLiteral("player_1"));
0222    colorImage (status, color1, width);
0223    svg.render (&s, QStringLiteral("lighting"));
0224    svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d));
0225    status1 = QPixmap::fromImage (status);
0226 
0227    d = width/5.0;
0228    p = width/3.0;
0229    status.fill (0);
0230    svg.render (&s, QStringLiteral("player_2"));
0231    colorImage (status, color2, width);
0232    svg.render (&s, QStringLiteral("lighting"));
0233    svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d));
0234    svg.render (&s, QStringLiteral("pip"), QRectF (p + p - d/2.0, p + p - d/2.0, d, d));
0235    s.end();
0236    status2 = QPixmap::fromImage (status);
0237 }
0238 
0239 void KCubeBoxWidget::makeSVGBackground (const int w, const int h)
0240 {
0241    QImage img (w, h, QImage::Format_ARGB32_Premultiplied);
0242    QPainter p (&img);
0243    img.fill (0);
0244    svg.render (&p, QStringLiteral("background"));
0245    p.end();
0246    background = QPixmap::fromImage (img);
0247 }
0248 
0249 void KCubeBoxWidget::makeSVGCubes (const int width)
0250 {
0251    QImage img (width, width, QImage::Format_ARGB32_Premultiplied);
0252    QPainter q;                 // Paints whole faces of the dice.
0253 
0254    QImage pip (width/7, width/7, QImage::Format_ARGB32_Premultiplied);
0255    QPainter r;                 // Paints the pips on the faces of the dice.
0256 
0257    QRectF rect (0, 0, width, width);
0258    qreal  pc = 20.0;        // % radius on corners.
0259    elements.clear();
0260    for (int i = FirstElement; i <= LastElement; i++) {
0261      q.begin(&img);
0262      q.setPen (Qt::NoPen);
0263      if (i == Pip) {
0264        pip.fill (0);
0265      }
0266      else {
0267        img.fill (0);
0268      }
0269 
0270      // NOTE: "neutral", "player_1" and "player_2" from file "default.svg" cause
0271      // odd effects at the corners. You get a cleaner look if they are omitted.
0272 
0273      switch (i) {
0274      case Neutral:
0275        // svg.render (&q, "neutral");
0276        q.setBrush (color0);
0277        q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
0278        svg.render (&q, QStringLiteral("lighting"));
0279        break;
0280      case Player1:
0281        // svg.render (&q, "player_1");
0282        q.setBrush (color1);
0283        q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
0284        svg.render (&q, QStringLiteral("lighting"));
0285        break;
0286      case Player2:
0287        // svg.render (&q, "player_2");
0288        q.setBrush (color2);
0289        q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
0290        svg.render (&q, QStringLiteral("lighting"));
0291        break;
0292      case Pip:
0293        r.begin(&pip);
0294        svg.render (&r, QStringLiteral("pip"));
0295        r.end();
0296        break;
0297      case BlinkLight:
0298        svg.render (&q, QStringLiteral("blink_light"));
0299        break;
0300      case BlinkDark:
0301        svg.render (&q, QStringLiteral("blink_dark"));
0302        break;
0303      default:
0304        break;
0305      }
0306      q.end();
0307      elements.append
0308        ((i == Pip) ? QPixmap::fromImage (pip) : QPixmap::fromImage (img));
0309    }
0310 }
0311 
0312 void KCubeBoxWidget::colorImage (QImage & img, const QColor & c, const int w)
0313 {
0314    QRgb rgba = c.rgba();
0315    for (int i = 0; i < w; i++) {
0316       for (int j = 0; j < w; j++) {
0317          if (img.pixel (i, j) != 0) {
0318         img.setPixel (i, j, rgba);
0319          }
0320       }
0321    }
0322 }
0323 
0324 void KCubeBoxWidget::paintEvent (QPaintEvent * /* event unused */)
0325 {
0326    QPainter p (this);
0327    p.drawPixmap (0, 0, background);
0328 }
0329 
0330 void KCubeBoxWidget::resizeEvent (QResizeEvent * event)
0331 {
0332    reCalculateGraphics (event->size().width(), event->size().height());
0333 }
0334 
0335 void KCubeBoxWidget::reCalculateGraphics (const int w, const int h)
0336 {
0337    int boxSize = qMin(w, h);
0338    int frameWidth = boxSize / 30;
0339    // qCDebug(KJUMPINGCUBE_LOG) << "boxSize" << boxSize << "frameWidth" << frameWidth;
0340    boxSize = boxSize - (2 * frameWidth);
0341    cubeSize = (boxSize / m_side);
0342    boxSize = (cubeSize * m_side);
0343    topLeft.setX ((w - boxSize)/2);
0344    topLeft.setY ((h - boxSize)/2);
0345 
0346    // qCDebug(KJUMPINGCUBE_LOG) << "Dimension:" << m_side << "cubeSize:" << cubeSize << "topLeft:" << topLeft;
0347    makeSVGBackground (w, h);
0348    makeSVGCubes (cubeSize);
0349    for (int x = 0; x < m_side; x++) {
0350       for (int y = 0; y < m_side; y++) {
0351      int index = x * m_side + y;
0352          cubes.at (index)->move (
0353                             topLeft.x() + (x * cubeSize),
0354                             topLeft.y() + (y * cubeSize));
0355          cubes.at (index)->resize (cubeSize, cubeSize);
0356       }
0357    }
0358    setPopup();
0359 }
0360 
0361 QSize  KCubeBoxWidget::sizeHint() const
0362 {
0363    return QSize(400,400);
0364 }
0365 
0366 /* ***************************************************************** **
0367 **                   other private functions                         **
0368 ** ***************************************************************** */
0369 
0370 void KCubeBoxWidget::startAnimation (bool cascading, int index)
0371 {
0372    int interval = 0;
0373    m_index = index;
0374    currentAnimation = cascading ? cascadeAnimation : ComputerMove;
0375    switch (currentAnimation) {
0376    case None:
0377       animationCount = 0;
0378       return;               // Should never happen.
0379       break;
0380    case ComputerMove:
0381       interval = 150 + (Prefs::animationSpeed() - 1) * 50;  // 150-600 msec.
0382       animationCount = 4;
0383       cubes.at (index)->setLight();
0384       break;
0385    case Darken:
0386       interval = animationTime;
0387       animationCount = 1;
0388       cubes.at (index)->setDark();
0389       break;
0390    case RapidBlink:
0391       interval = 60 + Prefs::animationSpeed() * 30;     // 120-360 msec.
0392       animationCount = 4;
0393       cubes.at (index)->setLight();
0394       break;
0395    case Scatter:
0396       interval = (animationTime + animationSteps/2) / animationSteps;
0397       animationCount = animationSteps;
0398       break;
0399    }
0400    animationTimer->setInterval (interval);
0401    animationTimer->start();
0402 }
0403 
0404 void KCubeBoxWidget::nextAnimationStep()
0405 {
0406    animationCount--;
0407    if (animationCount < 1) {
0408       animationTimer->stop();       // Finish normally.
0409       cubes.at (m_index)->setNeutral();
0410       currentAnimation = None;
0411       Q_EMIT animationDone (m_index);
0412       return;
0413    }
0414    switch (currentAnimation) {
0415    case None:
0416       return;               // Should not happen.
0417       break;
0418    case ComputerMove:
0419    case RapidBlink:
0420       if (animationCount%2 == 1) {  // Set light or dark phase.
0421          cubes.at (m_index)->setDark();
0422       }
0423       else {
0424          cubes.at (m_index)->setLight();
0425       }
0426       break;
0427    case Darken:
0428       break;                // Should never happen (1 tick).
0429    case Scatter:
0430       int step = animationSteps - animationCount;
0431       if (step <= 2) {          // Set the animation phase.
0432          cubes.at (m_index)->shrink(1.0 - step * 0.3);
0433       }
0434       else if (step < 7) {
0435          cubes.at (m_index)->expand((step - 2) * 0.2);
0436       }
0437       else if (step == 7) {
0438          cubes.at (m_index)->expand(1.2);
0439          scatterDots (0);
0440       }
0441       else {
0442          scatterDots (step - 7);
0443       }
0444       break;
0445    }
0446 }
0447 
0448 void KCubeBoxWidget::scatterDots (int step)
0449 {
0450    Player player = cubes.at(m_index)->owner();
0451    int d = m_side - 1;
0452    int x = m_index / m_side;
0453    int y = m_index % m_side;
0454    if (x > 0) cubes.at (m_index - m_side)->migrateDot (+1,  0, step, player);
0455    if (x < d) cubes.at (m_index + m_side)->migrateDot (-1,  0, step, player);
0456    if (y > 0) cubes.at (m_index - 1)     ->migrateDot ( 0, +1, step, player);
0457    if (y < d) cubes.at (m_index + 1)     ->migrateDot ( 0, -1, step, player);
0458 }
0459 
0460 int KCubeBoxWidget::killAnimation()
0461 {
0462    if (animationTimer->isActive()) {
0463       animationTimer->stop();       // Stop current animation immediately.
0464    }
0465    return m_index;
0466 }
0467 
0468 const QPixmap & KCubeBoxWidget::playerPixmap (const int p)
0469 {
0470    return ((p == 1) ? status1 : status2);
0471 }
0472 
0473 void KCubeBoxWidget::setPopup()
0474 {
0475    QFont f;
0476    f.setPixelSize ((int) (height() * 0.04 + 0.5));
0477    f.setWeight (QFont::Bold);
0478    f.setStretch (QFont::Expanded);
0479    m_popup->setStyleSheet(QStringLiteral("QLabel { color : rgba(255, 255, 255, 75%); }"));
0480    m_popup->setFont (f);
0481    m_popup->resize (width(), (int) (height() * 0.08 + 0.5));
0482    m_popup->setAlignment (Qt::AlignCenter);
0483 }
0484 
0485 void KCubeBoxWidget::showPopup (const QString & message)
0486 {
0487    m_popup->setText (message);
0488    m_popup->move ((this->width()  - m_popup->width()) / 2,
0489                   (this->height() - m_popup->height()) / 2 +
0490                   (cubes.at (0)->height() / 5));
0491    m_popup->raise();
0492    m_popup->show();
0493    update();
0494 }
0495 
0496 void KCubeBoxWidget::hidePopup()
0497 {
0498    m_popup->hide();
0499    update();
0500 }
0501 
0502 #include "moc_kcubeboxwidget.cpp"