/* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 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 "incidenceattachment.h" #include "attachmenteditdialog.h" #include "attachmenticonview.h" #ifdef KDEPIM_MOBILE_UI #include "ui_dialogmoremobile.h" #else #include "ui_dialogdesktop.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; #ifdef KDEPIM_MOBILE_UI IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoMore *ui ) #else IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoDesktop *ui ) #endif : IncidenceEditor( 0 ), mUi( ui ), mPopupMenu( new KMenu ) { setupActions(); setupAttachmentIconView(); setObjectName( "IncidenceAttachment" ); connect( mUi->mAddButton, SIGNAL(clicked()), SLOT(addAttachment()) ); connect( mUi->mRemoveButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments()) ); } IncidenceAttachment::~IncidenceAttachment() { delete mPopupMenu; } void IncidenceAttachment::load( const KCalCore::Incidence::Ptr &incidence ) { mLoadedIncidence = incidence; mAttachmentView->clear(); KCalCore::Attachment::List attachments = incidence->attachments(); KCalCore::Attachment::List::ConstIterator it; for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) { new AttachmentIconItem( (*it), mAttachmentView ); } mWasDirty = false; } void IncidenceAttachment::save( const KCalCore::Incidence::Ptr &incidence ) { incidence->clearAttachments(); for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); AttachmentIconItem *attitem = dynamic_cast(item); Q_ASSERT( item ); incidence->addAttachment( KCalCore::Attachment::Ptr( new KCalCore::Attachment( *( attitem->attachment() ) ) ) ); } } bool IncidenceAttachment::isDirty() const { if ( mLoadedIncidence ) { if ( mAttachmentView->count() != mLoadedIncidence->attachments().count() ) { return true; } KCalCore::Attachment::List origAttachments = mLoadedIncidence->attachments(); for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); Q_ASSERT( dynamic_cast( item ) ); const KCalCore::Attachment::Ptr listAttachment = static_cast( item )->attachment(); for ( int i = 0; i < origAttachments.count(); ++i ) { const KCalCore::Attachment::Ptr attachment = origAttachments.at( i ); if ( *attachment == *listAttachment ) { origAttachments.remove( i ); break; } } } // All attachments are removed from the list, meaning, the items in mAttachmentView // are equal to the attachments set on mLoadedIncidence. return !origAttachments.isEmpty(); } else { // No incidence loaded, so if the user added attachments we're dirty. return mAttachmentView->count() != 0; } return false; } int IncidenceAttachment::attachmentCount() const { return mAttachmentView->count(); } /// Private slots void IncidenceAttachment::addAttachment() { AttachmentIconItem *item = new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView ); #ifdef KDEPIM_MOBILE_UI QWeakPointer dialog( new AttachmentEditDialog( item, 0 ) ); #else QWeakPointer dialog( new AttachmentEditDialog( item, mAttachmentView ) ); #endif dialog.data()->setCaption( i18nc( "@title", "Add Attachment" ) ); if ( dialog.data()->exec() == KDialog::Rejected ) { delete item; } else { emit attachmentCountChanged( mAttachmentView->count() ); } if ( dialog.data() ) dialog.data()->deleteLater(); checkDirtyStatus(); } void IncidenceAttachment::copyToClipboard() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setMimeData( mAttachmentView->mimeData(), QClipboard::Clipboard ); #endif } void IncidenceAttachment::openURL( const KUrl &url ) { QString uri = url.url(); UriHandler::process( uri ); } void IncidenceAttachment::pasteFromClipboard() { #ifndef QT_NO_CLIPBOARD handlePasteOrDrop( QApplication::clipboard()->mimeData() ); #endif } void IncidenceAttachment::removeSelectedAttachments() { QList selected; QStringList labels; for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *it = mAttachmentView->item( itemIndex ); if ( it->isSelected() ) { AttachmentIconItem *attitem = static_cast( it ); if ( attitem ) { KCalCore::Attachment::Ptr att = attitem->attachment(); labels << att->label(); selected << it; } } } if ( selected.isEmpty() ) { return; } QString labelsStr = labels.join( "" ); if ( KMessageBox::questionYesNo( 0, i18nc( "@info", "Do you really want to remove these attachments?%1", labelsStr ), i18nc( "@title:window", "Remove Attachments?" ), KStandardGuiItem::yes(), KStandardGuiItem::no(), "calendarRemoveAttachments" ) != KMessageBox::Yes ) { return; } for ( QList::iterator it( selected.begin() ), end( selected.end() ); it != end; ++it ) { int row = mAttachmentView->row( *it ); QListWidgetItem *next = mAttachmentView->item( ++row ); QListWidgetItem *prev = mAttachmentView->item( --row ); if ( next ) { next->setSelected( true ); } else if ( prev ) { prev->setSelected( true ); } delete *it; } mAttachmentView->update(); emit attachmentCountChanged( mAttachmentView->count() ); checkDirtyStatus(); } void IncidenceAttachment::saveAttachment( QListWidgetItem *item ) { Q_ASSERT( item ); Q_ASSERT( dynamic_cast( item ) ); AttachmentIconItem *attitem = static_cast( item ); if ( !attitem->attachment() ) { return; } KCalCore::Attachment::Ptr att = attitem->attachment(); // get the saveas file name QString saveAsFile = KFileDialog::getSaveFileName( att->label(), QString(), 0, i18nc( "@title", "Save Attachment" ) ); if ( saveAsFile.isEmpty() || ( QFile( saveAsFile ).exists() && ( KMessageBox::warningYesNo( 0, i18nc( "@info", "%1 already exists. Do you want to overwrite it?", saveAsFile ) ) == KMessageBox::No ) ) ) { return; } KUrl sourceUrl; if ( att->isUri() ) { sourceUrl = att->uri(); } else { sourceUrl = mAttachmentView->tempFileForAttachment( att ); } // save the attachment url if ( !KIO::NetAccess::file_copy( sourceUrl, KUrl( saveAsFile ) ) && KIO::NetAccess::lastError() ) { KMessageBox::error( 0, KIO::NetAccess::lastErrorString() ); } } void IncidenceAttachment::saveSelectedAttachments() { for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); if ( item->isSelected() ) { saveAttachment( item ); } } } void IncidenceAttachment::showAttachment( QListWidgetItem *item ) { Q_ASSERT( item ); Q_ASSERT( dynamic_cast( item ) ); AttachmentIconItem *attitem = static_cast( item ); if ( !attitem->attachment() ) { return; } KCalCore::Attachment::Ptr att = attitem->attachment(); if ( att->isUri() ) { emit openURL( att->uri() ); } else { KRun::runUrl( mAttachmentView->tempFileForAttachment( att ), att->mimeType(), 0, true ); } } void IncidenceAttachment::showContextMenu( const QPoint &pos ) { QListWidgetItem *item = mAttachmentView->itemAt( pos ); const bool enable = item != 0; int numSelected = 0; for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); if ( item->isSelected() ) { numSelected++; } } mOpenAction->setEnabled( enable ); //TODO: support saving multiple attachments into a directory mSaveAsAction->setEnabled( enable && numSelected == 1 ); #ifndef QT_NO_CLIPBOARD mCopyAction->setEnabled( enable && numSelected == 1 ); mCutAction->setEnabled( enable && numSelected == 1 ); #endif mDeleteAction->setEnabled( enable ); mEditAction->setEnabled( enable ); mPopupMenu->exec( mAttachmentView->mapToGlobal( pos ) ); } void IncidenceAttachment::showSelectedAttachments() { for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); if ( item->isSelected() ) { showAttachment( item ); } } } void IncidenceAttachment::cutToClipboard() { #ifndef QT_NO_CLIPBOARD copyToClipboard(); removeSelectedAttachments(); #endif } void IncidenceAttachment::editSelectedAttachments() { for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); if ( item->isSelected() ) { Q_ASSERT( dynamic_cast( item ) ); AttachmentIconItem *attitem = static_cast( item ); if ( !attitem->attachment() ) { return; } #ifdef KDEPIM_MOBILE_UI QPointer dialog( new AttachmentEditDialog( attitem, 0, false ) ); #else QPointer dialog( new AttachmentEditDialog( attitem, mAttachmentView, false ) ); #endif dialog->setModal( false ); connect( dialog.data(), SIGNAL(hidden()), dialog.data(), SLOT(delayedDestruct()) ); dialog->show(); } } } void IncidenceAttachment::slotItemRenamed ( QListWidgetItem *item ) { Q_ASSERT( item ); Q_ASSERT( dynamic_cast( item ) ); static_cast( item )->setLabel( item->text() ); checkDirtyStatus(); } void IncidenceAttachment::slotSelectionChanged() { bool selected = false; for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) { QListWidgetItem *item = mAttachmentView->item( itemIndex ); if ( item->isSelected() ) { selected = true; break; } } mUi->mRemoveButton->setEnabled( selected ); } /// Private functions void IncidenceAttachment::handlePasteOrDrop( const QMimeData *mimeData ) { KUrl::List urls; bool probablyWeHaveUris = false; QStringList labels; if ( KABC::VCardDrag::canDecode( mimeData ) ) { KABC::Addressee::List addressees; KABC::VCardDrag::fromMimeData( mimeData, addressees ); for ( KABC::Addressee::List::ConstIterator it = addressees.constBegin(); it != addressees.constEnd(); ++it ) { urls.append( QString( QLatin1String( "uid:" ) + ( *it ).uid() ) ); // there is some weirdness about realName(), hence fromUtf8 labels.append( QString::fromUtf8( ( *it ).realName().toLatin1() ) ); } probablyWeHaveUris = true; } else if ( KUrl::List::canDecode( mimeData ) ) { QMap metadata; urls = KUrl::List::fromMimeData( mimeData, &metadata ); probablyWeHaveUris = true; labels = metadata["labels"].split( ':', QString::SkipEmptyParts ); for ( QStringList::Iterator it = labels.begin(); it != labels.end(); ++it ) { *it = KUrl::fromPercentEncoding( (*it).toLatin1() ); } } else if ( mimeData->hasText() ) { QString text = mimeData->text(); QStringList lst = text.split( '\n', QString::SkipEmptyParts ); for ( QStringList::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it ) { urls.append( *it ); } probablyWeHaveUris = true; } KMenu menu; QAction *linkAction = 0, *cancelAction; if ( probablyWeHaveUris ) { linkAction = menu.addAction( KIcon( "insert-link" ), i18nc( "@action:inmenu", "&Link here" ) ); // we need to check if we can reasonably expect to copy the objects bool weCanCopy = true; for ( KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it ) { if ( !( weCanCopy = KProtocolManager::supportsReading( *it ) ) ) { break; // either we can copy them all, or no copying at all } } if ( weCanCopy ) { menu.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) ); } } else { menu.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) ); } menu.addSeparator(); cancelAction = menu.addAction( KIcon( "process-stop" ), i18nc( "@action:inmenu", "C&ancel" ) ); QByteArray data; QString mimeType; QString label; if ( !mimeData->formats().isEmpty() && !probablyWeHaveUris ) { mimeType = mimeData->formats().first(); data = mimeData->data( mimeType ); KMimeType::Ptr mime = KMimeType::mimeType( mimeType ); if ( mime ) { label = mime->comment(); } } QAction *ret = menu.exec( QCursor::pos() ); if ( linkAction == ret ) { QStringList::ConstIterator jt = labels.constBegin(); for ( KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it ) { addUriAttachment( (*it).url(), QString(), ( jt == labels.constEnd() ? QString() : *( jt++ ) ), true ); } } else if ( cancelAction != ret ) { if ( probablyWeHaveUris ) { for ( KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it ) { KIO::Job *job = KIO::storedGet( *it ); connect( job, SIGNAL(result(KJob*)), SLOT(downloadComplete(KJob*)) ); } } else { // we take anything addDataAttachment( data, mimeType, label ); } } } void IncidenceAttachment::downloadComplete(KJob*) { //TODO } void IncidenceAttachment::setupActions() { KActionCollection *ac = new KActionCollection( this ); // ac->addAssociatedWidget( this ); mOpenAction = new KAction( i18nc( "@action:inmenu open the attachment in a viewer", "&Open" ), this ); connect( mOpenAction, SIGNAL(triggered(bool)), SLOT(showSelectedAttachments()) ); ac->addAction( "view", mOpenAction ); mPopupMenu->addAction( mOpenAction ); mSaveAsAction = new KAction( i18nc( "@action:inmenu save the attachment to a file", "Save As..." ), this ); connect( mSaveAsAction, SIGNAL(triggered(bool)), SLOT(saveSelectedAttachments()) ); mPopupMenu->addAction( mSaveAsAction ); mPopupMenu->addSeparator(); #ifndef QT_NO_CLIPBOARD mCopyAction = KStandardAction::copy( this, SLOT(copyToClipboard()), ac ); mPopupMenu->addAction( mCopyAction ); mCutAction = KStandardAction::cut( this, SLOT(cutToClipboard()), ac ); mPopupMenu->addAction( mCutAction ); KAction *action = KStandardAction::paste( this, SLOT(pasteFromClipboard()), ac ); mPopupMenu->addAction( action ); mPopupMenu->addSeparator(); #endif mDeleteAction = new KAction( i18nc( "@action:inmenu remove the attachment", "&Remove" ), this ); connect( mDeleteAction, SIGNAL(triggered(bool)), SLOT(removeSelectedAttachments()) ); ac->addAction( "remove", mDeleteAction ); mDeleteAction->setShortcut( Qt::Key_Delete ); mPopupMenu->addAction( mDeleteAction ); mPopupMenu->addSeparator(); mEditAction = new KAction( i18nc( "@action:inmenu show a dialog used to edit the attachment", "&Properties..." ), this ); connect( mEditAction, SIGNAL(triggered(bool)), SLOT(editSelectedAttachments()) ); ac->addAction( "edit", mEditAction ); mPopupMenu->addAction( mEditAction ); } void IncidenceAttachment::setupAttachmentIconView() { mAttachmentView = new AttachmentIconView; mAttachmentView->setWhatsThis( i18nc( "@info:whatsthis", "Displays items (files, mail, etc.) that " "have been associated with this event or to-do." ) ); connect( mAttachmentView, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(showAttachment(QListWidgetItem*)) ); connect( mAttachmentView, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(slotItemRenamed(QListWidgetItem*)) ); connect( mAttachmentView, SIGNAL(itemSelectionChanged()), SLOT(slotSelectionChanged()) ); connect( mAttachmentView, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showContextMenu(QPoint)) ); QGridLayout *layout = new QGridLayout( mUi->mAttachmentViewPlaceHolder ); layout->addWidget( mAttachmentView ); } // void IncidenceAttachmentEditor::addAttachment( KCalCore::Attachment *attachment ) // { // new AttachmentIconItem( attachment, mAttachmentView ); // } void IncidenceAttachment::addDataAttachment( const QByteArray &data, const QString &mimeType, const QString &label ) { AttachmentIconItem *item = new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView ); QString nlabel = label; if ( mimeType == "message/rfc822" ) { // mail message. try to set the label from the mail Subject: KMime::Message msg; msg.setContent( data ); msg.parse(); nlabel = msg.subject()->asUnicodeString(); } item->setData( data ); item->setLabel( nlabel ); if ( mimeType.isEmpty() ) { item->setMimeType( KMimeType::findByContent( data )->name() ); } else { item->setMimeType( mimeType ); } checkDirtyStatus(); } void IncidenceAttachment::addUriAttachment( const QString &uri, const QString &mimeType, const QString &label, bool inLine ) { if ( !inLine ) { AttachmentIconItem *item = new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView ); item->setUri( uri ); item->setLabel( label ); if ( mimeType.isEmpty() ) { if ( uri.startsWith( QLatin1String( "uid:" ) ) ) { item->setMimeType( "text/directory" ); } else if ( uri.startsWith( QLatin1String( "kmail:" ) ) ) { item->setMimeType( "message/rfc822" ); } else if ( uri.startsWith( QLatin1String( "urn:x-ical" ) ) ) { item->setMimeType( "text/calendar" ); } else if ( uri.startsWith( QLatin1String( "news:" ) ) ) { item->setMimeType( "message/news" ); } else { item->setMimeType( KMimeType::findByUrl( uri )->name() ); } } } else { QString tmpFile; if ( KIO::NetAccess::download( uri, tmpFile, 0 ) ) { QFile f( tmpFile ); if ( !f.open( QIODevice::ReadOnly ) ) { return; } const QByteArray data = f.readAll(); f.close(); addDataAttachment( data, mimeType, label ); } KIO::NetAccess::removeTempFile( tmpFile ); } }