File indexing completed on 2024-05-12 05:35:39

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