File indexing completed on 2023-05-30 12:30:43
0001 /* 0002 * Copyright 2001 Stefan Schimanski <1Stein@gmx.de> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0017 */ 0018 0019 #include "ktimer.h" 0020 0021 #include <KConfigGroup> 0022 #include <KGuiItem> 0023 #include <KHelpClient> 0024 #include <KLineEdit> 0025 #include <KSharedConfig> 0026 #include <KStandardAction> 0027 #include <KStandardGuiItem> 0028 #include <KStatusNotifierItem> 0029 #include <QAction> 0030 #include <QTimer> 0031 0032 class KTimerJobItem : public QTreeWidgetItem 0033 { 0034 public: 0035 KTimerJobItem(KTimerJob *job, QTreeWidget *parent) 0036 : QTreeWidgetItem() 0037 { 0038 parent->addTopLevelItem(this); 0039 m_job = job; 0040 m_error = false; 0041 update(); 0042 } 0043 0044 KTimerJobItem(KTimerJob *job, QTreeWidget *parent, QTreeWidgetItem *after) 0045 : QTreeWidgetItem() 0046 { 0047 int otherItemIndex = parent->indexOfTopLevelItem(after); 0048 parent->insertTopLevelItem(otherItemIndex + 1, this); 0049 m_job = job; 0050 m_error = false; 0051 update(); 0052 } 0053 0054 ~KTimerJobItem() override 0055 { 0056 delete m_job; 0057 } 0058 0059 KTimerJob *job() 0060 { 0061 return m_job; 0062 } 0063 0064 void setStatus(bool error) 0065 { 0066 m_error = error; 0067 update(); 0068 } 0069 0070 void update() 0071 { 0072 setText(0, m_job->formatTime(m_job->value())); 0073 0074 if (m_error) 0075 setIcon(0, QIcon::fromTheme(QStringLiteral("process-stop"))); 0076 else 0077 setIcon(0, QPixmap()); 0078 0079 setText(1, m_job->formatTime(m_job->delay())); 0080 0081 switch (m_job->state()) { 0082 case KTimerJob::Stopped: 0083 setIcon(2, QIcon::fromTheme(QStringLiteral("media-playback-stop"))); 0084 break; 0085 case KTimerJob::Paused: 0086 setIcon(2, QIcon::fromTheme(QStringLiteral("media-playback-pause"))); 0087 break; 0088 case KTimerJob::Started: 0089 setIcon(2, QIcon::fromTheme(QStringLiteral("arrow-right"))); 0090 break; 0091 } 0092 0093 setText(3, m_job->command()); 0094 } 0095 0096 private: 0097 bool m_error; 0098 KTimerJob *m_job; 0099 }; 0100 0101 /***************************************************************/ 0102 0103 struct KTimerPrefPrivate { 0104 QList<KTimerJob *> jobs; 0105 }; 0106 0107 KTimerPref::KTimerPref(QWidget *parent) 0108 : QDialog(parent) 0109 { 0110 d = new KTimerPrefPrivate; 0111 0112 setupUi(this); 0113 0114 // set icons 0115 m_stop->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); 0116 m_pause->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); 0117 m_start->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0118 0119 // create tray icon 0120 auto tray = new KStatusNotifierItem(this); 0121 tray->setIconByName(QStringLiteral("ktimer")); 0122 tray->setCategory(KStatusNotifierItem::ApplicationStatus); 0123 tray->setStatus(KStatusNotifierItem::Active); 0124 // set help button gui item 0125 KGuiItem::assign(m_help, KStandardGuiItem::help()); 0126 0127 // Exit 0128 QAction *exitAction = KStandardAction::quit(this, SLOT(exit()), this); 0129 addAction(exitAction); 0130 0131 // connect 0132 connect(m_add, &QPushButton::clicked, this, &KTimerPref::add); 0133 connect(m_remove, &QPushButton::clicked, this, &KTimerPref::remove); 0134 connect(m_help, &QPushButton::clicked, this, &KTimerPref::help); 0135 connect(m_list, &QTreeWidget::currentItemChanged, this, &KTimerPref::currentChanged); 0136 loadJobs(KSharedConfig::openConfig().data()); 0137 0138 show(); 0139 } 0140 0141 KTimerPref::~KTimerPref() 0142 { 0143 delete d; 0144 } 0145 0146 void KTimerPref::saveAllJobs() 0147 { 0148 saveJobs(KSharedConfig::openConfig().data()); 0149 } 0150 0151 void KTimerPref::add() 0152 { 0153 auto job = new KTimerJob; 0154 auto item = new KTimerJobItem(job, m_list); 0155 0156 connect(job, &KTimerJob::delayChanged, this, &KTimerPref::jobChanged); 0157 connect(job, &KTimerJob::valueChanged, this, &KTimerPref::jobChanged); 0158 connect(job, &KTimerJob::stateChanged, this, &KTimerPref::jobChanged); 0159 connect(job, &KTimerJob::commandChanged, this, &KTimerPref::jobChanged); 0160 connect(job, &KTimerJob::finished, this, &KTimerPref::jobFinished); 0161 0162 job->setUser(item); 0163 0164 // Qt drops currentChanged signals on first item (bug?) 0165 if (m_list->topLevelItemCount() == 1) 0166 currentChanged(item, nullptr); 0167 0168 m_list->setCurrentItem(item); 0169 m_list->update(); 0170 } 0171 0172 void KTimerPref::remove() 0173 { 0174 delete m_list->currentItem(); 0175 m_list->update(); 0176 } 0177 0178 void KTimerPref::help() 0179 { 0180 KHelpClient::invokeHelp(); 0181 } 0182 0183 // note, don't use old, but added it so we can connect to the new one 0184 void KTimerPref::currentChanged(QTreeWidgetItem *i, QTreeWidgetItem * /* old */) 0185 { 0186 auto item = static_cast<KTimerJobItem *>(i); 0187 if (item) { 0188 KTimerJob *job = item->job(); 0189 0190 m_state->setEnabled(true); 0191 m_settings->setEnabled(true); 0192 m_remove->setEnabled(true); 0193 m_delayH->disconnect(); 0194 m_delayM->disconnect(); 0195 m_delay->disconnect(); 0196 m_loop->disconnect(); 0197 m_one->disconnect(); 0198 m_consecutive->disconnect(); 0199 m_start->disconnect(); 0200 m_pause->disconnect(); 0201 m_stop->disconnect(); 0202 m_counter->disconnect(); 0203 m_slider->disconnect(); 0204 m_commandLine->disconnect(); 0205 m_commandLine->lineEdit()->disconnect(); 0206 0207 // Set hour, minute and second QSpinBoxes before we connect to signals. 0208 int h, m, s; 0209 job->secondsToHMS(job->delay(), &h, &m, &s); 0210 m_delayH->setValue(h); 0211 m_delayM->setValue(m); 0212 m_delay->setValue(s); 0213 0214 connect(m_commandLine->lineEdit(), &QLineEdit::textChanged, job, &KTimerJob::setCommand); 0215 connect(m_delayH, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KTimerPref::delayChanged); 0216 connect(m_delayM, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KTimerPref::delayChanged); 0217 connect(m_delay, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KTimerPref::delayChanged); 0218 connect(m_loop, &QCheckBox::toggled, job, &KTimerJob::setLoop); 0219 connect(m_one, &QCheckBox::toggled, job, &KTimerJob::setOneInstance); 0220 connect(m_consecutive, &QCheckBox::toggled, job, &KTimerJob::setConsecutive); 0221 connect(m_stop, &QToolButton::clicked, job, &KTimerJob::stop); 0222 connect(m_pause, &QToolButton::clicked, job, &KTimerJob::pause); 0223 connect(m_start, &QToolButton::clicked, job, &KTimerJob::start); 0224 connect(m_slider, SIGNAL(valueChanged(int)), job, SLOT(setValue(int))); 0225 0226 m_commandLine->lineEdit()->setText(job->command()); 0227 m_loop->setChecked(job->loop()); 0228 m_one->setChecked(job->oneInstance()); 0229 m_consecutive->setChecked(job->consecutive()); 0230 m_counter->display((int)job->value()); 0231 m_slider->setMaximum(job->delay()); 0232 m_slider->setValue(job->value()); 0233 0234 } else { 0235 m_state->setEnabled(false); 0236 m_settings->setEnabled(false); 0237 m_remove->setEnabled(false); 0238 } 0239 } 0240 0241 void KTimerPref::jobChanged(KTimerJob *job) 0242 { 0243 auto item = static_cast<KTimerJobItem *>(job->user()); 0244 if (item) { 0245 item->update(); 0246 m_list->update(); 0247 0248 if (item == m_list->currentItem()) { 0249 // XXX optimize 0250 m_slider->setMaximum(job->delay()); 0251 m_slider->setValue(job->value()); 0252 m_counter->display((int)job->value()); 0253 } 0254 } 0255 } 0256 0257 void KTimerPref::jobFinished(KTimerJob *job, bool error) 0258 { 0259 auto item = static_cast<KTimerJobItem *>(job->user()); 0260 item->setStatus(error); 0261 if (m_list->itemBelow(m_list->currentItem()) != nullptr && (static_cast<KTimerJobItem *>(m_list->itemBelow(m_list->currentItem())))->job()->consecutive()) { 0262 m_list->setCurrentItem(m_list->itemBelow(m_list->currentItem())); 0263 (static_cast<KTimerJobItem *>(m_list->currentItem()))->job()->start(); 0264 } 0265 m_list->update(); 0266 } 0267 0268 /* Hour/Minute/Second was changed. This slot calculates the seconds until we start 0269 the job and inform the current job */ 0270 void KTimerPref::delayChanged() 0271 { 0272 auto item = static_cast<KTimerJobItem *>(m_list->currentItem()); 0273 if (item) { 0274 KTimerJob *job = item->job(); 0275 int time_sec = job->timeToSeconds(m_delayH->value(), m_delayM->value(), m_delay->value()); 0276 job->setDelay(time_sec); 0277 } 0278 } 0279 0280 // Really quits the application 0281 void KTimerPref::exit() 0282 { 0283 done(0); 0284 qApp->quit(); 0285 } 0286 0287 void KTimerPref::done(int result) 0288 { 0289 saveAllJobs(); 0290 QDialog::done(result); 0291 } 0292 0293 void KTimerPref::saveJobs(KConfig *cfg) 0294 { 0295 const int nbList = m_list->topLevelItemCount(); 0296 for (int num = 0; num < nbList; ++num) { 0297 auto item = static_cast<KTimerJobItem *>(m_list->topLevelItem(num)); 0298 item->job()->save(cfg, QStringLiteral("Job%1").arg(num)); 0299 } 0300 0301 KConfigGroup jobscfg = cfg->group("Jobs"); 0302 jobscfg.writeEntry("Number", m_list->topLevelItemCount()); 0303 0304 jobscfg.sync(); 0305 } 0306 0307 void KTimerPref::loadJobs(KConfig *cfg) 0308 { 0309 const int num = cfg->group("Jobs").readEntry("Number", 0); 0310 for (int n = 0; n < num; n++) { 0311 auto job = new KTimerJob; 0312 auto item = new KTimerJobItem(job, m_list); 0313 0314 connect(job, &KTimerJob::delayChanged, this, &KTimerPref::jobChanged); 0315 connect(job, &KTimerJob::valueChanged, this, &KTimerPref::jobChanged); 0316 connect(job, &KTimerJob::stateChanged, this, &KTimerPref::jobChanged); 0317 connect(job, &KTimerJob::commandChanged, this, &KTimerPref::jobChanged); 0318 connect(job, &KTimerJob::finished, this, &KTimerPref::jobFinished); 0319 0320 job->load(cfg, QStringLiteral("Job%1").arg(n)); 0321 0322 job->setUser(item); 0323 jobChanged(job); 0324 } 0325 0326 m_list->update(); 0327 } 0328 0329 /*********************************************************************/ 0330 0331 struct KTimerJobPrivate { 0332 unsigned delay; 0333 QString command; 0334 bool loop; 0335 bool oneInstance; 0336 bool consecutive; 0337 unsigned value; 0338 KTimerJob::States state; 0339 QList<QProcess *> processes; 0340 void *user; 0341 0342 QTimer *timer; 0343 }; 0344 0345 KTimerJob::KTimerJob(QObject *parent) 0346 : QObject(parent) 0347 { 0348 d = new KTimerJobPrivate; 0349 0350 d->delay = 100; 0351 d->loop = false; 0352 d->oneInstance = true; 0353 d->consecutive = false; 0354 d->value = 100; 0355 d->state = Stopped; 0356 d->user = nullptr; 0357 0358 d->timer = new QTimer(this); 0359 connect(d->timer, &QTimer::timeout, this, &KTimerJob::timeout); 0360 } 0361 0362 KTimerJob::~KTimerJob() 0363 { 0364 delete d; 0365 } 0366 0367 void KTimerJob::save(KConfig *cfg, const QString &grp) 0368 { 0369 KConfigGroup groupcfg = cfg->group(grp); 0370 groupcfg.writeEntry("Delay", d->delay); 0371 groupcfg.writePathEntry("Command", d->command); 0372 groupcfg.writeEntry("Loop", d->loop); 0373 groupcfg.writeEntry("OneInstance", d->oneInstance); 0374 groupcfg.writeEntry("Consecutive", d->consecutive); 0375 groupcfg.writeEntry("State", (int)d->state); 0376 } 0377 0378 void KTimerJob::load(KConfig *cfg, const QString &grp) 0379 { 0380 KConfigGroup groupcfg = cfg->group(grp); 0381 setDelay(groupcfg.readEntry("Delay", 100)); 0382 setCommand(groupcfg.readPathEntry("Command", QString())); 0383 setLoop(groupcfg.readEntry("Loop", false)); 0384 setOneInstance(groupcfg.readEntry("OneInstance", d->oneInstance)); 0385 setConsecutive(groupcfg.readEntry("Consecutive", d->consecutive)); 0386 setState((States)groupcfg.readEntry("State", (int)Stopped)); 0387 } 0388 0389 // Format given seconds to hour:minute:seconds and return QString 0390 QString KTimerJob::formatTime(int seconds) const 0391 { 0392 int h, m, s; 0393 secondsToHMS(seconds, &h, &m, &s); 0394 return QStringLiteral("%1:%2:%3").arg(h).arg(m, 2, 10, QLatin1Char('0')).arg(s, 2, 10, QLatin1Char('0')); 0395 } 0396 0397 // calculate seconds from hour, minute and seconds, returns seconds 0398 int KTimerJob::timeToSeconds(int hours, int minutes, int seconds) const 0399 { 0400 return hours * 3600 + minutes * 60 + seconds; 0401 } 0402 0403 // calculates hours, minutes and seconds from given secs. 0404 void KTimerJob::secondsToHMS(int secs, int *hours, int *minutes, int *seconds) const 0405 { 0406 int s = secs; 0407 (*hours) = s / 3600; 0408 s = s % 3600; 0409 (*minutes) = s / 60; 0410 (*seconds) = s % 60; 0411 } 0412 0413 void *KTimerJob::user() 0414 { 0415 return d->user; 0416 } 0417 0418 void KTimerJob::setUser(void *user) 0419 { 0420 d->user = user; 0421 } 0422 0423 unsigned KTimerJob::delay() const 0424 { 0425 return d->delay; 0426 } 0427 0428 void KTimerJob::pause() 0429 { 0430 setState(Paused); 0431 } 0432 0433 void KTimerJob::stop() 0434 { 0435 setState(Stopped); 0436 } 0437 0438 void KTimerJob::start() 0439 { 0440 setState(Started); 0441 } 0442 0443 void KTimerJob::setDelay(int sec) 0444 { 0445 setDelay((unsigned)sec); 0446 } 0447 0448 void KTimerJob::setValue(int value) 0449 { 0450 setValue((unsigned)value); 0451 } 0452 0453 void KTimerJob::setDelay(unsigned int sec) 0454 { 0455 if (d->delay != sec) { 0456 d->delay = sec; 0457 0458 if (d->state == Stopped) 0459 setValue(sec); 0460 0461 Q_EMIT delayChanged(this, sec); 0462 Q_EMIT changed(this); 0463 } 0464 } 0465 0466 QString KTimerJob::command() const 0467 { 0468 return d->command; 0469 } 0470 0471 void KTimerJob::setCommand(const QString &cmd) 0472 { 0473 if (d->command != cmd) { 0474 d->command = cmd; 0475 Q_EMIT commandChanged(this, cmd); 0476 Q_EMIT changed(this); 0477 } 0478 } 0479 0480 bool KTimerJob::loop() const 0481 { 0482 return d->loop; 0483 } 0484 0485 void KTimerJob::setLoop(bool loop) 0486 { 0487 if (d->loop != loop) { 0488 d->loop = loop; 0489 Q_EMIT loopChanged(this, loop); 0490 Q_EMIT changed(this); 0491 } 0492 } 0493 0494 bool KTimerJob::oneInstance() const 0495 { 0496 return d->oneInstance; 0497 } 0498 0499 void KTimerJob::setOneInstance(bool one) 0500 { 0501 if (d->oneInstance != one) { 0502 d->oneInstance = one; 0503 Q_EMIT oneInstanceChanged(this, one); 0504 Q_EMIT changed(this); 0505 } 0506 } 0507 0508 bool KTimerJob::consecutive() const 0509 { 0510 return d->consecutive; 0511 } 0512 0513 void KTimerJob::setConsecutive(bool consecutive) 0514 { 0515 if (d->consecutive != consecutive) { 0516 d->consecutive = consecutive; 0517 Q_EMIT consecutiveChanged(this, consecutive); 0518 Q_EMIT changed(this); 0519 } 0520 } 0521 0522 unsigned KTimerJob::value() const 0523 { 0524 return d->value; 0525 } 0526 0527 void KTimerJob::setValue(unsigned int value) 0528 { 0529 if (d->value != value) { 0530 d->value = value; 0531 Q_EMIT valueChanged(this, value); 0532 Q_EMIT changed(this); 0533 } 0534 } 0535 0536 KTimerJob::States KTimerJob::state() const 0537 { 0538 return d->state; 0539 } 0540 0541 void KTimerJob::setState(KTimerJob::States state) 0542 { 0543 if (d->state != state) { 0544 if (state == Started) 0545 d->timer->start(1000); 0546 else 0547 d->timer->stop(); 0548 0549 if (state == Stopped) 0550 setValue(d->delay); 0551 0552 d->state = state; 0553 Q_EMIT stateChanged(this, state); 0554 Q_EMIT changed(this); 0555 } 0556 } 0557 0558 void KTimerJob::timeout() 0559 { 0560 if (d->state == Started && d->value != 0) { 0561 setValue(d->value - 1); 0562 if (d->value == 0) { 0563 fire(); 0564 if (d->loop) 0565 setValue(d->delay); 0566 else 0567 stop(); 0568 } 0569 } 0570 } 0571 0572 void KTimerJob::processExited(int, QProcess::ExitStatus status) 0573 { 0574 auto proc = static_cast<QProcess *>(sender()); 0575 const bool ok = status == 0; 0576 const int i = d->processes.indexOf(proc); 0577 if (i != -1) 0578 delete d->processes.takeAt(i); 0579 0580 if (!ok) 0581 Q_EMIT error(this); 0582 Q_EMIT finished(this, !ok); 0583 } 0584 0585 void KTimerJob::fire() 0586 { 0587 if (!d->oneInstance || d->processes.isEmpty()) { 0588 auto proc = new QProcess; 0589 d->processes.append(proc); 0590 connect(proc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KTimerJob::processExited); 0591 if (!d->command.simplified().isEmpty()) { 0592 QStringList splitArguments = QProcess::splitCommand(d->command); 0593 if (!splitArguments.isEmpty()) { 0594 const QString prog = splitArguments.takeFirst(); 0595 proc->start(prog, splitArguments); 0596 } 0597 Q_EMIT fired(this); 0598 } 0599 if (proc->state() == QProcess::NotRunning) { 0600 const int i = d->processes.indexOf(proc); 0601 if (i != -1) 0602 delete d->processes.takeAt(i); 0603 Q_EMIT error(this); 0604 Q_EMIT finished(this, true); 0605 } 0606 } 0607 }