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 }