File indexing completed on 2022-12-06 15:38:25

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 *exit = KStandardAction::quit(this, SLOT(exit()), this);
0129     addAction(exit);
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 }