1/* ****************************************************************************
2 This file is part of the game 'KJumpingCube'
3
4 Copyright (C) 1998-2000 by Matthias Kiefer <matthias.kiefer@gmx.de>
5 Copyright (C) 2012-2013 by Ian Wadhan <iandw.au@gmail.com>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20**************************************************************************** */
21
22#include "kcubeboxwidget.h"
23
24#include <KgTheme>
25#include <KStandardDirs>
26#include <KLocalizedString>
27#include <KMessageBox>
28#include <QTimer>
29#include <QLabel>
30#include <QPainter>
31
32#include <assert.h>
33#include <kcursor.h>
34
35#include "prefs.h"
36
37KCubeBoxWidget::KCubeBoxWidget (const int d, QWidget *parent)
38 : QWidget (parent),
39 m_side (d),
40 m_popup (new QLabel (this))
41{
42 qDebug() << "CONSTRUCT KCubeBoxWidget: side" << m_side;
43 cubes.clear();
44 init();
45}
46
47KCubeBoxWidget::~KCubeBoxWidget()
48{
49}
50
51bool KCubeBoxWidget::loadSettings()
52{
53 qDebug() << "LOAD VIEW SETTINGS";
54 bool reColorCubes = ((color1 != Prefs::color1()) ||
55 (color2 != Prefs::color2()) ||
56 (color0 != Prefs::color0()));
57
58 color1 = Prefs::color1();
59 color2 = Prefs::color2();
60 color0 = Prefs::color0();
61
62 if (Prefs::animationNone()) {
63 cascadeAnimation = None;
64 }
65 else if (Prefs::animationDelay() || (Prefs::animationSpeed() <= 1)) {
66 cascadeAnimation = Darken;
67 }
68 else if (Prefs::animationBlink()) {
69 cascadeAnimation = RapidBlink;
70 }
71 else if (Prefs::animationSpread()) {
72 cascadeAnimation = Scatter;
73 }
74
75 animationTime = Prefs::animationSpeed() * 150;
76
77 // NOTE: When the box-size (Prefs::cubeDim()) changes, Game::newGame() calls
78 // KCubeBoxWidget::loadSettings() first, then KCubeBoxWidget::setDim().
79
80 if (reColorCubes) {
81 makeStatusPixmaps (sWidth); // Make new status pixmaps.
82 makeSVGCubes (cubeSize);
83 setColors ();
84 }
85 return reColorCubes;
86}
87
88void KCubeBoxWidget::reset() // Called if a player wins or requests New game.
89{
90 foreach (KCubeWidget * cube, cubes) {
91 cube->reset();
92 }
93
94 KCubeWidget::enableClicks(true);
95 currentAnimation = None;
96}
97
98void KCubeBoxWidget::displayCube (int index, Player owner, int value)
99{
100 cubes.at(index)->setOwner (owner);
101 cubes.at(index)->setValue (value);
102}
103
104void KCubeBoxWidget::highlightCube (int index, bool highlight)
105{
106 if (highlight) {
107 cubes.at(index)->setDark();
108 }
109 else {
110 cubes.at(index)->setNeutral();
111 }
112}
113
114void KCubeBoxWidget::timedCubeHighlight (int index)
115{
116 if (m_highlighted > 0) {
117 highlightDone();
118 }
119 cubes.at(index)->setDark();
120 m_highlighted = index;
121 m_highlightTimer->start();
122}
123
124void KCubeBoxWidget::highlightDone()
125{
126 cubes.at(m_highlighted)->setNeutral();
127 m_highlightTimer->stop();
128 m_highlighted = -1;
129}
130
131void KCubeBoxWidget::setColors ()
132{
133 foreach (KCubeWidget * cube, cubes) {
134 cube->updateColors();
135 }
136}
137
138void KCubeBoxWidget::setDim(int d)
139{
140 if (d != m_side) {
141 m_side = d;
142 initCubes();
143 reCalculateGraphics (width(), height());
144 reset();
145 }
146}
147
148/* ***************************************************************** **
149** slots **
150** ***************************************************************** */
151
152void KCubeBoxWidget::setWaitCursor()
153{
154 setCursor (Qt::BusyCursor);
155}
156
157void KCubeBoxWidget::setNormalCursor()
158{
159 setCursor (Qt::PointingHandCursor);
160}
161
162bool KCubeBoxWidget::checkClick (int x, int y)
163{
164 /* IDW TODO - Remove this from the view OR rewrite it as a MouseEvent().
165 *
166 // IDW TODO - Write a new mouse-click event for KCubeBoxWidget? Remove the
167 // one that KCubeWidget has?
168 */
169 qDebug() << "Emit mouseClick (" << x << y << ")";
170 emit mouseClick (x, y);
171 return false;
172}
173
174/* ***************************************************************** **
175** initializing functions **
176** ***************************************************************** */
177void KCubeBoxWidget::init()
178{
179 currentAnimation = None;
180 animationSteps = 12;
181 animationCount = 0;
182
183 setMinimumSize (200, 200);
184 color1 = Prefs::color1(); // Set preferred colors.
185 color2 = Prefs::color2();
186 color0 = Prefs::color0();
187
188 KgTheme theme((QByteArray()));
189 theme.readFromDesktopFile(KStandardDirs::locate("appdata",
190 "pics/default.desktop"));
191 svg.load (theme.graphicsPath());
192
193 initCubes();
194
195 animationTime = Prefs::animationSpeed() * 150;
196 animationTimer = new QTimer(this);
197
198 m_highlightTimer = new QTimer(this);
199 m_highlightTimer->setInterval (1500);
200 m_highlighted = -1;
201
202 connect (animationTimer, SIGNAL(timeout()), SLOT(nextAnimationStep()));
203 connect (m_highlightTimer, SIGNAL(timeout()), SLOT(highlightDone()));
204 setNormalCursor();
205 setPopup();
206}
207
208void KCubeBoxWidget::initCubes()
209{
210 qDeleteAll (cubes);
211 cubes.clear();
212
213 int nCubes = m_side * m_side;
214 for (int n = 0; n < nCubes; n++) {
215 KCubeWidget * cube = new KCubeWidget (this);
216 cubes.append (cube);
217 cube->setCoordinates (n / m_side, n % m_side, m_side - 1);
218 cube->setPixmaps (&elements);
219 connect (cube, SIGNAL (clicked(int,int)),
220 SLOT (checkClick(int,int)));
221 cube->show();
222 }
223}
224
225void KCubeBoxWidget::makeStatusPixmaps (const int width)
226{
227 qreal d, p;
228 QImage status (width, width, QImage::Format_ARGB32_Premultiplied);
229 QPainter s (&status);
230 sWidth = width;
231
232 d = width/4.0;
233 p = width/2.0;
234 status.fill (0);
235 svg.render (&s, "player_1");
236 colorImage (status, color1, width);
237 svg.render (&s, "lighting");
238 svg.render (&s, "pip", QRectF (p - d/2.0, p - d/2.0, d, d));
239 status1 = QPixmap::fromImage (status);
240
241 d = width/5.0;
242 p = width/3.0;
243 status.fill (0);
244 svg.render (&s, "player_2");
245 colorImage (status, color2, width);
246 svg.render (&s, "lighting");
247 svg.render (&s, "pip", QRectF (p - d/2.0, p - d/2.0, d, d));
248 svg.render (&s, "pip", QRectF (p + p - d/2.0, p + p - d/2.0, d, d));
249 s.end();
250 status2 = QPixmap::fromImage (status);
251}
252
253void KCubeBoxWidget::makeSVGBackground (const int w, const int h)
254{
255 QImage img (w, h, QImage::Format_ARGB32_Premultiplied);
256 QPainter p (&img);
257 img.fill (0);
258 svg.render (&p, "background");
259 p.end();
260 background = QPixmap::fromImage (img);
261}
262
263void KCubeBoxWidget::makeSVGCubes (const int width)
264{
265 QImage img (width, width, QImage::Format_ARGB32_Premultiplied);
266 QPainter q; // Paints whole faces of the dice.
267
268 QImage pip (width/7, width/7, QImage::Format_ARGB32_Premultiplied);
269 QPainter r; // Paints the pips on the faces of the dice.
270
271 QRectF rect (0, 0, width, width);
272 qreal pc = 20.0; // % radius on corners.
273 elements.clear();
274 for (int i = FirstElement; i <= LastElement; i++) {
275 q.begin(&img);
276 q.setPen (Qt::NoPen);
277 if (i == Pip) {
278 pip.fill (0);
279 }
280 else {
281 img.fill (0);
282 }
283
284 // NOTE: "neutral", "player_1" and "player_2" from file "default.svg" cause
285 // odd effects at the corners. You get a cleaner look if they are omitted.
286
287 switch (i) {
288 case Neutral:
289 // svg.render (&q, "neutral");
290 q.setBrush (color0);
291 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
292 svg.render (&q, "lighting");
293 break;
294 case Player1:
295 // svg.render (&q, "player_1");
296 q.setBrush (color1);
297 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
298 svg.render (&q, "lighting");
299 break;
300 case Player2:
301 // svg.render (&q, "player_2");
302 q.setBrush (color2);
303 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
304 svg.render (&q, "lighting");
305 break;
306 case Pip:
307 r.begin(&pip);
308 svg.render (&r, "pip");
309 r.end();
310 break;
311 case BlinkLight:
312 svg.render (&q, "blink_light");
313 break;
314 case BlinkDark:
315 svg.render (&q, "blink_dark");
316 break;
317 default:
318 break;
319 }
320 q.end();
321 elements.append
322 ((i == Pip) ? QPixmap::fromImage (pip) : QPixmap::fromImage (img));
323 }
324}
325
326void KCubeBoxWidget::colorImage (QImage & img, const QColor & c, const int w)
327{
328 QRgb rgba = c.rgba();
329 for (int i = 0; i < w; i++) {
330 for (int j = 0; j < w; j++) {
331 if (img.pixel (i, j) != 0) {
332 img.setPixel (i, j, rgba);
333 }
334 }
335 }
336}
337
338void KCubeBoxWidget::paintEvent (QPaintEvent * /* event unused */)
339{
340 QPainter p (this);
341 p.drawPixmap (0, 0, background);
342}
343
344void KCubeBoxWidget::resizeEvent (QResizeEvent * event)
345{
346 reCalculateGraphics (event->size().width(), event->size().height());
347}
348
349void KCubeBoxWidget::reCalculateGraphics (const int w, const int h)
350{
351 int boxSize = qMin(w, h);
352 int frameWidth = boxSize / 30;
353 // qDebug() << "boxSize" << boxSize << "frameWidth" << frameWidth;
354 boxSize = boxSize - (2 * frameWidth);
355 cubeSize = (boxSize / m_side);
356 boxSize = (cubeSize * m_side);
357 topLeft.setX ((w - boxSize)/2);
358 topLeft.setY ((h - boxSize)/2);
359
360 // qDebug() << "Dimension:" << m_side << "cubeSize:" << cubeSize << "topLeft:" << topLeft;
361 makeSVGBackground (w, h);
362 makeSVGCubes (cubeSize);
363 for (int x = 0; x < m_side; x++) {
364 for (int y = 0; y < m_side; y++) {
365 int index = x * m_side + y;
366 cubes.at (index)->move (
367 topLeft.x() + (x * cubeSize),
368 topLeft.y() + (y * cubeSize));
369 cubes.at (index)->resize (cubeSize, cubeSize);
370 }
371 }
372 setPopup();
373}
374
375QSize KCubeBoxWidget::sizeHint() const
376{
377 return QSize(400,400);
378}
379
380/* ***************************************************************** **
381** other private functions **
382** ***************************************************************** */
383
384void KCubeBoxWidget::startAnimation (bool cascading, int index)
385{
386 int interval = 0;
387 m_index = index;
388 currentAnimation = cascading ? cascadeAnimation : ComputerMove;
389 switch (currentAnimation) {
390 case None:
391 animationCount = 0;
392 return; // Should never happen.
393 break;
394 case ComputerMove:
395 interval = 150 + (Prefs::animationSpeed() - 1) * 50; // 150-600 msec.
396 animationCount = 4;
397 cubes.at (index)->setLight();
398 break;
399 case Darken:
400 interval = animationTime;
401 animationCount = 1;
402 cubes.at (index)->setDark();
403 break;
404 case RapidBlink:
405 interval = 60 + Prefs::animationSpeed() * 30; // 120-360 msec.
406 animationCount = 4;
407 cubes.at (index)->setLight();
408 break;
409 case Scatter:
410 interval = (animationTime + animationSteps/2) / animationSteps;
411 animationCount = animationSteps;
412 break;
413 }
414 animationTimer->setInterval (interval);
415 animationTimer->start();
416}
417
418void KCubeBoxWidget::nextAnimationStep()
419{
420 animationCount--;
421 if (animationCount < 1) {
422 animationTimer->stop(); // Finish normally.
423 cubes.at (m_index)->setNeutral();
424 currentAnimation = None;
425 emit animationDone (m_index);
426 return;
427 }
428 switch (currentAnimation) {
429 case None:
430 return; // Should not happen.
431 break;
432 case ComputerMove:
433 case RapidBlink:
434 if (animationCount%2 == 1) { // Set light or dark phase.
435 cubes.at (m_index)->setDark();
436 }
437 else {
438 cubes.at (m_index)->setLight();
439 }
440 break;
441 case Darken:
442 break; // Should never happen (1 tick).
443 case Scatter:
444 int step = animationSteps - animationCount;
445 if (step <= 2) { // Set the animation phase.
446 cubes.at (m_index)->shrink(1.0 - step * 0.3);
447 }
448 else if (step < 7) {
449 cubes.at (m_index)->expand((step - 2) * 0.2);
450 }
451 else if (step == 7) {
452 cubes.at (m_index)->expand(1.2);
453 scatterDots (0);
454 }
455 else {
456 scatterDots (step - 7);
457 }
458 break;
459 }
460}
461
462void KCubeBoxWidget::scatterDots (int step)
463{
464 Player player = cubes.at(m_index)->owner();
465 int d = m_side - 1;
466 int x = m_index / m_side;
467 int y = m_index % m_side;
468 if (x > 0) cubes.at (m_index - m_side)->migrateDot (+1, 0, step, player);
469 if (x < d) cubes.at (m_index + m_side)->migrateDot (-1, 0, step, player);
470 if (y > 0) cubes.at (m_index - 1) ->migrateDot ( 0, +1, step, player);
471 if (y < d) cubes.at (m_index + 1) ->migrateDot ( 0, -1, step, player);
472}
473
474int KCubeBoxWidget::killAnimation()
475{
476 if (animationTimer->isActive()) {
477 animationTimer->stop(); // Stop current animation immediately.
478 }
479 return m_index;
480}
481
482const QPixmap & KCubeBoxWidget::playerPixmap (const int p)
483{
484 return ((p == 1) ? status1 : status2);
485}
486
487void KCubeBoxWidget::setPopup()
488{
489 QFont f;
490 f.setPixelSize ((int) (height() * 0.04 + 0.5));
491 f.setWeight (QFont::Bold);
492 f.setStretch (QFont::Expanded);
493 m_popup->setStyleSheet("QLabel { color : rgba(255, 255, 255, 75%); }");
494 m_popup->setFont (f);
495 m_popup->resize (width(), (int) (height() * 0.08 + 0.5));
496 m_popup->setAlignment (Qt::AlignCenter);
497}
498
499void KCubeBoxWidget::showPopup (const QString & message)
500{
501 m_popup->setText (message);
502 m_popup->move ((this->width() - m_popup->width()) / 2,
503 (this->height() - m_popup->height()) / 2 +
504 (cubes.at (0)->height() / 5));
505 m_popup->raise();
506 m_popup->show();
507 update();
508}
509
510void KCubeBoxWidget::hidePopup()
511{
512 m_popup->hide();
513 update();
514}
515
516#include "kcubeboxwidget.moc"
517