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 | |
37 | KCubeBoxWidget::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 | |
47 | KCubeBoxWidget::~KCubeBoxWidget() |
48 | { |
49 | } |
50 | |
51 | bool 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 | |
88 | void 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 | |
98 | void KCubeBoxWidget::displayCube (int index, Player owner, int value) |
99 | { |
100 | cubes.at(index)->setOwner (owner); |
101 | cubes.at(index)->setValue (value); |
102 | } |
103 | |
104 | void 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 | |
114 | void 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 | |
124 | void KCubeBoxWidget::highlightDone() |
125 | { |
126 | cubes.at(m_highlighted)->setNeutral(); |
127 | m_highlightTimer->stop(); |
128 | m_highlighted = -1; |
129 | } |
130 | |
131 | void KCubeBoxWidget::setColors () |
132 | { |
133 | foreach (KCubeWidget * cube, cubes) { |
134 | cube->updateColors(); |
135 | } |
136 | } |
137 | |
138 | void 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 | |
152 | void KCubeBoxWidget::setWaitCursor() |
153 | { |
154 | setCursor (Qt::BusyCursor); |
155 | } |
156 | |
157 | void KCubeBoxWidget::setNormalCursor() |
158 | { |
159 | setCursor (Qt::PointingHandCursor); |
160 | } |
161 | |
162 | bool 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 | ** ***************************************************************** */ |
177 | void 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 | |
208 | void 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 | |
225 | void 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 | |
253 | void 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 | |
263 | void 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 | |
326 | void 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 | |
338 | void KCubeBoxWidget::paintEvent (QPaintEvent * /* event unused */) |
339 | { |
340 | QPainter p (this); |
341 | p.drawPixmap (0, 0, background); |
342 | } |
343 | |
344 | void KCubeBoxWidget::resizeEvent (QResizeEvent * event) |
345 | { |
346 | reCalculateGraphics (event->size().width(), event->size().height()); |
347 | } |
348 | |
349 | void 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 | |
375 | QSize KCubeBoxWidget::sizeHint() const |
376 | { |
377 | return QSize(400,400); |
378 | } |
379 | |
380 | /* ***************************************************************** ** |
381 | ** other private functions ** |
382 | ** ***************************************************************** */ |
383 | |
384 | void 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 | |
418 | void 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 | |
462 | void 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 | |
474 | int KCubeBoxWidget::killAnimation() |
475 | { |
476 | if (animationTimer->isActive()) { |
477 | animationTimer->stop(); // Stop current animation immediately. |
478 | } |
479 | return m_index; |
480 | } |
481 | |
482 | const QPixmap & KCubeBoxWidget::playerPixmap (const int p) |
483 | { |
484 | return ((p == 1) ? status1 : status2); |
485 | } |
486 | |
487 | void KCubeBoxWidget::() |
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 | |
499 | void KCubeBoxWidget:: (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 | |
510 | void KCubeBoxWidget::() |
511 | { |
512 | m_popup->hide(); |
513 | update(); |
514 | } |
515 | |
516 | #include "kcubeboxwidget.moc" |
517 | |