summaryrefslogtreecommitdiffstats
path: root/korganizer/koeditorfreebusy.cpp
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch)
tree67208f7c145782a7e90b123b982ca78d88cc2c87 /korganizer/koeditorfreebusy.cpp
downloadtdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz
tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'korganizer/koeditorfreebusy.cpp')
-rw-r--r--korganizer/koeditorfreebusy.cpp931
1 files changed, 931 insertions, 0 deletions
diff --git a/korganizer/koeditorfreebusy.cpp b/korganizer/koeditorfreebusy.cpp
new file mode 100644
index 000000000..3849bc5d0
--- /dev/null
+++ b/korganizer/koeditorfreebusy.cpp
@@ -0,0 +1,931 @@
+/*
+ This file is part of KOrganizer.
+
+ Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
+
+ 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 <qtooltip.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcombobox.h>
+#include <qpushbutton.h>
+#include <qvaluevector.h>
+#include <qwhatsthis.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+
+#ifndef KORG_NOKABC
+#include <kabc/addresseedialog.h>
+#include <kabc/vcardconverter.h>
+#include <libkdepim/addressesdialog.h>
+#include <libkdepim/addresseelineedit.h>
+#include <libkdepim/distributionlist.h>
+#include <kabc/stdaddressbook.h>
+#endif
+
+#include <libkcal/event.h>
+#include <libkcal/freebusy.h>
+
+#include <libemailfunctions/email.h>
+
+#include <kdgantt/KDGanttView.h>
+#include <kdgantt/KDGanttViewTaskItem.h>
+#include <kdgantt/KDGanttViewSubwidgets.h>
+
+#include "koprefs.h"
+#include "koglobals.h"
+#include "kogroupware.h"
+#include "freebusymanager.h"
+#include "freebusyurldialog.h"
+
+#include "koeditorfreebusy.h"
+
+// The FreeBusyItem is the whole line for a given attendee.
+// Individual "busy" periods are created as sub-items of this item.
+//
+// We can't use the CustomListViewItem base class, since we need a
+// different inheritance hierarchy for supporting the Gantt view.
+class FreeBusyItem : public KDGanttViewTaskItem
+{
+ public:
+ FreeBusyItem( Attendee *attendee, KDGanttView *parent ) :
+ KDGanttViewTaskItem( parent, parent->lastItem() ), mAttendee( attendee ), mTimerID( 0 ),
+ mIsDownloading( false )
+ {
+ Q_ASSERT( attendee );
+ updateItem();
+ setFreeBusyPeriods( 0 );
+ setDisplaySubitemsAsGroup( true );
+ if ( listView () )
+ listView ()->setRootIsDecorated( false );
+ }
+ ~FreeBusyItem() {}
+
+ void updateItem();
+
+ Attendee *attendee() const { return mAttendee; }
+ void setFreeBusy( KCal::FreeBusy *fb ) { mFreeBusy = fb; }
+ KCal::FreeBusy* freeBusy() const { return mFreeBusy; }
+
+ void setFreeBusyPeriods( FreeBusy *fb );
+
+ QString key( int column, bool ) const
+ {
+ QMap<int,QString>::ConstIterator it = mKeyMap.find( column );
+ if ( it == mKeyMap.end() ) return listViewText( column );
+ else return *it;
+ }
+
+ void setSortKey( int column, const QString &key )
+ {
+ mKeyMap.insert( column, key );
+ }
+
+ QString email() const { return mAttendee->email(); }
+ void setUpdateTimerID( int id ) { mTimerID = id; }
+ int updateTimerID() const { return mTimerID; }
+
+ void startDownload( bool forceDownload ) {
+ mIsDownloading = true;
+ FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
+ if ( !m->retrieveFreeBusy( attendee()->email(), forceDownload ) )
+ mIsDownloading = false;
+ }
+ void setIsDownloading( bool d ) { mIsDownloading = d; }
+ bool isDownloading() const { return mIsDownloading; }
+
+ private:
+ Attendee *mAttendee;
+ KCal::FreeBusy *mFreeBusy;
+
+ QMap<int,QString> mKeyMap;
+
+ // This is used for the update timer
+ int mTimerID;
+
+ // Only run one download job at a time
+ bool mIsDownloading;
+};
+
+void FreeBusyItem::updateItem()
+{
+ setListViewText( 0, mAttendee->fullName() );
+ switch ( mAttendee->status() ) {
+ case Attendee::Accepted:
+ setPixmap( 0, KOGlobals::self()->smallIcon( "ok" ) );
+ break;
+ case Attendee::Declined:
+ setPixmap( 0, KOGlobals::self()->smallIcon( "no" ) );
+ break;
+ case Attendee::NeedsAction:
+ case Attendee::InProcess:
+ setPixmap( 0, KOGlobals::self()->smallIcon( "help" ) );
+ break;
+ case Attendee::Tentative:
+ setPixmap( 0, KOGlobals::self()->smallIcon( "apply" ) );
+ break;
+ case Attendee::Delegated:
+ setPixmap( 0, KOGlobals::self()->smallIcon( "mail_forward" ) );
+ break;
+ default:
+ setPixmap( 0, QPixmap() );
+ }
+}
+
+
+// Set the free/busy periods for this attendee
+void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
+{
+ if( fb ) {
+ // Clean out the old entries
+ for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
+ delete it;
+
+ // Evaluate free/busy information
+ QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
+ for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
+ it != busyPeriods.end(); ++it ) {
+ KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
+ newSubItem->setStartTime( (*it).start() );
+ newSubItem->setEndTime( (*it).end() );
+ newSubItem->setColors( Qt::red, Qt::red, Qt::red );
+ QString toolTip;
+ if ( !(*it).summary().isEmpty() )
+ toolTip += "<b>" + (*it).summary() + "</b><br/>";
+ if ( !(*it).location().isEmpty() )
+ toolTip += i18n( "Location: %1" ).arg( (*it).location() );
+ if ( !toolTip.isEmpty() )
+ newSubItem->setTooltipText( toolTip );
+ }
+ setFreeBusy( fb );
+ setShowNoInformation( false );
+ } else {
+ // No free/busy information
+ //debug only start
+ // int ii ;
+ // QDateTime cur = QDateTime::currentDateTime();
+ // for( ii = 0; ii < 10 ;++ii ) {
+ // KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
+ // cur = cur.addSecs( 7200 );
+ // newSubItem->setStartTime( cur );
+ // cur = cur.addSecs( 7200 );
+ // newSubItem->setEndTime( cur );
+ // newSubItem->setColors( Qt::red, Qt::red, Qt::red );
+ // }
+ //debug only end
+ setFreeBusy( 0 );
+ setShowNoInformation( true );
+ }
+
+ // We are no longer downloading
+ mIsDownloading = false;
+}
+
+////
+
+KOEditorFreeBusy::KOEditorFreeBusy( int spacing, QWidget *parent,
+ const char *name )
+ : KOAttendeeEditor( parent, name )
+{
+ QVBoxLayout *topLayout = new QVBoxLayout( this );
+ topLayout->setSpacing( spacing );
+
+ initOrganizerWidgets( this, topLayout );
+
+ // Label for status summary information
+ // Uses the tooltip palette to highlight it
+ mIsOrganizer = false; // Will be set later. This is just valgrind silencing
+ mStatusSummaryLabel = new QLabel( this );
+ mStatusSummaryLabel->setPalette( QToolTip::palette() );
+ mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
+ mStatusSummaryLabel->setLineWidth( 1 );
+ mStatusSummaryLabel->hide(); // Will be unhidden later if you are organizer
+ topLayout->addWidget( mStatusSummaryLabel );
+
+ // The control panel for the gantt widget
+ QBoxLayout *controlLayout = new QHBoxLayout( topLayout );
+
+ QString whatsThis = i18n("Sets the zoom level on the Gantt chart. "
+ "'Hour' shows a range of several hours, "
+ "'Day' shows a range of a few days, "
+ "'Week' shows a range of a few months, "
+ "and 'Month' shows a range of a few years, "
+ "while 'Automatic' selects the range most "
+ "appropriate for the current event or to-do.");
+ QLabel *label = new QLabel( i18n( "Scale: " ), this );
+ QWhatsThis::add( label, whatsThis );
+ controlLayout->addWidget( label );
+
+ scaleCombo = new QComboBox( this );
+ QWhatsThis::add( scaleCombo, whatsThis );
+ scaleCombo->insertItem( i18n( "Hour" ) );
+ scaleCombo->insertItem( i18n( "Day" ) );
+ scaleCombo->insertItem( i18n( "Week" ) );
+ scaleCombo->insertItem( i18n( "Month" ) );
+ scaleCombo->insertItem( i18n( "Automatic" ) );
+ scaleCombo->setCurrentItem( 0 ); // start with "hour"
+ connect( scaleCombo, SIGNAL( activated( int ) ),
+ SLOT( slotScaleChanged( int ) ) );
+ controlLayout->addWidget( scaleCombo );
+
+ QPushButton *button = new QPushButton( i18n( "Center on Start" ), this );
+ QWhatsThis::add( button,
+ i18n("Centers the Gantt chart on the start time "
+ "and day of this event.") );
+ connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) );
+ controlLayout->addWidget( button );
+
+ controlLayout->addStretch( 1 );
+
+ button = new QPushButton( i18n( "Pick Date" ), this );
+ QWhatsThis::add( button,
+ i18n("Moves the event to a date and time when all the "
+ "attendees are free.") );
+ connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
+ controlLayout->addWidget( button );
+
+ controlLayout->addStretch( 1 );
+
+ button = new QPushButton( i18n("Reload"), this );
+ QWhatsThis::add( button,
+ i18n("Reloads Free/Busy data for all attendees from "
+ "the corresponding servers.") );
+ controlLayout->addWidget( button );
+ connect( button, SIGNAL( clicked() ), SLOT( manualReload() ) );
+
+ mGanttView = new KDGanttView( this, "mGanttView" );
+ QWhatsThis::add( mGanttView,
+ i18n("Shows the free/busy status of all attendees. "
+ "Double-clicking on an attendees entry in the "
+ "list will allow you to enter the location of their "
+ "Free/Busy Information.") );
+ topLayout->addWidget( mGanttView );
+ // Remove the predefined "Task Name" column
+ mGanttView->removeColumn( 0 );
+ mGanttView->addColumn( i18n("Attendee") );
+ if ( KOPrefs::instance()->mCompactDialogs ) {
+ mGanttView->setFixedHeight( 78 );
+ }
+ mGanttView->setHeaderVisible( true );
+ mGanttView->setScale( KDGanttView::Hour );
+ mGanttView->setShowHeaderPopupMenu( false, false, false, false, false, false );
+ // Initially, show 15 days back and forth
+ // set start to even hours, i.e. to 12:AM 0 Min 0 Sec
+ QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
+ .addDays( -15 ).date() );
+ QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
+ mGanttView->setHorizonStart( horizonStart );
+ mGanttView->setHorizonEnd( horizonEnd );
+ mGanttView->setCalendarMode( true );
+ //mGanttView->setDisplaySubitemsAsGroup( true );
+ mGanttView->setShowLegendButton( false );
+ // Initially, center to current date
+ mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
+ if ( KGlobal::locale()->use12Clock() )
+ mGanttView->setHourFormat( KDGanttView::Hour_12 );
+ else
+ mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
+
+ // mEventRectangle is the colored rectangle representing the event being modified
+ mEventRectangle = new KDIntervalColorRectangle( mGanttView );
+ mEventRectangle->setColor( Qt::magenta );
+ mGanttView->addIntervalBackgroundColor( mEventRectangle );
+
+ connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
+ const QDateTime & ) ),
+ mGanttView, SLOT( zoomToSelection( const QDateTime &,
+ const QDateTime & ) ) );
+ connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
+ SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
+ connect( mGanttView, SIGNAL( intervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ),
+ this, SLOT( slotIntervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ) );
+
+ connect( mGanttView, SIGNAL(lvSelectionChanged(KDGanttViewItem*)),
+ this, SLOT(updateAttendeeInput()) );
+ connect( mGanttView, SIGNAL(lvItemLeftClicked(KDGanttViewItem*)),
+ this, SLOT(showAttendeeStatusMenu()) );
+ connect( mGanttView, SIGNAL(lvItemRightClicked(KDGanttViewItem*)),
+ this, SLOT(showAttendeeStatusMenu()) );
+ connect( mGanttView, SIGNAL(lvMouseButtonClicked(int, KDGanttViewItem*, const QPoint&, int)),
+ this, SLOT(listViewClicked(int, KDGanttViewItem*)) );
+
+ FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
+ connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
+ SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
+
+ connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( autoReload() ) );
+
+ initEditWidgets( this, topLayout );
+ connect( mRemoveButton, SIGNAL(clicked()),
+ SLOT(removeAttendee()) );
+
+ slotOrganizerChanged( mOrganizerCombo->currentText() );
+ connect( mOrganizerCombo, SIGNAL( activated(const QString&) ),
+ this, SLOT( slotOrganizerChanged(const QString&) ) );
+
+ //suppress the buggy consequences of clicks on the time header widget
+ mGanttView->timeHeaderWidget()->installEventFilter( this );
+}
+
+KOEditorFreeBusy::~KOEditorFreeBusy()
+{
+}
+
+void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
+{
+ FreeBusyItem *anItem =
+ static_cast<FreeBusyItem *>( mGanttView->firstChild() );
+ while( anItem ) {
+ if( anItem->attendee() == attendee ) {
+ if ( anItem->updateTimerID() != 0 )
+ killTimer( anItem->updateTimerID() );
+ delete anItem;
+ updateStatusSummary();
+ break;
+ }
+ anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
+ }
+}
+
+void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
+{
+ FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
+ if ( readFBList )
+ updateFreeBusyData( item );
+ else {
+ clearSelection();
+ mGanttView->setSelected( item, true );
+ }
+ updateStatusSummary();
+ emit updateAttendeeSummary( mGanttView->childCount() );
+}
+
+void KOEditorFreeBusy::clearAttendees()
+{
+ mGanttView->clear();
+}
+
+
+void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
+{
+ mGanttView->setUpdateEnabled( enabled );
+}
+
+bool KOEditorFreeBusy::updateEnabled() const
+{
+ return mGanttView->getUpdateEnabled();
+}
+
+
+void KOEditorFreeBusy::readEvent( Event *event )
+{
+ bool block = updateEnabled();
+ setUpdateEnabled( false );
+ clearAttendees();
+
+ setDateTimes( event->dtStart(), event->dtEnd() );
+ mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
+ updateStatusSummary();
+ clearSelection();
+ KOAttendeeEditor::readEvent( event );
+
+ setUpdateEnabled( block );
+ emit updateAttendeeSummary( mGanttView->childCount() );
+}
+
+void KOEditorFreeBusy::slotIntervalColorRectangleMoved( const QDateTime& start, const QDateTime& end )
+{
+ kdDebug() << k_funcinfo << "slotIntervalColorRectangleMoved " << start << "," << end << endl;
+ mDtStart = start;
+ mDtEnd = end;
+ emit dateTimesChanged( start, end );
+}
+
+void KOEditorFreeBusy::setDateTimes( const QDateTime &start, const QDateTime &end )
+{
+ slotUpdateGanttView( start, end );
+}
+
+void KOEditorFreeBusy::slotScaleChanged( int newScale )
+{
+ // The +1 is for the Minute scale which we don't offer in the combo box.
+ KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
+ mGanttView->setScale( scale );
+ slotCenterOnStart();
+}
+
+void KOEditorFreeBusy::slotCenterOnStart()
+{
+ mGanttView->centerTimeline( mDtStart );
+}
+
+void KOEditorFreeBusy::slotZoomToTime()
+{
+ mGanttView->zoomToFit();
+}
+
+void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
+{
+ if ( item->isDownloading() )
+ // This item is already in the process of fetching the FB list
+ return;
+
+ if ( item->updateTimerID() != 0 )
+ // An update timer is already running. Reset it
+ killTimer( item->updateTimerID() );
+
+ // This item does not have a download running, and no timer is set
+ // Do the download in five seconds
+ item->setUpdateTimerID( startTimer( 5000 ) );
+}
+
+void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
+{
+ killTimer( event->timerId() );
+ FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
+ while( item ) {
+ if( item->updateTimerID() == event->timerId() ) {
+ item->setUpdateTimerID( 0 );
+ item->startDownload( mForceDownload );
+ return;
+ }
+ item = static_cast<FreeBusyItem *>( item->nextSibling() );
+ }
+}
+
+// Set the Free Busy list for everyone having this email address
+// If fb == 0, this disabled the free busy list for them
+void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
+ const QString &email )
+{
+ kdDebug(5850) << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
+
+ if( fb )
+ fb->sortList();
+ bool block = mGanttView->getUpdateEnabled();
+ mGanttView->setUpdateEnabled( false );
+ for( KDGanttViewItem *it = mGanttView->firstChild(); it;
+ it = it->nextSibling() ) {
+ FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
+ if( item->email() == email )
+ item->setFreeBusyPeriods( fb );
+ }
+ mGanttView->setUpdateEnabled( block );
+}
+
+
+/*!
+ Centers the Gantt view to the date/time passed in.
+*/
+
+void KOEditorFreeBusy::slotUpdateGanttView( const QDateTime &dtFrom, const QDateTime &dtTo )
+{
+ mDtStart = dtFrom;
+ mDtEnd = dtTo;
+ bool block = mGanttView->getUpdateEnabled( );
+ mGanttView->setUpdateEnabled( false );
+ QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
+ mGanttView->setHorizonStart( horizonStart );
+ mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
+ mEventRectangle->setDateTimes( dtFrom, dtTo );
+ mGanttView->setUpdateEnabled( block );
+ mGanttView->centerTimelineAfterShow( dtFrom );
+}
+
+
+/*!
+ This slot is called when the user clicks the "Pick a date" button.
+*/
+void KOEditorFreeBusy::slotPickDate()
+{
+ QDateTime start = mDtStart;
+ QDateTime end = mDtEnd;
+ bool success = findFreeSlot( start, end );
+
+ if( success ) {
+ if ( start == mDtStart && end == mDtEnd ) {
+ KMessageBox::information( this,
+ i18n( "The meeting already has suitable start/end times." ), QString::null,
+ "MeetingTimeOKFreeBusy" );
+ } else {
+ emit dateTimesChanged( start, end );
+ slotUpdateGanttView( start, end );
+ KMessageBox::information( this,
+ i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
+ .arg( start.toString() ).arg( end.toString() ), QString::null,
+ "MeetingMovedFreeBusy" );
+ }
+ } else
+ KMessageBox::sorry( this, i18n( "No suitable date found." ) );
+}
+
+
+/*!
+ Finds a free slot in the future which has at least the same size as
+ the initial slot.
+*/
+bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
+{
+ if( tryDate( dtFrom, dtTo ) )
+ // Current time is acceptable
+ return true;
+
+ QDateTime tryFrom = dtFrom;
+ QDateTime 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.
+ if( tryFrom < QDateTime::currentDateTime() ) {
+ // The slot to look for is at least partially in the past.
+ int secs = tryFrom.secsTo( tryTo );
+ tryFrom = QDateTime::currentDateTime();
+ 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;
+}
+
+
+/*!
+ Checks whether the slot specified by (tryFrom, tryTo) is free
+ for all participants. If yes, return true. If at least one
+ participant is found for which this slot is occupied, this method
+ returns false, 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.
+*/
+bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
+{
+ FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
+ while( currentItem ) {
+ if( !tryDate( currentItem, tryFrom, tryTo ) ) {
+ // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl;
+ return false;
+ }
+
+ currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
+ }
+
+ return true;
+}
+
+/*!
+ Checks whether the slot specified by (tryFrom, tryTo) is available
+ for the participant specified with attendee. 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 KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
+ QDateTime &tryFrom, QDateTime &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.
+ KCal::FreeBusy *fb = attendee->freeBusy();
+ if( !fb )
+ return true;
+
+ QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
+ for( QValueList<KCal::Period>::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
+ int secsDuration = tryFrom.secsTo( tryTo );
+ tryFrom = (*it).end();
+ tryTo = tryFrom.addSecs( secsDuration );
+ // try again with the new try period
+ tryDate( attendee, tryFrom, tryTo );
+ // we had to change the date at least once
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void KOEditorFreeBusy::updateStatusSummary()
+{
+ FreeBusyItem *aItem =
+ static_cast<FreeBusyItem *>( mGanttView->firstChild() );
+ int total = 0;
+ int accepted = 0;
+ int tentative = 0;
+ int declined = 0;
+ while( aItem ) {
+ ++total;
+ switch( aItem->attendee()->status() ) {
+ case Attendee::Accepted:
+ ++accepted;
+ break;
+ case Attendee::Tentative:
+ ++tentative;
+ break;
+ case Attendee::Declined:
+ ++declined;
+ break;
+ case Attendee::NeedsAction:
+ case Attendee::Delegated:
+ case Attendee::Completed:
+ case Attendee::InProcess:
+ /* just to shut up the compiler */
+ break;
+ }
+ aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
+ }
+ if( total > 1 && mIsOrganizer ) {
+ mStatusSummaryLabel->show();
+ mStatusSummaryLabel->setText(
+ i18n( "Of the %1 participants, %2 have accepted, %3"
+ " have tentatively accepted, and %4 have declined.")
+ .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
+ } else {
+ mStatusSummaryLabel->hide();
+ }
+ mStatusSummaryLabel->adjustSize();
+}
+
+void KOEditorFreeBusy::triggerReload()
+{
+ mReloadTimer.start( 1000, true );
+}
+
+void KOEditorFreeBusy::cancelReload()
+{
+ mReloadTimer.stop();
+}
+
+void KOEditorFreeBusy::manualReload()
+{
+ mForceDownload = true;
+ reload();
+}
+
+void KOEditorFreeBusy::autoReload()
+{
+ mForceDownload = false;
+ reload();
+}
+
+void KOEditorFreeBusy::reload()
+{
+ kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
+
+ FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
+ while( item ) {
+ if ( mForceDownload )
+ item->startDownload( mForceDownload );
+ else
+ updateFreeBusyData( item );
+
+ item = static_cast<FreeBusyItem *>( item->nextSibling() );
+ }
+}
+
+void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
+{
+ FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
+ if ( !item ) return;
+
+ Attendee *attendee = item->attendee();
+
+ FreeBusyUrlDialog dialog( attendee, this );
+ dialog.exec();
+}
+
+void KOEditorFreeBusy::writeEvent(KCal::Event * event)
+{
+ event->clearAttendees();
+ QValueVector<FreeBusyItem*> toBeDeleted;
+ for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
+ item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
+ {
+ Attendee *attendee = item->attendee();
+ Q_ASSERT( attendee );
+ /* Check if the attendee is a distribution list and expand it */
+ if ( attendee->email().isEmpty() ) {
+ KPIM::DistributionList list =
+ KPIM::DistributionList::findByName( KABC::StdAddressBook::self(), attendee->name() );
+ if ( !list.isEmpty() ) {
+ toBeDeleted.push_back( item ); // remove it once we are done expanding
+ KPIM::DistributionList::Entry::List entries = list.entries( KABC::StdAddressBook::self() );
+ KPIM::DistributionList::Entry::List::Iterator it( entries.begin() );
+ while ( it != entries.end() ) {
+ KPIM::DistributionList::Entry &e = ( *it );
+ ++it;
+ // this calls insertAttendee, which appends
+ insertAttendeeFromAddressee( e.addressee, attendee );
+ // TODO: duplicate check, in case it was already added manually
+ }
+ }
+ } else {
+ bool skip = false;
+ if ( attendee->email().endsWith( "example.net" ) ) {
+ if ( KMessageBox::warningYesNo( this, i18n("%1 does not look like a valid email address. "
+ "Are you sure you want to invite this participant?").arg( attendee->email() ),
+ i18n("Invalid email address") ) != KMessageBox::Yes ) {
+ skip = true;
+ }
+ }
+ if ( !skip ) {
+ event->addAttendee( new Attendee( *attendee ) );
+ }
+ }
+ }
+
+ KOAttendeeEditor::writeEvent( event );
+
+ // cleanup
+ QValueVector<FreeBusyItem*>::iterator it;
+ for( it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it ) {
+ delete *it;
+ }
+}
+
+KCal::Attendee * KOEditorFreeBusy::currentAttendee() const
+{
+ KDGanttViewItem *item = mGanttView->selectedItem();
+ FreeBusyItem *aItem = static_cast<FreeBusyItem*>( item );
+ if ( !aItem )
+ return 0;
+ return aItem->attendee();
+}
+
+void KOEditorFreeBusy::updateCurrentItem()
+{
+ FreeBusyItem* item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
+ if ( item ) {
+ item->updateItem();
+ updateFreeBusyData( item );
+ updateStatusSummary();
+ }
+}
+
+void KOEditorFreeBusy::removeAttendee()
+{
+ FreeBusyItem *item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
+ if ( !item )
+ return;
+
+ Attendee *delA = new Attendee( item->attendee()->name(), item->attendee()->email(),
+ item->attendee()->RSVP(), item->attendee()->status(),
+ item->attendee()->role(), item->attendee()->uid() );
+ mdelAttendees.append( delA );
+ delete item;
+
+ updateStatusSummary();
+ updateAttendeeInput();
+ emit updateAttendeeSummary( mGanttView->childCount() );
+}
+
+void KOEditorFreeBusy::clearSelection() const
+{
+ KDGanttViewItem *item = mGanttView->selectedItem();
+ if ( item )
+ mGanttView->setSelected( item, false );
+ mGanttView->repaint();
+ item->repaint();
+}
+
+void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
+{
+ const QStringList myEmails = KOPrefs::instance()->allEmails();
+ for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
+ item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
+ {
+ for ( QStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
+ if ( item->attendee()->email() == *it2 ) {
+ item->attendee()->setStatus( status );
+ item->updateItem();
+ }
+ }
+ }
+}
+
+void KOEditorFreeBusy::showAttendeeStatusMenu()
+{
+ if ( mGanttView->mapFromGlobal( QCursor::pos() ).x() > 22 )
+ return;
+ QPopupMenu popup;
+ popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
+ popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
+ popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
+ popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
+ popup.insertItem( KOGlobals::self()->smallIcon( "mail_forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
+ popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
+ popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
+ popup.setItemChecked( currentAttendee()->status(), true );
+ int status = popup.exec( QCursor::pos() );
+ if ( status >= 0 ) {
+ currentAttendee()->setStatus( (Attendee::PartStat)status );
+ updateCurrentItem();
+ updateAttendeeInput();
+ }
+}
+
+void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
+{
+ if ( button == Qt::LeftButton && item == 0 )
+ addNewAttendee();
+}
+
+void KOEditorFreeBusy::slotOrganizerChanged(const QString & newOrganizer)
+{
+ if (newOrganizer==mCurrentOrganizer) return;
+
+ QString name;
+ QString email;
+ bool success = KPIM::getNameAndMail( newOrganizer, name, email );
+
+ if (!success) return;
+//
+
+ Attendee *currentOrganizerAttendee = 0;
+ Attendee *newOrganizerAttendee = 0;
+
+ FreeBusyItem *anItem =
+ static_cast<FreeBusyItem *>( mGanttView->firstChild() );
+ while( anItem ) {
+ Attendee *attendee = anItem->attendee();
+ if( attendee->fullName() == mCurrentOrganizer )
+ currentOrganizerAttendee = attendee;
+
+ if( attendee->fullName() == newOrganizer )
+ newOrganizerAttendee = attendee;
+
+ anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
+ }
+
+ int answer = KMessageBox::No;
+
+ if (currentOrganizerAttendee) {
+ answer = KMessageBox::questionYesNo( this, i18n("You are changing the organiser of "
+ "this event, who is also attending, "
+ "do you want to change that attendee "
+ "as well?") );
+ } else {
+ answer = KMessageBox::Yes;
+ }
+
+ if (answer==KMessageBox::Yes) {
+ if (currentOrganizerAttendee) {
+ removeAttendee( currentOrganizerAttendee );
+ }
+
+ if (!newOrganizerAttendee) {
+ Attendee *a = new Attendee( name, email, true );
+ insertAttendee( a, false );
+ updateAttendee();
+ }
+ }
+
+ mCurrentOrganizer = newOrganizer;
+}
+
+bool KOEditorFreeBusy::eventFilter( QObject *watched, QEvent *event )
+{
+ if ( watched == mGanttView->timeHeaderWidget() &&
+ event->type() >= QEvent::MouseButtonPress && event->type() <= QEvent::MouseMove ) {
+ return true;
+ } else {
+ return KOAttendeeEditor::eventFilter( watched, event );
+ }
+}
+
+#include "koeditorfreebusy.moc"