diff --git a/src/incidenceattachment.cpp b/src/incidenceattachment.cpp index eab1a10..71d2d90 100644 --- a/src/incidenceattachment.cpp +++ b/src/incidenceattachment.cpp @@ -1,619 +1,622 @@ /* 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" #include "ui_dialogdesktop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace IncidenceEditorNG; IncidenceAttachment::IncidenceAttachment(Ui::EventOrTodoDesktop *ui) : IncidenceEditor(nullptr) , mUi(ui) , mPopupMenu(new QMenu) { setupActions(); setupAttachmentIconView(); setObjectName(QStringLiteral("IncidenceAttachment")); connect(mUi->mAddButton, &QPushButton::clicked, this, &IncidenceAttachment::addAttachment); connect(mUi->mRemoveButton, &QPushButton::clicked, this, &IncidenceAttachment::removeSelectedAttachments); } IncidenceAttachment::~IncidenceAttachment() { delete mPopupMenu; } void IncidenceAttachment::load(const KCalendarCore::Incidence::Ptr &incidence) { mLoadedIncidence = incidence; mAttachmentView->clear(); KCalendarCore::Attachment::List attachments = incidence->attachments(); for (KCalendarCore::Attachment::List::ConstIterator it = attachments.constBegin(), end = attachments.constEnd(); it != end; ++it) { new AttachmentIconItem((*it), mAttachmentView); } mWasDirty = false; } void IncidenceAttachment::save(const KCalendarCore::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(attitem->attachment()); } } bool IncidenceAttachment::isDirty() const { if (mLoadedIncidence) { if (mAttachmentView->count() != mLoadedIncidence->attachments().count()) { return true; } KCalendarCore::Attachment::List origAttachments = mLoadedIncidence->attachments(); for (int itemIndex = 0; itemIndex < mAttachmentView->count(); ++itemIndex) { QListWidgetItem *item = mAttachmentView->item(itemIndex); Q_ASSERT(dynamic_cast(item)); const KCalendarCore::Attachment listAttachment = static_cast(item)->attachment(); for (int i = 0; i < origAttachments.count(); ++i) { const KCalendarCore::Attachment 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; } } int IncidenceAttachment::attachmentCount() const { return mAttachmentView->count(); } /// Private slots void IncidenceAttachment::addAttachment() { QPointer that(this); AttachmentIconItem *item = new AttachmentIconItem(KCalendarCore::Attachment(), mAttachmentView); QPointer dialog(new AttachmentEditDialog(item, mAttachmentView)); dialog->setWindowTitle(i18nc("@title", "Add Attachment")); auto dialogResult = dialog->exec(); if (!that) { return; } if (dialogResult == QDialog::Rejected) { delete item; } else { Q_EMIT attachmentCountChanged(mAttachmentView->count()); } delete dialog; checkDirtyStatus(); } void IncidenceAttachment::copyToClipboard() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setMimeData(mAttachmentView->mimeData(), QClipboard::Clipboard); #endif } void IncidenceAttachment::openURL(const QUrl &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) { const KCalendarCore::Attachment att = attitem->attachment(); labels << att.label(); selected << it; } } } if (selected.isEmpty()) { return; } QString labelsStr = labels.join(QLatin1String("")); if (KMessageBox::questionYesNo( nullptr, xi18nc("@info", "Do you really want to remove these attachments?%1", labelsStr), i18nc("@title:window", "Remove Attachments?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("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(); Q_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().isEmpty()) { return; } KCalendarCore::Attachment att = attitem->attachment(); // get the saveas file name const QString saveAsFile = QFileDialog::getSaveFileName(nullptr, i18nc("@title", "Save Attachment"), att.label()); if (saveAsFile.isEmpty()) { return; } QUrl sourceUrl; if (att.isUri()) { sourceUrl = QUrl(att.uri()); } else { sourceUrl = attitem->tempFileForAttachment(); } // save the attachment url auto job = KIO::file_copy(sourceUrl, QUrl::fromLocalFile(saveAsFile)); if (!job->exec() && job->error()) { KMessageBox::error(nullptr, job->errorString()); } } 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().isEmpty()) { return; } const KCalendarCore::Attachment att = attitem->attachment(); if (att.isUri()) { openURL(QUrl(att.uri())); } else { KRun::RunFlags flags; flags |= KRun::DeleteTemporaryFiles; flags |= KRun::RunExecutables; KRun::runUrl(attitem->tempFileForAttachment(), att.mimeType(), nullptr, flags); } } void IncidenceAttachment::showContextMenu(const QPoint &pos) { QListWidgetItem *item = mAttachmentView->itemAt(pos); const bool enable = item != nullptr; 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().isEmpty()) { return; } QPointer dialog( new AttachmentEditDialog(attitem, mAttachmentView, false)); dialog->setModal(false); dialog->setAttribute(Qt::WA_DeleteOnClose, true); 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) { + if (!mimeData) { + return; + } QList urls; bool probablyWeHaveUris = false; QStringList labels; if (KContacts::VCardDrag::canDecode(mimeData)) { KContacts::Addressee::List addressees; KContacts::VCardDrag::fromMimeData(mimeData, addressees); urls.reserve(addressees.count()); labels.reserve(addressees.count()); const KContacts::Addressee::List::ConstIterator end(addressees.constEnd()); for (KContacts::Addressee::List::ConstIterator it = addressees.constBegin(); it != end; ++it) { urls.append(QUrl(QStringLiteral("uid:") + (*it).uid())); // there is some weirdness about realName(), hence fromUtf8 labels.append(QString::fromUtf8((*it).realName().toLatin1())); } probablyWeHaveUris = true; } else if (mimeData->hasUrls()) { QMap metadata; //QT5 //urls = QList::fromMimeData( mimeData, &metadata ); probablyWeHaveUris = true; labels = metadata[QStringLiteral("labels")].split(QLatin1Char(':'), QString::SkipEmptyParts); const QStringList::Iterator end(labels.end()); for (QStringList::Iterator it = labels.begin(); it != end; ++it) { *it = QUrl::fromPercentEncoding((*it).toLatin1()); } } else if (mimeData->hasText()) { const QString text = mimeData->text(); QStringList lst = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); urls.reserve(lst.count()); QStringList::ConstIterator end(lst.constEnd()); for (QStringList::ConstIterator it = lst.constBegin(); it != end; ++it) { urls.append(QUrl(*it)); } probablyWeHaveUris = true; } QMenu menu; QAction *linkAction = nullptr, *cancelAction = nullptr; if (probablyWeHaveUris) { linkAction = menu.addAction(QIcon::fromTheme(QStringLiteral("insert-link")), i18nc("@action:inmenu", "&Link here")); // we need to check if we can reasonably expect to copy the objects bool weCanCopy = true; QList::ConstIterator end(urls.constEnd()); for (QList::ConstIterator it = urls.constBegin(); it != end; ++it) { if (!(weCanCopy = KProtocolManager::supportsReading(*it))) { break; // either we can copy them all, or no copying at all } } if (weCanCopy) { menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "&Copy here")); } } else { menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "&Copy here")); } menu.addSeparator(); cancelAction = menu.addAction(QIcon::fromTheme(QStringLiteral("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); QMimeDatabase db; QMimeType mime = db.mimeTypeForName(mimeType); if (mime.isValid()) { label = mime.comment(); } } QAction *ret = menu.exec(QCursor::pos()); if (linkAction == ret) { QStringList::ConstIterator jt = labels.constBegin(); const QList::ConstIterator jtEnd = urls.constEnd(); for (QList::ConstIterator it = urls.constBegin(); it != jtEnd; ++it) { addUriAttachment((*it).url(), QString(), (jt == labels.constEnd() ? QString() : *(jt++)), true); } } else if (cancelAction != ret) { if (probablyWeHaveUris) { QList::ConstIterator end = urls.constEnd(); for (QList::ConstIterator it = urls.constBegin(); it != end; ++it) { KIO::Job *job = KIO::storedGet(*it); //TODO verify if slot exist ! connect(job, &KIO::Job::result, this, &IncidenceAttachment::downloadComplete); } } 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 QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("@action:inmenu open the attachment in a viewer", "&Open"), this); connect(mOpenAction, &QAction::triggered, this, &IncidenceAttachment::showSelectedAttachments); ac->addAction(QStringLiteral("view"), mOpenAction); mPopupMenu->addAction(mOpenAction); mSaveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18nc("@action:inmenu save the attachment to a file", "Save As..."), this); connect(mSaveAsAction, &QAction::triggered, this, &IncidenceAttachment::saveSelectedAttachments); mPopupMenu->addAction(mSaveAsAction); mPopupMenu->addSeparator(); #ifndef QT_NO_CLIPBOARD mCopyAction = KStandardAction::copy(this, &IncidenceAttachment::copyToClipboard, ac); mPopupMenu->addAction(mCopyAction); mCutAction = KStandardAction::cut(this, &IncidenceAttachment::cutToClipboard, ac); mPopupMenu->addAction(mCutAction); QAction *action = KStandardAction::paste(this, &IncidenceAttachment::pasteFromClipboard, ac); mPopupMenu->addAction(action); mPopupMenu->addSeparator(); #endif mDeleteAction = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18nc("@action:inmenu remove the attachment", "&Remove"), this); connect(mDeleteAction, &QAction::triggered, this, &IncidenceAttachment::removeSelectedAttachments); ac->addAction(QStringLiteral("remove"), mDeleteAction); mDeleteAction->setShortcut(Qt::Key_Delete); mPopupMenu->addAction(mDeleteAction); mPopupMenu->addSeparator(); mEditAction = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu show a dialog used to edit the attachment", "&Properties..."), this); connect(mEditAction, &QAction::triggered, this, &IncidenceAttachment::editSelectedAttachments); ac->addAction(QStringLiteral("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, &AttachmentIconView::itemDoubleClicked, this, &IncidenceAttachment::showAttachment); connect(mAttachmentView, &AttachmentIconView::itemChanged, this, &IncidenceAttachment::slotItemRenamed); connect(mAttachmentView, &AttachmentIconView::itemSelectionChanged, this, &IncidenceAttachment::slotSelectionChanged); connect(mAttachmentView, &AttachmentIconView::customContextMenuRequested, this, &IncidenceAttachment::showContextMenu); QGridLayout *layout = new QGridLayout(mUi->mAttachmentViewPlaceHolder); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(mAttachmentView); } // void IncidenceAttachmentEditor::addAttachment( KCalendarCore::Attachment *attachment ) // { // new AttachmentIconItem( attachment, mAttachmentView ); // } void IncidenceAttachment::addDataAttachment(const QByteArray &data, const QString &mimeType, const QString &label) { AttachmentIconItem *item = new AttachmentIconItem(KCalendarCore::Attachment(), mAttachmentView); QString nlabel = label; if (mimeType == QLatin1String("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()) { QMimeDatabase db; item->setMimeType(db.mimeTypeForData(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(KCalendarCore::Attachment(), mAttachmentView); item->setUri(uri); item->setLabel(label); if (mimeType.isEmpty()) { if (uri.startsWith(QLatin1String("uid:"))) { item->setMimeType(QStringLiteral("text/directory")); } else if (uri.startsWith(QLatin1String("kmail:"))) { item->setMimeType(QStringLiteral("message/rfc822")); } else if (uri.startsWith(QLatin1String("urn:x-ical"))) { item->setMimeType(QStringLiteral("text/calendar")); } else if (uri.startsWith(QLatin1String("news:"))) { item->setMimeType(QStringLiteral("message/news")); } else { QMimeDatabase db; item->setMimeType(db.mimeTypeForUrl(QUrl(uri)).name()); } } } else { auto job = KIO::storedGet(QUrl(uri)); KJobWidgets::setWindow(job, nullptr); if (job->exec()) { const QByteArray data = job->data(); addDataAttachment(data, mimeType, label); } } }