Page MenuHome GnuPG

No OneTemporary

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 94c0b7c..bfa85cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,134 +1,136 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(PIM_VERSION "5.23.42")
project(MimeTreeParser VERSION ${PIM_VERSION})
# ECM setup
set(KF_MIN_VERSION "5.105.0")
find_package(ECM ${KF_MIN_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${ECM_MODULE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
)
set(QT_REQUIRED_VERSION "5.15.2")
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
endif()
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(ECMGeneratePriFile)
+include(ECMQmlModule)
include(ECMSetupVersion)
include(FeatureSummary)
include(KDEGitCommitHooks)
include(KDEClangFormat)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMAddQch)
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
set(KF_MIN_VERSION "5.240.0")
set(KF_MAJOR_VERSION "6")
else()
set(KF_MAJOR_VERSION "5")
endif()
set(KPIM_MIME_VERSION "5.23.40")
ecm_setup_version(PROJECT
VARIABLE_PREFIX MIMETREEPARSER
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfigVersion.cmake"
SOVERSION 5
)
configure_file(mimetreeparser-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/mimetreeparser-version.h @ONLY)
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
if(BUILD_TESTING)
add_definitions(-DBUILD_TESTING)
endif()
########### Find packages ###########
find_package(Qt${KF_MAJOR_VERSION}Gui ${QT_MIN_VERSION} CONFIG REQUIRED)
+find_package(Qt${KF_MAJOR_VERSION}Quick ${QT_MIN_VERSION} CONFIG REQUIRED)
find_package(KF${KF_MAJOR_VERSION}I18n ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KF${KF_MAJOR_VERSION}Codecs ${KF_MIN_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}Mime ${KPIM_MIME_VERSION} CONFIG REQUIRED)
find_package(Gpgme REQUIRED)
########### Targets ###########
ecm_set_disabled_deprecation_versions(QT 6.4 KF 5.105.0)
option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF)
if (USE_UNITY_CMAKE_SUPPORT)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON)
add_definitions(-DCOMPILE_WITH_UNITY_CMAKE_SUPPORT)
endif()
add_subdirectory(src)
add_subdirectory(examples)
if (BUILD_TESTING)
#add_subdirectory(autotests)
endif ()
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim${KF_MAJOR_VERSION}MimeTreeParser")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim${KF_MAJOR_VERSION}MimeTreeparser_QCH
FILE KPim${KF_MAJOR_VERSION}MimeTreeParserQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim${KF_MAJOR_VERSION}MimeTreeParserConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KPim${KF_MAJOR_VERSION}MimeTreeParserTargets.cmake NAMESPACE KPim${KF_MAJOR_VERSION}::)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser COMPONENT Devel
)
ecm_qt_install_logging_categories(
EXPORT MIMETREEPARSER
FILE mimetreeparser.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
ki18n_install(po)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/examples/qml/content/main.qml b/examples/qml/content/main.qml
index 3d336a2..022a6b2 100644
--- a/examples/qml/content/main.qml
+++ b/examples/qml/content/main.qml
@@ -1,33 +1,34 @@
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQuick.Dialogs 1.0
import org.kde.kirigami 2.20 as Kirigami
+import org.kde.pim.mimetreeparser 1.0
import org.kde.mimetreeparser 1.0
Kirigami.ApplicationWindow {
id: root
readonly property Kirigami.Action openFileAction: Kirigami.Action {
text: i18n("Open File")
onTriggered: fileDialog.open()
}
FileDialog {
id: fileDialog
title: i18n("Choose file")
onAccepted: fileOpener.open(fileUrl)
}
FileOpener {
id: fileOpener
objectName: "FileOpener"
onMessageOpened: pageStack.currentItem.message = message
}
pageStack.initialPage: MailViewer {
}
}
diff --git a/examples/qml/resources.qrc b/examples/qml/resources.qrc
index d2e4b11..b351c4d 100644
--- a/examples/qml/resources.qrc
+++ b/examples/qml/resources.qrc
@@ -1,19 +1,9 @@
<RCC>
<!--
SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: GPL-2.0-or-later
-->
<qresource prefix="/">
<file>content/main.qml</file>
- <file>content/Banner.qml</file>
- <file>content/ErrorPart.qml</file>
- <file>content/HtmlPart.qml</file>
- <file>content/ICalPart.qml</file>
- <file>content/MailPart.qml</file>
- <file>content/MailPartModel.qml</file>
- <file>content/MailPartView.qml</file>
- <file>content/MailViewer.qml</file>
- <file>content/TextPart.qml</file>
- <file>content/AttachmentDelegate.qml</file>
</qresource>
</RCC>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 858c3fc..493306f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,133 +1,135 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
add_definitions(-DTRANSLATION_DOMAIN=\"libmimetreeparser5\")
+add_subdirectory(plugins)
+
add_library(KPim${KF_MAJOR_VERSION}MimeTreeParser)
add_library(KPim${KF_MAJOR_VERSION}::MimeTreeParser ALIAS KPim${KF_MAJOR_VERSION}MimeTreeParser)
target_sources(KPim${KF_MAJOR_VERSION}MimeTreeParser PRIVATE
crypto.h
crypto.cpp
errors.h
async.h
attachmentmodel.h
bodypartformatter.h
bodypartformatterbasefactory.h
bodypartformatterbasefactory_p.h
cryptohelper.h
enums.h
htmlutils.h
mailcrypto.h
messageparser.h
messagepart.h
mimetreeparser_debug.h
objecttreeparser.h
partmetadata.h
partmodel.h
utils.h
attachmentmodel.cpp
bodypartformatter.cpp
bodypartformatter_impl.cpp
bodypartformatterbasefactory.cpp
cryptohelper.cpp
htmlutils.cpp
mailcrypto.cpp
messageparser.cpp
messagepart.cpp
mimetreeparser_debug.cpp
objecttreeparser.cpp
partmodel.cpp
utils.cpp
)
ecm_qt_declare_logging_category(KPim${KF_MAJOR_VERSION}MimeTreeParser
HEADER mimetreeparser_debug.h
IDENTIFIER MIMETREEPARSER_LOG
CATEGORY_NAME org.kde.pim.mimetreeparser
DESCRIPTION "mimetreeparser (pim lib)"
EXPORT MIMETREEPARSER
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParser PROPERTIES UNITY_BUILD ON)
endif()
generate_export_header(KPim${KF_MAJOR_VERSION}MimeTreeParser BASE_NAME mimetreeparser)
target_include_directories(KPim${KF_MAJOR_VERSION}MimeTreeParser INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/AkonadiCalendar>")
target_include_directories(KPim${KF_MAJOR_VERSION}MimeTreeParser PUBLIC "$<BUILD_INTERFACE:${Akonadi-Calendar_SOURCE_DIR}/src;${Akonadi-Calendar_BINARY_DIR}/src>")
target_link_libraries(KPim${KF_MAJOR_VERSION}MimeTreeParser
PUBLIC
KPim${KF_MAJOR_VERSION}::Mime
KF${KF_MAJOR_VERSION}::I18n
Qt${KF_MAJOR_VERSION}::Gui
PRIVATE
KF${KF_MAJOR_VERSION}::Codecs
Gpgme::Gpgme
)
set_target_properties(KPim${KF_MAJOR_VERSION}MimeTreeParser PROPERTIES
VERSION ${MIMETREEPARSER_VERSION}
SOVERSION ${MIMETREEPARSER_SOVERSION}
EXPORT_NAME MimeTreeParser
)
ecm_generate_pri_file(BASE_NAME MimeTreeParser
LIB_NAME KPim${KF_MAJOR_VERSION}MimeTreeParser
DEPS "MimeTreeParser"
FILENAME_VAR PRI_FILENAME
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(TARGETS
KPim${KF_MAJOR_VERSION}MimeTreeParser
EXPORT KPim${KF_MAJOR_VERSION}MimeTreeParserTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
ecm_generate_headers(MimeTreeParser_CamelCase_HEADERS
HEADER_NAMES
AttachmentModel
ObjectTreeParser
MessageParser
REQUIRED_HEADERS MimeTreeParser_HEADERS
PREFIX MimeTreeParser
)
install(FILES
${MimeTreeParser_CamelCase_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser/MimeTreeParser
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_export.h
${MimeTreeParser_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim${KF_MAJOR_VERSION}/MimeTreeParser/mimetreeparser
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KPim${KF_MAJOR_VERSION}MimeTreeParser_QCH
NAME MimeTreeParser
BASE_NAME KPim${KF_MAJOR_VERSION}MimeTreeParser
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
# using only public headers, to cover only public API
SOURCES ${MimeTreeParser_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt${QT_MAJOR_VERSION}Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
MIMETREEPARSER_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
diff --git a/src/messageparser.cpp b/src/messageparser.cpp
index 82ee32e..d3cbef4 100644
--- a/src/messageparser.cpp
+++ b/src/messageparser.cpp
@@ -1,127 +1,137 @@
// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messageparser.h"
#include "objecttreeparser.h"
#include <KLocalizedString>
#include <QElapsedTimer>
#include "async.h"
#include "attachmentmodel.h"
#include "partmodel.h"
class MessagePartPrivate
{
public:
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
KMime::Message::Ptr mMessage;
};
MessageParser::MessageParser(QObject *parent)
: QObject(parent)
, d(std::unique_ptr<MessagePartPrivate>(new MessagePartPrivate))
{
}
MessageParser::~MessageParser()
{
}
KMime::Message::Ptr MessageParser::message() const
{
return d->mMessage;
}
void MessageParser::setMessage(const KMime::Message::Ptr message)
{
if (message == d->mMessage) {
return;
}
d->mMessage = message;
QElapsedTimer time;
time.start();
auto parser = std::make_shared<MimeTreeParser::ObjectTreeParser>();
parser->parseObjectTree(message.data());
qDebug() << "Message parsing took: " << time.elapsed();
parser->decryptParts();
qDebug() << "Message parsing and decryption/verification: " << time.elapsed();
d->mParser = parser;
Q_EMIT htmlChanged();
}
bool MessageParser::loaded() const
{
return bool{d->mParser};
}
QString MessageParser::structureAsString() const
{
if (!d->mParser) {
return {};
}
return d->mParser->structureAsString();
}
QAbstractItemModel *MessageParser::parts() const
{
if (!d->mParser) {
return nullptr;
}
const auto model = new PartModel(d->mParser);
return model;
}
QAbstractItemModel *MessageParser::attachments() const
{
if (!d->mParser) {
return nullptr;
}
const auto model = new AttachmentModel(d->mParser);
return model;
}
QString MessageParser::subject() const
{
- if (d->mMessage && d->mMessage->subject()) {
+ if (d->mMessage && d->mMessage->subject() && d->mMessage->subject()->asUnicodeString().length()) {
return d->mMessage->subject()->asUnicodeString();
} else {
+ if (d->mMessage) {
+ const auto contents = d->mMessage->contents();
+ for (const auto content : contents) {
+ if (const auto message = dynamic_cast<KMime::Message *>(content)) {
+ if (message->subject() && message->subject()->asUnicodeString().length()) {
+ return message->subject()->asUnicodeString();
+ }
+ }
+ }
+ }
return i18nc("displayed as subject when the subject of a mail is empty", "No Subject");
}
}
QString MessageParser::from() const
{
if (d->mMessage && d->mMessage->from()) {
return d->mMessage->from()->asUnicodeString();
} else {
return QString();
}
}
QString MessageParser::sender() const
{
if (d->mMessage && d->mMessage->sender()) {
return d->mMessage->sender()->asUnicodeString();
} else {
return QString();
}
}
QString MessageParser::to() const
{
if (d->mMessage && d->mMessage->to()) {
return d->mMessage->to()->asUnicodeString();
} else {
return i18nc("displayed when a mail has unknown sender, receiver or date", "Unknown");
}
}
QDateTime MessageParser::date() const
{
if (d->mMessage && d->mMessage->date()) {
return d->mMessage->date()->dateTime();
} else {
return QDateTime();
}
}
diff --git a/src/messagepart.cpp b/src/messagepart.cpp
index ce60a74..5246103 100644
--- a/src/messagepart.cpp
+++ b/src/messagepart.cpp
@@ -1,962 +1,964 @@
// SPDX-FileCopyrightText: 2015 Sandro Knauß <knauss@kolabsys.com>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "messagepart.h"
#include "cryptohelper.h"
#include "mimetreeparser_debug.h"
#include "objecttreeparser.h"
#include "utils.h"
#include <KLocalizedString>
#include <KMime/Content>
#include <QTextCodec>
using namespace MimeTreeParser;
using namespace Crypto;
//------MessagePart-----------------------
MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text, KMime::Content *node)
: mText(text)
, mOtp(otp)
, mParentPart(nullptr)
, mNode(node) // only null for messagepartlist
, mError(NoError)
, mRoot(false)
{
}
MessagePart::~MessagePart()
{
for (auto n : std::as_const(mNodesToDelete)) {
delete n;
}
}
MessagePart::Disposition MessagePart::disposition() const
{
if (!mNode) {
return Invalid;
}
const auto cd = mNode->contentDisposition(false);
if (!cd) {
return Invalid;
}
switch (cd->disposition()) {
case KMime::Headers::CDinline:
return Inline;
case KMime::Headers::CDattachment:
return Attachment;
default:
return Invalid;
}
}
QString MessagePart::filename() const
{
if (!mNode) {
return {};
}
if (const auto cd = mNode->contentDisposition(false)) {
const auto name = cd->filename();
// Allow for a fallback for mails that have a ContentDisposition header, but don't set the filename anyways.
// Not the recommended way, but exists.
if (!name.isEmpty()) {
return name;
}
}
if (const auto ct = mNode->contentType(false)) {
return ct->name();
}
return {};
}
static KMime::Headers::ContentType *contentType(KMime::Content *node)
{
if (node) {
return node->contentType(false);
}
return nullptr;
}
QByteArray MessagePart::charset() const
{
if (!mNode) {
return "us-ascii";
}
if (auto ct = contentType(mNode)) {
return ct->charset();
}
// Per rfc2045 us-ascii is the default
return "us-ascii";
}
QByteArray MessagePart::mimeType() const
{
if (auto ct = contentType(mNode)) {
return ct->mimeType();
}
return {};
}
bool MessagePart::isText() const
{
if (auto ct = contentType(mNode)) {
return ct->isText();
}
return false;
}
MessagePart::Error MessagePart::error() const
{
return mError;
}
QString MessagePart::errorString() const
{
return mMetaData.errorText;
}
PartMetaData *MessagePart::partMetaData()
{
return &mMetaData;
}
bool MessagePart::isAttachment() const
{
if (mNode) {
return KMime::isAttachment(mNode);
}
return false;
}
KMime::Content *MessagePart::node() const
{
return mNode;
}
void MessagePart::setIsRoot(bool root)
{
mRoot = root;
}
bool MessagePart::isRoot() const
{
return mRoot;
}
QString MessagePart::text() const
{
return mText;
}
void MessagePart::setText(const QString &text)
{
mText = text;
}
bool MessagePart::isHtml() const
{
return false;
}
MessagePart *MessagePart::parentPart() const
{
return mParentPart;
}
void MessagePart::setParentPart(MessagePart *parentPart)
{
mParentPart = parentPart;
}
QString MessagePart::htmlContent() const
{
return text();
}
QString MessagePart::plaintextContent() const
{
return text();
}
void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
{
auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
mRoot = subMessagePart->isRoot();
for (const auto &part : std::as_const(subMessagePart->subParts())) {
appendSubPart(part);
}
}
void MessagePart::parseInternal(const QByteArray &data)
{
auto tempNode = new KMime::Content();
const auto lfData = KMime::CRLFtoLF(data);
// We have to deal with both bodies and full parts. In inline encrypted/signed parts we can have nested parts,
// or just plain-text, and both ends up here. setContent defaults to setting only the header, so we have to avoid this.
if (lfData.contains("\n\n")) {
tempNode->setContent(lfData);
} else {
tempNode->setBody(lfData);
}
tempNode->parse();
tempNode->contentType()->setCharset(charset());
bindLifetime(tempNode);
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString("temporary node");
}
parseInternal(tempNode);
}
QString MessagePart::renderInternalText() const
{
QString text;
for (const auto &mp : subParts()) {
text += mp->text();
}
return text;
}
void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
{
messagePart->setParentPart(this);
mBlocks.append(messagePart);
}
const QVector<MessagePart::Ptr> &MessagePart::subParts() const
{
return mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !mBlocks.isEmpty();
}
QVector<SignedMessagePart *> MessagePart::signatures() const
{
QVector<SignedMessagePart *> list;
if (auto sig = dynamic_cast<SignedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<SignedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
QVector<EncryptedMessagePart *> MessagePart::encryptions() const
{
QVector<EncryptedMessagePart *> list;
if (auto sig = dynamic_cast<EncryptedMessagePart *>(const_cast<MessagePart *>(this))) {
list << sig;
}
auto parent = parentPart();
while (parent) {
if (auto sig = dynamic_cast<EncryptedMessagePart *>(parent)) {
list << sig;
}
parent = parent->parentPart();
}
return list;
}
KMMsgEncryptionState MessagePart::encryptionState() const
{
if (!encryptions().isEmpty()) {
return KMMsgFullyEncrypted;
}
return KMMsgNotEncrypted;
}
KMMsgSignatureState MessagePart::signatureState() const
{
if (!signatures().isEmpty()) {
return KMMsgFullySigned;
}
return KMMsgNotSigned;
}
void MessagePart::bindLifetime(KMime::Content *node)
{
mNodesToDelete << node;
}
KMime::Headers::Base *MimeTreeParser::MessagePart::header(const char *header) const
{
if (node() && node()->hasHeader(header)) {
return node()->headerByType(header);
}
if (auto parent = parentPart()) {
return parent->header(header);
}
return nullptr;
}
//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
QString MessagePartList::text() const
{
return renderInternalText();
}
QString MessagePartList::plaintextContent() const
{
return QString();
}
QString MessagePartList::htmlContent() const
{
return QString();
}
//-----TextMessageBlock----------------------
TextMessagePart::TextMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePartList(otp, node)
, mSignatureState(KMMsgSignatureStateUnknown)
, mEncryptionState(KMMsgEncryptionStateUnknown)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
parseContent();
}
void TextMessagePart::parseContent()
{
mSignatureState = KMMsgNotSigned;
mEncryptionState = KMMsgNotEncrypted;
const auto blocks = prepareMessageForDecryption(mNode->decodedContent());
// We also get blocks for unencrypted messages
if (!blocks.isEmpty()) {
const auto aCodec = mOtp->codecFor(mNode);
const auto cryptProto = OpenPGP;
/* The (overall) signature/encrypted status is broken
* if one unencrypted part is at the beginning or in the middle
* because mailmain adds an unencrypted part at the end this should not break the overall status
*
* That's why we first set the tmp status and if one crypted/signed block comes afterwards, than
* the status is set to unencryped
*/
bool fullySignedOrEncrypted = true;
bool fullySignedOrEncryptedTmp = true;
for (const auto &block : blocks) {
if (!fullySignedOrEncryptedTmp) {
fullySignedOrEncrypted = false;
}
if (block.type() == NoPgpBlock && !block.text().trimmed().isEmpty()) {
fullySignedOrEncryptedTmp = false;
appendSubPart(MessagePart::Ptr(new MessagePart(mOtp, aCodec->toUnicode(KMime::CRLFtoLF(block.text())))));
} else if (block.type() == PgpMessageBlock) {
KMime::Content *content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, content, content, false));
mp->bindLifetime(content);
mp->setIsEncrypted(true);
appendSubPart(mp);
} else if (block.type() == ClearsignedBlock) {
KMime::Content *content = new KMime::Content;
content->setBody(block.text());
content->parse();
content->contentType()->setCharset(charset());
SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, cryptProto, nullptr, content, false));
mp->bindLifetime(content);
mp->setIsSigned(true);
appendSubPart(mp);
} else {
continue;
}
const auto mp = subParts().last().staticCast<MessagePart>();
const PartMetaData *messagePart(mp->partMetaData());
if (!messagePart->isEncrypted && !messagePart->isSigned && !block.text().trimmed().isEmpty()) {
mp->setText(aCodec->toUnicode(KMime::CRLFtoLF(block.text())));
}
if (messagePart->isEncrypted) {
mEncryptionState = KMMsgPartiallyEncrypted;
}
if (messagePart->isSigned) {
mSignatureState = KMMsgPartiallySigned;
}
}
// Do we have an fully Signed/Encrypted Message?
if (fullySignedOrEncrypted) {
if (mSignatureState == KMMsgPartiallySigned) {
mSignatureState = KMMsgFullySigned;
}
if (mEncryptionState == KMMsgPartiallyEncrypted) {
mEncryptionState = KMMsgFullyEncrypted;
}
}
}
}
KMMsgEncryptionState TextMessagePart::encryptionState() const
{
if (mEncryptionState == KMMsgNotEncrypted) {
return MessagePart::encryptionState();
}
return mEncryptionState;
}
KMMsgSignatureState TextMessagePart::signatureState() const
{
if (mSignatureState == KMMsgNotSigned) {
return MessagePart::signatureState();
}
return mSignatureState;
}
//-----AttachmentMessageBlock----------------------
AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: TextMessagePart(otp, node)
{
}
//-----HtmlMessageBlock----------------------
HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setText(mOtp->codecFor(mNode)->toUnicode(KMime::CRLFtoLF(mNode->decodedContent())));
}
//-----MimeMessageBlock----------------------
MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
: MessagePart(otp, QString(), node)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
parseInternal(mNode, onlyOneMimePart);
}
MimeMessagePart::~MimeMessagePart()
{
}
QString MimeMessagePart::text() const
{
return renderInternalText();
}
QString MimeMessagePart::plaintextContent() const
{
return QString();
}
QString MimeMessagePart::htmlContent() const
{
return QString();
}
//-----AlternativeMessagePart----------------------
AlternativeMessagePart::AlternativeMessagePart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
if (auto dataIcal = findTypeInDirectChildren(mNode, "text/calendar")) {
mChildParts[MultipartIcal] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataIcal, true));
}
if (auto dataText = findTypeInDirectChildren(mNode, "text/plain")) {
mChildParts[MultipartPlain] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataText, true));
}
if (auto dataHtml = findTypeInDirectChildren(mNode, "text/html")) {
mChildParts[MultipartHtml] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, dataHtml, true));
} else {
// If we didn't find the HTML part as the first child of the multipart/alternative, it might
// be that this is a HTML message with images, and text/plain and multipart/related are the
// immediate children of this multipart/alternative node.
// In this case, the HTML node is a child of multipart/related.
// In the case of multipart/related we don't expect multiple html parts, it is usually used to group attachments
// with html content.
//
// In any case, this is not a complete implementation of MIME, but an approximation for the kind of mails we actually see in the wild.
auto data = [&] {
if (auto d = findTypeInDirectChildren(mNode, "multipart/related")) {
return d;
}
return findTypeInDirectChildren(mNode, "multipart/mixed");
}();
if (data) {
QString htmlContent;
const auto parts = data->contents();
for (auto p : parts) {
if ((!p->contentType()->isEmpty()) && (p->contentType()->mimeType() == "text/html")) {
htmlContent += MimeMessagePart(mOtp, p, true).text();
} else if (KMime::isAttachment(p)) {
appendSubPart(MimeMessagePart::Ptr(new MimeMessagePart(otp, p, true)));
}
}
mChildParts[MultipartHtml] = MessagePart::Ptr(new MessagePart(mOtp, htmlContent, nullptr));
}
}
}
AlternativeMessagePart::~AlternativeMessagePart()
{
}
QList<AlternativeMessagePart::HtmlMode> AlternativeMessagePart::availableModes()
{
return mChildParts.keys();
}
QString AlternativeMessagePart::text() const
{
if (mChildParts.contains(MultipartPlain)) {
return mChildParts[MultipartPlain]->text();
}
return QString();
}
bool AlternativeMessagePart::isHtml() const
{
return mChildParts.contains(MultipartHtml);
}
QString AlternativeMessagePart::plaintextContent() const
{
return text();
}
QString AlternativeMessagePart::htmlContent() const
{
if (mChildParts.contains(MultipartHtml)) {
return mChildParts[MultipartHtml]->text();
} else {
return plaintextContent();
}
}
QString AlternativeMessagePart::icalContent() const
{
if (mChildParts.contains(MultipartIcal)) {
return mChildParts[MultipartIcal]->text();
}
return {};
}
//-----CertMessageBlock----------------------
CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const CryptoProtocol cryptoProto)
: MessagePart(otp, QString(), node)
, mProtocol(cryptoProto)
{
if (!mNode) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
}
CertMessagePart::~CertMessagePart()
{
}
void CertMessagePart::import()
{
importKey(mProtocol, mNode->decodedContent());
}
QString CertMessagePart::text() const
{
return QString();
}
//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp,
const CryptoProtocol cryptoProto,
KMime::Content *node,
KMime::Content *signedData,
bool parseAfterDecryption)
: MessagePart(otp, {}, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mSignedData(signedData)
{
mMetaData.isSigned = true;
mMetaData.isGoodSignature = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
}
SignedMessagePart::~SignedMessagePart()
{
}
void SignedMessagePart::setIsSigned(bool isSigned)
{
mMetaData.isSigned = isSigned;
}
bool SignedMessagePart::isSigned() const
{
return mMetaData.isSigned;
}
static QString prettifyDN(const char *uid)
{
// We used to use QGpgME::DN::prettyDN here. But I'm not sure what we actually need it for.
return QString::fromUtf8(uid);
}
static void sigStatusToMetaData(PartMetaData &mMetaData, const Signature &signature)
{
mMetaData.isGoodSignature = !signature.status;
if (!mMetaData.isGoodSignature) {
qWarning() << "The signature is bad." << signature.status;
}
// save extended signature status flags
mMetaData.keyMissing = signature.result == Crypto::Signature::KeyNotFound;
mMetaData.keyExpired = signature.result == Crypto::Signature::Expired;
Key key;
if (mMetaData.isGoodSignature) {
// Search for the key by its fingerprint so that we can check for trust etc.
const auto keys = findKeys({QString::fromUtf8(signature.fingerprint)});
if (keys.size() > 1) {
// Should not happen
qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint;
}
if (keys.empty()) {
// Should not happen at this point
qCWarning(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint;
} else {
key = keys[0];
}
}
mMetaData.keyId = key.keyId;
if (mMetaData.keyId.isEmpty()) {
mMetaData.keyId = signature.fingerprint;
}
mMetaData.keyIsTrusted = signature.isTrusted;
if (!key.userIds.empty()) {
mMetaData.signer = prettifyDN(key.userIds[0].id.data());
}
for (const auto &userId : key.userIds) {
QString email = QString::fromUtf8(userId.email);
// Work around cryptplug bug where email addresses are specified as angle-addr ( <person@example.org> ),
// not addr-spec ( person@example.org ):
if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) {
email = email.mid(1, email.length() - 2);
}
if (!email.isEmpty()) {
mMetaData.signerMailAddresses.append(email);
}
}
mMetaData.creationTime = signature.creationTime;
if (mMetaData.signer.isEmpty()) {
if (!key.userIds.empty()) {
mMetaData.signer = prettifyDN(key.userIds[0].name.data());
}
if (!mMetaData.signerMailAddresses.empty()) {
if (mMetaData.signer.isEmpty()) {
mMetaData.signer = mMetaData.signerMailAddresses.front();
} else {
mMetaData.signer += QLatin1String(" <") + mMetaData.signerMailAddresses.front() + QLatin1Char('>');
}
}
}
}
void SignedMessagePart::startVerification()
{
if (!mSignedData) {
return;
}
mMetaData.isSigned = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
const auto codec = mOtp->codecFor(mSignedData);
// If we have a mNode, this is a detached signature
if (mNode) {
const auto signature = mNode->decodedContent();
// This is necessary in case the original data contained CRLF's. Otherwise the signature will not match the data (since KMIME normalizes to LF)
const QByteArray signedData = KMime::LFtoCRLF(mSignedData->encodedContent());
setVerificationResult(Crypto::verifyDetachedSignature(mProtocol, signature, signedData), signedData);
setText(codec->toUnicode(KMime::CRLFtoLF(signedData)));
} else {
QByteArray outdata;
setVerificationResult(Crypto::verifyOpaqueSignature(mProtocol, mSignedData->decodedContent(), outdata), outdata);
setText(codec->toUnicode(KMime::CRLFtoLF(outdata)));
}
if (!mMetaData.isSigned) {
mMetaData.creationTime = QDateTime();
}
}
void SignedMessagePart::setVerificationResult(const VerificationResult &result, const QByteArray &signedData)
{
const auto signatures = result.signatures;
// FIXME
// mMetaData.auditLogError = result.error;
if (!signatures.empty()) {
mMetaData.isSigned = true;
sigStatusToMetaData(mMetaData, signatures.front());
if (!signedData.isEmpty() && mParseAfterDecryption) {
parseInternal(signedData);
}
}
}
QString SignedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString SignedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
//-----CryptMessageBlock---------------------
EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp,
const QString &text,
const CryptoProtocol cryptoProto,
KMime::Content *node,
KMime::Content *encryptedNode,
bool parseAfterDecryption)
: MessagePart(otp, text, node)
, mParseAfterDecryption(parseAfterDecryption)
, mProtocol(cryptoProto)
, mEncryptedNode(encryptedNode)
{
mMetaData.isSigned = false;
mMetaData.isGoodSignature = false;
mMetaData.isEncrypted = false;
mMetaData.isDecryptable = false;
mMetaData.status = i18n("Wrong Crypto Plug-In.");
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
mMetaData.isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return mMetaData.isEncrypted;
}
bool EncryptedMessagePart::isDecryptable() const
{
return mMetaData.isDecryptable;
}
bool EncryptedMessagePart::decrypt(KMime::Content &data)
{
mError = NoError;
mMetaData.errorText.clear();
// FIXME
// mMetaData.auditLogError = GpgME::Error();
mMetaData.auditLog.clear();
const QByteArray ciphertext = data.decodedContent();
QByteArray plainText;
DecryptionResult decryptResult;
VerificationResult verifyResult;
std::tie(decryptResult, verifyResult) = Crypto::decryptAndVerify(mProtocol, ciphertext, plainText);
mMetaData.isSigned = verifyResult.signatures.size() > 0;
// Normalize CRLF's
plainText = KMime::CRLFtoLF(plainText);
const auto codec = mOtp->codecFor(&data);
const auto decoded = codec->toUnicode(plainText);
if (verifyResult.signatures.size() > 0) {
// We simply attach a signed message part to indicate that this content is also signed
// We're forwarding mNode to not loose the encoding information
auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, mProtocol, mNode, nullptr));
subPart->setText(decoded);
subPart->setVerificationResult(verifyResult, plainText);
appendSubPart(subPart);
}
if (decryptResult.error && mMetaData.isSigned) {
// Only a signed part
mMetaData.isEncrypted = false;
mDecryptedData = plainText;
return true;
}
if (mMetaData.isEncrypted) {
mMetaData.keyId = [&] {
for (const auto &recipient : std::as_const(decryptResult.recipients)) {
if (recipient.secretKeyAvailable) {
return recipient.keyId;
}
}
return QByteArray{};
}();
}
if (!decryptResult.error) {
mDecryptedData = plainText;
setText(decoded);
} else {
mMetaData.isEncrypted = decryptResult.result != Crypto::DecryptionResult::NotEncrypted;
qWarning() << "Failed to decrypt : " << decryptResult.error;
if (decryptResult.result == Crypto::DecryptionResult::NoSecretKeyError) {
mError = NoKeyError;
mMetaData.errorText = i18n("Could not decrypt the data: no key found for recipients.");
} else if (decryptResult.result == Crypto::DecryptionResult::PassphraseError) {
mError = PassphraseError;
} else {
mError = UnknownError;
mMetaData.errorText = i18n("Could not decrypt the data.");
}
setText(QString::fromUtf8(mDecryptedData.constData()));
return false;
}
+ qWarning() << "decrypt" << text();
+
return true;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
mMetaData.isEncrypted = true;
mMetaData.isDecryptable = decrypt(*data);
if (mParseAfterDecryption && !mMetaData.isSigned) {
parseInternal(mDecryptedData);
}
}
void EncryptedMessagePart::startDecryption()
{
if (mEncryptedNode) {
startDecryption(mEncryptedNode);
} else {
startDecryption(mNode);
}
}
QString EncryptedMessagePart::plaintextContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::htmlContent() const
{
if (!mNode) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::text() const
{
if (hasSubParts()) {
auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
if (_mp) {
return _mp->text();
} else {
return MessagePart::text();
}
} else {
return MessagePart::text();
}
}
EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
: MessagePart(otp, QString(), node)
, mMessage(message)
{
mMetaData.isEncrypted = false;
mMetaData.isSigned = false;
mMetaData.isEncapsulatedRfc822Message = true;
if (!mMessage) {
qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
return;
}
parseInternal(message.data());
}
QString EncapsulatedRfc822MessagePart::text() const
{
return renderInternalText();
}
QString EncapsulatedRfc822MessagePart::from() const
{
if (auto from = mMessage->from(false)) {
return from->asUnicodeString();
}
return {};
}
QDateTime EncapsulatedRfc822MessagePart::date() const
{
if (auto date = mMessage->date(false)) {
return date->dateTime();
}
return {};
}
HeadersPart::HeadersPart(ObjectTreeParser *otp, KMime::Content *node)
: MessagePart(otp, QString(), node)
{
}
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
new file mode 100644
index 0000000..1c8a3e0
--- /dev/null
+++ b/src/plugins/CMakeLists.txt
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
+# SPDX-License-Identifier: BSD-3-Clause
+
+ecm_add_qml_module(mimetreeparser_plugin URI "org.kde.pim.mimetreeparser" VERSION 1.0)
+
+target_sources(mimetreeparser_plugin PRIVATE
+ mimetreeparserplugin.cpp
+ mimetreeparserplugin.h
+)
+
+target_link_libraries(mimetreeparser_plugin PRIVATE
+ Qt::Quick
+ KPim${KF_MAJOR_VERSION}::MimeTreeParser
+)
+
+ecm_target_qml_sources(mimetreeparser_plugin
+ SOURCES
+ qml/MailViewer.qml
+)
+
+ecm_target_qml_sources(mimetreeparser_plugin PRIVATE
+ PATH private
+ SOURCES
+ qml/private/AttachmentDelegate.qml
+ qml/private/Banner.qml
+ qml/private/ErrorPart.qml
+ qml/private/HtmlPart.qml
+ qml/private/ICalPart.qml
+ qml/private/MailPart.qml
+ qml/private/MailPartModel.qml
+ qml/private/MailPartView.qml
+ qml/private/TextPart.qml
+)
+
+ecm_finalize_qml_module(mimetreeparser_plugin
+ DESTINATION ${KDE_INSTALL_QMLDIR}
+ BUILD_SHARED_LIBS OFF
+)
diff --git a/src/plugins/mimetreeparserplugin.cpp b/src/plugins/mimetreeparserplugin.cpp
new file mode 100644
index 0000000..6e8bfe3
--- /dev/null
+++ b/src/plugins/mimetreeparserplugin.cpp
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#include "mimetreeparserplugin.h"
+
+#include <MimeTreeParser/MessageParser>
+#include <QQmlEngine>
+
+void MimeTreeParserPlugin::registerTypes(const char *uri)
+{
+ Q_ASSERT(uri == QByteArray("org.kde.pim.mimetreeparser"));
+
+ qmlRegisterModule(uri, 1, 0);
+ qmlRegisterType<MessageParser>(uri, 1, 0, "MessageParser");
+}
+
+void MimeTreeParserPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
+{
+ Q_UNUSED(uri);
+}
diff --git a/src/plugins/mimetreeparserplugin.h b/src/plugins/mimetreeparserplugin.h
new file mode 100644
index 0000000..b652ba4
--- /dev/null
+++ b/src/plugins/mimetreeparserplugin.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
+// SPDX-License-Identifier: LGPL-2.0-or-later
+
+#pragma once
+
+#include <QQmlExtensionPlugin>
+
+class MimeTreeParserPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
+
+public:
+ void registerTypes(const char *uri) override;
+ void initializeEngine(QQmlEngine *engine, const char *uri) override;
+};
diff --git a/examples/qml/content/MailViewer.qml b/src/plugins/qml/MailViewer.qml
similarity index 98%
rename from examples/qml/content/MailViewer.qml
rename to src/plugins/qml/MailViewer.qml
index 9e2c4bb..da2277f 100644
--- a/examples/qml/content/MailViewer.qml
+++ b/src/plugins/qml/MailViewer.qml
@@ -1,186 +1,187 @@
// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2
import QtGraphicalEffects 1.15
-import org.kde.mimetreeparser 1.0
+import org.kde.pim.mimetreeparser 1.0
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kitemmodels 1.0 as KItemModels
+import './private'
Kirigami.Page {
id: root
property alias message: mailPartView.message
readonly property string subject: mailPartView.subject
readonly property string from: mailPartView.from
readonly property string sender: mailPartView.sender
readonly property string to: mailPartView.to
readonly property date dateTime: mailPartView.dateTime
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
padding: Kirigami.Units.largeSpacing * 2
title: i18n("Message viewer")
header: QQC2.ToolBar {
id: mailHeader
padding: root.padding
visible: root.from.length > 0 || root.to.length > 0 || root.subject.length > 0
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Item {
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.alternateBackgroundColor
}
Kirigami.Separator {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
}
Kirigami.Separator {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
}
}
GridLayout {
width: mailHeader.width - mailHeader.leftPadding - mailHeader.rightPadding
rowSpacing: Kirigami.Units.smallSpacing
columnSpacing: Kirigami.Units.smallSpacing
columns: 3
QQC2.Label {
text: i18n('Subject:')
font.bold: true
visible: root.subject.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.subject
visible: text.length > 0
elide: Text.ElideRight
Layout.fillWidth: true
}
QQC2.Label {
text: root.dateTime.toLocaleString(Qt.locale(), Locale.ShortFormat)
visible: text.length > 0
horizontalAlignment: Text.AlignRight
}
QQC2.Label {
text: i18n('From:')
font.bold: true
visible: root.from.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.from
visible: text.length > 0
elide: Text.ElideRight
Layout.fillWidth: true
Layout.columnSpan: 2
}
QQC2.Label {
text: i18n('Sender:')
font.bold: true
visible: root.sender.length > 0 && root.sender !== root.from
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
visible: root.sender.length > 0 && root.sender !== root.from
text: root.sender
elide: Text.ElideRight
Layout.fillWidth: true
Layout.columnSpan: 2
}
QQC2.Label {
text: i18n('To:')
font.bold: true
visible: root.to.length > 0
Layout.rightMargin: Kirigami.Units.largeSpacing
}
QQC2.Label {
text: root.to
elide: Text.ElideRight
visible: root.to.length > 0
Layout.fillWidth: true
Layout.columnSpan: 2
}
}
}
MailPartView {
id: mailPartView
anchors.fill: parent
}
footer: QQC2.ToolBar {
padding: root.padding
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Item {
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
top: undefined
bottom: parent.bottom
}
}
}
Flow {
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
Repeater {
model: mailPartView.attachmentModel
delegate: AttachmentDelegate {
name: model.name
type: model.type
icon.name: model.iconName
clip: true
actionIcon: 'download'
actionTooltip: i18n("Save attachment")
onExecute: mailPartView.attachmentModel.saveAttachmentToDisk(mailPartView.attachmentModel.index(index, 0))
onClicked: mailPartView.attachmentModel.openAttachment(mailPartView.attachmentModel.index(index, 0))
onPublicKeyImport: mailPartView.attachmentModel.importPublicKey(mailPartView.attachmentModel.index(index, 0))
}
}
}
}
}
diff --git a/examples/qml/content/AttachmentDelegate.qml b/src/plugins/qml/private/AttachmentDelegate.qml
similarity index 100%
rename from examples/qml/content/AttachmentDelegate.qml
rename to src/plugins/qml/private/AttachmentDelegate.qml
diff --git a/examples/qml/content/Banner.qml b/src/plugins/qml/private/Banner.qml
similarity index 100%
rename from examples/qml/content/Banner.qml
rename to src/plugins/qml/private/Banner.qml
diff --git a/examples/qml/content/ErrorPart.qml b/src/plugins/qml/private/ErrorPart.qml
similarity index 100%
rename from examples/qml/content/ErrorPart.qml
rename to src/plugins/qml/private/ErrorPart.qml
diff --git a/examples/qml/content/HtmlPart.qml b/src/plugins/qml/private/HtmlPart.qml
similarity index 100%
rename from examples/qml/content/HtmlPart.qml
rename to src/plugins/qml/private/HtmlPart.qml
diff --git a/examples/qml/content/ICalPart.qml b/src/plugins/qml/private/ICalPart.qml
similarity index 100%
rename from examples/qml/content/ICalPart.qml
rename to src/plugins/qml/private/ICalPart.qml
diff --git a/examples/qml/content/MailPart.qml b/src/plugins/qml/private/MailPart.qml
similarity index 100%
rename from examples/qml/content/MailPart.qml
rename to src/plugins/qml/private/MailPart.qml
diff --git a/examples/qml/content/MailPartModel.qml b/src/plugins/qml/private/MailPartModel.qml
similarity index 100%
rename from examples/qml/content/MailPartModel.qml
rename to src/plugins/qml/private/MailPartModel.qml
diff --git a/examples/qml/content/MailPartView.qml b/src/plugins/qml/private/MailPartView.qml
similarity index 100%
rename from examples/qml/content/MailPartView.qml
rename to src/plugins/qml/private/MailPartView.qml
diff --git a/examples/qml/content/TextPart.qml b/src/plugins/qml/private/TextPart.qml
similarity index 100%
rename from examples/qml/content/TextPart.qml
rename to src/plugins/qml/private/TextPart.qml

File Metadata

Mime Type
text/x-diff
Expires
Mon, May 12, 6:13 PM (13 h, 2 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
51/af/a49210b450182acd386aaf81897e

Event Timeline