/* Copyright (C) 2010 Casey Link Copyright (c) 2009-2010 Klarälvdalens 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 "attendeeline.h" #include "attendeedata.h" #include #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; typedef QPair TextIconPair; AttendeeComboBox::AttendeeComboBox( QWidget *parent ) : QToolButton( parent ), mMenu( new QMenu( this ) ), mCurrentIndex( -1 ) { setPopupMode( QToolButton::InstantPopup ); setToolButtonStyle( Qt::ToolButtonIconOnly ); setMenu( mMenu ); } void AttendeeComboBox::addItem( const QIcon &icon, const QString &text ) { mList.append( TextIconPair( text, icon ) ); if ( mCurrentIndex == -1 ) { setCurrentIndex( 0 ); } int index = mList.size() - 1; QAction *act = menu()->addAction( icon, text, this, SLOT(slotActionTriggered()) ); act->setData( index ); } void AttendeeComboBox::addItems( const QStringList &texts ) { foreach ( const QString &str, texts ) { addItem( QIcon(), str ); } if ( mCurrentIndex == -1 ) { setCurrentIndex( 0 ); } } int AttendeeComboBox::currentIndex() const { return mCurrentIndex; } void AttendeeComboBox::clear() { mCurrentIndex = -1; mMenu->clear(); mList.clear(); } void AttendeeComboBox::setCurrentIndex( int index ) { Q_ASSERT( index < mList.size() ); const int old = mCurrentIndex; mCurrentIndex = index; setIcon( mList.at( index ).second ); setToolTip( mList.at( index ).first ); if ( old != index ) { emit itemChanged(); } } void AttendeeComboBox::slotActionTriggered() { int index = qobject_cast ( sender() )->data().toInt(); setCurrentIndex( index ); } void AttendeeComboBox::keyPressEvent( QKeyEvent *ev ) { if ( ev->key() == Qt::Key_Left ) { emit leftPressed(); } else if ( ev->key() == Qt::Key_Right ) { emit rightPressed(); } else { QToolButton::keyPressEvent( ev ); } } AttendeeLineEdit::AttendeeLineEdit( QWidget *parent ) : AddresseeLineEdit( parent, true ) { } void AttendeeLineEdit::keyPressEvent( QKeyEvent *ev ) { if ( ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) && !completionBox()->isVisible() ) { emit downPressed(); KPIM::AddresseeLineEdit::keyPressEvent( ev ); } else if ( ev->key() == Qt::Key_Backspace && text().isEmpty() ) { ev->accept(); emit deleteMe(); } else if ( ev->key() == Qt::Key_Left && cursorPosition() == 0 && !ev->modifiers().testFlag( Qt::ShiftModifier ) ) { // Shift would be pressed during selection emit leftPressed(); } else if ( ev->key() == Qt::Key_Right && cursorPosition() == (int)text().length() && !ev->modifiers().testFlag( Qt::ShiftModifier ) ) { // Shift would be pressed during selection emit rightPressed(); } else if ( ev->key() == Qt::Key_Down ) { emit downPressed(); } else if ( ev->key() == Qt::Key_Up ) { emit upPressed(); } else { KPIM::AddresseeLineEdit::keyPressEvent( ev ); } } AttendeeLine::AttendeeLine( QWidget *parent ) : MultiplyingLine( parent ), mRoleCombo( new AttendeeComboBox( this ) ), mStateCombo( new AttendeeComboBox( this ) ), mResponseCombo( new AttendeeComboBox( this ) ), mEdit( new AttendeeLineEdit( this ) ), mData( new AttendeeData( QString(), QString() ) ), mModified( false ) { setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); QBoxLayout *topLayout = new QHBoxLayout( this ); topLayout->setSpacing( KDialog::spacingHint() ); topLayout->setMargin( 0 ); #ifdef KDEPIM_MOBILE_UI mRoleCombo->addItem( DesktopIcon( "meeting-participant", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::ReqParticipant ) ); mRoleCombo->addItem( DesktopIcon( "meeting-participant-optional", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::OptParticipant ) ); mRoleCombo->addItem( DesktopIcon( "meeting-observer", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::NonParticipant ) ); mRoleCombo->addItem( DesktopIcon( "meeting-chair", 48 ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::Chair ) ); mResponseCombo->addItem( DesktopIcon( "meeting-participant-request-response", 48 ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseCombo->addItem( DesktopIcon( "meeting-participant-no-response", 48 ), i18nc( "@item:inlistbox", "Request No Response" ) ); #else mRoleCombo->addItem( SmallIcon( "meeting-participant" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::ReqParticipant ) ); mRoleCombo->addItem( SmallIcon( "meeting-participant-optional" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::OptParticipant ) ); mRoleCombo->addItem( SmallIcon( "meeting-observer" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::NonParticipant ) ); mRoleCombo->addItem( SmallIcon( "meeting-chair" ), KCalUtils::Stringify::attendeeRole( KCalCore::Attendee::Chair ) ); mResponseCombo->addItem( SmallIcon( "meeting-participant-request-response" ), i18nc( "@item:inlistbox", "Request Response" ) ); mResponseCombo->addItem( SmallIcon( "meeting-participant-no-response" ), i18nc( "@item:inlistbox", "Request No Response" ) ); #endif mEdit->setToolTip( i18nc( "@info:tooltip", "Enter the name or email address of the attendee." ) ); mEdit->setClearButtonShown( true ); mStateCombo->setWhatsThis( i18nc( "@info:whatsthis", "Edits the current attendance status of the attendee." ) ); mRoleCombo->setWhatsThis( i18nc( "@info:whatsthis", "Edits the role of the attendee." ) ); mEdit->setWhatsThis( i18nc( "@info:whatsthis", "The email address or name of the attendee. An invitation " "can be sent to the user if an email address is provided." ) ); setActions( EventActions ); mResponseCombo->setToolTip( i18nc( "@info:tooltip", "Request a response from the attendee" ) ); mResponseCombo->setWhatsThis( i18nc( "@info:whatsthis", "Edits whether to send an email to the " "attendee to request a response concerning " "attendance." ) ); // add them to the layout in the correct order topLayout->addWidget( mRoleCombo ); topLayout->addWidget( mEdit ); topLayout->addWidget( mStateCombo ); topLayout->addWidget( mResponseCombo ); connect( mEdit, SIGNAL(returnPressed()), SLOT(slotReturnPressed()) ); connect( mEdit, SIGNAL(deleteMe()), SLOT(slotPropagateDeletion()), Qt::QueuedConnection ); connect( mEdit, SIGNAL(textChanged(QString)), SLOT(slotTextChanged(QString)), Qt::QueuedConnection ); connect( mEdit, SIGNAL(upPressed()), SLOT(slotFocusUp()) ); connect( mEdit, SIGNAL(downPressed()), SLOT(slotFocusDown()) ); connect( mRoleCombo, SIGNAL(rightPressed()), mEdit, SLOT(setFocus()) ); connect( mEdit, SIGNAL(leftPressed()), mRoleCombo, SLOT(setFocus()) ); connect( mEdit, SIGNAL(rightPressed()), mStateCombo, SLOT(setFocus()) ); connect( mStateCombo, SIGNAL(leftPressed()), mEdit, SLOT(setFocus()) ); connect( mStateCombo, SIGNAL(rightPressed()), mResponseCombo, SLOT(setFocus()) ); connect( mResponseCombo, SIGNAL(leftPressed()), mStateCombo, SLOT(setFocus()) ); connect( mResponseCombo, SIGNAL(rightPressed()), SIGNAL(rightPressed()) ); connect( mEdit, SIGNAL(editingFinished()), SLOT(slotHandleChange()), Qt::QueuedConnection ); connect( mEdit, SIGNAL(textCompleted()), SLOT(slotHandleChange()), Qt::QueuedConnection ); connect( mEdit, SIGNAL(clearButtonClicked()), SLOT(slotPropagateDeletion()), Qt::QueuedConnection ); connect( mRoleCombo, SIGNAL(itemChanged()), this, SLOT(slotComboChanged()) ); connect( mStateCombo, SIGNAL(itemChanged()), this, SLOT(slotComboChanged()) ); connect( mResponseCombo, SIGNAL(itemChanged()), this, SLOT(slotComboChanged()) ); } void AttendeeLine::activate() { mEdit->setFocus(); } void AttendeeLine::clear() { mEdit->clear(); mRoleCombo->setCurrentIndex( 0 ); mStateCombo->setCurrentIndex( 0 ); mResponseCombo->setCurrentIndex( 0 ); mUid.clear(); } void AttendeeLine::clearModified() { mModified = false; mEdit->setModified( false ); } KPIM::MultiplyingLineData::Ptr AttendeeLine::data() const { if ( isModified() ) { const_cast( this )->dataFromFields(); } return mData; } void AttendeeLine::dataFromFields() { if ( !mData ) { return; } KCalCore::Attendee::Ptr oldAttendee( mData->attendee() ); QString email, name; KPIMUtils::extractEmailAddressAndName( mEdit->text(), email, name ); mData->setName( name ); mData->setEmail( email ); mData->setRole( AttendeeData::Role( mRoleCombo->currentIndex() ) ); mData->setStatus( AttendeeData::PartStat( mStateCombo->currentIndex() ) ); mData->setRSVP( mResponseCombo->currentIndex() == 0 ); mData->setUid( mUid ); clearModified(); if ( !( oldAttendee == mData->attendee() ) && !email.isEmpty() ) { // if email is empty, we don't want to update anything kDebug() << oldAttendee->email() << mData->email(); emit changed( oldAttendee, mData->attendee() ); } } void AttendeeLine::fieldsFromData() { if( !mData ) { return; } mEdit->setText( mData->fullName() ); mRoleCombo->setCurrentIndex( mData->role() ); AttendeeData::PartStat partStat = mData->status(); if ( partStat != AttendeeData::None ) { mStateCombo->setCurrentIndex( partStat ); } else { mStateCombo->setCurrentIndex( AttendeeData::NeedsAction ); } mResponseCombo->setCurrentIndex( mData->RSVP() ? 0 : 1 ); mUid = mData->uid(); } void AttendeeLine::fixTabOrder( QWidget *previous ) { setTabOrder( previous, mRoleCombo ); setTabOrder( mRoleCombo, mEdit ); setTabOrder( mEdit, mStateCombo ); setTabOrder( mStateCombo, mResponseCombo ); } QWidget *AttendeeLine::tabOut() const { return mResponseCombo; } bool AttendeeLine::isActive() const { return mEdit->hasFocus(); } bool AttendeeLine::isEmpty() const { return mEdit->text().isEmpty(); } bool AttendeeLine::isModified() const { return mModified || mEdit->isModified(); } void AttendeeLine::moveCompletionPopup() { if ( mEdit->completionBox( false ) ) { if ( mEdit->completionBox()->isVisible() ) { // ### trigger moving, is there a nicer way to do that? mEdit->completionBox()->hide(); mEdit->completionBox()->show(); } } } int AttendeeLine::setColumnWidth( int w ) { w = qMax( w, mRoleCombo->sizeHint().width() ); mRoleCombo->setFixedWidth( w ); mRoleCombo->updateGeometry(); parentWidget()->updateGeometry(); return w; } void AttendeeLine::setActions( AttendeeActions actions ) { mStateCombo->clear(); if ( actions == EventActions ) { #ifdef KDEPIM_MOBILE_UI mStateCombo->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateCombo->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateCombo->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateCombo->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateCombo->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #else mStateCombo->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateCombo->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateCombo->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateCombo->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateCombo->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); #endif } else { #ifdef KDEPIM_MOBILE_UI mStateCombo->addItem( DesktopIcon( "task-attention", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateCombo->addItem( DesktopIcon( "task-accepted", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateCombo->addItem( DesktopIcon( "task-reject", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateCombo->addItem( DesktopIcon( "task-attempt", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateCombo->addItem( DesktopIcon( "task-delegate", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateCombo->addItem( DesktopIcon( "task-complete", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateCombo->addItem( DesktopIcon( "task-ongoing", 48 ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #else mStateCombo->addItem( SmallIcon( "task-attention" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::NeedsAction ) ); mStateCombo->addItem( SmallIcon( "task-accepted" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Accepted ) ); mStateCombo->addItem( SmallIcon( "task-reject" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Declined ) ); mStateCombo->addItem( SmallIcon( "task-attempt" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Tentative ) ); mStateCombo->addItem( SmallIcon( "task-delegate" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Delegated ) ); mStateCombo->addItem( SmallIcon( "task-complete" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::Completed ) ); mStateCombo->addItem( SmallIcon( "task-ongoing" ), KCalUtils::Stringify::attendeeStatus( AttendeeData::InProcess ) ); #endif } } void AttendeeLine::setCompletionMode( KGlobalSettings::Completion mode ) { mEdit->setCompletionMode( mode ); } void AttendeeLine::setData( const KPIM::MultiplyingLineData::Ptr &data ) { AttendeeData::Ptr attendee = qSharedPointerDynamicCast( data ); if ( !attendee ) { return; } mData = attendee; fieldsFromData(); } void AttendeeLine::slotHandleChange() { if ( mEdit->text().isEmpty() ) { emit deleteLine( this ); } else { // has bad side-effects, and I have no idea what this was supposed to be doing // mEdit->setCursorPosition( 0 ); emit editingFinished( this ); dataFromFields(); } } void AttendeeLine::slotTextChanged( const QString &str ) { Q_UNUSED( str ); mModified = true; emit changed(); // TODO: This doesn't seem connected to anywhere in incidenceattendee.cpp. // but the important code is run in slotHandleChange() anyway so we don't see any bug } void AttendeeLine::slotComboChanged() { mModified = true; // If mUid is empty, we're still populating the widget, don't write fields to data yet if ( !mUid.isEmpty() ) dataFromFields(); } void AttendeeLine::aboutToBeDeleted() { if ( !mData ) { return; } emit changed( mData->attendee(), KCalCore::Attendee::Ptr( new KCalCore::Attendee( "", "" ) ) ); }