Commit 33eee8ca authored by Carl Schwan's avatar Carl Schwan 🚴 Committed by Claudio Cambra
Browse files

Display if the email contains an invitation



Signed-off-by: Carl Schwan's avatarCarl Schwan <carl@carlschwan.eu>
parent cdce3021
......@@ -69,6 +69,7 @@ endif()
ki18n_install(po)
ecm_set_disabled_deprecation_versions(QT 5.15.2 KF 5.97.0)
add_definitions(-DQT_STRICT_ITERATORS)
add_subdirectory(src)
if (BUILD_TESTING)
......
......@@ -85,26 +85,26 @@ AttachmentModel::~AttachmentModel()
QHash<int, QByteArray> AttachmentModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[NameRole] = "name";
roles[SizeRole] = "size";
roles[IconRole] = "iconName";
roles[IsEncryptedRole] = "encrypted";
roles[IsSignedRole] = "signed";
return roles;
return {
{TypeRole, QByteArrayLiteral("type")},
{NameRole, QByteArrayLiteral("name")},
{SizeRole, QByteArrayLiteral("size")},
{IconRole, QByteArrayLiteral("iconName")},
{IsEncryptedRole, QByteArrayLiteral("encrypted")},
{IsSignedRole, QByteArrayLiteral("signed")},
};
}
QModelIndex AttachmentModel::index(int row, int column, const QModelIndex &) const
{
if (row < 0 || column != 0) {
return QModelIndex();
return {};
}
if (row < d->mAttachments.size()) {
return createIndex(row, column, d->mAttachments.at(row).data());
}
return QModelIndex();
return {};
}
QVariant AttachmentModel::data(const QModelIndex &index, int role) const
......@@ -253,7 +253,7 @@ bool AttachmentModel::importPublicKey(const QModelIndex &index)
QModelIndex AttachmentModel::parent(const QModelIndex &) const
{
return QModelIndex();
return {};
}
int AttachmentModel::rowCount(const QModelIndex &parent) const
......
......@@ -106,6 +106,7 @@ public:
void checkPart(const MimeTreeParser::MessagePart::Ptr part)
{
mMimeTypeCache[part.data()] = part->mimeType();
// Extract the content of the part and
mContents.insert(part.data(), extractContent(part.data()));
}
......@@ -169,28 +170,32 @@ public:
containsHtmlAndPlain = false;
isTrimmed = false;
mParts = mParser->collectContentParts();
for (auto p : mParts) {
const auto parts = mParser->collectContentParts();
for (auto p : parts) {
checkPart(p);
if (auto e = p.dynamicCast<MimeTreeParser::EncapsulatedRfc822MessagePart>()) {
findEncapsulated(e);
}
}
for (auto p : parts) {
if (mMimeTypeCache[p.data()] == "text/calendar") {
mParts.prepend(p);
} else {
mParts.append(p);
}
}
}
PartModel *q;
QVector<MimeTreeParser::MessagePartPtr> mParts;
QHash<MimeTreeParser::MessagePart *, QByteArray> mMimeTypeCache;
QHash<MimeTreeParser::MessagePart *, QVector<MimeTreeParser::MessagePartPtr>> mEncapsulatedParts;
QHash<MimeTreeParser::MessagePart *, MimeTreeParser::MessagePart *> mParents;
QMap<MimeTreeParser::MessagePart *, QVariant> mContents;
std::shared_ptr<MimeTreeParser::ObjectTreeParser> mParser;
bool showHtml{false};
bool containsHtmlAndPlain{false};
#ifdef KUBE_EXPERIMENTAL
bool trimMail{true};
#else
bool trimMail{false};
#endif
bool isTrimmed{false};
};
......@@ -359,6 +364,16 @@ QVariant PartModel::data(const QModelIndex &index, int role) const
return QStringLiteral("ical");
}
}
if (auto attachmentPart = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(messagePart)) {
auto node = attachmentPart->node();
if (!node) {
qWarning() << "no content for attachment";
return {};
}
if (d->mMimeTypeCache[attachmentPart] == "text/calendar") {
return QStringLiteral("ical");
}
}
if (!d->showHtml && d->containsHtmlAndPlain) {
return QStringLiteral("plain");
}
......
/*
objecttreeparser.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
Copyright (c) 2015 Sandro Knauß <sknauss@kde.org>
Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsystems.com>
KMail is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.
KMail is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
*/
// MessageViewer includes
// This file is part of KMail, the KDE mail client.
// SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
// SPDX-FileCopyrightText: 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
// SPDX-FileCopyrightText: 2009 Andras Mantia <andras@kdab.net>
// SPDX-FileCopyrightText: 2015 Sandro Knauß <sknauss@kde.org>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsystems.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "objecttreeparser.h"
......@@ -224,8 +195,8 @@ QVector<MessagePart::Ptr> ObjectTreeParser::collectContentParts(MessagePart::Ptr
return true;
},
[start](const MessagePartPtr &part) {
if (dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(part.data())) {
return false;
if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart *>(part.data())) {
return attachment->mimeType() == "text/calendar";
} else if (const auto text = dynamic_cast<MimeTreeParser::TextMessagePart *>(part.data())) {
auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart *>(text->parentPart());
if (enc && enc->error()) {
......
/*
Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
// SPDX-FileCopyrightText: 2016 Sandro Knauß <sknauss@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "utils.h"
......
/*
Copyright (C) 2019 Christian Mollekopf, <mollekopf@kolabsys.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import QtQuick 2.7
import QtQuick.Controls 2
import QtQuick.Layouts 1
import org.kube.framework 1.0 as Kube
Item {
// SPDX-FileCopyrightText 2019 Christian Mollekopf, <mollekopf@kolabsys.com>
// SPDX-FileCopyrightText 2022 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQml.Models 2.2
import org.kde.kalendar 1.0
import org.kde.kalendar.mail 1.0
import org.kde.kirigami 2.19 as Kirigami
ColumnLayout {
id: root
property string content
......@@ -31,155 +19,13 @@ Item {
property string searchString
property int contentHeight: childrenRect.height
Kube.InvitationController {
id: controller
Component.onCompleted: loadICal(content)
}
states: [
State {
name: "exceptioncancelled"
extend: "cancelled"
when: controller.method == Kube.InvitationController.Cancel && !isNaN(controller.recurrenceId)
PropertyChanges {target: label; visible: true; text: qsTr("This is an exception for the event originally occurring at: \"%1\"").arg(controller.recurrenceId.toLocaleString(Qt.locale(), "dd. MMMM hh:mm"))}
},
State {
name: "cancelledaccepted"
extend: "cancelled"
when: controller.method == Kube.InvitationController.Cancel && controller.state == Kube.InvitationController.Accepted
PropertyChanges {target: buttons; visible: false}
},
State {
name: "cancelled"
when: controller.method == Kube.InvitationController.Cancel
PropertyChanges {target: heading; text: qsTr("\"%1\" has been cancelled.").arg(controller.summary)}
},
State {
name: "replyAccepted"
when: controller.method == Kube.InvitationController.Reply && controller.state == Kube.InvitationController.Accepted
PropertyChanges {target: heading; text: qsTr("%1 has accepted: \"%2\"").arg(controller.name).arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
},
State {
name: "replyDeclined"
when: controller.method == Kube.InvitationController.Reply && controller.state == Kube.InvitationController.Declined
PropertyChanges {target: heading; text: qsTr("%1 has declined: \"%2\"").arg(controller.name).arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
},
State {
name: "replyUnknown"
when: controller.method == Kube.InvitationController.Reply && controller.state == Kube.InvitationController.Unknown
PropertyChanges {target: heading; text: qsTr("%1 doesn't yet know about: \"%2\"").arg(controller.name).arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
},
State {
name: "updatedexception"
extend: "updated"
when: controller.method == Kube.InvitationController.Request && controller.eventState == Kube.InvitationController.Update && !isNaN(controller.recurrenceId)
PropertyChanges {target: label; visible: true; text: qsTr("This is an exception for the event originally occurring at: \"%1\"").arg(controller.recurrenceId.toLocaleString(Qt.locale(), "dd. MMMM hh:mm"))}
},
State {
name: "updated"
when: controller.method == Kube.InvitationController.Request && controller.eventState == Kube.InvitationController.Update
PropertyChanges {target: heading; text: qsTr("The invitation has been updated: \"%1\"").arg(controller.summary)}
},
State {
name: "accepted"
when: controller.method == Kube.InvitationController.Request && controller.state == Kube.InvitationController.Accepted
PropertyChanges {target: heading; text: qsTr("You have accepted: \"%1\"").arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
},
State {
name: "declined"
when: controller.method == Kube.InvitationController.Request && controller.state == Kube.InvitationController.Declined
PropertyChanges {target: heading; text: qsTr("You have declined: \"%1\"").arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
},
State {
name: "invited"
when: controller.method == Kube.InvitationController.Request && controller.state == Kube.InvitationController.Unknown
PropertyChanges {target: heading; text: qsTr("You've been invited to: \"%1\"").arg(controller.summary)}
},
State {
name: "notinvited"
when: controller.method == Kube.InvitationController.Request && controller.state == Kube.InvitationController.NoMatch
PropertyChanges {target: heading; text: qsTr("You are not listed as attendee to: \"%1\"").arg(controller.summary)}
PropertyChanges {target: buttons; visible: false}
}
]
ColumnLayout {
anchors {
left: parent.left
right: parent.right
}
spacing: Kube.Units.smallSpacing
Kube.Heading {
id: heading
elide: Text.ElideRight
Layout.fillWidth: true
}
Kube.Label {
id: label
visible: false
elide: Text.ElideRight
Layout.fillWidth: true
}
Kube.SelectableLabel {
Layout.fillWidth: true
visible: controller.allDay
text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM") + (/*DateUtils.sameDay(controller.start, controller.end)*/ true ? "" : " - " + controller.end.toLocaleString(Qt.locale(), "dd. MMMM"))
elide: Text.ElideRight
}
Kube.SelectableLabel {
Layout.fillWidth: true
visible: !controller.allDay
text: controller.start.toLocaleString(Qt.locale(), "dd. MMMM hh:mm") + " - " + (/*DateUtils.sameDay(controller.start, controller.end)*/ true ? controller.end.toLocaleString(Qt.locale(), "hh:mm") : controller.end.toLocaleString(Qt.locale(), "dd. MMMM hh:mm"))
elide: Text.ElideRight
}
RowLayout {
id: buttons
spacing: Kube.Units.smallSpacing
Layout.fillWidth: true
Kube.Button {
text: qsTr("Decline")
onClicked: {
controller.declineAction.execute()
}
}
Kube.PositiveButton {
text: qsTr("Accept")
onClicked: {
controller.acceptAction.execute()
}
}
Kube.Label {
text: qsTr("in")
}
Kube.EntityComboBox {
id: calendarSelector
Layout.fillWidth: true
accountId: Kube.Context.currentAccountId
type: "calendar"
filter: {"contentTypes": "event", "enabled": true}
onSelected: {
if (entity) {
controller.calendar = entity
}
}
}
}
spacing: Kirigami.Units.smallSpacing
Kirigami.InlineMessage {
id: signedButton
Layout.fillWidth: true
Layout.maximumWidth: parent.width
visible: true
text: i18n("This mail contains an invitation")
}
}
......@@ -4,6 +4,7 @@
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQml.Models 2.2
import org.kde.kalendar 1.0
import org.kde.kalendar.mail 1.0
......@@ -18,147 +19,141 @@ DelegateModel {
delegate: Item {
id: partColumn
width: parent ? parent.width : 0
width: ListView.view.width
height: childrenRect.height
/*function getColor(securityLevel) {
function getType(securityLevel) {
if (securityLevel == "good") {
return Kube.Colors.positiveColor
return Kirigami.MessageType.Positive
}
if (securityLevel == "bad") {
return Kube.Colors.negativeColor
return Kirigami.MessageType.Error
}
if (securityLevel == "notsogood") {
return Kube.Colors.warningColor
return Kirigami.MessageType.Warning
}
return Kube.Colors.lightgrey
return Kirigami.MessageType.Information
}
function getDetails(signatureDetails)
{
var details = "";
if (signatureDetails.noSignaturesFound) {
details += qsTr("This message has been signed but we failed to validate the signature.") + "\n"
} else if (signatureDetails.keyMissing) {
details += qsTr("This message has been signed using the key %1.").arg(signatureDetails.keyId) + "\n";
details += qsTr("The key details are not available.") + "\n";
function getColor(securityLevel) {
if (securityLevel == "good") {
return Kirigami.Theme.positiveTextColor
}
if (securityLevel == "bad") {
return Kirigami.Theme.negativeTextColor
}
if (securityLevel == "notsogood") {
return Kirigami.Theme.neutralTextColor
}
return Kirigami.Theme.disabledColor
}
function getDetails(signatureDetails) {
let details = "";
if (signatureDetails.keyMissing) {
details += i18n("This message has been signed using the key %1.", signatureDetails.keyId) + "\n";
details += i18n("The key details are not available.") + "\n";
} else {
details += qsTr("This message has been signed using the key %1 by %2.").arg(signatureDetails.keyId).arg(signatureDetails.signer) + "\n";
details += i18n("This message has been signed using the key %1 by %2.", signatureDetails.keyId, signatureDetails.signer) + "\n";
if (signatureDetails.keyRevoked) {
details += qsTr("The key was revoked.") + "\n"
details += i18n("The key was revoked.") + "\n"
}
if (signatureDetails.keyExpired) {
details += qsTr("The key has expired.") + "\n"
details += i18n("The key has expired.") + "\n"
}
if (signatureDetails.keyIsTrusted) {
details += qsTr("You are trusting this key.") + "\n"
details += i18n("You are trusting this key.") + "\n"
}
if (!signatureDetails.signatureIsGood && !signatureDetails.keyRevoked && !signatureDetails.keyExpired && !signatureDetails.keyIsTrusted) {
details += qsTr("The signature is invalid.") + "\n"
details += i18n("The signature is invalid.") + "\n"
}
}
return details
}
Column {
ColumnLayout {
id: buttons
anchors.left: parent.left
anchors.top: parent.top
anchors.rightMargin: Kirigami.Units.smallSpacing
anchors {
left: parent.left
right: parent.right
top: parent.top
rightMargin: Kirigami.Units.largeSpacing
leftMargin: Kirigami.Units.largeSpacing
topMargin: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
Kube.IconButton {
Kirigami.InlineMessage {
id: encryptedButton
width: Kube.Units.gridUnit
height: width
iconName: Kube.Icons.secure
color: getColor(model.encryptionSecurityLevel)
backgroundOpacity: 0.5
Layout.fillWidth: true
Layout.maximumWidth: parent.width
icon.name: 'mail-encrypted'
type: getType(model.encryptionSecurityLevel)
visible: model.encrypted
tooltip: model.encryptionDetails.keyId == "" ? qsTr("This message is encrypted but we don't have the key for it.") : qsTr("This message is encrypted to the key: %1").arg(model.encryptionDetails.keyId);
//FIXME make text copyable
// Kube.SelectableItem {
// visualParent: encryptedButton
// text: parent.tooltip
// }
text: model.encryptionDetails.keyId == "" ? i18n("This message is encrypted but we don't have the key for it.") : i18n("This message is encrypted to the key: %1", model.encryptionDetails.keyId);
}
QQC2.ToolButton {
Kirigami.InlineMessage {
id: signedButton
icon.name: 'mail-signed-full'
color: getColor(model.signatureSecurityLevel)
backgroundOpacity: 0.5
Layout.fillWidth: true
Layout.maximumWidth: parent.width
icon.name: 'mail-signed'
visible: model.signed
tooltip: getDetails(model.signatureDetails)
type: getType(model.signatureSecurityLevel)
text: getDetails(model.signatureDetails)
}
}*/
Rectangle {
id: border
visible: encryptedButton.hovered || signedButton.hovered
anchors.topMargin: Kirigami.Units.smallSpacing
anchors.top: buttons.bottom
anchors.bottom: partLoader.bottom
anchors.right: buttons.right
width: Kirigami.Units.smallSpacing
color: getColor(model.securityLevel)
opacity: 0.5
}
Loader {
id: partLoader
anchors {
top: parent.top
left: buttons.right
leftMargin: Kirigami.Units.smallSpacing
right: parent.right
}
height: item ? item.contentHeight : 0
width: parent.width
Binding {
target: partLoader.item
property: "searchString"
value: root.searchString
when: partLoader.status === Loader.Ready
}
Binding {
target: partLoader.item
property: "autoLoadImages"
value: root.autoLoadImages
when: partLoader.status === Loader.Ready
Loader {
id: partLoader
Layout.preferredHeight: item ? item.contentHeight : 0
Layout.maximumWidth: parent.width
width: parent.width
Binding {
target: partLoader.item
property: "searchString"
value: root.searchString
when: partLoader.status === Loader.Ready
}
Binding {
target: partLoader.item
property: "autoLoadImages"
value: root.autoLoadImages
when: partLoader.status === Loader.Ready
}
}
}
Component.onCompleted: {