/*************************************************************************** * Copyright 2007 by Aaron Seigo * * Copyright 2007 by Riccardo Iaconelli * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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"