File indexing completed on 2024-05-12 17:07:09

0001 /*
0002     Plasma analog-clock drawing code:
0003 
0004     SPDX-FileCopyrightText: 1998 Luca Montecchiani <m.luca@usa.net>
0005     SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org>
0006     SPDX-FileCopyrightText: 2007 Riccardo Iaconelli <riccardo@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "dtime.h"
0012 
0013 #include <stdlib.h>
0014 #include <sys/types.h>
0015 #include <time.h>
0016 #include <unistd.h>
0017 
0018 #include <QGroupBox>
0019 #include <QPainter>
0020 #include <QPushButton>
0021 #include <QTimeEdit>
0022 
0023 #include <KColorScheme>
0024 #include <KConfig>
0025 #include <KGlobal>
0026 #include <KMessageBox>
0027 #include <KProcess>
0028 #include <KSystemTimeZone>
0029 #include <KTreeWidgetSearchLine>
0030 #include <QDebug>
0031 #include <QGridLayout>
0032 #include <QHBoxLayout>
0033 #include <QVBoxLayout>
0034 
0035 #include <Plasma/Svg>
0036 
0037 #include "timedated_interface.h"
0038 
0039 #include "helper.h"
0040 
0041 Dtime::Dtime(QWidget *parent, bool haveTimeDated)
0042     : QWidget(parent)
0043     , m_haveTimedated(haveTimeDated)
0044 {
0045     setupUi(this);
0046 
0047     connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::serverTimeCheck);
0048     connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::configChanged);
0049 
0050     timeServerList->setEditable(false);
0051     connect(timeServerList, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &Dtime::configChanged);
0052     connect(timeServerList, &QComboBox::editTextChanged, this, &Dtime::configChanged);
0053     connect(setDateTimeAuto, &QCheckBox::toggled, timeServerList, &QComboBox::setEnabled);
0054     timeServerList->setEnabled(false);
0055     timeServerList->setEditable(true);
0056 
0057     if (!haveTimeDated) {
0058         findNTPutility();
0059         if (ntpUtility.isEmpty()) {
0060             QString toolTip = i18n(
0061                 "No NTP utility has been found. "
0062                 "Install 'ntpdate' or 'rdate' command to enable automatic "
0063                 "updating of date and time.");
0064             setDateTimeAuto->setEnabled(false);
0065             setDateTimeAuto->setToolTip(toolTip);
0066             timeServerList->setToolTip(toolTip);
0067         }
0068     }
0069 
0070     QVBoxLayout *v2 = new QVBoxLayout(timeBox);
0071     v2->setContentsMargins(0, 0, 0, 0);
0072 
0073     kclock = new Kclock(timeBox);
0074     kclock->setObjectName(QStringLiteral("Kclock"));
0075     kclock->setMinimumSize(150, 150);
0076     v2->addWidget(kclock);
0077 
0078     QHBoxLayout *v3 = new QHBoxLayout();
0079     v2->addLayout(v3);
0080 
0081     v3->addStretch();
0082 
0083     timeEdit = new QTimeEdit(timeBox);
0084     timeEdit->setWrapping(true);
0085     timeEdit->setDisplayFormat(KLocale::global()->use12Clock() ? "hh:mm:ss ap" : "HH:mm:ss");
0086     v3->addWidget(timeEdit);
0087 
0088     v3->addStretch();
0089 
0090     QString wtstr = i18n(
0091         "Here you can change the system time. Click into the"
0092         " hours, minutes or seconds field to change the relevant value, either"
0093         " using the up and down buttons to the right or by entering a new value.");
0094     timeEdit->setWhatsThis(wtstr);
0095 
0096     connect(timeEdit, &QTimeEdit::timeChanged, this, &Dtime::set_time);
0097     connect(cal, &KDatePicker::dateChanged, this, &Dtime::changeDate);
0098 
0099     connect(&internalTimer, &QTimer::timeout, this, &Dtime::timeout);
0100 
0101     kclock->setEnabled(false);
0102 
0103     // Timezone
0104     connect(tzonelist, &K4TimeZoneWidget::itemSelectionChanged, this, &Dtime::configChanged);
0105     tzonesearch->setTreeWidget(tzonelist);
0106 }
0107 
0108 void Dtime::currentZone()
0109 {
0110     KTimeZone localZone = KSystemTimeZones::local();
0111 
0112     if (localZone.abbreviations().isEmpty()) {
0113         m_local->setText(i18nc("%1 is name of time zone", "Current local time zone: %1", K4TimeZoneWidget::displayName(localZone)));
0114     } else {
0115         m_local->setText(i18nc("%1 is name of time zone, %2 is its abbreviation",
0116                                "Current local time zone: %1 (%2)",
0117                                K4TimeZoneWidget::displayName(localZone),
0118                                QString::fromUtf8(localZone.abbreviations().constFirst())));
0119     }
0120 }
0121 
0122 void Dtime::serverTimeCheck()
0123 {
0124     // Enable time and date if the ntp utility is missing
0125     bool enabled = ntpUtility.isEmpty() || !setDateTimeAuto->isChecked();
0126     cal->setEnabled(enabled);
0127     timeEdit->setEnabled(enabled);
0128     // kclock->setEnabled(enabled);
0129 }
0130 
0131 void Dtime::findNTPutility()
0132 {
0133     QByteArray envpath = qgetenv("PATH");
0134     if (!envpath.isEmpty() && envpath.startsWith(':')) {
0135         envpath.remove(0, 1);
0136     }
0137 
0138     QStringList path = {"/sbin", "/usr/sbin"};
0139     if (!envpath.isEmpty()) {
0140         path += QFile::decodeName(envpath).split(QLatin1Char(':'));
0141     } else {
0142         path += {"/bin", "/usr/bin"};
0143     }
0144 
0145     const auto possible_ntputilities = {"ntpdate", "rdate"};
0146     for (const QString &possible_ntputility : possible_ntputilities) {
0147         ntpUtility = QStandardPaths::findExecutable(possible_ntputility, path);
0148         if (!ntpUtility.isEmpty()) {
0149             qDebug() << "ntpUtility = " << ntpUtility;
0150             return;
0151         }
0152     }
0153 
0154     qDebug() << "ntpUtility not found!";
0155 }
0156 
0157 void Dtime::set_time()
0158 {
0159     if (ontimeout)
0160         return;
0161 
0162     internalTimer.stop();
0163 
0164     time = timeEdit->time();
0165     kclock->setTime(time);
0166 
0167     Q_EMIT timeChanged(true);
0168 }
0169 
0170 void Dtime::changeDate(const QDate &d)
0171 {
0172     date = d;
0173     Q_EMIT timeChanged(true);
0174 }
0175 
0176 void Dtime::configChanged()
0177 {
0178     Q_EMIT timeChanged(true);
0179 }
0180 
0181 void Dtime::load()
0182 {
0183     QString currentTimeZone;
0184 
0185     if (m_haveTimedated) {
0186         OrgFreedesktopTimedate1Interface timeDatedIface(QStringLiteral("org.freedesktop.timedate1"),
0187                                                         QStringLiteral("/org/freedesktop/timedate1"),
0188                                                         QDBusConnection::systemBus());
0189         // the server list is not relevant for timesyncd, it fetches it from the network
0190         timeServerList->setVisible(false);
0191         timeServerLabel->setVisible(false);
0192         setDateTimeAuto->setEnabled(timeDatedIface.canNTP());
0193         setDateTimeAuto->setChecked(timeDatedIface.nTP());
0194 
0195         currentTimeZone = timeDatedIface.timezone();
0196     } else {
0197         // The config is actually written to the system config, but the user does not have any local config,
0198         // since there is nothing writing it.
0199         KConfig _config(QStringLiteral("kcmclockrc"), KConfig::NoGlobals);
0200         KConfigGroup config(&_config, "NTP");
0201         timeServerList->clear();
0202         timeServerList->addItems(config
0203                                      .readEntry("servers", i18n("Public Time Server (pool.ntp.org),\
0204         asia.pool.ntp.org,\
0205         europe.pool.ntp.org,\
0206         north-america.pool.ntp.org,\
0207         oceania.pool.ntp.org"))
0208                                      .split(',', Qt::SkipEmptyParts));
0209         setDateTimeAuto->setChecked(config.readEntry("enabled", false));
0210 
0211         if (ntpUtility.isEmpty()) {
0212             timeServerList->setEnabled(false);
0213         }
0214         currentTimeZone = KSystemTimeZones::local().name();
0215     }
0216 
0217     // Reset to the current date and time
0218     time = QTime::currentTime();
0219     date = QDate::currentDate();
0220     cal->setDate(date);
0221 
0222     // start internal timer
0223     internalTimer.start(1000);
0224 
0225     timeout();
0226 
0227     // Timezone
0228     currentZone();
0229 
0230     tzonelist->setSelected(currentTimeZone, true);
0231     Q_EMIT timeChanged(false);
0232 }
0233 
0234 QString Dtime::selectedTimeZone() const
0235 {
0236     QStringList selectedZones(tzonelist->selection());
0237     if (!selectedZones.isEmpty()) {
0238         return selectedZones.first();
0239     }
0240 
0241     return QString();
0242 }
0243 
0244 QStringList Dtime::ntpServers() const
0245 {
0246     // Save the order, but don't duplicate!
0247     QStringList list;
0248     if (timeServerList->count() != 0)
0249         list.append(timeServerList->currentText());
0250     for (int i = 0; i < timeServerList->count(); i++) {
0251         QString text = timeServerList->itemText(i);
0252         if (!list.contains(text))
0253             list.append(text);
0254         // Limit so errors can go away and not stored forever
0255         if (list.count() == 10)
0256             break;
0257     }
0258     return list;
0259 }
0260 
0261 bool Dtime::ntpEnabled() const
0262 {
0263     return setDateTimeAuto->isChecked();
0264 }
0265 
0266 QDateTime Dtime::userTime() const
0267 {
0268     return QDateTime(date, QTime(timeEdit->time()));
0269 }
0270 
0271 void Dtime::processHelperErrors(int code)
0272 {
0273     if (code & ClockHelper::NTPError) {
0274         KMessageBox::error(this, i18n("Unable to contact time server: %1.", timeServer));
0275         setDateTimeAuto->setChecked(false);
0276     }
0277     if (code & ClockHelper::DateError) {
0278         KMessageBox::error(this, i18n("Can not set date."));
0279     }
0280     if (code & ClockHelper::TimezoneError)
0281         KMessageBox::error(this, i18n("Error setting new time zone."), i18n("Time zone Error"));
0282 }
0283 
0284 void Dtime::timeout()
0285 {
0286     // get current time
0287     time = QTime::currentTime();
0288 
0289     ontimeout = true;
0290     timeEdit->setTime(time);
0291     ontimeout = false;
0292 
0293     kclock->setTime(time);
0294 }
0295 
0296 QString Dtime::quickHelp() const
0297 {
0298     return i18n(
0299         "<h1>Date & Time</h1> This system settings module can be used to set the system date and"
0300         " time. As these settings do not only affect you as a user, but rather the whole system, you"
0301         " can only change these settings when you start the System Settings as root. If you do not have"
0302         " the root password, but feel the system time should be corrected, please contact your system"
0303         " administrator.");
0304 }
0305 
0306 Kclock::Kclock(QWidget *parent)
0307     : QWidget(parent)
0308 {
0309     m_theme = new Plasma::Svg(this);
0310     m_theme->setImagePath(QStringLiteral("widgets/clock"));
0311     m_theme->setContainsMultipleImages(true);
0312 }
0313 
0314 Kclock::~Kclock()
0315 {
0316     delete m_theme;
0317 }
0318 
0319 void Kclock::showEvent(QShowEvent *event)
0320 {
0321     setClockSize(size());
0322     QWidget::showEvent(event);
0323 }
0324 
0325 void Kclock::resizeEvent(QResizeEvent *)
0326 {
0327     setClockSize(size());
0328 }
0329 
0330 void Kclock::setClockSize(const QSize &size)
0331 {
0332     int dim = qMin(size.width(), size.height());
0333     QSize newSize = QSize(dim, dim) * devicePixelRatioF();
0334 
0335     if (newSize != m_faceCache.size()) {
0336         m_faceCache = QPixmap(newSize);
0337         m_handsCache = QPixmap(newSize);
0338         m_glassCache = QPixmap(newSize);
0339         m_faceCache.setDevicePixelRatio(devicePixelRatioF());
0340         m_handsCache.setDevicePixelRatio(devicePixelRatioF());
0341         m_glassCache.setDevicePixelRatio(devicePixelRatioF());
0342 
0343         m_theme->resize(QSize(dim, dim));
0344         m_repaintCache = RepaintAll;
0345     }
0346 }
0347 
0348 void Kclock::setTime(const QTime &time)
0349 {
0350     if (time.minute() != this->time.minute() || time.hour() != this->time.hour()) {
0351         if (m_repaintCache == RepaintNone) {
0352             m_repaintCache = RepaintHands;
0353         }
0354     }
0355     this->time = time;
0356     update();
0357 }
0358 
0359 void Kclock::drawHand(QPainter *p, const QRect &rect, const qreal verticalTranslation, const qreal rotation, const QString &handName)
0360 {
0361     // this code assumes the following conventions in the svg file:
0362     // - the _vertical_ position of the hands should be set with respect to the center of the face
0363     // - the _horizontal_ position of the hands does not matter
0364     // - the _shadow_ elements should have the same vertical position as their _hand_ element counterpart
0365 
0366     QRectF elementRect;
0367     QString name = handName + "HandShadow";
0368     if (m_theme->hasElement(name)) {
0369         p->save();
0370 
0371         elementRect = m_theme->elementRect(name);
0372         if (rect.height() < 64)
0373             elementRect.setWidth(elementRect.width() * 2.5);
0374         static const QPoint offset = QPoint(2, 3);
0375 
0376         p->translate(rect.x() + (rect.width() / 2) + offset.x(), rect.y() + (rect.height() / 2) + offset.y());
0377         p->rotate(rotation);
0378         p->translate(-elementRect.width() / 2, elementRect.y() - verticalTranslation);
0379         m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
0380 
0381         p->restore();
0382     }
0383 
0384     p->save();
0385 
0386     name = handName + "Hand";
0387     elementRect = m_theme->elementRect(name);
0388     if (rect.height() < 64) {
0389         elementRect.setWidth(elementRect.width() * 2.5);
0390     }
0391 
0392     p->translate(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
0393     p->rotate(rotation);
0394     p->translate(-elementRect.width() / 2, elementRect.y() - verticalTranslation);
0395     m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name);
0396 
0397     p->restore();
0398 }
0399 
0400 void Kclock::paintInterface(QPainter *p, const QRect &rect)
0401 {
0402     const bool m_showSecondHand = true;
0403 
0404     // compute hand angles
0405     const qreal minutes = 6.0 * time.minute() - 180;
0406     const qreal hours = 30.0 * time.hour() - 180 + ((time.minute() / 59.0) * 30.0);
0407     qreal seconds = 0;
0408     if (m_showSecondHand) {
0409         static const double anglePerSec = 6;
0410         seconds = anglePerSec * time.second() - 180;
0411     }
0412 
0413     // paint face and glass cache
0414     QRect faceRect = m_faceCache.rect();
0415     QRect targetRect = QRect(QPoint(0, 0), QSize(qRound(m_faceCache.width() / devicePixelRatioF()), qRound(m_faceCache.height() / devicePixelRatioF())));
0416 
0417     if (m_repaintCache == RepaintAll) {
0418         m_faceCache.fill(Qt::transparent);
0419         m_glassCache.fill(Qt::transparent);
0420 
0421         QPainter facePainter(&m_faceCache);
0422         QPainter glassPainter(&m_glassCache);
0423         facePainter.setRenderHint(QPainter::SmoothPixmapTransform);
0424         glassPainter.setRenderHint(QPainter::SmoothPixmapTransform);
0425 
0426         m_theme->paint(&facePainter, targetRect, QStringLiteral("ClockFace"));
0427 
0428         glassPainter.save();
0429         QRectF elementRect = QRectF(QPointF(0, 0), m_theme->elementSize(QStringLiteral("HandCenterScrew")));
0430         glassPainter.translate(faceRect.width() / (2 * devicePixelRatioF()) - elementRect.width() / 2,
0431                                faceRect.height() / (2 * devicePixelRatioF()) - elementRect.height() / 2);
0432         m_theme->paint(&glassPainter, elementRect, QStringLiteral("HandCenterScrew"));
0433         glassPainter.restore();
0434 
0435         m_theme->paint(&glassPainter, targetRect, QStringLiteral("Glass"));
0436 
0437         // get vertical translation, see drawHand() for more details
0438         m_verticalTranslation = m_theme->elementRect(QStringLiteral("ClockFace")).center().y();
0439     }
0440 
0441     // paint hour and minute hands cache
0442     if (m_repaintCache == RepaintHands || m_repaintCache == RepaintAll) {
0443         m_handsCache.fill(Qt::transparent);
0444 
0445         QPainter handsPainter(&m_handsCache);
0446         handsPainter.drawPixmap(targetRect, m_faceCache, faceRect);
0447         handsPainter.setRenderHint(QPainter::SmoothPixmapTransform);
0448 
0449         drawHand(&handsPainter, targetRect, m_verticalTranslation, hours, QStringLiteral("Hour"));
0450         drawHand(&handsPainter, targetRect, m_verticalTranslation, minutes, QStringLiteral("Minute"));
0451     }
0452 
0453     // reset repaint cache flag
0454     m_repaintCache = RepaintNone;
0455 
0456     // paint caches and second hand
0457     if (targetRect.width() < rect.width()) {
0458         targetRect.moveLeft((rect.width() - targetRect.width()) / 2);
0459     }
0460 
0461     p->drawPixmap(targetRect, m_handsCache, faceRect);
0462     if (m_showSecondHand) {
0463         p->setRenderHint(QPainter::SmoothPixmapTransform);
0464         drawHand(p, targetRect, m_verticalTranslation, seconds, QStringLiteral("Second"));
0465     }
0466     p->drawPixmap(targetRect, m_glassCache, faceRect);
0467 }
0468 
0469 void Kclock::paintEvent(QPaintEvent *)
0470 {
0471     QPainter paint(this);
0472 
0473     paint.setRenderHint(QPainter::Antialiasing);
0474     paintInterface(&paint, rect());
0475 }