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 }