* dtime.cpp
*
* Copyright (C) 1998 Luca Montecchiani <m.luca@usa.net>
*
* Plasma analog-clock drawing code:
*
* 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 "dtime.h"
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <QComboBox>
#include <QGroupBox>
#include <QPushButton>
#include <QPainter>
#include <QTimeEdit>
#include <QCheckBox>
#include <QPaintEvent>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDebug>
#include <kprocess.h>
#include <kmessagebox.h>
#include <kconfig.h>
#include <kcolorscheme.h>
#include <ksystemtimezone.h>
#include <KTreeWidgetSearchLine>
#include <KGlobal>
#include <Plasma/Svg>
#include "timedated_interface.h"
#include "helper.h"
Dtime::Dtime(QWidget * parent, bool haveTimeDated):
QWidget(parent),
m_haveTimedated(haveTimeDated)
{
setupUi(this);
connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::serverTimeCheck);
connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::configChanged);
timeServerList->setEditable(false);
connect(timeServerList, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Dtime::configChanged);
connect(timeServerList, &QComboBox::editTextChanged, this, &Dtime::configChanged);
connect(setDateTimeAuto, &QCheckBox::toggled, timeServerList, &QComboBox::setEnabled);
timeServerList->setEnabled(false);
timeServerList->setEditable(true);
if (!haveTimeDated) {
findNTPutility();
if (ntpUtility.isEmpty()) {
QString toolTip = i18n("No NTP utility has been found. "
"Install 'ntpdate' or 'rdate' command to enable automatic "
"updating of date and time.");
setDateTimeAuto->setEnabled(false);
setDateTimeAuto->setToolTip(toolTip);
timeServerList->setToolTip(toolTip);
}
}
QVBoxLayout *v2 = new QVBoxLayout( timeBox );
v2->setContentsMargins( 0, 0, 0, 0 );
kclock = new Kclock( timeBox );
kclock->setObjectName(QStringLiteral("Kclock"));
kclock->setMinimumSize(150,150);
v2->addWidget( kclock );
v2->addSpacing( style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) );
QHBoxLayout *v3 = new QHBoxLayout( );
v2->addLayout( v3 );
v3->addStretch();
timeEdit = new QTimeEdit( timeBox );
timeEdit->setWrapping(true);
timeEdit->setDisplayFormat(KLocale::global()->use12Clock() ? "hh:mm:ss ap" : "HH:mm:ss");
v3->addWidget(timeEdit);
v3->addStretch();
QString wtstr = i18n("Here you can change the system time. Click into the"
" hours, minutes or seconds field to change the relevant value, either"
" using the up and down buttons to the right or by entering a new value.");
timeEdit->setWhatsThis( wtstr );
connect(timeEdit, &QTimeEdit::timeChanged, this, &Dtime::set_time);
connect(cal, &KDatePicker::dateChanged, this, &Dtime::changeDate);
connect(&internalTimer, &QTimer::timeout, this, &Dtime::timeout);
kclock->setEnabled(false);
connect(tzonelist, &K4TimeZoneWidget::itemSelectionChanged, this, &Dtime::configChanged);
tzonesearch->setTreeWidget(tzonelist);
}
void Dtime::currentZone()
{
KTimeZone localZone = KSystemTimeZones::local();
if (localZone.abbreviations().isEmpty()) {
m_local->setText(i18nc("%1 is name of time zone", "Current local time zone: %1",
K4TimeZoneWidget::displayName(localZone)));
} else {
m_local->setText(i18nc("%1 is name of time zone, %2 is its abbreviation",
"Current local time zone: %1 (%2)",
K4TimeZoneWidget::displayName(localZone),
QString::fromUtf8(localZone.abbreviations().first())));
}
}
void Dtime::serverTimeCheck() {
bool enabled = ntpUtility.isEmpty() || !setDateTimeAuto->isChecked();
cal->setEnabled(enabled);
timeEdit->setEnabled(enabled);
}
void Dtime::findNTPutility(){
QByteArray envpath = qgetenv("PATH");
if (!envpath.isEmpty() && envpath.startsWith(':')) {
envpath.remove(0, 1);
}
QStringList path = {"/sbin", "/usr/sbin"};
if (!envpath.isEmpty()) {
path += QFile::decodeName(envpath).split(QLatin1Char(':'));
} else {
path += {"/bin", "/usr/bin"};
}
const auto possible_ntputilities = {"ntpdate", "rdate"};
for (const QString &possible_ntputility : possible_ntputilities) {
auto ntpUtility = QStandardPaths::findExecutable(possible_ntputility, path);
if (!ntpUtility.isEmpty()) {
qDebug() << "ntpUtility = " << ntpUtility;
return;
}
}
qDebug() << "ntpUtility not found!";
}
void Dtime::set_time()
{
if( ontimeout )
return;
internalTimer.stop();
time = timeEdit->time();
kclock->setTime( time );
emit timeChanged( true );
}
void Dtime::changeDate(const QDate &d)
{
date = d;
emit timeChanged( true );
}
void Dtime::configChanged(){
emit timeChanged( true );
}
void Dtime::load()
{
QString currentTimeZone;
if (m_haveTimedated) {
OrgFreedesktopTimedate1Interface timeDatedIface(QStringLiteral("org.freedesktop.timedate1"), QStringLiteral("/org/freedesktop/timedate1"), QDBusConnection::systemBus());
timeServerList->setVisible(false);
timeServerLabel->setVisible(false);
setDateTimeAuto->setEnabled(timeDatedIface.canNTP());
setDateTimeAuto->setChecked(timeDatedIface.nTP());
currentTimeZone = timeDatedIface.timezone();
} else {
KConfig _config( QStringLiteral("kcmclockrc"), KConfig::NoGlobals );
KConfigGroup config(&_config, "NTP");
timeServerList->clear();
timeServerList->addItems(config.readEntry("servers",
i18n("Public Time Server (pool.ntp.org),\
asia.pool.ntp.org,\
europe.pool.ntp.org,\
north-america.pool.ntp.org,\
oceania.pool.ntp.org")).split(',', QString::SkipEmptyParts));
setDateTimeAuto->setChecked(config.readEntry("enabled", false));
if (ntpUtility.isEmpty()) {
timeServerList->setEnabled(false);
}
currentTimeZone = KSystemTimeZones::local().name();
}
time = QTime::currentTime();
date = QDate::currentDate();
cal->setDate(date);
internalTimer.start( 1000 );
timeout();
currentZone();
tzonelist->setSelected(currentTimeZone, true);
emit timeChanged(false);
}
QString Dtime::selectedTimeZone() const
{
QStringList selectedZones(tzonelist->selection());
if (!selectedZones.isEmpty()) {
return selectedZones.first();
}
return QString();
}
QStringList Dtime::ntpServers() const
{
QStringList list;
if( timeServerList->count() != 0)
list.append(timeServerList->currentText());
for ( int i=0; i<timeServerList->count();i++ ) {
QString text = timeServerList->itemText(i);
if( !list.contains(text) )
list.append(text);
if( list.count() == 10)
break;
}
return list;
}
bool Dtime::ntpEnabled() const
{
return setDateTimeAuto->isChecked();
}
QDateTime Dtime::userTime() const
{
return QDateTime(date, QTime(timeEdit->time()));
}
void Dtime::processHelperErrors( int code )
{
if( code & ClockHelper::NTPError ) {
KMessageBox::error( this, i18n("Unable to contact time server: %1.", timeServer) );
setDateTimeAuto->setChecked( false );
}
if( code & ClockHelper::DateError ) {
KMessageBox::error( this, i18n("Can not set date."));
}
if( code & ClockHelper::TimezoneError)
KMessageBox::error( this, i18n("Error setting new time zone."),
i18n("Time zone Error"));
}
void Dtime::timeout()
{
time = QTime::currentTime();
ontimeout = true;
timeEdit->setTime(time);
ontimeout = false;
kclock->setTime( time );
}
QString Dtime::quickHelp() const
{
return i18n("<h1>Date & Time</h1> This system settings module can be used to set the system date and"
" time. As these settings do not only affect you as a user, but rather the whole system, you"
" can only change these settings when you start the System Settings as root. If you do not have"
" the root password, but feel the system time should be corrected, please contact your system"
" administrator.");
}
Kclock::Kclock(QWidget *parent)
: QWidget(parent)
{
m_theme = new Plasma::Svg(this);
m_theme->setImagePath(QStringLiteral("widgets/clock"));
m_theme->setContainsMultipleImages(true);
}
Kclock::~Kclock()
{
delete m_theme;
}
void Kclock::showEvent( QShowEvent *event )
{
setClockSize( size() );
QWidget::showEvent( event );
}
void Kclock::resizeEvent( QResizeEvent * )
{
setClockSize( size() );
}
void Kclock::setClockSize(const QSize &size)
{
int dim = qMin(size.width(), size.height());
QSize newSize = QSize(dim, dim) * devicePixelRatioF();
if (newSize != m_faceCache.size()) {
m_faceCache = QPixmap(newSize);
m_handsCache = QPixmap(newSize);
m_glassCache = QPixmap(newSize);
m_faceCache.setDevicePixelRatio(devicePixelRatioF());
m_handsCache.setDevicePixelRatio(devicePixelRatioF());
m_glassCache.setDevicePixelRatio(devicePixelRatioF());
m_theme->resize(QSize(dim, dim));
m_repaintCache = RepaintAll;
}
}
void Kclock::setTime(const QTime &time)
{
if (time.minute() != this->time.minute() || time.hour() != this->time.hour()) {
if (m_repaintCache == RepaintNone) {
m_repaintCache = RepaintHands;
}
}
this->time = time;
update();
}
void Kclock::drawHand(QPainter *p, const QRect &rect, const qreal verticalTranslation, const qreal rotation, const QString &handName)
{
QRectF elementRect;
QString name = handName + "HandShadow";
if (m_theme->hasElement(name)) {
p->save();
elementRect = m_theme->elementRect(name);
if( rect.height() < 64 )
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() < 64) {
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 Kclock::paintInterface(QPainter *p, const QRect &rect)
{
const bool m_showSecondHand = true;
const qreal minutes = 6.0 * time.minute() - 180;
const qreal hours = 30.0 * time.hour() - 180 +
((time.minute() / 59.0) * 30.0);
qreal seconds = 0;
if (m_showSecondHand) {
static const double anglePerSec = 6;
seconds = anglePerSec * time.second() - 180;
}
QRect faceRect = m_faceCache.rect();
QRect targetRect = QRect(QPoint(0, 0), QSize(qRound(m_faceCache.width() / devicePixelRatioF()), qRound(m_faceCache.height() / devicePixelRatioF())));
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, targetRect, QStringLiteral("ClockFace"));
glassPainter.save();
QRectF elementRect = QRectF(QPointF(0, 0), m_theme->elementSize(QStringLiteral("HandCenterScrew")));
glassPainter.translate(faceRect.width() / (2 * devicePixelRatioF()) - elementRect.width() / 2, faceRect.height() / (2 * devicePixelRatioF()) - elementRect.height() / 2);
m_theme->paint(&glassPainter, elementRect, QStringLiteral("HandCenterScrew"));
glassPainter.restore();
m_theme->paint(&glassPainter, targetRect, QStringLiteral("Glass"));
m_verticalTranslation = m_theme->elementRect(QStringLiteral("ClockFace")).center().y();
}
if (m_repaintCache == RepaintHands || m_repaintCache == RepaintAll) {
m_handsCache.fill(Qt::transparent);
QPainter handsPainter(&m_handsCache);
handsPainter.drawPixmap(targetRect, m_faceCache, faceRect);
handsPainter.setRenderHint(QPainter::SmoothPixmapTransform);
drawHand(&handsPainter, targetRect, m_verticalTranslation, hours, QStringLiteral("Hour"));
drawHand(&handsPainter, targetRect, m_verticalTranslation, minutes, QStringLiteral("Minute"));
}
m_repaintCache = RepaintNone;
if (targetRect.width() < rect.width()) {
targetRect.moveLeft((rect.width() - targetRect.width()) / 2);
}
p->drawPixmap(targetRect, m_handsCache, faceRect);
if (m_showSecondHand) {
p->setRenderHint(QPainter::SmoothPixmapTransform);
drawHand(p, targetRect, m_verticalTranslation, seconds, QStringLiteral("Second"));
}
p->drawPixmap(targetRect, m_glassCache, faceRect);
}
void Kclock::paintEvent( QPaintEvent * )
{
QPainter paint(this);
paint.setRenderHint(QPainter::Antialiasing);
paintInterface(&paint, rect());
}