/* Copyright (c) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidencerecurrence.h" #include "incidencedatetime.h" #ifdef KDEPIM_MOBILE_UI #include "ui_dialogmoremobile.h" #else #include "ui_dialogdesktop.h" #endif #include #include using namespace IncidenceEditorNG; enum { // Keep in sync with mRecurrenceEndCombo RecurrenceEndNever = 0, RecurrenceEndOn, RecurrenceEndAfter }; /** Description of available recurrence types: 0 - None 1 - 2 - 3 - rDaily 4 - rWeekly 5 - rMonthlyPos - 3rd Saturday of month, last Wednesday of month... 6 - rMonthlyDay - 17th day of month 7 - rYearlyMonth - 10th of July 8 - rYearlyDay - on the 117th day of the year 9 - rYearlyPos - 1st Wednesday of July */ enum { // Indexes of the month combo, keep in sync with descriptions. ComboIndexMonthlyDay = 0, // 11th of June ComboIndexMonthlyDayInverted, // 20th of June ( 11 to end ) ComboIndexMonthlyPos, // 1st Monday of the Month ComboIndexMonthlyPosInverted // Last Monday of the Month }; enum { // Indexes of the year combo, keep in sync with descriptions. ComboIndexYearlyMonth = 0, ComboIndexYearlyMonthInverted, ComboIndexYearlyPos, ComboIndexYearlyPosInverted, ComboIndexYearlyDay }; #ifdef KDEPIM_MOBILE_UI IncidenceRecurrence::IncidenceRecurrence( IncidenceDateTime *dateTime, Ui::EventOrTodoMore *ui ) #else IncidenceRecurrence::IncidenceRecurrence( IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui ) #endif : mUi( ui ), mDateTime( dateTime ), mMonthlyInitialType( 0 ), mYearlyInitialType( 0 ) { setObjectName( "IncidenceRecurrence" ); // Set some sane defaults mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeNone ); mUi->mRecurrenceEndCombo->setCurrentIndex( RecurrenceEndNever ); mUi->mRecurrenceEndStack->setCurrentIndex( 0 ); mUi->mRepeatStack->setCurrentIndex( 0 ); mUi->mEndDurationEdit->setValue( 1 ); handleEndAfterOccurrencesChange( 1 ); toggleRecurrenceWidgets( RecurrenceTypeNone ); fillCombos(); #ifndef KDEPIM_MOBILE_UI QList lineEdits; lineEdits << mUi->mExceptionDateEdit->lineEdit() << mUi->mRecurrenceEndDate->lineEdit(); foreach( QLineEdit *lineEdit, lineEdits ) { KLineEdit *klineEdit = qobject_cast( lineEdit ); if ( klineEdit ) klineEdit->setClearButtonShown( false ); } #endif connect( mDateTime, SIGNAL(startDateTimeToggled(bool)), SLOT(handleDateTimeToggle()) ); connect( mDateTime, SIGNAL(startDateChanged(QDate)), SLOT(handleStartDateChange(QDate)) ); connect( mUi->mExceptionAddButton, SIGNAL(clicked()), SLOT(addException())); connect( mUi->mExceptionRemoveButton, SIGNAL(clicked()), SLOT(removeExceptions()) ); connect( mUi->mExceptionDateEdit, SIGNAL(dateChanged(QDate)), SLOT(handleExceptionDateChange(QDate)) ); connect( mUi->mExceptionList, SIGNAL(itemSelectionChanged()), SLOT(updateRemoveExceptionButton()) ); connect( mUi->mRecurrenceTypeCombo, SIGNAL(currentIndexChanged(int)), SLOT(handleRecurrenceTypeChange(int))); connect( mUi->mEndDurationEdit, SIGNAL(valueChanged(int)), SLOT(handleEndAfterOccurrencesChange(int)) ); connect( mUi->mFrequencyEdit, SIGNAL(valueChanged(int)), SLOT(handleFrequencyChange()) ); // Check the dirty status when the user changes values. connect( mUi->mRecurrenceTypeCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mFrequencyEdit, SIGNAL(valueChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mFrequencyEdit, SIGNAL(valueChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mWeekDayCombo, SIGNAL(checkedItemsChanged(QStringList)), SLOT(checkDirtyStatus()) ); connect( mUi->mMonthlyCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mYearlyCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mRecurrenceEndCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mEndDurationEdit, SIGNAL(valueChanged(int)), SLOT(checkDirtyStatus()) ); connect( mUi->mRecurrenceEndDate, SIGNAL(dateChanged(QDate)), SLOT(checkDirtyStatus()) ); connect( mUi->mThisAndFutureCheck, SIGNAL(stateChanged(int)), SLOT(checkDirtyStatus()) ); } // this method must be at the top of this file in order to ensure // that its message to translators appears before any usages of this method. KLocalizedString IncidenceRecurrence::subsOrdinal ( const KLocalizedString &text, int number ) const { QString q = i18nc( "In several of the messages below, " "an ordinal number is substituted into the message. " "Translate this as \"0\" if English ordinal suffixes " "should be added (1st, 22nd, 123rd); " "translate this as \"1\" if just the number itself " "should be substituted (1, 22, 123).", "0" ); if ( q == "0" ) { QString ordinal; ordinal = numberToString( number ); return text.subs( ordinal ); } else { return text.subs( number ); } } void IncidenceRecurrence::load( const KCalCore::Incidence::Ptr &incidence ) { Q_ASSERT( incidence ); mLoadedIncidence = incidence; // We must be sure that the date/time in mDateTime is the correct date time. // So don't depend on CombinedIncidenceEditor or whatever external factor to // load the date/time before loading the recurrence mCurrentDate = mLoadedIncidence->dateTime( KCalCore::IncidenceBase::RoleRecurrenceStart ).date(); mDateTime->load( incidence ); mDateTime->endDate(); fillCombos(); setDefaults(); //This is an exception if ( mLoadedIncidence->hasRecurrenceId() ) { handleRecurrenceTypeChange( RecurrenceTypeException ); mUi->mThisAndFutureCheck->setChecked( mLoadedIncidence->thisAndFuture() ); mWasDirty = false; return; } int f = 0; KCalCore::Recurrence *r = 0; if ( mLoadedIncidence->recurrenceType() != KCalCore::Recurrence::rNone ) { r = mLoadedIncidence->recurrence(); f = r->frequency(); } switch ( mLoadedIncidence->recurrenceType() ) { case KCalCore::Recurrence::rNone: mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeNone ); handleRecurrenceTypeChange( RecurrenceTypeNone ); break; case KCalCore::Recurrence::rDaily: mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeDaily ); handleRecurrenceTypeChange( RecurrenceTypeDaily ); setFrequency( f ); break; case KCalCore::Recurrence::rWeekly: { mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeWeekly ); handleRecurrenceTypeChange( RecurrenceTypeWeekly ); QBitArray disableDays( 7/*size*/, 0/*default value*/ ); // dayOfWeek returns between 1 and 7 disableDays.setBit( currentDate().dayOfWeek()-1, 1 ); mUi->mWeekDayCombo->setDays( r->days(), disableDays ); setFrequency( f ); break; } case KCalCore::Recurrence::rMonthlyPos: // Fall through case KCalCore::Recurrence::rMonthlyDay: { mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeMonthly ); handleRecurrenceTypeChange( RecurrenceTypeMonthly ); selectMonthlyItem( r, mLoadedIncidence->recurrenceType() ); setFrequency( f ); break; } case KCalCore::Recurrence::rYearlyMonth: // Fall through case KCalCore::Recurrence::rYearlyPos: // Fall through case KCalCore::Recurrence::rYearlyDay: { mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeYearly ); handleRecurrenceTypeChange( RecurrenceTypeYearly ); selectYearlyItem( r, mLoadedIncidence->recurrenceType() ); setFrequency( f ); break; } default: break; } if ( mLoadedIncidence->recurs() && r ) { setDuration( r->duration() ); if ( r->duration() == 0 ) { mUi->mRecurrenceEndDate->setDate( r->endDate() ); } } setExceptionDates( mLoadedIncidence->recurrence()->exDates() ); handleDateTimeToggle(); mWasDirty = false; } void IncidenceRecurrence::writeToIncidence( const KCalCore::Incidence::Ptr &incidence ) const { // clear out any old settings; KCalCore::Recurrence *r = incidence->recurrence(); r->unsetRecurs(); // Why not clear() ? const RecurrenceType recurrenceType = currentRecurrenceType(); if ( recurrenceType == RecurrenceTypeException ) { incidence->setThisAndFuture( mUi->mThisAndFutureCheck->isChecked() ); return; } if ( recurrenceType == RecurrenceTypeNone || !mUi->mRecurrenceTypeCombo->isEnabled() ) { return; } const int lDuration = duration(); QDate endDate; if ( lDuration == 0 ) { endDate = mUi->mRecurrenceEndDate->date(); } if ( recurrenceType == RecurrenceTypeDaily ) { r->setDaily( mUi->mFrequencyEdit->value() ); } else if ( recurrenceType == RecurrenceTypeWeekly ) { r->setWeekly( mUi->mFrequencyEdit->value(), mUi->mWeekDayCombo->days() ); } else if ( recurrenceType == RecurrenceTypeMonthly ) { r->setMonthly( mUi->mFrequencyEdit->value() ); if ( mUi->mMonthlyCombo->currentIndex() == ComboIndexMonthlyDay ) { // Every nth r->addMonthlyDate( dayOfMonthFromStart() ); } else if ( mUi->mMonthlyCombo->currentIndex() == ComboIndexMonthlyDayInverted ) { // Every (last - n)th last day r->addMonthlyDate( -dayOfMonthFromEnd() ); } else if ( mUi->mMonthlyCombo->currentIndex() == ComboIndexMonthlyPos ) { // Every ith weekday r->addMonthlyPos( monthWeekFromStart(), weekday() ); } else { // Every (last - i)th last weekday r->addMonthlyPos( -monthWeekFromEnd(), weekday() ); } } else if ( recurrenceType == RecurrenceTypeYearly ) { r->setYearly( mUi->mFrequencyEdit->value() ); if ( mUi->mYearlyCombo->currentIndex() == ComboIndexYearlyMonth ) { //Every nth of month r->addYearlyDate( dayOfMonthFromStart() ); r->addYearlyMonth( currentDate().month() ); } else if ( mUi->mYearlyCombo->currentIndex() == ComboIndexYearlyMonthInverted ) { //Every (last - n)th last day of month r->addYearlyDate( -dayOfMonthFromEnd() ); r->addYearlyMonth( currentDate().month() ); } else if ( mUi->mYearlyCombo->currentIndex() == ComboIndexYearlyPos ) { //Every ith weekday of month r->addYearlyMonth( currentDate().month() ); r->addYearlyPos( monthWeekFromStart(), weekday() ); } else if ( mUi->mYearlyCombo->currentIndex() == ComboIndexYearlyPosInverted ) { //Every (last - i)th last weekday of month r->addYearlyMonth( currentDate().month() ); r->addYearlyPos( -monthWeekFromEnd(), weekday() ); } else { // The lth day of the year (l : 1 - 356) r->addYearlyDay( dayOfYearFromStart() ); } } r->setDuration( lDuration ); if ( lDuration == 0 ) { r->setEndDate( endDate ); } r->setExDates( mExceptionDates ); } void IncidenceRecurrence::save( const KCalCore::Incidence::Ptr &incidence ) { writeToIncidence( incidence ); mMonthlyInitialType = mUi->mMonthlyCombo->currentIndex(); mYearlyInitialType = mUi->mYearlyCombo->currentIndex(); } bool IncidenceRecurrence::isDirty() const { const RecurrenceType recurrenceType = currentRecurrenceType(); if ( mLoadedIncidence->recurs() && recurrenceType == RecurrenceTypeNone ) { return true; } if ( recurrenceType == RecurrenceTypeException ) { return ( mLoadedIncidence->thisAndFuture() != mUi->mThisAndFutureCheck->isChecked() ); } if ( !mLoadedIncidence->recurs() && recurrenceType != IncidenceEditorNG::RecurrenceTypeNone ) { return true; } // The incidence is not recurring and that hasn't changed, so don't check the // other values. if ( recurrenceType == RecurrenceTypeNone ) { return false; } const KCalCore::Recurrence *recurrence = mLoadedIncidence->recurrence(); switch ( recurrence->recurrenceType() ) { case KCalCore::Recurrence::rDaily: if ( recurrenceType != RecurrenceTypeDaily || mUi->mFrequencyEdit->value() != recurrence->frequency() ) { return true; } break; case KCalCore::Recurrence::rWeekly: if ( recurrenceType != RecurrenceTypeWeekly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mWeekDayCombo->days() != recurrence->days() ) { return true; } break; case KCalCore::Recurrence::rMonthlyDay: if ( recurrenceType != RecurrenceTypeMonthly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mMonthlyCombo->currentIndex() != mMonthlyInitialType ) { return true; } break; case KCalCore::Recurrence::rMonthlyPos: if ( recurrenceType != RecurrenceTypeMonthly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mMonthlyCombo->currentIndex() != mMonthlyInitialType ) { return true; } break; case KCalCore::Recurrence::rYearlyDay: if ( recurrenceType != RecurrenceTypeYearly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mYearlyCombo->currentIndex() != mYearlyInitialType ) { return true; } break; case KCalCore::Recurrence::rYearlyMonth: if ( recurrenceType != RecurrenceTypeYearly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mYearlyCombo->currentIndex() != mYearlyInitialType ) { return true; } break; case KCalCore::Recurrence::rYearlyPos: if ( recurrenceType != RecurrenceTypeYearly || mUi->mFrequencyEdit->value() != recurrence->frequency() || mUi->mYearlyCombo->currentIndex() != mYearlyInitialType ) { return true; } break; } // Recurrence end // -1 means "recurs forever" if ( recurrence->duration() == -1 && mUi->mRecurrenceEndCombo->currentIndex() != RecurrenceEndNever ) { return true; } else if ( recurrence->duration() == 0 ) { // 0 means "end date is set" if ( mUi->mRecurrenceEndCombo->currentIndex() != RecurrenceEndOn || recurrence->endDate() != mUi->mRecurrenceEndDate->date() ) { return true; } } else if ( recurrence->duration() > 0 ) { if ( mUi->mEndDurationEdit->value() != recurrence->duration() || mUi->mRecurrenceEndCombo->currentIndex() != RecurrenceEndAfter ) { return true; } } // Exceptions if ( mExceptionDates != recurrence->exDates() ) { return true; } return false; } bool IncidenceRecurrence::isValid() const { mLastErrorString.clear(); if ( currentRecurrenceType() == IncidenceEditorNG::RecurrenceTypeException ) { //Nothing you can do wrong here return true; } KCalCore::Incidence::Ptr incidence( mLoadedIncidence->clone() ); // Write start and end dates to the incidence mDateTime->save( incidence ); // Write new recurring parameters to incidence writeToIncidence( incidence ); // Check if the incidence will occur at least once if ( incidence->recurs() ) { // dtStart for events, dtDue for to-dos const KDateTime referenceDate = incidence->dateTime( KCalCore::Incidence::RoleRecurrenceStart ); if ( referenceDate.isValid() ) { if ( !( incidence->recurrence()->recursOn( referenceDate.date(), referenceDate.timeSpec() ) || incidence->recurrence()->getNextDateTime( referenceDate ).isValid() ) ) { mLastErrorString = i18n( "A recurring event or to-do must occur at least once. " "Adjust the recurring parameters." ); kDebug() << mLastErrorString; return false; } } else { mLastErrorString = i18n( "The incidence's start date is invalid." ); kDebug() << mLastErrorString; return false; } if ( mUi->mRecurrenceEndCombo->currentIndex() == RecurrenceEndOn && !mUi->mRecurrenceEndDate->date().isValid() ) { qWarning() << "Recurrence end date is invalid."; // TODO: strings after freeze return false; } } return true; } void IncidenceRecurrence::addException() { const QDate date = mUi->mExceptionDateEdit->date(); if ( !date.isValid() ) { qWarning() << "Refusing to add invalid date"; return; } const QString dateStr = KGlobal::locale()->formatDate( date ); if( mUi->mExceptionList->findItems( dateStr, Qt::MatchExactly ).isEmpty() ) { mExceptionDates.append( date ); mUi->mExceptionList->addItem( dateStr ); } mUi->mExceptionAddButton->setEnabled( false ); checkDirtyStatus(); } void IncidenceRecurrence::fillCombos() { if ( !currentDate().isValid() ) { // Can happen if you're editing with keyboard return; } const KCalendarSystem *calSys = KGlobal::locale()->calendar(); // Next the monthly combo. This contains the following elements: // - nth day of the month // - (month.lastDay() - n)th day of the month // - the ith ${weekday} of the month // - the (month.weekCount() - i)th day of the month const int currentMonthlyIndex = mUi->mMonthlyCombo->currentIndex(); mUi->mMonthlyCombo->clear(); const QDate date = mDateTime->startDate(); QString item = subsOrdinal( ki18nc( "example: the 30th", "the %1" ), dayOfMonthFromStart() ).toString(); mUi->mMonthlyCombo->addItem( item ); item = subsOrdinal( ki18nc( "example: the 4th to last day", "the %1 to last day" ), dayOfMonthFromEnd() ).toString(); mUi->mMonthlyCombo->addItem( item ); item = subsOrdinal( ki18nc( "example: the 5th Wednesday", "the %1 %2" ), monthWeekFromStart() ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ).toString(); mUi->mMonthlyCombo->addItem( item ); if ( monthWeekFromEnd() == 1 ) { item = ki18nc( "example: the last Wednesday", "the last %1" ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ).toString(); } else { item = subsOrdinal( ki18nc( "example: the 5th to last Wednesday", "the %1 to last %2" ), monthWeekFromEnd() ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ).toString(); } mUi->mMonthlyCombo->addItem( item ); mUi->mMonthlyCombo->setCurrentIndex( currentMonthlyIndex == -1 ? 0 : currentMonthlyIndex ); // Finally the yearly combo. This contains the following options: // - ${n}th of ${long-month-name} // - ${month.lastDay() - n}th last day of ${long-month-name} // - the ${i}th ${weekday} of ${long-month-name} // - the ${month.weekCount() - i}th day of ${long-month-name} // - the ${m}th day of the year const int currentYearlyIndex = mUi->mYearlyCombo->currentIndex(); mUi->mYearlyCombo->clear(); const QString longMonthName = calSys->monthName( date ); item = subsOrdinal( ki18nc( "example: the 5th of June", "the %1 of %2" ), date.day() ). subs( longMonthName ).toString(); mUi->mYearlyCombo->addItem( item ); item = subsOrdinal( ki18nc( "example: the 3rd to last day of June", "the %1 to last day of %2" ), date.daysInMonth() - date.day() ).subs( longMonthName ).toString(); mUi->mYearlyCombo->addItem( item ); item = subsOrdinal( ki18nc( "example: the 4th Wednesday of June", "the %1 %2 of %3" ), monthWeekFromStart() ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ). subs( longMonthName ).toString(); mUi->mYearlyCombo->addItem( item ); if ( monthWeekFromEnd() == 1 ) { item = ki18nc( "example: the last Wednesday of June", "the last %1 of %2" ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ). subs( longMonthName ).toString(); } else { item = subsOrdinal( ki18nc( "example: the 4th to last Wednesday of June", "the %1 to last %2 of %3 " ), monthWeekFromEnd() ). subs( calSys->weekDayName( date.dayOfWeek(), KCalendarSystem::LongDayName ) ). subs( longMonthName ).toString(); } mUi->mYearlyCombo->addItem( item ); item = subsOrdinal( ki18nc( "example: the 15th day of the year", "the %1 day of the year" ), date.dayOfYear() ).toString(); mUi->mYearlyCombo->addItem( item ); mUi->mYearlyCombo->setCurrentIndex( currentYearlyIndex == -1 ? 0 : currentYearlyIndex ); } void IncidenceRecurrence::handleDateTimeToggle() { QWidget *parent = mUi->mRepeatStack->parentWidget(); // Take the parent of a toplevel widget; if ( parent ) { parent->setEnabled( mDateTime->startDateTimeEnabled() ); } } void IncidenceRecurrence::handleEndAfterOccurrencesChange( int currentValue ) { mUi->mRecurrenceOccurrencesLabel->setText( i18ncp( "Recurrence ends after n occurrences", "occurrence", "occurrences", currentValue ) ); } void IncidenceRecurrence::handleExceptionDateChange( const QDate ¤tDate ) { const QDate date = mUi->mExceptionDateEdit->date(); const QString dateStr = KGlobal::locale()->formatDate( date ); mUi->mExceptionAddButton->setEnabled( currentDate >= mDateTime->startDate() && mUi->mExceptionList->findItems( dateStr, Qt::MatchExactly ).isEmpty() ); } void IncidenceRecurrence::handleFrequencyChange() { handleRecurrenceTypeChange( currentRecurrenceType() ); } void IncidenceRecurrence::handleRecurrenceTypeChange( int currentIndex ) { toggleRecurrenceWidgets( currentIndex ); QString labelFreq; QString freqKey; int frequency = mUi->mFrequencyEdit->value(); switch ( currentIndex ) { case 2: labelFreq = i18ncp( "repeat every N >weeks<", "week", "weeks", frequency ); freqKey = 'w'; break; case 3: labelFreq = i18ncp( "repeat every N >months<", "month", "months", frequency ); freqKey = 'm'; break; case 4: labelFreq = i18ncp( "repeat every N >years<", "year", "years", frequency ); freqKey = 'y'; break; default: labelFreq = i18ncp( "repeat every N >days<", "day", "days", frequency ); freqKey = 'd'; } QString labelEvery; labelEvery = ki18ncp( "repeat >every< N years/months/...; " "dynamic context 'type': 'd' days, 'w' weeks, " "'m' months, 'y' years", "every", "every" ). subs( frequency ).inContext( "type", freqKey ).toString(); mUi->mFrequencyLabel->setText( labelEvery ); mUi->mRecurrenceRuleLabel->setText( labelFreq ); emit recurrenceChanged( static_cast( currentIndex ) ); } void IncidenceRecurrence::removeExceptions() { QList selectedExceptions = mUi->mExceptionList->selectedItems(); foreach ( QListWidgetItem *selectedException, selectedExceptions ) { const int row = mUi->mExceptionList->row( selectedException ); mExceptionDates.removeAt( row ); delete mUi->mExceptionList->takeItem( row ); } handleExceptionDateChange( mUi->mExceptionDateEdit->date() ); checkDirtyStatus(); } void IncidenceRecurrence::updateRemoveExceptionButton() { mUi->mExceptionRemoveButton->setEnabled( mUi->mExceptionList->selectedItems().count() > 0 ); } void IncidenceRecurrence::updateWeekDays( const QDate &newStartDate ) { const int oldStartDayIndex = mUi->mWeekDayCombo->weekdayIndex( mCurrentDate ); const int newStartDayIndex = mUi->mWeekDayCombo->weekdayIndex( newStartDate ); if ( oldStartDayIndex >= 0 ) { mUi->mWeekDayCombo->setItemCheckState( oldStartDayIndex, Qt::Unchecked ); mUi->mWeekDayCombo->setItemEnabled( oldStartDayIndex, true ); } if ( newStartDayIndex >= 0 ) { mUi->mWeekDayCombo->setItemCheckState( newStartDayIndex, Qt::Checked ); mUi->mWeekDayCombo->setItemEnabled( newStartDayIndex, false ); } if ( newStartDate.isValid() ) { mCurrentDate = newStartDate; } } short IncidenceRecurrence::dayOfMonthFromStart() const { return currentDate().day(); } short IncidenceRecurrence::dayOfMonthFromEnd() const { const QDate start = currentDate(); return start.daysInMonth() - start.day() + 1; } short IncidenceRecurrence::dayOfYearFromStart() const { return currentDate().dayOfYear(); } int IncidenceRecurrence::duration() const { if ( mUi->mRecurrenceEndCombo->currentIndex() == RecurrenceEndNever ) { return -1; } else if ( mUi->mRecurrenceEndCombo->currentIndex() == RecurrenceEndAfter ) { return mUi->mEndDurationEdit->value(); } else { // 0 means "end date set" return 0; } } short IncidenceRecurrence::monthWeekFromStart() const { const QDate date = currentDate(); int count; if ( date.isValid() ) { count = 1; QDate tmp = date.addDays( -7 ); while ( tmp.month() == date.month() ) { tmp = tmp.addDays( -7 ); // Count backward ++count; } } else { // date can be invalid if you're editing the date with your keyboard count = -1; } // 1 is the first week, 4/5 is the last week of the month return count; } short IncidenceRecurrence::monthWeekFromEnd() const { const QDate date = currentDate(); int count; if ( date.isValid() ) { count = 1; QDate tmp = date.addDays( 7 ); while ( tmp.month() == date.month() ) { tmp = tmp.addDays( 7 ); // Count forward ++count; } } else { // date can be invalid if you're editing the date with your keyboard count = -1; } // 1 is the last week, 4/5 is the first week of the month return count; } QString IncidenceRecurrence::numberToString( int number ) const { // The code in here was adapted from an article by Johnathan Wood, see: // http://www.blackbeltcoder.com/Articles/strings/converting-numbers-to-ordinal-strings static QString _numSuffixes[] = { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; int i = ( number % 100 ); int j = ( i > 10 && i < 20 ) ? 0 : ( number % 10 ); return QString::number( number ) + _numSuffixes[j]; } void IncidenceRecurrence::selectMonthlyItem( KCalCore::Recurrence *recurrence, ushort recurenceType ) { Q_ASSERT( recurenceType == KCalCore::Recurrence::rMonthlyPos || recurenceType == KCalCore::Recurrence::rMonthlyDay ); if ( recurenceType == KCalCore::Recurrence::rMonthlyPos ) { QList rmp = recurrence->monthPositions(); if ( rmp.isEmpty() ) { return; // Use the default values. Probably marks the editor as dirty } if ( rmp.first().pos() > 0 ) { // nth day // TODO if ( rmp.first().pos() != mDateTime->startDate().day() ) { warn user } // NOTE: This silencly changes the recurrence when: // rmp.first().pos() != mDateTime->startDate().day() mUi->mMonthlyCombo->setCurrentIndex( ComboIndexMonthlyPos ); } else { // (month.last() - n)th day // TODO: Handle recurrences we cannot represent // QDate startDate = mDateTime->startDate(); // const int dayFromEnd = startDate.daysInMonth() - startDate.day(); // if ( qAbs( rmp.first().pos() ) != dayFromEnd ) { /* warn user */ } mUi->mMonthlyCombo->setCurrentIndex( ComboIndexMonthlyPosInverted ); } } else { // Monthly by day // check if we have any setting for which day (vcs import is broken and // does not set any day, thus we need to check) const int day = recurrence->monthDays().isEmpty() ? currentDate().day() : recurrence->monthDays().first(); // Days from the end are after the ones from the begin, so correct for the // negative sign and add 30 (index starting at 0) // TODO: Do similar checks as in the monthlyPos case if ( day > 0 && day <= 31 ) { mUi->mMonthlyCombo->setCurrentIndex( ComboIndexMonthlyDay ); } else if ( day < 0 ) { mUi->mMonthlyCombo->setCurrentIndex( ComboIndexMonthlyDayInverted ); } } // So we can easily detect if the user changed the type, without going through this logic ^ mMonthlyInitialType = mUi->mMonthlyCombo->currentIndex(); } void IncidenceRecurrence::selectYearlyItem( KCalCore::Recurrence *recurrence, ushort recurenceType ) { Q_ASSERT( recurenceType == KCalCore::Recurrence::rYearlyDay || recurenceType == KCalCore::Recurrence::rYearlyMonth || recurenceType == KCalCore::Recurrence::rYearlyPos ); if ( recurenceType == KCalCore::Recurrence::rYearlyDay ) { /* const int day = recurrence->yearDays().isEmpty() ? currentDate().dayOfYear() : recurrence->yearDays().first(); */ // TODO Check if day has actually the same value as in the combo. mUi->mYearlyCombo->setCurrentIndex( ComboIndexYearlyDay ); } else if ( recurenceType == KCalCore::Recurrence::rYearlyMonth ) { const int day = recurrence->yearDates().isEmpty() ? currentDate().day() : recurrence->yearDates().first(); /* int month = currentDate().month(); if ( !recurrence->yearMonths().isEmpty() ) { month = recurrence->yearMonths().first(); } */ // TODO check month and day to be correct values with respect to what is // presented in the combo box. if ( day > 0 ) { mUi->mYearlyCombo->setCurrentIndex( ComboIndexYearlyMonth ); } else { mUi->mYearlyCombo->setCurrentIndex( ComboIndexYearlyMonthInverted ); } } else { //KCalCore::Recurrence::rYearlyPos /* int month = currentDate().month(); if ( !recurrence->yearMonths().isEmpty() ) { month = recurrence->yearMonths().first(); } */ // count is the nth weekday of the month or the ith last weekday of the month. int count = ( currentDate().day() - 1 ) / 7; if ( !recurrence->yearPositions().isEmpty() ) { count = recurrence->yearPositions().first().pos(); } // TODO check month,count and day to be correct values with respect to what is // presented in the combo box. if ( count > 0 ) { mUi->mYearlyCombo->setCurrentIndex( ComboIndexYearlyPos ); } else { mUi->mYearlyCombo->setCurrentIndex( ComboIndexYearlyPosInverted ); } } // So we can easily detect if the user changed the type, without going through this logic ^ mYearlyInitialType = mUi->mYearlyCombo->currentIndex(); } void IncidenceRecurrence::setDefaults() { mUi->mRecurrenceEndCombo->setCurrentIndex( RecurrenceEndNever ); mUi->mRecurrenceEndDate->setDate( currentDate() ); mUi->mRecurrenceTypeCombo->setCurrentIndex( RecurrenceTypeNone ); setFrequency( 1 ); // -1 because we want between 0 and 6 const int day = KGlobal::locale()->calendar()->dayOfWeek( currentDate() ) - 1; QBitArray checkDays( 7, 0 ); checkDays.setBit( day ); QBitArray disableDays( 7, 0 ); disableDays.setBit( day ); mUi->mWeekDayCombo->setDays( checkDays, disableDays ); mUi->mMonthlyCombo->setCurrentIndex( 0 ); // Recur on the nth of the month mUi->mYearlyCombo->setCurrentIndex( 0 ); // Recur on the nth of the month } void IncidenceRecurrence::setDuration( int duration ) { if ( duration == -1 ) { // No end date mUi->mRecurrenceEndCombo->setCurrentIndex( RecurrenceEndNever ); mUi->mRecurrenceEndStack->setCurrentIndex( 0 ); } else if ( duration == 0 ) { mUi->mRecurrenceEndCombo->setCurrentIndex( RecurrenceEndOn ); mUi->mRecurrenceEndStack->setCurrentIndex( 1 ); } else { mUi->mRecurrenceEndCombo->setCurrentIndex( RecurrenceEndAfter ); mUi->mRecurrenceEndStack->setCurrentIndex( 2 ); mUi->mEndDurationEdit->setValue( duration ); } } void IncidenceRecurrence::setExceptionDates( const KCalCore::DateList &dates ) { mUi->mExceptionList->clear(); mExceptionDates.clear(); KCalCore::DateList::ConstIterator dit; for ( dit = dates.begin(); dit != dates.end(); ++dit ) { mUi->mExceptionList->addItem( KGlobal::locale()->formatDate(* dit ) ); mExceptionDates.append( *dit ); } } void IncidenceRecurrence::setFrequency( int frequency ) { if ( frequency < 1 ) { frequency = 1; } mUi->mFrequencyEdit->setValue( frequency ); } void IncidenceRecurrence::toggleRecurrenceWidgets( int recurrenceType ) { bool enable = (recurrenceType != RecurrenceTypeNone) && (recurrenceType != RecurrenceTypeException); mUi->mRecurrenceTypeCombo->setVisible(recurrenceType != RecurrenceTypeException); #ifndef KDEPIM_MOBILE_UI mUi->mRepeatLabel->setVisible(recurrenceType != RecurrenceTypeException); mUi->mRecurrenceEndLabel->setVisible( enable ); mUi->mOnLabel->setVisible( enable && recurrenceType != RecurrenceTypeDaily ); if ( !enable ) { // So we can hide the exceptions labels and not trigger column resizing. mUi->mRepeatLabel->setMinimumSize( mUi->mExceptionsLabel->sizeHint() ); } #endif mUi->mFrequencyLabel->setVisible( enable ); mUi->mFrequencyEdit->setVisible( enable ); mUi->mRecurrenceRuleLabel->setVisible( enable ); mUi->mRepeatStack->setVisible( enable && recurrenceType != RecurrenceTypeDaily ); mUi->mRepeatStack->setCurrentIndex( recurrenceType ); mUi->mRecurrenceEndCombo->setVisible( enable ); mUi->mEndDurationEdit->setVisible( enable ); mUi->mRecurrenceEndStack->setVisible( enable ); // Exceptions widgets mUi->mExceptionsLabel->setVisible( enable ); mUi->mExceptionDateEdit->setVisible( enable ); mUi->mExceptionAddButton->setVisible( enable ); mUi->mExceptionAddButton->setEnabled( mUi->mExceptionDateEdit->date() >= currentDate() ); mUi->mExceptionRemoveButton->setVisible( enable ); mUi->mExceptionRemoveButton->setEnabled( mUi->mExceptionList->selectedItems().count() > 0 ); mUi->mExceptionList->setVisible( enable ); mUi->mThisAndFutureCheck->setVisible( recurrenceType == RecurrenceTypeException ); } QBitArray IncidenceRecurrence::weekday() const { QBitArray days( 7 ); // QDate::dayOfWeek() -> returns [1 - 7], 1 == monday days.setBit( currentDate().dayOfWeek() - 1, true ); return days; } int IncidenceRecurrence::weekdayCountForMonth( const QDate &date ) const { Q_ASSERT( date.isValid() ); // This methods returns how often the weekday specified by @param date occurs // in the month represented by @param date. int count = 1; QDate tmp = date.addDays( -7 ); while ( tmp.month() == date.month() ) { tmp = tmp.addDays( -7 ); ++count; } tmp = date.addDays( 7 ); while ( tmp.month() == date.month() ) { tmp = tmp.addDays( 7 ); ++count; } return count; } RecurrenceType IncidenceRecurrence::currentRecurrenceType() const { if (mLoadedIncidence && mLoadedIncidence->hasRecurrenceId()) { return RecurrenceTypeException; } const int currentIndex = mUi->mRecurrenceTypeCombo->currentIndex(); Q_ASSERT_X( currentIndex >= 0 && currentIndex < RecurrenceTypeUnknown, "currentRecurrenceType", "Keep the combo-box values in sync with the enum" ); return static_cast( currentIndex ); } void IncidenceRecurrence::handleStartDateChange( const QDate &date ) { if ( currentDate().isValid() ) { fillCombos(); updateWeekDays( date ); mUi->mExceptionDateEdit->setDate( date ); } } QDate IncidenceRecurrence::currentDate() const { return mDateTime->startDate(); }