kde-playground/kdepim/incidenceeditor-ng/incidenceattachment.cpp
2015-04-14 21:49:29 +00:00

620 lines
20 KiB
C++

/*
Copyright (c) 2010 Bertjan Broeksema <broeksema@kde.org>
Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
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 <libkdepimdbusinterfaces/urihandler.h>
#include <KABC/VCardDrag>
#include <KMime/Message>
#include <KAction>
#include <KActionCollection>
#include <KFileDialog>
#include <KMenu>
#include <KMessageBox>
#include <KProtocolManager>
#include <KRun>
#include <KIO/Job>
#include <KIO/NetAccess>
#include <QClipboard>
#include <QMimeData>
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<AttachmentIconItem*>(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<AttachmentIconItem*>( item ) );
const KCalCore::Attachment::Ptr listAttachment =
static_cast<AttachmentIconItem*>( 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<AttachmentEditDialog> dialog( new AttachmentEditDialog( item, 0 ) );
#else
QWeakPointer<AttachmentEditDialog> 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<QListWidgetItem *> selected;
QStringList labels;
for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
QListWidgetItem *it = mAttachmentView->item( itemIndex );
if ( it->isSelected() ) {
AttachmentIconItem *attitem = static_cast<AttachmentIconItem *>( it );
if ( attitem ) {
KCalCore::Attachment::Ptr att = attitem->attachment();
labels << att->label();
selected << it;
}
}
}
if ( selected.isEmpty() ) {
return;
}
QString labelsStr = labels.join( "<nl/>" );
if ( KMessageBox::questionYesNo(
0,
i18nc( "@info",
"Do you really want to remove these attachments?<nl/>%1", labelsStr ),
i18nc( "@title:window", "Remove Attachments?" ),
KStandardGuiItem::yes(), KStandardGuiItem::no(),
"calendarRemoveAttachments" ) != KMessageBox::Yes ) {
return;
}
for ( QList<QListWidgetItem*>::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<AttachmentIconItem*>( item ) );
AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( 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<AttachmentIconItem*>( item ) );
AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( 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<AttachmentIconItem*>( item ) );
AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( item );
if ( !attitem->attachment() ) {
return;
}
#ifdef KDEPIM_MOBILE_UI
QPointer<AttachmentEditDialog> dialog(
new AttachmentEditDialog( attitem, 0, false ) );
#else
QPointer<AttachmentEditDialog> 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<AttachmentIconItem *>( item ) );
static_cast<AttachmentIconItem *>( 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<QString,QString> 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 );
}
}