1 /***************************************************************************
2 * Copyright 2007 by Aaron Seigo <aseigo@kde.org> *
3 * Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org> *
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. *
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. *
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 ***************************************************************************/
25 #include <QApplication>
27 #include <QGraphicsScene>
29 #include <QPaintEvent>
31 #include <QPainterPath>
33 #include <QStyleOptionGraphicsItem>
34 #include <QVBoxLayout>
35 #include <QHBoxLayout>
37 #include <QPushButton>
40 #include <KConfigDialog>
44 #include <KSharedConfig>
45 #include <KTimeZoneWidget>
48 #include <Plasma/Dialog>
49 #include <Plasma/PaintUtils>
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);
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()));
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()));
91 void Clock::connectToEngine()
93 Plasma::DataEngine
* timeEngine
= dataEngine("time");
94 if (m_showSecondHand
) {
95 timeEngine
->connectSource(currentTimezone(), this, 500);
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();
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));
129 void Clock::dataUpdated(const QString
& source
, const Plasma::DataEngine::Data
&data
)
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
140 if (m_time
.minute() != m_lastTimeSeen
.minute()) {
141 m_repaintCache
= RepaintHands
;
144 if (Plasma::ToolTipManager::self()->isVisible(this)) {
148 if (m_secondHandUpdateTimer
) {
149 m_secondHandUpdateTimer
->stop();
152 m_lastTimeSeen
= m_time
;
156 void Clock::createClockConfigurationInterface(KConfigDialog
*parent
)
158 //TODO: Make the size settable
159 QWidget
*widget
= new QWidget();
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
);
177 dataEngine("time")->disconnectSource(currentTimezone(), this);
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);
193 timeEngine
->connectSource(newTimezone
, this, 6000, Plasma::AlignToMinute
);
196 m_repaintCache
= RepaintAll
;
199 void Clock::repaintNeeded()
201 m_repaintCache
= RepaintAll
;
205 void Clock::moveSecondHand()
207 //kDebug() << "moving second hand";
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
219 QString name
= handName
+ "HandShadow";
220 if (m_theme
->hasElement(name
)) {
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());
228 p
->translate(-elementRect
.width()/2, elementRect
.y()-verticalTranslation
);
229 m_theme
->paint(p
, QRectF(QPointF(0, 0), elementRect
.size()), name
);
236 name
= handName
+ "Hand";
237 elementRect
= m_theme
->elementRect(name
);
239 p
->translate(rect
.width()/2, rect
.height()/2);
241 p
->translate(-elementRect
.width()/2, elementRect
.y()-verticalTranslation
);
242 m_theme
->paint(p
, QRectF(QPointF(0, 0), elementRect
.size()), name
);
247 void Clock::paintInterface(QPainter
*p
, const QStyleOptionGraphicsItem
*option
, const QRect
&rect
)
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);
256 if (m_showSecondHand
) {
257 static const double anglePerSec
= 6;
258 seconds
= anglePerSec
* m_time
.second() - 180;
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();
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();
286 seconds
+= -anglePerSec
+ (anglePerSec
* val
);
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);
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
);
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
);