diff --git a/calendarviews/prefs.cpp b/calendarviews/prefs.cpp index 9f46e60d1e..439d9e7536 100644 --- a/calendarviews/prefs.cpp +++ b/calendarviews/prefs.cpp @@ -1,1119 +1,1137 @@ /* Copyright (c) 2001,2003 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "prefs.h" #include "prefs_base.h" #include #include using namespace EventViews; +K_GLOBAL_STATIC( Prefs, globalPrefs ) +K_GLOBAL_STATIC( Prefs, globalPrefsKorg ) + QSet iconArrayToSet( const QByteArray &array ) { QSet set; for ( int i=0; i= EventViews::EventView::IconCount ) { kWarning() << "Icon array is too big: " << array.count(); return set; } if ( array[i] != 0 ) { set.insert( static_cast( i ) ); } } return set; } QByteArray iconSetToArray( const QSet &set ) { QByteArray array; for ( int i=0; i( i ) ); array.append( contains ? 1 : 0 ) ; } return array; } QByteArray agendaViewIconDefaults() { QByteArray iconDefaults; iconDefaults[EventViews::EventView::CalendarCustomIcon] = 1; iconDefaults[EventViews::EventView::TaskIcon] = 1; iconDefaults[EventViews::EventView::JournalIcon] = 1; iconDefaults[EventViews::EventView::RecurringIcon] = 1; iconDefaults[EventViews::EventView::ReminderIcon] = 1; iconDefaults[EventViews::EventView::ReadOnlyIcon] = 1; iconDefaults[EventViews::EventView::ReplyIcon] = 0; return iconDefaults; } QByteArray monthViewIconDefaults() { QByteArray iconDefaults; iconDefaults[EventViews::EventView::CalendarCustomIcon] = 1; iconDefaults[EventViews::EventView::TaskIcon] = 1; iconDefaults[EventViews::EventView::JournalIcon] = 1; iconDefaults[EventViews::EventView::RecurringIcon] = 0; iconDefaults[EventViews::EventView::ReminderIcon] = 0; iconDefaults[EventViews::EventView::ReadOnlyIcon] = 1; iconDefaults[EventViews::EventView::ReplyIcon] = 0; return iconDefaults; } class BaseConfig : public PrefsBase { public: BaseConfig(); void setResourceColor( const QString &resource, const QColor &color ); void setTimeScaleTimezones( const QStringList &timeZones ); QStringList timeScaleTimezones() const; public: QHash mResourceColors; QColor mDefaultResourceColor; QFont mDefaultMonthViewFont; QFont mDefaultAgendaTimeLabelsFont; KDateTime::Spec mTimeSpec; QStringList mTimeScaleTimeZones; QSet mAgendaViewIcons; QSet mMonthViewIcons; protected: void usrSetDefaults(); void usrReadConfig(); void usrWriteConfig(); void setTimeZoneDefault(); }; BaseConfig::BaseConfig() : PrefsBase() { mDefaultResourceColor = QColor(); //Default is a color invalid mDefaultAgendaTimeLabelsFont = KGlobalSettings::generalFont(); // make a large default time bar font, at least 16 points. mDefaultAgendaTimeLabelsFont.setPointSize( qMax( mDefaultAgendaTimeLabelsFont.pointSize() + 4, 16 ) ); mDefaultMonthViewFont = KGlobalSettings::generalFont(); // make it a bit smaller mDefaultMonthViewFont.setPointSize( qMax( mDefaultMonthViewFont.pointSize() - 2, 6 ) ); agendaTimeLabelsFontItem()->setDefaultValue( mDefaultAgendaTimeLabelsFont ); agendaTimeLabelsFontItem()->setDefault(); monthViewFontItem()->setDefaultValue( mDefaultMonthViewFont ); monthViewFontItem()->setDefault(); } void BaseConfig::setResourceColor( const QString &resource, const QColor &color ) { mResourceColors.insert( resource, color ); } void BaseConfig::setTimeScaleTimezones( const QStringList &list ) { mTimeScaleTimeZones = list; } QStringList BaseConfig::timeScaleTimezones() const { return mTimeScaleTimeZones; } void BaseConfig::usrSetDefaults() { setAgendaTimeLabelsFont( mDefaultAgendaTimeLabelsFont ); setMonthViewFont( mDefaultMonthViewFont ); setTimeZoneDefault(); PrefsBase::usrSetDefaults(); } void BaseConfig::usrReadConfig() { KConfigGroup generalConfig( config(), "General" ); // Note that the [Category Colors] group was removed after 3.2 due to // an algorithm change. That's why we now use [Category Colors2] // Resource colors KConfigGroup rColorsConfig( config(), "Resources Colors" ); const QStringList colorKeyList = rColorsConfig.keyList(); QStringList::ConstIterator it3; for ( it3 = colorKeyList.begin(); it3 != colorKeyList.end(); ++it3 ) { QColor color = rColorsConfig.readEntry( *it3, mDefaultResourceColor ); //kDebug() << "key:" << (*it3) << "value:" << color; setResourceColor( *it3, color ); } if ( !mTimeSpec.isValid() ) { setTimeZoneDefault(); } #if 0 config()->setGroup( "FreeBusy" ); if ( mRememberRetrievePw ) { mRetrievePassword = KStringHandler::obscure( config()->readEntry( "Retrieve Server Password" ) ); } #endif KConfigGroup defaultCalendarConfig( config(), "Calendar" ); KConfigGroup timeScaleConfig( config(), "Timescale" ); setTimeScaleTimezones( timeScaleConfig.readEntry( "Timescale Timezones", QStringList() ) ); KConfigGroup monthViewConfig( config(), "Month View" ); KConfigGroup agendaViewConfig( config(), "Agenda View" ); const QByteArray agendaIconArray = agendaViewConfig.readEntry( "agendaViewItemIcons", agendaViewIconDefaults() ); const QByteArray monthIconArray = monthViewConfig.readEntry( "monthViewItemIcons", monthViewIconDefaults() ); mAgendaViewIcons = iconArrayToSet( agendaIconArray ); mMonthViewIcons = iconArrayToSet( monthIconArray ); KConfigSkeleton::usrReadConfig(); } void BaseConfig::usrWriteConfig() { KConfigGroup generalConfig( config(), "General" ); KConfigGroup rColorsConfig( config(), "Resources Colors" ); QHash::const_iterator i = mResourceColors.constBegin(); while ( i != mResourceColors.constEnd() ) { rColorsConfig.writeEntry( i.key(), i.value() ); ++i; } #if 0 if ( mRememberRetrievePw ) { config()->writeEntry( "Retrieve Server Password", KStringHandler::obscure( mRetrievePassword ) ); } else { config()->deleteEntry( "Retrieve Server Password" ); } #endif KConfigGroup timeScaleConfig( config(), "Timescale" ); timeScaleConfig.writeEntry( "Timescale Timezones", timeScaleTimezones() ); KConfigGroup monthViewConfig( config(), "Month View" ); KConfigGroup agendaViewConfig( config(), "Agenda View" ); const QByteArray agendaIconArray = iconSetToArray( mAgendaViewIcons ); const QByteArray monthIconArray = iconSetToArray( mMonthViewIcons ); agendaViewConfig.writeEntry( "agendaViewItemIcons", agendaIconArray ); monthViewConfig.writeEntry( "monthViewItemIcons", monthIconArray ); KConfigSkeleton::usrWriteConfig(); } void BaseConfig::setTimeZoneDefault() { KTimeZone zone = KSystemTimeZones::local(); if ( !zone.isValid() ) { kError() << "KSystemTimeZones::local() return 0"; return; } kDebug () << "----- time zone:" << zone.name(); mTimeSpec = zone; } class Prefs::Private { public: Private( Prefs *parent ) : mAppConfig( 0 ), q( parent ) {} Private( Prefs *parent, KCoreConfigSkeleton *appConfig ) : mAppConfig( appConfig ), q( parent ) {} void setTimeZoneDefault(); KConfigSkeletonItem *appConfigItem( const KConfigSkeletonItem *baseConfigItem ) const; void setBool( KCoreConfigSkeleton::ItemBool *baseConfigItem, bool value ); bool getBool( const KCoreConfigSkeleton::ItemBool *baseConfigItem ) const; void setInt( KCoreConfigSkeleton::ItemInt *baseConfigItem, int value ); int getInt( const KCoreConfigSkeleton::ItemInt *baseConfigItem ) const; void setString( KCoreConfigSkeleton::ItemString *baseConfigItem, const QString &value ); QString getString( const KCoreConfigSkeleton::ItemString *baseConfigItem ) const; void setDateTime( KCoreConfigSkeleton::ItemDateTime *baseConfigItem, const QDateTime &value ); QDateTime getDateTime( const KCoreConfigSkeleton::ItemDateTime *baseConfigItem ) const; void setStringList( KCoreConfigSkeleton::ItemStringList *baseConfigItem, const QStringList &value ); QStringList getStringList( const KCoreConfigSkeleton::ItemStringList *baseConfigItem ) const; void setColor( KConfigSkeleton::ItemColor *baseConfigItem, const QColor &value ); QColor getColor( const KConfigSkeleton::ItemColor *baseConfigItem ) const; void setFont( KConfigSkeleton::ItemFont *baseConfigItem, const QFont &value ); QFont getFont( const KConfigSkeleton::ItemFont *baseConfigItem ) const; public: BaseConfig mBaseConfig; KCoreConfigSkeleton *mAppConfig; private: Prefs *q; }; KConfigSkeletonItem *Prefs::Private::appConfigItem( const KConfigSkeletonItem *baseConfigItem ) const { Q_ASSERT( baseConfigItem ); if ( mAppConfig ) { return mAppConfig->findItem( baseConfigItem->name() ); } return 0; } void Prefs::Private::setBool( KCoreConfigSkeleton::ItemBool *baseConfigItem, bool value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemBool *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type Bool"; } } else { baseConfigItem->setValue( value ); } } bool Prefs::Private::getBool( const KCoreConfigSkeleton::ItemBool *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemBool *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type Bool"; } return baseConfigItem->value(); } void Prefs::Private::setInt( KCoreConfigSkeleton::ItemInt *baseConfigItem, int value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemInt *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type Int"; } } else { baseConfigItem->setValue( value ); } } int Prefs::Private::getInt( const KCoreConfigSkeleton::ItemInt *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemInt *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type Int"; } return baseConfigItem->value(); } void Prefs::Private::setString( KCoreConfigSkeleton::ItemString *baseConfigItem, const QString &value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemString *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type String"; } } else { baseConfigItem->setValue( value ); } } QString Prefs::Private::getString( const KCoreConfigSkeleton::ItemString *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemString *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type String"; } return baseConfigItem->value(); } void Prefs::Private::setDateTime( KCoreConfigSkeleton::ItemDateTime *baseConfigItem, const QDateTime &value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemDateTime *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type DateTime"; } } else { baseConfigItem->setValue( value ); } } QDateTime Prefs::Private::getDateTime( const KCoreConfigSkeleton::ItemDateTime *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemDateTime *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type DateTime"; } return baseConfigItem->value(); } void Prefs::Private::setStringList( KCoreConfigSkeleton::ItemStringList *baseConfigItem, const QStringList &value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemStringList *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type StringList"; } } else { baseConfigItem->setValue( value ); } } QStringList Prefs::Private::getStringList( const KCoreConfigSkeleton::ItemStringList *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KCoreConfigSkeleton::ItemStringList *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type StringList"; } return baseConfigItem->value(); } void Prefs::Private::setColor( KConfigSkeleton::ItemColor *baseConfigItem, const QColor &value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KConfigSkeleton::ItemColor *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type Color"; } } else { baseConfigItem->setValue( value ); } } QColor Prefs::Private::getColor( const KConfigSkeleton::ItemColor *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KConfigSkeleton::ItemColor *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type Color"; } return baseConfigItem->value(); } void Prefs::Private::setFont( KConfigSkeleton::ItemFont *baseConfigItem, const QFont &value ) { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KConfigSkeleton::ItemFont *item = dynamic_cast( appItem ); if ( item ) { item->setValue( value ); } else { kError() << "Application config item" << appItem->name() << "is not of type Font"; } } else { baseConfigItem->setValue( value ); } } QFont Prefs::Private::getFont( const KConfigSkeleton::ItemFont *baseConfigItem ) const { KConfigSkeletonItem *appItem = appConfigItem( baseConfigItem ); if ( appItem ) { KConfigSkeleton::ItemFont *item = dynamic_cast( appItem ); if ( item ) { return item->value(); } kError() << "Application config item" << appItem->name() << "is not of type Font"; } return baseConfigItem->value(); } Prefs::Prefs() : d( new Private( this ) ) { } Prefs::Prefs( KCoreConfigSkeleton *appConfig ) : d( new Private( this, appConfig ) ) { } Prefs::~Prefs() { delete d; } void Prefs::readConfig() { d->mBaseConfig.readConfig(); if ( d->mAppConfig ) { d->mAppConfig->readConfig(); } } void Prefs::writeConfig() { d->mBaseConfig.writeConfig(); if ( d->mAppConfig ) { d->mAppConfig->writeConfig(); } } +Prefs *Prefs::instance() +{ + static bool firstCall = true; + + if ( firstCall ) { + firstCall = false; + KSharedConfigPtr pConfig = KSharedConfig::openConfig(QLatin1String("korganizerrc")); + globalPrefsKorg->d->mBaseConfig.setSharedConfig(pConfig); + globalPrefs->d->mAppConfig = &(globalPrefsKorg->d->mBaseConfig); + globalPrefs->readConfig(); + } + + return globalPrefs; +} + void Prefs::setMarcusBainsShowSeconds( bool showSeconds ) { d->setBool( d->mBaseConfig.marcusBainsShowSecondsItem(), showSeconds ); } bool Prefs::marcusBainsShowSeconds() const { return d->getBool( d->mBaseConfig.marcusBainsShowSecondsItem() ); } void Prefs::setAgendaMarcusBainsLineLineColor( const QColor &color ) { d->setColor( d->mBaseConfig.agendaMarcusBainsLineLineColorItem(), color ); } QColor Prefs::agendaMarcusBainsLineLineColor() const { return d->getColor( d->mBaseConfig.agendaMarcusBainsLineLineColorItem() ); } void Prefs::setMarcusBainsEnabled( bool enabled ) { d->setBool( d->mBaseConfig.marcusBainsEnabledItem(), enabled ); } bool Prefs::marcusBainsEnabled() const { return d->getBool( d->mBaseConfig.marcusBainsEnabledItem() ); } void Prefs::setAgendaMarcusBainsLineFont( const QFont &font ) { d->setFont( d->mBaseConfig.agendaMarcusBainsLineFontItem(), font ); } QFont Prefs::agendaMarcusBainsLineFont() const { return d->getFont( d->mBaseConfig.agendaMarcusBainsLineFontItem() ); } void Prefs::setHourSize( int size ) { d->setInt( d->mBaseConfig.hourSizeItem(), size ); } int Prefs::hourSize() const { return d->getInt( d->mBaseConfig.hourSizeItem() ); } void Prefs::setDayBegins( const QDateTime &dateTime ) { d->setDateTime( d->mBaseConfig.dayBeginsItem(), dateTime ); } QDateTime Prefs::dayBegins() const { return d->getDateTime( d->mBaseConfig.dayBeginsItem() ); } void Prefs::setWorkingHoursStart( const QDateTime &dateTime ) { d->setDateTime( d->mBaseConfig.workingHoursStartItem(), dateTime ); } QDateTime Prefs::workingHoursStart() const { return d->getDateTime( d->mBaseConfig.workingHoursStartItem() ); } void Prefs::setWorkingHoursEnd( const QDateTime &dateTime ) { d->setDateTime( d->mBaseConfig.workingHoursEndItem(), dateTime ); } QDateTime Prefs::workingHoursEnd() const { return d->getDateTime( d->mBaseConfig.workingHoursEndItem() ); } void Prefs::setSelectionStartsEditor( bool startEditor ) { d->setBool( d->mBaseConfig.selectionStartsEditorItem(), startEditor ); } bool Prefs::selectionStartsEditor() const { return d->getBool( d->mBaseConfig.selectionStartsEditorItem() ); } void Prefs::setAgendaGridWorkHoursBackgroundColor( const QColor &color ) { d->setColor( d->mBaseConfig.agendaGridWorkHoursBackgroundColorItem(), color ); } QColor Prefs::agendaGridWorkHoursBackgroundColor() const { return d->getColor( d->mBaseConfig.agendaGridWorkHoursBackgroundColorItem() ); } void Prefs::setAgendaGridHighlightColor( const QColor &color ) { d->setColor( d->mBaseConfig.agendaGridHighlightColorItem(), color ); } QColor Prefs::agendaGridHighlightColor() const { return d->getColor( d->mBaseConfig.agendaGridHighlightColorItem() ); } void Prefs::setAgendaGridBackgroundColor( const QColor &color ) { d->setColor( d->mBaseConfig.agendaGridBackgroundColorItem(), color ); } QColor Prefs::agendaGridBackgroundColor() const { return d->getColor( d->mBaseConfig.agendaGridBackgroundColorItem() ); } void Prefs::setEnableAgendaItemIcons( bool enable ) { d->setBool( d->mBaseConfig.enableAgendaItemIconsItem(), enable ); } bool Prefs::enableAgendaItemIcons() const { return d->getBool( d->mBaseConfig.enableAgendaItemIconsItem() ); } void Prefs::setTodosUseCategoryColors( bool useColors ) { d->setBool( d->mBaseConfig.todosUseCategoryColorsItem(), useColors ); } bool Prefs::todosUseCategoryColors() const { return d->getBool( d->mBaseConfig.todosUseCategoryColorsItem() ); } void Prefs::setAgendaHolidaysBackgroundColor( const QColor &color ) const { d->setColor( d->mBaseConfig.agendaHolidaysBackgroundColorItem(), color ); } QColor Prefs::agendaHolidaysBackgroundColor() const { return d->getColor( d->mBaseConfig.agendaHolidaysBackgroundColorItem() ); } void Prefs::setAgendaViewColors( int colors ) { d->setInt( d->mBaseConfig.agendaViewColorsItem(), colors ); } int Prefs::agendaViewColors() const { return d->getInt( d->mBaseConfig.agendaViewColorsItem() ); } void Prefs::setAgendaViewFont( const QFont &font ) { d->setFont( d->mBaseConfig.agendaViewFontItem(), font ); } QFont Prefs::agendaViewFont() const { return d->getFont( d->mBaseConfig.agendaViewFontItem() ); } void Prefs::setMonthViewFont( const QFont &font ) { d->setFont( d->mBaseConfig.monthViewFontItem(), font ); } QFont Prefs::monthViewFont() const { return d->getFont( d->mBaseConfig.monthViewFontItem() ); } QColor Prefs::monthGridBackgroundColor() const { return d->getColor( d->mBaseConfig.monthGridBackgroundColorItem() ); } void Prefs::setMonthGridBackgroundColor( const QColor &color ) { d->setColor( d->mBaseConfig.monthGridBackgroundColorItem(), color ); } QColor Prefs::monthGridWorkHoursBackgroundColor() const { return d->getColor( d->mBaseConfig.monthGridWorkHoursBackgroundColorItem() ); } void Prefs::monthGridWorkHoursBackgroundColor( const QColor &color ) { d->setColor( d->mBaseConfig.monthGridWorkHoursBackgroundColorItem(), color ); } int Prefs::monthViewColors() const { return d->getInt( d->mBaseConfig.monthViewColorsItem() ); } void Prefs::setMonthViewColors( int colors ) const { d->setInt( d->mBaseConfig.monthViewColorsItem(), colors ); } void Prefs::setEnableMonthItemIcons( bool enable ) { d->setBool( d->mBaseConfig.enableMonthItemIconsItem(), enable ); } bool Prefs::enableMonthItemIcons() const { return d->getBool( d->mBaseConfig.enableMonthItemIconsItem() ); } bool Prefs::showTimeInMonthView() const { return d->getBool( d->mBaseConfig.showTimeInMonthViewItem() ); } void Prefs::setShowTimeInMonthView( bool show ) { d->setBool( d->mBaseConfig.showTimeInMonthViewItem(), show ); } bool Prefs::showTodosMonthView() const { return d->getBool( d->mBaseConfig.showTodosMonthViewItem() ); } void Prefs::setShowTodosMonthView( bool enable ) { d->setBool( d->mBaseConfig.showTodosMonthViewItem(), enable ); } bool Prefs::showJournalsMonthView() const { return d->getBool( d->mBaseConfig.showJournalsMonthViewItem() ); } void Prefs::setShowJournalsMonthView( bool enable ) { d->setBool( d->mBaseConfig.showJournalsMonthViewItem(), enable ); } bool Prefs::fullViewMonth() const { return d->getBool( d->mBaseConfig.fullViewMonthItem() ); } void Prefs::setFullViewMonth( bool fullView ) { d->setBool( d->mBaseConfig.fullViewMonthItem(), fullView ); } bool Prefs::sortCompletedTodosSeparately() const { return d->getBool( d->mBaseConfig.sortCompletedTodosSeparatelyItem() ); } void Prefs::setSortCompletedTodosSeparately( bool enable ) { d->setBool( d->mBaseConfig.sortCompletedTodosSeparatelyItem(), enable ); } void Prefs::setEnableToolTips( bool enable ) { d->setBool( d->mBaseConfig.enableToolTipsItem(), enable ); } bool Prefs::enableToolTips() const { return d->getBool( d->mBaseConfig.enableToolTipsItem() ); } void Prefs::setShowTodosAgendaView( bool show ) { d->setBool( d->mBaseConfig.showTodosAgendaViewItem(), show ); } bool Prefs::showTodosAgendaView() const { return d->getBool( d->mBaseConfig.showTodosAgendaViewItem() ); } void Prefs::setAgendaTimeLabelsFont( const QFont &font ) { d->setFont( d->mBaseConfig.agendaTimeLabelsFontItem(), font ); } QFont Prefs::agendaTimeLabelsFont() const { return d->getFont( d->mBaseConfig.agendaTimeLabelsFontItem() ); } KDateTime::Spec Prefs::timeSpec() const { return KSystemTimeZones::local(); } void Prefs::setTimeSpec( const KDateTime::Spec &spec ) { d->mBaseConfig.mTimeSpec = spec; } bool Prefs::colorAgendaBusyDays() const { return d->getBool( d->mBaseConfig.colorBusyDaysEnabledItem() ); } bool Prefs::colorMonthBusyDays() const { return d->getBool( d->mBaseConfig.colorMonthBusyDaysEnabledItem() ); } QColor Prefs::viewBgBusyColor() const { return d->getColor( d->mBaseConfig.viewBgBusyColorItem() ); } void Prefs::setViewBgBusyColor( const QColor &color ) { d->mBaseConfig.mViewBgBusyColor = color; } QColor Prefs::holidayColor() const { return d->getColor( d->mBaseConfig.holidayColorItem() ); } void Prefs::setHolidayColor( const QColor &color ) { d->mBaseConfig.mHolidayColor = color; } QColor Prefs::agendaViewBackgroundColor() const { return d->getColor( d->mBaseConfig.agendaBgColorItem() ); } void Prefs::setAgendaViewBackgroundColor( const QColor &color ) { d->mBaseConfig.mAgendaBgColor = color; } QColor Prefs::workingHoursColor() const { return d->getColor( d->mBaseConfig.workingHoursColorItem() ); } void Prefs::setWorkingHoursColor( const QColor &color ) { d->mBaseConfig.mWorkingHoursColor = color; } QColor Prefs::todoDueTodayColor() const { return d->getColor( d->mBaseConfig.todoDueTodayColorItem() ); } void Prefs::setTodoDueTodayColor( const QColor &color ) { d->mBaseConfig.mTodoDueTodayColor = color; } QColor Prefs::todoOverdueColor() const { return d->getColor( d->mBaseConfig.todoOverdueColorItem() ); } void Prefs::setTodoOverdueColor( const QColor &color ) { d->mBaseConfig.mTodoOverdueColor = color; } void Prefs::setColorAgendaBusyDays( bool enable ) { d->mBaseConfig.mColorBusyDaysEnabled = enable; } void Prefs::setColorMonthBusyDays( bool enable ) { d->mBaseConfig.mColorMonthBusyDaysEnabled = enable; } void Prefs::setResourceColor ( const QString &cal, const QColor &color ) { d->mBaseConfig.setResourceColor( cal, color ); } QColor Prefs::resourceColorKnown(const QString &cal) { QColor color; if ( !cal.isEmpty() && d->mBaseConfig.mResourceColors.contains( cal ) ) { color = d->mBaseConfig.mResourceColors.value( cal ); } return color; } QColor Prefs::resourceColor( const QString &cal ) { if ( cal.isEmpty() ) { return d->mBaseConfig.mDefaultResourceColor; } QColor color = resourceColorKnown(cal); // assign default color if enabled if ( !color.isValid() && d->getBool( d->mBaseConfig.assignDefaultResourceColorsItem() ) ) { QColor defColor( 0x37, 0x7A, 0xBC ); const int seed = d->getInt( d->mBaseConfig.defaultResourceColorSeedItem() ); const QStringList colors = d->getStringList( d->mBaseConfig.defaultResourceColorsItem() ); if ( seed > 0 && seed - 1 < (int)colors.size() ) { defColor = QColor( colors[seed-1] ); } else { int h, s, v; defColor.getHsv( &h, &s, &v ); h = ( seed % 12 ) * 30; s -= s * static_cast( ( ( seed / 12 ) % 2 ) * 0.5 ); defColor.setHsv( h, s, v ); } d->setInt( d->mBaseConfig.defaultResourceColorSeedItem(), ( seed + 1 ) ); d->mBaseConfig.setResourceColor( cal, defColor ); color = d->mBaseConfig.mResourceColors[cal]; } if ( color.isValid() ) { return color; } else { return d->mBaseConfig.mDefaultResourceColor; } } QStringList Prefs::timeScaleTimezones() const { return d->mBaseConfig.timeScaleTimezones(); } void Prefs::setTimeScaleTimezones( const QStringList &list ) { d->mBaseConfig.setTimeScaleTimezones( list ); } KConfigSkeleton::ItemFont *Prefs::fontItem( const QString &name ) const { KConfigSkeletonItem *item = d->mAppConfig ? d->mAppConfig->findItem( name ) : 0; if ( !item ) { item = d->mBaseConfig.findItem( name ); } return dynamic_cast( item ); } QStringList Prefs::selectedPlugins() const { return d->mBaseConfig.mSelectedPlugins; } QStringList Prefs::decorationsAtAgendaViewTop() const { return d->mBaseConfig.decorationsAtAgendaViewTop(); } QStringList Prefs::decorationsAtAgendaViewBottom() const { return d->mBaseConfig.decorationsAtAgendaViewBottom(); } void Prefs::setSelectedPlugins( const QStringList &plugins ) { d->mBaseConfig.setSelectedPlugins( plugins ); } void Prefs::setDecorationsAtAgendaViewTop( const QStringList &decorations ) { d->mBaseConfig.setDecorationsAtAgendaViewTop( decorations ); } void Prefs::setDecorationsAtAgendaViewBottom( const QStringList &decorations ) { d->mBaseConfig.setDecorationsAtAgendaViewBottom( decorations ); } QSet Prefs::agendaViewIcons() const { return d->mBaseConfig.mAgendaViewIcons; } void Prefs::setAgendaViewIcons( const QSet &icons ) { d->mBaseConfig.mAgendaViewIcons = icons; } QSet Prefs::monthViewIcons() const { return d->mBaseConfig.mMonthViewIcons; } void Prefs::setMonthViewIcons( const QSet &icons ) { d->mBaseConfig.mMonthViewIcons = icons; } void Prefs::setFlatListTodo( bool enable ) { d->mBaseConfig.mFlatListTodo = enable; } bool Prefs::flatListTodo() const { return d->mBaseConfig.mFlatListTodo; } void Prefs::setFullViewTodo( bool enable ) { d->mBaseConfig.mFullViewTodo = enable; } bool Prefs::fullViewTodo() const { return d->mBaseConfig.mFullViewTodo; } bool Prefs::enableTodoQuickSearch() const { return d->mBaseConfig.mEnableTodoQuickSearch; } void Prefs::setEnableTodoQuickSearch( bool enable ) { d->mBaseConfig.mEnableTodoQuickSearch = enable; } bool Prefs::enableQuickTodo() const { return d->mBaseConfig.mEnableQuickTodo; } void Prefs::setEnableQuickTodo( bool enable ) { d->mBaseConfig.mEnableQuickTodo = enable; } bool Prefs::highlightTodos() const { return d->mBaseConfig.mHighlightTodos; } void Prefs::setHighlightTodos( bool highlight ) { d->mBaseConfig.mHighlightTodos = highlight; } KConfig *Prefs::config() const { return d->mAppConfig ? d->mAppConfig->config() : d->mBaseConfig.config(); } diff --git a/calendarviews/prefs.h b/calendarviews/prefs.h index 1c0aa032d9..5484092324 100644 --- a/calendarviews/prefs.h +++ b/calendarviews/prefs.h @@ -1,232 +1,236 @@ /* Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef EVENTVIEWS_PREFS_H #define EVENTVIEWS_PREFS_H #include "eventviews_export.h" #include "eventview.h" #include #include namespace boost { template class shared_ptr; } namespace EventViews { class EVENTVIEWS_EXPORT Prefs { public: /** Creates an instance of Prefs with just base config */ Prefs(); /** Creates an instance of Prefs with base config and application override config The passed @p appConfig will be queried for matching items whenever one of the accessors is called. If one is found it is used for setting/getting the value otherwise the one from the eventviews base config is used. */ explicit Prefs( KCoreConfigSkeleton *appConfig ); ~Prefs(); void readConfig(); void writeConfig(); + /** Get instance of Prefs. It is made sure that there is only one + instance. */ + static Prefs *instance(); + public: void setMarcusBainsShowSeconds( bool showSeconds ); bool marcusBainsShowSeconds() const; void setAgendaMarcusBainsLineLineColor( const QColor &color ); QColor agendaMarcusBainsLineLineColor() const; void setMarcusBainsEnabled( bool enabled ); bool marcusBainsEnabled() const; void setAgendaMarcusBainsLineFont( const QFont &font ); QFont agendaMarcusBainsLineFont() const; void setHourSize( int size ); int hourSize() const; void setDayBegins( const QDateTime &dateTime ); QDateTime dayBegins() const; void setWorkingHoursStart( const QDateTime &dateTime ); QDateTime workingHoursStart() const; void setWorkingHoursEnd( const QDateTime &dateTime ); QDateTime workingHoursEnd() const; void setSelectionStartsEditor( bool startEditor ); bool selectionStartsEditor() const; void setAgendaGridWorkHoursBackgroundColor( const QColor &color ); QColor agendaGridWorkHoursBackgroundColor() const; void setAgendaGridHighlightColor( const QColor &color ); QColor agendaGridHighlightColor() const; void setAgendaGridBackgroundColor( const QColor &color ); QColor agendaGridBackgroundColor() const; void setEnableAgendaItemIcons( bool enable ); bool enableAgendaItemIcons() const; void setTodosUseCategoryColors( bool useColors ); bool todosUseCategoryColors() const; void setAgendaHolidaysBackgroundColor( const QColor &color ) const; QColor agendaHolidaysBackgroundColor() const; void setAgendaViewColors( int colors ); int agendaViewColors() const; void setAgendaViewFont( const QFont &font ); QFont agendaViewFont() const; void setMonthViewFont( const QFont &font ); QFont monthViewFont() const; QColor monthGridBackgroundColor() const; void setMonthGridBackgroundColor( const QColor &color ); QColor monthGridWorkHoursBackgroundColor() const; void monthGridWorkHoursBackgroundColor( const QColor &color ); void setMonthViewColors( int colors ) const; int monthViewColors() const; bool enableMonthItemIcons() const; void setEnableMonthItemIcons( bool enable ); bool showTimeInMonthView() const; void setShowTimeInMonthView( bool show ); bool showTodosMonthView() const; void setShowTodosMonthView( bool show ); bool showJournalsMonthView() const; void setShowJournalsMonthView( bool show ); bool fullViewMonth() const; void setFullViewMonth( bool fullView ); bool sortCompletedTodosSeparately() const; void setSortCompletedTodosSeparately( bool sort ); void setEnableToolTips( bool enable ); bool enableToolTips() const; void setShowTodosAgendaView( bool show ); bool showTodosAgendaView() const; void setAgendaTimeLabelsFont( const QFont &font ); QFont agendaTimeLabelsFont() const; KConfigSkeleton::ItemFont *fontItem( const QString &name ) const; void setResourceColor ( const QString &, const QColor & ); QColor resourceColor( const QString & ); QColor resourceColorKnown( const QString & ); void setTimeSpec( const KDateTime::Spec &spec ); KDateTime::Spec timeSpec() const; QStringList timeScaleTimezones() const; void setTimeScaleTimezones( const QStringList &list ); QStringList selectedPlugins() const; void setSelectedPlugins( const QStringList &); QStringList decorationsAtAgendaViewTop() const; void setDecorationsAtAgendaViewTop( const QStringList & ); QStringList decorationsAtAgendaViewBottom() const; void setDecorationsAtAgendaViewBottom( const QStringList & ); bool colorAgendaBusyDays() const; void setColorAgendaBusyDays( bool enable ); bool colorMonthBusyDays() const; void setColorMonthBusyDays( bool enable ); QColor viewBgBusyColor() const; void setViewBgBusyColor( const QColor & ); QColor holidayColor() const; void setHolidayColor( const QColor &color ); QColor agendaViewBackgroundColor() const; void setAgendaViewBackgroundColor( const QColor &color ); QColor workingHoursColor() const; void setWorkingHoursColor( const QColor &color ); QColor todoDueTodayColor() const; void setTodoDueTodayColor( const QColor &color ); QColor todoOverdueColor() const; void setTodoOverdueColor( const QColor &color ); QSet agendaViewIcons() const; void setAgendaViewIcons( const QSet &icons ); QSet monthViewIcons() const; void setMonthViewIcons( const QSet &icons ); void setFlatListTodo( bool ); bool flatListTodo() const; void setFullViewTodo( bool ); bool fullViewTodo() const; bool enableTodoQuickSearch() const; void setEnableTodoQuickSearch( bool enable ); bool enableQuickTodo() const; void setEnableQuickTodo( bool enable ); bool highlightTodos() const; void setHighlightTodos( bool ); KConfig *config() const; private: class Private; Private *const d; }; typedef boost::shared_ptr PrefsPtr; } #endif // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/incidenceeditor-ng/conflictresolver.cpp b/incidenceeditor-ng/conflictresolver.cpp index e8691a1cc1..ab8b24f341 100644 --- a/incidenceeditor-ng/conflictresolver.cpp +++ b/incidenceeditor-ng/conflictresolver.cpp @@ -1,493 +1,544 @@ /* Copyright (c) 2000,2001,2004 Cornelius Schumacher Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia Copyright (C) 2010 Casey Link 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 "conflictresolver.h" #include "freebusymodel/freebusyitemmodel.h" #include #include #include static const int DEFAULT_RESOLUTION_SECONDS = 15 * 60; // 15 minutes, 1 slot = 15 minutes using namespace IncidenceEditorNG; ConflictResolver::ConflictResolver( QWidget *parentWidget, QObject *parent ) : QObject( parent ), mFBModel( new FreeBusyItemModel( this ) ), mParentWidget( parentWidget ), mWeekdays( 7 ), - mSlotResolutionSeconds( DEFAULT_RESOLUTION_SECONDS ) + mSlotResolutionSeconds( DEFAULT_RESOLUTION_SECONDS ), + mWorkingHoursOnly(false), + mDuration(0) { // trigger a reload in case any attendees were inserted before // the connection was made // triggerReload(); // set default values, all the days mWeekdays.setBit( 0 ); // Monday mWeekdays.setBit( 1 ); mWeekdays.setBit( 2 ); mWeekdays.setBit( 3 ); mWeekdays.setBit( 4 ); mWeekdays.setBit( 5 ); mWeekdays.setBit( 6 ); // Sunday mMandatoryRoles << KCalCore::Attendee::ReqParticipant << KCalCore::Attendee::OptParticipant << KCalCore::Attendee::NonParticipant << KCalCore::Attendee::Chair; connect( mFBModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(freebusyDataChanged()) ); connect( &mCalculateTimer, SIGNAL(timeout()), SLOT(findAllFreeSlots()) ); mCalculateTimer.setSingleShot( true ); } void ConflictResolver::insertAttendee( const KCalCore::Attendee::Ptr &attendee ) { if ( !mFBModel->containsAttendee( attendee ) ) { mFBModel->addItem( FreeBusyItem::Ptr( new FreeBusyItem( attendee, mParentWidget ) ) ); } } void ConflictResolver::insertAttendee( const FreeBusyItem::Ptr &freebusy ) { if ( !mFBModel->containsAttendee( freebusy->attendee() ) ) { mFBModel->addItem( freebusy ); } } void ConflictResolver::removeAttendee( const KCalCore::Attendee::Ptr &attendee ) { mFBModel->removeAttendee( attendee ); calculateConflicts(); } void ConflictResolver::clearAttendees() { mFBModel->clear(); } bool ConflictResolver::containsAttendee( const KCalCore::Attendee::Ptr &attendee ) { return mFBModel->containsAttendee( attendee ); } void ConflictResolver::setEarliestDate( const QDate &newDate ) { KDateTime newStart = mTimeframeConstraint.start(); newStart.setDate( newDate ); mTimeframeConstraint = KCalCore::Period( newStart, mTimeframeConstraint.end() ); calculateConflicts(); } void ConflictResolver::setEarliestTime( const QTime &newTime ) { KDateTime newStart = mTimeframeConstraint.start(); newStart.setTime( newTime ); mTimeframeConstraint = KCalCore::Period( newStart, mTimeframeConstraint.end() ); calculateConflicts(); } void ConflictResolver::setLatestDate( const QDate &newDate ) { KDateTime newEnd = mTimeframeConstraint.end(); newEnd.setDate( newDate ); mTimeframeConstraint = KCalCore::Period( mTimeframeConstraint.start(), newEnd ); calculateConflicts(); } void ConflictResolver::setLatestTime( const QTime &newTime ) { KDateTime newEnd = mTimeframeConstraint.end(); newEnd.setTime( newTime ); mTimeframeConstraint = KCalCore::Period( mTimeframeConstraint.start(), newEnd ); calculateConflicts(); } void ConflictResolver::setEarliestDateTime( const KDateTime &newDateTime ) { mTimeframeConstraint = KCalCore::Period( newDateTime, mTimeframeConstraint.end() ); calculateConflicts(); } void ConflictResolver::setLatestDateTime( const KDateTime &newDateTime ) { mTimeframeConstraint = KCalCore::Period( mTimeframeConstraint.start(), newDateTime ); calculateConflicts(); } void ConflictResolver::freebusyDataChanged() { calculateConflicts(); } +void ConflictResolver::setWorkingHoursOnly(bool workingTime) +{ + mWorkingHoursOnly = workingTime; + calculateConflicts(); +} + +bool ConflictResolver::workingHoursOnly() const +{ + return mWorkingHoursOnly; +} + +void ConflictResolver::setWorkingHours(QTime start, QTime end) +{ + mWorkingHoursStart = start; + mWorkingHoursEnd = end; + calculateConflicts(); +} + +void ConflictResolver::setDuration(int duration) +{ + mDuration = duration; + calculateConflicts(); +} + int ConflictResolver::tryDate( KDateTime &tryFrom, KDateTime &tryTo ) { int conflicts_count = 0; for ( int i = 0; i < mFBModel->rowCount(); ++i ) { QModelIndex index = mFBModel->index( i ); KCalCore::Attendee::Ptr attendee = mFBModel->data( index, FreeBusyItemModel::AttendeeRole ).value(); if ( !matchesRoleConstraint( attendee ) ) { continue; } KCalCore::FreeBusy::Ptr freebusy = mFBModel->data( index, FreeBusyItemModel::FreeBusyRole ).value(); if ( !tryDate( freebusy, tryFrom, tryTo ) ) { ++conflicts_count; } } return conflicts_count; } bool ConflictResolver::tryDate( const KCalCore::FreeBusy::Ptr &fb, KDateTime &tryFrom, KDateTime &tryTo ) { // If we don't have any free/busy information, assume the // participant is free. Otherwise a participant without available // information would block the whole allocation. if ( !fb ) { return true; } KCalCore::Period::List busyPeriods = fb->busyPeriods(); for ( KCalCore::Period::List::Iterator it = busyPeriods.begin(); it != busyPeriods.end(); ++it ) { if ( (*it).end() <= tryFrom || // busy period ends before try period (*it).start() >= tryTo ) { // busy period starts after try period continue; } else { // the current busy period blocks the try period, try // after the end of the current busy period const int secsDuration = tryFrom.secsTo( tryTo ); tryFrom = ( *it ).end(); tryTo = tryFrom.addSecs( secsDuration ); // try again with the new try period tryDate( fb, tryFrom, tryTo ); // we had to change the date at least once return false; } } return true; } bool ConflictResolver::findFreeSlot( const KCalCore::Period &dateTimeRange ) { KDateTime dtFrom = dateTimeRange.start(); KDateTime dtTo = dateTimeRange.end(); if ( tryDate( dtFrom, dtTo ) ) { // Current time is acceptable return true; } KDateTime tryFrom = dtFrom; KDateTime tryTo = dtTo; // Make sure that we never suggest a date in the past, even if the // user originally scheduled the meeting to be in the past. KDateTime now = KDateTime::currentUtcDateTime(); if ( tryFrom < now ) { // The slot to look for is at least partially in the past. const int secs = tryFrom.secsTo( tryTo ); tryFrom = now; tryTo = tryFrom.addSecs( secs ); } bool found = false; while ( !found ) { found = tryDate( tryFrom, tryTo ); // PENDING(kalle) Make the interval configurable if ( !found && dtFrom.daysTo( tryFrom ) > 365 ) { break; // don't look more than one year in the future } } dtFrom = tryFrom; dtTo = tryTo; return found; } void ConflictResolver::findAllFreeSlots() { // Uses an O(p*n) (n number of attendees, p timeframe range / timeslot resolution ) algorithm to // locate all free blocks in a given timeframe that match the search constraints. // Does so by: // 1. convert each attendees schedule for the timeframe into a bitarray according to // the time resolution, where each time slot has a value of 1 = busy, 0 = free. // 2. align the arrays vertically, and sum the columns // 3. the resulting summation indcates # of conflicts at each timeslot // 4. locate contiguous timeslots with a values of 0. these are the free time blocks. // define these locally for readability const KDateTime begin = mTimeframeConstraint.start(); const KDateTime end = mTimeframeConstraint.end(); + const int duration = mDuration / mSlotResolutionSeconds; + + bool workingHoursOnly = mWorkingHoursOnly; + + if (workingHoursOnly && (!mWorkingHoursStart.isValid() || !mWorkingHoursStart.isValid() )) { + workingHoursOnly = false; + } + // calculate the time resolution // each timeslot in the arrays represents a unit of time // specified here. if ( mSlotResolutionSeconds < 1 ) { // fallback to default, if the user's value is invalid mSlotResolutionSeconds = DEFAULT_RESOLUTION_SECONDS; } // calculate the length of the timeframe in terms of the amount of timeslots. // Example: 1 week timeframe, with resolution of 15 minutes // 1 week = 10080 minutes / 15 = 672 15 min timeslots // So, the array would have a length of 672 const int range = begin.secsTo( end ) / mSlotResolutionSeconds; if ( range <= 0 ) { kWarning() << "free slot calculation: invalid range. range( " << begin.secsTo( end ) << ") / mSlotResolutionSeconds(" << mSlotResolutionSeconds << ") = " << range; return; } kDebug() << "from " << begin << " to " << end << "; mSlotResolutionSeconds = " << mSlotResolutionSeconds - << "; range = " << range; + << "; range = " << range + << "; workingtime =" << workingHoursOnly; // filter out attendees for which we don't have FB data // and which don't match the mandatory role contrstaint QList filteredFBItems; for ( int i = 0; i < mFBModel->rowCount(); ++i ) { QModelIndex index = mFBModel->index( i ); KCalCore::Attendee::Ptr attendee = mFBModel->data( index, FreeBusyItemModel::AttendeeRole ).value(); if ( !matchesRoleConstraint( attendee ) ) { continue; } KCalCore::FreeBusy::Ptr freebusy = mFBModel->data( index, FreeBusyItemModel::FreeBusyRole ).value(); if( freebusy ) { filteredFBItems << freebusy; } } // now we know the number of attendees we are calculating for const int number_attendees = filteredFBItems.size(); if ( number_attendees <= 0 ) { kDebug() << "no attendees match search criteria"; return; } kDebug() << "num attendees: " << number_attendees; // this is a 2 dimensional array where the rows are attendees // and the columns are 0 or 1 denoting freee or busy respectively. QVector< QVector > fbTable; // Explanation of the following loop: // iterate: through each attendee // allocate: an array of length and fill it with 0s // iterate: through each attendee's busy period // if: the period lies inside our timeframe // then: // calculate the array index within the timeframe range of the beginning of the busy period // fill from that index until the period ends with a 1, representing busy // fi // etareti // append the allocated array to // etareti foreach ( KCalCore::FreeBusy::Ptr currentFB, filteredFBItems ) { Q_ASSERT( currentFB ); // sanity check KCalCore::Period::List busyPeriods = currentFB->busyPeriods(); QVector fbArray( range ); fbArray.fill( 0 ); // initialize to zero for ( KCalCore::Period::List::Iterator it = busyPeriods.begin(); it != busyPeriods.end(); ++it ) { if ( it->end() >= begin && it->start() <= end ) { int start_index = -1; // Initialize it to an invalid value. int duration = -1; // Initialize it to an invalid value. // case1: the period is completely in our timeframe if( it->end() <= end && it->start() >= begin ) { start_index = begin.secsTo( it->start() ) / mSlotResolutionSeconds; duration = it->start().secsTo( it->end() ) / mSlotResolutionSeconds; duration -= 1; // vector starts at 0 // case2: the period begins before our timeframe begins } else if( it->start() <= begin && it->end() <= end ) { start_index = 0; duration = ( begin.secsTo( it->end() ) / mSlotResolutionSeconds ) - 1; // case3: the period ends after our timeframe ends } else if( it->end() >= end && it->start() >= begin ) { start_index = begin.secsTo( it->start() ) / mSlotResolutionSeconds; duration = range - start_index - 1; // case4: case2+case3: our timeframe is inside the period } else if( it->start() <= begin && it->end() >= end ) { start_index = 0; duration = range - 1; } else { kFatal() << "impossible condition reached" << it->start() << it->end(); } // kDebug() << start_index << "+" << duration << "=" // << start_index + duration << "<=" << range; Q_ASSERT( ( start_index + duration ) < range ); // sanity check for ( int i = start_index; i <= start_index + duration; ++i ) { fbArray[i] = 1; } } } Q_ASSERT( fbArray.size() == range ); // sanity check fbTable.append( fbArray ); } Q_ASSERT( fbTable.size() == number_attendees ); // Now, create another array to represent the allowed weekdays constraints // All days which are not allowed, will be marked as busy const KCalendarSystem *calSys = KGlobal::locale()->calendar(); QVector fbArray( range ); fbArray.fill( 0 ); // initialize to zero for ( int slot = 0; slot < fbArray.size(); ++slot ) { const KDateTime dateTime = begin.addSecs( slot * mSlotResolutionSeconds ); const int dayOfWeek = calSys->dayOfWeek( dateTime.date() ) - 1; // bitarray is 0 indexed if ( !mWeekdays[dayOfWeek] ) { fbArray[slot] = 1; + } else if (workingHoursOnly && !(dateTime.time() >= mWorkingHoursStart && dateTime.time() < mWorkingHoursEnd)) { + fbArray[slot] = 1; } } fbTable.append( fbArray ); // Create the composite array that will hold the sums for // each 15 minute timeslot QVector summed( range ); summed.fill( 0 ); // initialize to zero // Sum the columns of the table for ( int i = 0; i < fbTable.size(); ++i ) { for ( int j = 0; j < range; ++j ) { summed[j] += fbTable[i][j]; } } + //if we know the event duration, skipall slotswith less time for the whole meeting + if (duration > 0) { + for (int i = 0; i < range - duration; ++i ) { + if (summed[i] == 0) { + for (int j = 1; j <= duration; j++) { + if (summed[i+j] != 0) { + summed[i] = 1; + break; + } + } + } + } + } + // Finally, iterate through the composite array locating contiguous free timeslots int free_count = 0; bool free_found = false; mAvailableSlots.clear(); for ( int i = 0; i < range; ++i ) { // free timeslot encountered, increment counter if ( summed[i] == 0 ) { ++free_count; } if ( summed[i] != 0 || ( i == ( range - 1 ) && summed[i] == 0 ) ) { // current slot is not free, so push the previous free blocks // OR we are in the last slot and it is free if ( free_count > 0 ) { int free_start_i;// start index of the free block int free_end_i; // end index of the free block if ( summed[i] == 0 ) { // special case: we are on the last slot and it is free // so we want to include this slot in the free block free_start_i = i - free_count + 1; // add one, to set us back inside the array because // free_count was incremented already this iteration free_end_i = i + 1; // add one to compensate for the fact that the array is 0 indexed } else { free_start_i = i - free_count; free_end_i = i - 1 + 1; // add one to compensate for the fact that the array is 0 indexed // compiler will optmize out the -1+1, but I leave it here to make the reasoning apparent. } // convert from our timeslot interval back into to normal seconds // then calculate the date times of the free block based on // our initial timeframe const KDateTime freeBegin = begin.addSecs( free_start_i * mSlotResolutionSeconds ); const KDateTime freeEnd = freeBegin.addSecs( ( free_end_i - free_start_i ) * mSlotResolutionSeconds ); // push the free block onto the list mAvailableSlots << KCalCore::Period( freeBegin, freeEnd ); free_count = 0; if ( !free_found ) { free_found = true; } } } } if ( free_found ) { emit freeSlotsAvailable( mAvailableSlots ); } #if 0 //DEBUG, dump the arrays. very helpful for debugging QTextStream dump( stdout ); dump << " "; dump.setFieldWidth( 3 ); for ( int i = 0; i < range; ++i ) { // header dump << i; } dump.setFieldWidth( 1 ); dump << "\n\n"; for ( int i = 0; i < number_attendees; ++i ) { dump.setFieldWidth( 1 ); dump << i << ": "; dump.setFieldWidth( 3 ); for ( int j = 0; j < range; ++j ) { dump << fbTable[i][j]; } dump << "\n\n"; } dump.setFieldWidth( 1 ); dump << " "; dump.setFieldWidth( 3 ); for ( int i = 0; i < range; ++i ) { dump << summed[i]; } dump << "\n"; #endif } void ConflictResolver::calculateConflicts() { KDateTime start = mTimeframeConstraint.start(); KDateTime end = mTimeframeConstraint.end(); const int count = tryDate( start, end ); emit conflictsDetected( count ); if ( !mCalculateTimer.isActive() ) { mCalculateTimer.start( 0 ); } } void ConflictResolver::setAllowedWeekdays( const QBitArray &weekdays ) { mWeekdays = weekdays; calculateConflicts(); } void ConflictResolver::setMandatoryRoles( const QSet< KCalCore::Attendee::Role > &roles ) { mMandatoryRoles = roles; calculateConflicts(); } bool ConflictResolver::matchesRoleConstraint( const KCalCore::Attendee::Ptr &attendee ) { return mMandatoryRoles.contains( attendee->role() ); } KCalCore::Period::List ConflictResolver::availableSlots() const { return mAvailableSlots; } void ConflictResolver::setResolution( int seconds ) { mSlotResolutionSeconds = seconds; } FreeBusyItemModel *ConflictResolver::model() const { return mFBModel; } diff --git a/incidenceeditor-ng/conflictresolver.h b/incidenceeditor-ng/conflictresolver.h index d01f20215a..df822bac2a 100644 --- a/incidenceeditor-ng/conflictresolver.h +++ b/incidenceeditor-ng/conflictresolver.h @@ -1,203 +1,214 @@ /* Copyright (c) 2000,2001,2004 Cornelius Schumacher Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia Copyright (C) 2010 Casey Link 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. */ #ifndef INCIDENCEEDITOR_CONFLICTRESOLVER_H #define INCIDENCEEDITOR_CONFLICTRESOLVER_H #include "incidenceeditors-ng_export.h" #include "freebusymodel/freebusyitem.h" #include #include #include class FreeBusyItemModel; namespace IncidenceEditorNG { /** * Takes a list of attendees and event info (e.g., min time start, max time end) * fetches their freebusy information, then identifies conflicts and periods of non-conflict. * * It exposes these periods so another class can display them to the user and allow * them to choose a correct time. * @author Casey Link */ class INCIDENCEEDITORS_NG_EXPORT ConflictResolver : public QObject { Q_OBJECT public: /** * @param parentWidget is passed to Akonadi when fetching free/busy data. */ explicit ConflictResolver( QWidget *parentWidget, QObject *parent = 0 ); /** * Add an attendee * The attendees free busy info will be fetched * and integrated into the resolver. */ void insertAttendee( const KCalCore::Attendee::Ptr &attendee ); void insertAttendee( const FreeBusyItem::Ptr &freebusy ); /** * Removes an attendee * The attendee will no longer be considered when * resolving conflicts */ void removeAttendee( const KCalCore::Attendee::Ptr &attendee ); /** * Clear all attendees */ void clearAttendees(); /** * Returns whether the resolver contains the attendee */ bool containsAttendee( const KCalCore::Attendee::Ptr &attendee ); /** * Constrain the free time slot search to the weekdays * identified by their KCalendarSystem integer representation * Default is Monday - Friday * @param weekdays a 7 bit array indicating the allowed days (bit 0=Monday, value 1=allowed). * @see KCalendarSystem */ void setAllowedWeekdays( const QBitArray &weekdays ); /** * Constrain the free time slot search to the set participant roles. * Mandatory roles are considered the minimum required to attend * the meeting, so only those attendees with the mandatory roles will * be considered in the search. * Default is all roles are mandatory. * @param roles the set of mandatory participant roles */ void setMandatoryRoles( const QSet &roles ); /** * Returns a list of date time ranges that conform to the * search constraints. * @see setMandatoryRoles * @see setAllowedWeekdays */ KCalCore::Period::List availableSlots() const; /** Finds a free slot in the future which has at least the same size as the initial slot. */ bool findFreeSlot( const KCalCore::Period &dateTimeRange ); QList freeBusyItems() const; FreeBusyItemModel *model() const; + bool workingHoursOnly() const; + signals: /** * Emitted when the user changes the start and end dateTimes * for the incidence. */ void dateTimesChanged( const KDateTime & newStart, const KDateTime & newEnd ); /** * Emitted when there are conflicts * @param number the number of conflicts */ void conflictsDetected( int number ); /** * Emitted when the resolver locates new free slots. */ void freeSlotsAvailable( const KCalCore::Period::List & ); public slots: /** * Set the timeframe constraints * * These control the timeframe for which conflicts are to be resolved. */ void setEarliestDate( const QDate &newDate ); void setEarliestTime( const QTime &newTime ); void setLatestDate( const QDate &newDate ); void setLatestTime( const QTime &newTime ); void setEarliestDateTime( const KDateTime &newDateTime ); void setLatestDateTime( const KDateTime &newDateTime ); + void setWorkingHours(QTime start, QTime end); + void setWorkingHoursOnly(bool workingTime); + void setDuration(int duration); //Duration of the event + void freebusyDataChanged(); void findAllFreeSlots(); void setResolution( int seconds ); private: /** Checks whether the slot specified by (tryFrom, tryTo) matches the search constraints. If yes, return true. The return value is the number of conflicts that were detected, and (tryFrom, tryTo) contain the next free slot for that participant. In other words, the returned slot does not have to be free for everybody else. */ int tryDate( KDateTime &tryFrom, KDateTime &tryTo ); /** Checks whether the slot specified by (tryFrom, tryTo) is available for the participant with specified fb. If yes, return true. If not, return false and change (tryFrom, tryTo) to contain the next possible slot for this participant (not necessarily a slot that is available for all participants). */ bool tryDate( const KCalCore::FreeBusy::Ptr &fb, KDateTime &tryFrom, KDateTime &tryTo ); /** * Checks whether the supplied attendee passes the * current mandatory role constraint. * @return true if the attendee is of one of the mandatory roles, false if not */ bool matchesRoleConstraint( const KCalCore::Attendee::Ptr &attendee ); void calculateConflicts(); KCalCore::Period mTimeframeConstraint; //!< the datetime range for outside of which //free slots won't be searched. KCalCore::Period::List mAvailableSlots; QTimer mCalculateTimer; //!< A timer is used control the calculation of conflicts // to prevent the process from being repeated many times // after a series of quick parameter changes. FreeBusyItemModel *mFBModel; QWidget *mParentWidget; QSet mMandatoryRoles; QBitArray mWeekdays; //!< a 7 bit array indicating the allowed days //(bit 0 = Monday, value 1 = allowed). int mSlotResolutionSeconds; + + bool mWorkingHoursOnly; //!< Search only in working times + int mDuration; //!< Duration of event to schedule + QTime mWorkingHoursStart; + QTime mWorkingHoursEnd; }; } #endif diff --git a/incidenceeditor-ng/incidenceattendee.cpp b/incidenceeditor-ng/incidenceattendee.cpp index d950603a1d..3512f7527a 100644 --- a/incidenceeditor-ng/incidenceattendee.cpp +++ b/incidenceeditor-ng/incidenceattendee.cpp @@ -1,1065 +1,1070 @@ /* Copyright (C) 2010 Casey Link Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company Based on old attendeeeditor.cpp: Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Volker Krause 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 "incidenceattendee.h" #include "attendeeeditor.h" #include "conflictresolver.h" #include "editorconfig.h" #include "incidencedatetime.h" #include "schedulingdialog.h" #include "attendeecomboboxdelegate.h" #include "freebusymodel/freebusyitemmodel.h" +#include #ifdef KDEPIM_MOBILE_UI #include "ui_dialogmoremobile.h" #else #include "ui_dialogdesktop.h" #endif #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; #ifdef KDEPIM_MOBILE_UI IncidenceAttendee::IncidenceAttendee( QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoMore *ui ) #else IncidenceAttendee::IncidenceAttendee( QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui ) #endif : mUi( ui ), mParentWidget( parent ), mStateDelegate(new AttendeeComboBoxDelegate(this)), mRoleDelegate(new AttendeeComboBoxDelegate(this)), mResponseDelegate(new AttendeeComboBoxDelegate(this)), mConflictResolver( 0 ), mDateTime( dateTime ) { KCalCore::Attendee::List attendees; KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee("", "")); attendees.append(attendee); mDataModel = new AttendeeTableModel(attendees, this); mDataModel->setKeepEmpty(true); mDataModel->setRemoveEmptyLines(true); #ifdef KDEPIM_MOBILE_UI mRoleDelegate->addItem(DesktopIcon("meeting-participant", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::ReqParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-participant-optional", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::OptParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-observer", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::NonParticipant)); mRoleDelegate->addItem(DesktopIcon("meeting-chair", 48), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::Chair)); mResponseDelegate->addItem( DesktopIcon( "meeting-participant-request-response", 48 ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseDelegate->addItem( DesktopIcon( "meeting-participant-no-response", 48 ), i18nc( "@item:inlistbox", "Request No Response" ) ); #else mRoleDelegate->addItem(SmallIcon("meeting-participant"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::ReqParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-participant-optional"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::OptParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-observer"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::NonParticipant)); mRoleDelegate->addItem(SmallIcon("meeting-chair"), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::Chair)); mResponseDelegate->addItem( SmallIcon( "meeting-participant-request-response" ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseDelegate->addItem( SmallIcon( "meeting-participant-no-response" ), i18nc( "@item:inlistbox", "Request No Response" ) ); #endif mStateDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits the current attendance status of the attendee." ) ); mRoleDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits the role of the attendee." ) ); mResponseDelegate->setToolTip( i18nc( "@info:tooltip", "Request a response from the attendee" ) ); mResponseDelegate->setWhatsThis( i18nc( "@info:whatsthis", "Edits whether to send an email to the " "attendee to request a response concerning " "attendance." ) ); setObjectName( "IncidenceAttendee" ); AttendeeFilterProxyModel *filterProxyModel = new AttendeeFilterProxyModel(this); filterProxyModel->setDynamicSortFilter(true); filterProxyModel->setSourceModel(mDataModel); #ifdef KDEPIM_MOBILE_UI #else connect(mUi->mGroupSubstitution, SIGNAL(clicked(bool)), SLOT(slotGroupSubstitutionPressed())); mUi->mAttendeeTable->setModel(filterProxyModel); mAttendeeDelegate = new AttendeeLineEditDelegate(this); mAttendeeDelegate->setCompletionMode( KGlobalSettings::self()->completionMode() ); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate()); #endif mUi->mOrganizerStack->setCurrentIndex( 0 ); fillOrganizerCombo(); mUi->mSolveButton->setEnabled( false ); mUi->mOrganizerLabel->setVisible( false ); mConflictResolver = new ConflictResolver( parent, parent ); mConflictResolver->setEarliestDateTime( mDateTime->currentStartDateTime() ); mConflictResolver->setLatestDateTime( mDateTime->currentEndDateTime() ); + EventViews::Prefs *eventviewsPrefs = EventViews::Prefs::instance(); + mConflictResolver->setWorkingHours(eventviewsPrefs->workingHoursStart().time(), eventviewsPrefs->workingHoursEnd().time()); + connect( mUi->mSelectButton, SIGNAL(clicked(bool)), this, SLOT(slotSelectAddresses()) ); connect( mUi->mSolveButton, SIGNAL(clicked(bool)), this, SLOT(slotSolveConflictPressed()) ); /* Added as part of kolab/issue2297, which is currently under review connect( mUi->mOrganizerCombo, SIGNAL(activated(QString)), this, SLOT(slotOrganizerChanged(QString)) ); */ connect( mUi->mOrganizerCombo, SIGNAL(currentIndexChanged(int)), SLOT(checkDirtyStatus()) ); connect( mDateTime, SIGNAL(startDateChanged(QDate)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(endDateChanged(QDate)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(startTimeChanged(QTime)), this, SLOT(slotEventDurationChanged()) ); connect( mDateTime, SIGNAL(endTimeChanged(QTime)), this, SLOT(slotEventDurationChanged()) ); connect( mConflictResolver, SIGNAL(conflictsDetected(int)), this, SLOT(slotUpdateConflictLabel(int)) ); connect( mConflictResolver->model(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(slotFreeBusyAdded(const QModelIndex&, int, int)) ); connect(mConflictResolver->model(), SIGNAL(layoutChanged()), SLOT(updateFBStatus())); connect(mConflictResolver->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotFreeBusyChanged(const QModelIndex&, const QModelIndex&))); slotUpdateConflictLabel( 0 ); //initialize label // confict resolver (should show also resources) connect(mDataModel, SIGNAL(layoutChanged()), SLOT(slotConflictResolverLayoutChanged())); connect(mDataModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int , int)), SLOT(slotConflictResolverAttendeeRemoved(const QModelIndex&,int,int))); connect(mDataModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(slotConflictResolverAttendeeAdded(const QModelIndex&,int,int))); connect(mDataModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotConflictResolverAttendeeChanged(const QModelIndex&, const QModelIndex&))); //Group substitution connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(slotGroupSubstitutionLayoutChanged())); connect(filterProxyModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int , int)), SLOT(slotGroupSubstitutionAttendeeRemoved(const QModelIndex&,int,int))); connect(filterProxyModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(slotGroupSubstitutionAttendeeAdded(const QModelIndex&,int,int))); connect(filterProxyModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(slotGroupSubstitutionAttendeeChanged(const QModelIndex&, const QModelIndex&))); connect(filterProxyModel, SIGNAL(rowsInserted(const QModelIndex&, int , int)), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(rowsRemoved(const QModelIndex&, int , int)), SLOT(updateCount())); // only update when FullName is changed connect(filterProxyModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(updateCount())); connect(filterProxyModel, SIGNAL(layoutChanged()), SLOT(filterLayoutChanged())); } IncidenceAttendee::~IncidenceAttendee() { } void IncidenceAttendee::load( const KCalCore::Incidence::Ptr &incidence ) { mLoadedIncidence = incidence; if ( iAmOrganizer() || incidence->organizer()->isEmpty() ) { mUi->mOrganizerStack->setCurrentIndex( 0 ); int found = -1; const QString fullOrganizer = incidence->organizer()->fullName(); const QString organizerEmail = incidence->organizer()->email(); for ( int i = 0; i < mUi->mOrganizerCombo->count(); ++i ) { KCalCore::Person::Ptr organizerCandidate = KCalCore::Person::fromFullName( mUi->mOrganizerCombo->itemText( i ) ); if ( organizerCandidate->email() == organizerEmail ) { found = i; mUi->mOrganizerCombo->setCurrentIndex( i ); break; } } if ( found < 0 && !fullOrganizer.isEmpty() ) { mUi->mOrganizerCombo->insertItem( 0, fullOrganizer ); mUi->mOrganizerCombo->setCurrentIndex( 0 ); } mUi->mOrganizerLabel->setVisible( false ); } else { // someone else is the organizer mUi->mOrganizerStack->setCurrentIndex( 1 ); mUi->mOrganizerLabel->setText( incidence->organizer()->fullName() ); mUi->mOrganizerLabel->setVisible( true ); } KCalCore::Attendee::List attendees; foreach(const KCalCore::Attendee::Ptr &a, incidence->attendees()) { attendees << KCalCore::Attendee::Ptr(new KCalCore::Attendee(*a)); } mDataModel->setAttendees(attendees); slotUpdateConflictLabel(0); setActions( incidence->type() ); mWasDirty = false; QTimer::singleShot(1, this, SLOT(slotEventDurationChanged())); } void IncidenceAttendee::save( const KCalCore::Incidence::Ptr &incidence ) { incidence->clearAttendees(); KCalCore::Attendee::List attendees = mDataModel->attendees(); foreach(KCalCore::Attendee::Ptr attendee, attendees) { Q_ASSERT(attendee); bool skip = false; if (attendee->fullName().isEmpty()) { continue; } if (KPIMUtils::isValidAddress(attendee->email())) { if (KMessageBox::warningYesNo( 0, i18nc("@info", "%1 does not look like a valid email address. " "Are you sure you want to invite this participant?", attendee->email()), i18nc("@title:window", "Invalid Email Address")) != KMessageBox::Yes) { skip = true; } } if (!skip) { incidence->addAttendee(attendee); } } // Must not have an organizer for items without attendees if (!incidence->attendeeCount()) { return; } if ( mUi->mOrganizerStack->currentIndex() == 0 ) { incidence->setOrganizer( mUi->mOrganizerCombo->currentText() ); } else { incidence->setOrganizer( mUi->mOrganizerLabel->text() ); } } bool IncidenceAttendee::isDirty() const { if ( iAmOrganizer() ) { KCalCore::Event tmp; tmp.setOrganizer( mUi->mOrganizerCombo->currentText() ); if ( mLoadedIncidence->organizer()->email() != tmp.organizer()->email() ) { kDebug() << "Organizer changed. Old was " << mLoadedIncidence->organizer()->name() << mLoadedIncidence->organizer()->email() << "; new is " << tmp.organizer()->name() << tmp.organizer()->email(); return true; } } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; foreach(KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // The lists sizes *must* be the same. When the organizer is attending the // event as well, he should be in the attendees list as well. if (originalList.size() != newList.size()) { return true; } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach(const KCalCore::Attendee::Ptr &attendee, originalList) { bool found = false; for (int i = 0; i < newList.count(); ++i) { if (*(newList[i]) == *attendee) { newList.remove(i); found = true; break; } } if (!found) { // One of the attendees in the original list was not found in the new list. return true; } } return false; } void IncidenceAttendee::changeStatusForMe( KCalCore::Attendee::PartStat stat ) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); Q_ASSERT( config ); for (int i=0;irowCount();i++) { QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email); if ( config->thatIsMe( mDataModel->data(index, Qt::DisplayRole).toString() ) ) { index = mDataModel->index(i, AttendeeTableModel::Status); mDataModel->setData(index, stat); break; } } checkDirtyStatus(); } void IncidenceAttendee::acceptForMe() { changeStatusForMe( KCalCore::Attendee::Accepted ); } void IncidenceAttendee::declineForMe() { changeStatusForMe( KCalCore::Attendee::Declined ); } void IncidenceAttendee::fillOrganizerCombo() { mUi->mOrganizerCombo->clear(); const QStringList lst = IncidenceEditorNG::EditorConfig::instance()->fullEmails(); QStringList uniqueList; for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { if ( !uniqueList.contains( *it ) ) { uniqueList << *it; } } mUi->mOrganizerCombo->addItems( uniqueList ); } void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalCore::Attendee::Ptr attendee ) { QString fullname = attendee->fullName(); // stop old job KJob *oldJob = mMightBeGroupJobs.key(attendee); if ( oldJob != 0 ) { disconnect(oldJob); oldJob->deleteLater(); mMightBeGroupJobs.remove(oldJob); } mGroupList.remove(attendee); if (!fullname.isEmpty()) { Akonadi::ContactGroupSearchJob *job = new Akonadi::ContactGroupSearchJob(); job->setQuery( Akonadi::ContactGroupSearchJob::Name, fullname); connect( job, SIGNAL(result(KJob*)), this, SLOT(groupSearchResult(KJob*)) ); mMightBeGroupJobs.insert( job, attendee ); } } void IncidenceAttendee::groupSearchResult( KJob *job ) { Akonadi::ContactGroupSearchJob *searchJob = qobject_cast( job ); Q_ASSERT(searchJob); Q_ASSERT(mMightBeGroupJobs.contains(job)); KCalCore::Attendee::Ptr attendee = mMightBeGroupJobs.take(job); const KABC::ContactGroup::List contactGroups = searchJob->contactGroups(); if ( contactGroups.isEmpty() ) { updateGroupExpand(); return; // Nothing todo, probably a normal email address was entered } // TODO: Give the user the possibility to choose a group when there is more than one?! KABC::ContactGroup group = contactGroups.first(); int row = dataModel()->attendees().indexOf(attendee); QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType); dataModel()->setData(index, KCalCore::Attendee::Group); mGroupList.insert(attendee, group); updateGroupExpand(); } void IncidenceAttendee::updateGroupExpand() { #ifndef KDEPIM_MOBILE_UI mUi->mGroupSubstitution->setEnabled(mGroupList.count() > 0); #endif } void IncidenceAttendee::slotGroupSubstitutionPressed() { foreach (KCalCore::Attendee::Ptr attendee, mGroupList.keys()) { Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob( mGroupList.value(attendee), this ); connect( expandJob, SIGNAL(result(KJob*)), this, SLOT(expandResult(KJob*)) ); mExpandGroupJobs.insert(expandJob, attendee); expandJob->start(); } } void IncidenceAttendee::expandResult( KJob *job ) { #ifndef KDEPIM_MOBILE_UI Akonadi::ContactGroupExpandJob *expandJob = qobject_cast( job ); Q_ASSERT( expandJob ); bool replace = mExpandGroupJobs.contains(job); KCalCore::Attendee::Ptr attendee; int row = dataModel()->attendees().size() - 1; if (replace){ attendee = mExpandGroupJobs.take(job); row = dataModel()->attendees().indexOf(attendee); dataModel()->removeRow(row); } else { attendee = KCalCore::Attendee::Ptr(new KCalCore::Attendee(QString(),QString())); } const KABC::Addressee::List groupMembers = expandJob->contacts(); foreach ( const KABC::Addressee &member, groupMembers ) { KCalCore::Attendee::Ptr newAt(new KCalCore::Attendee(member.realName(), member.preferredEmail(), attendee->RSVP(), attendee->status(), attendee->role(), member.uid())); dataModel()->insertAttendee(row, newAt); } #endif } void IncidenceAttendee::slotSelectAddresses() { QWeakPointer dialog( new Akonadi::EmailAddressSelectionDialog( ) ); dialog.data()->view()->view()->setSelectionMode( QAbstractItemView::ExtendedSelection ); if ( dialog.data()->exec() == QDialog::Accepted ) { Akonadi::EmailAddressSelectionDialog *dialogPtr = dialog.data(); if ( dialogPtr ) { const Akonadi::EmailAddressSelection::List list = dialogPtr->selectedAddresses(); foreach ( const Akonadi::EmailAddressSelection &selection, list ) { if ( selection.item().hasPayload() ) { Akonadi::ContactGroupExpandJob *job = new Akonadi::ContactGroupExpandJob( selection.item().payload(), this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(expandResult(KJob*)) ); job->start(); } else { KABC::Addressee contact; contact.setName( selection.name() ); contact.insertEmail( selection.email() ); if ( selection.item().hasPayload() ) { contact.setUid( selection.item().payload().uid() ); } insertAttendeeFromAddressee( contact ); } } } else { kDebug() << "dialog was already deleted"; } } if ( dialog.data() ) { dialog.data()->deleteLater(); } } void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed() { const int duration = mDateTime->startTime().secsTo( mDateTime->endTime() ); + mConflictResolver->setDuration(duration); QScopedPointer dialog( new SchedulingDialog( mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget ) ); dialog->slotUpdateIncidenceStartEnd( mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime() ); if ( dialog->exec() == KDialog::Accepted ) { kDebug () << dialog->selectedStartDate() << dialog->selectedStartTime(); mDateTime->setStartDate( dialog->selectedStartDate() ); mDateTime->setStartTime( dialog->selectedStartTime() ); } } void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { #ifndef KDEPIM_MOBILE_UI if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); if (mConflictResolver->containsAttendee(attendee)) { mConflictResolver->removeAttendee(attendee); } if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } } checkDirtyStatus(); #endif } void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee( dataModel()->data(email, AttendeeTableModel::AttendeeRole).value() ); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value()); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverLayoutChanged() { KCalCore::Attendee::List attendees = mDataModel->attendees(); mConflictResolver->clearAttendees(); foreach(KCalCore::Attendee::Ptr attendee, attendees) { if (!attendee->email().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } checkDirtyStatus(); } void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last) { // We are only interested in toplevel changes if (parent.isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = first; i <= last; i++) { QModelIndex index = model->index(i, 0, parent); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { // We are only interested in toplevel changes if (topLeft.parent().isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus() { QAbstractItemModel *model = mConflictResolver->model(); for (int i = 0; i < model->rowCount(); i++) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus(const KCalCore::Attendee::Ptr &attendee, const KCalCore::FreeBusy::Ptr &fb) { KCalCore::Attendee::List attendees = mDataModel->attendees(); KDateTime startTime = mDateTime->currentStartDateTime(); KDateTime endTime = mDateTime->currentEndDateTime(); QAbstractItemModel *model = mConflictResolver->model(); if (attendees.contains(attendee)) { int row = dataModel()->attendees().indexOf(attendee); QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available); if (fb) { KCalCore::Period::List busyPeriods = fb->busyPeriods(); for ( KCalCore::Period::List::Iterator it = busyPeriods.begin(); it != busyPeriods.end(); ++it ) { // periods started before and laping into the incidence (s < startTime && e >= startTime) // periods starting in the time of incidende (s >= startTime && s <= endTime) if ( ((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime) ) { switch (attendee->status()) { case KCalCore::Attendee::Accepted: dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted); return; default: dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy); return; } } } dataModel()->setData(attendeeIndex, AttendeeTableModel::Free); } else { dataModel()->setData(attendeeIndex, AttendeeTableModel::Unkown); } } } void IncidenceAttendee::slotUpdateConflictLabel( int count ) { kDebug() << "slotUpdateConflictLabel"; if ( attendeeCount() > 0 ) { mUi->mSolveButton->setEnabled( true ); if ( count > 0 ) { QString label = i18ncp( "@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count ); mUi->mConflictsLabel->setText( label ); mUi->mConflictsLabel->setVisible( true ); } else { mUi->mConflictsLabel->setVisible( false ); } } else { mUi->mSolveButton->setEnabled( false ); mUi->mConflictsLabel->setVisible( false ); } } void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; i++) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); KJob *job = mMightBeGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mMightBeGroupJobs.remove(job); } job = mExpandGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mExpandGroupJobs.remove(job); } mGroupList.remove(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionLayoutChanged() { foreach(KJob *job, mMightBeGroupJobs.keys()) { disconnect(job); job->deleteLater(); } foreach(KJob *job, mExpandGroupJobs.keys()) { disconnect(job); job->deleteLater(); } mMightBeGroupJobs.clear(); mExpandGroupJobs.clear(); mGroupList.clear(); #ifndef KDEPIM_MOBILE_UI QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model ) { return; } for(int i=0;i< model->rowCount(QModelIndex());i++) { QModelIndex index = model->index(i,AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); #endif } bool IncidenceAttendee::iAmOrganizer() const { if ( mLoadedIncidence ) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); return config->thatIsMe( mLoadedIncidence->organizer()->email() ); } return true; } void IncidenceAttendee::insertAttendeeFromAddressee( const KABC::Addressee &a, int pos/*=-1*/) { const bool sameAsOrganizer = mUi->mOrganizerCombo && KPIMUtils::compareEmail( a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false ); KCalCore::Attendee::PartStat partStat = KCalCore::Attendee::NeedsAction; bool rsvp = true; if ( iAmOrganizer() && sameAsOrganizer ) { partStat = KCalCore::Attendee::Accepted; rsvp = false; } KCalCore::Attendee::Ptr newAt( new KCalCore::Attendee( a.realName(), a.preferredEmail(), rsvp, partStat, KCalCore::Attendee::ReqParticipant, a.uid() ) ); if (pos < 0 ) { pos = dataModel()->rowCount() - 1; } dataModel()->insertAttendee(pos, newAt); } void IncidenceAttendee::slotEventDurationChanged() { const KDateTime start = mDateTime->currentStartDateTime(); const KDateTime end = mDateTime->currentEndDateTime(); if ( start >= end ) { // This can happen, especially for todos. return; } mConflictResolver->setEarliestDateTime( start ); mConflictResolver->setLatestDateTime( end ); updateFBStatus(); } void IncidenceAttendee::slotOrganizerChanged( const QString &newOrganizer ) { if ( KPIMUtils::compareEmail( newOrganizer, mOrganizer, false ) ) { return; } QString name; QString email; bool success = KPIMUtils::extractEmailAddressAndName( newOrganizer, email, name ); if ( !success ) { kWarning() << "Could not extract email address and name"; return; } int currentOrganizerAttendee = -1; int newOrganizerAttendee = -1; for(int i=0; irowCount(); i++) { QModelIndex index = mDataModel->index(i,AttendeeTableModel::FullName); QString fullName = mDataModel->data(index,Qt::DisplayRole).toString(); if ( fullName == mOrganizer ) { currentOrganizerAttendee = i; } if ( fullName == newOrganizer ) { newOrganizerAttendee = i; } } int answer = KMessageBox::No; if ( currentOrganizerAttendee > -1) { answer = KMessageBox::questionYesNo( mParentWidget, i18nc( "@option", "You are changing the organizer of this event. " "Since the organizer is also attending this event, would you " "like to change the corresponding attendee as well?" ) ); } else { answer = KMessageBox::Yes; } if ( answer == KMessageBox::Yes ) { if ( currentOrganizerAttendee > -1 ) { mDataModel->removeRows(currentOrganizerAttendee,1); } if ( newOrganizerAttendee == -1 ) { bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp. KCalCore::Attendee::PartStat status = iAmOrganizer() ? KCalCore::Attendee::Accepted : KCalCore::Attendee::NeedsAction; KCalCore::Attendee::Ptr newAt( new KCalCore::Attendee( name, email, rsvp, status, KCalCore::Attendee::ReqParticipant ) ); mDataModel->insertAttendee(mDataModel->rowCount(), newAt); } } mOrganizer = newOrganizer; } AttendeeTableModel* IncidenceAttendee::dataModel() { return mDataModel; } AttendeeComboBoxDelegate* IncidenceAttendee::responseDelegate() { return mResponseDelegate; } AttendeeComboBoxDelegate* IncidenceAttendee::roleDelegate() { return mRoleDelegate; } AttendeeComboBoxDelegate* IncidenceAttendee::stateDelegate() { return mStateDelegate; } AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() { return mAttendeeDelegate; } void IncidenceAttendee::filterLayoutChanged() { #ifndef KDEPIM_MOBILE_UI QHeaderView* headerView = mUi->mAttendeeTable->horizontalHeader(); headerView->setResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents); headerView->setResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch); headerView->setResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents); headerView->setResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents); headerView->setSectionHidden(AttendeeTableModel::CuType, true); headerView->setSectionHidden(AttendeeTableModel::Name, true); headerView->setSectionHidden(AttendeeTableModel::Email, true); headerView->setSectionHidden(AttendeeTableModel::Available, true); #endif } void IncidenceAttendee::updateCount() { emit attendeeCountChanged(attendeeCount()); checkDirtyStatus(); } int IncidenceAttendee::attendeeCount() const { #ifndef KDEPIM_MOBILE_UI int c=0; QModelIndex index; QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model ) { return 0; } for(int i=0;i< model->rowCount(QModelIndex());i++) { index = model->index(i,AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { c++; } } return c; #endif return 0; } void IncidenceAttendee::setActions( KCalCore::Incidence::IncidenceType actions ) { mStateDelegate->clear(); if ( actions == KCalCore::Incidence::TypeEvent ) { #ifdef KDEPIM_MOBILE_UI mStateDelegate->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #else mStateDelegate->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #endif } else { #ifdef KDEPIM_MOBILE_UI mStateDelegate->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateDelegate->addItem( DesktopIcon( "task-complete", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateDelegate->addItem( DesktopIcon( "task-ongoing", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #else mStateDelegate->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateDelegate->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateDelegate->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateDelegate->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateDelegate->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateDelegate->addItem( SmallIcon( "task-complete" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateDelegate->addItem( SmallIcon( "task-ongoing" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #endif } } void IncidenceAttendee::printDebugInfo() const { kDebug() << "I'm organizer : " << iAmOrganizer(); kDebug() << "Loaded organizer: "<< mLoadedIncidence->organizer()->email(); if ( iAmOrganizer() ) { KCalCore::Event tmp; tmp.setOrganizer( mUi->mOrganizerCombo->currentText() ); kDebug() << "Organizer combo: " << tmp.organizer()->email(); } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; foreach(KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach ( const KCalCore::Attendee::Ptr &attendee, originalList ) { bool found = false; for ( int i = 0; i < newList.count(); ++i ) { if ( newList[i] == attendee ) { newList.remove(i); found = true; break; } } if ( !found ) { kDebug() << "Attendee not found: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator() << "; we have:"; for ( int i = 0; i < newList.count(); ++i ) { KCalCore::Attendee::Ptr attendee = newList[i]; kDebug() << "Attendee: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator(); } return; } } } \ No newline at end of file diff --git a/incidenceeditor-ng/schedulingdialog.cpp b/incidenceeditor-ng/schedulingdialog.cpp index 25cc208c54..916aa446ea 100644 --- a/incidenceeditor-ng/schedulingdialog.cpp +++ b/incidenceeditor-ng/schedulingdialog.cpp @@ -1,240 +1,243 @@ /* Copyright (C) 2010 Casey Link Copyright (C) 2009-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 "schedulingdialog.h" #include "conflictresolver.h" #include "freebusymodel/freeperiodmodel.h" #ifndef KDEPIM_MOBILE_UI #include "visualfreebusywidget.h" #endif #include #include using namespace IncidenceEditorNG; SchedulingDialog::SchedulingDialog( const QDate &startDate, const QTime &startTime, int duration, ConflictResolver *resolver, QWidget *parent ) : KDialog( parent ), mResolver( resolver ), mPeriodModel( new FreePeriodModel( this ) ) { QWidget *w = new QWidget( this ); setupUi( w ); setMainWidget( w ); fillCombos(); Q_ASSERT( duration > 0 ); mDuration = duration; #ifndef KDEPIM_MOBILE_UI mVisualWidget = new VisualFreeBusyWidget( resolver->model(), 8, this ); QVBoxLayout *ganttlayout = new QVBoxLayout( mGanttTab ); mGanttTab->setLayout( ganttlayout ); ganttlayout->addWidget( mVisualWidget ); #endif connect( mStartDate, SIGNAL(dateEdited(QDate)), mResolver, SLOT(setEarliestDate(QDate)) ); connect( mStartTime, SIGNAL(timeEdited(QTime)), mResolver, SLOT(setEarliestTime(QTime)) ); connect( mEndDate, SIGNAL(dateEdited(QDate)), mResolver, SLOT(setLatestDate(QDate)) ); connect( mEndTime, SIGNAL(timeEdited(QTime)), mResolver, SLOT(setLatestTime(QTime)) ); + connect(mSearchOnlyWorkingTime, SIGNAL(toggled(bool)), + mResolver, SLOT(setWorkingHoursOnly(bool))); connect( mStartDate, SIGNAL(dateEdited(QDate)), this, SLOT(slotStartDateChanged(QDate)) ); connect( mWeekdayCombo, SIGNAL(checkedItemsChanged(QStringList)), SLOT(slotWeekdaysChanged()) ); connect( mWeekdayCombo, SIGNAL(checkedItemsChanged(QStringList)), SLOT(slotMandatoryRolesChanged()) ); connect( mResolver, SIGNAL(freeSlotsAvailable(KCalCore::Period::List)), mPeriodModel, SLOT(slotNewFreePeriods(KCalCore::Period::List)) ); connect( mMoveBeginTimeEdit, SIGNAL(timeEdited(QTime)), this, SLOT(slotSetEndTimeLabel(QTime)) ); mTableView->setModel( mPeriodModel ); connect( mTableView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), this, SLOT(slotRowSelectionChanged(QModelIndex,QModelIndex)) ); mStartDate->setDate( startDate ); mEndDate->setDate( mStartDate->date().addDays( 7 ) ); mStartTime->setTime( startTime ); mEndTime->setTime( startTime ); mResolver->setEarliestDate( mStartDate->date() ); mResolver->setEarliestTime( mStartTime->time() ); mResolver->setLatestDate( mEndDate->date() ); mResolver->setLatestTime( mEndTime->time() ); + mSearchOnlyWorkingTime->setChecked(mResolver->workingHoursOnly()); mMoveApptGroupBox->hide(); } SchedulingDialog::~SchedulingDialog() { } void SchedulingDialog::slotUpdateIncidenceStartEnd( const KDateTime &startDateTime, const KDateTime &endDateTime ) { #ifdef KDEPIM_MOBILE_UI Q_UNUSED( startDateTime ); Q_UNUSED( endDateTime ); #else mVisualWidget->slotUpdateIncidenceStartEnd( startDateTime, endDateTime ); #endif } void SchedulingDialog::fillCombos() { // Note: we depend on the following order #ifdef KDEPIM_MOBILE_UI mRolesCombo->addItem( DesktopIcon( "meeting-participant", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::ReqParticipant ) ); mRolesCombo->addItem( DesktopIcon( "meeting-participant-optional", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::OptParticipant ) ); mRolesCombo->addItem( DesktopIcon( "meeting-observer", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::NonParticipant ) ); mRolesCombo->addItem( DesktopIcon( "meeting-chair", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::Chair ) ); #else mRolesCombo->addItem( SmallIcon( "meeting-participant" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::ReqParticipant ) ); mRolesCombo->addItem( SmallIcon( "meeting-participant-optional" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::OptParticipant ) ); mRolesCombo->addItem( SmallIcon( "meeting-observer" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::NonParticipant ) ); mRolesCombo->addItem( SmallIcon( "meeting-chair" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::Chair ) ); #endif mRolesCombo->setWhatsThis( i18nc( "@info:whatsthis", "Edits the role of the attendee." ) ); mRolesCombo->setItemCheckState( 0, Qt::Checked ); mRolesCombo->setItemCheckState( 1, Qt::Checked ); mRolesCombo->setItemCheckState( 2, Qt::Checked ); mRolesCombo->setItemCheckState( 3, Qt::Checked ); QBitArray days( 7 ); days.setBit( 0 ); //Monday days.setBit( 1 ); //Tuesday days.setBit( 2 ); //Wednesday days.setBit( 3 ); //Thursday days.setBit( 4 ); //Friday.. surprise! mWeekdayCombo->setDays( days ); mResolver->setAllowedWeekdays( days ); } void SchedulingDialog::slotStartDateChanged( const QDate &newDate ) { QDate oldDate = mStDate; mStDate = newDate; if ( newDate.isValid() && oldDate.isValid() ) { updateWeekDays( oldDate ); } } void SchedulingDialog::updateWeekDays( const QDate &oldDate ) { const int oldStartDayIndex = mWeekdayCombo->weekdayIndex( oldDate ); const int newStartDayIndex = mWeekdayCombo->weekdayIndex( mStDate ); mWeekdayCombo->setItemCheckState( oldStartDayIndex, Qt::Unchecked ); mWeekdayCombo->setItemEnabled( oldStartDayIndex, true ); mWeekdayCombo->setItemCheckState( newStartDayIndex, Qt::Checked ); mWeekdayCombo->setItemEnabled( newStartDayIndex, false ); } void SchedulingDialog::slotWeekdaysChanged() { // notify the resolver mResolver->setAllowedWeekdays( mWeekdayCombo->days() ); } void SchedulingDialog::slotMandatoryRolesChanged() { QSet roles; for ( int i = 0; i < mRolesCombo->count(); ++i ) { if ( mRolesCombo->itemCheckState( i ) == Qt::Checked ) { roles << KCalCore::Attendee::Role( i ); } } mResolver->setMandatoryRoles( roles ); } void SchedulingDialog::slotRowSelectionChanged( const QModelIndex ¤t, const QModelIndex &previous ) { Q_UNUSED( previous ); if ( !current.isValid() ) { mMoveApptGroupBox->hide(); return; } KCalCore::Period period = current.data( FreePeriodModel::PeriodRole ).value(); const QDate startDate = period.start().date(); const KCalendarSystem *calSys = KGlobal::locale()->calendar(); const int dayOfWeek = calSys->dayOfWeek( startDate ); const QString dayLabel = ki18nc( "@label Day of week followed by day of the month, then the month. " "Example: Monday, 12 June", "%1, %2 %3" ). subs( calSys->weekDayName( dayOfWeek, KCalendarSystem::LongDayName ) ). subs( startDate.day() ). subs( calSys->monthName( startDate ) ).toString(); mMoveDayLabel->setText( dayLabel ); mMoveBeginTimeEdit->setTimeRange( period.start().time(), period.end().addSecs( -mDuration ).time() ); mMoveBeginTimeEdit->setTime( period.start().time() ); slotSetEndTimeLabel( period.start().time() ); mMoveApptGroupBox->show(); mSelectedDate = startDate; } void SchedulingDialog::slotSetEndTimeLabel( const QTime &startTime ) { const QTime endTime = startTime.addSecs( mDuration ); const QString endTimeLabel = ki18nc( "@label This is a suffix following a time selecting widget. " "Example: [timeedit] to 10:00am", "to %1" ).subs( KGlobal::locale()->formatTime( endTime ) ).toString(); mMoveEndTimeLabel->setText( endTimeLabel ); mSelectedTime = startTime; } QDate SchedulingDialog::selectedStartDate() const { return mSelectedDate; } QTime SchedulingDialog::selectedStartTime() const { return mSelectedTime; } diff --git a/incidenceeditor-ng/schedulingdialog.ui b/incidenceeditor-ng/schedulingdialog.ui index c5f0f9a7e7..13a2462cfb 100644 --- a/incidenceeditor-ng/schedulingdialog.ui +++ b/incidenceeditor-ng/schedulingdialog.ui @@ -1,282 +1,296 @@ Dialog 0 0 558 502 Schedule a time QFormLayout::ExpandingFieldsGrow Earliest time to start: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Latest time to end: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Check the weekdays to include in the search. Only the checked weekdays will be included in the free time slot search. Allowed weekdays: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Check the weekdays to include in the search. Only the checked weekdays will be included in the free time slot search. Check the roles to include in the search. Only the participants with the checked roles will be considered in the free time slot search. Mandatory roles: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Check the roles to include in the search. Only the participants with the checked roles will be considered in the free time slot search. + + + + + + + + + + + Seach only in working time + + + 0 Automatic Search true QAbstractItemView::SingleSelection QAbstractItemView::SelectRows false false true false false Visual Search Move your appointment to: Qt::Horizontal 40 20 Monday, 12th June Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter to 10:00am Qt::Horizontal 40 20 KTimeComboBox QComboBox
ktimecombobox.h
KPIM::KCheckComboBox KComboBox
libkdepim/widgets/kcheckcombobox.h
KDateComboBox QComboBox
kdatecombobox.h
KComboBox QComboBox
kcombobox.h
KTabWidget QTabWidget
ktabwidget.h
1
KPIM::KWeekdayCheckCombo KPIM::KCheckComboBox
libkdepim/widgets/kweekdaycheckcombo.h
diff --git a/incidenceeditor-ng/tests/conflictresolvertest.cpp b/incidenceeditor-ng/tests/conflictresolvertest.cpp index 93b9f467b7..8b946d9810 100644 --- a/incidenceeditor-ng/tests/conflictresolvertest.cpp +++ b/incidenceeditor-ng/tests/conflictresolvertest.cpp @@ -1,344 +1,406 @@ /* Copyright (C) 2010 Casey Link Copyright (C) 2009-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 "conflictresolvertest.h" #include "../conflictresolver.h" #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; void ConflictResolverTest::insertAttendees() { foreach ( FreeBusyItem::Ptr item, attendees ) { resolver->insertAttendee( item ); } } void ConflictResolverTest::addAttendee( const QString &email, const KCalCore::FreeBusy::Ptr &fb, KCalCore::Attendee::Role role ) { QString name = QString( "attendee %1" ).arg( attendees.count() ); FreeBusyItem::Ptr item( new FreeBusyItem( KCalCore::Attendee::Ptr( new KCalCore::Attendee( name, email, false, KCalCore::Attendee::Accepted, role ) ), 0 ) ); item->setFreeBusy( KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( *fb.data() ) ) ); attendees << item; } void ConflictResolverTest::initTestCase() { parent = new QWidget; init(); } void ConflictResolverTest::init() { base = KDateTime::currentLocalDateTime().addDays( 1 ); end = base.addSecs( 10 * 60 * 60 ); resolver = new ConflictResolver( parent, parent ); } void ConflictResolverTest::cleanup() { delete resolver; resolver = 0; attendees.clear(); } void ConflictResolverTest::simpleTest() { KCalCore::Period meeting( end.addSecs( -3 * 60 * 60 ), KCalCore::Duration( 2 * 60 * 60 ) ); addAttendee( "albert@einstein.net", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << meeting ) ) ); insertAttendees(); static const int resolution = 15 * 60; resolver->setResolution( resolution ); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QVERIFY( resolver->availableSlots().size() == 2 ); KCalCore::Period first = resolver->availableSlots().at( 0 ); QCOMPARE( first.start(), base ); QCOMPARE( first.end(), meeting.start() ); KCalCore::Period second = resolver->availableSlots().at( 1 ); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE( second.start(), meeting.end().addSecs( resolution ) ); //add 15 minutes because the //free block doesn't start until //the next timeslot QCOMPARE( second.end(), end ); } +void ConflictResolverTest::durationTest() +{ + KCalCore::Period meeting( end.addSecs( -3 * 60 * 60 ), KCalCore::Duration( 2 * 60 * 60 ) ); + addAttendee( "albert@einstein.net", + KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() + << meeting ) ) ); + + insertAttendees(); + + static const int resolution = 15 * 60; + resolver->setResolution( resolution ); + resolver->setEarliestDateTime( base ); + resolver->setLatestDateTime( end ); + resolver->setDuration( 2 * resolution ); + resolver->findAllFreeSlots(); + + QVERIFY( resolver->availableSlots().size() == 2 ); + + KCalCore::Period first = resolver->availableSlots().at( 0 ); + QCOMPARE( first.start(), base ); + QCOMPARE( first.end(), meeting.start().addSecs(-2 * resolution) ); +} + +void ConflictResolverTest::workingHoursTest() +{ + /* + * to got a valid result of these test we need workStart < workEnd, + * to schedule over the day and not over midnight. + * we get this, if set the time of "base" to something < 12am in our case 4am + */ + KDateTime base = KDateTime::currentLocalDateTime().addDays(1); + base.setTime(QTime(4,0)); + const KDateTime end = base.addSecs(10 * 60 * 60); + KCalCore::Period meeting( end.addSecs( -3 * 60 * 60 ), KCalCore::Duration( 2 * 60 * 60 ) ); + addAttendee( "albert@einstein.net", + KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() + << meeting ) ) ); + + insertAttendees(); + + static const int resolution = 15 * 60; + const KDateTime workStart = base.addSecs(2*resolution); + const KDateTime workEnd = end.addSecs(-2*resolution); + resolver->setResolution( resolution ); + resolver->setEarliestDateTime( base ); + resolver->setLatestDateTime( end ); + resolver->setWorkingHours(workStart.time(), workEnd.time()); + resolver->setWorkingHoursOnly(true); + resolver->findAllFreeSlots(); + + QCOMPARE( resolver->availableSlots().size(), 2 ); + + KCalCore::Period first = resolver->availableSlots().at( 0 ); + QCOMPARE( first.start(), workStart ); + QCOMPARE( first.end(), meeting.start() ); + + KCalCore::Period second = resolver->availableSlots().at( 1 ); + QCOMPARE( second.start(), meeting.end()); + QCOMPARE( second.end(), workEnd); + +} + void ConflictResolverTest::stillPrettySimpleTest() { KCalCore::Period meeting1( base, KCalCore::Duration( 2 * 60 * 60 ) ); KCalCore::Period meeting2( base.addSecs( 60 * 60 ), KCalCore::Duration( 2 * 60 * 60 ) ); KCalCore::Period meeting3( end.addSecs( -3 * 60 * 60 ), KCalCore::Duration( 2 * 60 * 60 ) ); addAttendee( "john.f@kennedy.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << meeting1 << meeting3 ) ) ); addAttendee( "elvis@rock.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << meeting2 << meeting3 ) ) ); addAttendee( "albert@einstein.net", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << meeting3 ) ) ); insertAttendees(); static const int resolution = 15 * 60; resolver->setResolution( resolution ); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QVERIFY( resolver->availableSlots().size() == 2 ); KCalCore::Period first = resolver->availableSlots().at( 0 ); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE( first.start(), meeting2.end().addSecs( resolution ) ); QCOMPARE( first.end(), meeting3.start() ); KCalCore::Period second = resolver->availableSlots().at( 1 ); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE( second.start(), meeting3.end().addSecs( resolution ) ); //add 15 minutes because the //free block doesn't start until //the next timeslot QCOMPARE( second.end(), end ); } #define _time( h, m ) KDateTime( base.date(), QTime( h, m ) ) void ConflictResolverTest::akademy2010() { // based off akademy 2010 schedule // first event was at 9:30, so lets align our start time there base.setTime( QTime( 9, 30 ) ); end = base.addSecs( 8 * 60 * 60 ); KCalCore::Period opening( _time( 9, 30 ), _time( 9, 45 ) ); KCalCore::Period keynote( _time( 9, 45 ), _time( 10, 30 ) ); KCalCore::Period sevenPrinciples( _time( 10, 30 ), _time( 11, 15 ) ); KCalCore::Period commAsService( _time( 10, 30 ), _time( 11, 15 ) ); KCalCore::Period kdeForums( _time( 11, 15 ), _time( 11, 45 ) ); KCalCore::Period oviStore( _time( 11, 15 ), _time( 11, 45 ) ); // 10 min break KCalCore::Period highlights( _time( 12, 0 ), _time( 12, 45 ) ); KCalCore::Period styles( _time( 12, 0 ), _time( 12, 45 ) ); KCalCore::Period wikimedia( _time( 12, 45 ), _time( 13, 15 ) ); KCalCore::Period avalanche( _time( 12, 45 ), _time( 13, 15 ) ); KCalCore::Period pimp( _time( 13, 15 ), _time( 13, 45 ) ); KCalCore::Period direction( _time( 13, 15 ), _time( 13, 45 ) ); // lunch 1 hr 25 min lunch KCalCore::Period blurr( _time( 15, 15 ), _time( 16, 00 ) ); KCalCore::Period plasma( _time( 15, 15 ), _time( 16, 00 ) ); // for ( int i = 1; i < 80; ++i ) { // adds 80 people (adds the same 8 peopl 10 times) addAttendee( "akademyattendee1@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << keynote << oviStore << wikimedia << direction ) ) ); addAttendee( "akademyattendee2@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << keynote << commAsService << highlights << pimp ) ) ); addAttendee( "akademyattendee3@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << kdeForums << styles << pimp << plasma ) ) ); addAttendee( "akademyattendee4@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << keynote << oviStore << pimp << blurr ) ) ); addAttendee( "akademyattendee5@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << keynote << oviStore << highlights << avalanche ) ) ); addAttendee( "akademyattendee6@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << keynote << commAsService << highlights ) ) ); addAttendee( "akademyattendee7@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << kdeForums << styles << avalanche << pimp << plasma ) ) ); addAttendee( "akademyattendee8@email.com", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << opening << keynote << oviStore << wikimedia << blurr ) ) ); // } insertAttendees(); const int resolution = 5 * 60; resolver->setResolution( resolution ); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); // QBENCHMARK { resolver->findAllFreeSlots(); // } QVERIFY( resolver->availableSlots().size() == 3 ); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Abort); QCOMPARE( resolver->availableSlots().at( 0 ).duration(), KCalCore::Duration( 10 * 60 ) ); QCOMPARE( resolver->availableSlots().at( 1 ).duration(), KCalCore::Duration( 1 * 60 * 60 + 25 * 60 ) ); QVERIFY( resolver->availableSlots().at( 2 ).start() > plasma.end() ); } void ConflictResolverTest::testPeriodIsLargerThenTimeframe() { base.setDate( QDate( 2010, 7, 29 ) ); base.setTime( QTime( 7, 30 ) ); end.setDate( QDate( 2010, 7, 29 ) ); end.setTime( QTime( 8, 30 ) ); KCalCore::Period testEvent( _time( 5, 45 ), _time( 8, 45 ) ); addAttendee( "kdabtest1@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << testEvent ) ) ); addAttendee( "kdabtest2@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() ) ) ); insertAttendees(); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QCOMPARE( resolver->availableSlots().size(), 0 ); } void ConflictResolverTest::testPeriodBeginsBeforeTimeframeBegins() { base.setDate( QDate( 2010, 7, 29 ) ); base.setTime( QTime( 7, 30 ) ); end.setDate( QDate( 2010, 7, 29 ) ); end.setTime( QTime( 9, 30 ) ); KCalCore::Period testEvent( _time( 5, 45 ), _time( 8, 45 ) ); addAttendee( "kdabtest1@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << testEvent ) ) ); addAttendee( "kdabtest2@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() ) ) ); insertAttendees(); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QCOMPARE( resolver->availableSlots().size(), 1 ); KCalCore::Period freeslot = resolver->availableSlots().at( 0 ); QCOMPARE( freeslot.start(), _time( 8, 45 ) ); QCOMPARE( freeslot.end(), end ); } void ConflictResolverTest::testPeriodEndsAfterTimeframeEnds() { base.setDate( QDate( 2010, 7, 29 ) ); base.setTime( QTime( 7, 30 ) ); end.setDate( QDate( 2010, 7, 29 ) ); end.setTime( QTime( 9, 30 ) ); KCalCore::Period testEvent( _time( 8, 00 ), _time( 9, 45 ) ); addAttendee( "kdabtest1@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << testEvent ) ) ); addAttendee( "kdabtest2@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() ) ) ); insertAttendees(); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QCOMPARE( resolver->availableSlots().size(), 1 ); KCalCore::Period freeslot = resolver->availableSlots().at( 0 ); QCOMPARE( freeslot.duration(), KCalCore::Duration( 30 * 60 ) ); QCOMPARE( freeslot.start(), base ); QCOMPARE( freeslot.end(), _time( 8, 00 ) ); } void ConflictResolverTest::testPeriodEndsAtSametimeAsTimeframe() { base.setDate( QDate( 2010, 7, 29 ) ); base.setTime( QTime( 7, 45 ) ); end.setDate( QDate( 2010, 7, 29 ) ); end.setTime( QTime( 8, 45 ) ); KCalCore::Period testEvent( _time( 5, 45 ), _time( 8, 45 ) ); addAttendee( "kdabtest1@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() << testEvent ) ) ); addAttendee( "kdabtest2@demo.kolab.org", KCalCore::FreeBusy::Ptr( new KCalCore::FreeBusy( KCalCore::Period::List() ) ) ); insertAttendees(); resolver->setEarliestDateTime( base ); resolver->setLatestDateTime( end ); resolver->findAllFreeSlots(); QCOMPARE( resolver->availableSlots().size(), 0 ); } QTEST_KDEMAIN( ConflictResolverTest, GUI ) diff --git a/incidenceeditor-ng/tests/conflictresolvertest.h b/incidenceeditor-ng/tests/conflictresolvertest.h index 82d643c9ff..137c290676 100644 --- a/incidenceeditor-ng/tests/conflictresolvertest.h +++ b/incidenceeditor-ng/tests/conflictresolvertest.h @@ -1,60 +1,62 @@ /* Copyright (C) 2010 Casey Link Copyright (C) 2009-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. */ #ifndef CONFLICTRESOLVERTEST_H #define CONFLICTRESOLVERTEST_H #include "freebusymodel/freebusyitem.h" #include #include #include namespace IncidenceEditorNG { class ConflictResolver; } class ConflictResolverTest : public QObject { Q_OBJECT private slots: void initTestCase(); void init(); void cleanup(); void simpleTest(); + void durationTest(); + void workingHoursTest(); void stillPrettySimpleTest(); void akademy2010(); void testPeriodBeginsBeforeTimeframeBegins(); void testPeriodEndsAfterTimeframeEnds(); void testPeriodIsLargerThenTimeframe(); void testPeriodEndsAtSametimeAsTimeframe(); private: void insertAttendees(); void addAttendee( const QString &email, const KCalCore::FreeBusy::Ptr &fb, KCalCore::Attendee::Role role = KCalCore::Attendee::ReqParticipant ) ; QList attendees; QWidget *parent; IncidenceEditorNG::ConflictResolver *resolver; KDateTime base, end; }; #endif