Better wording
[kdepim.git] / incidenceeditor-ng / incidenceattachment.cpp
blob232a0dc349eb494dd5ff3f0f914bea1169ea2273
1 /*
2 Copyright (c) 2010 Bertjan Broeksema <broeksema@kde.org>
3 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
21 #include "incidenceattachment.h"
22 #include "attachmenteditdialog.h"
23 #include "attachmenticonview.h"
24 #ifdef KDEPIM_MOBILE_UI
25 #include "ui_eventortodomoremobile.h"
26 #else
27 #include "ui_eventortododesktop.h"
28 #endif
30 #include <libkdepimdbusinterfaces/urihandler.h>
32 #include <KABC/VCardDrag>
34 #include <KMime/Message>
36 #include <KAction>
37 #include <KActionCollection>
38 #include <KFileDialog>
39 #include <KMenu>
40 #include <KMessageBox>
41 #include <KProtocolManager>
42 #include <KRun>
43 #include <KIO/Job>
44 #include <KIO/NetAccess>
46 #include <QClipboard>
47 #include <QMimeData>
49 using namespace IncidenceEditorNG;
51 #ifdef KDEPIM_MOBILE_UI
52 IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoMore *ui )
53 #else
54 IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoDesktop *ui )
55 #endif
56 : IncidenceEditor( 0 ),
57 mUi( ui ),
58 mPopupMenu( new KMenu )
60 setupActions();
61 setupAttachmentIconView();
62 setObjectName( "IncidenceAttachment" );
64 connect( mUi->mAddButton, SIGNAL(clicked()), SLOT(addAttachment()) );
65 connect( mUi->mRemoveButton, SIGNAL(clicked()), SLOT(removeSelectedAttachments()) );
68 IncidenceAttachment::~IncidenceAttachment()
70 delete mPopupMenu;
73 void IncidenceAttachment::load( const KCalCore::Incidence::Ptr &incidence )
75 mLoadedIncidence = incidence;
76 mAttachmentView->clear();
78 KCalCore::Attachment::List attachments = incidence->attachments();
79 KCalCore::Attachment::List::ConstIterator it;
80 for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
81 new AttachmentIconItem( (*it), mAttachmentView );
84 mWasDirty = false;
87 void IncidenceAttachment::save( const KCalCore::Incidence::Ptr &incidence )
89 incidence->clearAttachments();
91 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
92 QListWidgetItem *item = mAttachmentView->item( itemIndex );
93 AttachmentIconItem *attitem = dynamic_cast<AttachmentIconItem*>(item);
94 Q_ASSERT( item );
95 incidence->addAttachment(
96 KCalCore::Attachment::Ptr( new KCalCore::Attachment( *( attitem->attachment() ) ) ) );
100 bool IncidenceAttachment::isDirty() const
102 if ( mLoadedIncidence ) {
103 if ( mAttachmentView->count() != mLoadedIncidence->attachments().count() ) {
104 return true;
107 KCalCore::Attachment::List origAttachments = mLoadedIncidence->attachments();
108 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
109 QListWidgetItem *item = mAttachmentView->item( itemIndex );
110 Q_ASSERT( dynamic_cast<AttachmentIconItem*>( item ) );
112 const KCalCore::Attachment::Ptr listAttachment =
113 static_cast<AttachmentIconItem*>( item )->attachment();
115 for ( int i = 0; i < origAttachments.count(); ++i ) {
116 const KCalCore::Attachment::Ptr attachment = origAttachments.at( i );
118 if ( *attachment == *listAttachment ) {
119 origAttachments.remove( i );
120 break;
124 // All attachments are removed from the list, meaning, the items in mAttachmentView
125 // are equal to the attachments set on mLoadedIncidence.
126 return !origAttachments.isEmpty();
128 } else {
129 // No incidence loaded, so if the user added attachments we're dirty.
130 return mAttachmentView->count() != 0;
133 return false;
136 int IncidenceAttachment::attachmentCount() const
138 return mAttachmentView->count();
141 /// Private slots
143 void IncidenceAttachment::addAttachment()
145 AttachmentIconItem *item = new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView );
147 #ifdef KDEPIM_MOBILE_UI
148 QWeakPointer<AttachmentEditDialog> dialog( new AttachmentEditDialog( item, 0 ) );
149 #else
150 QWeakPointer<AttachmentEditDialog> dialog( new AttachmentEditDialog( item, mAttachmentView ) );
151 #endif
152 dialog.data()->setCaption( i18nc( "@title", "Add Attachment" ) );
153 if ( dialog.data()->exec() == KDialog::Rejected ) {
154 delete item;
155 } else {
156 emit attachmentCountChanged( mAttachmentView->count() );
159 checkDirtyStatus();
162 void IncidenceAttachment::copyToClipboard()
164 #ifndef QT_NO_CLIPBOARD
165 QApplication::clipboard()->setMimeData( mAttachmentView->mimeData(), QClipboard::Clipboard );
166 #endif
169 void IncidenceAttachment::openURL( const KUrl &url )
171 QString uri = url.url();
172 UriHandler::process( uri );
175 void IncidenceAttachment::pasteFromClipboard()
177 #ifndef QT_NO_CLIPBOARD
178 handlePasteOrDrop( QApplication::clipboard()->mimeData() );
179 #endif
182 void IncidenceAttachment::removeSelectedAttachments()
184 QList<QListWidgetItem *> selected;
185 QStringList labels;
187 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
188 QListWidgetItem *it = mAttachmentView->item( itemIndex );
189 if ( it->isSelected() ) {
190 AttachmentIconItem *attitem = static_cast<AttachmentIconItem *>( it );
191 if ( attitem ) {
192 KCalCore::Attachment::Ptr att = attitem->attachment();
193 labels << att->label();
194 selected << it;
199 if ( selected.isEmpty() ) {
200 return;
203 QString labelsStr = labels.join( "<nl/>" );
205 if ( KMessageBox::questionYesNo(
207 i18nc( "@info",
208 "Do you really want to remove these attachments?<nl/>%1", labelsStr ),
209 i18nc( "@title:window", "Remove Attachments?" ),
210 KStandardGuiItem::yes(), KStandardGuiItem::no(),
211 "calendarRemoveAttachments" ) != KMessageBox::Yes ) {
212 return;
215 for ( QList<QListWidgetItem*>::iterator it( selected.begin() ), end( selected.end() );
216 it != end; ++it ) {
217 int row = mAttachmentView->row( *it );
218 QListWidgetItem *next = mAttachmentView->item( ++row );
219 QListWidgetItem *prev = mAttachmentView->item( --row );
220 if ( next ) {
221 next->setSelected( true );
222 } else if ( prev ) {
223 prev->setSelected( true );
225 delete *it;
228 mAttachmentView->update();
229 emit attachmentCountChanged( mAttachmentView->count() );
230 checkDirtyStatus();
233 void IncidenceAttachment::saveAttachment( QListWidgetItem *item )
235 Q_ASSERT( item );
236 Q_ASSERT( dynamic_cast<AttachmentIconItem*>( item ) );
238 AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( item );
239 if ( !attitem->attachment() ) {
240 return;
243 KCalCore::Attachment::Ptr att = attitem->attachment();
245 // get the saveas file name
246 QString saveAsFile = KFileDialog::getSaveFileName(
247 att->label(),
248 QString(), 0,
249 i18nc( "@title", "Save Attachment" ) );
251 if ( saveAsFile.isEmpty() ||
252 ( QFile( saveAsFile ).exists() &&
253 ( KMessageBox::warningYesNo(
255 i18nc( "@info", "%1 already exists. Do you want to overwrite it?",
256 saveAsFile ) ) == KMessageBox::No ) ) ) {
257 return;
260 KUrl sourceUrl;
261 if ( att->isUri() ) {
262 sourceUrl = att->uri();
263 } else {
264 sourceUrl = mAttachmentView->tempFileForAttachment( att );
266 // save the attachment url
267 if ( !KIO::NetAccess::file_copy( sourceUrl, KUrl( saveAsFile ) ) &&
268 KIO::NetAccess::lastError() ) {
269 KMessageBox::error( 0, KIO::NetAccess::lastErrorString() );
273 void IncidenceAttachment::saveSelectedAttachments()
275 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
276 QListWidgetItem *item = mAttachmentView->item( itemIndex );
277 if ( item->isSelected() ) {
278 saveAttachment( item );
283 void IncidenceAttachment::showAttachment( QListWidgetItem *item )
285 Q_ASSERT( item );
286 Q_ASSERT( dynamic_cast<AttachmentIconItem*>( item ) );
287 AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( item );
288 if ( !attitem->attachment() ) {
289 return;
292 KCalCore::Attachment::Ptr att = attitem->attachment();
293 if ( att->isUri() ) {
294 emit openURL( att->uri() );
295 } else {
296 KRun::runUrl( mAttachmentView->tempFileForAttachment( att ), att->mimeType(), 0, true );
300 void IncidenceAttachment::showContextMenu( const QPoint &pos )
302 QListWidgetItem *item = mAttachmentView->itemAt( pos );
303 const bool enable = item != 0;
305 int numSelected = 0;
306 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
307 QListWidgetItem *item = mAttachmentView->item( itemIndex );
308 if ( item->isSelected() ) {
309 numSelected++;
313 mOpenAction->setEnabled( enable );
314 //TODO: support saving multiple attachments into a directory
315 mSaveAsAction->setEnabled( enable && numSelected == 1 );
316 #ifndef QT_NO_CLIPBOARD
317 mCopyAction->setEnabled( enable && numSelected == 1 );
318 mCutAction->setEnabled( enable && numSelected == 1 );
319 #endif
320 mDeleteAction->setEnabled( enable );
321 mEditAction->setEnabled( enable );
322 mPopupMenu->exec( mAttachmentView->mapToGlobal( pos ) );
325 void IncidenceAttachment::showSelectedAttachments()
327 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
328 QListWidgetItem *item = mAttachmentView->item( itemIndex );
329 if ( item->isSelected() ) {
330 showAttachment( item );
335 void IncidenceAttachment::cutToClipboard()
337 #ifndef QT_NO_CLIPBOARD
338 copyToClipboard();
339 removeSelectedAttachments();
340 #endif
343 void IncidenceAttachment::editSelectedAttachments()
345 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
346 QListWidgetItem *item = mAttachmentView->item( itemIndex );
347 if ( item->isSelected() ) {
348 Q_ASSERT( dynamic_cast<AttachmentIconItem*>( item ) );
350 AttachmentIconItem *attitem = static_cast<AttachmentIconItem*>( item );
351 if ( !attitem->attachment() ) {
352 return;
355 #ifdef KDEPIM_MOBILE_UI
356 QPointer<AttachmentEditDialog> dialog(
357 new AttachmentEditDialog( attitem, 0, false ) );
358 #else
359 QPointer<AttachmentEditDialog> dialog(
360 new AttachmentEditDialog( attitem, mAttachmentView, false ) );
361 #endif
362 dialog->setModal( false );
363 connect( dialog.data(), SIGNAL(hidden()), dialog.data(), SLOT(delayedDestruct()) );
364 dialog->show();
369 void IncidenceAttachment::slotItemRenamed ( QListWidgetItem *item )
371 Q_ASSERT( item );
372 Q_ASSERT( dynamic_cast<AttachmentIconItem *>( item ) );
373 static_cast<AttachmentIconItem *>( item )->setLabel( item->text() );
374 checkDirtyStatus();
377 void IncidenceAttachment::slotSelectionChanged()
379 bool selected = false;
380 for ( int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex ) {
381 QListWidgetItem *item = mAttachmentView->item( itemIndex );
382 if ( item->isSelected() ) {
383 selected = true;
384 break;
387 mUi->mRemoveButton->setEnabled( selected );
390 /// Private functions
392 void IncidenceAttachment::handlePasteOrDrop( const QMimeData *mimeData )
394 KUrl::List urls;
395 bool probablyWeHaveUris = false;
396 bool weCanCopy = true;
397 QStringList labels;
399 if ( KABC::VCardDrag::canDecode( mimeData ) ) {
400 KABC::Addressee::List addressees;
401 KABC::VCardDrag::fromMimeData( mimeData, addressees );
402 for ( KABC::Addressee::List::ConstIterator it = addressees.constBegin();
403 it != addressees.constEnd(); ++it ) {
404 urls.append( QString( QLatin1String( "uid:" ) + ( *it ).uid() ) );
405 // there is some weirdness about realName(), hence fromUtf8
406 labels.append( QString::fromUtf8( ( *it ).realName().toLatin1() ) );
408 probablyWeHaveUris = true;
409 } else if ( KUrl::List::canDecode( mimeData ) ) {
410 QMap<QString,QString> metadata;
412 urls = KUrl::List::fromMimeData( mimeData, &metadata );
413 probablyWeHaveUris = true;
414 labels = metadata["labels"].split( ':', QString::SkipEmptyParts );
415 for ( QStringList::Iterator it = labels.begin(); it != labels.end(); ++it ) {
416 *it = KUrl::fromPercentEncoding( (*it).toLatin1() );
418 } else if ( mimeData->hasText() ) {
419 QString text = mimeData->text();
420 QStringList lst = text.split( '\n', QString::SkipEmptyParts );
421 for ( QStringList::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it ) {
422 urls.append( *it );
424 probablyWeHaveUris = true;
426 KMenu menu;
427 QAction *linkAction = 0, *cancelAction;
428 if ( probablyWeHaveUris ) {
429 linkAction = menu.addAction( KIcon( "insert-link" ), i18nc( "@action:inmenu", "&Link here" ) );
430 // we need to check if we can reasonably expect to copy the objects
431 for ( KUrl::List::ConstIterator it = urls.constBegin(); it != urls.constEnd(); ++it ) {
432 if ( !( weCanCopy = KProtocolManager::supportsReading( *it ) ) ) {
433 break; // either we can copy them all, or no copying at all
436 if ( weCanCopy ) {
437 menu.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
439 } else {
440 menu.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
443 menu.addSeparator();
444 cancelAction = menu.addAction( KIcon( "process-stop" ), i18nc( "@action:inmenu", "C&ancel" ) );
446 QByteArray data;
447 QString mimeType;
448 QString label;
450 if ( !mimeData->formats().isEmpty() && !probablyWeHaveUris ) {
451 mimeType = mimeData->formats().first();
452 data = mimeData->data( mimeType );
453 KMimeType::Ptr mime = KMimeType::mimeType( mimeType );
454 if ( mime ) {
455 label = mime->comment();
459 QAction *ret = menu.exec( QCursor::pos() );
460 if ( linkAction == ret ) {
461 QStringList::ConstIterator jt = labels.constBegin();
462 for ( KUrl::List::ConstIterator it = urls.constBegin();
463 it != urls.constEnd(); ++it ) {
464 addUriAttachment( (*it).url(), QString(), ( jt == labels.constEnd() ?
465 QString() : *( jt++ ) ), true );
467 } else if ( cancelAction != ret ) {
468 if ( probablyWeHaveUris ) {
469 for ( KUrl::List::ConstIterator it = urls.constBegin();
470 it != urls.constEnd(); ++it ) {
471 KIO::Job *job = KIO::storedGet( *it );
472 connect( job, SIGNAL(result(KJob*)), SLOT(downloadComplete(KJob*)) );
474 } else { // we take anything
475 addDataAttachment( data, mimeType, label );
480 void IncidenceAttachment::setupActions()
482 KActionCollection *ac = new KActionCollection( this );
483 // ac->addAssociatedWidget( this );
485 mOpenAction = new KAction( i18nc( "@action:inmenu open the attachment in a viewer",
486 "&Open" ), this );
487 connect( mOpenAction, SIGNAL(triggered(bool)), SLOT(showSelectedAttachments()) );
488 ac->addAction( "view", mOpenAction );
489 mPopupMenu->addAction( mOpenAction );
491 mSaveAsAction = new KAction( i18nc( "@action:inmenu save the attachment to a file",
492 "Save As..." ), this );
493 connect( mSaveAsAction, SIGNAL(triggered(bool)), SLOT(saveSelectedAttachments()) );
494 mPopupMenu->addAction( mSaveAsAction );
495 mPopupMenu->addSeparator();
497 #ifndef QT_NO_CLIPBOARD
498 mCopyAction = KStandardAction::copy( this, SLOT(copyToClipboard()), ac );
499 mPopupMenu->addAction( mCopyAction );
501 mCutAction = KStandardAction::cut( this, SLOT(cutToClipboard()), ac );
502 mPopupMenu->addAction( mCutAction );
504 KAction *action = KStandardAction::paste( this, SLOT(pasteFromClipboard()), ac );
505 mPopupMenu->addAction( action );
506 mPopupMenu->addSeparator();
507 #endif
509 mDeleteAction = new KAction( i18nc( "@action:inmenu remove the attachment",
510 "&Remove" ), this );
511 connect( mDeleteAction, SIGNAL(triggered(bool)), SLOT(removeSelectedAttachments()) );
512 ac->addAction( "remove", mDeleteAction );
513 mDeleteAction->setShortcut( Qt::Key_Delete );
514 mPopupMenu->addAction( mDeleteAction );
515 mPopupMenu->addSeparator();
517 mEditAction = new KAction( i18nc( "@action:inmenu show a dialog used to edit the attachment",
518 "&Properties..." ), this );
519 connect( mEditAction, SIGNAL(triggered(bool)), SLOT(editSelectedAttachments()) );
520 ac->addAction( "edit", mEditAction );
521 mPopupMenu->addAction( mEditAction );
524 void IncidenceAttachment::setupAttachmentIconView()
526 mAttachmentView = new AttachmentIconView;
527 mAttachmentView->setWhatsThis( i18nc( "@info:whatsthis",
528 "Displays items (files, mail, etc.) that "
529 "have been associated with this event or to-do." ) );
531 connect( mAttachmentView, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
532 SLOT(showAttachment(QListWidgetItem*)) );
533 connect( mAttachmentView, SIGNAL(itemChanged(QListWidgetItem*)),
534 SLOT(slotItemRenamed(QListWidgetItem*)) );
535 connect( mAttachmentView, SIGNAL(itemSelectionChanged()),
536 SLOT(slotSelectionChanged()) );
537 connect( mAttachmentView, SIGNAL(customContextMenuRequested(QPoint)),
538 SLOT(showContextMenu(QPoint)) );
540 QGridLayout *layout = new QGridLayout( mUi->mAttachmentViewPlaceHolder );
541 layout->addWidget( mAttachmentView );
544 // void IncidenceAttachmentEditor::addAttachment( KCalCore::Attachment *attachment )
545 // {
546 // new AttachmentIconItem( attachment, mAttachmentView );
547 // }
549 void IncidenceAttachment::addDataAttachment( const QByteArray &data,
550 const QString &mimeType,
551 const QString &label )
553 AttachmentIconItem *item = new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView );
555 QString nlabel = label;
556 if ( mimeType == "message/rfc822" ) {
557 // mail message. try to set the label from the mail Subject:
558 KMime::Message msg;
559 msg.setContent( data );
560 msg.parse();
561 nlabel = msg.subject()->asUnicodeString();
564 item->setData( data );
565 item->setLabel( nlabel );
566 if ( mimeType.isEmpty() ) {
567 item->setMimeType( KMimeType::findByContent( data )->name() );
568 } else {
569 item->setMimeType( mimeType );
572 checkDirtyStatus();
575 void IncidenceAttachment::addUriAttachment( const QString &uri,
576 const QString &mimeType,
577 const QString &label,
578 bool inLine )
580 if ( !inLine ) {
581 AttachmentIconItem *item =
582 new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView );
583 item->setUri( uri );
584 item->setLabel( label );
585 if ( mimeType.isEmpty() ) {
586 if ( uri.startsWith( QLatin1String( "uid:" ) ) ) {
587 item->setMimeType( "text/directory" );
588 } else if ( uri.startsWith( QLatin1String( "kmail:" ) ) ) {
589 item->setMimeType( "message/rfc822" );
590 } else if ( uri.startsWith( QLatin1String( "urn:x-ical" ) ) ) {
591 item->setMimeType( "text/calendar" );
592 } else if ( uri.startsWith( QLatin1String( "news:" ) ) ) {
593 item->setMimeType( "message/news" );
594 } else {
595 item->setMimeType( KMimeType::findByUrl( uri )->name() );
598 } else {
599 QString tmpFile;
600 if ( KIO::NetAccess::download( uri, tmpFile, 0 ) ) {
601 QFile f( tmpFile );
602 if ( !f.open( QIODevice::ReadOnly ) ) {
603 return;
605 const QByteArray data = f.readAll();
606 f.close();
607 addDataAttachment( data, mimeType, label );
609 KIO::NetAccess::removeTempFile( tmpFile );
613 #include "moc_incidenceattachment.cpp"