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
21 #include "incidenceattachment.h"
22 #include "attachmenteditdialog.h"
23 #include "attachmenticonview.h"
24 #ifdef KDEPIM_MOBILE_UI
25 #include "ui_eventortodomoremobile.h"
27 #include "ui_eventortododesktop.h"
30 #include <libkdepimdbusinterfaces/urihandler.h>
32 #include <KABC/VCardDrag>
34 #include <KMime/Message>
37 #include <KActionCollection>
38 #include <KFileDialog>
40 #include <KMessageBox>
41 #include <KProtocolManager>
44 #include <KIO/NetAccess>
49 using namespace IncidenceEditorNG
;
51 #ifdef KDEPIM_MOBILE_UI
52 IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoMore
*ui
)
54 IncidenceAttachment::IncidenceAttachment( Ui::EventOrTodoDesktop
*ui
)
56 : IncidenceEditor( 0 ),
58 mPopupMenu( new KMenu
)
61 setupAttachmentIconView();
62 setObjectName( "IncidenceAttachment" );
64 connect( mUi
->mAddButton
, SIGNAL(clicked()), SLOT(addAttachment()) );
65 connect( mUi
->mRemoveButton
, SIGNAL(clicked()), SLOT(removeSelectedAttachments()) );
68 IncidenceAttachment::~IncidenceAttachment()
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
);
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
);
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() ) {
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
);
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();
129 // No incidence loaded, so if the user added attachments we're dirty.
130 return mAttachmentView
->count() != 0;
136 int IncidenceAttachment::attachmentCount() const
138 return mAttachmentView
->count();
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 ) );
150 QWeakPointer
<AttachmentEditDialog
> dialog( new AttachmentEditDialog( item
, mAttachmentView
) );
152 dialog
.data()->setCaption( i18nc( "@title", "Add Attachment" ) );
153 if ( dialog
.data()->exec() == KDialog::Rejected
) {
156 emit
attachmentCountChanged( mAttachmentView
->count() );
162 void IncidenceAttachment::copyToClipboard()
164 #ifndef QT_NO_CLIPBOARD
165 QApplication::clipboard()->setMimeData( mAttachmentView
->mimeData(), QClipboard::Clipboard
);
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() );
182 void IncidenceAttachment::removeSelectedAttachments()
184 QList
<QListWidgetItem
*> selected
;
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
);
192 KCalCore::Attachment::Ptr att
= attitem
->attachment();
193 labels
<< att
->label();
199 if ( selected
.isEmpty() ) {
203 QString labelsStr
= labels
.join( "<nl/>" );
205 if ( KMessageBox::questionYesNo(
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
) {
215 for ( QList
<QListWidgetItem
*>::iterator
it( selected
.begin() ), end( selected
.end() );
217 int row
= mAttachmentView
->row( *it
);
218 QListWidgetItem
*next
= mAttachmentView
->item( ++row
);
219 QListWidgetItem
*prev
= mAttachmentView
->item( --row
);
221 next
->setSelected( true );
223 prev
->setSelected( true );
228 mAttachmentView
->update();
229 emit
attachmentCountChanged( mAttachmentView
->count() );
233 void IncidenceAttachment::saveAttachment( QListWidgetItem
*item
)
236 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
238 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( item
);
239 if ( !attitem
->attachment() ) {
243 KCalCore::Attachment::Ptr att
= attitem
->attachment();
245 // get the saveas file name
246 QString saveAsFile
= KFileDialog::getSaveFileName(
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
) ) ) {
261 if ( att
->isUri() ) {
262 sourceUrl
= att
->uri();
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
)
286 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
287 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( item
);
288 if ( !attitem
->attachment() ) {
292 KCalCore::Attachment::Ptr att
= attitem
->attachment();
293 if ( att
->isUri() ) {
294 emit
openURL( att
->uri() );
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;
306 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
307 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
308 if ( item
->isSelected() ) {
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 );
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
339 removeSelectedAttachments();
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() ) {
355 #ifdef KDEPIM_MOBILE_UI
356 QPointer
<AttachmentEditDialog
> dialog(
357 new AttachmentEditDialog( attitem
, 0, false ) );
359 QPointer
<AttachmentEditDialog
> dialog(
360 new AttachmentEditDialog( attitem
, mAttachmentView
, false ) );
362 dialog
->setModal( false );
363 connect( dialog
.data(), SIGNAL(hidden()), dialog
.data(), SLOT(delayedDestruct()) );
369 void IncidenceAttachment::slotItemRenamed ( QListWidgetItem
*item
)
372 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
373 static_cast<AttachmentIconItem
*>( item
)->setLabel( item
->text() );
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() ) {
387 mUi
->mRemoveButton
->setEnabled( selected
);
390 /// Private functions
392 void IncidenceAttachment::handlePasteOrDrop( const QMimeData
*mimeData
)
395 bool probablyWeHaveUris
= false;
396 bool weCanCopy
= true;
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
) {
424 probablyWeHaveUris
= true;
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
437 menu
.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
440 menu
.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
444 cancelAction
= menu
.addAction( KIcon( "process-stop" ), i18nc( "@action:inmenu", "C&ancel" ) );
450 if ( !mimeData
->formats().isEmpty() && !probablyWeHaveUris
) {
451 mimeType
= mimeData
->formats().first();
452 data
= mimeData
->data( mimeType
);
453 KMimeType::Ptr mime
= KMimeType::mimeType( mimeType
);
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",
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();
509 mDeleteAction
= new KAction( i18nc( "@action:inmenu remove the attachment",
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 )
546 // new AttachmentIconItem( attachment, mAttachmentView );
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:
559 msg
.setContent( data
);
561 nlabel
= msg
.subject()->asUnicodeString();
564 item
->setData( data
);
565 item
->setLabel( nlabel
);
566 if ( mimeType
.isEmpty() ) {
567 item
->setMimeType( KMimeType::findByContent( data
)->name() );
569 item
->setMimeType( mimeType
);
575 void IncidenceAttachment::addUriAttachment( const QString
&uri
,
576 const QString
&mimeType
,
577 const QString
&label
,
581 AttachmentIconItem
*item
=
582 new AttachmentIconItem( KCalCore::Attachment::Ptr(), mAttachmentView
);
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" );
595 item
->setMimeType( KMimeType::findByUrl( uri
)->name() );
600 if ( KIO::NetAccess::download( uri
, tmpFile
, 0 ) ) {
602 if ( !f
.open( QIODevice::ReadOnly
) ) {
605 const QByteArray data
= f
.readAll();
607 addDataAttachment( data
, mimeType
, label
);
609 KIO::NetAccess::removeTempFile( tmpFile
);
613 #include "moc_incidenceattachment.cpp"