File indexing completed on 2022-09-20 21:18:13

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