korganizer

koeditoralarms.cpp

00001 /*
00002     This file is part of KOrganizer.
00003 
00004     Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006 
00007     This program is free software; you can redistribute it and/or modify
00008     it under the terms of the GNU General Public License as published by
00009     the Free Software Foundation; either version 2 of the License, or
00010     (at your option) any later version.
00011 
00012     This program is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00015     GNU General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program; if not, write to the Free Software
00019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020 
00021     As a special exception, permission is given to link this program
00022     with any edition of Qt, and distribute the resulting executable,
00023     without including the source code for Qt in the source distribution.
00024 */
00025 
00026 #include "koeditoralarms_base.h"
00027 #include "koeditoralarms.h"
00028 #include "koprefs.h"
00029 
00030 #include <libkcal/duration.h>
00031 
00032 #include <tqlayout.h>
00033 #include <tqlistview.h>
00034 #include <tqpushbutton.h>
00035 #include <tqspinbox.h>
00036 #include <tqcombobox.h>
00037 #include <tqcheckbox.h>
00038 #include <tqbuttongroup.h>
00039 #include <tqtextedit.h>
00040 #include <tqwidgetstack.h>
00041 #include <tqradiobutton.h>
00042 #include <tqtooltip.h>
00043 #include <tqwhatsthis.h>
00044 
00045 #include <kurlrequester.h>
00046 #include <klocale.h>
00047 #include <kdebug.h>
00048 
00049 #include <libkcal/alarm.h>
00050 #include <libkcal/incidence.h>
00051 
00052 #include <libemailfunctions/email.h>
00053 
00054 class AlarmListViewItem : public QListViewItem
00055 {
00056   public:
00057     AlarmListViewItem( TQListView *parent, KCal::Alarm *alarm, const TQCString &inctype );
00058     virtual ~AlarmListViewItem();
00059     KCal::Alarm *alarm() const { return mAlarm; }
00060     void construct();
00061     enum AlarmViewColumns { ColAlarmType=0, ColAlarmOffset, ColAlarmRepeat };
00062 
00063   protected:
00064     KCal::Alarm *mAlarm;
00065 
00066   private:
00067     TQCString mIncType;
00068 };
00069 
00070 AlarmListViewItem::AlarmListViewItem( TQListView *parent, KCal::Alarm *alarm,
00071                                       const TQCString &inctype )
00072     : TQListViewItem( parent ), mIncType( inctype )
00073 {
00074   if ( alarm ) {
00075     mAlarm = new KCal::Alarm( *alarm );
00076   } else {
00077     mAlarm = new KCal::Alarm( 0 );
00078     mAlarm->setType( KCal::Alarm::Display );
00079     int duration; // in secs
00080     switch( KOPrefs::instance()->mReminderTimeUnits ) {
00081     default:
00082     case 0: // mins
00083       duration = KOPrefs::instance()->mReminderTime * 60;
00084       break;
00085     case 1: // hours
00086       duration = KOPrefs::instance()->mReminderTime * 60 * 60;
00087       break;
00088     case 2: // days
00089       duration = KOPrefs::instance()->mReminderTime * 60 * 60 * 24;
00090       break;
00091     }
00092     if ( mIncType == "Event" ) {
00093       mAlarm->setStartOffset( KCal::Duration( -duration ) );
00094     } else {
00095       mAlarm->setEndOffset( KCal::Duration( -duration ) );
00096     }
00097   }
00098   construct();
00099 }
00100 
00101 AlarmListViewItem::~AlarmListViewItem()
00102 {
00103   delete mAlarm;
00104 }
00105 
00106 void AlarmListViewItem::construct()
00107 {
00108   if ( mAlarm ) {
00109     // Alarm type:
00110     TQString type;
00111     switch ( mAlarm->type() ) {
00112       case KCal::Alarm::Display:
00113         type = i18n("Reminder Dialog");
00114         break;
00115       case KCal::Alarm::Procedure:
00116         type = i18n("Program");
00117         break;
00118       case KCal::Alarm::Email:
00119         type = i18n("Email");
00120         break;
00121       case KCal::Alarm::Audio:
00122         type = i18n("Audio");
00123         break;
00124       default:
00125         type = i18n("Unknown");
00126         break;
00127     }
00128     setText( ColAlarmType, type );
00129 
00130     // Alarm offset:
00131     TQString offsetstr;
00132     int offset = 0;
00133     if ( mAlarm->hasStartOffset() ) {
00134       offset = mAlarm->startOffset().asSeconds();
00135       if ( offset <= 0 ) {
00136         offsetstr = i18n( "N days/hours/minutes before/after the start/end",
00137                           "%1 before the start" );
00138         offset = -offset;
00139       } else {
00140         offsetstr = i18n( "N days/hours/minutes before/after the start/end",
00141                           "%1 after the start" );
00142       }
00143     } else if ( mAlarm->hasEndOffset() ) {
00144       offset = mAlarm->endOffset().asSeconds();
00145       if ( offset <= 0 ) {
00146         if ( mIncType == "Todo" ) {
00147           offsetstr = i18n( "N days/hours/minutes before/after the due date",
00148                             "%1 before the to-do is due" );
00149         } else {
00150           offsetstr = i18n( "N days/hours/minutes before/after the start/end",
00151                             "%1 before the end" );
00152         }
00153         offset = -offset;
00154       } else {
00155         if ( mIncType == "Todo" ) {
00156           offsetstr = i18n( "N days/hours/minutes before/after the due date",
00157                             "%1 after the to-do is due" );
00158         } else {
00159           offsetstr = i18n( "N days/hours/minutes before/after the start/end",
00160                             "%1 after the end" );
00161         }
00162       }
00163     }
00164 
00165     offset = offset / 60; // make minutes
00166     int useoffset = offset;
00167 
00168     if ( offset % (24*60) == 0 && offset>0 ) { // divides evenly into days?
00169       useoffset = offset / (24*60);
00170       offsetstr = offsetstr.arg( i18n("1 day", "%n days", useoffset ) );
00171     } else if (offset % 60 == 0 && offset>0 ) { // divides evenly into hours?
00172       useoffset = offset / 60;
00173       offsetstr = offsetstr.arg( i18n("1 hour", "%n hours", useoffset ) );
00174     } else {
00175       useoffset = offset;
00176       offsetstr = offsetstr.arg( i18n("1 minute", "%n minutes", useoffset ) );
00177     }
00178     setText( ColAlarmOffset, offsetstr );
00179 
00180     // Alarm repeat
00181     if ( mAlarm->repeatCount()>0 ) {
00182       setText( ColAlarmRepeat, i18n("Yes") );
00183     } else {
00184       setText( ColAlarmRepeat, i18n("No") );
00185     }
00186   }
00187 }
00188 
00189 
00190 KOEditorAlarms::KOEditorAlarms( const TQCString &type,
00191                                 KCal::Alarm::List *alarms, TQWidget *parent,
00192                                 const char *name )
00193   : KDialogBase( parent, name, true, i18n("Advanced Reminders"), Ok | Cancel ),
00194     mType( type ), mAlarms( alarms ),mCurrentItem( 0 )
00195 {
00196   if ( mType != "Todo" ) {
00197     // only Todos and Events can have reminders
00198     mType = "Event";
00199   }
00200   setMainWidget( mWidget = new KOEditorAlarms_base( this ) );
00201 
00202   // The text is set here, and not in the UI file, because the i18n context is not
00203   // properly extracted from the UI file.
00204   mWidget->mAddButton->setText( i18n( "Add a new alarm to the alarm list.", "&Add" ) );
00205 
00206   mWidget->mAlarmList->setResizeMode( TQListView::LastColumn );
00207   mWidget->mAlarmList->setColumnWidthMode( 0, TQListView::Maximum );
00208   mWidget->mAlarmList->setColumnWidthMode( 1, TQListView::Maximum );
00209   connect( mWidget->mAlarmList, TQT_SIGNAL( selectionChanged( TQListViewItem * ) ),
00210            TQT_SLOT( selectionChanged( TQListViewItem * ) ) );
00211   connect( mWidget->mAddButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotAdd() ) );
00212   connect( mWidget->mRemoveButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotRemove() ) );
00213   connect( mWidget->mDuplicateButton, TQT_SIGNAL( clicked() ), TQT_SLOT( slotDuplicate() ) );
00214 
00215   connect( mWidget->mAlarmOffset, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( changed() ) );
00216   connect( mWidget->mOffsetUnit, TQT_SIGNAL( activated( int ) ), TQT_SLOT( changed() ) );
00217   connect( mWidget->mBeforeAfter, TQT_SIGNAL( activated( int ) ), TQT_SLOT( changed() ) );
00218   connect( mWidget->mRepeats, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( changed() ) );
00219   connect( mWidget->mRepeatCount, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( changed() ) );
00220   connect( mWidget->mRepeatInterval, TQT_SIGNAL( valueChanged( int ) ), TQT_SLOT( changed() ) );
00221   connect( mWidget->mAlarmType, TQT_SIGNAL(clicked(int)), TQT_SLOT( changed() ) );
00222   connect( mWidget->mDisplayText, TQT_SIGNAL( textChanged() ), TQT_SLOT( changed() ) );
00223   connect( mWidget->mSoundFile, TQT_SIGNAL( textChanged( const TQString & ) ), TQT_SLOT( changed() ) );
00224   connect( mWidget->mApplication, TQT_SIGNAL( textChanged( const TQString & ) ), TQT_SLOT( changed() ) );
00225   connect( mWidget->mAppArguments, TQT_SIGNAL( textChanged( const TQString & ) ), TQT_SLOT( changed() ) );
00226   connect( mWidget->mEmailAddress, TQT_SIGNAL( textChanged( const TQString & ) ), TQT_SLOT( changed() ) );
00227   connect( mWidget->mEmailText, TQT_SIGNAL( textChanged() ), TQT_SLOT( changed() ) );
00228 
00229   init();
00230 
00231   //TODO: backport email reminders from trunk
00232   mWidget->mTypeEmailRadio->hide(); //email reminders not implemented yet
00233 
00234   mWidget->setMinimumSize( 500, 500 );
00235 }
00236 
00237 KOEditorAlarms::~KOEditorAlarms()
00238 {
00239 }
00240 
00241 void KOEditorAlarms::changed()
00242 {
00243   if ( !mInitializing && mCurrentItem ) {
00244     KCal::Alarm *alarm = mCurrentItem->alarm();
00245 
00246     // Based on settings, provide default sound file for audio alarms
00247     if ( alarm->audioFile().isEmpty() &&
00248          KOPrefs::instance()->defaultAudioFileReminders() ) {
00249       alarm->setAudioFile( KOPrefs::instance()->audioFilePath() );
00250       mWidget->mSoundFile->setURL( KOPrefs::instance()->audioFilePath() );
00251     }
00252 
00253     writeAlarm( alarm );
00254     mCurrentItem->construct();
00255   }
00256 }
00257 
00258 void KOEditorAlarms::readAlarm( KCal::Alarm *alarm )
00259 {
00260   if ( !alarm ) return;
00261 
00262   mInitializing = true;
00263 
00264   // Offsets
00265   int offset;
00266   int beforeafterpos = 0;
00267   if ( mType == "Todo" ) {
00268     if ( !alarm->hasStartOffset() ) {
00269       beforeafterpos = 2;
00270     }
00271   }
00272   if ( alarm->hasEndOffset() ) {
00273     beforeafterpos = 2;
00274     offset = alarm->endOffset().asSeconds();
00275   } else {
00276     // TODO: Also allow alarms at fixed times, not relative to start/end
00277     offset = alarm->startOffset().asSeconds();
00278   }
00279   // Negative offset means before the start/end...
00280   if ( offset <= 0 ) {
00281     offset = -offset;
00282   } else {
00283     ++beforeafterpos;
00284   }
00285   mWidget->mBeforeAfter->setCurrentItem( beforeafterpos );
00286 
00287   offset = offset / 60; // make minutes
00288   int useoffset = offset;
00289 
00290   if ( offset % (24*60) == 0 && offset>0 ) { // divides evenly into days?
00291     useoffset = offset / (24*60);
00292     mWidget->mOffsetUnit->setCurrentItem( 2 );
00293   } else if (offset % 60 == 0 && offset>0 ) { // divides evenly into hours?
00294     useoffset = offset / 60;
00295     mWidget->mOffsetUnit->setCurrentItem( 1 );
00296   } else {
00297     useoffset = offset;
00298     mWidget->mOffsetUnit->setCurrentItem( 0 );
00299   }
00300   mWidget->mAlarmOffset->setValue( useoffset );
00301 
00302 
00303   // Repeating
00304   mWidget->mRepeats->setChecked( alarm->repeatCount()>0 );
00305   if ( alarm->repeatCount()>0 ) {
00306     mWidget->mRepeatCount->setValue( alarm->repeatCount() );
00307     mWidget->mRepeatInterval->setValue( alarm->snoozeTime().asSeconds() / 60 ); // show as minutes
00308   }
00309 
00310   switch ( alarm->type() ) {
00311     case KCal::Alarm::Audio:
00312         mWidget->mAlarmType->setButton( 1 );
00313         mWidget->mSoundFile->setURL( alarm->audioFile() );
00314         break;
00315     case KCal::Alarm::Procedure:
00316         mWidget->mAlarmType->setButton( 2 );
00317         mWidget->mApplication->setURL( alarm->programFile() );
00318         mWidget->mAppArguments->setText( alarm->programArguments() );
00319         break;
00320     case KCal::Alarm::Email: {
00321         mWidget->mAlarmType->setButton( 3 );
00322         TQValueList<KCal::Person> addresses = alarm->mailAddresses();
00323         TQStringList add;
00324         for ( TQValueList<KCal::Person>::ConstIterator it = addresses.begin();
00325               it != addresses.end(); ++it ) {
00326           add << (*it).fullName();
00327         }
00328         mWidget->mEmailAddress->setText( add.join(", ") );
00329         mWidget->mEmailText->setText( alarm->mailText() );
00330         break;}
00331     case KCal::Alarm::Display:
00332     case KCal::Alarm::Invalid:
00333     default:
00334         mWidget->mAlarmType->setButton( 0 );
00335         mWidget->mDisplayText->setText( alarm->text() );
00336         break;
00337   }
00338 
00339   mWidget->mTypeStack->raiseWidget( mWidget->mAlarmType->selectedId() );
00340 
00341   mInitializing = false;
00342 }
00343 
00344 void KOEditorAlarms::writeAlarm( KCal::Alarm *alarm )
00345 {
00346   // Offsets
00347   int offset = mWidget->mAlarmOffset->value()*60; // minutes
00348   int offsetunit = mWidget->mOffsetUnit->currentItem();
00349   if ( offsetunit >= 1 ) offset *= 60; // hours
00350   if ( offsetunit >= 2 ) offset *= 24; // days
00351   if ( offsetunit >= 3 ) offset *= 7; // weeks
00352 
00353   int beforeafterpos = mWidget->mBeforeAfter->currentItem();
00354   if ( beforeafterpos % 2 == 0 ) { // before -> negative
00355     offset = -offset;
00356   }
00357 
00358   // TODO: Add possibility to specify a given time for the reminder
00359   if ( beforeafterpos / 2 == 0 ) { // start offset
00360     alarm->setStartOffset( KCal::Duration( offset ) );
00361   } else {
00362     alarm->setEndOffset( KCal::Duration( offset ) );
00363   }
00364 
00365   // Repeating
00366   if ( mWidget->mRepeats->isChecked() ) {
00367     alarm->setRepeatCount( mWidget->mRepeatCount->value() );
00368     alarm->setSnoozeTime( KCal::Duration( mWidget->mRepeatInterval->value() * 60 ) ); // convert back to seconds
00369   } else {
00370     alarm->setRepeatCount( 0 );
00371   }
00372 
00373   switch ( mWidget->mAlarmType->selectedId() ) {
00374     case 1: // Audio
00375         alarm->setAudioAlarm( mWidget->mSoundFile->url() );
00376         break;
00377     case 2: // Procedure
00378         alarm->setProcedureAlarm( mWidget->mApplication->url(), mWidget->mAppArguments->text() );
00379         break;
00380     case 3: { // Email
00381         TQStringList addresses = KPIM::splitEmailAddrList( mWidget->mEmailAddress->text() );
00382         TQValueList<KCal::Person> add;
00383         for ( TQStringList::Iterator it = addresses.begin(); it != addresses.end();
00384               ++it ) {
00385           add << KCal::Person( *it );
00386         }
00387         // TODO: Add a subject line and possibilities for attachments
00388         alarm->setEmailAlarm( TQString::null, mWidget->mEmailText->text(),
00389                               add );
00390         break; }
00391     case 0: // Display
00392     default:
00393         alarm->setDisplayAlarm( mWidget->mDisplayText->text() );
00394         break;
00395   }
00396 }
00397 
00398 void KOEditorAlarms::selectionChanged( TQListViewItem *listviewitem )
00399 {
00400   AlarmListViewItem *item = dynamic_cast<AlarmListViewItem*>(listviewitem);
00401   mCurrentItem = item;
00402   mWidget->mTimeGroup->setEnabled( item );
00403   mWidget->mTypeGroup->setEnabled( item );
00404   if ( item ) {
00405     readAlarm( item->alarm() );
00406   }
00407 }
00408 
00409 void KOEditorAlarms::slotOk()
00410 {
00411   // save the current item settings, if any
00412   changed();
00413 
00414   // copy the mAlarms list
00415   if ( mAlarms ) {
00416     mAlarms->clear();
00417     TQListViewItemIterator it( mWidget->mAlarmList );
00418     while ( it.current() ) {
00419       AlarmListViewItem *item = dynamic_cast<AlarmListViewItem*>(*it);
00420       if ( item ) {
00421         mAlarms->append( new KCal::Alarm( *(item->alarm()) ) );
00422       }
00423       ++it;
00424     }
00425   }
00426   accept();
00427 }
00428 
00429 void KOEditorAlarms::slotAdd()
00430 {
00431   mCurrentItem = new AlarmListViewItem( mWidget->mAlarmList, 0, mType );
00432   mWidget->mAlarmList->setCurrentItem( mCurrentItem );
00433 }
00434 
00435 void KOEditorAlarms::slotDuplicate()
00436 {
00437   if ( mCurrentItem ) {
00438     mCurrentItem = new AlarmListViewItem( mWidget->mAlarmList, mCurrentItem->alarm(), mType );
00439     mWidget->mAlarmList->setCurrentItem( mCurrentItem );
00440   }
00441 }
00442 
00443 void KOEditorAlarms::slotRemove()
00444 {
00445   if ( mCurrentItem ) {
00446     delete mCurrentItem;
00447     mCurrentItem = dynamic_cast<AlarmListViewItem*>( mWidget->mAlarmList->currentItem() );
00448     mWidget->mAlarmList->setSelected( mCurrentItem, true );
00449   }
00450 }
00451 
00452 void KOEditorAlarms::init()
00453 {
00454   mInitializing = true;
00455 
00456   // Tweak some UI stuff depending on the Incidence type
00457   if ( mType == "Todo" ) {
00458     // Replace before/after end datetime with before/after due datetime
00459     mWidget->mBeforeAfter->clear();
00460     mWidget->mBeforeAfter->insertItem( i18n( "before the to-do starts" ), 0 );
00461     mWidget->mBeforeAfter->insertItem( i18n( "after the to-do starts" ), 1 );
00462     mWidget->mBeforeAfter->insertItem( i18n( "before the to-do is due" ), 2 );
00463     mWidget->mBeforeAfter->insertItem( i18n( "after the to-do is due" ), 3 );
00464     TQToolTip::add(
00465       mWidget->mBeforeAfter,
00466       i18n( "Select the reminder trigger relative to the start or due time" ) );
00467     TQWhatsThis::add(
00468       mWidget->mBeforeAfter,
00469       i18n( "Use this combobox to specify if you want the reminder to "
00470             "trigger before or after the start or due time." ) );
00471 
00472     mWidget->mBeforeAfter->setCurrentItem( 2 );  // default is before due start
00473   }
00474 
00475   // Fill-in existing alarms
00476   KCal::Alarm::List::ConstIterator it;
00477   for ( it = mAlarms->begin(); it != mAlarms->end(); ++it ) {
00478     new AlarmListViewItem( mWidget->mAlarmList, *it, mType );
00479   }
00480   mWidget->mAlarmList->setSelected( mWidget->mAlarmList->firstChild(), true );
00481   mInitializing = false;
00482 }
00483 
00484 #include "koeditoralarms.moc"