add more spacing
[personal-kdebase.git] / workspace / plasma / applets / analog-clock / clock.cpp
blobec13fb1fee1773087b53da4ad7c7f59f340565a2
1 /***************************************************************************
2 * Copyright 2007 by Aaron Seigo <aseigo@kde.org> *
3 * Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
19 ***************************************************************************/
21 #include "clock.h"
23 #include <math.h>
25 #include <QApplication>
26 #include <QBitmap>
27 #include <QGraphicsScene>
28 #include <QMatrix>
29 #include <QPaintEvent>
30 #include <QPainter>
31 #include <QPainterPath>
32 #include <QPixmap>
33 #include <QStyleOptionGraphicsItem>
34 #include <QVBoxLayout>
35 #include <QHBoxLayout>
36 #include <QCheckBox>
37 #include <QPushButton>
38 #include <QSpinBox>
40 #include <KConfigDialog>
41 #include <KDebug>
42 #include <KLocale>
43 #include <KIcon>
44 #include <KSharedConfig>
45 #include <KTimeZoneWidget>
46 #include <KDialog>
48 #include <Plasma/Dialog>
49 #include <Plasma/PaintUtils>
50 #include <Plasma/Svg>
51 #include <Plasma/Theme>
53 Clock::Clock(QObject *parent, const QVariantList &args)
54 : ClockApplet(parent, args),
55 m_showSecondHand(false),
56 m_showTimezoneString(false),
57 m_repaintCache(RepaintAll),
58 m_secondHandUpdateTimer(0)
60 KGlobal::locale()->insertCatalog("libplasmaclock");
62 setHasConfigurationInterface(true);
63 resize(125, 125);
64 setAspectRatioMode(Plasma::Square);
66 m_theme = new Plasma::Svg(this);
67 m_theme->setImagePath("widgets/clock");
68 m_theme->setContainsMultipleImages(true);
69 m_theme->resize(size());
71 connect(m_theme, SIGNAL(repaintNeeded()), this, SLOT(repaintNeeded()));
74 Clock::~Clock()
78 void Clock::init()
80 ClockApplet::init();
82 KConfigGroup cg = config();
83 m_showSecondHand = cg.readEntry("showSecondHand", false);
84 m_showTimezoneString = cg.readEntry("showTimezoneString", false);
85 m_fancyHands = cg.readEntry("fancyHands", false);
86 setCurrentTimezone(cg.readEntry("timezone", localTimezone()));
88 connectToEngine();
91 void Clock::connectToEngine()
93 Plasma::DataEngine* timeEngine = dataEngine("time");
94 if (m_showSecondHand) {
95 timeEngine->connectSource(currentTimezone(), this, 500);
96 } else {
97 timeEngine->connectSource(currentTimezone(), this, 6000, Plasma::AlignToMinute);
101 void Clock::constraintsEvent(Plasma::Constraints constraints)
103 ClockApplet::constraintsEvent(constraints);
105 if (constraints & Plasma::FormFactorConstraint) {
106 setBackgroundHints(NoBackground);
109 if (constraints & Plasma::SizeConstraint) {
110 m_theme->resize(size());
113 m_repaintCache = RepaintAll;
116 QPainterPath Clock::shape() const
118 if (m_theme->hasElement("hint-square-clock")) {
119 return Applet::shape();
122 QPainterPath path;
123 // we adjust by 2px all around to allow for smoothing the jaggies
124 // if the ellipse is too small, we'll get a nastily jagged edge around the clock
125 path.addEllipse(boundingRect().adjusted(-2, -2, 2, 2));
126 return path;
129 void Clock::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data)
131 Q_UNUSED(source);
132 m_time = data["Time"].toTime();
134 if (m_time.minute() == m_lastTimeSeen.minute() &&
135 m_time.second() == m_lastTimeSeen.second()) {
136 // avoid unnecessary repaints
137 return;
140 if (m_time.minute() != m_lastTimeSeen.minute()) {
141 m_repaintCache = RepaintHands;
144 if (Plasma::ToolTipManager::self()->isVisible(this)) {
145 updateTipContent();
148 if (m_secondHandUpdateTimer) {
149 m_secondHandUpdateTimer->stop();
152 m_lastTimeSeen = m_time;
153 update();
156 void Clock::createClockConfigurationInterface(KConfigDialog *parent)
158 //TODO: Make the size settable
159 QWidget *widget = new QWidget();
160 ui.setupUi(widget);
161 parent->addPage(widget, i18n("General"), icon());
163 ui.showSecondHandCheckBox->setChecked(m_showSecondHand);
164 ui.showTimezoneStringCheckBox->setChecked(m_showTimezoneString);
167 void Clock::clockConfigAccepted()
169 KConfigGroup cg = config();
170 m_showSecondHand = ui.showSecondHandCheckBox->isChecked();
171 m_showTimezoneString = ui.showTimezoneStringCheckBox->isChecked();
173 cg.writeEntry("showSecondHand", m_showSecondHand);
174 cg.writeEntry("showTimezoneString", m_showTimezoneString);
175 update();
177 dataEngine("time")->disconnectSource(currentTimezone(), this);
178 connectToEngine();
180 //TODO: why we don't call updateConstraints()?
181 constraintsEvent(Plasma::AllConstraints);
182 emit configNeedsSaving();
185 void Clock::changeEngineTimezone(const QString &oldTimezone, const QString &newTimezone)
187 dataEngine("time")->disconnectSource(oldTimezone, this);
188 Plasma::DataEngine* timeEngine = dataEngine("time");
190 if (m_showSecondHand) {
191 timeEngine->connectSource(newTimezone, this, 500);
192 } else {
193 timeEngine->connectSource(newTimezone, this, 6000, Plasma::AlignToMinute);
196 m_repaintCache = RepaintAll;
199 void Clock::repaintNeeded()
201 m_repaintCache = RepaintAll;
202 update();
205 void Clock::moveSecondHand()
207 //kDebug() << "moving second hand";
208 update();
211 void Clock::drawHand(QPainter *p, const QRect &rect, const qreal verticalTranslation, const qreal rotation, const QString &handName)
213 // this code assumes the following conventions in the svg file:
214 // - the _vertical_ position of the hands should be set with respect to the center of the face
215 // - the _horizontal_ position of the hands does not matter
216 // - the _shadow_ elements should have the same vertical position as their _hand_ element counterpart
218 QRectF elementRect;
219 QString name = handName + "HandShadow";
220 if (m_theme->hasElement(name)) {
221 p->save();
223 elementRect = m_theme->elementRect(name);
224 static const QPoint offset = QPoint(2, 3);
226 p->translate(rect.width()/2+offset.x(), rect.height()/2+offset.y());
227 p->rotate(rotation);
228 p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation);
229 m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
231 p->restore();
234 p->save();
236 name = handName + "Hand";
237 elementRect = m_theme->elementRect(name);
239 p->translate(rect.width()/2, rect.height()/2);
240 p->rotate(rotation);
241 p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation);
242 m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
244 p->restore();
247 void Clock::paintInterface(QPainter *p, const QStyleOptionGraphicsItem *option, const QRect &rect)
249 Q_UNUSED(option)
251 // compute hand angles
252 const qreal minutes = 6.0 * m_time.minute() - 180;
253 const qreal hours = 30.0 * m_time.hour() - 180 +
254 ((m_time.minute() / 59.0) * 30.0);
255 qreal seconds = 0;
256 if (m_showSecondHand) {
257 static const double anglePerSec = 6;
258 seconds = anglePerSec * m_time.second() - 180;
260 if (m_fancyHands) {
261 if (!m_secondHandUpdateTimer) {
262 m_secondHandUpdateTimer = new QTimer(this);
263 connect(m_secondHandUpdateTimer, SIGNAL(timeout()), this, SLOT(moveSecondHand()));
266 if (!m_secondHandUpdateTimer->isActive()) {
267 //kDebug() << "starting second hand movement";
268 m_secondHandUpdateTimer->start(50);
269 m_animationStart = QTime::currentTime().msec();
270 } else {
271 static const int runTime = 500;
272 static const double m = 1; // Mass
273 static const double b = 1; // Drag coefficient
274 static const double k = 1.5; // Spring constant
275 static const double PI = 3.141592653589793; // the universe is irrational
276 static const double gamma = b / (2 * m); // Dampening constant
277 static const double omega0 = sqrt(k / m);
278 static const double omega1 = sqrt(omega0 * omega0 - gamma * gamma);
279 const double elapsed = QTime::currentTime().msec() - m_animationStart;
280 const double t = (4 * PI) * (elapsed / runTime);
281 const double val = 1 + exp(-gamma * t) * -cos(omega1 * t);
283 if (elapsed > runTime) {
284 m_secondHandUpdateTimer->stop();
285 } else {
286 seconds += -anglePerSec + (anglePerSec * val);
289 } else {
290 if (!m_secondHandUpdateTimer) {
291 m_secondHandUpdateTimer = new QTimer(this);
292 connect(m_secondHandUpdateTimer, SIGNAL(timeout()), this, SLOT(moveSecondHand()));
295 if (!m_secondHandUpdateTimer->isActive()) {
296 m_secondHandUpdateTimer->start(50);
297 seconds += 1;
298 } else {
299 m_secondHandUpdateTimer->stop();
304 // paint face and glass cache
305 if (m_repaintCache == RepaintAll) {
306 m_faceCache = QPixmap(rect.size());
307 m_glassCache = QPixmap(rect.size());
308 m_faceCache.fill(Qt::transparent);
309 m_glassCache.fill(Qt::transparent);
311 QPainter facePainter(&m_faceCache);
312 QPainter glassPainter(&m_glassCache);
313 facePainter.setRenderHint(QPainter::SmoothPixmapTransform);
314 glassPainter.setRenderHint(QPainter::SmoothPixmapTransform);
316 m_theme->paint(&facePainter, rect, "ClockFace");
318 // optionally paint the time string
319 if (m_showTimezoneString || shouldDisplayTimezone()) {
320 QString time = prettyTimezone();
321 QFontMetrics fm(QApplication::font());
322 const int margin = 4;
324 if (!time.isEmpty()) {
325 const qreal labelHeight = fm.height() + 2 * margin;
326 // for small clocks, compute a minimum offset
327 qreal labelOffset = m_theme->elementRect("HandCenterScrew").height() / 2 + labelHeight;
328 // for larger clocks, add a relative component to the offset
329 if ((rect.height() / 2) / 3 > labelHeight) {
330 labelOffset += rect.height() / 2 * 0.05;
332 QRect textRect(rect.width() / 2 - fm.width(time) / 2, rect.height() / 2 - labelOffset,
333 fm.width(time), fm.height());
335 facePainter.setPen(Qt::NoPen);
336 QColor background = Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor);
337 background.setAlphaF(0.5);
338 facePainter.setBrush(background);
340 facePainter.setRenderHint(QPainter::Antialiasing, true);
341 facePainter.drawPath(Plasma::PaintUtils::roundedRectangle(textRect.adjusted(-margin, -margin, margin, margin), margin));
342 facePainter.setRenderHint(QPainter::Antialiasing, false);
344 facePainter.setPen(Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
346 facePainter.setFont(Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont));
347 facePainter.drawText(textRect, Qt::AlignCenter, time);
351 glassPainter.save();
352 QRectF elementRect = QRectF(QPointF(0, 0), m_theme->elementSize("HandCenterScrew"));
353 glassPainter.translate(rect.width() / 2 - elementRect.width() / 2, rect.height() / 2 - elementRect.height() / 2);
354 m_theme->paint(&glassPainter, elementRect, "HandCenterScrew");
355 glassPainter.restore();
357 m_theme->paint(&glassPainter, rect, "Glass");
359 // get vertical translation, see drawHand() for more details
360 m_verticalTranslation = m_theme->elementRect("ClockFace").center().y();
363 // paint hour and minute hands cache
364 if (m_repaintCache == RepaintHands || m_repaintCache == RepaintAll) {
365 m_handsCache = QPixmap(rect.size());
366 m_handsCache.fill(Qt::transparent);
368 QPainter handsPainter(&m_handsCache);
369 handsPainter.setRenderHint(QPainter::SmoothPixmapTransform);
371 drawHand(&handsPainter, rect, m_verticalTranslation, hours, "Hour");
372 drawHand(&handsPainter, rect, m_verticalTranslation, minutes, "Minute");
375 // reset repaint cache flag
376 m_repaintCache = RepaintNone;
378 // paint caches and second hand
379 p->setRenderHint(QPainter::SmoothPixmapTransform);
380 p->drawPixmap(rect, m_faceCache, rect);
381 p->drawPixmap(rect, m_handsCache, rect);
382 if (m_showSecondHand) {
383 drawHand(p, rect, m_verticalTranslation, seconds, "Second");
385 p->drawPixmap(rect, m_glassCache, rect);
389 #include "clock.moc"