mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 19:02:51 +00:00
472 lines
16 KiB
C++
472 lines
16 KiB
C++
/***************************************************************************
|
|
* Copyright 2007 by Aaron Seigo <aseigo@kde.org> *
|
|
* Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org> *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
|
|
***************************************************************************/
|
|
|
|
#include "clock.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <QGraphicsScene>
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <QPixmap>
|
|
#include <QtGui/qstyleoption.h>
|
|
#include <QCheckBox>
|
|
#include <QPushButton>
|
|
|
|
#include <KConfigDialog>
|
|
#include <KDebug>
|
|
#include <KLocale>
|
|
#include <KIcon>
|
|
#include <KIconLoader>
|
|
#include <KSharedConfig>
|
|
|
|
#include <Plasma/Dialog>
|
|
#include <Plasma/FrameSvg>
|
|
#include <Plasma/PaintUtils>
|
|
#include <Plasma/Svg>
|
|
#include <Plasma/Theme>
|
|
|
|
Clock::Clock(QObject *parent, const QVariantList &args)
|
|
: ClockApplet(parent, args),
|
|
m_showSecondHand(false),
|
|
m_showTimezoneString(false),
|
|
m_showingTimezone(false),
|
|
m_tzFrame(0),
|
|
m_repaintCache(RepaintAll),
|
|
m_faceCache(QPixmap()),
|
|
m_handsCache(QPixmap()),
|
|
m_glassCache(QPixmap()),
|
|
m_secondHandUpdateTimer(0),
|
|
m_animateSeconds(false)
|
|
{
|
|
KGlobal::locale()->insertCatalog("libplasmaclock");
|
|
// this catalog is only used once on the first start of the clock to translate the timezone in the configuration file
|
|
KGlobal::locale()->insertCatalog("timezones4");
|
|
setHasConfigurationInterface(true);
|
|
resize(256, 256);
|
|
setAspectRatioMode(Plasma::Square);
|
|
setBackgroundHints(NoBackground);
|
|
|
|
m_theme = new Plasma::Svg(this);
|
|
m_theme->setImagePath("widgets/clock");
|
|
m_theme->setContainsMultipleImages(true);
|
|
m_theme->resize(size());
|
|
|
|
connect(m_theme, SIGNAL(repaintNeeded()), this, SLOT(repaintNeeded()));
|
|
}
|
|
|
|
Clock::~Clock()
|
|
{
|
|
}
|
|
|
|
void Clock::init()
|
|
{
|
|
ClockApplet::init();
|
|
m_oldTimezone = currentTimezone();
|
|
configChanged();
|
|
}
|
|
|
|
void Clock::connectToEngine()
|
|
{
|
|
resetLastTimeSeen();
|
|
|
|
Plasma::DataEngine* timeEngine = dataEngine("time");
|
|
timeEngine->disconnectSource(m_oldTimezone, this);
|
|
m_oldTimezone = currentTimezone();
|
|
|
|
if (m_showSecondHand) {
|
|
timeEngine->connectSource(currentTimezone(), this, 500);
|
|
} else {
|
|
timeEngine->connectSource(currentTimezone(), this, 60000, Plasma::AlignToMinute);
|
|
}
|
|
}
|
|
|
|
void Clock::clockConfigChanged()
|
|
{
|
|
KConfigGroup cg = config();
|
|
m_showSecondHand = cg.readEntry("showSecondHand", false);
|
|
m_showTimezoneString = cg.readEntry("showTimezoneString", false);
|
|
m_showingTimezone = m_showTimezoneString;
|
|
m_fancyHands = cg.readEntry("fancyHands", false);
|
|
setCurrentTimezone(cg.readEntry("timezone", localTimezone()));
|
|
|
|
if (m_showSecondHand) {
|
|
//We don't need to cache the applet if it update every seconds
|
|
setCacheMode(QGraphicsItem::NoCache);
|
|
} else {
|
|
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
|
|
}
|
|
|
|
connectToEngine();
|
|
invalidateCache();
|
|
}
|
|
|
|
void Clock::constraintsEvent(Plasma::Constraints constraints)
|
|
{
|
|
ClockApplet::constraintsEvent(constraints);
|
|
|
|
if (constraints & Plasma::SizeConstraint) {
|
|
invalidateCache();
|
|
}
|
|
|
|
if (constraints & Plasma::FormFactorConstraint) {
|
|
if (formFactor() == Plasma::Planar || formFactor() == Plasma::MediaCenter) {
|
|
setPreferredSize(256, 256);
|
|
} else {
|
|
setPreferredSize(-1, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
QPainterPath Clock::shape() const
|
|
{
|
|
if (m_theme->hasElement("hint-square-clock")) {
|
|
return Applet::shape();
|
|
}
|
|
|
|
QPainterPath path;
|
|
// we adjust by 2px all around to allow for smoothing the jaggies
|
|
// if the ellipse is too small, we'll get a nastily jagged edge around the clock
|
|
path.addEllipse(boundingRect().adjusted(-2, -2, 2, 2));
|
|
return path;
|
|
}
|
|
|
|
void Clock::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data)
|
|
{
|
|
Q_UNUSED(source);
|
|
m_time = data["Time"].toTime();
|
|
|
|
if (m_time.minute() != lastTimeSeen().minute() && m_repaintCache == RepaintNone) {
|
|
m_repaintCache = RepaintHands;
|
|
}
|
|
|
|
if (Plasma::ToolTipManager::self()->isVisible(this)) {
|
|
updateTipContent();
|
|
}
|
|
|
|
if (m_secondHandUpdateTimer) {
|
|
m_secondHandUpdateTimer->stop();
|
|
}
|
|
|
|
m_animateSeconds = true;
|
|
|
|
updateClockApplet(data);
|
|
update();
|
|
}
|
|
|
|
void Clock::createClockConfigurationInterface(KConfigDialog *parent)
|
|
{
|
|
//TODO: Make the size settable
|
|
QWidget *widget = new QWidget();
|
|
ui.setupUi(widget);
|
|
parent->addPage(widget, i18n("Appearance"), "view-media-visualization");
|
|
|
|
ui.showSecondHandCheckBox->setChecked(m_showSecondHand);
|
|
ui.showTimezoneStringCheckBox->setChecked(m_showTimezoneString);
|
|
|
|
connect(ui.showSecondHandCheckBox, SIGNAL(stateChanged(int)), parent, SLOT(settingsModified()));
|
|
connect(ui.showTimezoneStringCheckBox, SIGNAL(stateChanged(int)), parent, SLOT(settingsModified()));
|
|
}
|
|
|
|
void Clock::clockConfigAccepted()
|
|
{
|
|
KConfigGroup cg = config();
|
|
m_showTimezoneString = ui.showTimezoneStringCheckBox->isChecked();
|
|
m_showingTimezone = m_showTimezoneString || shouldDisplayTimezone();
|
|
m_showSecondHand = ui.showSecondHandCheckBox->isChecked();
|
|
|
|
if (m_showSecondHand) {
|
|
//We don't need to cache the applet if it update every seconds
|
|
setCacheMode(QGraphicsItem::NoCache);
|
|
} else {
|
|
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
|
|
}
|
|
|
|
cg.writeEntry("showSecondHand", m_showSecondHand);
|
|
cg.writeEntry("showTimezoneString", m_showTimezoneString);
|
|
update();
|
|
|
|
dataEngine("time")->disconnectSource(currentTimezone(), this);
|
|
connectToEngine();
|
|
|
|
constraintsEvent(Plasma::AllConstraints);
|
|
emit configNeedsSaving();
|
|
}
|
|
|
|
void Clock::changeEngineTimezone(const QString &oldTimezone, const QString &newTimezone)
|
|
{
|
|
dataEngine("time")->disconnectSource(oldTimezone, this);
|
|
Plasma::DataEngine* timeEngine = dataEngine("time");
|
|
|
|
if (m_showSecondHand) {
|
|
timeEngine->connectSource(newTimezone, this, 500);
|
|
} else {
|
|
timeEngine->connectSource(newTimezone, this, 60000, Plasma::AlignToMinute);
|
|
}
|
|
|
|
if (m_showingTimezone != (m_showTimezoneString || shouldDisplayTimezone())) {
|
|
m_showingTimezone = !m_showingTimezone;
|
|
constraintsEvent(Plasma::SizeConstraint);
|
|
}
|
|
m_repaintCache = RepaintAll;
|
|
}
|
|
|
|
void Clock::repaintNeeded()
|
|
{
|
|
m_repaintCache = RepaintAll;
|
|
update();
|
|
}
|
|
|
|
void Clock::moveSecondHand()
|
|
{
|
|
//kDebug() << "moving second hand";
|
|
update();
|
|
}
|
|
|
|
void Clock::drawHand(QPainter *p, const QRect &rect, const qreal verticalTranslation, const qreal rotation, const QString &handName)
|
|
{
|
|
// this code assumes the following conventions in the svg file:
|
|
// - the _vertical_ position of the hands should be set with respect to the center of the face
|
|
// - the _horizontal_ position of the hands does not matter
|
|
// - the _shadow_ elements should have the same vertical position as their _hand_ element counterpart
|
|
|
|
QRectF elementRect;
|
|
QString name = handName + "HandShadow";
|
|
if (m_theme->hasElement(name)) {
|
|
p->save();
|
|
|
|
elementRect = m_theme->elementRect(name);
|
|
if( rect.height() < KIconLoader::SizeEnormous )
|
|
elementRect.setWidth( elementRect.width() * 2.5 );
|
|
static const QPoint offset = QPoint(2, 3);
|
|
|
|
p->translate(rect.x() + (rect.width() / 2) + offset.x(), rect.y() + (rect.height() / 2) + offset.y());
|
|
p->rotate(rotation);
|
|
p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation);
|
|
m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
|
|
|
|
p->restore();
|
|
}
|
|
|
|
p->save();
|
|
|
|
name = handName + "Hand";
|
|
elementRect = m_theme->elementRect(name);
|
|
if (rect.height() < KIconLoader::SizeEnormous) {
|
|
elementRect.setWidth(elementRect.width() * 2.5);
|
|
}
|
|
|
|
p->translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
|
|
p->rotate(rotation);
|
|
p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation);
|
|
m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
|
|
|
|
p->restore();
|
|
}
|
|
|
|
void Clock::paintInterface(QPainter *p, const QStyleOptionGraphicsItem *option, const QRect &rect)
|
|
{
|
|
Q_UNUSED(option)
|
|
|
|
// compute hand angles
|
|
const qreal minutes = 6.0 * m_time.minute() - 180;
|
|
const qreal hours = 30.0 * m_time.hour() - 180 +
|
|
((m_time.minute() / 59.0) * 30.0);
|
|
qreal seconds = 0;
|
|
if (m_showSecondHand) {
|
|
static const double anglePerSec = 6;
|
|
seconds = anglePerSec * m_time.second() - 180;
|
|
|
|
if (m_fancyHands) {
|
|
if (!m_secondHandUpdateTimer) {
|
|
m_secondHandUpdateTimer = new QTimer(this);
|
|
connect(m_secondHandUpdateTimer, SIGNAL(timeout()), this, SLOT(moveSecondHand()));
|
|
}
|
|
|
|
if (m_animateSeconds && !m_secondHandUpdateTimer->isActive()) {
|
|
//kDebug() << "starting second hand movement";
|
|
m_secondHandUpdateTimer->start(50);
|
|
m_animationStart = QTime::currentTime().msec();
|
|
} else {
|
|
static const int runTime = 500;
|
|
static const double m = 1; // Mass
|
|
static const double b = 1; // Drag coefficient
|
|
static const double k = 1.5; // Spring constant
|
|
static const double PI = 3.141592653589793; // the universe is irrational
|
|
static const double gamma = b / (2 * m); // Dampening constant
|
|
static const double omega0 = sqrt(k / m);
|
|
static const double omega1 = sqrt(omega0 * omega0 - gamma * gamma);
|
|
const double elapsed = QTime::currentTime().msec() - m_animationStart;
|
|
const double t = (4 * PI) * (elapsed / runTime);
|
|
const double val = 1 + exp(-gamma * t) * -cos(omega1 * t);
|
|
|
|
if (elapsed > runTime) {
|
|
m_secondHandUpdateTimer->stop();
|
|
m_animateSeconds = false;
|
|
} else {
|
|
seconds += -anglePerSec + (anglePerSec * val);
|
|
}
|
|
}
|
|
} else {
|
|
if (!m_secondHandUpdateTimer) {
|
|
m_secondHandUpdateTimer = new QTimer(this);
|
|
connect(m_secondHandUpdateTimer, SIGNAL(timeout()), this, SLOT(moveSecondHand()));
|
|
}
|
|
|
|
if (m_animationStart != seconds && !m_secondHandUpdateTimer->isActive()) {
|
|
m_secondHandUpdateTimer->start(50);
|
|
m_animationStart = seconds; //we don't want to have a second animation if there is a external update (wallpaper etc).
|
|
seconds += 1;
|
|
} else {
|
|
m_secondHandUpdateTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (contentsRect().size().toSize() != m_theme->size()) {
|
|
invalidateCache();
|
|
}
|
|
|
|
// paint face and glass cache
|
|
QRect faceRect = m_faceCache.rect();
|
|
if (m_repaintCache == RepaintAll) {
|
|
m_faceCache.fill(Qt::transparent);
|
|
m_glassCache.fill(Qt::transparent);
|
|
|
|
QPainter facePainter(&m_faceCache);
|
|
QPainter glassPainter(&m_glassCache);
|
|
facePainter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
glassPainter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
m_theme->paint(&facePainter, faceRect, "ClockFace");
|
|
|
|
glassPainter.save();
|
|
QRectF elementRect = QRectF(QPointF(0, 0), m_theme->elementSize("HandCenterScrew"));
|
|
glassPainter.translate(faceRect.width() / 2 - elementRect.width() / 2, faceRect.height() / 2 - elementRect.height() / 2);
|
|
m_theme->paint(&glassPainter, elementRect, "HandCenterScrew");
|
|
glassPainter.restore();
|
|
|
|
m_theme->paint(&glassPainter, faceRect, "Glass");
|
|
|
|
// get vertical translation, see drawHand() for more details
|
|
m_verticalTranslation = m_theme->elementRect("ClockFace").center().y();
|
|
}
|
|
|
|
// paint hour and minute hands cache
|
|
if (m_repaintCache == RepaintHands || m_repaintCache == RepaintAll) {
|
|
m_handsCache.fill(Qt::transparent);
|
|
|
|
QPainter handsPainter(&m_handsCache);
|
|
handsPainter.drawPixmap(faceRect, m_faceCache, faceRect);
|
|
handsPainter.setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
drawHand(&handsPainter, faceRect, m_verticalTranslation, hours, "Hour");
|
|
drawHand(&handsPainter, faceRect, m_verticalTranslation, minutes, "Minute");
|
|
}
|
|
|
|
// reset repaint cache flag
|
|
m_repaintCache = RepaintNone;
|
|
|
|
// paint caches and second hand
|
|
QRect targetRect = faceRect;
|
|
if (targetRect.width() < rect.width()) {
|
|
targetRect.moveLeft((rect.width() - targetRect.width()) / 2);
|
|
}
|
|
targetRect.translate(rect.topLeft());
|
|
|
|
p->drawPixmap(targetRect, m_handsCache, faceRect);
|
|
if (m_showSecondHand) {
|
|
p->setRenderHint(QPainter::SmoothPixmapTransform);
|
|
drawHand(p, targetRect, m_verticalTranslation, seconds, "Second");
|
|
}
|
|
p->drawPixmap(targetRect, m_glassCache, faceRect);
|
|
|
|
// optionally paint the time string
|
|
if (m_showingTimezone) {
|
|
QString time = prettyTimezone();
|
|
|
|
if (!time.isEmpty()) {
|
|
QRect textRect = tzRect(time);
|
|
tzFrame()->paintFrame(p, textRect, QRect(QPoint(0, 0), textRect.size()));
|
|
|
|
qreal left, top, right, bottom;
|
|
tzFrame()->getMargins(left, top, right, bottom);
|
|
|
|
p->setPen(Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
|
|
p->setFont(Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont));
|
|
p->drawText(textRect.adjusted(left, 0, -right, 0), Qt::AlignCenter, time);
|
|
}
|
|
}
|
|
}
|
|
|
|
QRect Clock::tzRect(const QString &text)
|
|
{
|
|
QRect rect = contentsRect().toRect();
|
|
|
|
QFont font = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
|
|
QFontMetrics fontMetrics(font);
|
|
|
|
qreal left, top, right, bottom;
|
|
tzFrame()->getMargins(left, top, right, bottom);
|
|
|
|
int width = left + right + fontMetrics.width(text) + fontMetrics.averageCharWidth() * 2;
|
|
int height = top + bottom + fontMetrics.height();
|
|
width = qMin(width, rect.width());
|
|
height = qMin(height, rect.height());
|
|
|
|
return QRect((rect.width() - width) / 2, rect.bottom() - height, width, height);
|
|
}
|
|
|
|
Plasma::FrameSvg *Clock::tzFrame()
|
|
{
|
|
if (!m_tzFrame) {
|
|
m_tzFrame = new Plasma::FrameSvg(this);
|
|
m_tzFrame->setImagePath("widgets/background");
|
|
}
|
|
|
|
return m_tzFrame;
|
|
}
|
|
|
|
void Clock::invalidateCache()
|
|
{
|
|
m_repaintCache = RepaintAll;
|
|
|
|
QSize pixmapSize = contentsRect().size().toSize();
|
|
const int squareEdge = qMin(pixmapSize.width(), pixmapSize.height());
|
|
|
|
if (m_showingTimezone) {
|
|
QRect tzArea = tzRect(prettyTimezone());
|
|
pixmapSize.setHeight(qMax(10, squareEdge - tzArea.height()));
|
|
tzFrame()->resizeFrame(tzArea.size());
|
|
}
|
|
|
|
pixmapSize.setWidth(pixmapSize.height());
|
|
m_faceCache = QPixmap(pixmapSize);
|
|
m_handsCache = QPixmap(pixmapSize);
|
|
m_glassCache = QPixmap(pixmapSize);
|
|
|
|
m_faceCache.fill(Qt::transparent);
|
|
m_glassCache.fill(Qt::transparent);
|
|
m_handsCache.fill(Qt::transparent);
|
|
|
|
m_theme->resize(pixmapSize);
|
|
}
|
|
|
|
#include "moc_clock.cpp"
|