1 /*
2 	SPDX-FileCopyrightText: 2009-2021 Graeme Gott <graeme@gottcode.org>
3 
4 	SPDX-License-Identifier: GPL-3.0-or-later
5 */
6 
7 #include "clock.h"
8 
9 #include <QPainter>
10 #include <QPainterPath>
11 #include <QSettings>
12 #include <QTime>
13 #include <QTimer>
14 
15 #include <algorithm>
16 
17 //-----------------------------------------------------------------------------
18 
19 /**
20  * @brief The Clock::Timer class defines how the timer updates for gameplay.
21  */
22 class Clock::Timer
23 {
24 public:
25 	/**
26 	 * Constructs a timer instance.
27 	 */
28 	Timer();
29 
30 	/**
31 	 * Destroys the timer.
32 	 */
33 	virtual ~Timer();
34 
35 	/**
36 	 * Updates timer based on adding a correctly spelled word.
37 	 * @param score how much the word is worth
38 	 * @return @c true if the time remaining has changed by adding the word
39 	 */
40 	virtual bool addWord(int score) = 0;
41 
42 	/**
43 	 * Updates timer based on adding an incorrectly spelled word.
44 	 * @param score how much the word is worth
45 	 * @return @c true if the time remaining has changed by adding the word
46 	 */
47 	virtual bool addIncorrectWord(int score);
48 
49 	/**
50 	 * @return the current display color of the timer
51 	 */
52 	virtual QColor color();
53 
54 	/**
55 	 * @return whether the timer has reached its end
56 	 */
57 	virtual bool isFinished();
58 
59 	/**
60 	 * Resets the timer to its default amount.
61 	 */
62 	virtual void start() = 0;
63 
64 	/**
65 	 * Stops the timer and resets to @c 0.
66 	 */
67 	virtual void stop();
68 
69 	/**
70 	 * @return the timer unique ID
71 	 */
72 	virtual int type() const = 0;
73 
74 	/**
75 	 * Updates the amount of time remaining and the display string of the timer.
76 	 * @return the current display string
77 	 */
78 	virtual QString update();
79 
80 	/**
81 	 * @return how full the timer should be out of a maximum of 180
82 	 */
83 	virtual int width() const;
84 
85 	/**
86 	 * Loads the timer values.
87 	 * @param game where to load the timer values from
88 	 */
89 	void load(const QSettings& game);
90 
91 	/**
92 	 * Stores the timer values.
93 	 * @param game where to save the timer values
94 	 */
95 	void save(QSettings& game);
96 
97 protected:
98 	int m_time; /**< The time remaining on the clock */
99 
100 private:
101 	/**
102 	 * Implementation specific handling of loading timer details.
103 	 * @param game where to load the timer values from
104 	 */
105 	virtual void loadDetails(const QSettings& game);
106 
107 	/**
108 	 * Implementation specific handling of saving timer details.
109 	 * @param game where to save the timer values
110 	 */
111 	virtual void saveDetails(QSettings& game);
112 };
113 
Timer()114 Clock::Timer::Timer()
115 	: m_time(0)
116 {
117 }
118 
~Timer()119 Clock::Timer::~Timer()
120 {
121 }
122 
addIncorrectWord(int)123 bool Clock::Timer::addIncorrectWord(int)
124 {
125 	return false;
126 }
127 
color()128 QColor Clock::Timer::color()
129 {
130 	QColor c;
131 	if (m_time > 20) {
132 		c = QColor(0x37, 0xa4, 0x2b);
133 	} else if (m_time > 10) {
134 		c = QColor(0xff, 0xaa, 0);
135 	} else if (m_time > 0) {
136 		c = QColor(0xbf, 0, 0);
137 	} else {
138 		c = QColor(0x37, 0x37, 0x37);
139 	}
140 	return c;
141 }
142 
isFinished()143 bool Clock::Timer::isFinished()
144 {
145 	return m_time == 0;
146 }
147 
stop()148 void Clock::Timer::stop()
149 {
150 	m_time = 0;
151 }
152 
update()153 QString Clock::Timer::update()
154 {
155 	m_time = std::max(m_time - 1, 0);
156 	return QTime(0, 0, 0).addSecs(m_time).toString(tr("m:ss"));
157 }
158 
width() const159 int Clock::Timer::width() const
160 {
161 	return m_time;
162 }
163 
load(const QSettings & game)164 void Clock::Timer::load(const QSettings& game)
165 {
166 	m_time = std::max(0, game.value("TimerDetails/Time", m_time).toInt()) + 1;
167 	loadDetails(game);
168 }
169 
save(QSettings & game)170 void Clock::Timer::save(QSettings& game)
171 {
172 	if (!isFinished()) {
173 		game.setValue("TimerDetails/Time", m_time);
174 		saveDetails(game);
175 	} else {
176 		stop();
177 	}
178 }
179 
loadDetails(const QSettings &)180 void Clock::Timer::loadDetails(const QSettings&)
181 {
182 }
183 
saveDetails(QSettings &)184 void Clock::Timer::saveDetails(QSettings&)
185 {
186 }
187 
188 //-----------------------------------------------------------------------------
189 
190 /**
191  * @brief The Clock::AllotmentTimer class is a timer that allows 30 guesses.
192  */
193 class Clock::AllotmentTimer : public Clock::Timer
194 {
195 public:
196 	int type() const override;
197 
198 	/**
199 	 * Reduce guesses remaining when a correct guess is made.
200 	 * @return @c true to update display
201 	 */
202 	bool addWord(int) override;
203 
204 	/**
205 	 * Reduce guesses remaining when an incorrect guess is made.
206 	 * @return @c true to update display
207 	 */
208 	bool addIncorrectWord(int) override;
209 
210 	/**
211 	 * Start with 30 guesses.
212 	 */
213 	void start() override;
214 
215 	/**
216 	 * @return the time as "X guesses".
217 	 */
218 	QString update() override;
219 
220 	/**
221 	 * @return the time multiplied by 6 to make it fill the timer display
222 	 */
223 	int width() const override;
224 
225 private:
226 	void loadDetails(const QSettings&) override;
227 };
228 
addWord(int)229 bool Clock::AllotmentTimer::addWord(int)
230 {
231 	m_time = std::max(m_time - 1, 0);
232 	return true;
233 }
234 
addIncorrectWord(int)235 bool Clock::AllotmentTimer::addIncorrectWord(int)
236 {
237 	m_time = std::max(m_time - 1, 0);
238 	return true;
239 }
240 
start()241 void Clock::AllotmentTimer::start()
242 {
243 	m_time = 30;
244 }
245 
type() const246 int Clock::AllotmentTimer::type() const
247 {
248 	return Clock::Allotment;
249 }
250 
update()251 QString Clock::AllotmentTimer::update()
252 {
253 	return tr("%n guesses(s)", "", m_time);
254 }
255 
width() const256 int Clock::AllotmentTimer::width() const
257 {
258 	return m_time * 6;
259 }
260 
loadDetails(const QSettings &)261 void Clock::AllotmentTimer::loadDetails(const QSettings&)
262 {
263 	m_time--;
264 }
265 
266 //-----------------------------------------------------------------------------
267 
268 /**
269  * @brief The Clock::ClassicTimer class is a timer that only counts down from 3 minutes.
270  */
271 class Clock::ClassicTimer : public Clock::Timer
272 {
273 public:
274 	int type() const override;
275 
276 	/**
277 	 * Ignore as guesses do not impact time remaining.
278 	 * @return always returns @c false
279 	 */
280 	bool addWord(int) override;
281 
282 	/**
283 	 * Start with 3 minutes.
284 	 */
285 	void start() override;
286 };
287 
addWord(int)288 bool Clock::ClassicTimer::addWord(int)
289 {
290 	return false;
291 }
292 
start()293 void Clock::ClassicTimer::start()
294 {
295 	m_time = 181;
296 }
297 
type() const298 int Clock::ClassicTimer::type() const
299 {
300 	return Clock::Classic;
301 }
302 
303 //-----------------------------------------------------------------------------
304 
305 class Clock::DisciplineTimer : public Clock::Timer
306 {
307 public:
308 	int type() const override;
309 
310 	/**
311 	 * Increase time based on how much a correct guess is worth.
312 	 * @param score how much the word is worth
313 	 * @return @c true to update display
314 	 */
315 	bool addWord(int score) override;
316 
317 	/**
318 	 * Decrease time based on how much an incorrect guess is worth.
319 	 * @param score how much the word is worth
320 	 * @return @c true to update display
321 	 */
322 	bool addIncorrectWord(int score) override;
323 
324 	/**
325 	 * Start with 30 seconds.
326 	 */
327 	void start() override;
328 };
329 
addWord(int score)330 bool Clock::DisciplineTimer::addWord(int score)
331 {
332 	m_time += score + 8;
333 	return true;
334 }
335 
addIncorrectWord(int score)336 bool Clock::DisciplineTimer::addIncorrectWord(int score)
337 {
338 	m_time = std::max(0, m_time - (score + 8));
339 	return true;
340 }
341 
start()342 void Clock::DisciplineTimer::start()
343 {
344 	m_time = 31;
345 }
346 
type() const347 int Clock::DisciplineTimer::type() const
348 {
349 	return Clock::Discipline;
350 }
351 
352 //-----------------------------------------------------------------------------
353 
354 /**
355  * @brief The Clock::RefillTimer class is a timer that refills on correct guesses.
356  */
357 class Clock::RefillTimer : public Clock::Timer
358 {
359 public:
360 	int type() const override;
361 
362 	/**
363 	 * Refills time back to 30 seconds on a correct guess.
364 	 * @return @c true to update display
365 	 */
366 	bool addWord(int) override;
367 
368 	/**
369 	 * Start with 30 seconds.
370 	 */
371 	void start() override;
372 
373 	/**
374 	 * @return the time multiplied by 6 to make it fill the timer display
375 	 */
376 	int width() const override;
377 };
378 
addWord(int)379 bool Clock::RefillTimer::addWord(int)
380 {
381 	m_time = 31;
382 	return true;
383 }
384 
start()385 void Clock::RefillTimer::start()
386 {
387 	m_time = 31;
388 }
389 
type() const390 int Clock::RefillTimer::type() const
391 {
392 	return Clock::Refill;
393 }
394 
width() const395 int Clock::RefillTimer::width() const
396 {
397 	return m_time * 6;
398 }
399 
400 //-----------------------------------------------------------------------------
401 
402 /**
403  * @brief The Clock::StaminaTimer class is a timer that pauses for 5 seconds on correct guesses.
404  */
405 class Clock::StaminaTimer : public Clock::Timer
406 {
407 public:
408 	int type() const override;
409 
410 	/**
411 	 * Pauses the timer on a correct guess.
412 	 * @return @c true to update display
413 	 */
414 	bool addWord(int) override;
415 
416 	/**
417 	 * @return blue if the timer is paused, otherwise returns the regular timer color
418 	 */
419 	QColor color() override;
420 
421 	/**
422 	 * Start with 45 seconds.
423 	 */
424 	void start() override;
425 
426 	/**
427 	 * Reduces the pause if it exists.
428 	 * @return the pause remaining or the regular display if the time is not paused
429 	 */
430 	QString update() override;
431 
432 	/**
433 	 * @return full 180 if paused, or time multiplied by 4 to fill timer display
434 	 */
435 	int width() const override;
436 
437 private:
438 	/**
439 	 * Load how much pause remains.
440 	 * @param game where to load how much pause remains
441 	 */
442 	void loadDetails(const QSettings& game) override;
443 
444 	/**
445 	 * Store how much pause remains.
446 	 * @param game where to store how much pause remains
447 	 */
448 	void saveDetails(QSettings& game) override;
449 
450 private:
451 	int m_freeze; /**< how much pause is left */
452 };
453 
addWord(int)454 bool Clock::StaminaTimer::addWord(int)
455 {
456 	m_freeze = 6;
457 	return true;
458 }
459 
color()460 QColor Clock::StaminaTimer::color()
461 {
462 	return (m_time && m_freeze) ? QColor(0x33, 0x89, 0xea) : Timer::color();
463 }
464 
start()465 void Clock::StaminaTimer::start()
466 {
467 	m_time = 46;
468 	m_freeze = 0;
469 }
470 
type() const471 int Clock::StaminaTimer::type() const
472 {
473 	return Clock::Stamina;
474 }
475 
update()476 QString Clock::StaminaTimer::update()
477 {
478 	m_freeze = std::max(0, m_freeze - 1);
479 	return (m_freeze) ? tr("+%1").arg(m_freeze) : Timer::update();
480 }
481 
width() const482 int Clock::StaminaTimer::width() const
483 {
484 	if (m_freeze) {
485 		return 180;
486 	} else {
487 		return m_time * 4;
488 	}
489 }
490 
loadDetails(const QSettings & game)491 void Clock::StaminaTimer::loadDetails(const QSettings& game)
492 {
493 	m_freeze = qBound(0, game.value("TimerDetails/Freeze").toInt(), 5) + 1;
494 }
495 
saveDetails(QSettings & game)496 void Clock::StaminaTimer::saveDetails(QSettings& game)
497 {
498 	game.setValue("TimerDetails/Freeze", m_freeze);
499 }
500 
501 //-----------------------------------------------------------------------------
502 
503 /**
504  * @brief The Clock::StrikeoutTimer class is a timer that stops after 3 incorrect guesses.
505  */
506 class Clock::StrikeoutTimer : public Clock::Timer
507 {
508 public:
509 	int type() const override;
510 
511 	/**
512 	 * Ignore adding a correct word as guesses do not impact time remaining.
513 	 * @return always returns @c false
514 	 */
515 	bool addWord(int) override;
516 
517 	/**
518 	 * Decreases amount of strikes remaining.
519 	 * @return @c true to update timer display
520 	 */
521 	bool addIncorrectWord(int score) override;
522 
523 	/**
524 	 * Start with 0 incorrect guesses.
525 	 */
526 	void start() override;
527 
528 	/**
529 	 * Resets count of incorrect guesses.
530 	 */
531 	void stop() override;
532 
533 	/**
534 	 * @return how many guesses remain
535 	 */
536 	QString update() override;
537 
538 	/**
539 	 * @return remaining guesses multipled by 60 to fill timer display
540 	 */
541 	int width() const override;
542 
543 private:
544 	/**
545 	 * Load how many incorrect guesses have happened.
546 	 * @param game where to load the count of incorrect guesses
547 	 */
548 	void loadDetails(const QSettings& game) override;
549 
550 	/**
551 	 * Store how many incorrect guesses have happened.
552 	 * @param game where to store count of incorrect guesses
553 	 */
554 	void saveDetails(QSettings& game) override;
555 
556 private:
557 	int m_strikes; /**< how many incorrect guesses have been made */
558 };
559 
addWord(int)560 bool Clock::StrikeoutTimer::addWord(int)
561 {
562 	return false;
563 }
564 
addIncorrectWord(int)565 bool Clock::StrikeoutTimer::addIncorrectWord(int)
566 {
567 	m_strikes = std::min(m_strikes + 1, 3);
568 	m_time = (3 - m_strikes) * 10;
569 	return true;
570 }
571 
start()572 void Clock::StrikeoutTimer::start()
573 {
574 	m_time = 30;
575 	m_strikes = 0;
576 }
577 
stop()578 void Clock::StrikeoutTimer::stop()
579 {
580 	m_time = 0;
581 	m_strikes = 3;
582 }
583 
type() const584 int Clock::StrikeoutTimer::type() const
585 {
586 	return Clock::Strikeout;
587 }
588 
update()589 QString Clock::StrikeoutTimer::update()
590 {
591 	return tr("%n strike(s)", "", m_strikes);
592 }
593 
width() const594 int Clock::StrikeoutTimer::width() const
595 {
596 	return (3 - m_strikes) * 60;
597 }
598 
loadDetails(const QSettings & game)599 void Clock::StrikeoutTimer::loadDetails(const QSettings& game)
600 {
601 	m_strikes = qBound(0, game.value("TimerDetails/Strikes").toInt(), 3);
602 	m_time = (3 - m_strikes) * 10;
603 }
604 
saveDetails(QSettings & game)605 void Clock::StrikeoutTimer::saveDetails(QSettings& game)
606 {
607 	game.setValue("TimerDetails/Strikes", m_strikes);
608 }
609 
610 //-----------------------------------------------------------------------------
611 
612 /**
613  * @brief The Clock::TangletTimer class is a timer that rewards on correct guesses.
614  */
615 class Clock::TangletTimer : public Clock::Timer
616 {
617 public:
618 	int type() const override;
619 
620 	/**
621 	 * Increase time based on how much a correct guess is worth.
622 	 * @param score how much the word is worth
623 	 * @return @c true to update display
624 	 */
625 	bool addWord(int score) override;
626 
627 	/**
628 	 * Start with 30 seconds.
629 	 */
630 	void start() override;
631 };
632 
addWord(int score)633 bool Clock::TangletTimer::addWord(int score)
634 {
635 	m_time += score + 8;
636 	return true;
637 }
638 
start()639 void Clock::TangletTimer::start()
640 {
641 	m_time = 31;
642 }
643 
type() const644 int Clock::TangletTimer::type() const
645 {
646 	return Clock::Tanglet;
647 }
648 
649 //-----------------------------------------------------------------------------
650 
651 /**
652  * @brief The Clock::UnlimitedTimer class is a timer that does nothing.
653  */
654 class Clock::UnlimitedTimer : public Clock::Timer
655 {
656 public:
657 	int type() const override;
658 
659 	/**
660 	 * Ignore adding a correct word as guesses do not impact anything.
661 	 * @return always returns @c false
662 	 */
663 	bool addWord(int) override;
664 
665 	/**
666 	 * Start with 180 seconds to fill display.
667 	 */
668 	void start() override;
669 
670 	/**
671 	 * @return infinity symbol
672 	 */
673 	QString update() override;
674 };
675 
addWord(int)676 bool Clock::UnlimitedTimer::addWord(int)
677 {
678 	return false;
679 }
680 
start()681 void Clock::UnlimitedTimer::start()
682 {
683 	m_time = 180;
684 }
685 
type() const686 int Clock::UnlimitedTimer::type() const
687 {
688 	return Clock::Unlimited;
689 }
690 
update()691 QString Clock::UnlimitedTimer::update()
692 {
693 	return "∞";
694 }
695 
696 //-----------------------------------------------------------------------------
697 
Clock(QWidget * parent)698 Clock::Clock(QWidget* parent)
699 	: QWidget(parent)
700 	, m_timer(nullptr)
701 {
702 	setTimer(Tanglet);
703 	setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
704 
705 	m_update = new QTimer(this);
706 	m_update->setInterval(1000);
707 	connect(m_update, &QTimer::timeout, this, &Clock::updateTime);
708 
709 	QFont f = font();
710 	f.setBold(true);
711 	setFont(f);
712 }
713 
714 //-----------------------------------------------------------------------------
715 
~Clock()716 Clock::~Clock()
717 {
718 	delete m_timer;
719 }
720 
721 //-----------------------------------------------------------------------------
722 
sizeHint() const723 QSize Clock::sizeHint() const
724 {
725 	return QSize(186, fontMetrics().height() + 8);
726 }
727 
728 //-----------------------------------------------------------------------------
729 
isFinished() const730 bool Clock::isFinished() const
731 {
732 	return m_timer->isFinished();
733 }
734 
735 //-----------------------------------------------------------------------------
736 
addWord(int score)737 void Clock::addWord(int score)
738 {
739 	if (m_timer->addWord(score)) {
740 		updateTime();
741 	}
742 }
743 
744 //-----------------------------------------------------------------------------
745 
addIncorrectWord(int score)746 void Clock::addIncorrectWord(int score)
747 {
748 	if (m_timer->addIncorrectWord(score)) {
749 		updateTime();
750 	}
751 }
752 
753 //-----------------------------------------------------------------------------
754 
setPaused(bool paused)755 void Clock::setPaused(bool paused)
756 {
757 	if (paused) {
758 		m_update->stop();
759 	} else {
760 		m_update->start();
761 	}
762 }
763 
764 //-----------------------------------------------------------------------------
765 
setText(const QString & text)766 void Clock::setText(const QString& text)
767 {
768 	m_text = text;
769 	update();
770 }
771 
772 //-----------------------------------------------------------------------------
773 
start()774 void Clock::start()
775 {
776 	m_timer->start();
777 	updateTime();
778 }
779 
780 //-----------------------------------------------------------------------------
781 
stop()782 void Clock::stop()
783 {
784 	m_timer->stop();
785 	updateTime();
786 }
787 
788 //-----------------------------------------------------------------------------
789 
load(const QSettings & game)790 void Clock::load(const QSettings& game)
791 {
792 	m_timer->load(game);
793 	updateTime();
794 }
795 
796 //-----------------------------------------------------------------------------
797 
save(QSettings & game)798 void Clock::save(QSettings& game)
799 {
800 	m_timer->save(game);
801 }
802 
803 //-----------------------------------------------------------------------------
804 
setTimer(int timer)805 void Clock::setTimer(int timer)
806 {
807 	if (!m_timer || m_timer->type() != timer) {
808 		delete m_timer;
809 
810 		switch (timer) {
811 		case Classic:
812 			m_timer = new ClassicTimer;
813 			break;
814 		case Refill:
815 			m_timer = new RefillTimer;
816 			break;
817 		case Stamina:
818 			m_timer = new StaminaTimer;
819 			break;
820 		case Strikeout:
821 			m_timer = new StrikeoutTimer;
822 			break;
823 		case Allotment:
824 			m_timer = new AllotmentTimer;
825 			break;
826 		case Discipline:
827 			m_timer = new DisciplineTimer;
828 			break;
829 		case Unlimited:
830 			m_timer = new UnlimitedTimer;
831 			break;
832 		case Tanglet:
833 		default:
834 			m_timer = new TangletTimer;
835 			break;
836 		}
837 	}
838 }
839 
840 //-----------------------------------------------------------------------------
841 
timer() const842 int Clock::timer() const
843 {
844 	return m_timer->type();
845 }
846 
847 //-----------------------------------------------------------------------------
848 
timerToString(int timer)849 QString Clock::timerToString(int timer)
850 {
851 	static const QStringList timers = QStringList()
852 			<< tr("Tanglet")
853 			<< tr("Classic")
854 			<< tr("Refill")
855 			<< tr("Stamina")
856 			<< tr("Strikeout")
857 			<< tr("Allotment")
858 			<< tr("Discipline")
859 			<< tr("Unlimited");
860 	return timers.at(qBound(0, timer, TotalTimers - 1));
861 }
862 
863 //-----------------------------------------------------------------------------
864 
timerDescription(int timer)865 QString Clock::timerDescription(int timer)
866 {
867 	static const QStringList timers = QStringList()
868 			<< tr("Counts down from 30 seconds and increases on correct guesses.")
869 			<< tr("Counts down from 3 minutes.")
870 			<< tr("Counts down from 30 seconds and refills on correct guesses.")
871 			<< tr("Counts down from 45 seconds and pauses on correct guesses.")
872 			<< tr("Game ends after 3 incorrect guesses.")
873 			<< tr("Game ends after 30 guesses.")
874 			<< tr("Counts down from 30 seconds and increases or decreases on guesses.")
875 			<< tr("Game ends when all words are found.");
876 	return timers.at(qBound(0, timer, TotalTimers - 1));
877 }
878 
879 //-----------------------------------------------------------------------------
880 
timerScoresGroup(int timer)881 QString Clock::timerScoresGroup(int timer)
882 {
883 	static const QStringList timers = QStringList()
884 			<< QStringLiteral("Scores_Tanglet")
885 			<< QStringLiteral("Scores_Classic")
886 			<< QStringLiteral("Scores_Refill")
887 			<< QStringLiteral("Scores_Stamina")
888 			<< QStringLiteral("Scores_Strikeout")
889 			<< QStringLiteral("Scores_Allotment")
890 			<< QStringLiteral("Scores_Discipline")
891 			<< QStringLiteral("Scores_Unlimited");
892 	return timers.at(qBound(0, timer, TotalTimers - 1));
893 }
894 
895 //-----------------------------------------------------------------------------
896 
paintEvent(QPaintEvent * event)897 void Clock::paintEvent(QPaintEvent* event)
898 {
899 	QWidget::paintEvent(event);
900 
901 	QColor color = m_timer->color();
902 
903 	QPainter painter(this);
904 	painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
905 	painter.setPen(Qt::NoPen);
906 
907 	// Draw indent
908 	QLinearGradient indent_gradient(0, 0, 0, height());
909 	QColor shadow = palette().color(QPalette::Shadow);
910 	indent_gradient.setColorAt(0, QColor(shadow.red(), shadow.green(), shadow.blue(), 64));
911 	indent_gradient.setColorAt(1, QColor(shadow.red(), shadow.green(), shadow.blue(), 0));
912 	painter.setBrush(indent_gradient);
913 	painter.drawRoundedRect(rect(), 4, 4);
914 
915 	// Draw outside border
916 	painter.setBrush(color.darker());
917 	painter.drawRoundedRect(rect().adjusted(1, 1, -1, -1), 3, 3);
918 
919 	// Draw filled background
920 	QLinearGradient gradient(0, 2, 0, height() - 2);
921 	gradient.setColorAt(0, color.lighter(115));
922 	gradient.setColorAt(0.49, color.darker(125));
923 	gradient.setColorAt(0.5, color.darker(150));
924 	gradient.setColorAt(1, color.lighter(105));
925 	painter.setBrush(gradient);
926 	painter.drawRoundedRect(rect().adjusted(2, 2, -2, -2), 3, 3);
927 
928 	int x = m_timer->width();
929 	int width = 180 - std::min(180, x);
930 	if ((width < 180) && (width > 0)) {
931 		// Draw empty space
932 		painter.setBrush(QColor(255, 255, 255, 160));
933 		painter.drawRoundedRect(x + 3, 2, width + 1, rect().height() - 4, 2, 2);
934 
935 		// Draw dividing line
936 		painter.setRenderHint(QPainter::Antialiasing, false);
937 		painter.setPen(color.darker(150));
938 		painter.drawLine(x + 3, 2, x + 3, rect().height() - 2);
939 		painter.setRenderHint(QPainter::Antialiasing, true);
940 	}
941 
942 	// Draw text shadow
943 	QPainterPath path;
944 	path.addText(93 - (fontMetrics().boundingRect(m_text).width() / 2), fontMetrics().ascent() + 3, font(), m_text);
945 	painter.setBrush(Qt::black);
946 	painter.translate(0.6, 0.6);
947 	painter.setPen(QPen(QColor(0, 0, 0, 32), 3));
948 	painter.drawPath(path);
949 	painter.translate(-0.2, -0.2);
950 	painter.setPen(QPen(QColor(0, 0, 0, 64), 2));
951 	painter.drawPath(path);
952 	painter.translate(-0.2, -0.2);
953 	painter.setPen(QPen(QColor(0, 0, 0, 192), 1));
954 	painter.drawPath(path);
955 	painter.translate(-0.2, -0.2);
956 
957 	// Draw text
958 	painter.setPen(Qt::NoPen);
959 	painter.setBrush(Qt::white);
960 	painter.fillPath(path, Qt::white);
961 }
962 
963 //-----------------------------------------------------------------------------
964 
updateTime()965 void Clock::updateTime()
966 {
967 	m_text = m_timer->update();
968 	update();
969 
970 	if (!isFinished()) {
971 		m_update->start();
972 	} else {
973 		m_update->stop();
974 		emit finished();
975 	}
976 }
977 
978 //-----------------------------------------------------------------------------
979