2 Copyright (C) 2010 Bertjan Broeksema b.broeksema@home.nl
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "incidenceattachmenteditor.h"
22 #include <QtCore/QDebug>
23 #include <QtCore/QMimeData>
24 #include <QtCore/QPointer>
25 #include <QtGui/QClipboard>
28 #include <KDE/KABC/VCardDrag>
29 #include <KDE/KAction>
30 #include <KDE/KActionCollection>
31 #include <KDE/KFileDialog>
32 #include <KDE/KIO/Job>
33 #include <KDE/KIO/NetAccess>
34 #include <KDE/KMime/Message>
36 #include <KDE/KMessageBox>
37 #include <KDE/KProtocolManager>
40 #include <libkdepimdbusinterfaces/urihandler.h>
42 #include "attachmenticonview.h"
43 #include "attachmenteditdialog.h"
44 #include "ui_incidenceattachmenteditor.h"
46 using namespace IncidenceEditorsNG
;
48 IncidenceAttachmentEditor::IncidenceAttachmentEditor( QWidget
*parent
)
49 : IncidenceEditor( parent
)
50 , mUi( new Ui::IncidenceAttachmentEditor
)
51 , mPopupMenu( new KMenu( this ) )
54 mUi
->mAddButton
->setIcon( KIcon( "list-add" ) );
55 mUi
->mRemoveButton
->setIcon( KIcon( "list-remove" ) );
58 setupAttachmentIconView();
60 connect( mUi
->mAddButton
, SIGNAL(clicked()), SLOT(addAttachment()) );
61 connect( mUi
->mRemoveButton
, SIGNAL(clicked()), SLOT(removeSelectedAttachments()) );
64 void IncidenceAttachmentEditor::load( KCal::Incidence::ConstPtr incidence
)
66 mLoadedIncidence
= incidence
;
67 mAttachmentView
->clear();
69 KCal::Attachment::List attachments
= incidence
->attachments();
70 KCal::Attachment::List::ConstIterator it
;
71 for ( it
= attachments
.constBegin(); it
!= attachments
.constEnd(); ++it
)
72 new AttachmentIconItem( (*it
), mAttachmentView
);
75 void IncidenceAttachmentEditor::save( KCal::Incidence::Ptr incidence
)
77 incidence
->clearAttachments();
79 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
80 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
81 AttachmentIconItem
*attitem
= dynamic_cast<AttachmentIconItem
*>(item
);
83 incidence
->addAttachment( new KCal::Attachment( *( attitem
->attachment() ) ) );
87 bool IncidenceAttachmentEditor::isDirty() const
89 if ( mLoadedIncidence
) {
90 if ( mAttachmentView
->count() != mLoadedIncidence
->attachments().count() )
93 KCal::Attachment::List origAttachments
= mLoadedIncidence
->attachments();
94 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
95 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
96 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
98 KCal::Attachment
* const listAttachment
99 = static_cast<AttachmentIconItem
*>( item
)->attachment();
102 for ( int i
= 0; i
< origAttachments
.size() && !found
; ++i
) {
103 KCal::Attachment
* const attachment
= origAttachments
.at( i
);
105 // Check for changed label first
106 if ( attachment
->label() != listAttachment
->label() )
109 if ( attachment
->isBinary() && listAttachment
->isBinary()
110 && attachment
->decodedData() == listAttachment
->decodedData() ) {
111 // Not sure about this. Might be too expensive.
112 origAttachments
.removeAt( i
);
114 } else if ( attachment
->isUri() && listAttachment
->isUri()
115 && attachment
->uri() == listAttachment
->uri() ) {
116 origAttachments
.removeAt( i
);
122 // All attachments are removed from the list, meaning, the items in mAttachmentView
123 // are equal to the attachements set on mLoadedIncidence.
124 return !origAttachments
.isEmpty();
128 // No incidence loaded, so if the user added attachments we're dirty.
129 return mAttachmentView
->count() != 0;
138 void IncidenceAttachmentEditor::addAttachment()
140 AttachmentIconItem
*item
= new AttachmentIconItem( 0, mAttachmentView
);
142 QPointer
<AttachmentEditDialog
> dlg
= new AttachmentEditDialog( item
, mAttachmentView
);
143 dlg
->setCaption( i18nc( "@title", "Add Attachment" ) );
144 if ( dlg
->exec() == KDialog::Rejected
)
150 void IncidenceAttachmentEditor::copyToClipboard()
152 QApplication::clipboard()->setMimeData( mAttachmentView
->mimeData(), QClipboard::Clipboard
);
155 void IncidenceAttachmentEditor::openURL( const KUrl
&url
)
157 QString uri
= url
.url();
158 UriHandler::process( uri
);
161 void IncidenceAttachmentEditor::pasteFromClipboard()
163 handlePasteOrDrop( QApplication::clipboard()->mimeData() );
166 void IncidenceAttachmentEditor::removeSelectedAttachments()
168 QList
<QListWidgetItem
*> selected
;
171 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
172 QListWidgetItem
*it
= mAttachmentView
->item( itemIndex
);
173 if ( it
->isSelected() ) {
174 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( it
);
176 KCal::Attachment
*att
= attitem
->attachment();
177 labels
<< att
->label();
183 if ( selected
.isEmpty() )
186 QString labelsStr
= labels
.join( "<br>" );
188 if ( KMessageBox::questionYesNo(
191 "Do you really want to remove these attachments?<nl>%1</nl>", labelsStr
),
192 i18nc( "@title:window", "Remove Attachments?" ),
193 KStandardGuiItem::yes(), KStandardGuiItem::no(),
194 "calendarRemoveAttachments" ) != KMessageBox::Yes
) {
198 for ( QList
<QListWidgetItem
*>::iterator
it( selected
.begin() ), end( selected
.end() );
200 int row
= mAttachmentView
->row( *it
);
201 QListWidgetItem
*next
= mAttachmentView
->item( ++row
);
202 QListWidgetItem
*prev
= mAttachmentView
->item( --row
);
204 next
->setSelected( true );
206 prev
->setSelected( true );
211 mAttachmentView
->update();
215 void IncidenceAttachmentEditor::saveAttachment( QListWidgetItem
*item
)
218 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
220 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( item
);
221 if ( !attitem
->attachment() )
224 KCal::Attachment
*att
= attitem
->attachment();
226 // get the saveas file name
227 QString saveAsFile
= KFileDialog::getSaveFileName(
230 i18nc( "@title", "Save Attachment" ) );
232 if ( saveAsFile
.isEmpty() ||
233 ( QFile( saveAsFile
).exists() &&
234 ( KMessageBox::warningYesNo(
236 i18nc( "@info", "%1 already exists. Do you want to overwrite it?",
237 saveAsFile
) ) == KMessageBox::No
) ) ) {
242 if ( att
->isUri() ) {
243 sourceUrl
= att
->uri();
245 sourceUrl
= mAttachmentView
->tempFileForAttachment( att
);
247 // save the attachment url
248 if ( !KIO::NetAccess::file_copy( sourceUrl
, KUrl( saveAsFile
) ) &&
249 KIO::NetAccess::lastError() ) {
250 KMessageBox::error( this, KIO::NetAccess::lastErrorString() );
254 void IncidenceAttachmentEditor::saveSelectedAttachments()
256 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
257 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
258 if ( item
->isSelected() )
259 saveAttachment( item
);
263 void IncidenceAttachmentEditor::showAttachment( QListWidgetItem
*item
)
266 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
267 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( item
);
268 if ( !attitem
->attachment() )
271 KCal::Attachment
*att
= attitem
->attachment();
272 if ( att
->isUri() ) {
273 emit
openURL( att
->uri() );
275 KRun::runUrl( mAttachmentView
->tempFileForAttachment( att
), att
->mimeType(), 0, true );
279 void IncidenceAttachmentEditor::showContextMenu( const QPoint
&pos
)
281 QListWidgetItem
*item
= mAttachmentView
->itemAt( pos
);
282 const bool enable
= item
!= 0;
285 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
286 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
287 if ( item
->isSelected() ) {
292 mOpenAction
->setEnabled( enable
);
293 //TODO: support saving multiple attachments into a directory
294 mSaveAsAction
->setEnabled( enable
&& numSelected
== 1 );
295 mCopyAction
->setEnabled( enable
&& numSelected
== 1 );
296 mCutAction
->setEnabled( enable
&& numSelected
== 1 );
297 mDeleteAction
->setEnabled( enable
);
298 mEditAction
->setEnabled( enable
);
299 mPopupMenu
->exec( mAttachmentView
->mapToGlobal( pos
) );
302 void IncidenceAttachmentEditor::showSelectedAttachments()
304 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
305 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
306 if ( item
->isSelected() )
307 showAttachment( item
);
311 void IncidenceAttachmentEditor::cutToClipboard()
314 removeSelectedAttachments();
317 void IncidenceAttachmentEditor::editSelectedAttachments()
319 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
320 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
321 if ( item
->isSelected() ) {
322 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
324 AttachmentIconItem
*attitem
= static_cast<AttachmentIconItem
*>( item
);
325 if ( !attitem
->attachment() )
328 AttachmentEditDialog
*dialog
= new AttachmentEditDialog( attitem
, mAttachmentView
, false );
329 dialog
->setModal( false );
330 connect( dialog
, SIGNAL(hidden()), dialog
, SLOT(delayedDestruct()) );
336 void IncidenceAttachmentEditor::slotItemRenamed ( QListWidgetItem
*item
)
339 Q_ASSERT( dynamic_cast<AttachmentIconItem
*>( item
) );
340 static_cast<AttachmentIconItem
*>( item
)->setLabel( item
->text() );
344 void IncidenceAttachmentEditor::slotSelectionChanged()
346 bool selected
= false;
347 for ( int itemIndex
= 0; itemIndex
< mAttachmentView
->count(); ++itemIndex
) {
348 QListWidgetItem
*item
= mAttachmentView
->item( itemIndex
);
349 if ( item
->isSelected() ) {
354 mUi
->mRemoveButton
->setEnabled( selected
);
358 /// Private functions
360 void IncidenceAttachmentEditor::handlePasteOrDrop( const QMimeData
*mimeData
)
363 bool probablyWeHaveUris
= false;
364 bool weCanCopy
= true;
367 if ( KABC::VCardDrag::canDecode( mimeData
) ) {
368 KABC::Addressee::List addressees
;
369 KABC::VCardDrag::fromMimeData( mimeData
, addressees
);
370 for ( KABC::Addressee::List::ConstIterator it
= addressees
.constBegin();
371 it
!= addressees
.constEnd(); ++it
) {
372 urls
.append( QLatin1String( "uid:" ) + ( *it
).uid() );
373 // there is some weirdness about realName(), hence fromUtf8
374 labels
.append( QString::fromUtf8( ( *it
).realName().toLatin1() ) );
376 probablyWeHaveUris
= true;
377 } else if ( KUrl::List::canDecode( mimeData
) ) {
378 QMap
<QString
,QString
> metadata
;
380 urls
= KUrl::List::fromMimeData( mimeData
, &metadata
);
381 probablyWeHaveUris
= true;
382 labels
= metadata
["labels"].split( ':', QString::SkipEmptyParts
);
383 for ( QStringList::Iterator it
= labels
.begin(); it
!= labels
.end(); ++it
) {
384 *it
= KUrl::fromPercentEncoding( (*it
).toLatin1() );
386 } else if ( mimeData
->hasText() ) {
387 QString text
= mimeData
->text();
388 QStringList lst
= text
.split( '\n', QString::SkipEmptyParts
);
389 for ( QStringList::ConstIterator it
= lst
.constBegin(); it
!= lst
.constEnd(); ++it
) {
392 probablyWeHaveUris
= true;
395 QAction
*linkAction
= 0, *cancelAction
;
396 if ( probablyWeHaveUris
) {
397 linkAction
= menu
.addAction( KIcon( "insert-link" ), i18nc( "@action:inmenu", "&Link here" ) );
398 // we need to check if we can reasonably expect to copy the objects
399 for ( KUrl::List::ConstIterator it
= urls
.constBegin(); it
!= urls
.constEnd(); ++it
) {
400 if ( !( weCanCopy
= KProtocolManager::supportsReading( *it
) ) ) {
401 break; // either we can copy them all, or no copying at all
405 menu
.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
408 menu
.addAction( KIcon( "edit-copy" ), i18nc( "@action:inmenu", "&Copy here" ) );
412 cancelAction
= menu
.addAction( KIcon( "process-stop" ) , i18nc( "@action:inmenu", "C&ancel" ) );
418 if(!mimeData
->formats().isEmpty() && !probablyWeHaveUris
) {
419 data
=mimeData
->data( mimeData
->formats().first() );
420 mimeType
= mimeData
->formats().first();
421 if( KMimeType::mimeType( mimeData
->formats().first() ) )
422 label
= KMimeType::mimeType( mimeData
->formats().first() )->name();
426 QAction
*ret
= menu
.exec( QCursor::pos() );
427 if ( linkAction
== ret
) {
428 QStringList::ConstIterator jt
= labels
.constBegin();
429 for ( KUrl::List::ConstIterator it
= urls
.constBegin();
430 it
!= urls
.constEnd(); ++it
) {
431 addUriAttachment( (*it
).url(), QString(), ( jt
== labels
.constEnd() ?
432 QString() : *( jt
++ ) ), true );
434 } else if ( cancelAction
!= ret
) {
435 if ( probablyWeHaveUris
) {
436 for ( KUrl::List::ConstIterator it
= urls
.constBegin();
437 it
!= urls
.constEnd(); ++it
) {
438 KIO::Job
*job
= KIO::storedGet( *it
);
439 connect( job
, SIGNAL(result(KJob
*)), SLOT(downloadComplete(KJob
*)) );
441 } else { // we take anything
442 addDataAttachment( data
, mimeType
, label
);
447 void IncidenceAttachmentEditor::setupActions()
449 KActionCollection
*ac
= new KActionCollection( this );
450 ac
->addAssociatedWidget( this );
452 mOpenAction
= new KAction( i18nc( "@action:inmenu open the attachment in a viewer",
454 connect( mOpenAction
, SIGNAL(triggered(bool)), SLOT(showSelectedAttachments()) );
455 ac
->addAction( "view", mOpenAction
);
456 mPopupMenu
->addAction( mOpenAction
);
458 mSaveAsAction
= new KAction( i18nc( "@action:inmenu save the attachment to a file",
459 "Save As..." ), this );
460 connect( mSaveAsAction
, SIGNAL(triggered(bool)), SLOT(saveSelectedAttachments()) );
461 mPopupMenu
->addAction( mSaveAsAction
);
462 mPopupMenu
->addSeparator();
464 mCopyAction
= KStandardAction::copy( this, SLOT(copyToClipboard()), ac
);
465 mPopupMenu
->addAction( mCopyAction
);
467 mCutAction
= KStandardAction::cut( this, SLOT(cutToClipboard()), ac
);
468 mPopupMenu
->addAction( mCutAction
);
470 KAction
*action
= KStandardAction::paste( this, SLOT(pasteFromClipboard()), ac
);
471 mPopupMenu
->addAction( action
);
472 mPopupMenu
->addSeparator();
474 mDeleteAction
= new KAction( i18nc( "@action:inmenu remove the attachment",
476 connect( mDeleteAction
, SIGNAL(triggered(bool)), SLOT(removeSelectedAttachments()) );
477 ac
->addAction( "remove", mDeleteAction
);
478 mDeleteAction
->setShortcut( Qt::Key_Delete
);
479 mPopupMenu
->addAction( mDeleteAction
);
480 mPopupMenu
->addSeparator();
482 mEditAction
= new KAction( i18nc( "@action:inmenu show a dialog used to edit the attachment",
483 "&Properties..." ), this );
484 connect( mEditAction
, SIGNAL(triggered(bool)), SLOT(editSelectedAttachments()) );
485 ac
->addAction( "edit", mEditAction
);
486 mPopupMenu
->addAction( mEditAction
);
489 void IncidenceAttachmentEditor::setupAttachmentIconView()
491 mAttachmentView
= new AttachmentIconView( this );
492 mAttachmentView
->setWhatsThis( i18nc( "@info:whatsthis",
493 "Displays items (files, mail, etc.) that "
494 "have been associated with this event or to-do." ) );
496 connect( mAttachmentView
, SIGNAL(itemDoubleClicked(QListWidgetItem
*)),
497 SLOT(showAttachment(QListWidgetItem
*)) );
498 connect( mAttachmentView
, SIGNAL(itemChanged(QListWidgetItem
*)),
499 SLOT(slotItemRenamed(QListWidgetItem
*)) );
500 connect( mAttachmentView
, SIGNAL(itemSelectionChanged()),
501 SLOT(slotSelectionChanged()) );
502 connect( mAttachmentView
, SIGNAL(customContextMenuRequested(QPoint
)),
503 SLOT(showContextMenu(QPoint
)) );
506 QGridLayout
*layout
= new QGridLayout( mUi
->mAttachmentViewPlaceHolder
);
507 layout
->addWidget( mAttachmentView
);
510 // void IncidenceAttachmentEditor::addAttachment( KCal::Attachment *attachment )
512 // new AttachmentIconItem( attachment, mAttachmentView );
515 void IncidenceAttachmentEditor::addDataAttachment( const QByteArray
&data
,
516 const QString
&mimeType
,
517 const QString
&label
)
519 AttachmentIconItem
*item
= new AttachmentIconItem( 0, mAttachmentView
);
521 QString nlabel
= label
;
522 if ( mimeType
== "message/rfc822" ) {
523 // mail message. try to set the label from the mail Subject:
525 msg
.setContent( data
);
527 nlabel
= msg
.subject()->asUnicodeString();
530 item
->setData( data
);
531 item
->setLabel( nlabel
);
532 if ( mimeType
.isEmpty() ) {
533 item
->setMimeType( KMimeType::findByContent( data
)->name() );
535 item
->setMimeType( mimeType
);
541 void IncidenceAttachmentEditor::addUriAttachment( const QString
&uri
,
542 const QString
&mimeType
,
543 const QString
&label
,
547 AttachmentIconItem
*item
= new AttachmentIconItem( 0, mAttachmentView
);
549 item
->setLabel( label
);
550 if ( mimeType
.isEmpty() ) {
551 if ( uri
.startsWith( QLatin1String( "uid:" ) ) ) {
552 item
->setMimeType( "text/directory" );
553 } else if ( uri
.startsWith( QLatin1String( "kmail:" ) ) ) {
554 item
->setMimeType( "message/rfc822" );
555 } else if ( uri
.startsWith( QLatin1String( "urn:x-ical" ) ) ) {
556 item
->setMimeType( "text/calendar" );
557 } else if ( uri
.startsWith( QLatin1String( "news:" ) ) ) {
558 item
->setMimeType( "message/news" );
560 item
->setMimeType( KMimeType::findByUrl( uri
)->name() );
565 if ( KIO::NetAccess::download( uri
, tmpFile
, this ) ) {
567 if ( !f
.open( QIODevice::ReadOnly
) ) {
570 const QByteArray data
= f
.readAll();
572 addDataAttachment( data
, mimeType
, label
);
574 KIO::NetAccess::removeTempFile( tmpFile
);