diff --git a/server/editor/attachment/attachmentcontrollerbase.cpp b/server/editor/attachment/attachmentcontrollerbase.cpp index 9c5d588..efa3b5a 100644 --- a/server/editor/attachment/attachmentcontrollerbase.cpp +++ b/server/editor/attachment/attachmentcontrollerbase.cpp @@ -1,860 +1,840 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "attachmentcontrollerbase.h" #include "../attachment/attachmentjob.h" #include "../attachment/attachmentmodel.h" #include "../attachment/attachmentupdatejob.h" #include "../attachment/attachmentfrompublickeyjob.h" #include "../attachment/attachmentfromurlbasejob.h" #include "../attachment/attachmentcompressjob.h" #include "../attachment/attachmentclipboardjob.h" #include "../attachment/attachmentfromurlutils.h" #include "../attachment/attachmentpropertiesdialog.h" #include "../composer.h" #include "../part/globalpart.h" #include "editor_debug.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include using namespace MessageComposer; using namespace MessageCore; class MessageComposer::AttachmentControllerBase::AttachmentControllerBasePrivate { public: AttachmentControllerBasePrivate(AttachmentControllerBase *qq); ~AttachmentControllerBasePrivate(); void attachmentRemoved(const AttachmentPart::Ptr &part); // slot void compressJobResult(KJob *job); // slot void loadJobResult(KJob *job); // slot void openSelectedAttachments(); // slot void viewSelectedAttachments(); // slot void removeSelectedAttachments(); // slot void saveSelectedAttachmentAs(); // slot void selectedAttachmentProperties(); // slot void attachPublicKeyJobResult(KJob *job); // slot void slotAttachmentContentCreated(KJob *job); // slot void addAttachmentPart(AttachmentPart::Ptr part); void attachClipBoardElement(KJob *job); void selectedAllAttachment(); void createOpenWithMenu(QMenu *topMenu, const AttachmentPart::Ptr &part); void reloadAttachment(); void updateJobResult(KJob *); AttachmentPart::List selectedParts; AttachmentControllerBase *const q; MessageComposer::AttachmentModel *model = nullptr; QWidget *wParent = nullptr; KActionCollection *mActionCollection = nullptr; QAction *attachPublicKeyAction = nullptr; QAction *attachMyPublicKeyAction = nullptr; QAction *openContextAction = nullptr; QAction *viewContextAction = nullptr; QAction *removeAction = nullptr; QAction *removeContextAction = nullptr; QAction *saveAsAction = nullptr; QAction *saveAsContextAction = nullptr; QAction *propertiesAction = nullptr; QAction *propertiesContextAction = nullptr; QAction *addAttachmentFileAction = nullptr; QAction *addAttachmentDirectoryAction = nullptr; QAction *addContextAction = nullptr; QAction *selectAllAction = nullptr; KActionMenu *attachmentMenu = nullptr; QAction *reloadAttachmentAction = nullptr; QAction *attachClipBoardAction = nullptr; // If part p is compressed, uncompressedParts[p] is the uncompressed part. QHash uncompressedParts; bool encryptEnabled = false; bool signEnabled = false; }; AttachmentControllerBase::AttachmentControllerBasePrivate::AttachmentControllerBasePrivate(AttachmentControllerBase *qq) : q(qq) { } AttachmentControllerBase::AttachmentControllerBasePrivate::~AttachmentControllerBasePrivate() = default; void AttachmentControllerBase::setSelectedParts(const AttachmentPart::List &selectedParts) { d->selectedParts = selectedParts; const int selectedCount = selectedParts.count(); d->openContextAction->setEnabled(selectedCount > 0); d->viewContextAction->setEnabled(selectedCount > 0); d->removeAction->setEnabled(selectedCount > 0); d->removeContextAction->setEnabled(selectedCount > 0); d->saveAsAction->setEnabled(selectedCount == 1); d->saveAsContextAction->setEnabled(selectedCount == 1); d->propertiesAction->setEnabled(selectedCount == 1); d->propertiesContextAction->setEnabled(selectedCount == 1); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachmentRemoved(const AttachmentPart::Ptr &part) { uncompressedParts.remove(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::compressJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18nc("@title:window", "Failed to compress attachment")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr originalPart = ajob->originalPart(); AttachmentPart::Ptr compressedPart = ajob->compressedPart(); if (ajob->isCompressedPartLarger()) { const int result = KMessageBox::questionTwoActions(wParent, i18n("The compressed attachment is larger than the original. " "Do you want to keep the original one?"), QString(/*caption*/), KGuiItem(i18nc("Do not compress", "Keep")), KGuiItem(i18n("Compress"))); if (result == KMessageBox::ButtonCode::PrimaryAction) { // The user has chosen to keep the uncompressed file. return; } } qCDebug(EDITOR_LOG) << "Replacing uncompressed part in model."; uncompressedParts[compressedPart] = originalPart; bool ok = model->replaceAttachment(originalPart, compressedPart); if (!ok) { // The attachment was removed from the model while we were compressing. qCDebug(EDITOR_LOG) << "Compressed a zombie."; } } void AttachmentControllerBase::AttachmentControllerBasePrivate::loadJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach file")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::openSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) { q->openAttachment(part); } } void AttachmentControllerBase::AttachmentControllerBasePrivate::viewSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); for (const AttachmentPart::Ptr &part : std::as_const(selectedParts)) { q->viewAttachment(part); } } void AttachmentControllerBase::AttachmentControllerBasePrivate::removeSelectedAttachments() { Q_ASSERT(selectedParts.count() >= 1); // We must store list, otherwise when we remove it changes selectedParts (as selection changed) => it will crash. const AttachmentPart::List toRemove = selectedParts; for (const AttachmentPart::Ptr &part : toRemove) { if (!model->removeAttachment(part)) { qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << part->fileName(); } } } void AttachmentControllerBase::AttachmentControllerBasePrivate::saveSelectedAttachmentAs() { Q_ASSERT(selectedParts.count() == 1); q->saveAttachmentAs(selectedParts.constFirst()); } void AttachmentControllerBase::AttachmentControllerBasePrivate::selectedAttachmentProperties() { Q_ASSERT(selectedParts.count() == 1); q->attachmentProperties(selectedParts.constFirst()); } void AttachmentControllerBase::AttachmentControllerBasePrivate::reloadAttachment() { Q_ASSERT(selectedParts.count() == 1); auto ajob = new AttachmentUpdateJob(selectedParts.constFirst(), q); connect(ajob, &AttachmentUpdateJob::result, q, [this](KJob *job) { updateJobResult(job); }); ajob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::updateJobResult(KJob *job) { if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to reload attachment")); return; } auto ajob = qobject_cast(job); Q_ASSERT(ajob); AttachmentPart::Ptr originalPart = ajob->originalPart(); AttachmentPart::Ptr updatedPart = ajob->updatedPart(); attachmentRemoved(originalPart); bool ok = model->replaceAttachment(originalPart, updatedPart); if (!ok) { // The attachment was removed from the model while we were compressing. qCDebug(EDITOR_LOG) << "Updated a zombie."; } } void AttachmentControllerBase::AttachmentControllerBasePrivate::createOpenWithMenu(QMenu *topMenu, const AttachmentPart::Ptr &part) { const QString contentTypeStr = QString::fromLatin1(part->mimeType()); const KService::List offers = KFileItemActions::associatedApplications(QStringList() << contentTypeStr); if (!offers.isEmpty()) { QMenu *menu = topMenu; auto actionGroup = new QActionGroup(menu); connect(actionGroup, &QActionGroup::triggered, q, &AttachmentControllerBase::slotOpenWithAction); if (offers.count() > 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QLatin1StringView("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } // qCDebug(EDITOR_LOG) << offers.count() << "offers" << topMenu << menu; KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for (; it != end; ++it) { /* TODO Carl QAction *act = MessageViewer::Util::createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); */ } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } auto openWithAct = new QAction(menu); openWithAct->setText(openWithActionName); QObject::connect(openWithAct, &QAction::triggered, q, &AttachmentControllerBase::slotOpenWithDialog); menu->addAction(openWithAct); } else { // no app offers -> Open With... auto act = new QAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); QObject::connect(act, &QAction::triggered, q, &AttachmentControllerBase::slotOpenWithDialog); topMenu->addAction(act); } } void AttachmentControllerBase::exportPublicKey(const GpgME::Key &key) { if (key.isNull() || !QGpgME::openpgp()) { qCWarning(EDITOR_LOG) << "Tried to export key with empty fingerprint, or no OpenPGP."; return; } auto ajob = new MessageComposer::AttachmentFromPublicKeyJob(key, this); connect(ajob, &AttachmentFromPublicKeyJob::result, this, [this](KJob *job) { d->attachPublicKeyJobResult(job); }); ajob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachPublicKeyJobResult(KJob *job) { // The only reason we can't use loadJobResult() and need a separate method // is that we want to show the proper caption ("public key" instead of "file")... if (job->error()) { KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach public key")); return; } Q_ASSERT(dynamic_cast(job)); auto ajob = static_cast(job); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } void AttachmentControllerBase::AttachmentControllerBasePrivate::attachClipBoardElement(KJob *job) { if (job->error()) { qCDebug(EDITOR_LOG) << " Error during when get try to attach text from clipboard"; KMessageBox::error(wParent, job->errorString(), i18n("Failed to attach text from clipboard")); return; } auto ajob = static_cast(job); AttachmentPart::Ptr part = ajob->attachmentPart(); q->addAttachment(part); } static QTemporaryFile *dumpAttachmentToTempFile(const AttachmentPart::Ptr &part) // local { auto file = new QTemporaryFile; if (!file->open()) { qCCritical(EDITOR_LOG) << "Could not open tempfile" << file->fileName(); delete file; return nullptr; } if (file->write(part->data()) == -1) { qCCritical(EDITOR_LOG) << "Could not dump attachment to tempfile."; delete file; return nullptr; } file->flush(); return file; } AttachmentControllerBase::AttachmentControllerBase(MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection) : QObject(wParent) , d(new AttachmentControllerBasePrivate(this)) { d->model = model; connect(model, &MessageComposer::AttachmentModel::attachUrlsRequested, this, &AttachmentControllerBase::addAttachments); connect(model, &MessageComposer::AttachmentModel::attachmentRemoved, this, [this](const MessageCore::AttachmentPart::Ptr &attr) { d->attachmentRemoved(attr); }); connect(model, &AttachmentModel::attachmentCompressRequested, this, &AttachmentControllerBase::compressAttachment); connect(model, &MessageComposer::AttachmentModel::encryptEnabled, this, &AttachmentControllerBase::setEncryptEnabled); connect(model, &MessageComposer::AttachmentModel::signEnabled, this, &AttachmentControllerBase::setSignEnabled); d->wParent = wParent; d->mActionCollection = actionCollection; } AttachmentControllerBase::~AttachmentControllerBase() = default; void AttachmentControllerBase::createActions() { // Create the actions. d->attachPublicKeyAction = new QAction(i18n("Attach &Public Key..."), this); connect(d->attachPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachPublicKeyDialog); d->attachMyPublicKeyAction = new QAction(i18n("Attach &My Public Key"), this); connect(d->attachMyPublicKeyAction, &QAction::triggered, this, &AttachmentControllerBase::attachMyPublicKey); d->attachmentMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Attach"), this); connect(d->attachmentMenu, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); d->attachmentMenu->setPopupMode(QToolButton::DelayedPopup); d->addAttachmentFileAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach File..."), this); d->addAttachmentFileAction->setIconText(i18n("Attach")); d->addContextAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Add Attachment..."), this); connect(d->addAttachmentFileAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); connect(d->addContextAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentFileDialog); d->addAttachmentDirectoryAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Directory..."), this); d->addAttachmentDirectoryAction->setIconText(i18n("Attach")); connect(d->addAttachmentDirectoryAction, &QAction::triggered, this, &AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog); d->attachClipBoardAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach Text From Clipboard..."), this); d->attachClipBoardAction->setIconText(i18n("Attach Text From Clipboard")); connect(d->attachClipBoardAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachClipBoard); d->attachmentMenu->addAction(d->addAttachmentFileAction); d->attachmentMenu->addAction(d->addAttachmentDirectoryAction); d->attachmentMenu->addSeparator(); d->attachmentMenu->addAction(d->attachClipBoardAction); d->removeAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Attachment"), this); d->removeContextAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Remove"), this); // FIXME need two texts. is there a better way? connect(d->removeAction, &QAction::triggered, this, [this]() { d->removeSelectedAttachments(); }); connect(d->removeContextAction, &QAction::triggered, this, [this]() { d->removeSelectedAttachments(); }); d->openContextAction = new QAction(i18nc("to open", "Open"), this); connect(d->openContextAction, &QAction::triggered, this, [this]() { d->openSelectedAttachments(); }); d->viewContextAction = new QAction(i18nc("to view", "View"), this); connect(d->viewContextAction, &QAction::triggered, this, [this]() { d->viewSelectedAttachments(); }); d->saveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("&Save Attachment As..."), this); d->saveAsContextAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As..."), this); connect(d->saveAsAction, &QAction::triggered, this, [this]() { d->saveSelectedAttachmentAs(); }); connect(d->saveAsContextAction, &QAction::triggered, this, [this]() { d->saveSelectedAttachmentAs(); }); d->propertiesAction = new QAction(i18n("Attachment Pr&operties..."), this); d->propertiesContextAction = new QAction(i18n("Properties"), this); connect(d->propertiesAction, &QAction::triggered, this, [this]() { d->selectedAttachmentProperties(); }); connect(d->propertiesContextAction, &QAction::triggered, this, [this]() { d->selectedAttachmentProperties(); }); d->selectAllAction = new QAction(i18n("Select All"), this); connect(d->selectAllAction, &QAction::triggered, this, &AttachmentControllerBase::selectedAllAttachment); d->reloadAttachmentAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload"), this); connect(d->reloadAttachmentAction, &QAction::triggered, this, [this]() { d->reloadAttachment(); }); // Insert the actions into the composer window's menu. KActionCollection *collection = d->mActionCollection; collection->addAction(QStringLiteral("attach_public_key"), d->attachPublicKeyAction); collection->addAction(QStringLiteral("attach_my_public_key"), d->attachMyPublicKeyAction); collection->addAction(QStringLiteral("attach"), d->addAttachmentFileAction); collection->setDefaultShortcut(d->addAttachmentFileAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_A)); collection->addAction(QStringLiteral("attach_directory"), d->addAttachmentDirectoryAction); collection->addAction(QStringLiteral("remove"), d->removeAction); collection->addAction(QStringLiteral("attach_save"), d->saveAsAction); collection->addAction(QStringLiteral("attach_properties"), d->propertiesAction); collection->addAction(QStringLiteral("select_all_attachment"), d->selectAllAction); collection->addAction(QStringLiteral("attach_menu"), d->attachmentMenu); setSelectedParts(AttachmentPart::List()); Q_EMIT actionsCreated(); } void AttachmentControllerBase::setEncryptEnabled(bool enabled) { d->encryptEnabled = enabled; } void AttachmentControllerBase::setSignEnabled(bool enabled) { d->signEnabled = enabled; } void AttachmentControllerBase::compressAttachment(const AttachmentPart::Ptr &part, bool compress) { if (compress) { qCDebug(EDITOR_LOG) << "Compressing part."; auto ajob = new AttachmentCompressJob(part, this); connect(ajob, &AttachmentCompressJob::result, this, [this](KJob *job) { d->compressJobResult(job); }); ajob->start(); } else { qCDebug(EDITOR_LOG) << "Uncompressing part."; // Replace the compressed part with the original uncompressed part, and delete // the compressed part. AttachmentPart::Ptr originalPart = d->uncompressedParts.take(part); Q_ASSERT(originalPart); // Found in uncompressedParts. bool ok = d->model->replaceAttachment(part, originalPart); Q_ASSERT(ok); Q_UNUSED(ok) } } void AttachmentControllerBase::showContextMenu() { Q_EMIT refreshSelection(); const int numberOfParts(d->selectedParts.count()); QMenu menu; if (numberOfParts > 0) { if (numberOfParts == 1) { const QString mimetype = QString::fromLatin1(d->selectedParts.first()->mimeType()); QMimeDatabase mimeDb; auto mime = mimeDb.mimeTypeForName(mimetype); QStringList parentMimeType; if (mime.isValid()) { parentMimeType = mime.allAncestors(); } if ((mimetype == QLatin1String("text/plain")) || (mimetype == QLatin1String("image/png")) || (mimetype == QLatin1String("image/jpeg")) || parentMimeType.contains(QLatin1String("text/plain")) || parentMimeType.contains(QLatin1String("image/png")) || parentMimeType.contains(QLatin1String("image/jpeg"))) { menu.addAction(d->viewContextAction); } d->createOpenWithMenu(&menu, d->selectedParts.constFirst()); } menu.addAction(d->openContextAction); } menu.addSeparator(); if (numberOfParts == 1) { if (!d->selectedParts.first()->url().isEmpty()) { menu.addAction(d->reloadAttachmentAction); } menu.addAction(d->saveAsContextAction); menu.addSeparator(); menu.addAction(d->propertiesContextAction); menu.addSeparator(); } if (numberOfParts > 0) { menu.addAction(d->removeContextAction); menu.addSeparator(); } const int nbAttachment = d->model->rowCount(); if (nbAttachment != numberOfParts) { menu.addAction(d->selectAllAction); menu.addSeparator(); } if (numberOfParts == 0) { menu.addAction(d->addContextAction); } menu.exec(QCursor::pos()); } void AttachmentControllerBase::slotOpenWithDialog() { openWith(); } void AttachmentControllerBase::slotOpenWithAction(QAction *act) { auto app = act->data().value(); Q_ASSERT(d->selectedParts.count() == 1); openWith(app); } void AttachmentControllerBase::openWith(const KService::Ptr &offer) { QTemporaryFile *tempFile = dumpAttachmentToTempFile(d->selectedParts.constFirst()); if (!tempFile) { KMessageBox::error(d->wParent, i18n("KMail was unable to write the attachment to a temporary file."), i18nc("@title:window", "Unable to open attachment")); return; } QUrl url = QUrl::fromLocalFile(tempFile->fileName()); tempFile->setPermissions(QFile::ReadUser); // If offer is null, this will show the "open with" dialog auto job = new KIO::ApplicationLauncherJob(offer); job->setUrls({url}); job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->wParent)); job->start(); connect(job, &KJob::result, this, [tempFile, job]() { if (job->error()) { delete tempFile; } }); // Delete the file only when the composer is closed // (and this object is destroyed). tempFile->setParent(this); // Manages lifetime. } void AttachmentControllerBase::openAttachment(const AttachmentPart::Ptr &part) { QTemporaryFile *tempFile = dumpAttachmentToTempFile(part); if (!tempFile) { KMessageBox::error(d->wParent, i18n("KMail was unable to write the attachment to a temporary file."), i18nc("@title:window", "Unable to open attachment")); return; } tempFile->setPermissions(QFile::ReadUser); auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(tempFile->fileName()), QString::fromLatin1(part->mimeType())); job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, d->wParent)); job->setDeleteTemporaryFile(true); connect(job, &KIO::OpenUrlJob::result, this, [this, tempFile](KJob *job) { if (job->error() == KIO::ERR_USER_CANCELED) { KMessageBox::error(d->wParent, i18n("KMail was unable to open the attachment."), job->errorString()); delete tempFile; } else { // The file was opened. Delete it only when the composer is closed // (and this object is destroyed). tempFile->setParent(this); // Manages lifetime. } }); job->start(); } void AttachmentControllerBase::viewAttachment(const AttachmentPart::Ptr &part) { auto composer = new MessageComposer::Composer; composer->globalPart()->setFallbackCharsetEnabled(true); auto attachmentJob = new MessageComposer::AttachmentJob(part, composer); connect(attachmentJob, &AttachmentJob::result, this, [this](KJob *job) { d->slotAttachmentContentCreated(job); }); attachmentJob->start(); } void AttachmentControllerBase::AttachmentControllerBasePrivate::slotAttachmentContentCreated(KJob *job) { if (!job->error()) { const MessageComposer::AttachmentJob *const attachmentJob = qobject_cast(job); Q_ASSERT(attachmentJob); if (attachmentJob) { Q_EMIT q->showAttachment(attachmentJob->content(), QByteArray()); } } else { // TODO: show warning to the user qCWarning(EDITOR_LOG) << "Error creating KMime::Content for attachment:" << job->errorText(); } } void AttachmentControllerBase::saveAttachmentAs(const AttachmentPart::Ptr &part) { QString pname = part->name(); if (pname.isEmpty()) { pname = i18n("unnamed"); } - const QUrl url = QFileDialog::getSaveFileUrl(d->wParent, i18n("Save Attachment As"), QUrl::fromLocalFile(pname)); + const QString fileName = QFileDialog::getSaveFileName(d->wParent, i18n("Save Attachment As"), pname); - if (url.isEmpty()) { + if (fileName.isEmpty()) { qCDebug(EDITOR_LOG) << "Save Attachment As dialog canceled."; return; } - byteArrayToRemoteFile(part->data(), url); -} - -void AttachmentControllerBase::byteArrayToRemoteFile(const QByteArray &aData, const QUrl &aURL, bool overwrite) -{ - KIO::StoredTransferJob *job = KIO::storedPut(aData, aURL, -1, overwrite ? KIO::Overwrite : KIO::DefaultFlags); - connect(job, &KIO::StoredTransferJob::result, this, &AttachmentControllerBase::slotPutResult); -} - -void AttachmentControllerBase::slotPutResult(KJob *job) -{ - auto _job = qobject_cast(job); - - if (job->error()) { - if (job->error() == KIO::ERR_FILE_ALREADY_EXIST) { - if (KMessageBox::warningContinueCancel(nullptr, - i18n("File %1 exists.\nDo you want to replace it?", _job->url().toLocalFile()), - i18nc("@title:window", "Save to File"), - KGuiItem(i18n("&Replace"))) - == KMessageBox::Continue) { - byteArrayToRemoteFile(_job->data(), _job->url(), true); - } - } else { - KJobUiDelegate *ui = static_cast(job)->uiDelegate(); - ui->showErrorMessage(); - } + QSaveFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + KMessageBox::error(d->wParent, i18n("File %1 could not be created.", fileName), i18n("Error saving message")); + return; } + file.write(part->data()); + file.commit(); } void AttachmentControllerBase::attachmentProperties(const AttachmentPart::Ptr &part) { QPointer dialog = new AttachmentPropertiesDialog(part, false, d->wParent); dialog->setEncryptEnabled(d->encryptEnabled); dialog->setSignEnabled(d->signEnabled); if (dialog->exec() && dialog) { d->model->updateAttachment(part); } delete dialog; } void AttachmentControllerBase::attachDirectory(const QUrl &url) { const int rc = KMessageBox::warningTwoActions(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()), i18nc("@title:window", "Attach directory"), KGuiItem(i18nc("@action:button", "Attach")), KStandardGuiItem::cancel()); if (rc == KMessageBox::ButtonCode::PrimaryAction) { addAttachment(url); } } void AttachmentControllerBase::showAttachClipBoard() { auto job = new MessageComposer::AttachmentClipBoardJob(this); connect(job, &AttachmentClipBoardJob::result, this, [this](KJob *job) { d->attachClipBoardElement(job); }); job->start(); } void AttachmentControllerBase::showAddAttachmentCompressedDirectoryDialog() { const QUrl url = QFileDialog::getExistingDirectoryUrl(d->wParent, i18nc("@title:window", "Attach Directory")); if (url.isValid()) { attachDirectory(url); } } static QString fixEncoding(const QString &encoding) { QString returnEncoding = encoding; // According to https://www.iana.org/assignments/character-sets, uppercase is // preferred in MIME headers const QString returnEncodingToUpper = returnEncoding.toUpper(); if (returnEncodingToUpper.contains(QLatin1String("ISO "))) { returnEncoding = returnEncodingToUpper; returnEncoding.replace(QLatin1String("ISO "), QStringLiteral("ISO-")); } return returnEncoding; } void AttachmentControllerBase::showAddAttachmentFileDialog() { const KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(), QUrl(), QString(), d->wParent, i18nc("@title:window", "Attach File")); if (!result.URLs.isEmpty()) { const QString encoding = fixEncoding(result.encoding); const int numberOfFiles(result.URLs.count()); for (int i = 0; i < numberOfFiles; ++i) { const QUrl url = result.URLs.at(i); QUrl urlWithEncoding = url; QUrlQuery query; query.addQueryItem(QStringLiteral("charset"), encoding); urlWithEncoding.setQuery(query); QMimeDatabase mimeDb; const auto mimeType = mimeDb.mimeTypeForUrl(urlWithEncoding); if (mimeType.name() == QLatin1String("inode/directory")) { const int rc = KMessageBox::warningTwoActions(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()), i18nc("@title:window", "Attach directory"), KGuiItem(i18nc("@action:button", "Attach")), KStandardGuiItem::cancel()); if (rc == KMessageBox::ButtonCode::PrimaryAction) { addAttachment(urlWithEncoding); } } else { addAttachment(urlWithEncoding); } } } } void AttachmentControllerBase::addAttachment(const AttachmentPart::Ptr &part) { part->setEncrypted(d->model->isEncryptSelected()); part->setSigned(d->model->isSignSelected()); d->model->addAttachment(part); Q_EMIT fileAttached(); } void AttachmentControllerBase::addAttachmentUrlSync(const QUrl &url) { MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this); if (ajob->exec()) { AttachmentPart::Ptr part = ajob->attachmentPart(); addAttachment(part); } else { if (ajob->error()) { KMessageBox::error(d->wParent, ajob->errorString(), i18nc("@title:window", "Failed to attach file")); } } } void AttachmentControllerBase::addAttachment(const QUrl &url) { MessageCore::AttachmentFromUrlBaseJob *ajob = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(url, this); connect(ajob, &AttachmentFromUrlBaseJob::result, this, [this](KJob *job) { d->loadJobResult(job); }); ajob->start(); } void AttachmentControllerBase::addAttachments(const QList &urls) { for (const QUrl &url : urls) { addAttachment(url); } } void AttachmentControllerBase::showAttachPublicKeyDialog() { using Kleo::KeySelectionDialog; QPointer dialog = new KeySelectionDialog(i18n("Attach Public OpenPGP Key"), i18n("Select the public key which should be attached."), std::vector(), KeySelectionDialog::PublicKeys | KeySelectionDialog::OpenPGPKeys, false /* no multi selection */, false /* no remember choice box */, d->wParent); if (dialog->exec() == QDialog::Accepted) { exportPublicKey(dialog->selectedKey()); } delete dialog; } void AttachmentControllerBase::attachMyPublicKey() { } void AttachmentControllerBase::enableAttachPublicKey(bool enable) { d->attachPublicKeyAction->setEnabled(enable); } void AttachmentControllerBase::enableAttachMyPublicKey(bool enable) { d->attachMyPublicKeyAction->setEnabled(enable); } void AttachmentControllerBase::clear() { const auto parts = d->model->attachments(); for (const auto &attachmentPart : parts) { if (!d->model->removeAttachment(attachmentPart)) { qCWarning(EDITOR_LOG) << "Impossible to remove attachment" << attachmentPart->fileName(); } } } #include "moc_attachmentcontrollerbase.cpp" diff --git a/server/editor/attachment/attachmentcontrollerbase.h b/server/editor/attachment/attachmentcontrollerbase.h index ebb971e..a5c9012 100644 --- a/server/editor/attachment/attachmentcontrollerbase.h +++ b/server/editor/attachment/attachmentcontrollerbase.h @@ -1,90 +1,88 @@ /* * This file is part of KMail. * SPDX-FileCopyrightText: 2009 Constantin Berzan * * Parts based on KMail code by: * Various authors. * * SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include #include "attachmentpart.h" class KActionCollection; class QAction; class KJob; namespace MessageComposer { class AttachmentModel; /** * @brief The AttachmentControllerBase class */ class AttachmentControllerBase : public QObject { Q_OBJECT public: AttachmentControllerBase(MessageComposer::AttachmentModel *model, QWidget *wParent, KActionCollection *actionCollection); ~AttachmentControllerBase() override; void createActions(); // TODO dnd stuff... void setSelectedParts(const MessageCore::AttachmentPart::List &selectedParts); public Q_SLOTS: /// model sets these void setEncryptEnabled(bool enabled); void setSignEnabled(bool enabled); /// compression is async... void compressAttachment(const MessageCore::AttachmentPart::Ptr &part, bool compress); void showContextMenu(); void openAttachment(const MessageCore::AttachmentPart::Ptr &part); void viewAttachment(const MessageCore::AttachmentPart::Ptr &part); void saveAttachmentAs(const MessageCore::AttachmentPart::Ptr &part); void attachmentProperties(const MessageCore::AttachmentPart::Ptr &part); void showAddAttachmentFileDialog(); void showAddAttachmentCompressedDirectoryDialog(); /// sets sign, encrypt, shows properties dialog if so configured void addAttachment(const MessageCore::AttachmentPart::Ptr &part); void addAttachment(const QUrl &url); void addAttachmentUrlSync(const QUrl &url); void addAttachments(const QList &urls); void showAttachPublicKeyDialog(); void showAttachClipBoard(); virtual void attachMyPublicKey(); void clear(); Q_SIGNALS: void actionsCreated(); void refreshSelection(); void showAttachment(KMime::Content *content, const QByteArray &charset); void selectedAllAttachment(); void fileAttached(); protected: void exportPublicKey(const GpgME::Key &key); void enableAttachPublicKey(bool enable); void enableAttachMyPublicKey(bool enable); - void byteArrayToRemoteFile(const QByteArray &aData, const QUrl &aURL, bool overwrite = false); void openWith(const KService::Ptr &offer = KService::Ptr()); private: void attachDirectory(const QUrl &url); - void slotPutResult(KJob *job); void slotOpenWithDialog(); void slotOpenWithAction(QAction *act); private: class AttachmentControllerBasePrivate; std::unique_ptr const d; }; } // diff --git a/server/editor/urlrequester.h b/server/editor/urlrequester.h index a63a295..fd04914 100644 --- a/server/editor/urlrequester.h +++ b/server/editor/urlrequester.h @@ -1,686 +1,686 @@ /* This file is part of the KDE libraries SPDX-FileCopyrightText: 1999, 2000, 2001 Carsten Pfeiffer SPDX-FileCopyrightText: 2013 Teo Mrnjavac SPDX-License-Identifier: LGPL-2.0-only */ #include "urlrequester.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KUrlDragPushButton : public QPushButton { Q_OBJECT public: explicit KUrlDragPushButton(QWidget *parent) : QPushButton(parent) { new DragDecorator(this); } ~KUrlDragPushButton() override { } void setURL(const QUrl &url) { m_urls.clear(); m_urls.append(url); } private: class DragDecorator : public KDragWidgetDecoratorBase { public: explicit DragDecorator(KUrlDragPushButton *button) : KDragWidgetDecoratorBase(button) , m_button(button) { } protected: QDrag *dragObject() override { if (m_button->m_urls.isEmpty()) { return nullptr; } QDrag *drag = new QDrag(m_button); QMimeData *mimeData = new QMimeData; mimeData->setUrls(m_button->m_urls); drag->setMimeData(mimeData); return drag; } private: KUrlDragPushButton *m_button; }; QList m_urls; }; class KUrlRequester::KUrlRequesterPrivate { public: explicit KUrlRequesterPrivate(KUrlRequester *parent) : m_fileDialogModeWasDirAndFile(false) , m_parent(parent) , edit(nullptr) , combo(nullptr) , fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly) , fileDialogAcceptMode(QFileDialog::AcceptOpen) { } ~KUrlRequesterPrivate() { delete myCompletion; delete myFileDialog; } void init(); void setText(const QString &text) { if (combo) { if (combo->isEditable()) { combo->setEditText(text); } else { int i = combo->findText(text); if (i == -1) { combo->addItem(text); combo->setCurrentIndex(combo->count() - 1); } else { combo->setCurrentIndex(i); } } } else { edit->setText(text); } } void connectSignals(KUrlRequester *receiver) { if (combo) { connect(combo, &QComboBox::currentTextChanged, receiver, &KUrlRequester::textChanged); connect(combo, &QComboBox::editTextChanged, receiver, &KUrlRequester::textEdited); connect(combo, &KComboBox::returnPressed, receiver, &KUrlRequester::returnPressed); } else if (edit) { connect(edit, &QLineEdit::textChanged, receiver, &KUrlRequester::textChanged); connect(edit, &QLineEdit::textEdited, receiver, &KUrlRequester::textEdited); connect(edit, qOverload<>(&QLineEdit::returnPressed), receiver, [this]() { m_parent->Q_EMIT returnPressed(QString{}); }); if (auto kline = qobject_cast(edit)) { connect(kline, &KLineEdit::returnKeyPressed, receiver, &KUrlRequester::returnPressed); } } } void setCompletionObject(KCompletion *comp) { if (combo) { combo->setCompletionObject(comp); } else { edit->setCompletionObject(comp); } } void updateCompletionStartDir(const QUrl &newStartDir) { myCompletion->setDir(newStartDir); } QString text() const { return combo ? combo->currentText() : edit->text(); } /** * replaces ~user or $FOO, if necessary * if text() is a relative path, make it absolute using startDir() */ QUrl url() const { const QString txt = text(); KUrlCompletion *comp; if (combo) { comp = qobject_cast(combo->completionObject()); } else { comp = qobject_cast(edit->completionObject()); } QString enteredPath; if (comp) { enteredPath = comp->replacedPath(txt); } else { enteredPath = txt; } if (Utils::isAbsoluteLocalPath(enteredPath)) { return QUrl::fromLocalFile(enteredPath); } const QUrl enteredUrl = QUrl(enteredPath); // absolute or relative if (enteredUrl.isRelative() && !txt.isEmpty()) { QUrl finalUrl(m_startDir); finalUrl.setPath(Utils::concatPaths(finalUrl.path(), enteredPath)); return finalUrl; } else { return enteredUrl; } } static void applyFileMode(QFileDialog *dlg, KFile::Modes m, QFileDialog::AcceptMode acceptMode) { QFileDialog::FileMode fileMode; bool dirsOnly = false; if (m & KFile::Directory) { fileMode = QFileDialog::Directory; if ((m & KFile::File) == 0 && (m & KFile::Files) == 0) { dirsOnly = true; } } else if (m & KFile::Files && m & KFile::ExistingOnly) { fileMode = QFileDialog::ExistingFiles; } else if (m & KFile::File && m & KFile::ExistingOnly) { fileMode = QFileDialog::ExistingFile; } else { fileMode = QFileDialog::AnyFile; } dlg->setFileMode(fileMode); dlg->setAcceptMode(acceptMode); dlg->setOption(QFileDialog::ShowDirsOnly, dirsOnly); } QUrl getDirFromFileDialog(const QUrl &openUrl) const { return QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly); } void createFileDialog() { // Creates the fileDialog if it doesn't exist yet QFileDialog *dlg = m_parent->fileDialog(); if (!url().isEmpty() && !url().isRelative()) { QUrl u(url()); // If we won't be able to list it (e.g. http), then don't try :) if (KProtocolManager::supportsListing(u)) { dlg->selectUrl(u); } } else { dlg->setDirectoryUrl(m_startDir); } dlg->setAcceptMode(fileDialogAcceptMode); // Update the file dialog window modality if (dlg->windowModality() != fileDialogModality) { dlg->setWindowModality(fileDialogModality); } if (fileDialogModality == Qt::NonModal) { dlg->show(); } else { dlg->exec(); } } // slots void slotUpdateUrl(); void slotOpenDialog(); void slotFileDialogAccepted(); QUrl m_startDir; bool m_startDirCustomized; bool m_fileDialogModeWasDirAndFile; KUrlRequester *const m_parent; // TODO: rename to 'q' KLineEdit *edit; KComboBox *combo; KFile::Modes fileDialogMode; QFileDialog::AcceptMode fileDialogAcceptMode; QStringList nameFilters; QStringList mimeTypeFilters; KEditListWidget::CustomEditor editor; KUrlDragPushButton *myButton; QFileDialog *myFileDialog; KUrlCompletion *myCompletion; Qt::WindowModality fileDialogModality; }; KUrlRequester::KUrlRequester(QWidget *editWidget, QWidget *parent) : QWidget(parent) , d(new KUrlRequesterPrivate(this)) { // must have this as parent editWidget->setParent(this); d->combo = qobject_cast(editWidget); d->edit = qobject_cast(editWidget); if (d->edit) { d->edit->setClearButtonEnabled(true); } d->init(); } KUrlRequester::KUrlRequester(QWidget *parent) : QWidget(parent) , d(new KUrlRequesterPrivate(this)) { d->init(); } KUrlRequester::KUrlRequester(const QUrl &url, QWidget *parent) : QWidget(parent) , d(new KUrlRequesterPrivate(this)) { d->init(); setUrl(url); } KUrlRequester::~KUrlRequester() { QWidget *widget = d->combo ? static_cast(d->combo) : static_cast(d->edit); widget->removeEventFilter(this); } void KUrlRequester::KUrlRequesterPrivate::init() { myFileDialog = nullptr; fileDialogModality = Qt::ApplicationModal; if (!combo && !edit) { edit = new KLineEdit(m_parent); edit->setClearButtonEnabled(true); } QWidget *widget = combo ? static_cast(combo) : static_cast(edit); QHBoxLayout *topLayout = new QHBoxLayout(m_parent); topLayout->setContentsMargins(0, 0, 0, 0); topLayout->setSpacing(-1); // use default spacing topLayout->addWidget(widget); myButton = new KUrlDragPushButton(m_parent); myButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); int buttonSize = myButton->sizeHint().expandedTo(widget->sizeHint()).height(); myButton->setFixedSize(buttonSize, buttonSize); myButton->setToolTip(i18n("Open file dialog")); connect(myButton, &KUrlDragPushButton::pressed, m_parent, [this]() { slotUpdateUrl(); }); widget->installEventFilter(m_parent); m_parent->setFocusProxy(widget); m_parent->setFocusPolicy(Qt::StrongFocus); topLayout->addWidget(myButton); connectSignals(m_parent); connect(myButton, &KUrlDragPushButton::clicked, m_parent, [this]() { slotOpenDialog(); }); m_startDir = QUrl::fromLocalFile(QDir::currentPath()); m_startDirCustomized = false; myCompletion = new KUrlCompletion(); updateCompletionStartDir(m_startDir); setCompletionObject(myCompletion); QAction *openAction = new QAction(m_parent); openAction->setShortcut(QKeySequence::Open); m_parent->connect(openAction, &QAction::triggered, m_parent, [this]() { slotOpenDialog(); }); } void KUrlRequester::setUrl(const QUrl &url) { d->setText(url.toDisplayString(QUrl::PreferLocalFile)); } void KUrlRequester::setText(const QString &text) { d->setText(text); } void KUrlRequester::setStartDir(const QUrl &startDir) { d->m_startDir = startDir; d->m_startDirCustomized = true; d->updateCompletionStartDir(startDir); } void KUrlRequester::changeEvent(QEvent *e) { if (e->type() == QEvent::WindowTitleChange) { if (d->myFileDialog) { d->myFileDialog->setWindowTitle(windowTitle()); } } QWidget::changeEvent(e); } QUrl KUrlRequester::url() const { #include "urlrequester.moc" return d->url(); } QUrl KUrlRequester::startDir() const { return d->m_startDir; } QString KUrlRequester::text() const { return d->text(); } void KUrlRequester::KUrlRequesterPrivate::slotOpenDialog() { if (myFileDialog) { if (myFileDialog->isVisible()) { // The file dialog is already being shown, raise it and exit myFileDialog->raise(); myFileDialog->activateWindow(); return; } } if (!m_fileDialogModeWasDirAndFile && (((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) || /* catch possible fileDialog()->setMode( KFile::Directory ) changes */ (myFileDialog && (myFileDialog->fileMode() == QFileDialog::Directory && myFileDialog->testOption(QFileDialog::ShowDirsOnly))))) { const QUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative()) ? m_parent->url() : m_startDir; /* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */ QUrl newUrl; if (fileDialogMode & KFile::LocalOnly) { newUrl = QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly, QStringList() << QStringLiteral("file")); } else { newUrl = getDirFromFileDialog(openUrl); } if (newUrl.isValid()) { m_parent->setUrl(newUrl); Q_EMIT m_parent->urlSelected(url()); } } else { Q_EMIT m_parent->openFileDialog(m_parent); if (((fileDialogMode & KFile::Directory) && (fileDialogMode & KFile::File)) || m_fileDialogModeWasDirAndFile) { QMenu *dirOrFileMenu = new QMenu(); QAction *fileAction = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("File")); QAction *dirAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Directory")); dirOrFileMenu->addAction(fileAction); dirOrFileMenu->addAction(dirAction); connect(fileAction, &QAction::triggered, [this]() { fileDialogMode = KFile::File; applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode); m_fileDialogModeWasDirAndFile = true; createFileDialog(); }); connect(dirAction, &QAction::triggered, [this]() { fileDialogMode = KFile::Directory; applyFileMode(m_parent->fileDialog(), fileDialogMode, fileDialogAcceptMode); m_fileDialogModeWasDirAndFile = true; createFileDialog(); }); dirOrFileMenu->exec(m_parent->mapToGlobal(QPoint(m_parent->width(), m_parent->height()))); return; } createFileDialog(); } } void KUrlRequester::KUrlRequesterPrivate::slotFileDialogAccepted() { if (!myFileDialog) { return; } const QUrl newUrl = myFileDialog->selectedUrls().constFirst(); if (newUrl.isValid()) { m_parent->setUrl(newUrl); Q_EMIT m_parent->urlSelected(url()); // remember url as defaultStartDir and update startdir for autocompletion if (newUrl.isLocalFile() && !m_startDirCustomized) { m_startDir = newUrl.adjusted(QUrl::RemoveFilename); updateCompletionStartDir(m_startDir); } } } void KUrlRequester::setMode(KFile::Modes mode) { Q_ASSERT((mode & KFile::Files) == 0); d->fileDialogMode = mode; if ((mode & KFile::Directory) && !(mode & KFile::File)) { d->myCompletion->setMode(KUrlCompletion::DirCompletion); } if (d->myFileDialog) { d->applyFileMode(d->myFileDialog, mode, d->fileDialogAcceptMode); } } KFile::Modes KUrlRequester::mode() const { return d->fileDialogMode; } void KUrlRequester::setAcceptMode(QFileDialog::AcceptMode mode) { d->fileDialogAcceptMode = mode; if (d->myFileDialog) { d->applyFileMode(d->myFileDialog, d->fileDialogMode, mode); } } QFileDialog::AcceptMode KUrlRequester::acceptMode() const { return d->fileDialogAcceptMode; } QStringList KUrlRequester::nameFilters() const { return d->nameFilters; } void KUrlRequester::setNameFilters(const QStringList &filters) { d->nameFilters = filters; if (d->myFileDialog) { d->myFileDialog->setNameFilters(d->nameFilters); } } void KUrlRequester::setNameFilter(const QString &filter) { if (filter.isEmpty()) { setNameFilters(QStringList()); return; } // by default use ";;" as separator // if not present, support alternatively "\n" (matching QFileDialog behaviour) // if also not present split() will simply return the string passed in QString separator = QStringLiteral(";;"); if (!filter.contains(separator)) { separator = QStringLiteral("\n"); } setNameFilters(filter.split(separator)); } void KUrlRequester::setMimeTypeFilters(const QStringList &mimeTypes) { d->mimeTypeFilters = mimeTypes; if (d->myFileDialog) { d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters); } d->myCompletion->setMimeTypeFilters(d->mimeTypeFilters); } QStringList KUrlRequester::mimeTypeFilters() const { return d->mimeTypeFilters; } QFileDialog *KUrlRequester::fileDialog() const { if (d->myFileDialog && ((d->myFileDialog->fileMode() == QFileDialog::Directory && !(d->fileDialogMode & KFile::Directory)) || (d->myFileDialog->fileMode() != QFileDialog::Directory && (d->fileDialogMode & KFile::Directory)))) { delete d->myFileDialog; d->myFileDialog = nullptr; } if (!d->myFileDialog) { d->myFileDialog = new QFileDialog(window(), windowTitle()); if (!d->mimeTypeFilters.isEmpty()) { d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters); } else { d->myFileDialog->setNameFilters(d->nameFilters); } d->applyFileMode(d->myFileDialog, d->fileDialogMode, d->fileDialogAcceptMode); d->myFileDialog->setWindowModality(d->fileDialogModality); connect(d->myFileDialog, &QFileDialog::accepted, this, [this]() { d->slotFileDialogAccepted(); }); } return d->myFileDialog; } void KUrlRequester::clear() { d->setText(QString()); } KLineEdit *KUrlRequester::lineEdit() const { return d->edit; } KComboBox *KUrlRequester::comboBox() const { return d->combo; } void KUrlRequester::KUrlRequesterPrivate::slotUpdateUrl() { const QUrl visibleUrl = url(); QUrl u = visibleUrl; if (visibleUrl.isRelative()) { u = QUrl::fromLocalFile(QDir::currentPath() + QLatin1Char('/')).resolved(visibleUrl); } myButton->setURL(u); } bool KUrlRequester::eventFilter(QObject *obj, QEvent *ev) { if ((d->edit == obj) || (d->combo == obj)) { if ((ev->type() == QEvent::FocusIn) || (ev->type() == QEvent::FocusOut)) // Forward focusin/focusout events to the urlrequester; needed by file form element in khtml { QApplication::sendEvent(this, ev); } } return QWidget::eventFilter(obj, ev); } QPushButton *KUrlRequester::button() const { return d->myButton; } KUrlCompletion *KUrlRequester::completionObject() const { return d->myCompletion; } void KUrlRequester::setPlaceholderText(const QString &msg) { if (d->edit) { d->edit->setPlaceholderText(msg); } } QString KUrlRequester::placeholderText() const { if (d->edit) { return d->edit->placeholderText(); } else { return QString(); } } Qt::WindowModality KUrlRequester::fileDialogModality() const { return d->fileDialogModality; } void KUrlRequester::setFileDialogModality(Qt::WindowModality modality) { d->fileDialogModality = modality; } const KEditListWidget::CustomEditor &KUrlRequester::customEditor() { setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); KLineEdit *edit = d->edit; if (!edit && d->combo) { edit = qobject_cast(d->combo->lineEdit()); } #ifndef NDEBUG if (!edit) { - qCWarning(KIO_WIDGETS) << "KUrlRequester's lineedit is not a KLineEdit!??\n"; + qCWarning(EDITOR_LOG) << "KUrlRequester's lineedit is not a KLineEdit!??\n"; } #endif d->editor.setRepresentationWidget(this); d->editor.setLineEdit(edit); return d->editor; } KUrlComboRequester::KUrlComboRequester(QWidget *parent) : KUrlRequester(new KComboBox(false), parent) , d(nullptr) { } #include "urlrequester.moc" #include "moc_urlrequester.cpp"