No OneTemporary

File Metadata

Created
Thu, May 23, 9:37 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/.gitignore b/.gitignore
index 7032c325..0d58ded9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,21 +1,21 @@
# Ignore the following files
*~
*.[oa]
*.diff
*.kate-swp
*.kdev4
.kdev_include_paths
*.kdevelop.pcs
*.moc
*.moc.cpp
*.orig
*.user
.*.swp
.swp.*
Doxyfile
Makefile
avail
random_seed
-/build/
+/build*/
CMakeLists.txt.user*
*.unc-backup*
diff --git a/.krazy b/.krazy
new file mode 100644
index 00000000..c8686010
--- /dev/null
+++ b/.krazy
@@ -0,0 +1 @@
+SKIP /theme/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f29dda7c..822aa613 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,132 +1,133 @@
-cmake_minimum_required(VERSION 3.0)
-set(PIM_VERSION "5.9.51")
+cmake_minimum_required(VERSION 3.5)
+set(PIM_VERSION "5.11.42")
if (POLICY CMP0053)
cmake_policy(SET CMP0053 NEW)
endif()
project(Messagelib VERSION ${PIM_VERSION})
option(MIMETREEPARSER_ONLY_BUILD "Build only mimetreeparser" FALSE)
option(KDEPIM_ENTERPRISE_BUILD "Enable features specific to the enterprise branch, which are normally disabled. Also, it disables many components not needed for Kontact such as the Kolab client." FALSE)
+option(KDEPIM_RUN_AKONADI_TEST "Enable autotest based on Akonadi." TRUE)
+option(DKIM_CHECKER_BUILD "DKIM CHECKER (experimental)" FALSE)
-
-set(KF5_VERSION "5.50.0")
+set(KF5_MIN_VERSION "5.57.0")
set(MESSAGELIB_LIB_VERSION ${PIM_VERSION})
-set(AKONADIMIME_LIB_VERSION "5.9.40")
-
-set(QT_REQUIRED_VERSION "5.9.0")
-
-set(AKONADI_VERSION "5.9.40")
-set(GRANTLEETHEME_LIB_VERSION "5.9.40")
-set(GRAVATAR_LIB_VERSION "5.9.40")
-set(IDENTITYMANAGEMENT_LIB_VERSION "5.9.40")
-set(KCONTACTS_LIB_VERSION "5.9.40")
-set(KDEPIM_APPS_LIB_VERSION "5.9.41")
-set(KLDAP_LIB_VERSION "5.9.40")
-set(KMAILTRANSPORT_LIB_VERSION "5.9.41")
-set(KMBOX_LIB_VERSION "5.9.40")
-set(KMIME_LIB_VERSION "5.9.40")
-set(KPIMTEXTEDIT_LIB_VERSION "5.9.40")
-set(LIBKDEPIM_LIB_VERSION "5.9.40")
-set(LIBKLEO_LIB_VERSION "5.9.40")
-set(PIMCOMMON_LIB_VERSION "5.9.40")
-set(GPGME_LIB_VERSION "1.8.0")
-set(AKONADI_CONTACT_VERSION "5.9.40")
+set(AKONADIMIME_LIB_VERSION "5.11.40")
+
+set(QT_REQUIRED_VERSION "5.10.0")
+
+set(AKONADI_VERSION "5.11.40")
+set(GRANTLEETHEME_LIB_VERSION "5.11.40")
+set(GRAVATAR_LIB_VERSION "5.11.40")
+set(IDENTITYMANAGEMENT_LIB_VERSION "5.11.40")
+set(KCONTACTS_LIB_VERSION "5.11.40")
+set(KDEPIM_APPS_LIB_VERSION "5.11.40")
+set(KLDAP_LIB_VERSION "5.11.40")
+set(KMAILTRANSPORT_LIB_VERSION "5.11.40")
+set(KMBOX_LIB_VERSION "5.11.40")
+set(KMIME_LIB_VERSION "5.11.40")
+set(KPIMTEXTEDIT_LIB_VERSION "5.11.40")
+set(LIBKDEPIM_LIB_VERSION "5.11.40")
+set(LIBKLEO_LIB_VERSION "5.11.40")
+set(PIMCOMMON_LIB_VERSION "5.11.40")
+set(GPGME_LIB_VERSION "1.11.1")
+set(AKONADI_CONTACT_VERSION "5.11.40")
if (${MIMETREEPARSER_ONLY_BUILD})
set(ECM_VERSION "5.26.0")
set(KMIME_LIB_VERSION "5.1.40")
else()
- set(ECM_VERSION ${KF5_VERSION})
+ set(ECM_VERSION ${KF5_MIN_VERSION})
endif()
find_package(ECM ${ECM_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${Messagelib_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH})
set(LIBRARY_NAMELINK)
include(GenerateExportHeader)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMGeneratePriFile)
-include(CMakePackageConfigHelpers)
+
include(FeatureSummary)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
-include(ECMCoverageOption)
+
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Gui Test)
-find_package(KF5Codecs ${KF5_VERSION} CONFIG REQUIRED)
-find_package(KF5I18n ${KF5_VERSION} CONFIG REQUIRED)
+find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED)
+find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(KF5Mime ${KMIME_LIB_VERSION} CONFIG REQUIRED)
find_package(QGpgme ${GPGME_LIB_VERSION} CONFIG REQUIRED)
-
+message(STATUS "GPGME Version ${QGpgme_VERSION}")
if (NOT ${MIMETREEPARSER_ONLY_BUILD})
find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Network PrintSupport WebEngine WebEngineWidgets)
- find_package(KF5Archive ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5Completion ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5Config ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5ConfigWidgets ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5IconThemes ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5ItemViews ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5JobWidgets ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5KIO ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5Service ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5Sonnet ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5TextWidgets ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5WidgetsAddons ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5WindowSystem ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5XmlGui ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5SyntaxHighlighting ${KF5_VERSION} CONFIG REQUIRED)
- find_package(KF5DBusAddons ${KF5_VERSION} CONFIG REQUIRED)
+ find_package(KF5Archive ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5Completion ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5ItemViews ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5JobWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5KIO ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5Service ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5Sonnet ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5TextWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5WindowSystem ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5SyntaxHighlighting ${KF5_MIN_VERSION} CONFIG REQUIRED)
+ find_package(KF5DBusAddons ${KF5_MIN_VERSION} CONFIG REQUIRED)
find_package(Grantlee5 "5.1" CONFIG REQUIRED)
find_package(KF5Akonadi ${AKONADI_VERSION} CONFIG REQUIRED)
find_package(KF5AkonadiMime ${AKONADIMIME_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Contacts ${KCONTACTS_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5AkonadiContact ${AKONADI_CONTACT_VERSION} CONFIG REQUIRED)
find_package(KF5FollowupReminder ${KDEPIM_APPS_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5GrantleeTheme ${GRANTLEETHEME_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Gravatar ${GRAVATAR_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5IdentityManagement ${IDENTITYMANAGEMENT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5KaddressbookGrantlee ${KDEPIM_APPS_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Ldap ${KLDAP_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5LibkdepimAkonadi ${LIBKDEPIM_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Libkleo ${LIBKLEO_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5MailTransportAkonadi ${KMAILTRANSPORT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5Mbox ${KMBOX_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5PimCommonAkonadi ${PIMCOMMON_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5PimTextEdit ${KPIMTEXTEDIT_LIB_VERSION} CONFIG REQUIRED)
find_package(KF5SendLater ${KDEPIM_APPS_LIB_VERSION} CONFIG REQUIRED)
- find_package(KF5AkonadiSearch "5.9.40" CONFIG REQUIRED)
+ find_package(KF5AkonadiSearch "5.11.40" CONFIG REQUIRED)
set_package_properties(KF5AkonadiSearch PROPERTIES DESCRIPTION "The Akonadi Search libraries" URL "http://www.kde.org" TYPE REQUIRED PURPOSE "Provides search capabilities in KMail and Akonadi")
endif()
+set(CMAKE_CXX_STANDARD 14)
#add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000)
if(BUILD_TESTING)
add_definitions(-DBUILD_TESTING)
endif()
-add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT)
add_subdirectory(mimetreeparser)
if (NOT ${MIMETREEPARSER_ONLY_BUILD})
add_subdirectory(messageviewer)
add_subdirectory(templateparser)
add_subdirectory(messagecomposer)
add_subdirectory(messagecore)
add_subdirectory(messagelist)
add_subdirectory(webengineviewer)
endif()
install( FILES messagelib.renamecategories messagelib.categories DESTINATION ${KDE_INSTALL_CONFDIR} )
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/messagecomposer/autotests/CMakeLists.txt b/messagecomposer/autotests/CMakeLists.txt
index ea7284c1..6f7e60b7 100644
--- a/messagecomposer/autotests/CMakeLists.txt
+++ b/messagecomposer/autotests/CMakeLists.txt
@@ -1,93 +1,94 @@
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
include( ${CMAKE_SOURCE_DIR}/cmake/modules/kdepim_add_gpg_crypto_test.cmake )
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src/part)
# Convenience macro to add messagecomposer unit tests.
macro( add_messagecomposer_test _source )
ecm_add_test(${_source}
NAME_PREFIX "messagecomposer-"
LINK_LIBRARIES
KF5::Mime
KF5::MessageComposer
KF5::MessageViewer
KF5::MessageCore
KF5::TemplateParser
KF5::PimTextEdit
Qt5::Test
KF5::IdentityManagement
KF5::Contacts
KF5::AkonadiCore
KF5::XmlGui
KF5::Libkdepim
KF5::IconThemes
KF5::Completion
)
endmacro()
macro( add_messagecomposer_cryptotest _source )
set( _test cryptofunctions.cpp setupenv.cpp ${_source} )
get_filename_component( _name ${_source} NAME_WE )
add_executable( ${_name} ${_test} )
target_link_libraries(
${_name}
KF5::Mime
KF5::MessageComposer
KF5::MessageViewer
KF5::MessageCore
KF5::Libkleo
KF5::Libkdepim
KF5::PimTextEdit
KF5::AkonadiCore
Qt5::Test
QGpgme
KF5::IconThemes
KF5::Completion
KF5::MailTransport
KF5::Ldap
KF5::Contacts
)
add_gpg_crypto_test(${_name} messagecomposer-${_name})
set_tests_properties(messagecomposer-${_name} PROPERTIES RUN_SERIAL TRUE) # can't be parallelized due to gpg-agent
endmacro()
# Utility stuff.
add_messagecomposer_test( utiltest.cpp )
add_messagecomposer_test( messagefactoryngtest.cpp )
add_messagecomposer_test( plugineditorcheckbeforesendparamstest.cpp )
# Non-content jobs.
add_messagecomposer_test( skeletonmessagejobtest.cpp )
# Basic content jobs.
add_messagecomposer_test( singlepartjobtest.cpp )
add_messagecomposer_test( multipartjobtest.cpp )
# More complex content jobs.
add_messagecomposer_test( attachmentjobtest.cpp )
add_messagecomposer_test( maintextjobtest.cpp )
# Composer.
add_messagecomposer_test( composertest.cpp )
add_messagecomposer_cryptotest( cryptocomposertest.cpp )
add_messagecomposer_test( infoparttest.cpp )
add_messagecomposer_test( textparttest.cpp )
add_messagecomposer_test( globalparttest.cpp )
add_messagecomposer_test( composerviewbasetest.cpp )
add_messagecomposer_test( recipientseditortest.cpp )
# Crypto
add_messagecomposer_cryptotest( signjobtest.cpp )
add_messagecomposer_cryptotest( encryptjobtest.cpp )
add_messagecomposer_cryptotest( signencrypttest.cpp )
+if (KDEPIM_RUN_AKONADI_TEST)
+ set(KDEPIMLIBS_RUN_ISOLATED_TESTS TRUE)
+ set(KDEPIMLIBS_RUN_SQLITE_ISOLATED_TESTS TRUE)
-set(KDEPIMLIBS_RUN_ISOLATED_TESTS TRUE)
-set(KDEPIMLIBS_RUN_SQLITE_ISOLATED_TESTS TRUE)
+ add_akonadi_isolated_test_advanced( followupreminderselectdatedialogtest.cpp "../src/followupreminder/followupreminderselectdatedialog.cpp" "KF5::CalendarCore;KF5::AkonadiCore;KF5::AkonadiWidgets;KF5::MessageComposer;KF5::I18n")
-add_akonadi_isolated_test_advanced( followupreminderselectdatedialogtest.cpp "../src/followupreminder/followupreminderselectdatedialog.cpp" "KF5::CalendarCore;KF5::AkonadiCore;KF5::AkonadiWidgets;KF5::MessageComposer;KF5::I18n")
-
-add_akonadi_isolated_test_advanced( attachmentvcardfromaddressbookjobtest.cpp "../src/job/attachmentvcardfromaddressbookjob.cpp" "KF5::CalendarCore;KF5::AkonadiCore;KF5::AkonadiWidgets;KF5::MessageComposer;KF5::Contacts;KF5::AkonadiContact;KF5::I18n")
+ add_akonadi_isolated_test_advanced( attachmentvcardfromaddressbookjobtest.cpp "../src/job/attachmentvcardfromaddressbookjob.cpp" "KF5::CalendarCore;KF5::AkonadiCore;KF5::AkonadiWidgets;KF5::MessageComposer;KF5::Contacts;KF5::AkonadiContact;KF5::I18n")
+endif()
diff --git a/messagecomposer/autotests/attachmentjobtest.cpp b/messagecomposer/autotests/attachmentjobtest.cpp
index 288c113d..216f0d16 100644
--- a/messagecomposer/autotests/attachmentjobtest.cpp
+++ b/messagecomposer/autotests/attachmentjobtest.cpp
@@ -1,116 +1,116 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentjobtest.h"
#include "qtest_messagecomposer.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
#include <kmime/kmime_headers.h>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/AttachmentJob>
using namespace MessageComposer;
-#include <MessageCore/AttachmentFromUrlJob>
+//#include "messagecore/attachmentfromfolderjob.h"
#include <MessageCore/AttachmentPart>
using namespace MessageCore;
#define PATH_ATTACHMENTS QLatin1String(KDESRCDIR "/attachments/")
QTEST_MAIN(AttachmentJobTest)
void AttachmentJobTest::testAttachment()
{
const QString name = QStringLiteral("name");
const QString fileName = QStringLiteral("filename");
const QString description = QStringLiteral("long long long description...");
const QByteArray mimeType("x-some/x-type");
const QByteArray data("la la la");
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
part->setName(name);
part->setFileName(fileName);
part->setDescription(description);
part->setMimeType(mimeType);
part->setData(data);
Composer *composer = new Composer;
composer->globalPart()->setFallbackCharsetEnabled(true);
AttachmentJob *ajob = new AttachmentJob(part, composer);
QVERIFY(ajob->exec());
Content *result = ajob->content();
delete ajob;
ajob = nullptr;
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->contentType(false)->name(), name);
QCOMPARE(result->contentDisposition(false)->filename(), fileName);
QCOMPARE(result->contentDescription(false)->asUnicodeString(), description);
QCOMPARE(result->contentType(false)->mimeType(), mimeType);
QCOMPARE(result->body(), data);
QVERIFY(result->contentDisposition(false)->disposition() == Headers::CDattachment);
}
#if 0
// Disabled: using UTF-8 instead of trying to detect charset.
void AttachmentJobTest::testTextCharsetAutodetect_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QByteArray>("charset");
// PATH_ATTACHMENTS is defined by CMake.
QTest::newRow("ascii") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("ascii.txt"))
<< QByteArray("us-ascii");
QTest::newRow("iso8859-2") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("iso8859-2.txt"))
<< QByteArray("iso-8859-2");
// TODO not sure how to test utf-16.
}
void AttachmentJobTest::testTextCharsetAutodetect()
{
QFETCH(QUrl, url);
QFETCH(QByteArray, charset);
AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url);
VERIFYEXEC(ljob);
AttachmentPart::Ptr part = ljob->attachmentPart();
delete ljob;
ljob = 0;
Composer *composer = new Composer;
composer->globalPart()->setFallbackCharsetEnabled(true);
AttachmentJob *ajob = new AttachmentJob(part, composer);
VERIFYEXEC(ajob);
Content *result = ajob->content();
delete ajob;
ajob = 0;
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->contentType(false)->charset(), charset);
}
#endif
diff --git a/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.cpp b/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.cpp
index 3f46e121..1d9ddc38 100644
--- a/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.cpp
+++ b/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.cpp
@@ -1,89 +1,89 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#include "attachmentvcardfromaddressbookjobtest.h"
#include "MessageComposer/AttachmentVcardFromAddressBookJob"
-#include <qtest.h>
+#include <QTest>
#include <KContacts/Addressee>
AttachmentVcardFromAddressBookJobTest::AttachmentVcardFromAddressBookJobTest(QObject *parent)
: QObject(parent)
{
}
AttachmentVcardFromAddressBookJobTest::~AttachmentVcardFromAddressBookJobTest()
{
}
void AttachmentVcardFromAddressBookJobTest::testAttachmentVCardWithInvalidItem()
{
Akonadi::Item item;
MessageComposer::AttachmentVcardFromAddressBookJob *job = new MessageComposer::AttachmentVcardFromAddressBookJob(item);
QVERIFY(!job->exec());
delete job;
job = nullptr;
}
void AttachmentVcardFromAddressBookJobTest::testAttachmentVCardWithValidItem()
{
Akonadi::Item item(42);
item.setMimeType(KContacts::Addressee::mimeType());
KContacts::Addressee address;
const QString name = QStringLiteral("foo1");
address.setName(name);
item.setPayload<KContacts::Addressee>(address);
MessageComposer::AttachmentVcardFromAddressBookJob *job = new MessageComposer::AttachmentVcardFromAddressBookJob(item);
QVERIFY(job->exec());
MessageCore::AttachmentPart::Ptr part = job->attachmentPart();
delete job;
job = nullptr;
QVERIFY(!part->data().isEmpty());
QCOMPARE(part->mimeType(), QByteArray("text/x-vcard"));
const QString newName = name + QLatin1String(".vcf");
QCOMPARE(part->name(), newName);
QVERIFY(part->description().isEmpty());
QVERIFY(!part->isInline());
QVERIFY(!part->fileName().isEmpty());
}
void AttachmentVcardFromAddressBookJobTest::testAttachmentVCardWithInvalidVCard()
{
Akonadi::Item item(42);
MessageComposer::AttachmentVcardFromAddressBookJob *job = new MessageComposer::AttachmentVcardFromAddressBookJob(item);
QVERIFY(!job->exec());
delete job;
job = nullptr;
}
void AttachmentVcardFromAddressBookJobTest::testAttachmentVCardWithEmptyVCard()
{
Akonadi::Item item(42);
item.setMimeType(KContacts::Addressee::mimeType());
KContacts::Addressee address;
item.setPayload<KContacts::Addressee>(address);
MessageComposer::AttachmentVcardFromAddressBookJob *job = new MessageComposer::AttachmentVcardFromAddressBookJob(item);
QVERIFY(!job->exec());
delete job;
job = nullptr;
}
QTEST_MAIN(AttachmentVcardFromAddressBookJobTest)
diff --git a/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.h b/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.h
index 7a48aabe..1cf19007 100644
--- a/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.h
+++ b/messagecomposer/autotests/attachmentvcardfromaddressbookjobtest.h
@@ -1,39 +1,39 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#ifndef ATTACHMENTVCARDFROMADDRESSBOOKJOBTEST_H
#define ATTACHMENTVCARDFROMADDRESSBOOKJOBTEST_H
#include <QObject>
class AttachmentVcardFromAddressBookJobTest : public QObject
{
Q_OBJECT
public:
explicit AttachmentVcardFromAddressBookJobTest(QObject *parent = nullptr);
~AttachmentVcardFromAddressBookJobTest();
private Q_SLOTS:
void testAttachmentVCardWithInvalidItem();
void testAttachmentVCardWithValidItem();
void testAttachmentVCardWithInvalidVCard();
void testAttachmentVCardWithEmptyVCard();
};
#endif // ATTACHMENTVCARDFROMADDRESSBOOKJOBTEST_H
diff --git a/messagecomposer/autotests/composertest.cpp b/messagecomposer/autotests/composertest.cpp
index 94eafe71..772aa183 100644
--- a/messagecomposer/autotests/composertest.cpp
+++ b/messagecomposer/autotests/composertest.cpp
@@ -1,159 +1,159 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "composertest.h"
#include "qtest_messagecomposer.h"
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_headers.h>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/InfoPart>
#include <MessageComposer/TextPart>
using namespace MessageComposer;
#include <MessageCore/AttachmentPart>
using MessageCore::AttachmentPart;
QTEST_MAIN(ComposerTest)
void ComposerTest::testAttachments()
{
Composer *composer = new Composer;
fillComposerData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
attachment->setData("abc");
attachment->setMimeType("x-some/x-type");
composer->addAttachmentPart(attachment);
QVERIFY(composer->exec());
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().constFirst();
delete composer;
composer = nullptr;
// multipart/mixed
{
QVERIFY(message->contentType(false));
QCOMPARE(message->contentType()->mimeType(), QByteArray("multipart/mixed"));
QCOMPARE(message->contents().count(), 2);
// text/plain
{
Content *plain = message->contents().at(0);
QVERIFY(plain->contentType(false));
QCOMPARE(plain->contentType()->mimeType(), QByteArray("text/plain"));
}
// x-some/x-type (attachment)
{
Content *plain = message->contents().at(1);
QVERIFY(plain->contentType(false));
QCOMPARE(plain->contentType()->mimeType(), QByteArray("x-some/x-type"));
}
}
}
void ComposerTest::testAutoSave()
{
Composer *composer = new Composer;
fillComposerData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
attachment->setData("abc");
attachment->setMimeType("x-some/x-type");
composer->addAttachmentPart(attachment);
// This tests if autosave in crash mode works without invoking an event loop, since using an
// event loop in the crash handler would be a pretty bad idea
composer->setAutoSave(true);
composer->start();
QVERIFY(composer->finished());
QCOMPARE(composer->resultMessages().size(), 1);
delete composer;
composer = nullptr;
}
void ComposerTest::testNonAsciiHeaders()
{
Composer *composer = new Composer;
fillComposerData(composer);
const QString mailbox = QStringLiteral(" <bla@example.com>");
const QString fromDisplayName = QStringLiteral("Hellö");
const QString toDisplayName = QStringLiteral("æſłĸð");
const QString ccDisplayName = QStringLiteral("Вася");
const QString bccDisplayName = QStringLiteral("ĸłſðđøþĸµ»«„¢þµ¢”«ł„·ĸ”");
const QString replyToDisplayName = QStringLiteral("æĸſłð˝đВасяðæĸđ");
const QString from = fromDisplayName + mailbox;
const QString to = toDisplayName + mailbox;
const QString cc = ccDisplayName + mailbox;
const QString bcc = bccDisplayName + mailbox;
- const QString replyto = replyToDisplayName + mailbox;
+ const QStringList replyto = QStringList{replyToDisplayName + mailbox};
composer->infoPart()->setFrom(from);
composer->infoPart()->setTo(QStringList() << to);
composer->infoPart()->setCc(QStringList() << cc);
composer->infoPart()->setBcc(QStringList() << bcc);
composer->infoPart()->setReplyTo(replyto);
QVERIFY(composer->exec());
QCOMPARE(composer->resultMessages().size(), 1);
const KMime::Message::Ptr message = composer->resultMessages().constFirst();
message->assemble();
message->parse();
QCOMPARE(message->bcc(false)->displayNames().size(), 1);
QCOMPARE(message->to(false)->displayNames().size(), 1);
QCOMPARE(message->cc(false)->displayNames().size(), 1);
QCOMPARE(message->from(false)->displayNames().size(), 1);
QCOMPARE(message->replyTo(false)->displayNames().size(), 1);
QCOMPARE(message->from()->displayNames().constFirst(), fromDisplayName);
QCOMPARE(message->to()->displayNames().constFirst(), toDisplayName);
QCOMPARE(message->cc()->displayNames().constFirst(), ccDisplayName);
QCOMPARE(message->bcc()->displayNames().constFirst(), bccDisplayName);
QCOMPARE(message->replyTo()->displayNames().constFirst(), replyToDisplayName);
delete composer;
composer = nullptr;
}
void ComposerTest::testBug271192()
{
const QString displayName = QStringLiteral("Интернет-компания example");
const QString mailbox = QStringLiteral("example@example.com");
Composer *composer = new Composer;
fillComposerData(composer);
composer->infoPart()->setTo(QStringList() << (displayName + QLatin1String(" <") + mailbox + QLatin1String(">")));
QVERIFY(composer->exec());
QCOMPARE(composer->resultMessages().size(), 1);
const KMime::Message::Ptr message = composer->resultMessages().first();
QCOMPARE(message->to()->displayNames().size(), 1);
QCOMPARE(message->to()->displayNames().first().toUtf8(), displayName.toUtf8());
delete composer;
composer = nullptr;
}
void ComposerTest::fillComposerData(Composer *composer)
{
composer->globalPart()->setFallbackCharsetEnabled(true);
composer->infoPart()->setFrom(QStringLiteral("me@me.me"));
composer->infoPart()->setTo(QStringList(QStringLiteral("you@you.you")));
composer->textPart()->setWrappedPlainText(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
}
diff --git a/messagecomposer/autotests/composerviewbasetest.cpp b/messagecomposer/autotests/composerviewbasetest.cpp
index 4a713b75..fb82af02 100644
--- a/messagecomposer/autotests/composerviewbasetest.cpp
+++ b/messagecomposer/autotests/composerviewbasetest.cpp
@@ -1,54 +1,54 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "composerviewbasetest.h"
-#include <qtest.h>
+#include <QTest>
#include "../src/composer/composerviewbase.h"
ComposerViewBaseTest::ComposerViewBaseTest(QObject *parent)
: QObject(parent)
{
}
ComposerViewBaseTest::~ComposerViewBaseTest()
{
}
void ComposerViewBaseTest::shouldHaveDefaultValue()
{
MessageComposer::ComposerViewBase composerViewBase;
QVERIFY(!composerViewBase.attachmentModel());
QVERIFY(!composerViewBase.attachmentController());
QVERIFY(!composerViewBase.recipientsEditor());
QVERIFY(!composerViewBase.signatureController());
QVERIFY(!composerViewBase.identityCombo());
QVERIFY(!composerViewBase.identityManager());
QVERIFY(!composerViewBase.editor());
QVERIFY(!composerViewBase.transportComboBox());
QVERIFY(!composerViewBase.fccCombo());
QVERIFY(!composerViewBase.dictionary());
#if 0 //mrecipient is null
QVERIFY(composerViewBase.to().isEmpty());
QVERIFY(composerViewBase.cc().isEmpty());
QVERIFY(composerViewBase.bcc().isEmpty());
#endif
QVERIFY(composerViewBase.from().isEmpty());
QVERIFY(composerViewBase.replyTo().isEmpty());
QVERIFY(composerViewBase.subject().isEmpty());
}
QTEST_MAIN(ComposerViewBaseTest)
diff --git a/messagecomposer/autotests/composerviewbasetest.h b/messagecomposer/autotests/composerviewbasetest.h
index 50e2749c..d5bf2eff 100644
--- a/messagecomposer/autotests/composerviewbasetest.h
+++ b/messagecomposer/autotests/composerviewbasetest.h
@@ -1,33 +1,33 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef COMPOSERVIEWBASETEST_H
#define COMPOSERVIEWBASETEST_H
#include <QObject>
class ComposerViewBaseTest : public QObject
{
Q_OBJECT
public:
explicit ComposerViewBaseTest(QObject *parent = nullptr);
~ComposerViewBaseTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // COMPOSERVIEWBASETEST_H
diff --git a/messagecomposer/autotests/cryptocomposertest.cpp b/messagecomposer/autotests/cryptocomposertest.cpp
index 1a290067..c6e71ef4 100644
--- a/messagecomposer/autotests/cryptocomposertest.cpp
+++ b/messagecomposer/autotests/cryptocomposertest.cpp
@@ -1,608 +1,608 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "cryptocomposertest.h"
#include "qtest_messagecomposer.h"
#include "cryptofunctions.h"
#include "setupenv.h"
#include <Libkleo/Enum>
#include <KMime/Headers>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/ComposerViewBase>
#include <MessageComposer/RichTextComposerNg>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/InfoPart>
#include <MessageComposer/TextPart>
#include <MessageComposer/Util>
#include <MessageComposer/AttachmentControllerBase>
#include <MessageComposer/AttachmentModel>
using namespace MessageComposer;
#include <MessageCore/AttachmentPart>
#include <MessageCore/NodeHelper>
using namespace MessageCore;
#include <MimeTreeParser/ObjectTreeParser>
#include <MimeTreeParser/SimpleObjectTreeSource>
#include <gpgme++/key.h>
#include <QDebug>
#include <QTest>
Q_DECLARE_METATYPE(MessageCore::AttachmentPart)
QTEST_MAIN(CryptoComposerTest)
void CryptoComposerTest::initTestCase()
{
MessageComposer::Test::setupEnv();
}
Q_DECLARE_METATYPE(Headers::contentEncoding)
// openpgp
void CryptoComposerTest::testOpenPGPMime_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
QTest::newRow("SignOpenPGPMime") << data << true << false << Headers::CE7Bit;
QTest::newRow("EncryptOpenPGPMime") << data << false << true << Headers::CE7Bit;
QTest::newRow("SignEncryptOpenPGPMime") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testOpenPGPMime()
{
QFETCH(QString, data);
QFETCH(bool, sign);
QFETCH(bool, encrypt);
QFETCH(Headers::contentEncoding, cte);
Composer *composer = new Composer;
fillComposerData(composer, data);
fillComposerCryptoData(composer);
composer->setSignAndEncrypt(sign, encrypt);
composer->setMessageCryptoFormat(Kleo::OpenPGPMIMEFormat);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
//qDebug()<< "message:" << message.data()->encodedContent();
ComposerTestUtil::verify(sign, encrypt, message.data(), data.toUtf8(),
Kleo::OpenPGPMIMEFormat, cte);
QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
}
// the following will do for s-mime as well, as the same sign/enc jobs are used
void CryptoComposerTest::testEncryptSameAttachments_data()
{
QTest::addColumn<int>("format");
QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat;
//TODO: fix Inline PGP with encrypted attachments
//QTest::newRow( "InlineOpenPGP" ) << (int) Kleo::InlineOpenPGPFormat;
}
void CryptoComposerTest::testEncryptSameAttachments()
{
QFETCH(int, format);
Composer *composer = new Composer;
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
fillComposerData(composer, data);
fillComposerCryptoData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
attachment->setData("abc");
attachment->setMimeType("x-some/x-type");
attachment->setFileName(QString::fromLocal8Bit("anattachment.txt"));
attachment->setEncrypted(true);
attachment->setSigned(false);
composer->addAttachmentPart(attachment);
composer->setSignAndEncrypt(false, true);
composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
//qDebug()<< "message:" << message.data()->encodedContent();
ComposerTestUtil::verifyEncryption(message.data(), data.toUtf8(),
(Kleo::CryptoMessageFormat)format, true);
QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
MimeTreeParser::SimpleObjectTreeSource testSource;
testSource.setDecryptMessage(true);
MimeTreeParser::NodeHelper *nh = new MimeTreeParser::NodeHelper;
MimeTreeParser::ObjectTreeParser otp(&testSource, nh);
otp.parseObjectTree(message.data());
KMime::Message::Ptr unencrypted = nh->unencryptedMessage(message);
KMime::Content *testAttachment = Util::findTypeInMessage(unencrypted.data(), "x-some", "x-type");
QCOMPARE(testAttachment->body(), QString::fromLatin1("abc").toUtf8());
QCOMPARE(testAttachment->contentDisposition()->filename(), QString::fromLatin1("anattachment.txt"));
}
void CryptoComposerTest::testEditEncryptAttachments_data()
{
QTest::addColumn<int>("format");
QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat;
//TODO: SMIME should also be tested
}
void CryptoComposerTest::testEditEncryptAttachments()
{
QFETCH(int, format);
Composer *composer = new Composer;
QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
fillComposerData(composer, data);
fillComposerCryptoData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
const QString fileName = QStringLiteral("anattachment.txt");
const QByteArray fileData = "abc";
attachment->setData(fileData);
attachment->setMimeType("x-some/x-type");
attachment->setFileName(fileName);
attachment->setEncrypted(true);
attachment->setSigned(false);
composer->addAttachmentPart(attachment);
composer->setSignAndEncrypt(false, true);
composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
// setup a viewer
ComposerViewBase view(this, nullptr);
AttachmentModel model(this);
AttachmentControllerBase controller(&model, nullptr, nullptr);
MessageComposer::RichTextComposerNg editor;
view.setAttachmentModel(&model);
view.setAttachmentController(&controller);
view.setEditor(&editor);
// Let's load the email to the viewer
view.setMessage(message, true);
QModelIndex index = model.index(0, 0);
QCOMPARE(editor.toPlainText(), data);
QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.data(index, AttachmentModel::NameRole).toString(), fileName);
AttachmentPart::Ptr part = model.attachments()[0];
QCOMPARE(part->data(), fileData);
QCOMPARE(part->fileName(), fileName);
}
void CryptoComposerTest::testEditEncryptAndLateAttachments_data()
{
QTest::addColumn<int>("format");
QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat;
//TODO: SMIME should also be tested
}
void CryptoComposerTest::testEditEncryptAndLateAttachments()
{
QFETCH(int, format);
Composer *composer = new Composer;
QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
fillComposerData(composer, data);
fillComposerCryptoData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
const QString fileName = QStringLiteral("anattachment.txt");
const QByteArray fileData = "abc";
const QString fileName2 = QStringLiteral("nonencrypt.txt");
const QByteArray fileData2 = "readable";
attachment->setData(fileData);
attachment->setMimeType("x-some/x-type");
attachment->setFileName(fileName);
attachment->setEncrypted(true);
attachment->setSigned(false);
composer->addAttachmentPart(attachment);
attachment = AttachmentPart::Ptr(new AttachmentPart);
attachment->setData(fileData2);
attachment->setMimeType("x-some/x-type2");
attachment->setFileName(fileName2);
attachment->setEncrypted(false);
attachment->setSigned(false);
composer->addAttachmentPart(attachment);
composer->setSignAndEncrypt(false, true);
composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
// setup a viewer
ComposerViewBase view(this, nullptr);
AttachmentModel model(this);
AttachmentControllerBase controller(&model, nullptr, nullptr);
MessageComposer::RichTextComposerNg editor;
view.setAttachmentModel(&model);
view.setAttachmentController(&controller);
view.setEditor(&editor);
// Let's load the email to the viewer
view.setMessage(message, true);
//QModelIndex index = model.index(0, 0);
QCOMPARE(editor.toPlainText(), data);
QCOMPARE(model.rowCount(), 2);
AttachmentPart::Ptr part = model.attachments()[0];
QCOMPARE(part->fileName(), fileName);
QCOMPARE(part->data(), fileData);
part = model.attachments()[1];
QCOMPARE(part->fileName(), fileName2);
QCOMPARE(part->data(), fileData2);
}
void CryptoComposerTest::testSignEncryptLateAttachments_data()
{
QTest::addColumn<int>("format");
QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat;
QTest::newRow("InlineOpenPGP") << (int)Kleo::InlineOpenPGPFormat;
}
void CryptoComposerTest::testSignEncryptLateAttachments()
{
QFETCH(int, format);
Composer *composer = new Composer;
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
fillComposerData(composer, data);
fillComposerCryptoData(composer);
AttachmentPart::Ptr attachment = AttachmentPart::Ptr(new AttachmentPart);
attachment->setData("abc");
attachment->setMimeType("x-some/x-type");
attachment->setFileName(QString::fromLocal8Bit("anattachment.txt"));
attachment->setEncrypted(false);
attachment->setSigned(false);
composer->addAttachmentPart(attachment);
composer->setSignAndEncrypt(true, true);
composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
// as we have an additional attachment, just ignore it when checking for sign/encrypt
KMime::Content *b = MessageCore::NodeHelper::firstChild(message.data());
ComposerTestUtil::verifySignatureAndEncryption(b, data.toUtf8(),
(Kleo::CryptoMessageFormat)format, true);
QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
// now check the attachment separately
QCOMPARE(QString::fromLatin1(MessageCore::NodeHelper::nextSibling(b)->body()), QString::fromLatin1("abc"));
}
void CryptoComposerTest::testBCCEncrypt_data()
{
QTest::addColumn<int>("format");
QTest::newRow("OpenPGPMime") << (int)Kleo::OpenPGPMIMEFormat;
QTest::newRow("InlineOpenPGP") << (int)Kleo::InlineOpenPGPFormat;
}
// secondary recipients
void CryptoComposerTest::testBCCEncrypt()
{
QFETCH(int, format);
Composer *composer = new Composer;
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
fillComposerData(composer, data);
- composer->infoPart()->setBcc(QStringList(QString::fromLatin1("bcc@bcc.org")));
+ composer->infoPart()->setBcc(QStringList(QLatin1String("bcc@bcc.org")));
std::vector<GpgME::Key> keys = MessageComposer::Test::getKeys();
QStringList primRecipients;
primRecipients << QString::fromLocal8Bit("you@you.you");
std::vector< GpgME::Key > pkeys;
pkeys.push_back(keys[1]);
QStringList secondRecipients;
secondRecipients << QString::fromLocal8Bit("bcc@bcc.org");
std::vector< GpgME::Key > skeys;
skeys.push_back(keys[2]);
QList<QPair<QStringList, std::vector<GpgME::Key> > > encKeys;
encKeys.append(QPair<QStringList, std::vector<GpgME::Key> >(primRecipients, pkeys));
encKeys.append(QPair<QStringList, std::vector<GpgME::Key> >(secondRecipients, skeys));
composer->setSignAndEncrypt(true, true);
composer->setMessageCryptoFormat((Kleo::CryptoMessageFormat)format);
composer->setEncryptionKeys(encKeys);
composer->setSigningKeys(keys);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 2);
KMime::Message::Ptr primMessage = composer->resultMessages().first();
KMime::Message::Ptr secMessage = composer->resultMessages()[1];
delete composer;
composer = nullptr;
ComposerTestUtil::verifySignatureAndEncryption(primMessage.data(), data.toUtf8(),
(Kleo::CryptoMessageFormat)format);
QCOMPARE(primMessage->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(primMessage->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
ComposerTestUtil::verifySignatureAndEncryption(secMessage.data(), data.toUtf8(),
(Kleo::CryptoMessageFormat)format);
QCOMPARE(secMessage->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(secMessage->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
}
// inline pgp
void CryptoComposerTest::testOpenPGPInline_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
QTest::newRow("SignOpenPGPInline") << data << true << false << Headers::CE7Bit;
QTest::newRow("EncryptOpenPGPInline") << data << false << true << Headers::CE7Bit;
QTest::newRow("SignEncryptOpenPGPInline") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testOpenPGPInline()
{
QFETCH(QString, data);
QFETCH(bool, sign);
QFETCH(bool, encrypt);
QFETCH(Headers::contentEncoding, cte);
Composer *composer = new Composer;
fillComposerData(composer, data);
fillComposerCryptoData(composer);
composer->setSignAndEncrypt(sign, encrypt);
composer->setMessageCryptoFormat(Kleo::InlineOpenPGPFormat);
VERIFYEXEC(composer);
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
if (sign && !encrypt) {
- data += QString::fromLatin1("\n");
+ data += QLatin1String("\n");
}
//qDebug() << "message:" << message->encodedContent();
ComposerTestUtil::verify(sign, encrypt, message.data(), data.toUtf8(),
Kleo::InlineOpenPGPFormat, cte);
QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("me@me.me"));
QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
}
// s-mime
void CryptoComposerTest::testSMIME_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
QTest::newRow("SignSMIME") << data << true << false << Headers::CE7Bit;
QTest::newRow("EncryptSMIME") << data << false << true << Headers::CE7Bit;
QTest::newRow("SignEncryptSMIME") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testSMIME()
{
QFETCH(bool, sign);
QFETCH(bool, encrypt);
runSMIMETest(sign, encrypt, false);
}
void CryptoComposerTest::testSMIMEOpaque_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
- QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ QString data(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
QTest::newRow("SignSMIMEOpaque") << data << true << false << Headers::CE7Bit;
QTest::newRow("EncryptSMIMEOpaque") << data << false << true << Headers::CE7Bit;
QTest::newRow("SignEncryptSMIMEOpaque") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testSMIMEOpaque()
{
QFETCH(bool, sign);
QFETCH(bool, encrypt);
runSMIMETest(sign, encrypt, true);
}
// contentTransferEncoding
void CryptoComposerTest::testCTEquPr_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
QString data(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way. [ä]"));
QTest::newRow("CTEquPr:Sign") << data << true << false << Headers::CEquPr;
QTest::newRow("CTEquPr:Encrypt") << data << false << true << Headers::CE7Bit;
QTest::newRow("CTEquPr:SignEncrypt") << data << true << true << Headers::CE7Bit;
data = QStringLiteral(
"All happy families are alike;\n\n\n\neach unhappy family is unhappy in its own way.\n--\n hallloasdfasdfsadfsdf asdf sadfasdf sdf sdf sdf sadfasdf sdaf daf sdf asdf sadf asdf asdf [ä]");
QTest::newRow("CTEquPr:Sign:Newline") << data << true << false << Headers::CEquPr;
QTest::newRow("CTEquPr:Encrypt:Newline") << data << false << true << Headers::CE7Bit;
QTest::newRow("CTEquPr:SignEncrypt:Newline") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testCTEquPr()
{
testSMIME();
testSMIMEOpaque();
testOpenPGPMime();
testOpenPGPInline();
}
void CryptoComposerTest::testCTEbase64_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("sign");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<Headers::contentEncoding>("cte");
QString data(QStringLiteral(
"[ääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääää]"));
QTest::newRow("CTEbase64:Sign") << data << true << false << Headers::CEbase64;
QTest::newRow("CTEbase64:Encrypt") << data << false << true << Headers::CE7Bit;
QTest::newRow("CTEbase64:SignEncrypt") << data << true << true << Headers::CE7Bit;
}
void CryptoComposerTest::testCTEbase64()
{
testSMIME();
testSMIMEOpaque();
testOpenPGPMime();
testOpenPGPInline();
}
// Helper methods
-void CryptoComposerTest::fillComposerData(Composer *composer, QString data)
+void CryptoComposerTest::fillComposerData(Composer *composer, const QString &data)
{
composer->globalPart()->setFallbackCharsetEnabled(true);
- composer->infoPart()->setFrom(QString::fromLatin1("me@me.me"));
- composer->infoPart()->setTo(QStringList(QString::fromLatin1("you@you.you")));
+ composer->infoPart()->setFrom(QStringLiteral("me@me.me"));
+ composer->infoPart()->setTo(QStringList(QLatin1String("you@you.you")));
composer->textPart()->setWrappedPlainText(data);
}
void CryptoComposerTest::fillComposerCryptoData(Composer *composer)
{
std::vector<GpgME::Key> keys = MessageComposer::Test::getKeys();
//qDebug() << "got num of keys:" << keys.size();
QStringList recipients;
recipients << QString::fromLocal8Bit("you@you.you");
QList<QPair<QStringList, std::vector<GpgME::Key> > > encKeys;
encKeys.append(QPair<QStringList, std::vector<GpgME::Key> >(recipients, keys));
composer->setEncryptionKeys(encKeys);
composer->setSigningKeys(keys);
}
void CryptoComposerTest::runSMIMETest(bool sign, bool enc, bool opaque)
{
QFETCH(QString, data);
QFETCH(Headers::contentEncoding, cte);
Composer *composer = new Composer;
fillComposerData(composer, data);
- composer->infoPart()->setFrom(QString::fromLatin1("test@example.com"));
+ composer->infoPart()->setFrom(QStringLiteral("test@example.com"));
std::vector<GpgME::Key> keys = MessageComposer::Test::getKeys(true);
QStringList recipients;
recipients << QString::fromLocal8Bit("you@you.you");
QList<QPair<QStringList, std::vector<GpgME::Key> > > encKeys;
encKeys.append(QPair<QStringList, std::vector<GpgME::Key> >(recipients, keys));
composer->setEncryptionKeys(encKeys);
composer->setSigningKeys(keys);
composer->setSignAndEncrypt(sign, enc);
Kleo::CryptoMessageFormat f;
if (opaque) {
f = Kleo::SMIMEOpaqueFormat;
} else {
f = Kleo::SMIMEFormat;
}
composer->setMessageCryptoFormat(f);
const bool result = composer->exec();
//QEXPECT_FAIL("", "GPG setup problems", Continue);
QVERIFY(result);
if (result) {
QCOMPARE(composer->resultMessages().size(), 1);
KMime::Message::Ptr message = composer->resultMessages().first();
delete composer;
composer = nullptr;
//qDebug() << "message:" << message->encodedContent();
ComposerTestUtil::verify(sign, enc, message.data(), data.toUtf8(), f, cte);
QCOMPARE(message->from()->asUnicodeString(), QString::fromLocal8Bit("test@example.com"));
QCOMPARE(message->to()->asUnicodeString(), QString::fromLocal8Bit("you@you.you"));
}
}
diff --git a/messagecomposer/autotests/cryptocomposertest.h b/messagecomposer/autotests/cryptocomposertest.h
index b5527dd8..a678b93b 100644
--- a/messagecomposer/autotests/cryptocomposertest.h
+++ b/messagecomposer/autotests/cryptocomposertest.h
@@ -1,83 +1,83 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#ifndef CRYPTOCOMPOSERTEST_H
#define CRYPTOCOMPOSERTEST_H
#include <QObject>
namespace MessageComposer {
class Composer;
}
class CryptoComposerTest : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
private Q_SLOTS:
// openpgp
void testOpenPGPMime();
void testOpenPGPMime_data();
// the following will do for s-mime as well, as the same sign/enc jobs are used
void testEncryptSameAttachments();
void testEncryptSameAttachments_data();
void testSignEncryptLateAttachments();
void testSignEncryptLateAttachments_data();
void testEditEncryptAttachments();
void testEditEncryptAttachments_data();
void testEditEncryptAndLateAttachments();
void testEditEncryptAndLateAttachments_data();
// secondary recipients
void testBCCEncrypt();
void testBCCEncrypt_data();
// inline pgp
void testOpenPGPInline_data();
void testOpenPGPInline();
// s-mime
void testSMIME_data();
void testSMIME();
void testSMIMEOpaque_data();
void testSMIMEOpaque();
// contentTransferEncoding
void testCTEquPr_data();
void testCTEquPr();
void testCTEbase64_data();
void testCTEbase64();
// TODO test the code for autodetecting the charset of a text attachment.
private:
- void fillComposerData(MessageComposer::Composer *composer, QString data);
+ void fillComposerData(MessageComposer::Composer *composer, const QString &data);
void fillComposerCryptoData(MessageComposer::Composer *composer);
// convenience, shared code
void runSMIMETest(bool sign, bool enc, bool opaque);
};
#endif
diff --git a/messagecomposer/autotests/followupreminderselectdatedialogtest.cpp b/messagecomposer/autotests/followupreminderselectdatedialogtest.cpp
index f5aec399..c3a660a6 100644
--- a/messagecomposer/autotests/followupreminderselectdatedialogtest.cpp
+++ b/messagecomposer/autotests/followupreminderselectdatedialogtest.cpp
@@ -1,116 +1,116 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "followupreminderselectdatedialogtest.h"
#include "../src/followupreminder/followupreminderselectdatedialog.h"
-#include <qtest.h>
+#include <QTest>
#include <KDateComboBox>
#include <AkonadiWidgets/CollectionComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <AkonadiCore/EntityTreeModel>
#include <QStandardItemModel>
#include <KCalCore/Todo>
FollowupReminderSelectDateDialogTest::FollowupReminderSelectDateDialogTest(QObject *parent)
: QObject(parent)
{
}
FollowupReminderSelectDateDialogTest::~FollowupReminderSelectDateDialogTest()
{
}
QStandardItemModel *FollowupReminderSelectDateDialogTest::defaultItemModel()
{
QStandardItemModel *model = new QStandardItemModel;
for (int id = 42; id < 51; ++id) {
Akonadi::Collection collection(id);
collection.setRights(Akonadi::Collection::AllRights);
collection.setName(QString::number(id));
collection.setContentMimeTypes(QStringList() << KCalCore::Todo::todoMimeType());
QStandardItem *item = new QStandardItem(collection.name());
item->setData(QVariant::fromValue(collection),
Akonadi::EntityTreeModel::CollectionRole);
item->setData(QVariant::fromValue(collection.id()),
Akonadi::EntityTreeModel::CollectionIdRole);
model->appendRow(item);
}
return model;
}
void FollowupReminderSelectDateDialogTest::shouldHaveDefaultValue()
{
MessageComposer::FollowUpReminderSelectDateDialog dlg(nullptr, defaultItemModel());
KDateComboBox *datecombobox = dlg.findChild<KDateComboBox *>(QStringLiteral("datecombobox"));
QVERIFY(datecombobox);
Akonadi::CollectionComboBox *combobox = dlg.findChild<Akonadi::CollectionComboBox *>(QStringLiteral("collectioncombobox"));
QVERIFY(combobox);
QDate currentDate = QDate::currentDate();
QCOMPARE(datecombobox->date(), currentDate);
QPushButton *okButton = dlg.findChild<QPushButton *>(QStringLiteral("ok_button"));
QVERIFY(okButton);
QVERIFY(okButton->isEnabled());
}
void FollowupReminderSelectDateDialogTest::shouldDisableOkButtonIfDateIsEmpty()
{
MessageComposer::FollowUpReminderSelectDateDialog dlg(nullptr, defaultItemModel());
KDateComboBox *datecombobox = dlg.findChild<KDateComboBox *>(QStringLiteral("datecombobox"));
QVERIFY(datecombobox);
QPushButton *okButton = dlg.findChild<QPushButton *>(QStringLiteral("ok_button"));
QVERIFY(okButton->isEnabled());
datecombobox->lineEdit()->clear();
QVERIFY(!okButton->isEnabled());
}
void FollowupReminderSelectDateDialogTest::shouldDisableOkButtonIfDateIsNotValid()
{
MessageComposer::FollowUpReminderSelectDateDialog dlg(nullptr, defaultItemModel());
KDateComboBox *datecombobox = dlg.findChild<KDateComboBox *>(QStringLiteral("datecombobox"));
QVERIFY(datecombobox);
datecombobox->lineEdit()->setText(QStringLiteral(" "));
QPushButton *okButton = dlg.findChild<QPushButton *>(QStringLiteral("ok_button"));
QVERIFY(!okButton->isEnabled());
const QDate date = QDate::currentDate();
datecombobox->setDate(date);
QVERIFY(okButton->isEnabled());
}
void FollowupReminderSelectDateDialogTest::shouldDisableOkButtonIfModelIsEmpty()
{
MessageComposer::FollowUpReminderSelectDateDialog dlg(nullptr, new QStandardItemModel(nullptr));
KDateComboBox *datecombobox = dlg.findChild<KDateComboBox *>(QStringLiteral("datecombobox"));
QVERIFY(datecombobox);
QPushButton *okButton = dlg.findChild<QPushButton *>(QStringLiteral("ok_button"));
QVERIFY(!okButton->isEnabled());
datecombobox->lineEdit()->setText(QStringLiteral(" "));
QVERIFY(!okButton->isEnabled());
const QDate date = QDate::currentDate();
datecombobox->setDate(date);
QVERIFY(!okButton->isEnabled());
}
QTEST_MAIN(FollowupReminderSelectDateDialogTest)
diff --git a/messagecomposer/autotests/followupreminderselectdatedialogtest.h b/messagecomposer/autotests/followupreminderselectdatedialogtest.h
index 891aa011..569e3543 100644
--- a/messagecomposer/autotests/followupreminderselectdatedialogtest.h
+++ b/messagecomposer/autotests/followupreminderselectdatedialogtest.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef FOLLOWUPREMINDERSELECTDATEDIALOGTEST_H
#define FOLLOWUPREMINDERSELECTDATEDIALOGTEST_H
#include <QObject>
class QStandardItemModel;
class FollowupReminderSelectDateDialogTest : public QObject
{
Q_OBJECT
public:
explicit FollowupReminderSelectDateDialogTest(QObject *parent = nullptr);
~FollowupReminderSelectDateDialogTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldDisableOkButtonIfDateIsEmpty();
void shouldDisableOkButtonIfDateIsNotValid();
void shouldDisableOkButtonIfModelIsEmpty();
private:
QStandardItemModel *defaultItemModel();
};
#endif // FOLLOWUPREMINDERSELECTDATEDIALOGTEST_H
diff --git a/messagecomposer/autotests/globalparttest.cpp b/messagecomposer/autotests/globalparttest.cpp
index f8533608..4ec26dec 100644
--- a/messagecomposer/autotests/globalparttest.cpp
+++ b/messagecomposer/autotests/globalparttest.cpp
@@ -1,43 +1,43 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "globalparttest.h"
-#include <qtest.h>
+#include <QTest>
#include <../src/part/globalpart.h>
GlobalPartTest::GlobalPartTest(QObject *parent)
: QObject(parent)
{
}
GlobalPartTest::~GlobalPartTest()
{
}
void GlobalPartTest::shouldHaveDefaultValue()
{
MessageComposer::GlobalPart globalpart;
QVERIFY(globalpart.isGuiEnabled());
QVERIFY(!globalpart.parentWidgetForGui());
QVERIFY(!globalpart.isFallbackCharsetEnabled());
QVERIFY(!globalpart.is8BitAllowed());
QVERIFY(!globalpart.MDNRequested());
QVERIFY(!globalpart.requestDeleveryConfirmation());
}
QTEST_MAIN(GlobalPartTest)
diff --git a/messagecomposer/autotests/globalparttest.h b/messagecomposer/autotests/globalparttest.h
index 3cb75587..5a8123ad 100644
--- a/messagecomposer/autotests/globalparttest.h
+++ b/messagecomposer/autotests/globalparttest.h
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef GLOBALPARTTEST_H
#define GLOBALPARTTEST_H
#include <QObject>
class GlobalPartTest : public QObject
{
Q_OBJECT
public:
explicit GlobalPartTest(QObject *parent = nullptr);
~GlobalPartTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // GLOBALPARTTEST_H
diff --git a/messagecomposer/autotests/infoparttest.cpp b/messagecomposer/autotests/infoparttest.cpp
index 4225203b..86c9ba81 100644
--- a/messagecomposer/autotests/infoparttest.cpp
+++ b/messagecomposer/autotests/infoparttest.cpp
@@ -1,50 +1,50 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "infoparttest.h"
#include "../src/part/infopart.h"
-#include <qtest.h>
+#include <QTest>
InfoPartTest::InfoPartTest(QObject *parent)
: QObject(parent)
{
}
InfoPartTest::~InfoPartTest()
{
}
void InfoPartTest::shouldHaveDefaultValue()
{
MessageComposer::InfoPart infopart;
QCOMPARE(infopart.transportId(), 0);
QVERIFY(!infopart.urgent());
QVERIFY(infopart.from().isEmpty());
QVERIFY(infopart.to().isEmpty());
QVERIFY(infopart.cc().isEmpty());
QVERIFY(infopart.bcc().isEmpty());
QVERIFY(infopart.replyTo().isEmpty());
QVERIFY(infopart.subject().isEmpty());
QVERIFY(infopart.fcc().isEmpty());
QVERIFY(infopart.userAgent().isEmpty());
QVERIFY(infopart.inReplyTo().isEmpty());
QVERIFY(infopart.references().isEmpty());
QVERIFY(infopart.extraHeaders().isEmpty());
}
QTEST_MAIN(InfoPartTest)
diff --git a/messagecomposer/autotests/infoparttest.h b/messagecomposer/autotests/infoparttest.h
index 069091c8..eb95388d 100644
--- a/messagecomposer/autotests/infoparttest.h
+++ b/messagecomposer/autotests/infoparttest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef INFOPARTTEST_H
#define INFOPARTTEST_H
#include <QObject>
class InfoPartTest : public QObject
{
Q_OBJECT
public:
explicit InfoPartTest(QObject *parent = nullptr);
~InfoPartTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // INFOPARTTEST_H
diff --git a/messagecomposer/autotests/maintextjobtest.cpp b/messagecomposer/autotests/maintextjobtest.cpp
index 0d052232..3c7f2bd6 100644
--- a/messagecomposer/autotests/maintextjobtest.cpp
+++ b/messagecomposer/autotests/maintextjobtest.cpp
@@ -1,312 +1,312 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "maintextjobtest.h"
#include <QTextCodec>
#include <QStandardPaths>
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/MainTextJob>
#include <MessageComposer/TextPart>
#include <MessageComposer/RichTextComposerNg>
#include <KPIMTextEdit/RichTextComposerControler>
#include <KPIMTextEdit/RichTextComposerImages>
#include <KActionCollection>
//#include <kpimtextedit/textedit.h>
using namespace MessageComposer;
QTEST_MAIN(MainTextJobTest)
void MainTextJobTest::initTestCase()
{
qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher
QStandardPaths::setTestModeEnabled(true);
}
void MainTextJobTest::testPlainText()
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
QList<QByteArray> charsets;
charsets << "us-ascii" << "utf-8";
composer->globalPart()->setCharsets(charsets);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("they said their nevers they slept their dream");
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
QCOMPARE(result->contentType()->charset(), QByteArray("us-ascii"));
QCOMPARE(QString::fromLatin1(result->body()), data);
}
void MainTextJobTest::testWrappingErrors()
{
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
composer->globalPart()->setFallbackCharsetEnabled(true);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("they said their nevers they slept their dream");
textPart->setWordWrappingEnabled(false);
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(!mjob->exec()); // error: not UseWrapping but given only wrapped text
QCOMPARE(mjob->error(), int(JobBase::BugError));
}
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
composer->globalPart()->setFallbackCharsetEnabled(true);
TextPart *textPart = new TextPart;
textPart->setWordWrappingEnabled(true);
QString data = QStringLiteral("they said their nevers they slept their dream");
textPart->setCleanPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(!mjob->exec()); // error: UseWrapping but given only clean text
QCOMPARE(mjob->error(), int(JobBase::BugError));
}
}
void MainTextJobTest::testCustomCharset()
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
QByteArray charset("iso-8859-2");
composer->globalPart()->setCharsets(QList<QByteArray>() << charset);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("şi el o să se-nchidă cu o frunză de pelin");
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
QCOMPARE(result->contentType()->charset(), charset);
QByteArray outData = result->body();
QTextCodec *codec = QTextCodec::codecForName(charset);
QVERIFY(codec);
QCOMPARE(codec->toUnicode(outData), data);
}
void MainTextJobTest::testNoCharset()
{
Composer *composer = new Composer;
QVERIFY(!composer->globalPart()->isFallbackCharsetEnabled());
composer->globalPart()->setGuiEnabled(false);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("do you still play the accordion?");
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QSKIP("This tests has been failing for a long time, please someone fix it", SkipSingle);
QVERIFY(!mjob->exec()); // Error.
QCOMPARE(mjob->error(), int(JobBase::BugError));
qDebug() << mjob->errorString();
}
void MainTextJobTest::testBadCharset()
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
QByteArray charset("us-ascii"); // Cannot handle Romanian chars.
composer->globalPart()->setCharsets(QList<QByteArray>() << charset);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("el a plâns peste ţară cu lacrima limbii noastre");
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QSKIP("This tests has been failing for a long time, please someone fix it", SkipSingle);
QVERIFY(!mjob->exec()); // Error.
QCOMPARE(mjob->error(), int(JobBase::UserError));
qDebug() << mjob->errorString();
}
void MainTextJobTest::testFallbackCharset()
{
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
composer->globalPart()->setFallbackCharsetEnabled(true);
TextPart *textPart = new TextPart;
QString data = QStringLiteral("and when he falleth...");
textPart->setWrappedPlainText(data);
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
QCOMPARE(result->contentType()->charset(), QByteArray("us-ascii")); // Fallback is us-ascii or utf8.
QCOMPARE(QString::fromLatin1(result->body()), data);
}
void MainTextJobTest::testHtml()
{
QLatin1String originalHtml("<html><head></head><body>Test <em>with</em> formatting...<br>The end.</body></html>");
MessageComposer::RichTextComposerNg editor;
editor.createActions(new KActionCollection(this));
editor.setTextOrHtml(originalHtml);
QVERIFY(editor.composerControler()->isFormattingUsed());
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
composer->globalPart()->setFallbackCharsetEnabled(true);
TextPart *textPart = new TextPart;
textPart->setWordWrappingEnabled(false);
textPart->setCleanPlainText(editor.composerControler()->toCleanPlainText());
textPart->setCleanHtml(editor.toCleanHtml());
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
// multipart/alternative
{
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("multipart/alternative"));
QCOMPARE(result->contents().count(), 2);
// text/plain
{
Content *plain = result->contents().at(0);
QVERIFY(plain->contentType(false));
QCOMPARE(plain->contentType()->mimeType(), QByteArray("text/plain"));
QCOMPARE(QString::fromLatin1(plain->body()), editor.composerControler()->toCleanPlainText());
}
// text/html
{
Content *html = result->contents().at(1);
QVERIFY(html->contentType(false));
QCOMPARE(html->contentType()->mimeType(), QByteArray("text/html"));
// The editor adds extra Html stuff, so we can't compare to originalHtml.
QCOMPARE(QLatin1String(html->body()), editor.toCleanHtml());
}
}
}
void MainTextJobTest::testHtmlWithImages()
{
KActionCollection ac(this);
MessageComposer::RichTextComposerNg editor;
editor.createActions(new KActionCollection(this));
QImage image1(16, 16, QImage::Format_ARGB32_Premultiplied);
image1.fill(Qt::red);
const QString image1Path = QCoreApplication::applicationDirPath() + QLatin1String("/image1.png");
image1.save(image1Path);
QImage image2(16, 16, QImage::Format_ARGB32_Premultiplied);
image2.fill(Qt::blue);
const QString image2Path = QCoreApplication::applicationDirPath() + QLatin1String("/image2.png");
image2.save(image2Path);
QString data = QStringLiteral("dust in the wind");
editor.setTextOrHtml(data);
editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
KPIMTextEdit::ImageList images = editor.composerControler()->composerImages()->embeddedImages();
QCOMPARE(images.count(), 2);
QString cid1 = images[0]->contentID;
QString cid2 = images[1]->contentID;
QString name1 = images[0]->imageName;
QString name2 = images[1]->imageName;
Composer *composer = new Composer;
composer->globalPart()->setGuiEnabled(false);
composer->globalPart()->setFallbackCharsetEnabled(true);
TextPart *textPart = new TextPart;
textPart->setWordWrappingEnabled(false);
textPart->setCleanPlainText(editor.composerControler()->toCleanPlainText());
textPart->setCleanHtml(editor.composerControler()->toCleanHtml());
textPart->setEmbeddedImages(editor.composerControler()->composerImages()->embeddedImages());
MainTextJob *mjob = new MainTextJob(textPart, composer);
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
// multipart/related
{
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("multipart/related"));
QCOMPARE(result->contents().count(), 3);
// multipart/alternative
{
Content *alternative = result->contents().at(0);
QVERIFY(alternative->contentType(false));
QCOMPARE(alternative->contentType()->mimeType(), QByteArray("multipart/alternative"));
QCOMPARE(alternative->contents().count(), 2);
// text/plain
{
Content *plain = alternative->contents().at(0);
QCOMPARE(plain->contentType()->mimeType(), QByteArray("text/plain"));
QCOMPARE(QString::fromLatin1(plain->body()), data);
}
// text/html
{
Content *html = alternative->contents().at(1);
QCOMPARE(html->contentType()->mimeType(), QByteArray("text/html"));
QString data = QString::fromLatin1(html->body());
int idx1 = data.indexOf(QStringLiteral("cid:%1").arg(cid1));
int idx2 = data.indexOf(QStringLiteral("cid:%1").arg(cid2));
QVERIFY(idx1 > 0);
QVERIFY(idx2 > 0);
QVERIFY(idx1 < idx2);
}
}
// First image/png
{
Content *image = result->contents().at(1);
QVERIFY(image->contentType(false));
QCOMPARE(image->contentType()->mimeType(), QByteArray("image/png"));
QCOMPARE(image->contentType()->name(), name1);
const Headers::ContentID *cid = image->header<Headers::ContentID>();
QVERIFY(cid);
QCOMPARE(cid->identifier(), cid1.toLatin1());
}
// Second image/png
{
Content *image = result->contents().at(2);
QVERIFY(image->contentType(false));
QCOMPARE(image->contentType()->mimeType(), QByteArray("image/png"));
QCOMPARE(image->contentType()->name(), name2);
const Headers::ContentID *cid = image->header<Headers::ContentID>();
QVERIFY(cid);
QCOMPARE(cid->identifier(), cid2.toLatin1());
}
}
}
diff --git a/messagecomposer/autotests/messagefactoryngtest.cpp b/messagecomposer/autotests/messagefactoryngtest.cpp
index f5cc8afb..e07cc3cb 100644
--- a/messagecomposer/autotests/messagefactoryngtest.cpp
+++ b/messagecomposer/autotests/messagefactoryngtest.cpp
@@ -1,966 +1,966 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "messagefactoryngtest.h"
#include "qtest_messagecomposer.h"
#include "cryptofunctions.h"
#include "setupenv.h"
#include <MessageCore/StringUtil>
#include <MessageComposer/Composer>
#include <MessageComposer/MessageFactoryNG>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/MessageComposerSettings>
#include <MessageComposer/Util>
#include <MessageComposer/InfoPart>
#include <MessageComposer/TextPart>
#include <KMime/DateFormatter>
#include <KIdentityManagement/KIdentityManagement/IdentityManager>
#include <KIdentityManagement/KIdentityManagement/Identity>
#include "globalsettings_templateparser.h"
#include <KCharsets>
#include <QDateTime>
#include <QDir>
#include <QLocale>
#include <QSignalSpy>
#include <QTest>
using namespace MessageComposer;
#ifndef Q_OS_WIN
void initLocale()
{
setenv("LC_ALL", "en_US.utf-8", 1);
setenv("TZ", "UTC", 1);
}
Q_CONSTRUCTOR_FUNCTION(initLocale)
#endif
namespace {
template<typename String>
String very_simplistic_diff(const String &a, const String &b)
{
const QList<String> al = a.split('\n');
const QList<String> bl = b.split('\n');
String result;
int ai = 0, bi = 0;
while (ai < al.size() && bi < bl.size()) {
if (al[ai] == bl[bi]) {
//qDebug( "found equal line a@%d x b@%d", ai, bi );
result += " " + al[ai] + '\n';
++ai;
++bi;
} else {
//qDebug( "found unequal line a@%d x b@%d", ai, bi );
const int b_in_a = al.indexOf(bl[bi], ai);
const int a_in_b = bl.indexOf(al[ai], bi);
//qDebug( " b_in_a == %d", b_in_a );
//qDebug( " a_in_b == %d", a_in_b );
if (b_in_a == -1) {
if (a_in_b == -1) {
// (at least) one line changed:
result += "- " + al[ai++] + '\n'
+ "+ " + bl[bi++] + '\n';
} else {
// some lines added:
while (bi < a_in_b) {
result += "+ " + bl[bi++] + '\n';
}
}
} else {
// some lines removed:
while (ai < b_in_a) {
result += "- " + al[ai++] + '\n';
}
// some lines added:
while (bi < a_in_b) {
result += "+ " + bl[bi++] + '\n';
}
}
//qDebug( "result ( a@%d b@%d ):\n%s\n--end", ai, bi, result.constData() );
}
}
const int sizeal(al.size());
for (int i = ai; i < sizeal; ++i) {
result += "- " + al[i] + '\n';
}
const int sizebl(bl.size());
for (int i = bi; i < sizebl; ++i) {
result += "+ " + bl[i] + '\n';
}
return result;
}
}
#define QCOMPARE_OR_DIFF(a, b) \
if (a != b) { \
qDebug("diff:\n--begin--\n%s\n--end--", very_simplistic_diff(a, b).constData());} \
QVERIFY(a == b)
QTEST_MAIN(MessageFactoryTest)
void MessageFactoryTest::cleanupTestCase()
{
delete mIdentMan;
mIdentMan = nullptr;
QDir dir(QDir::homePath() + QStringLiteral("/.qttest/"));
dir.removeRecursively();
}
void MessageFactoryTest::initTestCase()
{
// Force fake XDG dirs so that KIdentityManagement does not
// read actual user data when running tests locally
QStandardPaths::setTestModeEnabled(true);
qRegisterMetaType<MessageComposer::MessageFactoryNG::MessageReply>();
mIdentMan = new KIdentityManagement::IdentityManager;
KIdentityManagement::Identity &ident = mIdentMan->modifyIdentityForUoid(mIdentMan->identityForUoidOrDefault(0).uoid());
ident.setFullName(QStringLiteral("another"));
ident.setPrimaryEmailAddress(QStringLiteral("another@another.com"));
mIdentMan->newFromScratch(QStringLiteral("test1"));
mIdentMan->newFromScratch(QStringLiteral("test2"));
mIdentMan->newFromScratch(QStringLiteral("test3"));
mIdentMan->setAsDefault(ident.uoid());
mIdentMan->commit();
}
KMime::Message::Ptr MessageFactoryTest::loadMessage(const QString &filename)
{
QFile mailFile(filename);
if (!mailFile.open(QIODevice::ReadOnly)) {
return {};
}
const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
if (mailData.isEmpty()) {
return {};
}
KMime::Message::Ptr origMsg(new KMime::Message);
origMsg->setContent(mailData);
origMsg->parse();
return origMsg;
}
void MessageFactoryTest::testCreateReplyToAllWithUseSenderAndIdentityInCCAsync()
{
const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_with_identity_message_and_identity_in_cc.mbox"));
KMime::Message::Ptr msg = loadMessage(filename);
KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1"));
i1.setFullName(QStringLiteral("foo1"));
i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com"));
KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2"));
i2.setFullName(QStringLiteral("foo2"));
i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com"));
mIdentMan->commit();
MessageFactoryNG factory(msg, 0);
factory.setReplyStrategy(ReplyAll);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender");
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: foo1 <identity1@bla.com>\n"
"X-KMail-Identity: %1\n"
"Date: %2\n"
"Cc: blo <blo@blo.org>, bli <bli@bli.org>, blu <blu@blu.org>, bly <bly@bly.org>, Bla <identity1@bla.com>\n"
"To: Bla <identity1@bla.com>\n"
"Subject: Re: Plain Message Test\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n"
"%3")
.arg(i1.uoid()).arg(dateStr).arg(replyStr);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyToAllWithUseSenderAsync()
{
const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_with_identity_message.mbox"));
KMime::Message::Ptr msg = loadMessage(filename);
KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1"));
i1.setFullName(QStringLiteral("foo1"));
i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com"));
KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2"));
i2.setFullName(QStringLiteral("foo2"));
i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com"));
mIdentMan->commit();
MessageFactoryNG factory(msg, 0);
factory.setReplyStrategy(ReplyAll);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender");
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: another <another@another.com>\n"
"Date: %1\n"
"Cc: blo <blo@blo.org>, bli <bli@bli.org>, blu <blu@blu.org>, bly <bly@bly.org>\n"
"To: Bla <identity1@bla.com>\n"
"Subject: Re: Plain Message Test\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n"
"%2")
.arg(dateStr).arg(replyStr);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyToAllWithUseSenderByNoSameIdentitiesAsync()
{
const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/replyall_without_identity_message.mbox"));
KMime::Message::Ptr msg = loadMessage(filename);
KIdentityManagement::Identity &i1 = mIdentMan->modifyIdentityForName(QStringLiteral("test1"));
i1.setFullName(QStringLiteral("foo1"));
i1.setPrimaryEmailAddress(QStringLiteral("identity1@bla.com"));
KIdentityManagement::Identity &i2 = mIdentMan->modifyIdentityForName(QStringLiteral("test2"));
i2.setFullName(QStringLiteral("foo2"));
i2.setPrimaryEmailAddress(QStringLiteral("identity2@bla.com"));
mIdentMan->commit();
MessageFactoryNG factory(msg, 0);
factory.setReplyStrategy(ReplyAll);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr = QStringLiteral("> This is a mail for testing replyall and sender");
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: another <another@another.com>\n"
"Date: %1\n"
"Cc: blo <blo@blo.org>, bli <bli@bli.org>, blu <blu@blu.org>, bly <bly@bly.org>\n"
"To: Bla <bloblo@bla.com>\n"
"Subject: Re: Plain Message Test\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n"
"%2")
.arg(dateStr).arg(replyStr);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyToListAsync()
{
const QString filename(QStringLiteral(MAIL_DATA_DIR) + QStringLiteral("/list_message.mbox"));
KMime::Message::Ptr msg = loadMessage(filename);
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
factory.setReplyStrategy(ReplyList);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr = QString::fromLatin1(QByteArray("> This is a mail from ML"));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: another <another@another.com>\n"
"Date: %1\n"
"To: list@list.org\n"
"Subject: Re: Plain Message Test\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n"
"%2")
.arg(dateStr).arg(replyStr);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyToAuthorAsync()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
factory.setReplyStrategy(ReplyAuthor);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr
= QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n")));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
QString replyTo = reply.msg->inReplyTo()->asUnicodeString();
QString reference = reply.msg->references()->asUnicodeString();
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: another <another@another.com>\n"
"Date: %1\n"
"X-KMail-Transport: 0\n"
"To: me@me.me\n"
"References: %3\n"
"In-Reply-To: %2\n"
"Subject: Re: Test Email Subject\n"
"X-KMail-CursorPos: %5\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n"
"%4")
.arg(dateStr).arg(replyTo).arg(reference).arg(replyStr).arg(replyStr.length() - 1);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyAllWithMultiEmailsAsync()
{
KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
factory.setReplyStrategy(ReplyAll);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr
= QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n")));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject"));
QString replyTo = reply.msg->inReplyTo()->asUnicodeString();
QString reference = reply.msg->references()->asUnicodeString();
QString dateStr = reply.msg->date()->asUnicodeString();
QString ba = QString::fromLatin1("From: another <another@another.com>\n"
"Date: %1\n"
"X-KMail-Transport: 0\n"
"Cc: you@you.you, you2@you.you, cc@cc.cc, cc2@cc.cc\n"
"To: me@me.me\n"
"References: %3\n"
"In-Reply-To: %2\n"
"Subject: Re: Test Email Subject\nContent-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\nMIME-Version: 1.0\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: reply\n\n> All happy families are alike; each unhappy family is unhappy in its own way.")
.arg(dateStr).arg(replyTo).arg(reference);
QCOMPARE_OR_DIFF(reply.msg->encodedContent(), ba.toLatin1());
}
void MessageFactoryTest::testCreateReplyAllAsync()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.setIdentityManager(mIdentMan);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << reply.msg->body();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString replyStr
= QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n\n")));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Test Email Subject"));
QCOMPARE_OR_DIFF(reply.msg->body(), replyStr.toLatin1());
}
void MessageFactoryTest::testCreateReplyHtmlAsync()
{
KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("html_utf8_encoded.mbox"));
//qDebug() << "html message:" << msg->encodedContent();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.setIdentityManager(mIdentMan);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
//qDebug() << "html reply" << reply.msg->encodedContent();
QDateTime date = msg->date()->dateTime().toLocalTime();
QString datetime = QLocale().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat);
QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> encoded?\n\n")));
QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative"));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please"));
QCOMPARE(reply.msg->contents().count(), 2);
QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1());
TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(false);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 2);
reply = spy.at(1).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale::system().toString(date.time(), QLocale::LongFormat);
QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("text/plain"));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: reply to please"));
QCOMPARE(reply.msg->contents().count(), 0);
TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true);
}
void MessageFactoryTest::testCreateReplyUTF16Base64Async()
{
KMime::Message::Ptr msg = loadMessageFromFile(QStringLiteral("plain_utf16.mbox"));
TemplateParser::TemplateParserSettings::self()->setReplyUsingHtml(true);
// qDebug() << "plain base64 msg message:" << msg->encodedContent();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.setIdentityManager(mIdentMan);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
// qDebug() << "html reply" << reply.msg->encodedContent();
QDateTime date = msg->date()->dateTime().toLocalTime();
QString datetime = QLocale().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1Char(' ') + QLocale().toString(date.time(), QLocale::LongFormat);
QString replyStr = QString::fromLatin1(QByteArray(QByteArray("On ") + datetime.toLatin1() + QByteArray(" you wrote:\n> quote me please.\n\n")));
QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative"));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: asking for reply"));
QCOMPARE_OR_DIFF(reply.msg->contents().at(0)->body(), replyStr.toLatin1());
}
void MessageFactoryTest::testCreateForwardMultiEmailsAsync()
{
KMime::Message::Ptr msg = createPlainTestMessageWithMultiEmails();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone);
factory.createForwardAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
KMime::Message::Ptr fw = spy.at(0).at(0).value<KMime::Message::Ptr>();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString fwdMsg = QString::fromLatin1(
"From: another <another@another.com>\n"
"Date: %2\n"
"X-KMail-Transport: 0\n"
"MIME-Version: 1.0\n"
"Subject: Fwd: Test Email Subject\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: forward\n"
"\n"
"---------- Forwarded Message ----------\n"
"\n"
"Subject: Test Email Subject\n"
"Date: %1\n"
"From: me@me.me\n"
"To: you@you.you, you2@you.you\n"
"CC: cc@cc.cc, cc2@cc.cc\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.\n"
"-----------------------------------------");
fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString());
// qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1();
QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject"));
QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1());
}
void MessageFactoryTest::testCreateForwardAsync()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QSignalSpy spy(&factory, &MessageFactoryNG::createForwardDone);
factory.createForwardAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
KMime::Message::Ptr fw = spy.at(0).at(0).value<KMime::Message::Ptr>();
QDateTime date = msg->date()->dateTime();
QString datetime = QLocale::system().toString(date.date(), QLocale::LongFormat);
datetime += QLatin1String(", ") + QLocale::system().toString(date.time(), QLocale::LongFormat);
QString fwdMsg = QString::fromLatin1(
"From: another <another@another.com>\n"
"Date: %2\n"
"X-KMail-Transport: 0\n"
"MIME-Version: 1.0\n"
"Subject: Fwd: Test Email Subject\n"
"Content-Type: text/plain; charset=\"US-ASCII\"\n"
"Content-Transfer-Encoding: 8Bit\n"
"X-KMail-Link-Message: 0\n"
"X-KMail-Link-Type: forward\n"
"\n"
"---------- Forwarded Message ----------\n"
"\n"
"Subject: Test Email Subject\n"
"Date: %1\n"
"From: me@me.me\n"
"To: you@you.you\n"
"CC: cc@cc.cc\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.\n"
"-----------------------------------------");
fwdMsg = fwdMsg.arg(datetime).arg(fw->date()->asUnicodeString());
// qDebug() << "got:" << fw->encodedContent() << "against" << fwdMsg.toLatin1();
QCOMPARE(fw->subject()->asUnicodeString(), QStringLiteral("Fwd: Test Email Subject"));
QCOMPARE_OR_DIFF(fw->encodedContent(), fwdMsg.toLatin1());
}
void MessageFactoryTest::testCreateRedirectToAndCCAndBCC()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QString redirectTo = QStringLiteral("redir@redir.com");
QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com");
QString redirectBcc = QStringLiteral("redirbcc@redirbcc.com, redirbcc2@redirbcc.com");
KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc, redirectBcc);
QString datetime = rdir->date()->asUnicodeString();
// qDebug() << rdir->encodedContent();
-
- QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)"));
+ QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)"));
rx.indexIn(QString::fromLatin1(rdir->head()));
- QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)"));
+ QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)"));
rxmessageid.indexIn(QString::fromLatin1(rdir->head()));
//qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")";
QString baseline = QString::fromLatin1("From: me@me.me\n"
"Cc: cc@cc.cc\n"
"Bcc: bcc@bcc.bcc\n"
"Subject: Test Email Subject\n"
"Date: %1\n"
"X-KMail-Transport: 0\n"
"Message-ID: %2\n"
"Disposition-Notification-To: me@me.me\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 7Bit\n"
"Content-Type: text/plain; charset=\"us-ascii\"\n"
"Resent-Message-ID: %3\n"
"Resent-Date: %4\n"
"Resent-From: %5\n"
"To: you@you.you\n"
"Resent-To: redir@redir.com\n"
"Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n"
"Resent-Bcc: redirbcc@redirbcc.com, redirbcc2@redirbcc.com\n"
"X-KMail-Redirect-From: me@me.me (by way of another <another@another.com>)\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.");
baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another <another@another.com>"));
// qDebug() << baseline.toLatin1();
// qDebug() << "instead:" << rdir->encodedContent();
// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" );
QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject"));
QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1());
}
void MessageFactoryTest::testCreateRedirectToAndCC()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QString redirectTo = QStringLiteral("redir@redir.com");
QString redirectCc = QStringLiteral("redircc@redircc.com, redircc2@redircc.com");
KMime::Message::Ptr rdir = factory.createRedirect(redirectTo, redirectCc);
QString datetime = rdir->date()->asUnicodeString();
// qDebug() << rdir->encodedContent();
QString msgId = MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), QString());
- QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)"));
+ QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)"));
rx.indexIn(QString::fromLatin1(rdir->head()));
- QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)"));
+ QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)"));
rxmessageid.indexIn(QString::fromLatin1(rdir->head()));
//qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")";
QString baseline = QString::fromLatin1("From: me@me.me\n"
"Cc: cc@cc.cc\n"
"Bcc: bcc@bcc.bcc\n"
"Subject: Test Email Subject\n"
"Date: %1\n"
"X-KMail-Transport: 0\n"
"Message-ID: %2\n"
"Disposition-Notification-To: me@me.me\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 7Bit\n"
"Content-Type: text/plain; charset=\"us-ascii\"\n"
"Resent-Message-ID: %3\n"
"Resent-Date: %4\n"
"Resent-From: %5\n"
"To: you@you.you\n"
"Resent-To: redir@redir.com\n"
"Resent-Cc: redircc@redircc.com, redircc2@redircc.com\n"
"X-KMail-Redirect-From: me@me.me (by way of another <another@another.com>)\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.");
baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another <another@another.com>"));
// qDebug() << baseline.toLatin1();
// qDebug() << "instead:" << rdir->encodedContent();
// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" );
QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject"));
QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1());
}
void MessageFactoryTest::testCreateRedirect()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
QString redirectTo = QStringLiteral("redir@redir.com");
KMime::Message::Ptr rdir = factory.createRedirect(redirectTo);
QString datetime = rdir->date()->asUnicodeString();
// qDebug() << rdir->encodedContent();
- QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)"));
+ QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)"));
rx.indexIn(QString::fromLatin1(rdir->head()));
- QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)"));
+ QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)"));
rxmessageid.indexIn(QString::fromLatin1(rdir->head()));
//qWarning() << "messageid:" << rxmessageid.cap(1) << "(" << rdir->head() << ")";
QString baseline = QString::fromLatin1("From: me@me.me\n"
"Cc: cc@cc.cc\n"
"Bcc: bcc@bcc.bcc\n"
"Subject: Test Email Subject\n"
"Date: %1\n"
"X-KMail-Transport: 0\n"
"Message-ID: %2\n"
"Disposition-Notification-To: me@me.me\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 7Bit\n"
"Content-Type: text/plain; charset=\"us-ascii\"\n"
"Resent-Message-ID: %3\n"
"Resent-Date: %4\n"
"Resent-From: %5\n"
"To: you@you.you\n"
"Resent-To: redir@redir.com\n"
"X-KMail-Redirect-From: me@me.me (by way of another <another@another.com>)\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.");
baseline = baseline.arg(datetime).arg(rxmessageid.cap(1)).arg(rx.cap(1)).arg(datetime).arg(QStringLiteral("another <another@another.com>"));
// qDebug() << baseline.toLatin1();
// qDebug() << "instead:" << rdir->encodedContent();
// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" );
QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject"));
QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1());
}
void MessageFactoryTest::testCreateResend()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
KMime::Message::Ptr rdir = factory.createResend();
QString datetime = rdir->date()->asUnicodeString();
// qDebug() << msg->encodedContent();
- QRegExp rx(QString::fromLatin1("Resent-Message-ID: ([^\n]*)"));
+ QRegExp rx(QLatin1String("Resent-Message-ID: ([^\n]*)"));
rx.indexIn(QString::fromLatin1(rdir->head()));
- QRegExp rxmessageid(QString::fromLatin1("Message-ID: ([^\n]+)"));
+ QRegExp rxmessageid(QLatin1String("Message-ID: ([^\n]+)"));
rxmessageid.indexIn(QString::fromLatin1(rdir->head()));
QString baseline = QString::fromLatin1("From: me@me.me\n"
"To: %1\n"
"Cc: cc@cc.cc\n"
"Bcc: bcc@bcc.bcc\n"
"Subject: Test Email Subject\n"
"Date: %2\n"
"X-KMail-Transport: 0\n"
"Message-ID: %3\n"
"Disposition-Notification-To: me@me.me\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 7Bit\n"
"Content-Type: text/plain; charset=\"us-ascii\"\n"
"\n"
"All happy families are alike; each unhappy family is unhappy in its own way.");
baseline = baseline.arg(msg->to()->asUnicodeString()).arg(datetime).arg(rxmessageid.cap(1));
//qDebug() << baseline.toLatin1();
//qDebug() << "instead:" << rdir->encodedContent();
// QString fwdStr = QString::fromLatin1( "On " + datetime.toLatin1() + " you wrote:\n> All happy families are alike; each unhappy family is unhappy in its own way.\n" );
QCOMPARE(rdir->subject()->asUnicodeString(), QStringLiteral("Test Email Subject"));
QCOMPARE_OR_DIFF(rdir->encodedContent(), baseline.toLatin1());
}
void MessageFactoryTest::testCreateMDN()
{
KMime::Message::Ptr msg = createPlainTestMessage();
MessageFactoryNG factory(msg, 0);
factory.setIdentityManager(mIdentMan);
KMime::Message::Ptr mdn = factory.createMDN(KMime::MDN::AutomaticAction, KMime::MDN::Displayed, KMime::MDN::SentAutomatically);
QVERIFY(mdn.data());
//qDebug() << "mdn" << mdn->encodedContent();
QString mdnContent = QString::fromLatin1("The message sent on %1 to %2 with subject \"%3\" has been displayed. "
"This is no guarantee that the message has been read or understood.");
mdnContent = mdnContent.arg(KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch()))
.arg(msg->to()->asUnicodeString()).arg(msg->subject()->asUnicodeString());
//qDebug() << "comparing with:" << mdnContent;
QCOMPARE_OR_DIFF(Util::findTypeInMessage(mdn.data(), "multipart", "report")->contents().at(0)->body(),
mdnContent.toLatin1());
}
KMime::Message::Ptr MessageFactoryTest::createPlainTestMessage()
{
Composer *composer = new Composer;
composer->globalPart()->setFallbackCharsetEnabled(true);
- composer->infoPart()->setFrom(QString::fromLatin1("me@me.me"));
- composer->infoPart()->setTo(QStringList(QString::fromLatin1("you@you.you")));
- composer->infoPart()->setCc(QStringList(QString::fromLatin1("cc@cc.cc")));
- composer->infoPart()->setBcc(QStringList(QString::fromLatin1("bcc@bcc.bcc")));
- composer->textPart()->setWrappedPlainText(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ composer->infoPart()->setFrom(QStringLiteral("me@me.me"));
+ composer->infoPart()->setTo(QStringList(QLatin1String("you@you.you")));
+ composer->infoPart()->setCc(QStringList(QLatin1String("cc@cc.cc")));
+ composer->infoPart()->setBcc(QStringList(QLatin1String("bcc@bcc.bcc")));
+ composer->textPart()->setWrappedPlainText(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
composer->infoPart()->setSubject(QStringLiteral("Test Email Subject"));
composer->globalPart()->setMDNRequested(true);
composer->exec();
KMime::Message::Ptr message = KMime::Message::Ptr(composer->resultMessages().first());
delete composer;
MessageComposerSettings::self()->setPreferredCharsets(QStringList() << QStringLiteral("us-ascii") << QStringLiteral("iso-8859-1") << QStringLiteral("utf-8"));
return message;
}
KMime::Message::Ptr MessageFactoryTest::createPlainTestMessageWithMultiEmails()
{
Composer *composer = new Composer;
composer->globalPart()->setFallbackCharsetEnabled(true);
- composer->infoPart()->setFrom(QString::fromLatin1("me@me.me"));
+ composer->infoPart()->setFrom(QStringLiteral("me@me.me"));
composer->infoPart()->setTo(QStringList() << QStringLiteral("you@you.you") << QStringLiteral("you2@you.you"));
composer->infoPart()->setCc(QStringList() << QStringLiteral("cc@cc.cc") << QStringLiteral("cc2@cc.cc"));
composer->infoPart()->setBcc(QStringList() << QStringLiteral("bcc@bcc.bcc") << QStringLiteral("bcc2@bcc.bcc"));
- composer->textPart()->setWrappedPlainText(QString::fromLatin1("All happy families are alike; each unhappy family is unhappy in its own way."));
+ composer->textPart()->setWrappedPlainText(QStringLiteral("All happy families are alike; each unhappy family is unhappy in its own way."));
composer->infoPart()->setSubject(QStringLiteral("Test Email Subject"));
composer->globalPart()->setMDNRequested(true);
composer->exec();
KMime::Message::Ptr message = KMime::Message::Ptr(composer->resultMessages().first());
delete composer;
MessageComposerSettings::self()->setPreferredCharsets(QStringList() << QStringLiteral("us-ascii") << QStringLiteral("iso-8859-1") << QStringLiteral("utf-8"));
return message;
}
KMime::Message::Ptr MessageFactoryTest::loadMessageFromFile(const QString &filename)
{
QFile file(QLatin1String(QByteArray(MAIL_DATA_DIR "/" + filename.toLatin1())));
const bool opened = file.open(QIODevice::ReadOnly);
Q_ASSERT(opened);
Q_UNUSED(opened);
const QByteArray data = KMime::CRLFtoLF(file.readAll());
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
void MessageFactoryTest::test_multipartAlternative_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<int>("contentAt");
QTest::addColumn<QString>("selection");
QTest::addColumn<QString>("expected");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
- foreach (const QString &file, dir.entryList(QStringList(QLatin1String("plain_message.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks)) {
+ const QStringList lst = dir.entryList(QStringList(QStringLiteral("plain_message.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ for (const QString &file : lst) {
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 0 << ""
<<"> This *is* the *message* text *from* Sudhendu Kumar<dontspamme@yoohoo.com>\n"
"> \n"
"> --\n"
"> Thanks & Regards\n"
"> Sudhendu Kumar";
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 1 << "" << "<html><head></head><body>"
"<blockquote>This <i>is</i> the <b>message</b> text <u>from</u> Sudhendu Kumar&lt;dontspamme@yoohoo.com&gt;<br>"
"<br>-- <br>Thanks &amp; Regards<br>Sudhendu Kumar<br>\n</blockquote><br/></body></html>";
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 0 << "This *is* the *message* text *from*"
<<"> This *is* the *message* text *from*";
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << 1 << "This *is* the *message* text *from*"
<<"<html><head></head><body><blockquote>This *is* the *message* text *from*</blockquote><br/></body></html>";
}
}
void MessageFactoryTest::test_multipartAlternative()
{
QFETCH(QString, mailFileName);
QFETCH(int, contentAt);
QFETCH(QString, selection);
QFETCH(QString, expected);
KMime::Message::Ptr origMsg = loadMessage(mailFileName);
MessageFactoryNG factory(origMsg, 0);
factory.setIdentityManager(mIdentMan);
factory.setSelection(selection);
factory.setQuote(true);
factory.setReplyStrategy(ReplyAll);
TemplateParser::TemplateParserSettings::self()->setTemplateReplyAll(QStringLiteral("%QUOTE"));
QString str;
str = TemplateParser::TemplateParserSettings::self()->templateReplyAll();
factory.setTemplate(str);
QSignalSpy spy(&factory, &MessageFactoryNG::createReplyDone);
factory.createReplyAsync();
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
MessageFactoryNG::MessageReply reply = spy.at(0).at(0).value<MessageComposer::MessageFactoryNG::MessageReply>();
reply.replyAll = true;
QCOMPARE(reply.msg->contentType()->mimeType(), QByteArrayLiteral("multipart/alternative"));
QCOMPARE(reply.msg->subject()->asUnicodeString(), QLatin1String("Re: Plain Message Test"));
QCOMPARE(reply.msg->contents().at(contentAt)->encodedBody().data(), expected.toLatin1().data());
}
diff --git a/messagecomposer/autotests/messagefactoryngtest.h b/messagecomposer/autotests/messagefactoryngtest.h
index 79941a45..c76018c0 100644
--- a/messagecomposer/autotests/messagefactoryngtest.h
+++ b/messagecomposer/autotests/messagefactoryngtest.h
@@ -1,69 +1,69 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MESSAGEFACTORYNG_TEST_H
#define MESSAGEFACTORYNG_TEST_H
#include <QObject>
#include <kmime/kmime_message.h>
namespace KIdentityManagement {
class IdentityManager;
}
class MessageFactoryTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testCreateReplyAllAsync();
void testCreateReplyHtmlAsync();
void testCreateReplyUTF16Base64Async();
void testCreateForwardAsync();
void testCreateRedirect();
void testCreateResend();
void testCreateMDN();
void testCreateRedirectToAndCC();
void testCreateRedirectToAndCCAndBCC();
//MultiPart
void test_multipartAlternative_data();
void test_multipartAlternative();
void testCreateForwardMultiEmailsAsync();
void testCreateReplyToAuthorAsync();
void testCreateReplyAllWithMultiEmailsAsync();
void testCreateReplyToListAsync();
void testCreateReplyToAllWithUseSenderAsync();
void testCreateReplyToAllWithUseSenderByNoSameIdentitiesAsync();
void testCreateReplyToAllWithUseSenderAndIdentityInCCAsync();
void cleanupTestCase();
private:
KMime::Message::Ptr createPlainTestMessage();
KMime::Message::Ptr loadMessageFromFile(const QString &filename);
KMime::Message::Ptr createPlainTestMessageWithMultiEmails();
KMime::Message::Ptr loadMessage(const QString &filename);
KIdentityManagement::IdentityManager *mIdentMan = nullptr;
};
#endif
diff --git a/messagecomposer/autotests/multipartjobtest.cpp b/messagecomposer/autotests/multipartjobtest.cpp
index 7b75d2bd..6fff885f 100644
--- a/messagecomposer/autotests/multipartjobtest.cpp
+++ b/messagecomposer/autotests/multipartjobtest.cpp
@@ -1,103 +1,103 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "multipartjobtest.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/SinglepartJob>
#include <MessageComposer/MultipartJob>
using namespace MessageComposer;
QTEST_MAIN(MultipartJobTest)
void MultipartJobTest::testMultipartMixed()
{
Composer *composer = new Composer;
MultipartJob *mjob = new MultipartJob(composer);
mjob->setMultipartSubtype("mixed");
QByteArray data1("one");
QByteArray data2("two");
QByteArray type1("text/plain");
QByteArray type2("application/x-mors-ontologica");
{
SinglepartJob *cjob = new SinglepartJob(mjob);
cjob->setData(data1);
cjob->contentType()->setMimeType(type1);
}
{
SinglepartJob *cjob = new SinglepartJob(mjob);
cjob->setData(data2);
cjob->contentType()->setMimeType(type2);
}
QVERIFY(mjob->exec());
Content *result = mjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), QByteArray("multipart/mixed"));
QCOMPARE(result->contents().count(), 2);
{
Content *c = result->contents().at(0);
QCOMPARE(c->body(), data1);
QVERIFY(c->contentType(false));
QCOMPARE(c->contentType()->mimeType(), type1);
}
{
Content *c = result->contents().at(1);
QCOMPARE(c->body(), data2);
QVERIFY(c->contentType(false));
QCOMPARE(c->contentType()->mimeType(), type2);
}
}
void MultipartJobTest::test8BitPropagation()
{
// If a subpart is 8bit, its parent must be 8bit too.
Composer *composer = new Composer;
composer->globalPart()->set8BitAllowed(true);
MultipartJob *mjob = new MultipartJob(composer);
mjob->setMultipartSubtype("mixed");
MultipartJob *mjob2 = new MultipartJob(mjob);
mjob2->setMultipartSubtype("mixed");
SinglepartJob *cjob = new SinglepartJob(mjob2);
QByteArray data("time is so short and I'm sure there must be something more");
cjob->setData(data);
cjob->contentTransferEncoding()->setEncoding(Headers::CE8Bit);
QVERIFY(mjob->exec());
Content *content = mjob->content();
content->assemble();
qDebug() << content->encodedContent();
QVERIFY(content->contentTransferEncoding(false));
QCOMPARE(content->contentTransferEncoding()->encoding(), Headers::CE8Bit);
}
diff --git a/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.cpp b/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.cpp
index 745e3c3d..1320cae3 100644
--- a/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.cpp
+++ b/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.cpp
@@ -1,120 +1,120 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesendparamstest.h"
#include <MessageComposer/PluginEditorCheckBeforeSendParams>
#include <QTest>
PluginEditorCheckBeforeSendParamsTest::PluginEditorCheckBeforeSendParamsTest(QObject *parent)
: QObject(parent)
{
}
PluginEditorCheckBeforeSendParamsTest::~PluginEditorCheckBeforeSendParamsTest()
{
}
void PluginEditorCheckBeforeSendParamsTest::shouldHaveDefaultValues()
{
MessageComposer::PluginEditorCheckBeforeSendParams params;
QVERIFY(params.subject().isEmpty());
QVERIFY(params.plainText().isEmpty());
QCOMPARE(params.identity(), static_cast<uint>(0));
QCOMPARE(params.transportId(), -1);
QVERIFY(!params.isHtmlMail());
QVERIFY(params.bccAddresses().isEmpty());
QVERIFY(params.ccAddresses().isEmpty());
QVERIFY(params.toAddresses().isEmpty());
QVERIFY(params.defaultDomain().isEmpty());
QVERIFY(!params.hasAttachment());
}
void PluginEditorCheckBeforeSendParamsTest::shouldBeEqual()
{
MessageComposer::PluginEditorCheckBeforeSendParams params1;
const QString subject(QStringLiteral("foo"));
const QString plainText(QStringLiteral("bla"));
const QString defaultDomain(QStringLiteral("bli"));
bool isHmlMail = true;
bool hasAttachment = true;
int transportId = 5;
QString to(QStringLiteral("to"));
QString cc(QStringLiteral("cc"));
QString bcc(QStringLiteral("bcc"));
params1.setSubject(subject);
params1.setHtmlMail(isHmlMail);
params1.setPlainText(plainText);
params1.setDefaultDomain(defaultDomain);
params1.setHasAttachment(hasAttachment);
params1.setTransportId(transportId);
params1.setBccAddresses(bcc);
params1.setCcAddresses(cc);
params1.setToAddresses(to);
MessageComposer::PluginEditorCheckBeforeSendParams params2 = params1;
QVERIFY(params2 == params1);
QCOMPARE(params2.isHtmlMail(), isHmlMail);
QCOMPARE(params2.subject(), subject);
QCOMPARE(params2.plainText(), plainText);
QCOMPARE(params2.defaultDomain(), defaultDomain);
QCOMPARE(params2.hasAttachment(), hasAttachment);
QCOMPARE(params2.transportId(), transportId);
QCOMPARE(params2.bccAddresses(), bcc);
QCOMPARE(params2.ccAddresses(), cc);
QCOMPARE(params2.toAddresses(), to);
}
void PluginEditorCheckBeforeSendParamsTest::shouldAssignValue()
{
MessageComposer::PluginEditorCheckBeforeSendParams params1;
const QString subject(QStringLiteral("foo"));
const QString plainText(QStringLiteral("bla"));
const QString defaultDomain(QStringLiteral("bli"));
bool isHmlMail = true;
bool hasAttachment = true;
uint identity = 5;
int transportId = 6;
QString to(QStringLiteral("to"));
QString cc(QStringLiteral("cc"));
QString bcc(QStringLiteral("bcc"));
params1.setSubject(subject);
params1.setHtmlMail(isHmlMail);
params1.setPlainText(plainText);
params1.setIdentity(identity);
params1.setDefaultDomain(defaultDomain);
params1.setHasAttachment(hasAttachment);
params1.setTransportId(transportId);
params1.setBccAddresses(bcc);
params1.setCcAddresses(cc);
params1.setToAddresses(to);
QCOMPARE(params1.isHtmlMail(), isHmlMail);
QCOMPARE(params1.subject(), subject);
QCOMPARE(params1.identity(), identity);
QCOMPARE(params1.plainText(), plainText);
QCOMPARE(params1.defaultDomain(), defaultDomain);
QCOMPARE(params1.hasAttachment(), hasAttachment);
QCOMPARE(params1.transportId(), transportId);
QCOMPARE(params1.bccAddresses(), bcc);
QCOMPARE(params1.ccAddresses(), cc);
QCOMPARE(params1.toAddresses(), to);
}
QTEST_MAIN(PluginEditorCheckBeforeSendParamsTest)
diff --git a/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.h b/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.h
index 4292c0d0..c9d37d58 100644
--- a/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.h
+++ b/messagecomposer/autotests/plugineditorcheckbeforesendparamstest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESENDPARAMSTEST_H
#define PLUGINEDITORCHECKBEFORESENDPARAMSTEST_H
#include <QObject>
class PluginEditorCheckBeforeSendParamsTest : public QObject
{
Q_OBJECT
public:
explicit PluginEditorCheckBeforeSendParamsTest(QObject *parent = nullptr);
~PluginEditorCheckBeforeSendParamsTest();
private Q_SLOTS:
void shouldHaveDefaultValues();
void shouldBeEqual();
void shouldAssignValue();
};
#endif // PLUGINEDITORCHECKBEFORESENDPARAMSTEST_H
diff --git a/messagecomposer/autotests/setupenv.cpp b/messagecomposer/autotests/setupenv.cpp
index 13a52bd2..caa9cc9a 100644
--- a/messagecomposer/autotests/setupenv.cpp
+++ b/messagecomposer/autotests/setupenv.cpp
@@ -1,73 +1,73 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#include "setupenv.h"
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#include <gpgme++/keylistresult.h>
#include <QFile>
#include <QDir>
void MessageComposer::Test::setupEnv()
{
qputenv("LC_ALL", "C");
- qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1("/.qttest")).constData());
+ qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.qttest")).constData());
}
std::vector< GpgME::Key, std::allocator< GpgME::Key > > MessageComposer::Test::getKeys(bool smime)
{
QGpgME::KeyListJob *job = nullptr;
if (smime) {
const QGpgME::Protocol *const backend = QGpgME::smime();
Q_ASSERT(backend);
job = backend->keyListJob(false);
} else {
const QGpgME::Protocol *const backend = QGpgME::openpgp();
Q_ASSERT(backend);
job = backend->keyListJob(false);
}
Q_ASSERT(job);
std::vector< GpgME::Key > keys;
GpgME::KeyListResult res = job->exec(QStringList(), true, keys);
if (!smime) {
Q_ASSERT(keys.size() == 3);
}
Q_ASSERT(!res.error());
/*
qDebug() << "got private keys:" << keys.size();
for (std::vector< GpgME::Key >::iterator i = keys.begin(); i != keys.end(); ++i) {
qDebug() << "key isnull:" << i->isNull() << "isexpired:" << i->isExpired();
qDebug() << "key numuserIds:" << i->numUserIDs();
for (uint k = 0; k < i->numUserIDs(); ++k) {
qDebug() << "userIDs:" << i->userID(k).email();
}
}
*/
return keys;
}
diff --git a/messagecomposer/autotests/signencrypttest.cpp b/messagecomposer/autotests/signencrypttest.cpp
index 50e2c2f3..9a1852ae 100644
--- a/messagecomposer/autotests/signencrypttest.cpp
+++ b/messagecomposer/autotests/signencrypttest.cpp
@@ -1,148 +1,148 @@
/*
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "signencrypttest.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include "qtest_messagecomposer.h"
#include "cryptofunctions.h"
#include <kmime/kmime_content.h>
#include <Libkleo/Enum>
#include <kjob.h>
#include <MessageComposer/Composer>
#include <MessageComposer/EncryptJob>
#include <MessageComposer/MainTextJob>
#include <MessageComposer/SignJob>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/TextPart>
#include <MimeTreeParser/NodeHelper>
#include <setupenv.h>
#include <MessageCore/NodeHelper>
QTEST_MAIN(SignEncryptTest)
void SignEncryptTest::initTestCase()
{
MessageComposer::Test::setupEnv();
}
void SignEncryptTest::testContent()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
QVERIFY(eJob);
QList<QByteArray> charsets;
charsets << "us-ascii";
composer->globalPart()->setCharsets(charsets);
MessageComposer::TextPart *part = new MessageComposer::TextPart(this);
part->setWordWrappingEnabled(false);
part->setCleanPlainText(QStringLiteral("one flew over the cuckoo's nest"));
MessageComposer::MainTextJob *mainTextJob = new MessageComposer::MainTextJob(part, composer);
QVERIFY(composer);
QVERIFY(mainTextJob);
VERIFYEXEC(mainTextJob);
QStringList recipients;
recipients << QString::fromLocal8Bit("test@kolab.org");
sJob->setContent(mainTextJob->content());
sJob->setSigningKeys(keys);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
eJob->setRecipients(recipients);
eJob->setEncryptionKeys(keys);
eJob->appendSubjob(sJob);
VERIFYEXEC(eJob);
KMime::Content *result = eJob->content();
QVERIFY(result);
result->assemble();
qDebug() << "result:" << result->encodedContent();
ComposerTestUtil::verifySignatureAndEncryption(
result,
QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8(),
Kleo::OpenPGPMIMEFormat);
}
void SignEncryptTest::testHeaders()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
MessageComposer::EncryptJob *eJob = new MessageComposer::EncryptJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
QVERIFY(eJob);
QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8());
KMime::Content *content = new KMime::Content;
content->setBody(data);
QStringList recipients;
recipients << QString::fromLocal8Bit("test@kolab.org");
sJob->setContent(content);
sJob->setSigningKeys(keys);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
eJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
eJob->setRecipients(recipients);
eJob->setEncryptionKeys(keys);
eJob->appendSubjob(sJob);
VERIFYEXEC(eJob);
KMime::Content *result = eJob->content();
QVERIFY(result);
result->assemble();
QByteArray mimeType("multipart/encrypted");
QByteArray charset("ISO-8859-1");
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), mimeType);
QCOMPARE(result->contentType()->charset(), charset);
QCOMPARE(result->contentType()->parameter(QString::fromLocal8Bit("protocol")), QString::fromLocal8Bit("application/pgp-encrypted"));
QCOMPARE(result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit);
}
diff --git a/messagecomposer/autotests/signjobtest.cpp b/messagecomposer/autotests/signjobtest.cpp
index 017864ec..4ef75b6f 100644
--- a/messagecomposer/autotests/signjobtest.cpp
+++ b/messagecomposer/autotests/signjobtest.cpp
@@ -1,219 +1,219 @@
/*
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "signjobtest.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include "qtest_messagecomposer.h"
#include "cryptofunctions.h"
#include <kmime/kmime_content.h>
#include <Libkleo/Enum>
#include <kjob.h>
#include <MessageComposer/Composer>
#include <MessageComposer/SignJob>
#include <MessageComposer/TransparentJob>
#include <MessageCore/NodeHelper>
#include <setupenv.h>
#include <stdlib.h>
QTEST_MAIN(SignJobTest)
void SignJobTest::initTestCase()
{
MessageComposer::Test::setupEnv();
}
void SignJobTest::testContentDirect()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8());
KMime::Content *content = new KMime::Content;
content->setBody(data);
sJob->setContent(content);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
sJob->setSigningKeys(keys);
checkSignJob(sJob);
}
void SignJobTest::testContentChained()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8());
KMime::Content *content = new KMime::Content;
content->setBody(data);
MessageComposer::TransparentJob *tJob = new MessageComposer::TransparentJob;
tJob->setContent(content);
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
sJob->setSigningKeys(keys);
sJob->appendSubjob(tJob);
checkSignJob(sJob);
}
void SignJobTest::testHeaders()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
QByteArray data(QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8());
KMime::Content *content = new KMime::Content;
content->setBody(data);
sJob->setContent(content);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
sJob->setSigningKeys(keys);
VERIFYEXEC(sJob);
QByteArray mimeType("multipart/signed");
QByteArray charset("ISO-8859-1");
KMime::Content *result = sJob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), mimeType);
QCOMPARE(result->contentType()->charset(), charset);
QVERIFY(result->contentType()->parameter(QString::fromLocal8Bit("micalg")).startsWith(QLatin1String("pgp-sha"))); // sha1 or sha256, depending on GnuPG version
QCOMPARE(result->contentType()->parameter(QString::fromLocal8Bit("protocol")), QString::fromLocal8Bit("application/pgp-signature"));
QCOMPARE(result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit);
}
void SignJobTest::testRecommentationRFC3156()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
QString data = QStringLiteral("=2D Magic foo\nFrom test\n\n-- quaak\nOhno");
KMime::Headers::contentEncoding cte = KMime::Headers::CEquPr;
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
KMime::Content *content = new KMime::Content;
content->setBody(data.toUtf8());
sJob->setContent(content);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
sJob->setSigningKeys(keys);
VERIFYEXEC(sJob);
KMime::Content *result = sJob->content();
result->assemble();
qDebug() << result->encodedContent();
QByteArray body = MessageCore::NodeHelper::firstChild(result)->body();
QCOMPARE(QString::fromUtf8(body),
QStringLiteral("=3D2D Magic foo\n=46rom test\n\n=2D- quaak\nOhno"));
ComposerTestUtil::verify(true, false, result, data.toUtf8(),
Kleo::OpenPGPMIMEFormat, cte);
}
void SignJobTest::testMixedContent()
{
std::vector< GpgME::Key > keys = MessageComposer::Test::getKeys();
- QString data = QString::fromUtf8("=2D Magic foo\nFrom test\n\n-- quaak\nOhno");
+ QString data = QStringLiteral("=2D Magic foo\nFrom test\n\n-- quaak\nOhno");
MessageComposer::Composer *composer = new MessageComposer::Composer;
MessageComposer::SignJob *sJob = new MessageComposer::SignJob(composer);
QVERIFY(composer);
QVERIFY(sJob);
KMime::Content *content = new KMime::Content;
content->contentType()->setMimeType(QByteArrayLiteral("multipart/mixed"));
content->contentType()->setBoundary(KMime::multiPartBoundary());
KMime::Content *subcontent = new KMime::Content;
subcontent->contentType()->setMimeType(QByteArrayLiteral("text/plain"));
subcontent->setBody(data.toUtf8());
KMime::Content *attachment = new KMime::Content;
attachment->contentType()->setMimeType(QByteArrayLiteral("text/plain"));
QByteArray attachmentData("an attachment");
attachment->setBody(attachmentData);
content->addContent(subcontent);
content->addContent(attachment);
content->assemble();
sJob->setContent(content);
sJob->setCryptoMessageFormat(Kleo::OpenPGPMIMEFormat);
sJob->setSigningKeys(keys);
VERIFYEXEC(sJob);
KMime::Content *result = sJob->content();
result->assemble();
qDebug() << result->encodedContent();
KMime::Content *firstChild = MessageCore::NodeHelper::firstChild(result);
QCOMPARE(result->contents().count(), 2);
QCOMPARE(firstChild->contents().count(), 2);
QCOMPARE(firstChild->body(), QByteArray());
QCOMPARE(firstChild->contentType()->mimeType(), QByteArrayLiteral("multipart/mixed"));
QCOMPARE(firstChild->contents()[0]->body(), data.toUtf8());
QCOMPARE(firstChild->contents()[1]->body(), attachmentData);
ComposerTestUtil::verify(true, false, result, data.toUtf8(),
Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit);
}
void SignJobTest::checkSignJob(MessageComposer::SignJob *sJob)
{
VERIFYEXEC(sJob);
KMime::Content *result = sJob->content();
Q_ASSERT(result);
result->assemble();
ComposerTestUtil::verifySignature(result, QString::fromLocal8Bit("one flew over the cuckoo's nest").toUtf8(), Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit);
}
diff --git a/messagecomposer/autotests/singlepartjobtest.cpp b/messagecomposer/autotests/singlepartjobtest.cpp
index 44de895c..d5756cb0 100644
--- a/messagecomposer/autotests/singlepartjobtest.cpp
+++ b/messagecomposer/autotests/singlepartjobtest.cpp
@@ -1,169 +1,169 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "singlepartjobtest.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
using namespace KMime;
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/SinglepartJob>
using namespace MessageComposer;
QTEST_MAIN(SinglepartJobTest)
void SinglepartJobTest::testContent()
{
Composer *composer = new Composer;
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("birds came flying from the underground");
cjob->setData(data);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->body(), data);
QVERIFY(result->contentDisposition(false) == nullptr); // Not created unless demanded.
QVERIFY(result->contentType(false) == nullptr); // Not created unless demanded.
QVERIFY(result->contentTransferEncoding(false)); // KMime gives it a default one (7bit).
}
void SinglepartJobTest::testContentDisposition()
{
Composer *composer = new Composer;
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("birds came flying from the underground");
cjob->setData(data);
QString filename = QStringLiteral("test_ăîşţâ.txt");
cjob->contentDisposition()->setDisposition(Headers::CDattachment);
cjob->contentDisposition()->setFilename(filename);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->body(), data);
QVERIFY(result->contentDisposition(false));
QCOMPARE(result->contentDisposition()->disposition(), Headers::CDattachment);
QCOMPARE(result->contentDisposition()->filename(), filename);
}
void SinglepartJobTest::testContentID()
{
Composer *composer = new Composer;
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("birds came flying from the underground");
QByteArray id("play@cold");
cjob->setData(data);
cjob->contentID()->setIdentifier(id);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->body(), data);
QVERIFY(result->header<Headers::ContentID>());
QCOMPARE(result->header<Headers::ContentID>()->identifier(), id);
}
void SinglepartJobTest::testContentType()
{
Composer *composer = new Composer;
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("birds came flying from the underground");
cjob->setData(data);
QByteArray mimeType("text/plain");
QByteArray charset("utf-8");
cjob->contentType()->setMimeType(mimeType);
cjob->contentType()->setCharset(charset);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QCOMPARE(result->body(), data);
QVERIFY(result->contentType(false));
QCOMPARE(result->contentType()->mimeType(), mimeType);
QCOMPARE(result->contentType()->charset(), charset);
}
void SinglepartJobTest::testContentTransferEncoding()
{
Composer *composer = new Composer;
QVERIFY(!composer->globalPart()->is8BitAllowed());
composer->globalPart()->setFallbackCharsetEnabled(true);
// 7bit if possible.
{
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("and the sun will set for you...");
cjob->setData(data);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentTransferEncoding(false));
QCOMPARE(result->contentTransferEncoding()->encoding(), Headers::CE7Bit);
QCOMPARE(result->body(), data);
}
// quoted-printable if text doesn't fit in 7bit.
{
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("some long text to make qupr more compact than base64 [ăîşţâ]"); // utf-8
cjob->setData(data);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentTransferEncoding(false));
QCOMPARE(result->contentTransferEncoding()->encoding(), Headers::CEquPr);
QCOMPARE(result->body(), data);
}
// base64 if it's shorter than quoted-printable
{
SinglepartJob *cjob = new SinglepartJob(composer);
QByteArray data("[ăîşţâ]"); // utf-8
cjob->setData(data);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentTransferEncoding(false));
QCOMPARE(result->contentTransferEncoding()->encoding(), Headers::CEbase64);
QCOMPARE(result->body(), data);
}
// 8bit if asked for and allowed.
{
composer->globalPart()->set8BitAllowed(true);
QByteArray data("[ăîşţâ]"); // utf-8
SinglepartJob *cjob = new SinglepartJob(composer);
cjob->setData(data);
QVERIFY(cjob->exec());
Content *result = cjob->content();
result->assemble();
qDebug() << result->encodedContent();
QVERIFY(result->contentTransferEncoding(false));
QCOMPARE(result->contentTransferEncoding()->encoding(), Headers::CE8Bit);
QCOMPARE(result->body(), data);
}
}
diff --git a/messagecomposer/autotests/skeletonmessagejobtest.cpp b/messagecomposer/autotests/skeletonmessagejobtest.cpp
index d28c36f4..1fe6f0fc 100644
--- a/messagecomposer/autotests/skeletonmessagejobtest.cpp
+++ b/messagecomposer/autotests/skeletonmessagejobtest.cpp
@@ -1,215 +1,215 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "skeletonmessagejobtest.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_message.h>
#include <MessageComposer/Composer>
#include <MessageComposer/InfoPart>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/SkeletonMessageJob>
using namespace MessageComposer;
QTEST_MAIN(SkeletonMessageJobTest)
void SkeletonMessageJobTest::testSubject_data()
{
QTest::addColumn<QString>("subject");
QTest::newRow("simple subject") << QStringLiteral("Antaa virrata sateen...");
QTest::newRow("non-ascii subject") << QStringLiteral("Muzicologă în bej, vând whisky și tequila, preț fix.");
// NOTE: This works fine, but shows ??s in the debug output. Why?
}
void SkeletonMessageJobTest::testSubject()
{
// An InfoPart should belong to a Composer, even if we don't use the composer itself.
Composer *composer = new Composer;
InfoPart *infoPart = composer->infoPart();
GlobalPart *globalPart = composer->globalPart();
Q_ASSERT(infoPart);
QFETCH(QString, subject);
//qDebug() << subject;
infoPart->setSubject(subject);
SkeletonMessageJob *sjob = new SkeletonMessageJob(infoPart, globalPart, composer);
QVERIFY(sjob->exec());
KMime::Message *message = sjob->message();
QVERIFY(message->subject(false));
qDebug() << message->subject()->asUnicodeString();
QCOMPARE(subject, message->subject()->asUnicodeString());
}
void SkeletonMessageJobTest::testAddresses_data()
{
QTest::addColumn<QString>("from");
- QTest::addColumn<QString>("replyto");
+ QTest::addColumn<QStringList>("replyto");
QTest::addColumn<QStringList>("to");
QTest::addColumn<QStringList>("cc");
QTest::addColumn<QStringList>("bcc");
{
QString from = QStringLiteral("one@example.com");
QStringList to;
to << QStringLiteral("two@example.com");
QStringList cc;
cc << QStringLiteral("three@example.com");
QStringList bcc;
bcc << QStringLiteral("four@example.com");
QString replyto = QStringLiteral("five@example.com");
- QTest::newRow("simple single address") << from << replyto << to << cc << bcc;
+ QTest::newRow("simple single address") << from << QStringList{replyto} << to << cc << bcc;
}
{
QString from = QStringLiteral("one@example.com");
QStringList to;
to << QStringLiteral("two@example.com");
to << QStringLiteral("two.two@example.com");
QStringList cc;
cc << QStringLiteral("three@example.com");
cc << QStringLiteral("three.three@example.com");
QStringList bcc;
bcc << QStringLiteral("four@example.com");
bcc << QStringLiteral("four.four@example.com");
QString replyto = QStringLiteral("five@example.com");
- QTest::newRow("simple multi address") << from << replyto << to << cc << bcc;
+ QTest::newRow("simple multi address") << from << QStringList{replyto} << to << cc << bcc;
}
{
QString from = QStringLiteral("Me <one@example.com>");
QStringList to;
to << QStringLiteral("You <two@example.com>");
to << QStringLiteral("two.two@example.com");
QStringList cc;
cc << QStringLiteral("And you <three@example.com>");
cc << QStringLiteral("three.three@example.com");
QStringList bcc;
bcc << QStringLiteral("And you too <four@example.com>");
bcc << QStringLiteral("four.four@example.com");
QString replyto = QStringLiteral("You over there <five@example.com>");
- QTest::newRow("named multi address") << from << replyto << to << cc << bcc;
+ QTest::newRow("named multi address") << from << QStringList{replyto} << to << cc << bcc;
}
{
QString from = QStringLiteral("Şîşkin <one@example.com>");
QStringList to;
to << QStringLiteral("Ivan Turbincă <two@example.com>");
to << QStringLiteral("two.two@example.com");
QStringList cc;
cc << QStringLiteral("Luceafărul <three@example.com>");
cc << QStringLiteral("three.three@example.com");
QStringList bcc;
bcc << QStringLiteral("Zburătorul <four@example.com>");
bcc << QStringLiteral("four.four@example.com");
QString replyto = QStringLiteral("Şîşzbură <five@example.com>");
- QTest::newRow("non-ascii named multi address") << from << replyto << to << cc << bcc;
+ QTest::newRow("non-ascii named multi address") << from << QStringList{replyto} << to << cc << bcc;
}
}
void SkeletonMessageJobTest::testAddresses()
{
// An InfoPart should belong to a Composer, even if we don't use the composer itself.
Composer *composer = new Composer;
InfoPart *infoPart = composer->infoPart();
GlobalPart *globalPart = composer->globalPart();
Q_ASSERT(infoPart);
QFETCH(QString, from);
- QFETCH(QString, replyto);
+ QFETCH(QStringList, replyto);
QFETCH(QStringList, to);
QFETCH(QStringList, cc);
QFETCH(QStringList, bcc);
infoPart->setFrom(from);
infoPart->setReplyTo(replyto);
infoPart->setTo(to);
infoPart->setCc(cc);
infoPart->setBcc(bcc);
SkeletonMessageJob *sjob = new SkeletonMessageJob(infoPart, globalPart, composer);
QVERIFY(sjob->exec());
KMime::Message *message = sjob->message();
{
QVERIFY(message->from(false));
qDebug() << "From:" << message->from()->asUnicodeString();
QCOMPARE(from, message->from()->asUnicodeString());
}
{
QVERIFY(message->replyTo(false));
qDebug() << "Reply-To:" << message->replyTo()->asUnicodeString();
- QCOMPARE(replyto, message->replyTo()->asUnicodeString());
+ QCOMPARE(replyto.join(QLatin1Char(',')), message->replyTo()->asUnicodeString());
}
{
QVERIFY(message->to(false));
qDebug() << "To:" << message->to()->asUnicodeString();
foreach (const auto &addr, message->to()->mailboxes()) {
qDebug() << addr.prettyAddress();
QVERIFY(to.contains(addr.prettyAddress()));
to.removeOne(addr.prettyAddress());
}
QVERIFY(to.isEmpty());
}
{
QVERIFY(message->cc(false));
qDebug() << "Cc:" << message->cc()->asUnicodeString();
foreach (const auto &addr, message->cc()->mailboxes()) {
qDebug() << addr.prettyAddress();
QVERIFY(cc.contains(addr.prettyAddress()));
cc.removeOne(addr.prettyAddress());
}
QVERIFY(cc.isEmpty());
}
{
QVERIFY(message->bcc(false));
qDebug() << "Bcc:" << message->bcc()->asUnicodeString();
foreach (const auto &addr, message->bcc()->mailboxes()) {
qDebug() << addr.prettyAddress();
QVERIFY(bcc.contains(addr.prettyAddress()));
bcc.removeOne(addr.prettyAddress());
}
QVERIFY(bcc.isEmpty());
}
}
void SkeletonMessageJobTest::testMessageID()
{
Composer *composer = new Composer();
InfoPart *infoPart = composer->infoPart();
GlobalPart *globalPart = composer->globalPart();
Q_ASSERT(infoPart);
SkeletonMessageJob *sjob = new SkeletonMessageJob(infoPart, globalPart, composer);
QVERIFY(sjob->exec());
KMime::Message *message = sjob->message();
QVERIFY(message->messageID(false));
QVERIFY(!message->messageID(false)->isEmpty());
delete sjob;
delete composer;
}
diff --git a/messagecomposer/autotests/textparttest.cpp b/messagecomposer/autotests/textparttest.cpp
index 9f63f151..fe3036c0 100644
--- a/messagecomposer/autotests/textparttest.cpp
+++ b/messagecomposer/autotests/textparttest.cpp
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "textparttest.h"
-#include <qtest.h>
+#include <QTest>
#include <../src/part/textpart.h>
TextPartTest::TextPartTest(QObject *parent)
: QObject(parent)
{
}
TextPartTest::~TextPartTest()
{
}
void TextPartTest::shouldHaveDefaultValue()
{
MessageComposer::TextPart textpart;
QVERIFY(textpart.warnBadCharset());
QVERIFY(textpart.isWordWrappingEnabled());
QVERIFY(!textpart.hasEmbeddedImages());
QVERIFY(!textpart.isHtmlUsed());
}
QTEST_APPLESS_MAIN(TextPartTest)
diff --git a/messagecomposer/autotests/textparttest.h b/messagecomposer/autotests/textparttest.h
index f4df86a6..2c7bf30e 100644
--- a/messagecomposer/autotests/textparttest.h
+++ b/messagecomposer/autotests/textparttest.h
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef TEXTPARTTEST_H
#define TEXTPARTTEST_H
#include <QObject>
class TextPartTest : public QObject
{
Q_OBJECT
public:
explicit TextPartTest(QObject *parent = nullptr);
~TextPartTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // TEXTPARTTEST_H
diff --git a/messagecomposer/autotests/utiltest.cpp b/messagecomposer/autotests/utiltest.cpp
index e7500437..fa96edbe 100644
--- a/messagecomposer/autotests/utiltest.cpp
+++ b/messagecomposer/autotests/utiltest.cpp
@@ -1,108 +1,108 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "utiltest.h"
#include <QTextDocument>
-#include <qtest.h>
+#include <QTest>
#include <QStandardPaths>
#include <MessageComposer/Util>
using namespace MessageComposer;
-QTEST_APPLESS_MAIN(UtilTest)
+QTEST_MAIN(UtilTest)
UtilTest::UtilTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
void UtilTest::testSelectCharset()
{
// Pick a charset that actually works.
{
QString text = QStringLiteral("text 123 ăîşţâ");
QList<QByteArray> charsets;
charsets << "us-ascii";
charsets << "iso-8859-1";
charsets << "iso-8859-2"; // This one works.
QByteArray choice = Util::selectCharset(charsets, text);
QCOMPARE(choice, QByteArrayLiteral("iso-8859-2"));
}
// Pick as simple a charset as possible.
{
QString text = QStringLiteral("plain English text");
QList<QByteArray> charsets;
charsets << "us-ascii"; // This one works.
charsets << "iso-8859-1";
charsets << "utf-8";
QByteArray choice = Util::selectCharset(charsets, text);
QCOMPARE(choice, QByteArrayLiteral("us-ascii"));
}
// Return empty charset if none works.
{
QString text = QStringLiteral("text 123 ăîşţâ");
QList<QByteArray> charsets;
QByteArray choice = Util::selectCharset(charsets, text);
QVERIFY(choice.isEmpty());
charsets << "us-ascii";
charsets << "iso-8859-1";
choice = Util::selectCharset(charsets, text);
QVERIFY(choice.isEmpty());
}
}
void UtilTest::shouldTestHasMissingAttachment_data()
{
QTest::addColumn<QStringList>("attachmentKeywords");
QTest::addColumn<QString>("subject");
QTest::addColumn<QString>("body");
QTest::addColumn<bool>("hasMissingAttachment");
QStringList lstDefaultAttachement{
QStringLiteral("attachment"), QStringLiteral("att2")
};
QTest::newRow("emptybody") << lstDefaultAttachement << QStringLiteral("foo") << QString() << false;
QTest::newRow("emptybodyandsubject") << lstDefaultAttachement << QString() << QString() << false;
QTest::newRow("subjectwithattachmentkeyword") << lstDefaultAttachement << QStringLiteral("attachment foo") << QString() << true;
QTest::newRow("subjectwithattachmentkeywordonly") << lstDefaultAttachement << QStringLiteral("attachment") << QString() << true;
QTest::newRow("subjectwithattachmentkeywordbutreply") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QString() << false;
QTest::newRow("subjectwithattachmentkeywordbutreplywithoutattachement") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("foo bla\n bli") << false;
QTest::newRow("subjectwithattachmentkeywordbutreplywithattachement") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("foo bla\n attachment:") << true;
QTest::newRow("subjectwithattachmentkeywordbutreplywithattachement2") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("foo bla att2\n bli:") << true;
QTest::newRow("excludequotedstr") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("> foo bla att2\n bli:") << false;
QTest::newRow("excludequotedstr2") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("> foo bla att2\n att2:") << true;
QTest::newRow("excludequotedstr3") << lstDefaultAttachement << QStringLiteral("Re: attachment") << QStringLiteral("| foo bla att2\n att2:") << true;
QTest::newRow("subjectwithattachmentkeywordbutforward") << lstDefaultAttachement << QStringLiteral("Fwd: attachment") << QString() << false;
}
void UtilTest::shouldTestHasMissingAttachment()
{
QFETCH(QStringList, attachmentKeywords);
QFETCH(QString, subject);
QFETCH(QString, body);
QFETCH(bool, hasMissingAttachment);
QTextDocument doc(body);
QCOMPARE(Util::hasMissingAttachments(attachmentKeywords, &doc, subject), hasMissingAttachment);
}
diff --git a/messagecomposer/autotests/utiltest.h b/messagecomposer/autotests/utiltest.h
index 50d32fe8..14ca82cd 100644
--- a/messagecomposer/autotests/utiltest.h
+++ b/messagecomposer/autotests/utiltest.h
@@ -1,37 +1,37 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef UTILTEST_H
#define UTILTEST_H
#include <QObject>
class UtilTest : public QObject
{
Q_OBJECT
public:
explicit UtilTest(QObject *parent = nullptr);
private Q_SLOTS:
void testSelectCharset();
void shouldTestHasMissingAttachment();
void shouldTestHasMissingAttachment_data();
};
#endif
diff --git a/messagecomposer/metainfo.yaml b/messagecomposer/metainfo.yaml
index 045de4d5..123b869c 100644
--- a/messagecomposer/metainfo.yaml
+++ b/messagecomposer/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer: mlaurent
description: MessageComposer Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: MessageComposer
cmake: "KF5::MessageComposer"
- cmakename: KF5MessageComposer
+cmakename: KF5MessageComposer
diff --git a/messagecomposer/src/CMakeLists.txt b/messagecomposer/src/CMakeLists.txt
index 0fa75142..63c83e19 100644
--- a/messagecomposer/src/CMakeLists.txt
+++ b/messagecomposer/src/CMakeLists.txt
@@ -1,447 +1,461 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libmessagecomposer\")
if(BUILD_TESTING)
add_subdirectory( imagescaling/autotests )
add_subdirectory( imagescaling/tests )
add_subdirectory( composer-ng/autotests )
endif()
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/part)
set( messagecomposer_job_src
job/jobbase.cpp
job/contentjobbase.cpp
job/attachmentjob.cpp
job/singlepartjob.cpp
job/multipartjob.cpp
job/maintextjob.cpp
job/signjob.cpp
job/encryptjob.cpp
job/signencryptjob.cpp
job/transparentjob.cpp
job/inserttextfilejob.cpp
job/skeletonmessagejob.cpp
job/aliasesexpandjob.cpp
job/emailaddressresolvejob.cpp
job/attachmentfrompublickeyjob.cpp
job/distributionlistexpandjob.cpp
job/savecontactpreferencejob.cpp
job/attachmentvcardfromaddressbookjob.cpp
job/attachmentclipboardjob.cpp
)
set( messagecomposer_composer_src
composer/composer.cpp
composer/signaturecontroller.cpp
composer/composerlineedit.cpp
composer/composerviewbase.cpp
composer/keyresolver.cpp
)
set( messagecomposer_recipient_src
recipient/recipientspicker.cpp
recipient/recipient.cpp
recipient/recipientline.cpp
recipient/recipientseditor.cpp
recipient/recipientseditorsidewidget.cpp
recipient/kwindowpositioner.cpp
recipient/distributionlistdialog.cpp
recipient/recipientseditormanager.cpp
recipient/recipientspickerwidget.cpp
)
set( messagecomposer_imagescaling_src
imagescaling/imagescaling.cpp
imagescaling/imagescalingwidget.cpp
imagescaling/imagescalingutils.cpp
imagescaling/imagescalingselectformat.cpp
)
set( messagecomposer_part_src
part/messagepart.cpp
part/globalpart.cpp
part/infopart.cpp
part/textpart.cpp
)
set( messagecomposer_attachment_src
attachment/attachmentcontrollerbase.cpp
attachment/attachmentmodel.cpp
)
set( messagecomposer_helper_src
helper/messagehelper.cpp
helper/messagefactoryng.cpp
helper/messagefactoryforwardjob.cpp
helper/messagefactoryreplyjob.cpp
)
set( messagecomposer_sender_src
sender/akonadisender.cpp
)
set(messagecomposer_followupreminder_SRCS
followupreminder/followupreminderselectdatedialog.cpp
followupreminder/followupremindercreatejob.cpp
)
set(messagecomposer_richtextcomposerng_SRCS
composer-ng/richtextcomposerng.cpp
composer-ng/richtextcomposersignatures.cpp
)
set(messagecomposer_plugineditor_SRCS
plugineditor/plugineditormanager.cpp
plugineditor/plugineditor.cpp
plugineditor/plugineditorinterface.cpp
plugineditor/pluginactiontype.cpp
)
set(messagecomposer_plugineditorcheckbeforesend_SRCS
plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp
plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp
plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp
plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp
plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp
)
set(messagecomposer_plugineditorinit_SRCS
plugineditorinit/plugineditorinitconfigurewidget.cpp
plugineditorinit/plugineditorinit.cpp
plugineditorinit/plugineditorinitmanager.cpp
plugineditorinit/plugineditorinitinterface.cpp
)
set(messagecomposer_plugineditorconverttext_SRCS
plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp
plugineditorconverttext/plugineditorconverttext.cpp
plugineditorconverttext/plugineditorconverttextmanager.cpp
plugineditorconverttext/plugineditorconverttextinterface.cpp
plugineditorconverttext/plugineditorconverterinitialdata.cpp
plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp
)
-
+set(messagecomposer_plugineditorgrammar_SRCS
+ plugineditorgrammar/plugineditorgrammarmanager.cpp
+ plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.cpp
+ )
set( messagecomposer_src
${messagecomposer_plugineditorconverttext_SRCS}
${messagecomposer_plugineditorinit_SRCS}
${messagecomposer_plugineditor_SRCS}
${messagecomposer_richtextcomposerng_SRCS}
${messagecomposer_part_src}
${messagecomposer_imagescaling_src}
${messagecomposer_job_src}
${messagecomposer_composer_src}
${messagecomposer_recipient_src}
${messagecomposer_attachment_src}
${messagecomposer_helper_src}
${messagecomposer_sender_src}
${messagecomposer_followupreminder_SRCS}
${messagecomposer_plugineditorcheckbeforesend_SRCS}
+ ${messagecomposer_plugineditorgrammar_SRCS}
utils/util.cpp
settings/messagecomposersettings.cpp
)
ki18n_wrap_ui(messagecomposer_src
imagescaling/ui/imagescalingwidget.ui
)
ecm_qt_declare_logging_category(messagecomposer_src HEADER messagecomposer_debug.h IDENTIFIER MESSAGECOMPOSER_LOG CATEGORY_NAME org.kde.pim.messagecomposer)
if(KDEPIM_ENTERPRISE_BUILD)
set(WARN_TOOMANY_RECIPIENTS_DEFAULT true)
set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT true)
else()
set(WARN_TOOMANY_RECIPIENTS_DEFAULT false)
set(ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT false)
endif()
configure_file(settings/messagecomposer.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/messagecomposer.kcfg)
kconfig_add_kcfg_files(messagecomposer_src
settings/messagecomposersettings_base.kcfgc
)
add_library( KF5MessageComposer ${messagecomposer_src} )
generate_export_header(KF5MessageComposer BASE_NAME messagecomposer)
add_library(KF5::MessageComposer ALIAS KF5MessageComposer)
target_link_libraries(KF5MessageComposer
PUBLIC
KF5::Mime
KF5::MessageCore
KF5::PimCommon
KF5::AkonadiCore
KF5::IdentityManagement
KF5::AkonadiMime
KF5::Libkleo
KF5::MessageViewer
PRIVATE
KF5::MailTransportAkonadi
KF5::PimTextEdit
KF5::TemplateParser
KF5::AkonadiWidgets
KF5::LibkdepimAkonadi
KF5::KIOCore
KF5::I18n
KF5::Completion # for KComboBox
KF5::KIOWidgets # for KIO::JobUiDelegate
KF5::KIOFileWidgets # for KEncodingDialog
KF5::XmlGui # for KActionCollection
KF5::SonnetUi
Grantlee5::TextDocument
KF5::CalendarCore # for KCalCore/Todo
KF5::SendLater
KF5::FollowupReminder
KF5::Archive
KF5::Contacts
KF5::SonnetCore
)
target_include_directories(KF5MessageComposer INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/MessageComposer/;${KDE_INSTALL_INCLUDEDIR_KF5}/messagecomposer>")
set_target_properties(KF5MessageComposer PROPERTIES
VERSION ${MESSAGECOMPOSER_VERSION_STRING}
SOVERSION ${MESSAGECOMPOSER_SOVERSION}
EXPORT_NAME MessageComposer
)
install(TARGETS
KF5MessageComposer
EXPORT KF5MessageComposerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK}
)
ecm_generate_headers(MessageComposer_Camelplugineditor_HEADERS
HEADER_NAMES
PluginEditor
PluginActionType
PluginEditorInterface
PluginEditorManager
REQUIRED_HEADERS MessageComposer_plugineditor_HEADERS
PREFIX MessageComposer
RELATIVE plugineditor
)
ecm_generate_headers(MessageComposer_Camelplugineditorinit_HEADERS
HEADER_NAMES
PluginEditorInitConfigureWidget
PluginEditorInit
PluginEditorInitManager
PluginEditorInitInterface
REQUIRED_HEADERS MessageComposer_plugineditorinit_HEADERS
PREFIX MessageComposer
RELATIVE plugineditorinit
)
ecm_generate_headers(MessageComposer_Camelplugineditorconverttext_HEADERS
HEADER_NAMES
PluginEditorConvertTextConfigureWidget
PluginEditorConvertText
PluginEditorConvertTextManager
PluginEditorConvertTextInterface
PluginEditorConverterInitialData
PluginEditorConverterBeforeConvertingData
REQUIRED_HEADERS MessageComposer_plugineditorconverttext_HEADERS
PREFIX MessageComposer
RELATIVE plugineditorconverttext
)
+ecm_generate_headers(MessageComposer_Camelplugineditorgrammar_HEADERS
+ HEADER_NAMES
+ PluginEditorGrammarManager
+ PluginEditorGrammarCustomToolsViewInterface
+ REQUIRED_HEADERS MessageComposer_plugineditorgrammar_HEADERS
+ PREFIX MessageComposer
+ RELATIVE plugineditorgrammar
+ )
+
ecm_generate_headers(MessageComposer_Camelplugineditorcheckbeforesend_HEADERS
HEADER_NAMES
PluginEditorCheckBeforeSend
PluginEditorCheckBeforeSendInterface
PluginEditorCheckBeforeSendManager
PluginEditorCheckBeforeSendConfigureWidget
PluginEditorCheckBeforeSendParams
REQUIRED_HEADERS MessageComposer_plugineditorcheckbeforesend_HEADERS
PREFIX MessageComposer
RELATIVE plugineditorcheckbeforesend
)
ecm_generate_headers(MessageComposer_Camelcaseattachement_HEADERS
HEADER_NAMES
AttachmentModel
AttachmentControllerBase
REQUIRED_HEADERS MessageComposer_attachement_HEADERS
PREFIX MessageComposer
RELATIVE attachment
)
ecm_generate_headers(MessageComposer_Camelcasecomposer_HEADERS
HEADER_NAMES
Composer
ComposerLineEdit
ComposerViewBase
SignatureController
REQUIRED_HEADERS MessageComposer_composer_HEADERS
PREFIX MessageComposer
RELATIVE composer
)
ecm_generate_headers(MessageComposer_Camelcasecomposerng_HEADERS
HEADER_NAMES
RichTextComposerNg
RichTextComposerSignatures
REQUIRED_HEADERS MessageComposer_composerng_HEADERS
PREFIX MessageComposer
RELATIVE composer-ng
)
ecm_generate_headers(MessageComposer_Camelcasesender_HEADERS
HEADER_NAMES
AkonadiSender
MessageSender
REQUIRED_HEADERS MessageComposer_sender_HEADERS
PREFIX MessageComposer
RELATIVE sender
)
ecm_generate_headers(MessageComposer_Camelcaseutils_HEADERS
HEADER_NAMES
Util
Kleo_Util
REQUIRED_HEADERS MessageComposer_utils_HEADERS
PREFIX MessageComposer
RELATIVE utils
)
ecm_generate_headers(MessageComposer_Camelcasehelper_HEADERS
HEADER_NAMES
MessageHelper
MessageFactoryNG
REQUIRED_HEADERS MessageComposer_helper_HEADERS
PREFIX MessageComposer
RELATIVE helper
)
ecm_generate_headers(MessageComposer_Camelcasesettings_HEADERS
HEADER_NAMES
MessageComposerSettings
REQUIRED_HEADERS MessageComposer_settings_HEADERS
PREFIX MessageComposer
RELATIVE settings
)
ecm_generate_headers(MessageComposer_Camelcasepart_HEADERS
HEADER_NAMES
TextPart
GlobalPart
InfoPart
MessagePart
REQUIRED_HEADERS MessageComposer_part_HEADERS
PREFIX MessageComposer
RELATIVE part
)
ecm_generate_headers(MessageComposer_Camelcasefollowupreminder_HEADERS
HEADER_NAMES
FollowupReminderCreateJob
FollowUpReminderSelectDateDialog
REQUIRED_HEADERS MessageComposer_followupreminder_HEADERS
PREFIX MessageComposer
RELATIVE followupreminder
)
ecm_generate_headers(MessageComposer_Camelcaserecipient_HEADERS
HEADER_NAMES
Recipient
RecipientsEditor
- DistributionListDialog
RecipientLine
REQUIRED_HEADERS MessageComposer_recipient_HEADERS
PREFIX MessageComposer
RELATIVE recipient
)
ecm_generate_headers(MessageComposer_Camelcaseimagescaling_HEADERS
HEADER_NAMES
ImageScalingWidget
REQUIRED_HEADERS MessageComposer_imagescaling_HEADERS
PREFIX MessageComposer
RELATIVE imagescaling
)
ecm_generate_headers(MessageComposer_Camelcasejob_HEADERS
HEADER_NAMES
JobBase
AbstractEncryptJob
ContentJobBase
InsertTextFileJob
AttachmentJob
SinglepartJob
MainTextJob
AttachmentFromPublicKeyJob
MultipartJob
EncryptJob
AttachmentVcardFromAddressBookJob
SignJob
TransparentJob
JobBase
AliasesExpandJob
SkeletonMessageJob
AttachmentClipBoardJob
REQUIRED_HEADERS MessageComposer_job_HEADERS
PREFIX MessageComposer
RELATIVE job
)
ecm_generate_pri_file(BASE_NAME MessageComposer
LIB_NAME KF5MessageComposer
DEPS "Mime MessageCore PimCommon Akonadi IdentityManagement AkonadiMime Libkleo" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageComposer
)
install(FILES
${MessageComposer_Camelplugineditorconverttext_HEADERS}
${MessageComposer_Camelplugineditorinit_HEADERS}
${MessageComposer_Camelplugineditorcheckbeforesend_HEADERS}
${MessageComposer_Camelcasecomposer_HEADERS}
${MessageComposer_Camelcasecomposerng_HEADERS}
${MessageComposer_Camelcasesender_HEADERS}
${MessageComposer_Camelcaseutils_HEADERS}
${MessageComposer_Camelcasehelper_HEADERS}
${MessageComposer_Camelcasesettings_HEADERS}
${MessageComposer_Camelcasepart_HEADERS}
${MessageComposer_Camelcasefollowupreminder_HEADERS}
${MessageComposer_Camelcaserecipient_HEADERS}
${MessageComposer_Camelcaseimagescaling_HEADERS}
${MessageComposer_Camelcasejob_HEADERS}
${MessageComposer_Camelcaseattachement_HEADERS}
${MessageComposer_Camelplugineditor_HEADERS}
+ ${MessageComposer_Camelplugineditorgrammar_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageComposer
COMPONENT Devel
)
install(FILES
${MessageComposer_HEADERS}
${MessageComposer_plugineditorconverttext_HEADERS}
${MessageComposer_plugineditorinit_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/messagecomposer_export.h
${CMAKE_CURRENT_BINARY_DIR}/messagecomposersettings_base.h
${CMAKE_CURRENT_BINARY_DIR}/messagecomposer_debug.h
${MessageComposer_composer_HEADERS}
${MessageComposer_composerng_HEADERS}
${MessageComposer_sender_HEADERS}
${MessageComposer_utils_HEADERS}
${MessageComposer_helper_HEADERS}
${MessageComposer_settings_HEADERS}
${MessageComposer_part_HEADERS}
${MessageComposer_followupreminder_HEADERS}
${MessageComposer_recipient_HEADERS}
${MessageComposer_imagescaling_HEADERS}
${MessageComposer_attachement_HEADERS}
${MessageComposer_job_HEADERS}
${MessageComposer_plugineditor_HEADERS}
${MessageComposer_plugineditorcheckbeforesend_HEADERS}
+ ${MessageComposer_plugineditorgrammar_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/messagecomposer
COMPONENT Devel
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
diff --git a/messagecomposer/src/attachment/attachmentcontrollerbase.cpp b/messagecomposer/src/attachment/attachmentcontrollerbase.cpp
index 4e69d88a..7a949a4b 100644
--- a/messagecomposer/src/attachment/attachmentcontrollerbase.cpp
+++ b/messagecomposer/src/attachment/attachmentcontrollerbase.cpp
@@ -1,1026 +1,1062 @@
/*
* This file is part of KMail.
* Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
*
* Parts based on KMail code by:
* Various authors.
*
* 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.
*/
#include "attachmentcontrollerbase.h"
#include <MessageComposer/AttachmentModel>
#include "MessageComposer/AttachmentJob"
#include "MessageComposer/AttachmentFromPublicKeyJob"
#include "MessageComposer/AttachmentVcardFromAddressBookJob"
#include "MessageComposer/AttachmentClipBoardJob"
#include "MessageComposer/Composer"
#include "MessageComposer/GlobalPart"
#include <MessageViewer/EditorWatcher>
#include <MessageViewer/MessageViewerUtil>
#include <MimeTreeParser/NodeHelper>
#include <MessageCore/StringUtil>
#include <AkonadiCore/itemfetchjob.h>
#include <kio/jobuidelegate.h>
#include <QIcon>
#include <QMenu>
#include <QPointer>
#include <QTreeView>
#include <QUrlQuery>
#include <QAction>
#include <KActionCollection>
#include "messagecomposer_debug.h"
#include <KEncodingFileDialog>
#include <KLocalizedString>
#include <KMessageBox>
#include <KMimeTypeTrader>
#include <QPushButton>
#include <KRun>
#include <QTemporaryFile>
#include <QMimeDatabase>
#include <KFileItemActions>
#include <KActionMenu>
#include <QGpgME/Protocol>
#include <Libkleo/KeySelectionDialog>
#include <MessageCore/AttachmentCompressJob>
-#include <MessageCore/AttachmentFromFolderJob>
-#include <MessageCore/AttachmentFromMimeContentJob>
-#include <MessageCore/AttachmentFromUrlJob>
+#include "attachment/attachmentfrommimecontentjob.h"
+#include "attachment/attachmentfromurljob.h"
#include <MessageCore/AttachmentPropertiesDialog>
-#include <MessageCore/AttachmentUpdateJob>
+#include "messagecore/attachmentupdatejob.h"
#include <MessageCore/AttachmentFromUrlUtils>
#include <settings/messagecomposersettings.h>
#include <KIO/Job>
#include <Akonadi/Contact/EmailAddressSelectionDialog>
#include <Akonadi/Contact/EmailAddressSelectionWidget>
#include <KMime/Content>
#include <QFileDialog>
using namespace MessageComposer;
using namespace MessageCore;
class Q_DECL_HIDDEN MessageComposer::AttachmentControllerBase::Private
{
public:
Private(AttachmentControllerBase *qq);
~Private();
void attachmentRemoved(const AttachmentPart::Ptr &part); // slot
void compressJobResult(KJob *job); // slot
void loadJobResult(KJob *job); // slot
void openSelectedAttachments(); // slot
void viewSelectedAttachments(); // slot
void editSelectedAttachment(); // slot
void editSelectedAttachmentWith(); // slot
void removeSelectedAttachments(); // slot
void saveSelectedAttachmentAs(); // slot
void selectedAttachmentProperties(); // slot
void editDone(MessageViewer::EditorWatcher *watcher); // slot
void attachPublicKeyJobResult(KJob *job); // slot
void slotAttachmentContentCreated(KJob *job); // slot
void addAttachmentPart(AttachmentPart::Ptr part);
void attachVcardFromAddressBook(KJob *job); //slot
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;
QHash<MessageViewer::EditorWatcher *, AttachmentPart::Ptr> editorPart;
QHash<MessageViewer::EditorWatcher *, QTemporaryFile *> editorTempFile;
KActionCollection *mActionCollection = nullptr;
QAction *attachPublicKeyAction = nullptr;
QAction *attachMyPublicKeyAction = nullptr;
QAction *openContextAction = nullptr;
QAction *viewContextAction = nullptr;
QAction *editContextAction = nullptr;
QAction *editWithContextAction = 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 *addOwnVcardAction = nullptr;
QAction *reloadAttachmentAction = nullptr;
QAction *attachVCardsAction = nullptr;
QAction *attachClipBoardAction = nullptr;
// If part p is compressed, uncompressedParts[p] is the uncompressed part.
QHash<AttachmentPart::Ptr, AttachmentPart::Ptr> uncompressedParts;
bool encryptEnabled = false;
bool signEnabled = false;
-
};
AttachmentControllerBase::Private::Private(AttachmentControllerBase *qq)
: q(qq)
{
}
AttachmentControllerBase::Private::~Private()
{
}
void AttachmentControllerBase::setSelectedParts(const AttachmentPart::List &selectedParts)
{
d->selectedParts = selectedParts;
const int selectedCount = selectedParts.count();
const bool enableEditAction = (selectedCount == 1)
&& (!selectedParts.first()->isMessageOrMessageCollection());
d->openContextAction->setEnabled(selectedCount > 0);
d->viewContextAction->setEnabled(selectedCount > 0);
d->editContextAction->setEnabled(enableEditAction);
d->editWithContextAction->setEnabled(enableEditAction);
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::Private::attachmentRemoved(const AttachmentPart::Ptr &part)
{
uncompressedParts.remove(part);
}
void AttachmentControllerBase::Private::compressJobResult(KJob *job)
{
if (job->error()) {
KMessageBox::sorry(wParent, job->errorString(), i18n("Failed to compress attachment"));
return;
}
Q_ASSERT(dynamic_cast<AttachmentCompressJob *>(job));
AttachmentCompressJob *ajob = static_cast<AttachmentCompressJob *>(job);
//AttachmentPart *originalPart = const_cast<AttachmentPart*>( ajob->originalPart() );
AttachmentPart::Ptr originalPart = ajob->originalPart();
AttachmentPart::Ptr compressedPart = ajob->compressedPart();
if (ajob->isCompressedPartLarger()) {
const int result = KMessageBox::questionYesNo(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::Yes) {
// The user has chosen to keep the uncompressed file.
return;
}
}
qCDebug(MESSAGECOMPOSER_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(MESSAGECOMPOSER_LOG) << "Compressed a zombie.";
}
}
void AttachmentControllerBase::Private::loadJobResult(KJob *job)
{
if (job->error()) {
KMessageBox::sorry(wParent, job->errorString(), i18n("Failed to attach file"));
return;
}
Q_ASSERT(dynamic_cast<AttachmentLoadJob *>(job));
AttachmentLoadJob *ajob = static_cast<AttachmentLoadJob *>(job);
AttachmentPart::Ptr part = ajob->attachmentPart();
q->addAttachment(part);
}
void AttachmentControllerBase::Private::openSelectedAttachments()
{
Q_ASSERT(selectedParts.count() >= 1);
for (const AttachmentPart::Ptr &part : qAsConst(selectedParts)) {
q->openAttachment(part);
}
}
void AttachmentControllerBase::Private::viewSelectedAttachments()
{
Q_ASSERT(selectedParts.count() >= 1);
for (const AttachmentPart::Ptr &part : qAsConst(selectedParts)) {
q->viewAttachment(part);
}
}
void AttachmentControllerBase::Private::editSelectedAttachment()
{
Q_ASSERT(selectedParts.count() == 1);
q->editAttachment(selectedParts.first(), MessageViewer::EditorWatcher::NoOpenWithDialog);
}
void AttachmentControllerBase::Private::editSelectedAttachmentWith()
{
Q_ASSERT(selectedParts.count() == 1);
q->editAttachment(selectedParts.first(), MessageViewer::EditorWatcher::OpenWithDialog);
}
void AttachmentControllerBase::Private::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) {
model->removeAttachment(part);
}
}
void AttachmentControllerBase::Private::saveSelectedAttachmentAs()
{
Q_ASSERT(selectedParts.count() == 1);
q->saveAttachmentAs(selectedParts.first());
}
void AttachmentControllerBase::Private::selectedAttachmentProperties()
{
Q_ASSERT(selectedParts.count() == 1);
q->attachmentProperties(selectedParts.first());
}
void AttachmentControllerBase::Private::reloadAttachment()
{
Q_ASSERT(selectedParts.count() == 1);
AttachmentUpdateJob *ajob = new AttachmentUpdateJob(selectedParts.first(), q);
- connect(ajob, &AttachmentUpdateJob::result, q, [this](KJob *job) { updateJobResult(job); });
+ connect(ajob, &AttachmentUpdateJob::result, q, [this](KJob *job) {
+ updateJobResult(job);
+ });
ajob->start();
}
void AttachmentControllerBase::Private::updateJobResult(KJob *job)
{
if (job->error()) {
KMessageBox::sorry(wParent, job->errorString(), i18n("Failed to reload attachment"));
return;
}
Q_ASSERT(dynamic_cast<AttachmentUpdateJob *>(job));
AttachmentUpdateJob *ajob = static_cast<AttachmentUpdateJob *>(job);
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(MESSAGECOMPOSER_LOG) << "Updated a zombie.";
}
}
void AttachmentControllerBase::Private::editDone(MessageViewer::EditorWatcher *watcher)
{
AttachmentPart::Ptr part = editorPart.take(watcher);
Q_ASSERT(part);
QTemporaryFile *tempFile = editorTempFile.take(watcher);
Q_ASSERT(tempFile);
if (watcher->fileChanged()) {
qCDebug(MESSAGECOMPOSER_LOG) << "File has changed.";
const QString name = watcher->url().path();
QFile file(name);
if (file.open(QIODevice::ReadOnly)) {
const QByteArray data = file.readAll();
part->setData(data);
model->updateAttachment(part);
}
}
delete tempFile;
// The watcher deletes itself.
}
void AttachmentControllerBase::Private::createOpenWithMenu(QMenu *topMenu, const AttachmentPart::Ptr &part)
{
const QString contentTypeStr = QString::fromLatin1(part->mimeType());
const KService::List offers = KFileItemActions::associatedApplications(QStringList() << contentTypeStr, QString());
if (!offers.isEmpty()) {
QMenu *menu = topMenu;
QActionGroup *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(QStringLiteral("openWith_submenu")); // for the unittest
topMenu->addMenu(menu);
}
//qCDebug(MESSAGECOMPOSER_LOG) << offers.count() << "offers" << topMenu << menu;
KService::List::ConstIterator it = offers.constBegin();
KService::List::ConstIterator end = offers.constEnd();
for (; it != end; ++it) {
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...");
}
QAction *openWithAct = new QAction(menu);
openWithAct->setText(openWithActionName);
QObject::connect(openWithAct, &QAction::triggered, q, &AttachmentControllerBase::slotOpenWithDialog);
menu->addAction(openWithAct);
} else { // no app offers -> Open With...
QAction *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 QString &fingerprint)
{
if (fingerprint.isEmpty() || !QGpgME::openpgp()) {
qCWarning(MESSAGECOMPOSER_LOG) << "Tried to export key with empty fingerprint, or no OpenPGP.";
return;
}
MessageComposer::AttachmentFromPublicKeyJob *ajob = new MessageComposer::AttachmentFromPublicKeyJob(fingerprint, this);
- connect(ajob, &AttachmentFromPublicKeyJob::result, this, [this](KJob *job) {d->attachPublicKeyJobResult(job); });
+ connect(ajob, &AttachmentFromPublicKeyJob::result, this, [this](KJob *job) {
+ d->attachPublicKeyJobResult(job);
+ });
ajob->start();
}
void AttachmentControllerBase::Private::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::sorry(wParent, job->errorString(), i18n("Failed to attach public key"));
return;
}
Q_ASSERT(dynamic_cast<MessageComposer::AttachmentFromPublicKeyJob *>(job));
MessageComposer::AttachmentFromPublicKeyJob *ajob = static_cast<MessageComposer::AttachmentFromPublicKeyJob *>(job);
AttachmentPart::Ptr part = ajob->attachmentPart();
q->addAttachment(part);
}
void AttachmentControllerBase::Private::attachVcardFromAddressBook(KJob *job)
{
if (job->error()) {
qCDebug(MESSAGECOMPOSER_LOG) << " Error during when get vCard";
KMessageBox::sorry(wParent, job->errorString(), i18n("Failed to attach vCard"));
return;
}
MessageComposer::AttachmentVcardFromAddressBookJob *ajob = static_cast<MessageComposer::AttachmentVcardFromAddressBookJob *>(job);
AttachmentPart::Ptr part = ajob->attachmentPart();
q->addAttachment(part);
}
void AttachmentControllerBase::Private::attachClipBoardElement(KJob *job)
{
if (job->error()) {
qCDebug(MESSAGECOMPOSER_LOG) << " Error during when get try to attach text from clipboard";
KMessageBox::sorry(wParent, job->errorString(), i18n("Failed to attach text from clipboard"));
return;
}
MessageComposer::AttachmentClipBoardJob *ajob = static_cast<MessageComposer::AttachmentClipBoardJob *>(job);
AttachmentPart::Ptr part = ajob->attachmentPart();
q->addAttachment(part);
}
static QTemporaryFile *dumpAttachmentToTempFile(const AttachmentPart::Ptr &part) // local
{
QTemporaryFile *file = new QTemporaryFile;
if (!file->open()) {
qCCritical(MESSAGECOMPOSER_LOG) << "Could not open tempfile" << file->fileName();
delete file;
return nullptr;
}
if (file->write(part->data()) == -1) {
qCCritical(MESSAGECOMPOSER_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 Private(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); });
+ 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()
{
delete d;
}
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->setDelayed(true);
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->addOwnVcardAction = new QAction(i18n("Attach Own vCard"), this);
d->addOwnVcardAction->setIconText(i18n("Own vCard"));
d->addOwnVcardAction->setCheckable(true);
connect(d->addOwnVcardAction, &QAction::triggered, this, &AttachmentControllerBase::addOwnVcard);
d->attachVCardsAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("&Attach vCards..."), this);
d->attachVCardsAction->setIconText(i18n("Attach"));
connect(d->attachVCardsAction, &QAction::triggered, this, &AttachmentControllerBase::showAttachVcard);
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->addOwnVcardAction);
d->attachmentMenu->addSeparator();
d->attachmentMenu->addAction(d->attachVCardsAction);
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(); });
+ 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(); });
+ 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(); });
+ connect(d->viewContextAction, &QAction::triggered, this, [this]() {
+ d->viewSelectedAttachments();
+ });
d->editContextAction = new QAction(i18nc("to edit", "Edit"), this);
- connect(d->editContextAction, &QAction::triggered, this, [this]() { d->editSelectedAttachment(); });
+ connect(d->editContextAction, &QAction::triggered, this, [this]() {
+ d->editSelectedAttachment();
+ });
d->editWithContextAction = new QAction(i18n("Edit With..."), this);
- connect(d->editWithContextAction, &QAction::triggered, this, [this]() { d->editSelectedAttachmentWith(); });
+ connect(d->editWithContextAction, &QAction::triggered, this, [this]() {
+ d->editSelectedAttachmentWith();
+ });
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(); });
+ this, [this]() {
+ d->saveSelectedAttachmentAs();
+ });
connect(d->saveAsContextAction, &QAction::triggered,
- this, [this]() { d->saveSelectedAttachmentAs(); });
+ 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(); });
+ this, [this]() {
+ d->selectedAttachmentProperties();
+ });
connect(d->propertiesContextAction, &QAction::triggered,
- this, [this]() { d->selectedAttachmentProperties(); });
+ 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();});
+ 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->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);
collection->addAction(QStringLiteral("attach_own_vcard"), d->addOwnVcardAction);
collection->addAction(QStringLiteral("attach_vcards"), d->attachVCardsAction);
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(MESSAGECOMPOSER_LOG) << "Compressing part.";
AttachmentCompressJob *ajob = new AttachmentCompressJob(part, this);
- connect(ajob, &AttachmentCompressJob::result, this, [this](KJob *job) { d->compressJobResult(job); });
+ connect(ajob, &AttachmentCompressJob::result, this, [this](KJob *job) {
+ d->compressJobResult(job);
+ });
ajob->start();
} else {
qCDebug(MESSAGECOMPOSER_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;
const bool enableEditAction = (numberOfParts == 1)
&& (!d->selectedParts.first()->isMessageOrMessageCollection());
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.first());
}
menu.addAction(d->openContextAction);
}
if (enableEditAction) {
menu.addAction(d->editWithContextAction);
menu.addAction(d->editContextAction);
}
if (numberOfParts > 0) {
menu.addAction(d->removeContextAction);
}
if (numberOfParts == 1) {
if (!d->selectedParts.first()->url().isEmpty()) {
menu.addAction(d->reloadAttachmentAction);
}
menu.addAction(d->saveAsContextAction);
menu.addSeparator();
menu.addAction(d->propertiesContextAction);
}
const int nbAttachment = d->model->rowCount();
if (nbAttachment != numberOfParts) {
menu.addSeparator();
menu.addAction(d->selectAllAction);
}
if (numberOfParts == 0) {
menu.addSeparator();
menu.addAction(d->addContextAction);
}
menu.exec(QCursor::pos());
}
void AttachmentControllerBase::slotOpenWithDialog()
{
openWith();
}
void AttachmentControllerBase::slotOpenWithAction(QAction *act)
{
KService::Ptr app = act->data().value<KService::Ptr>();
Q_ASSERT(d->selectedParts.count() == 1);
openWith(app);
}
void AttachmentControllerBase::openWith(const KService::Ptr &offer)
{
QTemporaryFile *tempFile = dumpAttachmentToTempFile(d->selectedParts.first());
if (!tempFile) {
KMessageBox::sorry(d->wParent,
i18n("KMail was unable to write the attachment to a temporary file."),
i18n("Unable to open attachment"));
return;
}
QList<QUrl> lst;
QUrl url = QUrl::fromLocalFile(tempFile->fileName());
lst.append(url);
tempFile->setPermissions(QFile::ReadUser);
bool result = false;
if (offer) {
result = KRun::runService(*offer, lst, d->wParent, false);
} else {
result = KRun::displayOpenWithDialog(lst, d->wParent, false);
}
if (!result) {
delete tempFile;
tempFile = nullptr;
} else {
// The file was opened. Delete it 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::sorry(d->wParent,
i18n("KMail was unable to write the attachment to a temporary file."),
i18n("Unable to open attachment"));
return;
}
tempFile->setPermissions(QFile::ReadUser);
KRun::RunFlags flags;
flags |= KRun::DeleteTemporaryFiles;
bool success = KRun::runUrl(QUrl::fromLocalFile(tempFile->fileName()),
QString::fromLatin1(part->mimeType()),
d->wParent,
flags);
if (!success) {
if (!KMimeTypeTrader::self()->preferredService(QString::fromLatin1(part->mimeType())).data()) {
// KRun showed an Open-With dialog, and it was canceled.
} else {
// KRun failed.
KMessageBox::sorry(d->wParent,
i18n("KMail was unable to open the attachment."),
i18n("Unable to open attachment"));
}
delete tempFile;
tempFile = nullptr;
} else {
// The file was opened. Delete it only when the composer is closed
// (and this object is destroyed).
tempFile->setParent(this); // Manages lifetime.
}
}
void AttachmentControllerBase::viewAttachment(const AttachmentPart::Ptr &part)
{
MessageComposer::Composer *composer = new MessageComposer::Composer;
composer->globalPart()->setFallbackCharsetEnabled(true);
MessageComposer::AttachmentJob *attachmentJob = new MessageComposer::AttachmentJob(part, composer);
- connect(attachmentJob, &AttachmentJob::result, this, [this](KJob *job) { d->slotAttachmentContentCreated(job); });
+ connect(attachmentJob, &AttachmentJob::result, this, [this](KJob *job) {
+ d->slotAttachmentContentCreated(job);
+ });
attachmentJob->start();
}
void AttachmentControllerBase::Private::slotAttachmentContentCreated(KJob *job)
{
if (!job->error()) {
const MessageComposer::AttachmentJob *const attachmentJob = dynamic_cast<MessageComposer::AttachmentJob *>(job);
Q_ASSERT(attachmentJob);
if (attachmentJob) {
Q_EMIT q->showAttachment(attachmentJob->content(), QByteArray());
}
} else {
// TODO: show warning to the user
qCWarning(MESSAGECOMPOSER_LOG) << "Error creating KMime::Content for attachment:" << job->errorText();
}
}
void AttachmentControllerBase::editAttachment(AttachmentPart::Ptr part, MessageViewer::EditorWatcher::OpenWithOption openWithOption)
{
QTemporaryFile *tempFile = dumpAttachmentToTempFile(part);
if (!tempFile) {
KMessageBox::sorry(d->wParent,
i18n("KMail was unable to write the attachment to a temporary file."),
i18n("Unable to edit attachment"));
return;
}
MessageViewer::EditorWatcher *watcher = new MessageViewer::EditorWatcher(
QUrl::fromLocalFile(tempFile->fileName()),
QString::fromLatin1(part->mimeType()), openWithOption,
this, d->wParent);
connect(watcher, &MessageViewer::EditorWatcher::editDone,
- this, [this](MessageViewer::EditorWatcher *watcher) { d->editDone(watcher);});
+ this, [this](MessageViewer::EditorWatcher *watcher) {
+ d->editDone(watcher);
+ });
switch (watcher->start()) {
case MessageViewer::EditorWatcher::NoError:
// The attachment is being edited.
// We will clean things up in editDone().
d->editorPart[ watcher ] = part;
d->editorTempFile[ watcher ] = tempFile;
// Delete the temp file if the composer is closed (and this object is destroyed).
tempFile->setParent(this); // Manages lifetime.
break;
case MessageViewer::EditorWatcher::CannotStart:
qCWarning(MESSAGECOMPOSER_LOG) << "Could not start EditorWatcher.";
Q_FALLTHROUGH();
case MessageViewer::EditorWatcher::Unknown:
case MessageViewer::EditorWatcher::Canceled:
case MessageViewer::EditorWatcher::NoServiceFound:
delete watcher;
delete tempFile;
break;
}
}
void AttachmentControllerBase::editAttachmentWith(const AttachmentPart::Ptr &part)
{
editAttachment(part, MessageViewer::EditorWatcher::OpenWithDialog);
}
void AttachmentControllerBase::saveAttachmentAs(const AttachmentPart::Ptr &part)
{
QString pname = part->name();
if (pname.isEmpty()) {
pname = i18n("unnamed");
}
QUrl url = QFileDialog::getSaveFileUrl(d->wParent, i18n("Save Attachment As"), QUrl::fromLocalFile(pname));
if (url.isEmpty()) {
qCDebug(MESSAGECOMPOSER_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)
{
KIO::StoredTransferJob *_job = qobject_cast<KIO::StoredTransferJob *>(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()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
== KMessageBox::Continue) {
byteArrayToRemoteFile(_job->data(), _job->url(), true);
}
} else {
KJobUiDelegate *ui = static_cast<KIO::Job *>(job)->uiDelegate();
ui->showErrorMessage();
}
}
}
void AttachmentControllerBase::attachmentProperties(const AttachmentPart::Ptr &part)
{
QPointer<AttachmentPropertiesDialog> 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::warningYesNo(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()),
i18nc("@title:window", "Attach directory"));
if (rc == KMessageBox::Yes) {
addAttachment(url);
}
}
void AttachmentControllerBase::attachFiles(const QList<QUrl> &urls, const QString &encoding)
{
const int numberOfFiles(urls.count());
for (int i = 0; i < numberOfFiles; ++i) {
auto url = urls.at(i);
QUrlQuery query(url);
query.addQueryItem(QStringLiteral("charset"), encoding);
url.setQuery(query.toString());
addAttachment(url);
}
}
void AttachmentControllerBase::showAttachVcard()
{
QPointer<Akonadi::EmailAddressSelectionDialog> dlg = new Akonadi::EmailAddressSelectionDialog(d->wParent);
dlg->view()->view()->setSelectionMode(QAbstractItemView::MultiSelection);
if (dlg->exec()) {
const Akonadi::EmailAddressSelection::List selectedEmail = dlg->selectedAddresses();
for (const Akonadi::EmailAddressSelection &selected : selectedEmail) {
MessageComposer::AttachmentVcardFromAddressBookJob *ajob = new MessageComposer::AttachmentVcardFromAddressBookJob(selected.item(), this);
- connect(ajob, &AttachmentVcardFromAddressBookJob::result, this, [this](KJob *job) {d->attachVcardFromAddressBook(job);});
+ connect(ajob, &AttachmentVcardFromAddressBookJob::result, this, [this](KJob *job) {
+ d->attachVcardFromAddressBook(job);
+ });
ajob->start();
}
}
delete dlg;
}
void AttachmentControllerBase::showAttachClipBoard()
{
MessageComposer::AttachmentClipBoardJob *job = new MessageComposer::AttachmentClipBoardJob(this);
- connect(job, &AttachmentClipBoardJob::result, this, [this](KJob *job) {d->attachClipBoardElement(job);});
+ 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);
}
}
void AttachmentControllerBase::showAddAttachmentFileDialog()
{
KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlsAndEncoding(QString(),
QUrl(),
QString(),
d->wParent,
i18nc("@title:window", "Attach File"));
if (!result.URLs.isEmpty()) {
const QString encoding = MimeTreeParser::NodeHelper::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;
MessageCore::StringUtil::setEncodingFile(urlWithEncoding, encoding);
QMimeDatabase mimeDb;
auto mimeType = mimeDb.mimeTypeForUrl(urlWithEncoding);
if (mimeType.name() == QLatin1String("inode/directory")) {
const int rc = KMessageBox::warningYesNo(d->wParent, i18n("Do you really want to attach this directory \"%1\"?", url.toLocalFile()),
i18nc("@title:window", "Attach directory"));
if (rc == KMessageBox::Yes) {
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::sorry(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); });
+ connect(ajob, &AttachmentFromUrlBaseJob::result, this, [this](KJob *job) {
+ d->loadJobResult(job);
+ });
ajob->start();
}
void AttachmentControllerBase::addAttachments(const QList<QUrl> &urls)
{
for (const QUrl &url : urls) {
addAttachment(url);
}
}
void AttachmentControllerBase::showAttachPublicKeyDialog()
{
using Kleo::KeySelectionDialog;
QPointer<KeySelectionDialog> dialog = new KeySelectionDialog(
i18n("Attach Public OpenPGP Key"),
i18n("Select the public key which should be attached."),
std::vector<GpgME::Key>(),
KeySelectionDialog::PublicKeys | KeySelectionDialog::OpenPGPKeys,
false /* no multi selection */,
false /* no remember choice box */,
d->wParent);
if (dialog->exec() == QDialog::Accepted) {
exportPublicKey(dialog->fingerprint());
}
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::setAttachOwnVcard(bool attachVcard)
{
d->addOwnVcardAction->setChecked(attachVcard);
}
bool AttachmentControllerBase::attachOwnVcard() const
{
return d->addOwnVcardAction->isChecked();
}
void AttachmentControllerBase::setIdentityHasOwnVcard(bool state)
{
d->addOwnVcardAction->setEnabled(state);
}
#include "moc_attachmentcontrollerbase.cpp"
diff --git a/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.cpp b/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.cpp
index 538ff49b..2bbe71c0 100644
--- a/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.cpp
+++ b/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.cpp
@@ -1,533 +1,533 @@
/*
- Copyright (C) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2016-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "richtextcomposerngtest.h"
#include "../richtextcomposerng.h"
#include "../richtextcomposersignatures.h"
#include <KPIMTextEdit/RichTextComposerControler>
#include <KActionCollection>
#include <PimCommon/AutoCorrection>
#include <QTest>
RichTextComposerNgTest::RichTextComposerNgTest(QObject *parent)
: QObject(parent)
{
}
RichTextComposerNgTest::~RichTextComposerNgTest()
{
}
void RichTextComposerNgTest::shouldHaveDefaultValue()
{
MessageComposer::RichTextComposerNg richtextComposerNg;
QVERIFY(richtextComposerNg.composerSignature());
QVERIFY(!richtextComposerNg.autocorrection());
QVERIFY(richtextComposerNg.composerControler());
QVERIFY(richtextComposerNg.composerControler()->composerImages());
}
void RichTextComposerNgTest::shouldForceAutoCorrection_data()
{
QTest::addColumn<QString>("original");
QTest::addColumn<QString>("expected");
//FIXME first char !
QTest::newRow("test1") << QStringLiteral("boo bla bli. foo faa") << QStringLiteral("boo bla bli. Foo faa");
QTest::newRow("test2") << QStringLiteral("boo bla bli.\nfoo faa") << QStringLiteral("boo bla bli.\nFoo faa");
QTest::newRow("test3") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nBoo bla bli.\nFoo faa");
}
void RichTextComposerNgTest::shouldForceAutoCorrection()
{
QFETCH(QString, original);
QFETCH(QString, expected);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.setPlainText(original);
PimCommon::AutoCorrection autocorrection;
autocorrection.setEnabledAutoCorrection(true);
autocorrection.setUppercaseFirstCharOfSentence(true);
richtextComposerNg.setAutocorrection(&autocorrection);
richtextComposerNg.forceAutoCorrection(false);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldForceAutoCorrectionWithSelection_data()
{
QTest::addColumn<QString>("original");
QTest::addColumn<QString>("expected");
QTest::addColumn<int>("selectionStart");
QTest::addColumn<int>("selectionEnd");
QTest::newRow("noselection") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nBoo bla bli.\nFoo faa") << 0 << 0;
QTest::newRow("noselection2") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nBoo bla bli.\nFoo faa") << 1 << 1;
QTest::newRow("fullselection") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nBoo bla bli.\nFoo faa") << 0 << 21;
QTest::newRow("selection1") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nBoo bla bli.\nfoo faa") << 0 << 10;
QTest::newRow("selection2") << QStringLiteral("\nboo bla bli.\nfoo faa") << QStringLiteral("\nboo bla bli.\nfoo faa") << 5 << 10;
QTest::newRow("twouppercase") << QStringLiteral("\nBOo bla bli.\nfoo FAa") << QStringLiteral("\nBoo bla bli.\nFoo Faa") << 0 << 21;
}
void RichTextComposerNgTest::shouldForceAutoCorrectionWithSelection()
{
QFETCH(QString, original);
QFETCH(QString, expected);
QFETCH(int, selectionStart);
QFETCH(int, selectionEnd);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.setPlainText(original);
QTextCursor cur = richtextComposerNg.textCursor();
cur.setPosition(selectionStart);
cur.setPosition(selectionEnd, QTextCursor::KeepAnchor);
richtextComposerNg.setTextCursor(cur);
PimCommon::AutoCorrection autocorrection;
autocorrection.setEnabledAutoCorrection(true);
autocorrection.setUppercaseFirstCharOfSentence(true);
autocorrection.setFixTwoUppercaseChars(true);
richtextComposerNg.setAutocorrection(&autocorrection);
richtextComposerNg.forceAutoCorrection(true);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldNotChangeSignatureWhenOriginalAndNewSignatureAreSame()
{
MessageComposer::RichTextComposerNg richtextComposerNg;
KIdentityManagement::Signature oldSignature;
const bool replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(oldSignature, oldSignature);
QVERIFY(!replaceSignature);
}
Q_DECLARE_METATYPE(KIdentityManagement::Signature::Placement)
Q_DECLARE_METATYPE(KIdentityManagement::Signature::AddedTextFlag)
Q_DECLARE_METATYPE(KIdentityManagement::Signature::Type)
void RichTextComposerNgTest::shouldAddSignature_data()
{
QTest::addColumn<QString>("original");
QTest::addColumn<QString>("expected");
QTest::addColumn<KIdentityManagement::Signature::Placement>("signatureplacement");
QTest::addColumn<KIdentityManagement::Signature::AddedTextFlag>("signatureaddtext");
QTest::newRow("startandaddseparator") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("-- \nSignaturefoo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("startandnewline") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("\n\nSignature\nfoo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("startandnothing") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("Signaturefoo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("endandaddseparator") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("foo bla, bli\nbb-- \nSignature")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("endandnewline") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("foo bla, bli\nbb\nSignature")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("endandnothing") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("foo bla, bli\nbbSignature")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
//TODO test "Add Cursor"
}
void RichTextComposerNgTest::shouldAddSignature()
{
QFETCH(QString, original);
QFETCH(QString, expected);
QFETCH(KIdentityManagement::Signature::Placement, signatureplacement);
QFETCH(KIdentityManagement::Signature::AddedTextFlag, signatureaddtext);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.setPlainText(original);
KIdentityManagement::Signature newSignature(QStringLiteral("Signature"));
newSignature.setEnabledSignature(true);
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldAddSpecificSignature_data()
{
QTest::addColumn<QString>("original");
QTest::addColumn<QString>("expected");
QTest::addColumn<KIdentityManagement::Signature::Placement>("signatureplacement");
QTest::addColumn<KIdentityManagement::Signature::AddedTextFlag>("signatureaddtext");
QTest::addColumn<bool>("enablesignature");
QTest::addColumn<bool>("signaturehtml");
QTest::newRow("startandaddseparatordisablenonhtml") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator
<< false << false;
QTest::newRow("startandaddseparatordisablehtml") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator
<< false << true;
QTest::newRow("startandaddseparatorenablehtml") << QStringLiteral("foo bla, bli\nbb") << QStringLiteral("-- \nSignaturefoo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator
<< true << true;
}
void RichTextComposerNgTest::shouldAddSpecificSignature()
{
QFETCH(QString, original);
QFETCH(QString, expected);
QFETCH(KIdentityManagement::Signature::Placement, signatureplacement);
QFETCH(KIdentityManagement::Signature::AddedTextFlag, signatureaddtext);
QFETCH(bool, enablesignature);
QFETCH(bool, signaturehtml);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.createActions(new KActionCollection(this));
richtextComposerNg.setPlainText(original);
KIdentityManagement::Signature newSignature(QStringLiteral("Signature"));
newSignature.setEnabledSignature(enablesignature);
newSignature.setInlinedHtml(signaturehtml);
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
//qDebug() << " clean html "<<richtextComposerNg.toCleanHtml();
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldReplaceSignature_data()
{
QTest::addColumn<QString>("signatureText");
QTest::addColumn<QString>("bodytext");
QTest::addColumn<KIdentityManagement::Signature::Placement>("signatureplacement");
QTest::addColumn<KIdentityManagement::Signature::AddedTextFlag>("signatureaddtext");
//Add Separator AtEnd
QTest::newRow("newlinebody") << QStringLiteral("Signature") << QStringLiteral("\n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("emptybody") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("spacebody") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("simple") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewline") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewlineatbegin") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewlineatbeginandend") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
//Add separator AtStart
QTest::newRow("newlinebody-2") << QStringLiteral("Signature") << QStringLiteral("\n")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("emptybody-2") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("spacebody-2") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("simple-2") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewline-2") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewlineatbegin-2") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("withnewlineatbeginandend-2") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
//Add nothing End
//FIXME
//QTest::newRow("newlinebody-3") << QStringLiteral("Signature") << QStringLiteral("\n")
// << KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("emptybody-3") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("spacebody-3") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("simple-3") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewline-3") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewlineatbegin-3") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewlineatbeginandend-3") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNothing;
//Add nothing Start
QTest::newRow("newlinebody-4") << QStringLiteral("Signature") << QStringLiteral("\n")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("emptybody-4") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("spacebody-4") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("simple-4") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewline-4") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewlineatbegin-4") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
QTest::newRow("withnewlineatbeginandend-4") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNothing;
//Add newline End
QTest::newRow("emptybody-5") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("newlinebody-5") << QStringLiteral("Signature") << QStringLiteral("\n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("spacebody-5") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("simple-5") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewline-5") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewlineatbegin-5") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewlineatbeginandend-5") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddNewLines;
#if 0 //Need to fix it.
//Add newline start
QTest::newRow("emptybody-6") << QStringLiteral("Signature") << QString()
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("newlinebody-6") << QStringLiteral("Signature") << QStringLiteral("\n")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("spacebody-6") << QStringLiteral("Signature") << QStringLiteral(" ")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("simple-6") << QStringLiteral("Signature") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewline-6") << QStringLiteral("Signature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewlineatbegin-6") << QStringLiteral("\nSignature\nnew line") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
QTest::newRow("withnewlineatbeginandend6") << QStringLiteral("\nSignature\nnew line\n") << QStringLiteral("foo bla, bli\nbb")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
#endif
}
void RichTextComposerNgTest::shouldReplaceSignature()
{
QFETCH(QString, signatureText);
QFETCH(QString, bodytext);
QFETCH(KIdentityManagement::Signature::Placement, signatureplacement);
QFETCH(KIdentityManagement::Signature::AddedTextFlag, signatureaddtext);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.createActions(new KActionCollection(this));
const QString original(bodytext);
richtextComposerNg.setPlainText(original);
KIdentityManagement::Signature newSignature(signatureText);
newSignature.setEnabledSignature(true);
newSignature.setInlinedHtml(false);
QString addText;
switch (signatureaddtext) {
case KIdentityManagement::Signature::AddNothing:
break;
case KIdentityManagement::Signature::AddSeparator:
addText = QStringLiteral("-- \n");
break;
case KIdentityManagement::Signature::AddNewLines:
addText = QStringLiteral("\n");
break;
}
QString expected;
switch (signatureplacement) {
case KIdentityManagement::Signature::Start:
expected = addText + signatureText + bodytext;
break;
case KIdentityManagement::Signature::End:
expected = bodytext + addText + signatureText;
break;
case KIdentityManagement::Signature::AtCursor:
break;
}
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
KIdentityManagement::Signature emptySignature;
bool replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(newSignature, emptySignature);
QVERIFY(replaceSignature);
QCOMPARE(richtextComposerNg.toPlainText(), original);
replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(emptySignature, newSignature);
QVERIFY(!replaceSignature);
//When signature is empty we can't replace it.=> we need to insertSignature
//=> insertSignature(signature, KIdentityManagement::Signature::End, addedText);
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldLoadSignatureFromFile_data()
{
QTest::addColumn<QString>("signatureFile");
QTest::addColumn<QString>("bodytext");
QTest::addColumn<KIdentityManagement::Signature::Placement>("signatureplacement");
QTest::addColumn<KIdentityManagement::Signature::AddedTextFlag>("signatureaddtext");
QTest::newRow("signature1") << QStringLiteral("signature1.txt") << QStringLiteral("\n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("signature2") << QStringLiteral("signature2.txt") << QStringLiteral("\n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
}
void RichTextComposerNgTest::shouldLoadSignatureFromFile()
{
QFETCH(QString, signatureFile);
QFETCH(QString, bodytext);
QFETCH(KIdentityManagement::Signature::Placement, signatureplacement);
QFETCH(KIdentityManagement::Signature::AddedTextFlag, signatureaddtext);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.createActions(new KActionCollection(this));
const QString original(bodytext);
richtextComposerNg.setPlainText(original);
KIdentityManagement::Signature newSignature(QLatin1String(SIGNATURE_DATA_DIR) + QLatin1Char('/') + signatureFile, false);
newSignature.setEnabledSignature(true);
newSignature.setInlinedHtml(false);
QString addText;
switch (signatureaddtext) {
case KIdentityManagement::Signature::AddNothing:
break;
case KIdentityManagement::Signature::AddSeparator:
addText = QStringLiteral("-- \n");
break;
case KIdentityManagement::Signature::AddNewLines:
addText = QStringLiteral("\n");
break;
}
QString expected;
QString signatureText = newSignature.toPlainText();
QVERIFY(!signatureText.isEmpty());
switch (signatureplacement) {
case KIdentityManagement::Signature::Start:
expected = addText + signatureText + bodytext;
break;
case KIdentityManagement::Signature::End:
expected = bodytext + addText + signatureText;
break;
case KIdentityManagement::Signature::AtCursor:
break;
}
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
KIdentityManagement::Signature emptySignature;
bool replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(newSignature, emptySignature);
QVERIFY(replaceSignature);
QCOMPARE(richtextComposerNg.toPlainText(), original);
replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(emptySignature, newSignature);
QVERIFY(!replaceSignature);
//When signature is empty we can't replace it.=> we need to insertSignature
//=> insertSignature(signature, KIdentityManagement::Signature::End, addedText);
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
void RichTextComposerNgTest::shouldLoadSignatureFromCommand_data()
{
QTest::addColumn<QString>("command");
QTest::addColumn<QString>("bodytext");
QTest::addColumn<KIdentityManagement::Signature::Placement>("signatureplacement");
QTest::addColumn<KIdentityManagement::Signature::AddedTextFlag>("signatureaddtext");
QTest::newRow("command1") << QStringLiteral("echo \"foo\"") << QStringLiteral("\n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("command2") << QStringLiteral("echo \"foo\"") << QStringLiteral("foo ddd \n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("command3") << QStringLiteral("echo \"foo\"") << QString()
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("command4") << QStringLiteral("echo \"foo\nsss\"") << QString()
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
QTest::newRow("command5") << QStringLiteral("echo \"foo\nsss\n\"") << QStringLiteral("foo ddd \n")
<< KIdentityManagement::Signature::End << KIdentityManagement::Signature::AddSeparator;
//Start
QTest::newRow("command6") << QStringLiteral("echo \"foo\nsss\n\"") << QStringLiteral("foo ddd \n")
<< KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddSeparator;
//Failed
//QTest::newRow("command7") << QStringLiteral("echo \"foo\nsss\n\"") << QStringLiteral("foo ddd \n")
// << KIdentityManagement::Signature::Start << KIdentityManagement::Signature::AddNewLines;
}
void RichTextComposerNgTest::shouldLoadSignatureFromCommand()
{
QFETCH(QString, command);
QFETCH(QString, bodytext);
QFETCH(KIdentityManagement::Signature::Placement, signatureplacement);
QFETCH(KIdentityManagement::Signature::AddedTextFlag, signatureaddtext);
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.createActions(new KActionCollection(this));
const QString original(bodytext);
richtextComposerNg.setPlainText(original);
KIdentityManagement::Signature newSignature(command, true);
newSignature.setEnabledSignature(true);
newSignature.setInlinedHtml(false);
QString addText;
switch (signatureaddtext) {
case KIdentityManagement::Signature::AddNothing:
break;
case KIdentityManagement::Signature::AddSeparator:
addText = QStringLiteral("-- \n");
break;
case KIdentityManagement::Signature::AddNewLines:
addText = QStringLiteral("\n");
break;
}
QString expected;
QString signatureText = newSignature.toPlainText();
QVERIFY(!signatureText.isEmpty());
switch (signatureplacement) {
case KIdentityManagement::Signature::Start:
expected = addText + signatureText + bodytext;
break;
case KIdentityManagement::Signature::End:
expected = bodytext + addText + signatureText;
break;
case KIdentityManagement::Signature::AtCursor:
break;
}
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
KIdentityManagement::Signature emptySignature;
bool replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(newSignature, emptySignature);
QVERIFY(replaceSignature);
QCOMPARE(richtextComposerNg.toPlainText(), original);
replaceSignature = richtextComposerNg.composerSignature()->replaceSignature(emptySignature, newSignature);
QVERIFY(!replaceSignature);
//When signature is empty we can't replace it.=> we need to insertSignature
//=> insertSignature(signature, KIdentityManagement::Signature::End, addedText);
richtextComposerNg.insertSignature(newSignature, signatureplacement, signatureaddtext);
QCOMPARE(richtextComposerNg.toPlainText(), expected);
}
QTEST_MAIN(RichTextComposerNgTest)
diff --git a/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.h b/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.h
index f9ded633..81ada624 100644
--- a/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.h
+++ b/messagecomposer/src/composer-ng/autotests/richtextcomposerngtest.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2016-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef RICHTEXTCOMPOSERNGTEST_H
#define RICHTEXTCOMPOSERNGTEST_H
#include <QObject>
class RichTextComposerNgTest : public QObject
{
Q_OBJECT
public:
explicit RichTextComposerNgTest(QObject *parent = nullptr);
~RichTextComposerNgTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldForceAutoCorrection_data();
void shouldForceAutoCorrection();
void shouldForceAutoCorrectionWithSelection_data();
void shouldForceAutoCorrectionWithSelection();
void shouldNotChangeSignatureWhenOriginalAndNewSignatureAreSame();
void shouldAddSignature_data();
void shouldAddSignature();
void shouldAddSpecificSignature_data();
void shouldAddSpecificSignature();
void shouldReplaceSignature_data();
void shouldReplaceSignature();
void shouldLoadSignatureFromFile_data();
void shouldLoadSignatureFromFile();
void shouldLoadSignatureFromCommand_data();
void shouldLoadSignatureFromCommand();
};
#endif // RICHTEXTCOMPOSERNGTEST_H
diff --git a/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.cpp b/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.cpp
index b929b00a..387078ff 100644
--- a/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.cpp
+++ b/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.cpp
@@ -1,99 +1,93 @@
/*
- Copyright (C) 2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2018-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
-
#include "richtextcomposersignaturestest.h"
#include "../richtextcomposerng.h"
#include "../richtextcomposersignatures.h"
#include <KActionCollection>
#include <QTest>
QTEST_MAIN(RichTextComposerSignaturesTest)
RichTextComposerSignaturesTest::RichTextComposerSignaturesTest(QObject *parent)
: QObject(parent)
{
-
}
void RichTextComposerSignaturesTest::shouldCleanSignature()
{
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.createActions(new KActionCollection(this));
MessageComposer::RichTextComposerSignatures *composerSignature = richtextComposerNg.composerSignature();
QVERIFY(composerSignature);
KIdentityManagement::Signature signature1;
signature1.setText(QStringLiteral("bla Bla\t"));
signature1.setEnabledSignature(true);
signature1.setInlinedHtml(false);
KIdentityManagement::Signature signature2(QStringLiteral("Signature"));
signature2.setText(QStringLiteral("Foo Bla\t"));
signature2.setEnabledSignature(true);
signature2.setInlinedHtml(false);
richtextComposerNg.insertSignature(signature1, KIdentityManagement::Signature::Start, KIdentityManagement::Signature::AddNewLines);
composerSignature->cleanWhitespace(signature2);
-
composerSignature->replaceSignature(signature1, signature2);
QCOMPARE(richtextComposerNg.toPlainText(), QStringLiteral("\n\nbla Bla "));
for (int i = 0; i < 10; i++) {
composerSignature->replaceSignature(signature2, signature1);
composerSignature->replaceSignature(signature1, signature2);
}
QCOMPARE(richtextComposerNg.toPlainText(), QStringLiteral("\n\nbla Bla "));
-
}
void RichTextComposerSignaturesTest::shouldReplaceSignatureWhenText()
{
MessageComposer::RichTextComposerNg richtextComposerNg;
richtextComposerNg.setText(QStringLiteral("foo\nbla \nfoo"));
richtextComposerNg.createActions(new KActionCollection(this));
MessageComposer::RichTextComposerSignatures *composerSignature = richtextComposerNg.composerSignature();
QVERIFY(composerSignature);
KIdentityManagement::Signature signature1;
signature1.setText(QStringLiteral("bla Bla\t"));
signature1.setEnabledSignature(true);
signature1.setInlinedHtml(false);
KIdentityManagement::Signature signature2(QStringLiteral("Signature"));
signature2.setText(QStringLiteral("Foo Bla\t"));
signature2.setEnabledSignature(true);
signature2.setInlinedHtml(false);
richtextComposerNg.insertSignature(signature1, KIdentityManagement::Signature::End, KIdentityManagement::Signature::AddSeparator);
composerSignature->cleanWhitespace(signature2);
-
composerSignature->replaceSignature(signature1, signature2);
const QString result = QStringLiteral("foo\nbla\nfoo--\nbla Bla ");
QCOMPARE(richtextComposerNg.toPlainText(), result);
for (int i = 0; i < 10; i++) {
composerSignature->replaceSignature(signature2, signature1);
composerSignature->replaceSignature(signature1, signature2);
}
QCOMPARE(richtextComposerNg.toPlainText(), result);
-
}
diff --git a/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.h b/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.h
index 1d58ee91..9bd65129 100644
--- a/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.h
+++ b/messagecomposer/src/composer-ng/autotests/richtextcomposersignaturestest.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2018-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef RICHTEXTCOMPOSERSIGNATURESTEST_H
#define RICHTEXTCOMPOSERSIGNATURESTEST_H
#include <QObject>
class RichTextComposerSignaturesTest : public QObject
{
Q_OBJECT
public:
explicit RichTextComposerSignaturesTest(QObject *parent = nullptr);
~RichTextComposerSignaturesTest() = default;
private Q_SLOTS:
void shouldCleanSignature();
void shouldReplaceSignatureWhenText();
};
#endif // RICHTEXTCOMPOSERSIGNATURESTEST_H
diff --git a/messagecomposer/src/composer-ng/richtextcomposerng.cpp b/messagecomposer/src/composer-ng/richtextcomposerng.cpp
index 81904809..6d438ffb 100644
--- a/messagecomposer/src/composer-ng/richtextcomposerng.cpp
+++ b/messagecomposer/src/composer-ng/richtextcomposerng.cpp
@@ -1,415 +1,419 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "richtextcomposerng.h"
#include <kpimtextedit/richtextcomposercontroler.h>
#include <kpimtextedit/richtextcomposerimages.h>
#include "richtextcomposersignatures.h"
#include <pimcommon/autocorrection.h>
-
+#include <QDebug>
#include <part/textpart.h>
#include "settings/messagecomposersettings.h"
#include <grantlee/markupdirector.h>
#include <grantlee/plaintextmarkupbuilder.h>
using namespace MessageComposer;
class MessageComposer::RichTextComposerNgPrivate
{
public:
RichTextComposerNgPrivate(RichTextComposerNg *q)
: richtextComposer(q)
{
richTextComposerSignatures = new MessageComposer::RichTextComposerSignatures(richtextComposer, richtextComposer);
}
void fixHtmlFontSize(QString &cleanHtml);
QString toCleanHtml() const;
PimCommon::AutoCorrection *autoCorrection = nullptr;
RichTextComposerNg *richtextComposer = nullptr;
MessageComposer::RichTextComposerSignatures *richTextComposerSignatures = nullptr;
};
RichTextComposerNg::RichTextComposerNg(QWidget *parent)
: KPIMTextEdit::RichTextComposer(parent)
, d(new MessageComposer::RichTextComposerNgPrivate(this))
{
}
RichTextComposerNg::~RichTextComposerNg()
{
delete d;
}
MessageComposer::RichTextComposerSignatures *RichTextComposerNg::composerSignature() const
{
return d->richTextComposerSignatures;
}
PimCommon::AutoCorrection *RichTextComposerNg::autocorrection() const
{
return d->autoCorrection;
}
void RichTextComposerNg::setAutocorrection(PimCommon::AutoCorrection *autocorrect)
{
d->autoCorrection = autocorrect;
}
void RichTextComposerNg::setAutocorrectionLanguage(const QString &lang)
{
if (d->autoCorrection) {
d->autoCorrection->setLanguage(lang);
}
}
static bool isSpecial(const QTextCharFormat &charFormat)
{
return charFormat.isFrameFormat() || charFormat.isImageFormat()
|| charFormat.isListFormat() || charFormat.isTableFormat() || charFormat.isTableCellFormat();
}
bool RichTextComposerNg::processAutoCorrection(QKeyEvent *e)
{
if (d->autoCorrection && d->autoCorrection->isEnabledAutoCorrection()) {
if ((e->key() == Qt::Key_Space) || (e->key() == Qt::Key_Enter) || (e->key() == Qt::Key_Return)) {
if (!isLineQuoted(textCursor().block().text()) && !textCursor().hasSelection()) {
const QTextCharFormat initialTextFormat = textCursor().charFormat();
const bool richText = (textMode() == RichTextComposer::Rich);
int position = textCursor().position();
const bool addSpace = d->autoCorrection->autocorrect(richText, *document(), position);
QTextCursor cur = textCursor();
cur.setPosition(position);
const bool spacePressed = (e->key() == Qt::Key_Space);
if (overwriteMode() && spacePressed) {
if (addSpace) {
const QChar insertChar = QLatin1Char(' ');
if (!cur.atBlockEnd()) {
cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 1);
}
if (richText && !isSpecial(initialTextFormat)) {
cur.insertText(insertChar, initialTextFormat);
} else {
cur.insertText(insertChar);
}
setTextCursor(cur);
}
} else {
const QChar insertChar = spacePressed ? QLatin1Char(' ') : QLatin1Char('\n');
if (richText && !isSpecial(initialTextFormat)) {
if ((spacePressed && addSpace) || !spacePressed) {
cur.insertText(insertChar, initialTextFormat);
}
} else {
if ((spacePressed && addSpace) || !spacePressed) {
cur.insertText(insertChar);
}
}
setTextCursor(cur);
}
return true;
}
}
}
return false;
}
void RichTextComposerNgPrivate::fixHtmlFontSize(QString &cleanHtml)
{
static const QString FONTSTYLEREGEX = QStringLiteral("<span style=\".*font-size:(.*)pt;.*</span>");
QRegExp styleRegex(FONTSTYLEREGEX);
styleRegex.setMinimal(true);
int offset = styleRegex.indexIn(cleanHtml, 0);
while (offset != -1) {
// replace all the matching text with the new line text
bool ok = false;
const QString fontSizeStr = styleRegex.cap(1);
const int ptValue = fontSizeStr.toInt(&ok);
if (ok) {
double emValue = static_cast<double>(ptValue) / 12;
const QString emValueStr = QString::number(emValue, 'g', 2);
cleanHtml.replace(styleRegex.pos(1), QString(fontSizeStr + QLatin1String("px")).length(), emValueStr + QLatin1String("em"));
}
// advance the search offset to just beyond the last replace
offset += styleRegex.matchedLength();
// find the next occurrence
offset = styleRegex.indexIn(cleanHtml, offset);
}
}
-bool RichTextComposerNg::convertPlainText(MessageComposer::TextPart *textPart)
+MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus RichTextComposerNg::convertPlainText(MessageComposer::TextPart *textPart)
{
Q_UNUSED(textPart);
- return false;
+ return MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus::NotConverted;
}
void RichTextComposerNg::fillComposerTextPart(MessageComposer::TextPart *textPart)
{
+ bool wasConverted =
+ convertPlainText(textPart) == MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus::Converted;
if (composerControler()->isFormattingUsed()) {
- if (!convertPlainText(textPart)) {
+ if (!wasConverted) {
if (MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage()) {
Grantlee::PlainTextMarkupBuilder *pb = new Grantlee::PlainTextMarkupBuilder();
Grantlee::MarkupDirector *pmd = new Grantlee::MarkupDirector(pb);
pmd->processDocument(document());
const QString plainText = pb->getResult();
textPart->setCleanPlainText(composerControler()->toCleanPlainText(plainText));
QTextDocument *doc = new QTextDocument(plainText);
doc->adjustSize();
textPart->setWrappedPlainText(composerControler()->toWrappedPlainText(doc));
delete doc;
delete pmd;
delete pb;
} else {
textPart->setCleanPlainText(composerControler()->toCleanPlainText());
textPart->setWrappedPlainText(composerControler()->toWrappedPlainText());
}
}
} else {
- textPart->setCleanPlainText(composerControler()->toCleanPlainText());
- textPart->setWrappedPlainText(composerControler()->toWrappedPlainText());
+ if (!wasConverted) {
+ textPart->setCleanPlainText(composerControler()->toCleanPlainText());
+ textPart->setWrappedPlainText(composerControler()->toWrappedPlainText());
+ }
}
textPart->setWordWrappingEnabled(lineWrapMode() == QTextEdit::FixedColumnWidth);
- if (composerControler()->isFormattingUsed()) {
+ if (composerControler()->isFormattingUsed() && !wasConverted) {
QString cleanHtml = d->toCleanHtml();
d->fixHtmlFontSize(cleanHtml);
textPart->setCleanHtml(cleanHtml);
textPart->setEmbeddedImages(composerControler()->composerImages()->embeddedImages());
}
}
QString RichTextComposerNgPrivate::toCleanHtml() const
{
QString result = richtextComposer->toHtml();
static const QString EMPTYLINEHTML = QStringLiteral(
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; "
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; \">&nbsp;</p>");
// Qt inserts various style properties based on the current mode of the editor (underline,
// bold, etc), but only empty paragraphs *also* have qt-paragraph-type set to 'empty'.
static const QString EMPTYLINEREGEX = QStringLiteral(
"<p style=\"-qt-paragraph-type:empty;(.*)</p>");
static const QString OLLISTPATTERNQT = QStringLiteral(
"<ol style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
static const QString ULLISTPATTERNQT = QStringLiteral(
"<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
static const QString ORDEREDLISTHTML = QStringLiteral(
"<ol style=\"margin-top: 0px; margin-bottom: 0px;");
static const QString UNORDEREDLISTHTML = QStringLiteral(
"<ul style=\"margin-top: 0px; margin-bottom: 0px;");
// fix 1 - empty lines should show as empty lines - MS Outlook treats margin-top:0px; as
// a non-existing line.
// Although we can simply remove the margin-top style property, we still get unwanted results
// if you have three or more empty lines. It's best to replace empty <p> elements with <p>&nbsp;</p>.
QRegExp emptyLineFinder(EMPTYLINEREGEX);
emptyLineFinder.setMinimal(true);
// find the first occurrence
int offset = emptyLineFinder.indexIn(result, 0);
while (offset != -1) {
// replace all the matching text with the new line text
result.replace(offset, emptyLineFinder.matchedLength(), EMPTYLINEHTML);
// advance the search offset to just beyond the last replace
offset += EMPTYLINEHTML.length();
// find the next occurrence
offset = emptyLineFinder.indexIn(result, offset);
}
// fix 2a - ordered lists - MS Outlook treats margin-left:0px; as
// a non-existing number; e.g: "1. First item" turns into "First Item"
result.replace(OLLISTPATTERNQT, ORDEREDLISTHTML);
// fix 2b - unordered lists - MS Outlook treats margin-left:0px; as
// a non-existing bullet; e.g: "* First bullet" turns into "First Bullet"
result.replace(ULLISTPATTERNQT, UNORDEREDLISTHTML);
return result;
}
static bool isCursorAtEndOfLine(const QTextCursor &cursor)
{
QTextCursor testCursor = cursor;
testCursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
return !testCursor.hasSelection();
}
static void insertSignatureHelper(const QString &signature, RichTextComposerNg *textEdit, KIdentityManagement::Signature::Placement placement, bool isHtml, bool addNewlines)
{
if (!signature.isEmpty()) {
// Save the modified state of the document, as inserting a signature
// shouldn't change this. Restore it at the end of this function.
bool isModified = textEdit->document()->isModified();
// Move to the desired position, where the signature should be inserted
QTextCursor cursor = textEdit->textCursor();
QTextCursor oldCursor = cursor;
cursor.beginEditBlock();
if (placement == KIdentityManagement::Signature::End) {
cursor.movePosition(QTextCursor::End);
} else if (placement == KIdentityManagement::Signature::Start) {
cursor.movePosition(QTextCursor::Start);
} else if (placement == KIdentityManagement::Signature::AtCursor) {
cursor.movePosition(QTextCursor::StartOfLine);
}
textEdit->setTextCursor(cursor);
QString lineSep;
if (addNewlines) {
if (isHtml) {
lineSep = QStringLiteral("<br>");
} else {
lineSep = QLatin1Char('\n');
}
}
// Insert the signature and newlines depending on where it was inserted.
int newCursorPos = -1;
QString headSep;
QString tailSep;
if (placement == KIdentityManagement::Signature::End) {
// There is one special case when re-setting the old cursor: The cursor
// was at the end. In this case, QTextEdit has no way to know
// if the signature was added before or after the cursor, and just
// decides that it was added before (and the cursor moves to the end,
// but it should not when appending a signature). See bug 167961
if (oldCursor.position() == textEdit->toPlainText().length()) {
newCursorPos = oldCursor.position();
}
headSep = lineSep;
} else if (placement == KIdentityManagement::Signature::Start) {
// When prepending signatures, add a couple of new lines before
// the signature, and move the cursor to the beginning of the QTextEdit.
// People tends to insert new text there.
newCursorPos = 0;
headSep = lineSep + lineSep;
if (!isCursorAtEndOfLine(cursor)) {
tailSep = lineSep;
}
} else if (placement == KIdentityManagement::Signature::AtCursor) {
if (!isCursorAtEndOfLine(cursor)) {
tailSep = lineSep;
}
}
const QString full_signature = headSep + signature + tailSep;
if (isHtml) {
textEdit->insertHtml(full_signature);
} else {
textEdit->insertPlainText(full_signature);
}
cursor.endEditBlock();
if (newCursorPos != -1) {
oldCursor.setPosition(newCursorPos);
}
textEdit->setTextCursor(oldCursor);
textEdit->ensureCursorVisible();
textEdit->document()->setModified(isModified);
if (isHtml) {
textEdit->activateRichText();
}
}
}
void RichTextComposerNg::insertSignature(const KIdentityManagement::Signature &signature, KIdentityManagement::Signature::Placement placement, KIdentityManagement::Signature::AddedText addedText)
{
if (signature.isEnabledSignature()) {
QString signatureStr;
if (addedText & KIdentityManagement::Signature::AddSeparator) {
signatureStr = signature.withSeparator();
} else {
signatureStr = signature.rawText();
}
insertSignatureHelper(signatureStr, this, placement,
(signature.isInlinedHtml()
&& signature.type() == KIdentityManagement::Signature::Inlined),
(addedText & KIdentityManagement::Signature::AddNewLines));
// We added the text of the signature above, now it is time to add the images as well.
if (signature.isInlinedHtml()) {
- foreach (const KIdentityManagement::Signature::EmbeddedImagePtr &image, signature.embeddedImages()) {
+ for (const KIdentityManagement::Signature::EmbeddedImagePtr &image : signature.embeddedImages()) {
composerControler()->composerImages()->loadImage(image->image, image->name, image->name);
}
}
}
}
QString RichTextComposerNg::toCleanHtml() const
{
return d->toCleanHtml();
}
void RichTextComposerNg::forceAutoCorrection(bool selectedText)
{
if (document()->isEmpty()) {
return;
}
if (d->autoCorrection && d->autoCorrection->isEnabledAutoCorrection()) {
const bool richText = (textMode() == RichTextComposer::Rich);
const int initialPosition = textCursor().position();
QTextCursor cur = textCursor();
cur.beginEditBlock();
if (selectedText && cur.hasSelection()) {
const int positionStart = qMin(cur.selectionEnd(), cur.selectionStart());
const int positionEnd = qMax(cur.selectionEnd(), cur.selectionStart());
cur.setPosition(positionStart);
int cursorPosition = positionStart;
while (cursorPosition < positionEnd) {
if (isLineQuoted(cur.block().text())) {
cur.movePosition(QTextCursor::NextBlock);
} else {
cur.movePosition(QTextCursor::NextWord);
}
cursorPosition = cur.position();
(void)d->autoCorrection->autocorrect(richText, *document(), cursorPosition);
}
} else {
cur.movePosition(QTextCursor::Start);
while (!cur.atEnd()) {
if (isLineQuoted(cur.block().text())) {
cur.movePosition(QTextCursor::NextBlock);
} else {
cur.movePosition(QTextCursor::NextWord);
}
int cursorPosition = cur.position();
(void)d->autoCorrection->autocorrect(richText, *document(), cursorPosition);
}
}
cur.endEditBlock();
if (cur.position() != initialPosition) {
cur.setPosition(initialPosition);
setTextCursor(cur);
}
}
}
diff --git a/messagecomposer/src/composer-ng/richtextcomposerng.h b/messagecomposer/src/composer-ng/richtextcomposerng.h
index 63f8e47e..d2b0d864 100644
--- a/messagecomposer/src/composer-ng/richtextcomposerng.h
+++ b/messagecomposer/src/composer-ng/richtextcomposerng.h
@@ -1,61 +1,62 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef RICHTEXTCOMPOSERNG_H
#define RICHTEXTCOMPOSERNG_H
#include "messagecomposer_export.h"
#include <kpimtextedit/richtextcomposer.h>
+#include <MessageComposer/PluginEditorConvertTextInterface>
#include <KIdentityManagement/Signature>
namespace PimCommon {
class AutoCorrection;
}
namespace MessageComposer {
class TextPart;
class RichTextComposerSignatures;
class RichTextComposerNgPrivate;
class MESSAGECOMPOSER_EXPORT RichTextComposerNg : public KPIMTextEdit::RichTextComposer
{
Q_OBJECT
public:
explicit RichTextComposerNg(QWidget *parent = nullptr);
~RichTextComposerNg() override;
Q_REQUIRED_RESULT PimCommon::AutoCorrection *autocorrection() const;
void setAutocorrection(PimCommon::AutoCorrection *autocorrect);
void setAutocorrectionLanguage(const QString &lang);
void fillComposerTextPart(MessageComposer::TextPart *textPart);
Q_REQUIRED_RESULT MessageComposer::RichTextComposerSignatures *composerSignature() const;
void insertSignature(const KIdentityManagement::Signature &signature, KIdentityManagement::Signature::Placement placement, KIdentityManagement::Signature::AddedText addedText);
Q_REQUIRED_RESULT QString toCleanHtml() const;
void forceAutoCorrection(bool selectedText = false) override;
- Q_REQUIRED_RESULT virtual bool convertPlainText(MessageComposer::TextPart *textPart);
+ Q_REQUIRED_RESULT virtual MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus convertPlainText(MessageComposer::TextPart *textPart);
private:
bool processAutoCorrection(QKeyEvent *event) override;
RichTextComposerNgPrivate *const d;
};
}
#endif // RICHTEXTCOMPOSERNG_H
diff --git a/messagecomposer/src/composer-ng/richtextcomposersignatures.cpp b/messagecomposer/src/composer-ng/richtextcomposersignatures.cpp
index aa0cc6f7..f3059184 100644
--- a/messagecomposer/src/composer-ng/richtextcomposersignatures.cpp
+++ b/messagecomposer/src/composer-ng/richtextcomposersignatures.cpp
@@ -1,202 +1,202 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "richtextcomposerng.h"
#include "richtextcomposersignatures.h"
#include <KIdentityManagement/Signature>
#include <QTextBlock>
#include "messagecomposer_debug.h"
using namespace MessageComposer;
class Q_DECL_HIDDEN RichTextComposerSignatures::RichTextComposerSignaturesPrivate
{
public:
RichTextComposerSignaturesPrivate(RichTextComposerNg *composer)
: richTextComposer(composer)
{
}
void cleanWhitespaceHelper(const QRegExp &regExp, const QString &newText, const KIdentityManagement::Signature &sig);
QList<QPair<int, int> > signaturePositions(const KIdentityManagement::Signature &sig) const;
RichTextComposerNg *richTextComposer = nullptr;
};
RichTextComposerSignatures::RichTextComposerSignatures(MessageComposer::RichTextComposerNg *composer, QObject *parent)
: QObject(parent)
, d(new RichTextComposerSignaturesPrivate(composer))
{
}
RichTextComposerSignatures::~RichTextComposerSignatures()
{
delete d;
}
void RichTextComposerSignatures::RichTextComposerSignaturesPrivate::cleanWhitespaceHelper(const QRegExp &regExp, const QString &newText, const KIdentityManagement::Signature &sig)
{
int currentSearchPosition = 0;
forever {
// Find the text
QString text = richTextComposer->document()->toPlainText();
int currentMatch = regExp.indexIn(text, currentSearchPosition);
currentSearchPosition = currentMatch;
if (currentMatch == -1) {
break;
}
// Select the text
QTextCursor cursor(richTextComposer->document());
cursor.setPosition(currentMatch);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
regExp.matchedLength());
// Skip quoted text
if (richTextComposer->isLineQuoted(cursor.block().text())) {
currentSearchPosition += regExp.matchedLength();
continue;
}
// Skip text inside signatures
bool insideSignature = false;
const QList< QPair<int, int> > sigPositions = signaturePositions(sig);
for (const QPair<int, int> &position : sigPositions) {
if (cursor.position() >= position.first
&& cursor.position() <= position.second) {
insideSignature = true;
}
}
if (insideSignature) {
currentSearchPosition += regExp.matchedLength();
continue;
}
// Replace the text
cursor.removeSelectedText();
cursor.insertText(newText);
currentSearchPosition += newText.length();
}
}
void RichTextComposerSignatures::cleanWhitespace(const KIdentityManagement::Signature &sig)
{
QTextCursor cursor(d->richTextComposer->document());
cursor.beginEditBlock();
// Squeeze tabs and spaces
d->cleanWhitespaceHelper(QRegExp(QLatin1String("[\t ]+")),
QString(QLatin1Char(' ')), sig);
// Remove trailing whitespace
d->cleanWhitespaceHelper(QRegExp(QLatin1String("[\t ][\n]")),
QString(QLatin1Char('\n')), sig);
// Single space lines
d->cleanWhitespaceHelper(QRegExp(QLatin1String("[\n]{3,}")),
QStringLiteral("\n\n"), sig);
if (!d->richTextComposer->textCursor().hasSelection()) {
d->richTextComposer->textCursor().clearSelection();
}
cursor.endEditBlock();
}
QList< QPair<int, int> >
RichTextComposerSignatures::RichTextComposerSignaturesPrivate::signaturePositions(const KIdentityManagement::Signature &sig) const
{
QList< QPair<int, int> > signaturePositions;
if (!sig.rawText().isEmpty()) {
QString sigText = sig.toPlainText();
int currentSearchPosition = 0;
forever {
// Find the next occurrence of the signature text
const QString text = richTextComposer->document()->toPlainText();
const int currentMatch = text.indexOf(sigText, currentSearchPosition);
currentSearchPosition = currentMatch + sigText.length();
if (currentMatch == -1) {
break;
}
signaturePositions.append(QPair<int, int>(currentMatch,
currentMatch + sigText.length()));
}
}
return signaturePositions;
}
bool RichTextComposerSignatures::replaceSignature(const KIdentityManagement::Signature &oldSig, const KIdentityManagement::Signature &newSig)
{
bool found = false;
if (oldSig == newSig) {
return false;
}
QString oldSigText = oldSig.toPlainText();
if (oldSigText.isEmpty()) {
return false;
}
QTextCursor cursor(d->richTextComposer->document());
cursor.beginEditBlock();
int currentSearchPosition = 0;
forever {
// Find the next occurrence of the signature text
const QString text = d->richTextComposer->document()->toPlainText();
int currentMatch = text.indexOf(oldSigText, currentSearchPosition);
currentSearchPosition = currentMatch;
if (currentMatch == -1) {
break;
}
// Select the signature
QTextCursor cursor(d->richTextComposer->document());
cursor.setPosition(currentMatch);
// If the new signature is completely empty, we also want to remove the
// signature separator, so include it in the selection
int additionalMove = 0;
if (newSig.rawText().isEmpty()
&& text.mid(currentMatch - 4, 4) == QLatin1String("-- \n")) {
cursor.movePosition(QTextCursor::PreviousCharacter,
QTextCursor::MoveAnchor, 4);
additionalMove = 4;
} else if (newSig.rawText().isEmpty()
&& text.mid(currentMatch - 1, 1) == QLatin1String("\n")) {
cursor.movePosition(QTextCursor::PreviousCharacter,
QTextCursor::MoveAnchor, 1);
additionalMove = 1;
}
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
oldSigText.length() + additionalMove);
// Skip quoted signatures
if (d->richTextComposer->isLineQuoted(cursor.block().text())) {
currentSearchPosition += oldSig.toPlainText().length();
continue;
}
// Remove the old and insert the new signature
cursor.removeSelectedText();
d->richTextComposer->setTextCursor(cursor);
d->richTextComposer->insertSignature(newSig, KIdentityManagement::Signature::AtCursor, KIdentityManagement::Signature::AddNothing);
found = true;
currentSearchPosition += newSig.toPlainText().length();
}
cursor.endEditBlock();
return found;
}
diff --git a/messagecomposer/src/composer-ng/richtextcomposersignatures.h b/messagecomposer/src/composer-ng/richtextcomposersignatures.h
index e87d7938..dc0e79f9 100644
--- a/messagecomposer/src/composer-ng/richtextcomposersignatures.h
+++ b/messagecomposer/src/composer-ng/richtextcomposersignatures.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef RICHTEXTCOMPOSERSIGNATURES_H
#define RICHTEXTCOMPOSERSIGNATURES_H
#include <QObject>
#include "messagecomposer_export.h"
namespace KIdentityManagement {
class Signature;
}
namespace MessageComposer {
class RichTextComposerNg;
class MESSAGECOMPOSER_EXPORT RichTextComposerSignatures : public QObject
{
Q_OBJECT
public:
explicit RichTextComposerSignatures(MessageComposer::RichTextComposerNg *composer, QObject *parent = nullptr);
~RichTextComposerSignatures();
void cleanWhitespace(const KIdentityManagement::Signature &sig);
Q_REQUIRED_RESULT bool replaceSignature(const KIdentityManagement::Signature &oldSig, const KIdentityManagement::Signature &newSig);
private:
class RichTextComposerSignaturesPrivate;
RichTextComposerSignaturesPrivate *const d;
};
}
#endif // RICHTEXTCOMPOSERSIGNATURES_H
diff --git a/messagecomposer/src/composer/composer.cpp b/messagecomposer/src/composer/composer.cpp
index 2e9c6ccc..f106113e 100644
--- a/messagecomposer/src/composer/composer.cpp
+++ b/messagecomposer/src/composer/composer.cpp
@@ -1,609 +1,607 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "composer.h"
#include "job/attachmentjob.h"
#include "part/globalpart.h"
#include "part/infopart.h"
#include "job/jobbase_p.h"
#include "part/textpart.h"
#include "job/maintextjob.h"
#include "job/multipartjob.h"
#include "job/signjob.h"
#include "job/encryptjob.h"
#include "job/signencryptjob.h"
#include "job/skeletonmessagejob.h"
#include "job/transparentjob.h"
#include "imagescaling/imagescaling.h"
#include "imagescaling/imagescalingutils.h"
#include "settings/messagecomposersettings.h"
#include "messagecomposer_debug.h"
#include <klocalizedstring.h>
using namespace MessageComposer;
using MessageCore::AttachmentPart;
class MessageComposer::ComposerPrivate : public JobBasePrivate
{
public:
ComposerPrivate(Composer *qq)
: JobBasePrivate(qq)
{
}
void init();
void doStart(); // slot
void composeStep1();
void skeletonJobFinished(KJob *job); // slot
void composeStep2();
QList<ContentJobBase *> createEncryptJobs(ContentJobBase *contentJob, bool sign);
void contentJobFinished(KJob *job); // slot
void composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector<GpgME::Key> &keys, const QStringList &recipients);
void attachmentsFinished(KJob *job); // slot
void composeFinalStep(KMime::Content *headers, KMime::Content *content);
QList<QPair<QStringList, std::vector<GpgME::Key> > > encData;
std::vector<GpgME::Key> signers;
AttachmentPart::List attachmentParts;
// attachments with different sign/encrypt settings from
// main message body. added at the end of the process
AttachmentPart::List lateAttachmentParts;
QList<KMime::Message::Ptr> resultMessages;
Kleo::CryptoMessageFormat format;
// Stuff that the application plays with.
GlobalPart *globalPart = nullptr;
InfoPart *infoPart = nullptr;
TextPart *textPart = nullptr;
// Stuff that we play with.
KMime::Message *skeletonMessage = nullptr;
- KMime::Content *resultContent = nullptr;
bool started = false;
bool finished = false;
bool sign = false;
bool encrypt = false;
bool noCrypto = false;
bool autoSaving = false;
Q_DECLARE_PUBLIC(Composer)
};
void ComposerPrivate::init()
{
Q_Q(Composer);
// We cannot create these in ComposerPrivate's constructor, because
// their parent q is not fully constructed at that time.
globalPart = new GlobalPart(q);
infoPart = new InfoPart(q);
textPart = new TextPart(q);
}
void ComposerPrivate::doStart()
{
Q_ASSERT(!started);
started = true;
composeStep1();
}
void ComposerPrivate::composeStep1()
{
Q_Q(Composer);
// Create skeleton message (containing headers only; no content).
SkeletonMessageJob *skeletonJob = new SkeletonMessageJob(infoPart, globalPart, q);
QObject::connect(skeletonJob, &SkeletonMessageJob::finished, q, [this](KJob *job) {
skeletonJobFinished(job);
});
q->addSubjob(skeletonJob);
skeletonJob->start();
}
void ComposerPrivate::skeletonJobFinished(KJob *job)
{
if (job->error()) {
return; // KCompositeJob takes care of the error.
}
Q_ASSERT(dynamic_cast<SkeletonMessageJob *>(job));
SkeletonMessageJob *sjob = static_cast<SkeletonMessageJob *>(job);
// SkeletonMessageJob is a special job creating a Message instead of a Content.
Q_ASSERT(skeletonMessage == nullptr);
skeletonMessage = sjob->message();
Q_ASSERT(skeletonMessage);
skeletonMessage->assemble();
composeStep2();
}
void ComposerPrivate::composeStep2()
{
Q_Q(Composer);
ContentJobBase *mainJob = nullptr;
MainTextJob *mainTextJob = new MainTextJob(textPart, q);
if ((sign || encrypt) && format & Kleo::InlineOpenPGPFormat) { // needs custom handling --- one SignEncryptJob by itself
qCDebug(MESSAGECOMPOSER_LOG) << "sending to sign/enc inline job!";
if (encrypt) {
//TODO: fix Inline PGP with encrypted attachments
const QList<ContentJobBase *> jobs = createEncryptJobs(mainTextJob, sign);
for (ContentJobBase *subJob : jobs) {
if (attachmentParts.isEmpty()) {
// We have no attachments. Use the content given by the MainTextJob.
mainJob = subJob;
} else {
MultipartJob *multipartJob = new MultipartJob(q);
multipartJob->setMultipartSubtype("mixed");
multipartJob->appendSubjob(subJob);
for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
multipartJob->appendSubjob(new AttachmentJob(part));
}
mainJob = multipartJob;
}
QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
q->addSubjob(mainJob);
}
} else {
SignJob *subJob = new SignJob(q);
subJob->setSigningKeys(signers);
subJob->setCryptoMessageFormat(format);
subJob->appendSubjob(mainTextJob);
if (attachmentParts.isEmpty()) {
// We have no attachments. Use the content given by the MainTextJob.
mainJob = subJob;
} else {
MultipartJob *multipartJob = new MultipartJob(q);
multipartJob->setMultipartSubtype("mixed");
multipartJob->appendSubjob(subJob);
for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
multipartJob->appendSubjob(new AttachmentJob(part));
}
mainJob = multipartJob;
}
QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
q->addSubjob(mainJob);
}
if (mainJob) {
mainJob->start();
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "main job is null";
}
return;
}
if (attachmentParts.isEmpty()) {
// We have no attachments. Use the content given by the MainTextJob.
mainJob = mainTextJob;
} else {
// We have attachments. Create a multipart/mixed content.
QMutableListIterator<AttachmentPart::Ptr> iter(attachmentParts);
while (iter.hasNext()) {
AttachmentPart::Ptr part = iter.next();
qCDebug(MESSAGECOMPOSER_LOG) << "Checking attachment crypto policy..." << part->isSigned() << part->isEncrypted();
if (!noCrypto && !autoSaving && (sign != part->isSigned() || encrypt != part->isEncrypted())) { // different policy
qCDebug(MESSAGECOMPOSER_LOG) << "got attachment with different crypto policy!";
lateAttachmentParts.append(part);
iter.remove();
}
}
MultipartJob *multipartJob = new MultipartJob(q);
multipartJob->setMultipartSubtype("mixed");
multipartJob->appendSubjob(mainTextJob);
for (const AttachmentPart::Ptr &part : qAsConst(attachmentParts)) {
multipartJob->appendSubjob(new AttachmentJob(part));
}
mainJob = multipartJob;
}
if (sign) {
SignJob *sJob = new SignJob(q);
sJob->setCryptoMessageFormat(format);
sJob->setSigningKeys(signers);
sJob->appendSubjob(mainJob);
mainJob = sJob;
}
if (encrypt) {
foreach (ContentJobBase *eJob, createEncryptJobs(mainJob, false)) {
QObject::connect(eJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
q->addSubjob(eJob);
mainJob = eJob; //start only last EncryptJob
}
} else {
QObject::connect(mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)));
q->addSubjob(mainJob);
}
mainJob->start();
}
QList<ContentJobBase *> ComposerPrivate::createEncryptJobs(ContentJobBase *contentJob, bool sign)
{
Q_Q(Composer);
QList<ContentJobBase *> jobs;
// each SplitInfo holds a list of recipients/keys, if there is more than
// one item in it then it means there are secondary recipients that need
// different messages w/ clean headers
qCDebug(MESSAGECOMPOSER_LOG) << "starting enc jobs";
qCDebug(MESSAGECOMPOSER_LOG) << "format:" << format;
qCDebug(MESSAGECOMPOSER_LOG) << "enc data:" << encData.size();
if (encData.isEmpty()) { // no key data! bail!
q->setErrorText(i18n("No key data for recipients found."));
q->setError(Composer::IncompleteError);
q->emitResult();
return jobs;
}
const int encDataSize = encData.size();
jobs.reserve(encDataSize);
for (int i = 0; i < encDataSize; ++i) {
QPair<QStringList, std::vector<GpgME::Key> > recipients = encData[ i ];
qCDebug(MESSAGECOMPOSER_LOG) << "got first list of recipients:" << recipients.first;
ContentJobBase *subJob = nullptr;
if (sign) {
SignEncryptJob *seJob = new SignEncryptJob(q);
seJob->setCryptoMessageFormat(format);
seJob->setSigningKeys(signers);
seJob->setEncryptionKeys(recipients.second);
seJob->setRecipients(recipients.first);
subJob = seJob;
} else {
EncryptJob *eJob = new EncryptJob(q);
eJob->setCryptoMessageFormat(format);
eJob->setEncryptionKeys(recipients.second);
eJob->setRecipients(recipients.first);
subJob = eJob;
}
qCDebug(MESSAGECOMPOSER_LOG) << "subJob" << subJob;
subJob->appendSubjob(contentJob);
jobs.append(subJob);
}
qCDebug(MESSAGECOMPOSER_LOG) << jobs.size();
return jobs;
}
void ComposerPrivate::contentJobFinished(KJob *job)
{
if (job->error()) {
return; // KCompositeJob takes care of the error.
}
qCDebug(MESSAGECOMPOSER_LOG) << "composing final message";
KMime::Message *headers = nullptr;
KMime::Content *resultContent = nullptr;
std::vector<GpgME::Key> keys;
QStringList recipients;
Q_ASSERT(dynamic_cast<ContentJobBase *>(job) == static_cast<ContentJobBase *>(job));
ContentJobBase *contentJob = static_cast<ContentJobBase *>(job);
// create the final headers and body,
// taking into account secondary recipients for encryption
if (encData.size() > 1) { // crypto job with secondary recipients..
Q_ASSERT(dynamic_cast<MessageComposer::AbstractEncryptJob *>(job)); // we need to get the recipients for this job
MessageComposer::AbstractEncryptJob *eJob = dynamic_cast<MessageComposer::AbstractEncryptJob *>(job);
keys = eJob->encryptionKeys();
recipients = eJob->recipients();
resultContent = contentJob->content(); // content() comes from superclass
headers = new KMime::Message;
headers->setHeader(skeletonMessage->from());
headers->setHeader(skeletonMessage->to());
headers->setHeader(skeletonMessage->cc());
headers->setHeader(skeletonMessage->subject());
headers->setHeader(skeletonMessage->date());
headers->setHeader(skeletonMessage->messageID());
KMime::Headers::Generic *realTo = new KMime::Headers::Generic("X-KMail-EncBccRecipients");
realTo->fromUnicodeString(eJob->recipients().join(QLatin1Char('%')), "utf-8");
qCDebug(MESSAGECOMPOSER_LOG) << "got one of multiple messages sending to:" << realTo->asUnicodeString();
qCDebug(MESSAGECOMPOSER_LOG) << "sending to recipients:" << recipients;
headers->setHeader(realTo);
headers->assemble();
} else { // just use the saved headers from before
if (!encData.isEmpty()) {
qCDebug(MESSAGECOMPOSER_LOG) << "setting enc data:" << encData[ 0 ].first << "with num keys:" << encData[ 0 ].second.size();
keys = encData[ 0 ].second;
recipients = encData[ 0 ].first;
}
headers = skeletonMessage;
resultContent = contentJob->content();
}
if (lateAttachmentParts.isEmpty()) {
composeFinalStep(headers, resultContent);
} else {
composeWithLateAttachments(headers, resultContent, lateAttachmentParts, keys, recipients);
}
}
-void ComposerPrivate::composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector<GpgME::Key> &keys,
- const QStringList &recipients)
+void ComposerPrivate::composeWithLateAttachments(KMime::Message *headers, KMime::Content *content, const AttachmentPart::List &parts, const std::vector<GpgME::Key> &keys, const QStringList &recipients)
{
Q_Q(Composer);
MultipartJob *multiJob = new MultipartJob(q);
multiJob->setMultipartSubtype("mixed");
// wrap the content into a job for the multijob to handle it
MessageComposer::TransparentJob *tJob = new MessageComposer::TransparentJob(q);
tJob->setContent(content);
multiJob->appendSubjob(tJob);
multiJob->setExtraContent(headers);
qCDebug(MESSAGECOMPOSER_LOG) << "attachment encr key size:" << keys.size() << recipients;
// operate correctly on each attachment that has a different crypto policy than body.
for (const AttachmentPart::Ptr &attachment : qAsConst(parts)) {
AttachmentJob *attachJob = new AttachmentJob(attachment, q);
qCDebug(MESSAGECOMPOSER_LOG) << "got a late attachment";
if (attachment->isSigned()) {
qCDebug(MESSAGECOMPOSER_LOG) << "adding signjob for late attachment";
SignJob *sJob = new SignJob(q);
sJob->setContent(nullptr);
sJob->setCryptoMessageFormat(format);
sJob->setSigningKeys(signers);
sJob->appendSubjob(attachJob);
if (attachment->isEncrypted()) {
qCDebug(MESSAGECOMPOSER_LOG) << "adding sign + encrypt job for late attachment";
EncryptJob *eJob = new EncryptJob(q);
eJob->setCryptoMessageFormat(format);
eJob->setEncryptionKeys(keys);
eJob->setRecipients(recipients);
eJob->appendSubjob(sJob);
multiJob->appendSubjob(eJob);
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "Just signing late attachment";
multiJob->appendSubjob(sJob);
}
} else if (attachment->isEncrypted()) { // only encryption
qCDebug(MESSAGECOMPOSER_LOG) << "just encrypting late attachment";
EncryptJob *eJob = new EncryptJob(q);
eJob->setCryptoMessageFormat(format);
eJob->setEncryptionKeys(keys);
eJob->setRecipients(recipients);
eJob->appendSubjob(attachJob);
multiJob->appendSubjob(eJob);
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "attaching plain non-crypto attachment";
AttachmentJob *attachJob = new AttachmentJob(attachment, q);
multiJob->appendSubjob(attachJob);
}
}
QObject::connect(multiJob, SIGNAL(finished(KJob*)), q, SLOT(attachmentsFinished(KJob*)));
q->addSubjob(multiJob);
multiJob->start();
}
void ComposerPrivate::attachmentsFinished(KJob *job)
{
if (job->error()) {
return; // KCompositeJob takes care of the error.
}
qCDebug(MESSAGECOMPOSER_LOG) << "composing final message with late attachments";
Q_ASSERT(dynamic_cast<ContentJobBase *>(job));
ContentJobBase *contentJob = static_cast<ContentJobBase *>(job);
KMime::Content *content = contentJob->content();
KMime::Content *headers = contentJob->extraContent();
composeFinalStep(headers, content);
}
void ComposerPrivate::composeFinalStep(KMime::Content *headers, KMime::Content *content)
{
content->assemble();
QByteArray allData = headers->head() + content->encodedContent();
KMime::Message::Ptr resultMessage(new KMime::Message);
resultMessage->setContent(allData);
resultMessage->parse(); // Not strictly necessary.
resultMessages.append(resultMessage);
}
Composer::Composer(QObject *parent)
: JobBase(*new ComposerPrivate(this), parent)
{
Q_D(Composer);
d->init();
}
Composer::~Composer()
{
}
QList<KMime::Message::Ptr> Composer::resultMessages() const
{
Q_D(const Composer);
Q_ASSERT(d->finished);
Q_ASSERT(!error());
QList<KMime::Message::Ptr> results = d->resultMessages;
return results;
}
GlobalPart *Composer::globalPart() const
{
Q_D(const Composer);
return d->globalPart;
}
InfoPart *Composer::infoPart() const
{
Q_D(const Composer);
return d->infoPart;
}
TextPart *Composer::textPart() const
{
Q_D(const Composer);
return d->textPart;
}
AttachmentPart::List Composer::attachmentParts() const
{
Q_D(const Composer);
return d->attachmentParts;
}
void Composer::addAttachmentPart(AttachmentPart::Ptr part, bool autoresizeImage)
{
Q_D(Composer);
Q_ASSERT(!d->started);
Q_ASSERT(!d->attachmentParts.contains(part));
if (autoresizeImage) {
MessageComposer::Utils resizeUtils;
if (resizeUtils.resizeImage(part)) {
MessageComposer::ImageScaling autoResizeJob;
autoResizeJob.setName(part->name());
autoResizeJob.setMimetype(part->mimeType());
if (autoResizeJob.loadImageFromData(part->data())) {
if (autoResizeJob.resizeImage()) {
part->setData(autoResizeJob.imageArray());
part->setMimeType(autoResizeJob.mimetype());
part->setName(autoResizeJob.generateNewName());
resizeUtils.changeFileName(part);
}
}
}
}
d->attachmentParts.append(part);
}
void Composer::addAttachmentParts(const AttachmentPart::List &parts, bool autoresizeImage)
{
for (const AttachmentPart::Ptr &part : parts) {
addAttachmentPart(part, autoresizeImage);
}
}
void Composer::removeAttachmentPart(AttachmentPart::Ptr part)
{
Q_D(Composer);
Q_ASSERT(!d->started);
const int numberOfElements = d->attachmentParts.removeAll(part);
if (numberOfElements <= 0) {
qCCritical(MESSAGECOMPOSER_LOG) << "Unknown attachment part" << part.data();
Q_ASSERT(false);
return;
}
}
void Composer::setSignAndEncrypt(const bool doSign, const bool doEncrypt)
{
Q_D(Composer);
d->sign = doSign;
d->encrypt = doEncrypt;
}
void Composer::setMessageCryptoFormat(Kleo::CryptoMessageFormat format)
{
Q_D(Composer);
d->format = format;
}
void Composer::setSigningKeys(std::vector<GpgME::Key> &signers)
{
Q_D(Composer);
d->signers = signers;
}
void Composer::setEncryptionKeys(const QList<QPair<QStringList, std::vector<GpgME::Key> > > &encData)
{
Q_D(Composer);
d->encData = encData;
}
void Composer::setNoCrypto(bool noCrypto)
{
Q_D(Composer);
d->noCrypto = noCrypto;
}
bool Composer::finished() const
{
Q_D(const Composer);
- return d->autoSaving;
+ return d->finished;
}
bool Composer::autoSave() const
{
Q_D(const Composer);
return d->autoSaving;
}
void Composer::setAutoSave(bool isAutoSave)
{
Q_D(Composer);
d->autoSaving = isAutoSave;
}
void Composer::start()
{
Q_D(Composer);
d->doStart();
}
void Composer::slotResult(KJob *job)
{
Q_D(Composer);
JobBase::slotResult(job);
if (!hasSubjobs()) {
d->finished = true;
emitResult();
}
}
#include "moc_composer.cpp"
diff --git a/messagecomposer/src/composer/composerviewbase.cpp b/messagecomposer/src/composer/composerviewbase.cpp
index b50b360f..2273911e 100644
--- a/messagecomposer/src/composer/composerviewbase.cpp
+++ b/messagecomposer/src/composer/composerviewbase.cpp
@@ -1,2057 +1,2110 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#include "composerviewbase.h"
#include "attachment/attachmentcontrollerbase.h"
#include "attachment/attachmentmodel.h"
#include "composer/signaturecontroller.h"
#include "composer-ng/richtextcomposerng.h"
#include <KPIMTextEdit/RichTextComposerImages>
#include "composer-ng/richtextcomposersignatures.h"
#include "KPIMTextEdit/RichTextComposerControler"
#include "job/emailaddressresolvejob.h"
#include "composer/keyresolver.h"
#include "part/globalpart.h"
#include "utils/kleo_util.h"
#include "part/infopart.h"
#include "composer.h"
#include "utils/util.h"
#include "utils/util_p.h"
#include "imagescaling/imagescalingutils.h"
#include "SendLater/SendLaterInfo"
#include "SendLater/SendLaterUtil"
#include <libkdepim/recentaddresses.h>
#include "helper/messagehelper.h"
#include <MessageComposer/RecipientsEditor>
#include "settings/messagecomposersettings.h"
#include <MimeTreeParser/ObjectTreeParser>
#include <MimeTreeParser/SimpleObjectTreeSource>
#ifndef QT_NO_CURSOR
#include <Libkdepim/KCursorSaver>
#endif
#include <Sonnet/DictionaryComboBox>
#include <KIdentityManagement/Identity>
#include <MessageCore/StringUtil>
#include <MessageCore/NodeHelper>
#include <mailtransport/transportcombobox.h>
#include <mailtransportakonadi/messagequeuejob.h>
#include <mailtransport/transportmanager.h>
#include <Akonadi/KMime/SpecialMailCollections>
#include <AkonadiCore/itemcreatejob.h>
#include <AkonadiCore/collectionfetchjob.h>
#include <AkonadiWidgets/collectioncombobox.h>
#include <Akonadi/KMime/MessageFlags>
#include <KIdentityManagement/kidentitymanagement/identitycombo.h>
#include <KIdentityManagement/kidentitymanagement/identitymanager.h>
#include <KEmailAddress>
#include <QSaveFile>
#include <KLocalizedString>
#include <KMessageBox>
#include "messagecomposer_debug.h"
#include <QDir>
#include <QTimer>
#include <QUuid>
#include <QStandardPaths>
#include <followupreminder/followupremindercreatejob.h>
using namespace MessageComposer;
static QStringList encodeIdn(const QStringList &emails)
{
QStringList encoded;
encoded.reserve(emails.count());
for (const QString &email : emails) {
encoded << KEmailAddress::normalizeAddressesAndEncodeIdn(email);
}
return encoded;
}
ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
: QObject(parent)
, m_msg(KMime::Message::Ptr(new KMime::Message))
, m_parentWidget(parentGui)
, m_cryptoMessageFormat(Kleo::AutoFormat)
, m_autoSaveInterval(1 * 1000 * 60) // default of 1 min
{
m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set.
initAutoSave();
}
ComposerViewBase::~ComposerViewBase()
{
delete mSendLaterInfo;
}
bool ComposerViewBase::isComposing() const
{
return !m_composers.isEmpty();
}
void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
{
if (m_attachmentModel) {
foreach (const MessageCore::AttachmentPart::Ptr &attachment, m_attachmentModel->attachments()) {
if (!m_attachmentModel->removeAttachment(attachment)) {
qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
}
}
}
m_msg = msg;
if (m_recipientsEditor) {
m_recipientsEditor->clear();
m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), MessageComposer::Recipient::To);
m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
+ m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
m_recipientsEditor->setFocusBottom();
// If we are loading from a draft, load unexpanded aliases as well
if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-To")) {
const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
for (const QString &addr : spl) {
if (!m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::To)) {
qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
}
}
}
if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
for (const QString &addr : spl) {
if (!m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Cc)) {
qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
}
}
}
if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
for (const QString &addr : spl) {
if (!m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
}
}
}
+ if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
+ const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
+ for (const QString &addr : spl) {
+ if (!m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::ReplyTo)) {
+ qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
+ }
+ }
+ }
}
// First, we copy the message and then parse it to the object tree parser.
// The otp gets the message text out of it, in textualContent(), and also decrypts
// the message if necessary.
KMime::Content *msgContent = new KMime::Content;
msgContent->setContent(m_msg->encodedContent());
msgContent->parse();
MimeTreeParser::SimpleObjectTreeSource emptySource;
MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok
emptySource.setDecryptMessage(allowDecryption);
otp.parseObjectTree(msgContent);
// Load the attachments
foreach (const auto &att, otp.nodeHelper()->attachmentsOfExtraContents()) {
addAttachmentPart(att);
}
foreach (const auto &att, msgContent->attachments()) {
addAttachmentPart(att);
}
int transportId = -1;
if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
transportId = hdr->asUnicodeString().toInt();
}
if (m_transport) {
const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(transportId);
if (transport) {
if (!m_transport->setCurrentTransport(transport->id())) {
qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to find transport id" << transport->id();
}
}
}
// Set the HTML text and collect HTML images
QString htmlContent = otp.htmlContent();
if (htmlContent.isEmpty()) {
m_editor->setPlainText(otp.plainTextContent());
} else {
//Bug 372085 <div id="name"> is replaced in qtextedit by <a id="name">... => break url
htmlContent.replace(QRegularExpression(QStringLiteral("<div\\s*id=\".*\">")), QStringLiteral("<div>"));
m_editor->setHtml(htmlContent);
Q_EMIT enableHtml();
collectImages(m_msg.data());
}
if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
}
delete msgContent;
}
void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
{
// First, we copy the message and then parse it to the object tree parser.
// The otp gets the message text out of it, in textualContent(), and also decrypts
// the message if necessary.
KMime::Content *msgContent = new KMime::Content;
msgContent->setContent(msg->encodedContent());
msgContent->parse();
MimeTreeParser::SimpleObjectTreeSource emptySource;
MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok
otp.parseObjectTree(msgContent);
// Set the HTML text and collect HTML images
if (!otp.htmlContent().isEmpty()) {
m_editor->setHtml(otp.htmlContent());
Q_EMIT enableHtml();
collectImages(msg.data());
} else {
m_editor->setPlainText(otp.plainTextContent());
}
if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
}
delete msgContent;
}
void ComposerViewBase::saveMailSettings()
{
const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
auto header = new KMime::Headers::Generic("X-KMail-Transport");
header->fromUnicodeString(QString::number(m_transport->currentTransportId()), "utf-8");
m_msg->setHeader(header);
+ header = new KMime::Headers::Generic("X-KMail-Transport-Name");
+ header->fromUnicodeString(m_transport->currentText(), "utf-8");
+ m_msg->setHeader(header);
+
header = new KMime::Headers::Generic("X-KMail-Fcc");
header->fromUnicodeString(QString::number(m_fccCollection.id()), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(identity.uoid()), "utf-8");
m_msg->setHeader(header);
+
+ header = new KMime::Headers::Generic("X-KMail-Identity-Name");
+ header->fromUnicodeString(identity.identityName(), "utf-8");
+ m_msg->setHeader(header);
+
header = new KMime::Headers::Generic("X-KMail-Dictionary");
header->fromUnicodeString(m_dictionary->currentDictionary(), "utf-8");
m_msg->setHeader(header);
// Save the quote prefix which is used for this message. Each message can have
// a different quote prefix, for example depending on the original sender.
if (m_editor->quotePrefixName().isEmpty()) {
m_msg->removeHeader("X-KMail-QuotePrefix");
} else {
header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
header->fromUnicodeString(m_editor->quotePrefixName(), "utf-8");
m_msg->setHeader(header);
}
if (m_editor->composerControler()->isFormattingUsed()) {
qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
header = new KMime::Headers::Generic("X-KMail-Markup");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
m_msg->setHeader(header);
} else {
m_msg->removeHeader("X-KMail-Markup");
qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
}
}
void ComposerViewBase::clearFollowUp()
{
mFollowUpDate = QDate();
mFollowUpCollection = Akonadi::Collection();
}
void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
{
mSendMethod = method;
mSaveIn = saveIn;
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
const QString vcardFileName = identity.vCardFile();
if (!vcardFileName.isEmpty()) {
m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
}
}
saveMailSettings();
if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
const QString keepBtnText = m_encrypt
? m_sign ? i18n("&Keep markup, do not sign/encrypt")
: i18n("&Keep markup, do not encrypt")
: i18n("&Keep markup, do not sign");
const QString yesBtnText = m_encrypt
? m_sign ? i18n("Sign/Encrypt (delete markup)")
: i18n("Encrypt (delete markup)")
: i18n("Sign (delete markup)");
int ret = KMessageBox::warningYesNoCancel(m_parentWidget,
i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
"<p>do you want to delete your markup?</p></qt>"),
i18n("Sign/Encrypt Message?"),
KGuiItem(yesBtnText),
KGuiItem(keepBtnText));
if (KMessageBox::Cancel == ret) {
return;
}
if (KMessageBox::No == ret) {
m_encrypt = false;
m_sign = false;
} else {
Q_EMIT disableHtml(NoConfirmationNeeded);
}
}
if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
// we can't use the state of the mail itself, to remember the
// signing and encryption state, so let's add a header instead
auto header = new KMime::Headers::Generic("X-KMail-SignatureActionEnabled");
header->fromUnicodeString(m_sign ? QStringLiteral("true") : QStringLiteral("false"), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-EncryptActionEnabled");
header->fromUnicodeString(m_encrypt ? QStringLiteral("true") : QStringLiteral("false"), "utf-8");
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-CryptoMessageFormat");
header->fromUnicodeString(QString::number(m_cryptoMessageFormat), "utf-8");
m_msg->setHeader(header);
} else {
MessageComposer::Util::removeNotNecessaryHeaders(m_msg);
}
if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget);
}
readyForSending();
}
void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
{
m_customHeader = customHeader;
}
void ComposerViewBase::readyForSending()
{
qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
if (!m_msg) {
qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
return;
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(MESSAGECOMPOSER_LOG) << "Called while composer active; ignoring.";
return;
}
// first, expand all addresses
MessageComposer::EmailAddressResolveJob *job = new MessageComposer::EmailAddressResolveJob(this);
const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
if (!identity.isNull()) {
job->setDefaultDomainName(identity.defaultDomainName());
}
job->setFrom(from());
job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
+ job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
+
connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
job->start();
}
void ComposerViewBase::slotEmailAddressResolved(KJob *job)
{
if (job->error()) {
qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
// This error could be caused by a broken search infrastructure, so we ignore it for now
// to not block sending emails completely.
}
bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
const MessageComposer::EmailAddressResolveJob *resolveJob = qobject_cast<MessageComposer::EmailAddressResolveJob *>(job);
if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
mExpandedFrom = resolveJob->expandedFrom();
mExpandedTo = resolveJob->expandedTo();
mExpandedCc = resolveJob->expandedCc();
mExpandedBcc = resolveJob->expandedBcc();
+ mExpandedReplyTo = resolveJob->expandedReplyTo();
if (autoresizeImage) {
QStringList listEmails;
listEmails << mExpandedFrom;
listEmails << mExpandedTo;
listEmails << mExpandedCc;
listEmails << mExpandedBcc;
+ listEmails << mExpandedReplyTo;
MessageComposer::Utils resizeUtils;
autoresizeImage = resizeUtils.filterRecipients(listEmails);
}
} else { // saved to draft, so keep the old values, not very nice.
mExpandedFrom = from();
foreach (const MessageComposer::Recipient::Ptr &r, m_recipientsEditor->recipients()) {
switch (r->type()) {
case MessageComposer::Recipient::To:
mExpandedTo << r->email();
break;
case MessageComposer::Recipient::Cc:
mExpandedCc << r->email();
break;
case MessageComposer::Recipient::Bcc:
mExpandedBcc << r->email();
break;
+ case MessageComposer::Recipient::ReplyTo:
+ mExpandedReplyTo << r->email();
+ break;
case MessageComposer::Recipient::Undefined:
- Q_ASSERT(!"Unknown recpient type!");
+ Q_ASSERT(!"Unknown recipient type!");
break;
}
}
- QStringList unExpandedTo, unExpandedCc, unExpandedBcc;
+ QStringList unExpandedTo, unExpandedCc, unExpandedBcc, unExpandedReplyTo;
foreach (const QString &exp, resolveJob->expandedTo()) {
if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
unExpandedTo << exp;
}
}
foreach (const QString &exp, resolveJob->expandedCc()) {
if (!mExpandedCc.contains(exp)) {
unExpandedCc << exp;
}
}
foreach (const QString &exp, resolveJob->expandedBcc()) {
if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
unExpandedBcc << exp;
}
}
+ foreach (const QString &exp, resolveJob->expandedReplyTo()) {
+ if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
+ unExpandedReplyTo << exp;
+ }
+ }
auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
header->from7BitString(unExpandedTo.join(QStringLiteral(", ")).toLatin1());
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
header->from7BitString(unExpandedCc.join(QStringLiteral(", ")).toLatin1());
m_msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
header->from7BitString(unExpandedBcc.join(QStringLiteral(", ")).toLatin1());
m_msg->setHeader(header);
+ header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
+ header->from7BitString(unExpandedReplyTo.join(QStringLiteral(", ")).toLatin1());
+ m_msg->setHeader(header);
autoresizeImage = false;
}
Q_ASSERT(m_composers.isEmpty()); //composers should be empty. The caller of this function
- //checks for emptyness before calling it
+ //checks for emptiness before calling it
//so just ensure it actually is empty
//and document it
// we first figure out if we need to create multiple messages with different crypto formats
// if so, we create a composer per format
// if we aren't signing or encrypting, this just returns a single empty message
bool wasCanceled = false;
if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
MessageComposer::Composer *composer = new MessageComposer::Composer;
composer->setNoCrypto(true);
m_composers.append(composer);
} else {
m_composers = generateCryptoMessages(wasCanceled);
if (wasCanceled) {
return;
}
}
if (m_composers.isEmpty()) {
Q_EMIT failed(i18n("It was not possible to create a message composer."));
return;
}
if (autoresizeImage) {
if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
if (m_attachmentModel) {
MessageComposer::Utils resizeUtils;
if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
const int rc = KMessageBox::warningYesNo(m_parentWidget, i18n("Do you want to resize images?"),
i18n("Auto Resize Images"), KStandardGuiItem::yes(), KStandardGuiItem::no());
if (rc == KMessageBox::Yes) {
autoresizeImage = true;
} else {
autoresizeImage = false;
}
} else {
autoresizeImage = false;
}
}
}
}
// Compose each message and prepare it for queueing, sending, or storing
foreach (MessageComposer::Composer *composer, m_composers) {
fillGlobalPart(composer->globalPart());
m_editor->fillComposerTextPart(composer->textPart());
fillInfoPart(composer->infoPart(), UseExpandedRecipients);
if (m_attachmentModel) {
composer->addAttachmentParts(m_attachmentModel->attachments(), autoresizeImage);
}
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
composer->start();
qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
}
}
namespace {
// helper methods for reading encryption settings
inline int encryptKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
return qMax(1, num);
}
inline int signingKeyNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnSignKeyNearExpiryThresholdDays();
return qMax(1, num);
}
inline int encryptRootCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
return qMax(1, num);
}
inline int signingRootCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnSignRootNearExpiryThresholdDays();
return qMax(1, num);
}
inline int encryptChainCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
return qMax(1, num);
}
inline int signingChainCertNearExpiryWarningThresholdInDays()
{
if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
return -1;
}
const int num
= MessageComposer::MessageComposerSettings::self()->cryptoWarnSignChaincertNearExpiryThresholdDays();
return qMax(1, num);
}
inline bool encryptToSelf()
{
return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf();
}
inline bool showKeyApprovalDialog()
{
return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
}
} // nameless namespace
QList< MessageComposer::Composer * > ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
{
const KIdentityManagement::Identity &id = m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
- Kleo::KeyResolver *keyResolver = new Kleo::KeyResolver(encryptToSelf(),
- showKeyApprovalDialog(),
- id.pgpAutoEncrypt(),
- m_cryptoMessageFormat,
- encryptKeyNearExpiryWarningThresholdInDays(),
- signingKeyNearExpiryWarningThresholdInDays(),
- encryptRootCertNearExpiryWarningThresholdInDays(),
- signingRootCertNearExpiryWarningThresholdInDays(),
- encryptChainCertNearExpiryWarningThresholdInDays(),
- signingChainCertNearExpiryWarningThresholdInDays());
+ QScopedPointer<Kleo::KeyResolver> keyResolver(new Kleo::KeyResolver(encryptToSelf(),
+ showKeyApprovalDialog(),
+ id.pgpAutoEncrypt(),
+ m_cryptoMessageFormat,
+ encryptKeyNearExpiryWarningThresholdInDays(),
+ signingKeyNearExpiryWarningThresholdInDays(),
+ encryptRootCertNearExpiryWarningThresholdInDays(),
+ signingRootCertNearExpiryWarningThresholdInDays(),
+ encryptChainCertNearExpiryWarningThresholdInDays(),
+ signingChainCertNearExpiryWarningThresholdInDays()));
QStringList encryptToSelfKeys;
QStringList signKeys;
bool signSomething = m_sign;
bool doSignCompletely = m_sign;
bool encryptSomething = m_encrypt;
bool doEncryptCompletely = m_encrypt;
//Add encryptionkeys from id to keyResolver
qCDebug(MESSAGECOMPOSER_LOG) << id.pgpEncryptionKey().isEmpty() << id.smimeEncryptionKey().isEmpty();
if (!id.pgpEncryptionKey().isEmpty()) {
encryptToSelfKeys.push_back(QLatin1String(id.pgpEncryptionKey()));
}
if (!id.smimeEncryptionKey().isEmpty()) {
encryptToSelfKeys.push_back(QLatin1String(id.smimeEncryptionKey()));
}
if (keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
return QList< MessageComposer::Composer * >();
}
//Add signingkeys from id to keyResolver
if (!id.pgpSigningKey().isEmpty()) {
signKeys.push_back(QLatin1String(id.pgpSigningKey()));
}
if (!id.smimeSigningKey().isEmpty()) {
signKeys.push_back(QLatin1String(id.smimeSigningKey()));
}
if (keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
return QList< MessageComposer::Composer * >();
}
if (m_attachmentModel) {
foreach (const MessageCore::AttachmentPart::Ptr &attachment, m_attachmentModel->attachments()) {
if (attachment->isSigned()) {
signSomething = true;
} else {
doEncryptCompletely = false;
}
if (attachment->isEncrypted()) {
encryptSomething = true;
} else {
doSignCompletely = false;
}
}
}
QStringList recipients(mExpandedTo), bcc(mExpandedBcc);
recipients.append(mExpandedCc);
keyResolver->setPrimaryRecipients(recipients);
keyResolver->setSecondaryRecipients(bcc);
bool result = true;
bool canceled = false;
- signSomething = determineWhetherToSign(doSignCompletely, keyResolver, signSomething, result, canceled);
+ signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
if (!result) {
// TODO handle failure
qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
if (!canceled) {
Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
} else {
Q_EMIT failed(QString());
}
wasCanceled = canceled;
return QList< MessageComposer::Composer *>();
}
canceled = false;
- encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver, encryptSomething, signSomething, result, canceled);
+ encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
if (!result) {
// TODO handle failure
qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
if (!canceled) {
Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
} else {
Q_EMIT failed(QString());
}
wasCanceled = canceled;
return QList< MessageComposer::Composer *>();
}
//No encryption or signing is needed
if (!signSomething && !encryptSomething) {
return QList< MessageComposer::Composer * >() << new MessageComposer::Composer();
}
const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
if (kpgpResult == Kleo::Canceled) {
qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
return QList< MessageComposer::Composer *>();
} else if (kpgpResult != Kleo::Ok) {
// TODO handle failure
qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
return QList< MessageComposer::Composer *>();
}
qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys:";
QList< MessageComposer::Composer * > composers;
if (encryptSomething || signSomething) {
Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
concreteFormat = concreteCryptoMessageFormats[i];
if (keyResolver->encryptionItems(concreteFormat).empty()) {
continue;
}
if (!(concreteFormat & m_cryptoMessageFormat)) {
continue;
}
MessageComposer::Composer *composer = new MessageComposer::Composer;
if (encryptSomething) {
std::vector<Kleo::KeyResolver::SplitInfo> encData = keyResolver->encryptionItems(concreteFormat);
std::vector<Kleo::KeyResolver::SplitInfo>::iterator it;
std::vector<Kleo::KeyResolver::SplitInfo>::iterator end(encData.end());
QList<QPair<QStringList, std::vector<GpgME::Key> > > data;
data.reserve(encData.size());
for (it = encData.begin(); it != end; ++it) {
QPair<QStringList, std::vector<GpgME::Key> > p(it->recipients, it->keys);
data.append(p);
qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
}
composer->setEncryptionKeys(data);
}
if (signSomething) {
// find signing keys for this format
std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
composer->setSigningKeys(signingKeys);
}
composer->setMessageCryptoFormat(concreteFormat);
composer->setSignAndEncrypt(signSomething, encryptSomething);
composers.append(composer);
}
} else {
MessageComposer::Composer *composer = new MessageComposer::Composer;
composers.append(composer);
//If we canceled sign or encrypt be sure to change status in attachment.
markAllAttachmentsForSigning(false);
markAllAttachmentsForEncryption(false);
}
if (composers.isEmpty() && (signSomething || encryptSomething)) {
Q_ASSERT_X(false, "ComposerViewBase::fillCryptoInfo", "No concrete sign or encrypt method selected");
}
return composers;
}
void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
{
globalPart->setParentWidgetForGui(m_parentWidget);
globalPart->setCharsets(m_charsets);
globalPart->setMDNRequested(m_mdnRequested);
globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
}
void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
{
// TODO splitAddressList and expandAliases ugliness should be handled by a
// special AddressListEdit widget... (later: see RecipientsEditor)
if (m_fccCombo) {
infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
} else {
if (m_fccCollection.isValid()) {
infoPart->setFcc(QString::number(m_fccCollection.id()));
}
}
infoPart->setTransportId(m_transport->currentTransportId());
- infoPart->setReplyTo(replyTo());
if (expansion == UseExpandedRecipients) {
infoPart->setFrom(mExpandedFrom);
infoPart->setTo(mExpandedTo);
infoPart->setCc(mExpandedCc);
infoPart->setBcc(mExpandedBcc);
+ infoPart->setReplyTo(mExpandedReplyTo);
} else {
infoPart->setFrom(from());
infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
+ infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
}
infoPart->setSubject(subject());
infoPart->setUserAgent(QStringLiteral("KMail"));
infoPart->setUrgent(m_urgent);
if (m_msg->inReplyTo()) {
infoPart->setInReplyTo(m_msg->inReplyTo()->asUnicodeString());
}
if (m_msg->references()) {
infoPart->setReferences(m_msg->references()->asUnicodeString());
}
KMime::Headers::Base::List extras;
if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
extras << hdr;
}
+ if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
+ extras << hdr;
+ }
if (auto hdr = m_msg->organization(false)) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-Face")) {
extras << hdr;
}
if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
extras << hdr;
}
- if (auto hdr = m_msg->headerByType("X-KMail-Dictionary")) {
+ if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
+ extras << hdr;
+ }
+ if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
extras << hdr;
}
infoPart->setExtraHeaders(extras);
}
void ComposerViewBase::slotSendComposeResult(KJob *job)
{
- qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error error" << job->error() << "errorString" << job->errorString();
Q_ASSERT(dynamic_cast< MessageComposer::Composer * >(job));
MessageComposer::Composer *composer = static_cast< MessageComposer::Composer * >(job);
+ if (composer->error() != MessageComposer::Composer::NoError) {
+ qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
+ }
if (composer->error() == MessageComposer::Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully.
qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
const int numberOfMessage(composer->resultMessages().size());
for (int i = 0; i < numberOfMessage; ++i) {
if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
queueMessage(composer->resultMessages().at(i), composer);
} else {
saveMessage(composer->resultMessages().at(i), mSaveIn);
}
}
saveRecentAddresses(composer->resultMessages().at(0));
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"));
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
QString msg;
if (composer->error() == MessageComposer::Composer::BugError) {
msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
} else {
msg = i18n("Could not compose message: %1", job->errorString());
}
Q_EMIT failed(msg);
}
m_composers.removeAll(composer);
}
void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
{
KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
foreach (const QByteArray &address, msg->to()->addresses()) {
KPIM::RecentAddresses::self(config)->add(QLatin1String(address));
}
foreach (const QByteArray &address, msg->cc()->addresses()) {
KPIM::RecentAddresses::self(config)->add(QLatin1String(address));
}
foreach (const QByteArray &address, msg->bcc()->addresses()) {
KPIM::RecentAddresses::self(config)->add(QLatin1String(address));
}
}
void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
{
const MessageComposer::InfoPart *infoPart = composer->infoPart();
MailTransport::MessageQueueJob *qjob = new MailTransport::MessageQueueJob(this);
qjob->setMessage(message);
qjob->transportAttribute().setTransportId(infoPart->transportId());
if (mSendMethod == MessageComposer::MessageSender::SendLater) {
qjob->dispatchModeAttribute().setDispatchMode(MailTransport::DispatchModeAttribute::Manual);
}
if (message->hasHeader("X-KMail-FccDisabled")) {
qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::Delete);
} else if (!infoPart->fcc().isEmpty()) {
qjob->sentBehaviourAttribute().setSentBehaviour(MailTransport::SentBehaviourAttribute::MoveToCollection);
const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
} else {
qjob->sentBehaviourAttribute().setSentBehaviour(
MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection);
}
MessageComposer::Util::addSendReplyForwardAction(message, qjob);
fillQueueJobHeaders(qjob, message, infoPart);
MessageCore::StringUtil::removePrivateHeaderFields(message, false);
QMapIterator<QByteArray, QString> customHeader(m_customHeader);
while (customHeader.hasNext()) {
customHeader.next();
auto header = new KMime::Headers::Generic(customHeader.key().constData());
header->fromUnicodeString(customHeader.value(), "utf-8");
message->setHeader(header);
}
message->assemble();
connect(qjob, &MailTransport::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
m_pendingQueueJobs++;
qjob->start();
qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
}
void ComposerViewBase::slotQueueResult(KJob *job)
{
m_pendingQueueJobs--;
MailTransport::MessageQueueJob *qjob = static_cast<MailTransport::MessageQueueJob * >(job);
qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
Q_ASSERT(m_pendingQueueJobs >= 0);
if (job->error()) {
qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
// There is not much we can do now, since all the MessageQueueJobs have been
// started. So just wait for them to finish.
// TODO show a message box or something
QString msg = i18n("There were problems trying to queue the message for sending: %1",
job->errorString());
if (m_pendingQueueJobs == 0) {
Q_EMIT failed(msg);
return;
}
}
if (m_pendingQueueJobs == 0) {
addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
Q_EMIT sentSuccessfully();
}
}
void ComposerViewBase::fillQueueJobHeaders(MailTransport::MessageQueueJob *qjob, KMime::Message::Ptr message, const MessageComposer::InfoPart *infoPart)
{
MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
if (transport && transport->specifySenderOverwriteAddress()) {
qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(transport->senderOverwriteAddress())));
} else {
qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
}
// if this header is not empty, it contains the real recipient of the message, either the primary or one of the
// secondary recipients. so we set that to the transport job, while leaving the message itself alone.
if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
qjob->addressAttribute().setTo(cleanEmailList(encodeIdn(realTo->asUnicodeString().split(QLatin1Char('%')))));
message->removeHeader("X-KMail-EncBccRecipients");
message->assemble();
qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
} else {
qjob->addressAttribute().setTo(cleanEmailList(encodeIdn(infoPart->to())));
qjob->addressAttribute().setCc(cleanEmailList(encodeIdn(infoPart->cc())));
qjob->addressAttribute().setBcc(cleanEmailList(encodeIdn(infoPart->bcc())));
}
}
void ComposerViewBase::initAutoSave()
{
- qCDebug(MESSAGECOMPOSER_LOG) << "initalising autosave";
+ qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
// Ensure that the autosave directory exists.
QDir dataDirectory(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/"));
if (!dataDirectory.exists(QStringLiteral("autosave"))) {
qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
dataDirectory.mkdir(QStringLiteral("autosave"));
}
// Construct a file name
if (m_autoSaveUUID.isEmpty()) {
m_autoSaveUUID = QUuid::createUuid().toString();
}
updateAutoSave();
}
Akonadi::Collection ComposerViewBase::followUpCollection() const
{
return mFollowUpCollection;
}
void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
{
mFollowUpCollection = followUpCollection;
}
QDate ComposerViewBase::followUpDate() const
{
return mFollowUpDate;
}
void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
{
mFollowUpDate = followUpDate;
}
Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
{
return m_dictionary;
}
void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
{
m_dictionary = dictionary;
}
void ComposerViewBase::updateAutoSave()
{
if (m_autoSaveInterval == 0) {
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
} else {
if (!m_autoSaveTimer) {
m_autoSaveTimer = new QTimer(this);
if (m_parentWidget) {
connect(m_autoSaveTimer, SIGNAL(timeout()),
m_parentWidget, SLOT(autoSaveMessage()));
} else {
connect(m_autoSaveTimer, &QTimer::timeout,
this, &ComposerViewBase::autoSaveMessage);
}
}
m_autoSaveTimer->start(m_autoSaveInterval);
}
}
void ComposerViewBase::cleanupAutoSave()
{
delete m_autoSaveTimer;
m_autoSaveTimer = nullptr;
if (!m_autoSaveUUID.isEmpty()) {
qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
// Delete the autosave files
QDir autoSaveDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/autosave"));
// Filter out only this composer window's autosave files
QStringList autoSaveFilter;
autoSaveFilter << m_autoSaveUUID + QLatin1String("*");
autoSaveDir.setNameFilters(autoSaveFilter);
// Return the files to be removed
const QStringList autoSaveFiles = autoSaveDir.entryList();
qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
// Delete each file
for (const QString &file : autoSaveFiles) {
autoSaveDir.remove(file);
}
m_autoSaveUUID.clear();
}
}
//-----------------------------------------------------------------------------
void ComposerViewBase::autoSaveMessage()
{
qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
if (m_autoSaveTimer) {
m_autoSaveTimer->stop();
}
if (!m_composers.isEmpty()) {
// This may happen if e.g. the autosave timer calls applyChanges.
qCDebug(MESSAGECOMPOSER_LOG) << "Called while composer active; ignoring.";
return;
}
MessageComposer::Composer *const composer = createSimpleComposer();
composer->setAutoSave(true);
m_composers.append(composer);
connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
composer->start();
}
void ComposerViewBase::setAutoSaveFileName(const QString &fileName)
{
m_autoSaveUUID = fileName;
Q_EMIT modified(true);
}
void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
{
using MessageComposer::Composer;
Q_ASSERT(dynamic_cast< Composer * >(job));
Composer *composer = static_cast< Composer * >(job);
if (composer->error() == Composer::NoError) {
Q_ASSERT(m_composers.contains(composer));
// The messages were composed successfully. Only save the first message, there should
// only be one anyway, since crypto is disabled.
qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
writeAutoSaveToDisk(composer->resultMessages().first());
Q_ASSERT(composer->resultMessages().size() == 1);
if (m_autoSaveInterval > 0) {
updateAutoSave();
}
} else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
// The job warned the user about something, and the user chose to return
// to the message. Nothing to do.
qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
}
m_composers.removeAll(composer);
}
void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
{
const QString autosavePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kmail2/autosave/");
QDir().mkpath(autosavePath);
const QString filename = autosavePath + m_autoSaveUUID;
QSaveFile file(filename);
QString errorMessage;
qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
if (file.open(QIODevice::WriteOnly)) {
file.setPermissions(QFile::ReadUser | QFile::WriteUser);
if (file.write(message->encodedContent())
!= static_cast<qint64>(message->encodedContent().size())) {
errorMessage = i18n("Could not write all data to file.");
} else {
if (!file.commit()) {
errorMessage = i18n("Could not finalize the file.");
}
}
} else {
errorMessage = i18n("Could not open file.");
}
if (!errorMessage.isEmpty()) {
qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
if (!m_autoSaveErrorShown) {
KMessageBox::sorry(m_parentWidget, i18n("Autosaving the message as %1 failed.\n"
"%2\n"
"Reason: %3",
filename,
errorMessage,
file.errorString()),
i18n("Autosaving Message Failed"));
// Error dialog shown, hide the errors the next time
m_autoSaveErrorShown = true;
}
} else {
// No error occurred, the next error should be shown again
m_autoSaveErrorShown = false;
}
file.commit();
}
void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
{
Akonadi::Collection target;
const KIdentityManagement::Identity identity = identityManager()->identityForUoid(m_identityCombo->currentIdentity());
message->date()->setDateTime(QDateTime::currentDateTime());
if (!identity.isNull()) {
if (auto header = message->headerByType("X-KMail-Fcc")) {
const int sentCollectionId = header->asUnicodeString().toInt();
if (identity.fcc() == QString::number(sentCollectionId)) {
message->removeHeader("X-KMail-Fcc");
}
}
}
message->assemble();
Akonadi::Item item;
item.setMimeType(QStringLiteral("message/rfc822"));
item.setPayload(message);
Akonadi::MessageFlags::copyMessageFlags(*message, item);
if (!identity.isNull()) { // we have a valid identity
if (saveIn == MessageComposer::MessageSender::SaveInTemplates) {
if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
target = Akonadi::Collection(identity.templates().toLongLong());
}
} else { //Draft
if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
target = Akonadi::Collection(identity.drafts().toLongLong());
}
}
Akonadi::CollectionFetchJob *saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
} else {
// preinitialize with the default collections
if (saveIn == MessageComposer::MessageSender::SaveInTemplates) {
target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Templates);
} else { //Draft
target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Drafts);
}
Akonadi::ItemCreateJob *create = new Akonadi::ItemCreateJob(item, target, this);
connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
++m_pendingQueueJobs;
}
}
void ComposerViewBase::slotSaveMessage(KJob *job)
{
Akonadi::Collection target;
Akonadi::Item item = job->property("Akonadi::Item").value<Akonadi::Item>();
if (job->error()) {
target = defaultSpecialTarget();
} else {
const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);
if (fetchJob->collections().isEmpty()) {
target = defaultSpecialTarget();
} else {
target = fetchJob->collections().at(0);
}
}
Akonadi::ItemCreateJob *create = new Akonadi::ItemCreateJob(item, target, this);
connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
++m_pendingQueueJobs;
}
Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
{
Akonadi::Collection target;
if (mSaveIn == MessageComposer::MessageSender::SaveInTemplates) {
target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Templates);
} else {
target = Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::Drafts);
}
return target;
}
void ComposerViewBase::slotCreateItemResult(KJob *job)
{
--m_pendingQueueJobs;
qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
Q_ASSERT(m_pendingQueueJobs >= 0);
if (job->error()) {
qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
return;
}
if (mSendLaterInfo) {
Akonadi::ItemCreateJob *createJob = static_cast<Akonadi::ItemCreateJob *>(job);
const Akonadi::Item item = createJob->item();
if (item.isValid()) {
mSendLaterInfo->setItemId(item.id());
SendLater::SendLaterUtil::writeSendLaterInfo(SendLater::SendLaterUtil::defaultConfig(), mSendLaterInfo);
delete mSendLaterInfo;
mSendLaterInfo = nullptr;
}
}
if (m_pendingQueueJobs == 0) {
Q_EMIT sentSuccessfully();
}
}
void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
{
Q_UNUSED(comment);
qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
if (sync) {
m_attachmentController->addAttachmentUrlSync(url);
} else {
m_attachmentController->addAttachment(url);
}
}
void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
{
MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
if (!data.isEmpty()) {
attachment->setName(name);
attachment->setFileName(filename);
attachment->setData(data);
attachment->setCharset(charset.toLatin1());
attachment->setMimeType(mimeType);
// TODO what about the other fields?
m_attachmentController->addAttachment(attachment);
}
}
void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
{
MessageCore::AttachmentPart::Ptr part(new MessageCore::AttachmentPart);
if (partToAttach->contentType()->mimeType() == "multipart/digest"
|| partToAttach->contentType()->mimeType() == "message/rfc822") {
// if it is a digest or a full message, use the encodedContent() of the attachment,
// which already has the proper headers
part->setData(partToAttach->encodedContent());
} else {
part->setData(partToAttach->decodedContent());
}
part->setMimeType(partToAttach->contentType()->mimeType());
if (partToAttach->contentDescription(false)) {
part->setDescription(partToAttach->contentDescription()->asUnicodeString());
}
if (partToAttach->contentType(false)) {
if (partToAttach->contentType()->hasParameter(QStringLiteral("name"))) {
part->setName(partToAttach->contentType()->parameter(QStringLiteral("name")));
}
}
if (partToAttach->contentDisposition(false)) {
part->setFileName(partToAttach->contentDisposition()->filename());
part->setInline(partToAttach->contentDisposition()->disposition() == KMime::Headers::CDinline);
}
if (part->name().isEmpty() && !part->fileName().isEmpty()) {
part->setName(part->fileName());
}
if (part->fileName().isEmpty() && !part->name().isEmpty()) {
part->setFileName(part->name());
}
m_attachmentController->addAttachment(part);
}
MessageComposer::Composer *ComposerViewBase::createSimpleComposer()
{
MessageComposer::Composer *composer = new MessageComposer::Composer;
fillGlobalPart(composer->globalPart());
m_editor->fillComposerTextPart(composer->textPart());
fillInfoPart(composer->infoPart(), UseUnExpandedRecipients);
if (m_attachmentModel) {
composer->addAttachmentParts(m_attachmentModel->attachments());
}
return composer;
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::to() const
{
- return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
+ if (m_recipientsEditor) {
+ return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
+ }
+ return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::cc() const
{
- return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
+ if (m_recipientsEditor) {
+ return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
+ }
+ return {};
}
//-----------------------------------------------------------------------------
QString ComposerViewBase::bcc() const
{
- return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
+ if (m_recipientsEditor) {
+ return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
+ }
+ return {};
}
QString ComposerViewBase::from() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_from);
}
QString ComposerViewBase::replyTo() const
{
- return MessageComposer::Util::cleanedUpHeaderString(m_replyTo);
+ if (m_recipientsEditor) {
+ return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
+ }
+ return {};
}
QString ComposerViewBase::subject() const
{
return MessageComposer::Util::cleanedUpHeaderString(m_subject);
}
void ComposerViewBase::setParentWidgetForGui(QWidget *w)
{
m_parentWidget = w;
}
void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
{
m_attachmentController = controller;
}
MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
{
return m_attachmentController;
}
void ComposerViewBase::setAttachmentModel(MessageComposer::AttachmentModel *model)
{
m_attachmentModel = model;
}
MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
{
return m_attachmentModel;
}
void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
{
m_recipientsEditor = recEditor;
}
MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
{
return m_recipientsEditor;
}
void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
{
m_signatureController = sigController;
}
MessageComposer::SignatureController *ComposerViewBase::signatureController()
{
return m_signatureController;
}
void ComposerViewBase::setIdentityCombo(KIdentityManagement::IdentityCombo *identCombo)
{
m_identityCombo = identCombo;
}
KIdentityManagement::IdentityCombo *ComposerViewBase::identityCombo()
{
return m_identityCombo;
}
void ComposerViewBase::updateRecipients(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, MessageComposer::Recipient::Type type)
{
QString oldIdentList;
QString newIdentList;
if (type == MessageComposer::Recipient::Bcc) {
oldIdentList = oldIdent.bcc();
newIdentList = ident.bcc();
} else if (type == MessageComposer::Recipient::Cc) {
oldIdentList = oldIdent.cc();
newIdentList = ident.cc();
+ } else if (type == MessageComposer::Recipient::ReplyTo) {
+ oldIdentList = oldIdent.replyToAddr();
+ newIdentList = ident.replyToAddr();
} else {
return;
}
if (oldIdentList != newIdentList) {
const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
for (const KMime::Types::Mailbox &recipient : oldRecipients) {
m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
}
const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
for (const KMime::Types::Mailbox &recipient : newRecipients) {
m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
}
m_recipientsEditor->setFocusBottom();
}
}
void ComposerViewBase::identityChanged(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, bool msgCleared)
{
updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
+ updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
KIdentityManagement::Signature oldSig = const_cast<KIdentityManagement::Identity &>
(oldIdent).signature();
KIdentityManagement::Signature newSig = const_cast<KIdentityManagement::Identity &>
(ident).signature();
//replace existing signatures
const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
// Just append the signature if there was no old signature
if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
signatureController()->applySignature(newSig);
}
const QString vcardFileName = ident.vCardFile();
attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
attachmentController()->setAttachOwnVcard(ident.attachVcard());
m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
}
void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
{
m_editor = editor;
m_editor->document()->setModified(false);
}
MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
{
return m_editor;
}
void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
{
m_transport = transpCombo;
}
MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
{
return m_transport;
}
void ComposerViewBase::setIdentityManager(KIdentityManagement::IdentityManager *identMan)
{
m_identMan = identMan;
}
KIdentityManagement::IdentityManager *ComposerViewBase::identityManager()
{
return m_identMan;
}
void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
{
if (m_fccCombo) {
m_fccCombo->setDefaultCollection(fccCollection);
} else {
m_fccCollection = fccCollection;
}
Akonadi::CollectionFetchJob *const checkFccCollectionJob
= new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
}
void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
{
if (job->error()) {
qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
const Akonadi::Collection sentMailCol
= Akonadi::SpecialMailCollections::self()->defaultCollection(Akonadi::SpecialMailCollections::SentMail);
if (m_fccCombo) {
m_fccCombo->setDefaultCollection(sentMailCol);
} else {
m_fccCollection = sentMailCol;
}
}
}
void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
{
m_fccCombo = fcc;
}
Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
{
return m_fccCombo;
}
void ComposerViewBase::setFrom(const QString &from)
{
m_from = from;
}
-void ComposerViewBase::setReplyTo(const QString &replyTo)
-{
- m_replyTo = replyTo;
-}
-
void ComposerViewBase::setSubject(const QString &subject)
{
m_subject = subject;
if (mSendLaterInfo) {
mSendLaterInfo->setSubject(m_subject);
mSendLaterInfo->setTo(to());
}
}
void ComposerViewBase::setAutoSaveInterval(int interval)
{
m_autoSaveInterval = interval;
}
void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
{
m_sign = sign;
m_encrypt = encrypt;
m_cryptoMessageFormat = format;
m_neverEncrypt = neverEncryptDrafts;
}
void ComposerViewBase::setCharsets(const QList< QByteArray > &charsets)
{
m_charsets = charsets;
}
void ComposerViewBase::setMDNRequested(bool mdnRequested)
{
m_mdnRequested = mdnRequested;
}
void ComposerViewBase::setUrgent(bool urgent)
{
m_urgent = urgent;
}
QStringList ComposerViewBase::cleanEmailList(const QStringList &emails)
{
QStringList clean;
clean.reserve(emails.count());
for (const QString &email : emails) {
clean << KEmailAddress::extractEmailAddress(email);
}
return clean;
}
int ComposerViewBase::autoSaveInterval() const
{
return m_autoSaveInterval;
}
//-----------------------------------------------------------------------------
void ComposerViewBase::collectImages(KMime::Content *root)
{
if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
KMime::Content *parentnode = n->parent();
if (parentnode
&& parentnode->contentType()->isMultipart()
&& parentnode->contentType()->subType() == "related") {
KMime::Content *node = MessageCore::NodeHelper::nextSibling(n);
while (node) {
if (node->contentType()->isImage()) {
qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
QImage img;
img.loadFromData(node->decodedContent());
m_editor->composerControler()->composerImages()->loadImage(img, QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
node->contentType()->name());
}
node = MessageCore::NodeHelper::nextSibling(node);
}
}
}
}
//-----------------------------------------------------------------------------
bool ComposerViewBase::inlineSigningEncryptionSelected()
{
if (!m_sign && !m_encrypt) {
return false;
}
return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
}
bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
{
if (attachmentKeywords.isEmpty()) {
return false;
}
if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
return false;
}
return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
}
ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
{
if (!hasMissingAttachments(attachmentKeywords)) {
return NoMissingAttachmentFound;
}
int rc = KMessageBox::warningYesNoCancel(m_editor,
i18n("The message you have composed seems to refer to an "
"attached file but you have not attached anything.\n"
"Do you want to attach a file to your message?"),
i18n("File Attachment Reminder"),
KGuiItem(i18n("&Attach File...")),
KGuiItem(i18n("&Send as Is")));
if (rc == KMessageBox::Cancel) {
return FoundMissingAttachmentAndCancel;
}
if (rc == KMessageBox::Yes) {
m_attachmentController->showAddAttachmentFileDialog();
return FoundMissingAttachmentAndAddedAttachment;
}
return FoundMissingAttachmentAndSending;
}
void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
{
if (m_attachmentModel) {
foreach (MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments()) {
attachment->setSigned(sign);
}
}
}
void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
{
if (m_attachmentModel) {
foreach (MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments()) {
attachment->setEncrypted(encrypt);
}
}
}
bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
{
bool sign = false;
switch (keyResolver->checkSigningPreferences(signSomething)) {
case Kleo::DoIt:
if (!signSomething) {
markAllAttachmentsForSigning(true);
return true;
}
sign = true;
break;
case Kleo::DontDoIt:
sign = false;
break;
case Kleo::AskOpportunistic:
assert(0);
case Kleo::Ask:
{
// the user wants to be asked or has to be asked
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = i18n("Examination of the recipient's signing preferences "
"yielded that you be asked whether or not to sign "
"this message.\n"
"Sign this message?");
switch (KMessageBox::questionYesNoCancel(m_parentWidget, msg,
i18n("Sign Message?"),
KGuiItem(i18nc("to sign", "&Sign")),
KGuiItem(i18n("Do &Not Sign")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForSigning(true);
return true;
case KMessageBox::No:
markAllAttachmentsForSigning(false);
return false;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
break;
}
case Kleo::Conflict:
{
// warn the user that there are conflicting signing preferences
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = i18n("There are conflicting signing preferences "
"for these recipients.\n"
"Sign this message?");
switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg,
i18n("Sign Message?"),
KGuiItem(i18nc("to sign", "&Sign")),
KGuiItem(i18n("Do &Not Sign")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForSigning(true);
return true;
case KMessageBox::No:
markAllAttachmentsForSigning(false);
return false;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
break;
}
case Kleo::Impossible:
{
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = i18n("You have requested to sign this message, "
"but no valid signing keys have been configured "
"for this identity.");
if (KMessageBox::warningContinueCancel(m_parentWidget, msg,
i18n("Send Unsigned?"),
KGuiItem(i18n("Send &Unsigned")))
== KMessageBox::Cancel) {
result = false;
return false;
} else {
markAllAttachmentsForSigning(false);
return false;
}
}
}
if (!sign || !doSignCompletely) {
if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned()) {
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = sign && !doSignCompletely
? i18n("Some parts of this message will not be signed.\n"
"Sending only partially signed messages might violate site policy.\n"
"Sign all parts instead?") // oh, I hate this...
: i18n("This message will not be signed.\n"
"Sending unsigned message might violate site policy.\n"
"Sign message instead?"); // oh, I hate this...
const QString buttonText = sign && !doSignCompletely
? i18n("&Sign All Parts") : i18n("&Sign");
switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg,
i18n("Unsigned-Message Warning"),
KGuiItem(buttonText),
KGuiItem(i18n("Send &As Is")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForSigning(true);
return true;
case KMessageBox::No:
return sign || doSignCompletely;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
}
}
return sign || doSignCompletely;
}
bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely, Kleo::KeyResolver *keyResolver, bool encryptSomething, bool signSomething, bool &result, bool &canceled)
{
bool encrypt = false;
bool opportunistic = false;
switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
case Kleo::DoIt:
if (!encryptSomething) {
markAllAttachmentsForEncryption(true);
return true;
}
encrypt = true;
break;
case Kleo::DontDoIt:
encrypt = false;
break;
case Kleo::AskOpportunistic:
opportunistic = true;
// fall through...
Q_FALLTHROUGH();
case Kleo::Ask:
{
// the user wants to be asked or has to be asked
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = opportunistic
? i18n("Valid trusted encryption keys were found for all recipients.\n"
"Encrypt this message?")
: i18n("Examination of the recipient's encryption preferences "
"yielded that you be asked whether or not to encrypt "
"this message.\n"
"Encrypt this message?");
switch (KMessageBox::questionYesNoCancel(m_parentWidget, msg,
i18n("Encrypt Message?"),
KGuiItem(signSomething
? i18n("Sign && &Encrypt")
: i18n("&Encrypt")),
KGuiItem(signSomething
? i18n("&Sign Only")
: i18n("&Send As-Is")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForEncryption(true);
return true;
case KMessageBox::No:
markAllAttachmentsForEncryption(false);
return false;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
break;
}
case Kleo::Conflict:
{
// warn the user that there are conflicting encryption preferences
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = i18n("There are conflicting encryption preferences "
"for these recipients.\n"
"Encrypt this message?");
switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg,
i18n("Encrypt Message?"),
KGuiItem(i18n("&Encrypt")),
KGuiItem(i18n("Do &Not Encrypt")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForEncryption(true);
return true;
case KMessageBox::No:
markAllAttachmentsForEncryption(false);
return false;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
break;
}
case Kleo::Impossible:
{
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = i18n("You have requested to encrypt this message, "
"and to encrypt a copy to yourself, "
"but no valid trusted encryption keys have been "
"configured for this identity.");
if (KMessageBox::warningContinueCancel(m_parentWidget, msg,
i18n("Send Unencrypted?"),
KGuiItem(i18n("Send &Unencrypted")))
== KMessageBox::Cancel) {
result = false;
return false;
} else {
markAllAttachmentsForEncryption(false);
return false;
}
}
}
if (!encrypt || !doEncryptCompletely) {
if (MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted()) {
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
const QString msg = !doEncryptCompletely
? i18n("Some parts of this message will not be encrypted.\n"
"Sending only partially encrypted messages might violate "
"site policy and/or leak sensitive information.\n"
"Encrypt all parts instead?") // oh, I hate this...
: i18n("This message will not be encrypted.\n"
"Sending unencrypted messages might violate site policy and/or "
"leak sensitive information.\n"
"Encrypt messages instead?"); // oh, I hate this...
const QString buttonText = !doEncryptCompletely
? i18n("&Encrypt All Parts") : i18n("&Encrypt");
switch (KMessageBox::warningYesNoCancel(m_parentWidget, msg,
i18n("Unencrypted Message Warning"),
KGuiItem(buttonText),
KGuiItem(signSomething
? i18n("&Sign Only")
: i18n("&Send As-Is")))) {
case KMessageBox::Cancel:
result = false;
canceled = true;
return false;
case KMessageBox::Yes:
markAllAttachmentsForEncryption(true);
return true;
case KMessageBox::No:
return encrypt || doEncryptCompletely;
default:
qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
return false;
}
}
}
return encrypt || doEncryptCompletely;
}
void ComposerViewBase::setSendLaterInfo(SendLater::SendLaterInfo *info)
{
delete mSendLaterInfo;
mSendLaterInfo = info;
}
SendLater::SendLaterInfo *ComposerViewBase::sendLaterInfo() const
{
return mSendLaterInfo;
}
void ComposerViewBase::addFollowupReminder(const QString &messageId)
{
if (!messageId.isEmpty()) {
if (mFollowUpDate.isValid()) {
MessageComposer::FollowupReminderCreateJob *job = new MessageComposer::FollowupReminderCreateJob;
job->setSubject(m_subject);
job->setMessageId(messageId);
- job->setTo(m_replyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : m_replyTo);
+ job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
job->setFollowUpReminderDate(mFollowUpDate);
job->setCollectionToDo(mFollowUpCollection);
job->start();
}
}
}
bool ComposerViewBase::requestDeleveryConfirmation() const
{
return m_requestDeleveryConfirmation;
}
void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
{
m_requestDeleveryConfirmation = requestDeleveryConfirmation;
}
KMime::Message::Ptr ComposerViewBase::msg() const
{
return m_msg;
}
diff --git a/messagecomposer/src/composer/composerviewbase.h b/messagecomposer/src/composer/composerviewbase.h
index ffdeb39e..3718a24a 100644
--- a/messagecomposer/src/composer/composerviewbase.h
+++ b/messagecomposer/src/composer/composerviewbase.h
@@ -1,378 +1,378 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#ifndef COMPOSER_VIEW_BASE_H
#define COMPOSER_VIEW_BASE_H
#include "messagecomposer_export.h"
#include "messagecomposer/messagesender.h"
#include "MessageComposer/Recipient"
-
#include <AkonadiCore/collection.h>
#include <KMime/Message>
#include <QObject>
#include <QUrl>
#include <Libkleo/Enum>
class QTimer;
class KJob;
class QWidget;
namespace Sonnet {
class DictionaryComboBox;
}
namespace SendLater {
class SendLaterInfo;
}
namespace Akonadi {
class CollectionComboBox;
}
namespace MailTransport {
class TransportComboBox;
class MessageQueueJob;
}
namespace KIdentityManagement {
class IdentityCombo;
class Identity;
class IdentityManager;
}
namespace Kleo {
class KeyResolver;
}
namespace MessageComposer {
class RecipientsEditor;
class RichTextComposerNg;
class InfoPart;
class GlobalPart;
class Composer;
class AttachmentControllerBase;
class AttachmentModel;
class SignatureController;
/**
*
*/
class MESSAGECOMPOSER_EXPORT ComposerViewBase : public QObject
{
Q_OBJECT
public:
explicit ComposerViewBase(QObject *parent = nullptr, QWidget *widget = nullptr);
~ComposerViewBase() override;
enum Confirmation {
LetUserConfirm, NoConfirmationNeeded
};
enum MissingAttachment {
NoMissingAttachmentFound, FoundMissingAttachmentAndSending, FoundMissingAttachmentAndAddedAttachment, FoundMissingAttachmentAndCancel
};
enum FailedType {
Sending, AutoSave
};
/**
* Set the message to be opened in the composer window, and set the internal data structures to
* keep track of it.
*/
void setMessage(const KMime::Message::Ptr &newMsg, bool allowDecryption);
void updateTemplate(const KMime::Message::Ptr &msg);
/**
* Send the message with the specified method, saving it in the specified folder.
*/
void send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher = true);
/**
* Returns true if there is at least one composer job running.
*/
Q_REQUIRED_RESULT bool isComposing() const;
/**
* Add the given attachment to the message.
*/
void addAttachment(const QUrl &url, const QString &comment, bool sync);
void addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType);
void addAttachmentPart(KMime::Content *part);
MessageComposer::Composer *createSimpleComposer();
/**
* Header fields in recipients editor.
*/
Q_REQUIRED_RESULT QString to() const;
Q_REQUIRED_RESULT QString cc() const;
Q_REQUIRED_RESULT QString bcc() const;
Q_REQUIRED_RESULT QString from() const;
Q_REQUIRED_RESULT QString replyTo() const;
Q_REQUIRED_RESULT QString subject() const;
/**
* The following are for setting the various options and widgets in the
* composer.
*/
void setAttachmentModel(MessageComposer::AttachmentModel *model);
Q_REQUIRED_RESULT MessageComposer::AttachmentModel *attachmentModel();
void setAttachmentController(MessageComposer::AttachmentControllerBase *controller);
Q_REQUIRED_RESULT MessageComposer::AttachmentControllerBase *attachmentController();
void setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor);
Q_REQUIRED_RESULT MessageComposer::RecipientsEditor *recipientsEditor();
void setSignatureController(MessageComposer::SignatureController *sigController);
Q_REQUIRED_RESULT MessageComposer::SignatureController *signatureController();
void setIdentityCombo(KIdentityManagement::IdentityCombo *identCombo);
Q_REQUIRED_RESULT KIdentityManagement::IdentityCombo *identityCombo();
void setIdentityManager(KIdentityManagement::IdentityManager *identMan);
Q_REQUIRED_RESULT KIdentityManagement::IdentityManager *identityManager();
void setEditor(MessageComposer::RichTextComposerNg *editor);
Q_REQUIRED_RESULT MessageComposer::RichTextComposerNg *editor() const;
void setTransportCombo(MailTransport::TransportComboBox *transpCombo);
Q_REQUIRED_RESULT MailTransport::TransportComboBox *transportComboBox() const;
void setFccCombo(Akonadi::CollectionComboBox *fcc);
Q_REQUIRED_RESULT Akonadi::CollectionComboBox *fccCombo() const;
void setFcc(const Akonadi::Collection &id);
Q_REQUIRED_RESULT Sonnet::DictionaryComboBox *dictionary() const;
void setDictionary(Sonnet::DictionaryComboBox *dictionary);
/**
* Widgets for editing differ in client classes, so
* values are set before sending.
*/
void setFrom(const QString &from);
- void setReplyTo(const QString &replyTo);
void setSubject(const QString &subject);
/**
* The following are various settings the user can modify when composing a message. If they are not set,
* the default values will be used.
*/
void setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts = false);
void setCharsets(const QList< QByteArray > &charsets);
void setMDNRequested(bool mdnRequested);
void setUrgent(bool urgent);
void setAutoSaveInterval(int interval);
void setCustomHeader(const QMap<QByteArray, QString> &customHeader);
/**
* Enables/disables autosaving depending on the value of the autosave
* interval.
*/
void updateAutoSave();
/**
* Sets the filename to use when autosaving something. This is used when the client recovers
* the autosave files: It calls this method, so that the composer uses the same filename again.
* That way, the recovered autosave file is properly cleaned up in cleanupAutoSave():
*/
void setAutoSaveFileName(const QString &fileName);
/**
* Stop autosaving and delete the autosaved message.
*/
void cleanupAutoSave();
void setParentWidgetForGui(QWidget *);
/**
* Check if the mail has references to attachments, but no attachments are added to it.
* If missing attachments are found, a dialog to add new attachments is shown.
* @param attachmentKeywords a list with the keywords that indicate an attachment should be present
* @return NoMissingAttachmentFound, if there is attachment in email
* FoundMissingAttachmentAndCancelSending, if mail might miss attachment but sending
* FoundMissingAttachmentAndAddedAttachment, if mail might miss attachment and we added an attachment
* FoundMissingAttachmentAndCancel, if mail might miss attachment and cancel sending
*/
ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords);
Q_REQUIRED_RESULT bool hasMissingAttachments(const QStringList &attachmentKeywords);
void setSendLaterInfo(SendLater::SendLaterInfo *info);
Q_REQUIRED_RESULT SendLater::SendLaterInfo *sendLaterInfo() const;
void saveMailSettings();
Q_REQUIRED_RESULT QDate followUpDate() const;
void setFollowUpDate(const QDate &followUpDate);
void clearFollowUp();
Q_REQUIRED_RESULT Akonadi::Collection followUpCollection() const;
void setFollowUpCollection(const Akonadi::Collection &followUpCollection);
Q_REQUIRED_RESULT KMime::Message::Ptr msg() const;
bool requestDeleveryConfirmation() const;
void setRequestDeleveryConfirmation(bool requestDeleveryConfirmation);
public Q_SLOTS:
void identityChanged(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, bool msgCleared = false);
/**
* Save the message.
*/
void autoSaveMessage();
Q_SIGNALS:
/**
* Message sending completed successfully.
*/
void sentSuccessfully();
/**
* Message sending failed with given error message.
*/
void failed(const QString &errorMessage, MessageComposer::ComposerViewBase::FailedType type = Sending);
/**
* The composer was modified. This can happen behind the users' back
* when, for example, and autosaved message was recovered.
*/
void modified(bool isModified);
/**
* Enabling or disabling HTML in the editor is affected
* by various client options, so when that would otherwise happen,
* hand it off to the client to enact it for real.
*/
void disableHtml(MessageComposer::ComposerViewBase::Confirmation);
void enableHtml();
private Q_SLOTS:
void slotEmailAddressResolved(KJob *);
void slotSendComposeResult(KJob *);
void slotQueueResult(KJob *job);
void slotCreateItemResult(KJob *);
void slotAutoSaveComposeResult(KJob *job);
void slotFccCollectionCheckResult(KJob *job);
void slotSaveMessage(KJob *job);
private:
Akonadi::Collection defaultSpecialTarget() const;
/**
* Searches the mime tree, where root is the root node, for embedded images,
* extracts them froom the body and adds them to the editor.
*/
void collectImages(KMime::Content *root);
bool inlineSigningEncryptionSelected();
/**
* Applies the user changes to the message object of the composer
* and signs/encrypts the message if activated.
* Disables the controls of the composer window.
*/
void readyForSending();
enum RecipientExpansion {
UseExpandedRecipients, UseUnExpandedRecipients
};
QList< MessageComposer::Composer * > generateCryptoMessages(bool &wasCanceled);
void fillGlobalPart(MessageComposer::GlobalPart *globalPart);
void fillInfoPart(MessageComposer::InfoPart *part, RecipientExpansion expansion);
void queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer);
void saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn);
void fillQueueJobHeaders(MailTransport::MessageQueueJob *qjob, KMime::Message::Ptr message, const MessageComposer::InfoPart *infoPart);
QStringList cleanEmailList(const QStringList &emails);
void saveRecentAddresses(const KMime::Message::Ptr &ptr);
void updateRecipients(const KIdentityManagement::Identity &ident, const KIdentityManagement::Identity &oldIdent, MessageComposer::Recipient::Type type);
void markAllAttachmentsForSigning(bool sign);
void markAllAttachmentsForEncryption(bool encrypt);
bool determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled);
bool determineWhetherToEncrypt(bool doEncryptCompletely, Kleo::KeyResolver *keyResolver, bool encryptSomething, bool signSomething, bool &result, bool &canceled);
/**
* Writes out autosave data to the disk from the KMime::Message message.
* Also appends the msgNum to the filename as a message can have a number of
* KMime::Messages
*/
void writeAutoSaveToDisk(const KMime::Message::Ptr &message);
/**
* Returns the autosave interval in milliseconds (as needed for QTimer).
*/
int autoSaveInterval() const;
/**
* Initialize autosaving (timer and filename).
*/
void initAutoSave();
void addFollowupReminder(const QString &messageId);
KMime::Message::Ptr m_msg;
MessageComposer::AttachmentControllerBase *m_attachmentController = nullptr;
MessageComposer::AttachmentModel *m_attachmentModel = nullptr;
MessageComposer::SignatureController *m_signatureController = nullptr;
MessageComposer::RecipientsEditor *m_recipientsEditor = nullptr;
KIdentityManagement::IdentityCombo *m_identityCombo = nullptr;
KIdentityManagement::IdentityManager *m_identMan = nullptr;
MessageComposer::RichTextComposerNg *m_editor = nullptr;
MailTransport::TransportComboBox *m_transport = nullptr;
Sonnet::DictionaryComboBox *m_dictionary = nullptr;
Akonadi::CollectionComboBox *m_fccCombo = nullptr;
Akonadi::Collection m_fccCollection;
QWidget *m_parentWidget = nullptr;
// List of active composer jobs. For example, saving as draft, autosaving and printing
// all create a composer, which is added to this list as long as it is active.
// Used mainly to prevent closing the window if a composer is active
QList< MessageComposer::Composer * > m_composers;
bool m_sign = false;
bool m_encrypt = false;
bool m_neverEncrypt = false;
bool m_mdnRequested = false;
bool m_urgent = false;
bool m_requestDeleveryConfirmation = false;
Kleo::CryptoMessageFormat m_cryptoMessageFormat;
- QString mExpandedFrom, m_from, m_replyTo, m_subject;
- QStringList mExpandedTo, mExpandedCc, mExpandedBcc;
+ QString mExpandedFrom;
+ QString m_from;
+ QString m_subject;
+ QStringList mExpandedTo, mExpandedCc, mExpandedBcc, mExpandedReplyTo;
QList< QByteArray > m_charsets;
QMap<QByteArray, QString> m_customHeader;
int m_pendingQueueJobs = 0;
QTimer *m_autoSaveTimer = nullptr;
QString m_autoSaveUUID;
bool m_autoSaveErrorShown = false; // Stops an error message being shown every time autosave is executed.
int m_autoSaveInterval;
MessageComposer::MessageSender::SendMethod mSendMethod;
MessageComposer::MessageSender::SaveIn mSaveIn;
QDate mFollowUpDate;
Akonadi::Collection mFollowUpCollection;
SendLater::SendLaterInfo *mSendLaterInfo = nullptr;
};
} // namespace
#endif
diff --git a/messagecomposer/src/composer/keyresolver.cpp b/messagecomposer/src/composer/keyresolver.cpp
index 6ba9f0e6..68899e89 100644
--- a/messagecomposer/src/composer/keyresolver.cpp
+++ b/messagecomposer/src/composer/keyresolver.cpp
@@ -1,2031 +1,2030 @@
/* -*- c++ -*-
keyresolver.cpp
This file is part of libkleopatra, the KDE keymanagement library
Copyright (c) 2004 Klarälvdalens Datakonsult AB
Based on kpgp.cpp
Copyright (C) 2001,2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
Libkleopatra 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.
Libkleopatra 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.
*/
#include "composer/keyresolver.h"
#include "job/savecontactpreferencejob.h"
#ifndef QT_NO_CURSOR
#include "Libkdepim/KCursorSaver"
#endif
#include "utils/kleo_util.h"
#include <KEmailAddress>
#include <Libkleo/KeySelectionDialog>
#include <Libkleo/Dn>
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <Akonadi/Contact/ContactSearchJob>
#include <KLocalizedString>
#include "messagecomposer_debug.h"
-#include <qinputdialog.h>
+#include <QInputDialog>
#include <kmessagebox.h>
#include <QStringList>
#include <QPointer>
#include <algorithm>
#include <cassert>
#include <ctime>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <set>
// this should go into stl_util.h, which has since moved into messageviewer.
// for lack of a better place put it in here for now.
namespace kdtools {
template<typename Iterator, typename UnaryPredicate>
bool any(Iterator first, Iterator last, UnaryPredicate p)
{
while (first != last) {
if (p(*first)) {
return true;
} else {
++first;
}
}
return false;
}
} // namespace kdtools
//
// some predicates to be used in STL algorithms:
//
static inline bool EmptyKeyList(const Kleo::KeyApprovalDialog::Item &item)
{
return item.keys.empty();
}
static inline QString ItemDotAddress(const Kleo::KeyResolver::Item &item)
{
return item.address;
}
static inline bool ApprovalNeeded(const Kleo::KeyResolver::Item &item)
{
return item.pref == Kleo::UnknownPreference || item.pref == Kleo::NeverEncrypt || item.keys.empty();
}
static inline Kleo::KeyResolver::Item
CopyKeysAndEncryptionPreferences(const Kleo::KeyResolver::Item &oldItem, const Kleo::KeyApprovalDialog::Item &newItem)
{
return Kleo::KeyResolver::Item(oldItem.address, newItem.keys, newItem.pref, oldItem.signPref, oldItem.format);
}
static bool ValidOpenPGPEncryptionKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::OpenPGP) {
return false;
}
if (key.isRevoked()) {
qCWarning(MESSAGECOMPOSER_LOG) << "is revoked";
}
if (key.isExpired()) {
qCWarning(MESSAGECOMPOSER_LOG) << "is expired";
}
if (key.isDisabled()) {
qCWarning(MESSAGECOMPOSER_LOG) << "is disabled";
}
if (!key.canEncrypt()) {
qCWarning(MESSAGECOMPOSER_LOG) << "can't encrypt";
}
if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
return false;
}
return true;
}
static bool ValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
{
if (!ValidOpenPGPEncryptionKey(key)) {
return false;
}
const std::vector<GpgME::UserID> uids = key.userIDs();
std::vector<GpgME::UserID>::const_iterator end(uids.end());
for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != end; ++it) {
if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
return true;
} else if (it->isRevoked()) {
qCWarning(MESSAGECOMPOSER_LOG) << "a userid is revoked";
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "bad validity" << int(it->validity());
}
}
return false;
}
static bool ValidSMIMEEncryptionKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::CMS) {
return false;
}
if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
return false;
}
return true;
}
static bool ValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
{
if (!ValidSMIMEEncryptionKey(key)) {
return false;
}
return true;
}
static inline bool ValidTrustedEncryptionKey(const GpgME::Key &key)
{
switch (key.protocol()) {
case GpgME::OpenPGP:
return ValidTrustedOpenPGPEncryptionKey(key);
case GpgME::CMS:
return ValidTrustedSMIMEEncryptionKey(key);
default:
return false;
}
}
static inline bool ValidEncryptionKey(const GpgME::Key &key)
{
switch (key.protocol()) {
case GpgME::OpenPGP:
return ValidOpenPGPEncryptionKey(key);
case GpgME::CMS:
return ValidSMIMEEncryptionKey(key);
default:
return false;
}
}
static inline bool ValidSigningKey(const GpgME::Key &key)
{
if (key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign()) {
return false;
}
return key.hasSecret();
}
static inline bool ValidOpenPGPSigningKey(const GpgME::Key &key)
{
return key.protocol() == GpgME::OpenPGP && ValidSigningKey(key);
}
static inline bool ValidSMIMESigningKey(const GpgME::Key &key)
{
return key.protocol() == GpgME::CMS && ValidSigningKey(key);
}
static inline bool NotValidTrustedOpenPGPEncryptionKey(const GpgME::Key &key)
{
return !ValidTrustedOpenPGPEncryptionKey(key);
}
static inline bool NotValidTrustedSMIMEEncryptionKey(const GpgME::Key &key)
{
return !ValidTrustedSMIMEEncryptionKey(key);
}
static inline bool NotValidTrustedEncryptionKey(const GpgME::Key &key)
{
return !ValidTrustedEncryptionKey(key);
}
static inline bool NotValidEncryptionKey(const GpgME::Key &key)
{
return !ValidEncryptionKey(key);
}
static inline bool NotValidOpenPGPSigningKey(const GpgME::Key &key)
{
return !ValidOpenPGPSigningKey(key);
}
static inline bool NotValidSMIMESigningKey(const GpgME::Key &key)
{
return !ValidSMIMESigningKey(key);
}
namespace {
struct ByTrustScore {
static int score(const GpgME::UserID &uid)
{
return uid.isRevoked() || uid.isInvalid() ? -1 : uid.validity();
}
bool operator()(const GpgME::UserID &lhs, const GpgME::UserID &rhs) const
{
return score(lhs) < score(rhs);
}
};
}
static std::vector<GpgME::UserID> matchingUIDs(const std::vector<GpgME::UserID> &uids, const QString &address)
{
if (address.isEmpty()) {
return std::vector<GpgME::UserID>();
}
std::vector<GpgME::UserID> result;
result.reserve(uids.size());
for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(), end = uids.end(); it != end; ++it) {
// PENDING(marc) check DN for an EMAIL, too, in case of X.509 certs... :/
if (const char *email = it->email()) {
if (*email && QString::fromUtf8(email).simplified().toLower() == address) {
result.push_back(*it);
}
}
}
return result;
}
static GpgME::UserID findBestMatchUID(const GpgME::Key &key, const QString &address)
{
const std::vector<GpgME::UserID> all = key.userIDs();
if (all.empty()) {
return GpgME::UserID();
}
const std::vector<GpgME::UserID> matching = matchingUIDs(all, address.toLower());
const std::vector<GpgME::UserID> &v = matching.empty() ? all : matching;
return *std::max_element(v.begin(), v.end(), ByTrustScore());
}
static QStringList keysAsStrings(const std::vector<GpgME::Key> &keys)
{
QStringList strings;
strings.reserve(keys.size());
for (std::vector<GpgME::Key>::const_iterator it = keys.begin(); it != keys.end(); ++it) {
assert(!(*it).userID(0).isNull());
QString keyLabel = QString::fromUtf8((*it).userID(0).email());
if (keyLabel.isEmpty()) {
keyLabel = QString::fromUtf8((*it).userID(0).name());
}
if (keyLabel.isEmpty()) {
keyLabel = QString::fromUtf8((*it).userID(0).id());
}
strings.append(keyLabel);
}
return strings;
}
static std::vector<GpgME::Key> trustedOrConfirmed(const std::vector<GpgME::Key> &keys, const QString &address, bool &canceled)
{
// PENDING(marc) work on UserIDs here?
std::vector<GpgME::Key> fishies;
std::vector<GpgME::Key> ickies;
std::vector<GpgME::Key> rewookies;
std::vector<GpgME::Key>::const_iterator it = keys.begin();
const std::vector<GpgME::Key>::const_iterator end = keys.end();
for (; it != end; ++it) {
const GpgME::Key &key = *it;
assert(ValidEncryptionKey(key));
const GpgME::UserID uid = findBestMatchUID(key, address);
if (uid.isRevoked()) {
rewookies.push_back(key);
}
if (!uid.isRevoked() && uid.validity() == GpgME::UserID::Marginal) {
fishies.push_back(key);
}
if (!uid.isRevoked() && uid.validity() < GpgME::UserID::Never) {
ickies.push_back(key);
}
}
if (fishies.empty() && ickies.empty() && rewookies.empty()) {
return keys;
}
// if some keys are not fully trusted, let the user confirm their use
QString msg = address.isEmpty()
? i18n("One or more of your configured OpenPGP encryption "
"keys or S/MIME certificates is not fully trusted "
"for encryption.")
: i18n("One or more of the OpenPGP encryption keys or S/MIME "
"certificates for recipient \"%1\" is not fully trusted "
"for encryption.", address);
if (!fishies.empty()) {
// certificates can't have marginal trust
msg += i18n("\nThe following keys are only marginally trusted: \n");
msg += keysAsStrings(fishies).join(QLatin1Char(','));
}
if (!ickies.empty()) {
msg += i18n("\nThe following keys or certificates have unknown trust level: \n");
msg += keysAsStrings(ickies).join(QLatin1Char(','));
}
if (!rewookies.empty()) {
msg += i18n("\nThe following keys or certificates are <b>revoked</b>: \n");
msg += keysAsStrings(rewookies).join(QLatin1Char(','));
}
if (KMessageBox::warningContinueCancel(nullptr, msg, i18n("Not Fully Trusted Encryption Keys"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("not fully trusted encryption key warning"))
== KMessageBox::Continue) {
return keys;
} else {
canceled = true;
}
return std::vector<GpgME::Key>();
}
namespace {
struct IsNotForFormat : public std::unary_function<GpgME::Key, bool> {
IsNotForFormat(Kleo::CryptoMessageFormat f) : format(f)
{
}
bool operator()(const GpgME::Key &key) const
{
return
(isOpenPGP(format) && key.protocol() != GpgME::OpenPGP)
|| (isSMIME(format) && key.protocol() != GpgME::CMS);
}
const Kleo::CryptoMessageFormat format;
};
struct IsForFormat : std::unary_function<GpgME::Key, bool> {
explicit IsForFormat(Kleo::CryptoMessageFormat f)
: protocol(isOpenPGP(f) ? GpgME::OpenPGP
: isSMIME(f) ? GpgME::CMS
: GpgME::UnknownProtocol)
{
}
bool operator()(const GpgME::Key &key) const
{
return key.protocol() == protocol;
}
const GpgME::Protocol protocol;
};
}
class Kleo::KeyResolver::SigningPreferenceCounter : public std::unary_function<Kleo::KeyResolver::Item, void>
{
public:
SigningPreferenceCounter()
: mTotal(0)
, mUnknownSigningPreference(0)
, mNeverSign(0)
, mAlwaysSign(0)
, mAlwaysSignIfPossible(0)
, mAlwaysAskForSigning(0)
, mAskSigningWheneverPossible(0)
{
}
void operator()(const Kleo::KeyResolver::Item &item);
#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; }
make_int_accessor(UnknownSigningPreference)
make_int_accessor(NeverSign)
make_int_accessor(AlwaysSign)
make_int_accessor(AlwaysSignIfPossible)
make_int_accessor(AlwaysAskForSigning)
make_int_accessor(AskSigningWheneverPossible)
make_int_accessor(Total)
#undef make_int_accessor
private:
unsigned int mTotal;
unsigned int mUnknownSigningPreference, mNeverSign, mAlwaysSign,
mAlwaysSignIfPossible, mAlwaysAskForSigning, mAskSigningWheneverPossible;
};
void Kleo::KeyResolver::SigningPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
switch (item.signPref) {
#define CASE(x) case x: \
++m ## x; break
CASE(UnknownSigningPreference);
CASE(NeverSign);
CASE(AlwaysSign);
CASE(AlwaysSignIfPossible);
CASE(AlwaysAskForSigning);
CASE(AskSigningWheneverPossible);
#undef CASE
}
++mTotal;
}
class Kleo::KeyResolver::EncryptionPreferenceCounter : public std::unary_function<Item, void>
{
const Kleo::KeyResolver *_this;
public:
EncryptionPreferenceCounter(const Kleo::KeyResolver *kr, EncryptionPreference defaultPreference)
: _this(kr)
, mDefaultPreference(defaultPreference)
, mTotal(0)
, mNoKey(0)
, mNeverEncrypt(0)
, mUnknownPreference(0)
, mAlwaysEncrypt(0)
, mAlwaysEncryptIfPossible(0)
, mAlwaysAskForEncryption(0)
, mAskWheneverPossible(0)
{
}
void operator()(Item &item);
template<typename Container>
void process(Container &c)
{
*this = std::for_each(c.begin(), c.end(), *this);
}
#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; }
make_int_accessor(NoKey)
make_int_accessor(NeverEncrypt)
make_int_accessor(UnknownPreference)
make_int_accessor(AlwaysEncrypt)
make_int_accessor(AlwaysEncryptIfPossible)
make_int_accessor(AlwaysAskForEncryption)
make_int_accessor(AskWheneverPossible)
make_int_accessor(Total)
#undef make_int_accessor
private:
EncryptionPreference mDefaultPreference;
unsigned int mTotal;
unsigned int mNoKey;
unsigned int mNeverEncrypt, mUnknownPreference, mAlwaysEncrypt,
mAlwaysEncryptIfPossible, mAlwaysAskForEncryption, mAskWheneverPossible;
};
void Kleo::KeyResolver::EncryptionPreferenceCounter::operator()(Item &item)
{
if (_this) {
if (item.needKeys) {
item.keys = _this->getEncryptionKeys(item.address, true);
}
if (item.keys.empty()) {
++mNoKey;
return;
}
}
switch (!item.pref ? mDefaultPreference : item.pref) {
#define CASE(x) case Kleo::x: \
++m ## x; break
CASE(NeverEncrypt);
CASE(UnknownPreference);
CASE(AlwaysEncrypt);
CASE(AlwaysEncryptIfPossible);
CASE(AlwaysAskForEncryption);
CASE(AskWheneverPossible);
#undef CASE
}
++mTotal;
}
namespace {
class FormatPreferenceCounterBase : public std::unary_function<Kleo::KeyResolver::Item, void>
{
public:
FormatPreferenceCounterBase()
: mTotal(0)
, mInlineOpenPGP(0)
, mOpenPGPMIME(0)
, mSMIME(0)
, mSMIMEOpaque(0)
{
}
#define make_int_accessor(x) unsigned int num ## x() const { return m ## x; \
}
make_int_accessor(Total)
make_int_accessor(InlineOpenPGP)
make_int_accessor(OpenPGPMIME)
make_int_accessor(SMIME)
make_int_accessor(SMIMEOpaque)
#undef make_int_accessor
unsigned int numOf(Kleo::CryptoMessageFormat f) const
{
switch (f) {
#define CASE(x) case Kleo::x ## Format: \
return m ## x
CASE(InlineOpenPGP);
CASE(OpenPGPMIME);
CASE(SMIME);
CASE(SMIMEOpaque);
#undef CASE
default:
return 0;
}
}
protected:
unsigned int mTotal;
unsigned int mInlineOpenPGP, mOpenPGPMIME, mSMIME, mSMIMEOpaque;
};
class EncryptionFormatPreferenceCounter : public FormatPreferenceCounterBase
{
public:
EncryptionFormatPreferenceCounter() : FormatPreferenceCounterBase()
{
}
void operator()(const Kleo::KeyResolver::Item &item);
};
class SigningFormatPreferenceCounter : public FormatPreferenceCounterBase
{
public:
SigningFormatPreferenceCounter() : FormatPreferenceCounterBase()
{
}
void operator()(const Kleo::KeyResolver::Item &item);
};
#define CASE(x) if (item.format & Kleo::x ## Format) {++m ## x;}
void EncryptionFormatPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
if (item.format & (Kleo::InlineOpenPGPFormat | Kleo::OpenPGPMIMEFormat)
&& std::find_if(item.keys.begin(), item.keys.end(),
ValidTrustedOpenPGPEncryptionKey) != item.keys.end()) { // -= trusted?
CASE(OpenPGPMIME);
CASE(InlineOpenPGP);
}
if (item.format & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat)
&& std::find_if(item.keys.begin(), item.keys.end(),
ValidTrustedSMIMEEncryptionKey) != item.keys.end()) { // -= trusted?
CASE(SMIME);
CASE(SMIMEOpaque);
}
++mTotal;
}
void SigningFormatPreferenceCounter::operator()(const Kleo::KeyResolver::Item &item)
{
CASE(InlineOpenPGP);
CASE(OpenPGPMIME);
CASE(SMIME);
CASE(SMIMEOpaque);
++mTotal;
}
#undef CASE
} // anon namespace
static QString canonicalAddress(const QString &_address)
{
const QString address = KEmailAddress::extractEmailAddress(_address);
if (!address.contains(QLatin1Char('@'))) {
// local address
//return address + '@' + KNetwork::KResolver::localHostName();
return address + QLatin1String("@localdomain");
} else {
return address;
}
}
struct FormatInfo {
std::vector<Kleo::KeyResolver::SplitInfo> splitInfos;
std::vector<GpgME::Key> signKeys;
};
struct Q_DECL_HIDDEN Kleo::KeyResolver::Private {
std::set<QByteArray> alreadyWarnedFingerprints;
std::vector<GpgME::Key> mOpenPGPSigningKeys; // signing
std::vector<GpgME::Key> mSMIMESigningKeys; // signing
std::vector<GpgME::Key> mOpenPGPEncryptToSelfKeys; // encryption to self
std::vector<GpgME::Key> mSMIMEEncryptToSelfKeys; // encryption to self
std::vector<Item> mPrimaryEncryptionKeys; // encryption to To/CC
std::vector<Item> mSecondaryEncryptionKeys; // encryption to BCC
std::map<CryptoMessageFormat, FormatInfo> mFormatInfoMap;
// key=email address, value=crypto preferences for this contact (from kabc)
typedef std::map<QString, ContactPreferences> ContactPreferencesMap;
ContactPreferencesMap mContactPreferencesMap;
};
-Kleo::KeyResolver::KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int f, int encrWarnThresholdKey, int signWarnThresholdKey, int encrWarnThresholdRootCert,
- int signWarnThresholdRootCert, int encrWarnThresholdChainCert, int signWarnThresholdChainCert)
+Kleo::KeyResolver::KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int f, int encrWarnThresholdKey, int signWarnThresholdKey, int encrWarnThresholdRootCert, int signWarnThresholdRootCert, int encrWarnThresholdChainCert, int signWarnThresholdChainCert)
: mEncryptToSelf(encToSelf)
, mShowApprovalDialog(showApproval)
, mOpportunisticEncyption(oppEncryption)
, mCryptoMessageFormats(f)
, mEncryptKeyNearExpiryWarningThreshold(encrWarnThresholdKey)
, mSigningKeyNearExpiryWarningThreshold(signWarnThresholdKey)
, mEncryptRootCertNearExpiryWarningThreshold(encrWarnThresholdRootCert)
, mSigningRootCertNearExpiryWarningThreshold(signWarnThresholdRootCert)
, mEncryptChainCertNearExpiryWarningThreshold(encrWarnThresholdChainCert)
, mSigningChainCertNearExpiryWarningThreshold(signWarnThresholdChainCert)
{
d = new Private();
}
Kleo::KeyResolver::~KeyResolver()
{
delete d;
d = nullptr;
}
Kleo::Result Kleo::KeyResolver::checkKeyNearExpiry(const GpgME::Key &key, const char *dontAskAgainName, bool mine, bool sign, bool ca, int recur_limit, const GpgME::Key &orig) const
{
if (recur_limit <= 0) {
qCDebug(MESSAGECOMPOSER_LOG) << "Key chain too long (>100 certs)";
return Kleo::Ok;
}
const GpgME::Subkey subkey = key.subkey(0);
if (d->alreadyWarnedFingerprints.count(subkey.fingerprint())) {
return Kleo::Ok; // already warned about this one (and so about it's issuers)
}
if (subkey.neverExpires()) {
return Kleo::Ok;
}
static const double secsPerDay = 24 * 60 * 60;
const double secsTillExpiry = ::difftime(subkey.expirationTime(), time(nullptr));
if (secsTillExpiry <= 0) {
const int daysSinceExpiry = 1 + int(-secsTillExpiry / secsPerDay);
qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.shortKeyID() << " expired less than "
<< daysSinceExpiry << " days ago";
const QString msg
= key.protocol() == GpgME::OpenPGP
? (mine ? sign
? ki18np("<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired less than a day ago.</p>",
"<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired less than a day ago.</p>",
"<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
"<p>expired %1 days ago.</p>"))
.subs(daysSinceExpiry)
.subs(QString::fromUtf8(key.userID(0).id()))
.subs(QString::fromLatin1(key.shortKeyID()))
.toString()
: (ca
? (key.isRoot()
? (mine ? sign
? ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The root certificate</p><p align=center><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>"))
: (mine ? sign
? ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")))
.subs(daysSinceExpiry)
.subs(Kleo::DN(orig.userID(0).id()).prettyDN())
.subs(QString::fromLatin1(orig.issuerSerial()))
.subs(Kleo::DN(key.userID(0).id()).prettyDN())
.toString()
: (mine ? sign
? ki18np("<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>")
: ki18np("<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired less than a day ago.</p>",
"<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
"<p>expired %1 days ago.</p>"))
.subs(daysSinceExpiry)
.subs(Kleo::DN(key.userID(0).id()).prettyDN())
.subs(QString::fromLatin1(key.issuerSerial()))
.toString());
d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
if (KMessageBox::warningContinueCancel(nullptr, msg,
key.protocol() == GpgME::OpenPGP
? i18n("OpenPGP Key Expired")
: i18n("S/MIME Certificate Expired"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String(dontAskAgainName)) == KMessageBox::Cancel) {
return Kleo::Canceled;
}
} else {
const int daysTillExpiry = 1 + int(secsTillExpiry / secsPerDay);
qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.shortKeyID() << "expires in less than"
<< daysTillExpiry << "days";
const int threshold
= ca
? (key.isRoot()
? (sign
? signingRootCertNearExpiryWarningThresholdInDays()
: encryptRootCertNearExpiryWarningThresholdInDays())
: (sign
? signingChainCertNearExpiryWarningThresholdInDays()
: encryptChainCertNearExpiryWarningThresholdInDays()))
: (sign
? signingKeyNearExpiryWarningThresholdInDays()
: encryptKeyNearExpiryWarningThresholdInDays());
if (threshold > -1 && daysTillExpiry <= threshold) {
const QString msg
= key.protocol() == GpgME::OpenPGP
? (mine ? sign
? ki18np("<p>Your OpenPGP signing key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than a day.</p>",
"<p>Your OpenPGP signing key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>Your OpenPGP encryption key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than a day.</p>",
"<p>Your OpenPGP encryption key</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The OpenPGP key for</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than a day.</p>",
"<p>The OpenPGP key for</p><p align=\"center\"><b>%2</b> (KeyID 0x%3)</p>"
"<p>expires in less than %1 days.</p>"))
.subs(daysTillExpiry)
.subs(QString::fromUtf8(key.userID(0).id()))
.subs(QString::fromLatin1(key.shortKeyID()))
.toString()
: (ca
? (key.isRoot()
? (mine ? sign
? ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The root certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>"))
: (mine ? sign
? ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The intermediate CA certificate</p><p align=\"center\"><b>%4</b></p>"
"<p>for S/MIME certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")))
.subs(daysTillExpiry)
.subs(Kleo::DN(orig.userID(0).id()).prettyDN())
.subs(QString::fromLatin1(orig.issuerSerial()))
.subs(Kleo::DN(key.userID(0).id()).prettyDN())
.toString()
: (mine ? sign
? ki18np("<p>Your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>Your S/MIME signing certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>Your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>Your S/MIME encryption certificate</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>")
: ki18np("<p>The S/MIME certificate for</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than a day.</p>",
"<p>The S/MIME certificate for</p><p align=\"center\"><b>%2</b> (serial number %3)</p>"
"<p>expires in less than %1 days.</p>"))
.subs(daysTillExpiry)
.subs(Kleo::DN(key.userID(0).id()).prettyDN())
.subs(QString::fromLatin1(key.issuerSerial()))
.toString());
d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
if (KMessageBox::warningContinueCancel(nullptr, msg,
key.protocol() == GpgME::OpenPGP
? i18n("OpenPGP Key Expires Soon")
: i18n("S/MIME Certificate Expires Soon"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QLatin1String(dontAskAgainName))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
}
}
if (key.isRoot()) {
return Kleo::Ok;
} else if (const char *chain_id = key.chainID()) {
const std::vector<GpgME::Key> issuer = lookup(QStringList(QLatin1String(chain_id)), false);
if (issuer.empty()) {
return Kleo::Ok;
} else {
return checkKeyNearExpiry(issuer.front(), dontAskAgainName, mine, sign,
true, recur_limit - 1, ca ? orig : key);
}
}
return Kleo::Ok;
}
Kleo::Result Kleo::KeyResolver::setEncryptToSelfKeys(const QStringList &fingerprints)
{
if (!encryptToSelf()) {
return Kleo::Ok;
}
std::vector<GpgME::Key> keys = lookup(fingerprints);
std::remove_copy_if(keys.begin(), keys.end(),
std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
NotValidTrustedOpenPGPEncryptionKey); // -= trusted?
std::remove_copy_if(keys.begin(), keys.end(),
std::back_inserter(d->mSMIMEEncryptToSelfKeys),
NotValidTrustedSMIMEEncryptionKey); // -= trusted?
if (d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size()
< keys.size()) {
// too few keys remain...
const QString msg = i18n("One or more of your configured OpenPGP encryption "
"keys or S/MIME certificates is not usable for "
"encryption. Please reconfigure your encryption keys "
"and certificates for this identity in the identity "
"configuration dialog.\n"
"If you choose to continue, and the keys are needed "
"later on, you will be prompted to specify the keys "
"to use.");
return KMessageBox::warningContinueCancel(nullptr, msg, i18n("Unusable Encryption Keys"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("unusable own encryption key warning"))
== KMessageBox::Continue ? Kleo::Ok : Kleo::Canceled;
}
// check for near-expiry:
std::vector<GpgME::Key>::const_iterator end(d->mOpenPGPEncryptToSelfKeys.end());
for (std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPEncryptToSelfKeys.begin(); it != end; ++it) {
const Kleo::Result r = checkKeyNearExpiry(*it, "own encryption key expires soon warning",
true, false);
if (r != Kleo::Ok) {
return r;
}
}
std::vector<GpgME::Key>::const_iterator end2(d->mSMIMEEncryptToSelfKeys.end());
for (std::vector<GpgME::Key>::const_iterator it = d->mSMIMEEncryptToSelfKeys.begin(); it != end2; ++it) {
const Kleo::Result r = checkKeyNearExpiry(*it, "own encryption key expires soon warning",
true, false);
if (r != Kleo::Ok) {
return r;
}
}
return Kleo::Ok;
}
Kleo::Result Kleo::KeyResolver::setSigningKeys(const QStringList &fingerprints)
{
std::vector<GpgME::Key> keys = lookup(fingerprints, true); // secret keys
std::remove_copy_if(keys.begin(), keys.end(),
std::back_inserter(d->mOpenPGPSigningKeys),
NotValidOpenPGPSigningKey);
std::remove_copy_if(keys.begin(), keys.end(),
std::back_inserter(d->mSMIMESigningKeys),
NotValidSMIMESigningKey);
if (d->mOpenPGPSigningKeys.size() + d->mSMIMESigningKeys.size() < keys.size()) {
// too few keys remain...
const QString msg = i18n("One or more of your configured OpenPGP signing keys "
"or S/MIME signing certificates is not usable for "
"signing. Please reconfigure your signing keys "
"and certificates for this identity in the identity "
"configuration dialog.\n"
"If you choose to continue, and the keys are needed "
"later on, you will be prompted to specify the keys "
"to use.");
return KMessageBox::warningContinueCancel(nullptr, msg, i18n("Unusable Signing Keys"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("unusable signing key warning"))
== KMessageBox::Continue ? Kleo::Ok : Kleo::Canceled;
}
// check for near expiry:
for (std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPSigningKeys.begin(); it != d->mOpenPGPSigningKeys.end(); ++it) {
const Kleo::Result r = checkKeyNearExpiry(*it, "signing key expires soon warning",
true, true);
if (r != Kleo::Ok) {
return r;
}
}
for (std::vector<GpgME::Key>::const_iterator it = d->mSMIMESigningKeys.begin(); it != d->mSMIMESigningKeys.end(); ++it) {
const Kleo::Result r = checkKeyNearExpiry(*it, "signing key expires soon warning",
true, true);
if (r != Kleo::Ok) {
return r;
}
}
return Kleo::Ok;
}
void Kleo::KeyResolver::setPrimaryRecipients(const QStringList &addresses)
{
d->mPrimaryEncryptionKeys = getEncryptionItems(addresses);
}
void Kleo::KeyResolver::setSecondaryRecipients(const QStringList &addresses)
{
d->mSecondaryEncryptionKeys = getEncryptionItems(addresses);
}
std::vector<Kleo::KeyResolver::Item> Kleo::KeyResolver::getEncryptionItems(const QStringList &addresses)
{
std::vector<Item> items;
items.reserve(addresses.size());
QStringList::const_iterator end(addresses.constEnd());
for (QStringList::const_iterator it = addresses.constBegin(); it != end; ++it) {
QString addr = canonicalAddress(*it).toLower();
const ContactPreferences pref = lookupContactPreferences(addr);
items.push_back(Item(*it, /*getEncryptionKeys( *it, true ),*/
pref.encryptionPreference,
pref.signingPreference,
pref.cryptoMessageFormat));
}
return items;
}
static Kleo::Action action(bool doit, bool ask, bool donot, bool requested)
{
if (requested && !donot) {
return Kleo::DoIt;
}
if (doit && !ask && !donot) {
return Kleo::DoIt;
}
if (!doit && ask && !donot) {
return Kleo::Ask;
}
if (!doit && !ask && donot) {
return requested ? Kleo::Conflict : Kleo::DontDoIt;
}
if (!doit && !ask && !donot) {
return Kleo::DontDoIt;
}
return Kleo::Conflict;
}
Kleo::Action Kleo::KeyResolver::checkSigningPreferences(bool signingRequested) const
{
if (signingRequested && d->mOpenPGPSigningKeys.empty() && d->mSMIMESigningKeys.empty()) {
return Impossible;
}
SigningPreferenceCounter count;
count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
count);
count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
count);
unsigned int sign = count.numAlwaysSign();
unsigned int ask = count.numAlwaysAskForSigning();
const unsigned int dontSign = count.numNeverSign();
if (signingPossible()) {
sign += count.numAlwaysSignIfPossible();
ask += count.numAskSigningWheneverPossible();
}
return action(sign, ask, dontSign, signingRequested);
}
bool Kleo::KeyResolver::signingPossible() const
{
return !d->mOpenPGPSigningKeys.empty() || !d->mSMIMESigningKeys.empty();
}
Kleo::Action Kleo::KeyResolver::checkEncryptionPreferences(bool encryptionRequested) const
{
if (d->mPrimaryEncryptionKeys.empty() && d->mSecondaryEncryptionKeys.empty()) {
return DontDoIt;
}
if (encryptionRequested && encryptToSelf()
&& d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty()) {
return Impossible;
}
if (!encryptionRequested && !mOpportunisticEncyption) {
// try to minimize crypto ops (including key lookups) by only
// looking up keys when at least one of the encryption
// preferences needs it:
EncryptionPreferenceCounter count(nullptr, UnknownPreference);
count.process(d->mPrimaryEncryptionKeys);
count.process(d->mSecondaryEncryptionKeys);
if (!count.numAlwaysEncrypt()
&& !count.numAlwaysAskForEncryption() // this guy might not need a lookup, when declined, but it's too complex to implement that here
&& !count.numAlwaysEncryptIfPossible()
&& !count.numAskWheneverPossible()) {
return DontDoIt;
}
}
EncryptionPreferenceCounter count(this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference);
count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
count);
count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
count);
unsigned int encrypt = count.numAlwaysEncrypt();
unsigned int ask = count.numAlwaysAskForEncryption();
const unsigned int dontEncrypt = count.numNeverEncrypt() + count.numNoKey();
if (encryptionPossible()) {
encrypt += count.numAlwaysEncryptIfPossible();
ask += count.numAskWheneverPossible();
}
const Action act = action(encrypt, ask, dontEncrypt, encryptionRequested);
if (act != Ask
|| std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
EncryptionPreferenceCounter(this, UnknownPreference))).numAlwaysAskForEncryption()) {
return act;
} else {
return AskOpportunistic;
}
}
bool Kleo::KeyResolver::encryptionPossible() const
{
return std::find_if(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
EmptyKeyList) == d->mPrimaryEncryptionKeys.end()
&& std::find_if(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
EmptyKeyList) == d->mSecondaryEncryptionKeys.end();
}
Kleo::Result Kleo::KeyResolver::resolveAllKeys(bool &signingRequested, bool &encryptionRequested)
{
if (!encryptionRequested && !signingRequested) {
// make a dummy entry with all recipients, but no signing or
// encryption keys to avoid special-casing on the caller side:
dump();
d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back(SplitInfo(allRecipients()));
dump();
return Kleo::Ok;
}
Kleo::Result result = Kleo::Ok;
if (encryptionRequested) {
bool finalySendUnencrypted = false;
result = resolveEncryptionKeys(signingRequested, finalySendUnencrypted);
if (finalySendUnencrypted) {
encryptionRequested = false;
}
}
if (result != Kleo::Ok) {
return result;
}
if (encryptionRequested) {
result = resolveSigningKeysForEncryption();
} else {
result = resolveSigningKeysForSigningOnly();
if (result == Kleo::Failure) {
signingRequested = false;
return Kleo::Ok;
}
}
return result;
}
Kleo::Result Kleo::KeyResolver::resolveEncryptionKeys(bool signingRequested, bool &finalySendUnencrypted)
{
//
// 1. Get keys for all recipients:
//
qCDebug(MESSAGECOMPOSER_LOG) << "resolving enc keys";
for (std::vector<Item>::iterator it = d->mPrimaryEncryptionKeys.begin(); it != d->mPrimaryEncryptionKeys.end(); ++it) {
qCDebug(MESSAGECOMPOSER_LOG) << "checking primary:" << it->address;
if (!it->needKeys) {
continue;
}
it->keys = getEncryptionKeys(it->address, false);
qCDebug(MESSAGECOMPOSER_LOG) << "got # keys:" << it->keys.size();
if (it->keys.empty()) {
return Kleo::Canceled;
}
QString addr = canonicalAddress(it->address).toLower();
const ContactPreferences pref = lookupContactPreferences(addr);
it->pref = pref.encryptionPreference;
it->signPref = pref.signingPreference;
it->format = pref.cryptoMessageFormat;
qCDebug(MESSAGECOMPOSER_LOG) << "set key data:" << int(it->pref) << int(it->signPref) << int(it->format);
}
for (std::vector<Item>::iterator it = d->mSecondaryEncryptionKeys.begin(); it != d->mSecondaryEncryptionKeys.end(); ++it) {
if (!it->needKeys) {
continue;
}
it->keys = getEncryptionKeys(it->address, false);
if (it->keys.empty()) {
return Kleo::Canceled;
}
QString addr = canonicalAddress(it->address).toLower();
const ContactPreferences pref = lookupContactPreferences(addr);
it->pref = pref.encryptionPreference;
it->signPref = pref.signingPreference;
it->format = pref.cryptoMessageFormat;
}
// 1a: Present them to the user
const Kleo::Result res = showKeyApprovalDialog(finalySendUnencrypted);
if (res != Kleo::Ok) {
return res;
}
//
// 2. Check what the primary recipients need
//
// 2a. Try to find a common format for all primary recipients,
// else use as many formats as needed
const EncryptionFormatPreferenceCounter primaryCount
= std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
EncryptionFormatPreferenceCounter());
CryptoMessageFormat commonFormat = AutoFormat;
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
if (!(concreteCryptoMessageFormats[i] & mCryptoMessageFormats)) {
continue;
}
if (signingRequested && signingKeysFor(concreteCryptoMessageFormats[i]).empty()) {
continue;
}
if (encryptToSelf() && encryptToSelfKeysFor(concreteCryptoMessageFormats[i]).empty()) {
continue;
}
if (primaryCount.numOf(concreteCryptoMessageFormats[i]) == primaryCount.numTotal()) {
commonFormat = concreteCryptoMessageFormats[i];
break;
}
}
qCDebug(MESSAGECOMPOSER_LOG) << "got commonFormat for primary recipients:" << int(commonFormat);
if (commonFormat != AutoFormat) {
addKeys(d->mPrimaryEncryptionKeys, commonFormat);
} else {
addKeys(d->mPrimaryEncryptionKeys);
}
collapseAllSplitInfos(); // these can be encrypted together
// 2b. Just try to find _something_ for each secondary recipient,
// with a preference to a common format (if that exists)
const EncryptionFormatPreferenceCounter secondaryCount
= std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
EncryptionFormatPreferenceCounter());
if (commonFormat != AutoFormat
&& secondaryCount.numOf(commonFormat) == secondaryCount.numTotal()) {
addKeys(d->mSecondaryEncryptionKeys, commonFormat);
} else {
addKeys(d->mSecondaryEncryptionKeys);
}
// 3. Check for expiry:
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
const std::vector<SplitInfo> si_list = encryptionItems(concreteCryptoMessageFormats[i]);
for (std::vector<SplitInfo>::const_iterator sit = si_list.begin(); sit != si_list.end(); ++sit) {
for (std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin(); kit != sit->keys.end(); ++kit) {
const Kleo::Result r = checkKeyNearExpiry(*kit, "other encryption key near expiry warning",
false, false);
if (r != Kleo::Ok) {
return r;
}
}
}
}
// 4. Check that we have the right keys for encryptToSelf()
if (!encryptToSelf()) {
return Kleo::Ok;
}
// 4a. Check for OpenPGP keys
qCDebug(MESSAGECOMPOSER_LOG) << "sizes of encryption items:" << encryptionItems(InlineOpenPGPFormat).size() << encryptionItems(OpenPGPMIMEFormat).size() << encryptionItems(SMIMEFormat).size()
<< encryptionItems(SMIMEOpaqueFormat).size();
if (!encryptionItems(InlineOpenPGPFormat).empty()
|| !encryptionItems(OpenPGPMIMEFormat).empty()) {
// need them
if (d->mOpenPGPEncryptToSelfKeys.empty()) {
const QString msg = i18n("Examination of recipient's encryption preferences "
"yielded that the message should be encrypted using "
"OpenPGP, at least for some recipients;\n"
"however, you have not configured valid trusted "
"OpenPGP encryption keys for this identity.\n"
"You may continue without encrypting to yourself, "
"but be aware that you will not be able to read your "
"own messages if you do so.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Unusable Encryption Keys"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("encrypt-to-self will fail warning"))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
// FIXME: Allow selection
}
addToAllSplitInfos(d->mOpenPGPEncryptToSelfKeys,
InlineOpenPGPFormat | OpenPGPMIMEFormat);
}
// 4b. Check for S/MIME certs:
if (!encryptionItems(SMIMEFormat).empty()
|| !encryptionItems(SMIMEOpaqueFormat).empty()) {
// need them
if (d->mSMIMEEncryptToSelfKeys.empty()) {
// don't have one
const QString msg = i18n("Examination of recipient's encryption preferences "
"yielded that the message should be encrypted using "
"S/MIME, at least for some recipients;\n"
"however, you have not configured valid "
"S/MIME encryption certificates for this identity.\n"
"You may continue without encrypting to yourself, "
"but be aware that you will not be able to read your "
"own messages if you do so.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Unusable Encryption Keys"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("encrypt-to-self will fail warning"))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
// FIXME: Allow selection
}
addToAllSplitInfos(d->mSMIMEEncryptToSelfKeys,
SMIMEFormat | SMIMEOpaqueFormat);
}
// FIXME: Present another message if _both_ OpenPGP and S/MIME keys
// are missing.
return Kleo::Ok;
}
Kleo::Result Kleo::KeyResolver::resolveSigningKeysForEncryption()
{
if ((!encryptionItems(InlineOpenPGPFormat).empty()
|| !encryptionItems(OpenPGPMIMEFormat).empty())
&& d->mOpenPGPSigningKeys.empty()) {
const QString msg = i18n("Examination of recipient's signing preferences "
"yielded that the message should be signed using "
"OpenPGP, at least for some recipients;\n"
"however, you have not configured valid "
"OpenPGP signing certificates for this identity.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Unusable Signing Keys"),
KGuiItem(i18n("Do Not OpenPGP-Sign")),
KStandardGuiItem::cancel(),
QStringLiteral("signing will fail warning"))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
// FIXME: Allow selection
}
if ((!encryptionItems(SMIMEFormat).empty()
|| !encryptionItems(SMIMEOpaqueFormat).empty())
&& d->mSMIMESigningKeys.empty()) {
const QString msg = i18n("Examination of recipient's signing preferences "
"yielded that the message should be signed using "
"S/MIME, at least for some recipients;\n"
"however, you have not configured valid "
"S/MIME signing certificates for this identity.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Unusable Signing Keys"),
KGuiItem(i18n("Do Not S/MIME-Sign")),
KStandardGuiItem::cancel(),
QStringLiteral("signing will fail warning"))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
// FIXME: Allow selection
}
// FIXME: Present another message if _both_ OpenPGP and S/MIME keys
// are missing.
for (std::map<CryptoMessageFormat, FormatInfo>::iterator it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
if (!it->second.splitInfos.empty()) {
dump();
it->second.signKeys = signingKeysFor(it->first);
dump();
}
}
return Kleo::Ok;
}
Kleo::Result Kleo::KeyResolver::resolveSigningKeysForSigningOnly()
{
//
// we don't need to distinguish between primary and secondary
// recipients here:
//
SigningFormatPreferenceCounter count;
count = std::for_each(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
count);
count = std::for_each(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
count);
// try to find a common format that works for all (and that we have signing keys for):
CryptoMessageFormat commonFormat = AutoFormat;
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
if (!(mCryptoMessageFormats & concreteCryptoMessageFormats[i])) {
continue; // skip
}
if (signingKeysFor(concreteCryptoMessageFormats[i]).empty()) {
continue; // skip
}
if (count.numOf(concreteCryptoMessageFormats[i]) == count.numTotal()) {
commonFormat = concreteCryptoMessageFormats[i];
break;
}
}
if (commonFormat != AutoFormat) { // found
dump();
FormatInfo &fi = d->mFormatInfoMap[ commonFormat ];
fi.signKeys = signingKeysFor(commonFormat);
fi.splitInfos.resize(1);
fi.splitInfos.front() = SplitInfo(allRecipients());
dump();
return Kleo::Ok;
}
const QString msg = i18n("Examination of recipient's signing preferences "
"showed no common type of signature matching your "
"available signing keys.\n"
"Send message without signing?");
if (KMessageBox::warningContinueCancel(nullptr, msg, i18n("No signing possible"),
KStandardGuiItem::cont())
== KMessageBox::Continue) {
d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back(SplitInfo(allRecipients()));
return Kleo::Failure; // means "Ok, but without signing"
}
return Kleo::Canceled;
}
std::vector<GpgME::Key> Kleo::KeyResolver::signingKeysFor(CryptoMessageFormat f) const
{
if (isOpenPGP(f)) {
return d->mOpenPGPSigningKeys;
}
if (isSMIME(f)) {
return d->mSMIMESigningKeys;
}
return std::vector<GpgME::Key>();
}
std::vector<GpgME::Key> Kleo::KeyResolver::encryptToSelfKeysFor(CryptoMessageFormat f) const
{
if (isOpenPGP(f)) {
return d->mOpenPGPEncryptToSelfKeys;
}
if (isSMIME(f)) {
return d->mSMIMEEncryptToSelfKeys;
}
return std::vector<GpgME::Key>();
}
QStringList Kleo::KeyResolver::allRecipients() const
{
QStringList result;
std::transform(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
std::back_inserter(result), ItemDotAddress);
std::transform(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
std::back_inserter(result), ItemDotAddress);
return result;
}
void Kleo::KeyResolver::collapseAllSplitInfos()
{
dump();
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
std::map<CryptoMessageFormat, FormatInfo>::iterator pos
= d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
if (pos == d->mFormatInfoMap.end()) {
continue;
}
std::vector<SplitInfo> &v = pos->second.splitInfos;
if (v.size() < 2) {
continue;
}
SplitInfo &si = v.front();
for (std::vector<SplitInfo>::const_iterator it = v.begin() + 1; it != v.end(); ++it) {
si.keys.insert(si.keys.end(), it->keys.begin(), it->keys.end());
std::copy(it->recipients.begin(), it->recipients.end(), std::back_inserter(si.recipients));
}
v.resize(1);
}
dump();
}
void Kleo::KeyResolver::addToAllSplitInfos(const std::vector<GpgME::Key> &keys, unsigned int f)
{
dump();
if (!f || keys.empty()) {
return;
}
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
if (!(f & concreteCryptoMessageFormats[i])) {
continue;
}
std::map<CryptoMessageFormat, FormatInfo>::iterator pos
= d->mFormatInfoMap.find(concreteCryptoMessageFormats[i]);
if (pos == d->mFormatInfoMap.end()) {
continue;
}
std::vector<SplitInfo> &v = pos->second.splitInfos;
for (std::vector<SplitInfo>::iterator it = v.begin(); it != v.end(); ++it) {
it->keys.insert(it->keys.end(), keys.begin(), keys.end());
}
}
dump();
}
void Kleo::KeyResolver::dump() const
{
#ifndef NDEBUG
if (d->mFormatInfoMap.empty()) {
qCDebug(MESSAGECOMPOSER_LOG) << "Keyresolver: Format info empty";
}
for (std::map<CryptoMessageFormat, FormatInfo>::const_iterator it = d->mFormatInfoMap.begin(); it != d->mFormatInfoMap.end(); ++it) {
qCDebug(MESSAGECOMPOSER_LOG) << "Format info for " << Kleo::cryptoMessageFormatToString(it->first)
<< ": Signing keys: ";
for (std::vector<GpgME::Key>::const_iterator sit = it->second.signKeys.begin(); sit != it->second.signKeys.end(); ++sit) {
qCDebug(MESSAGECOMPOSER_LOG) << " " << sit->shortKeyID() << " ";
}
unsigned int i = 0;
for (std::vector<SplitInfo>::const_iterator sit = it->second.splitInfos.begin(), sitEnd = it->second.splitInfos.end(); sit != sitEnd; ++sit, ++i) {
qCDebug(MESSAGECOMPOSER_LOG) << " SplitInfo #" << i << " encryption keys: ";
for (std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin(), sitEnd = sit->keys.end(); kit != sitEnd; ++kit) {
qCDebug(MESSAGECOMPOSER_LOG) << " " << kit->shortKeyID();
}
qCDebug(MESSAGECOMPOSER_LOG) << " SplitInfo #" << i << " recipients: "
<< qPrintable(sit->recipients.join(QStringLiteral(", ")));
}
}
#endif
}
Kleo::Result Kleo::KeyResolver::showKeyApprovalDialog(bool &finalySendUnencrypted)
{
const bool showKeysForApproval = showApprovalDialog()
|| std::find_if(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
ApprovalNeeded) != d->mPrimaryEncryptionKeys.end()
|| std::find_if(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
ApprovalNeeded) != d->mSecondaryEncryptionKeys.end();
if (!showKeysForApproval) {
return Kleo::Ok;
}
std::vector<Kleo::KeyApprovalDialog::Item> items;
items.reserve(d->mPrimaryEncryptionKeys.size()
+d->mSecondaryEncryptionKeys.size());
std::copy(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
std::back_inserter(items));
std::copy(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
std::back_inserter(items));
std::vector<GpgME::Key> senderKeys;
senderKeys.reserve(d->mOpenPGPEncryptToSelfKeys.size()
+d->mSMIMEEncryptToSelfKeys.size());
std::copy(d->mOpenPGPEncryptToSelfKeys.begin(), d->mOpenPGPEncryptToSelfKeys.end(),
std::back_inserter(senderKeys));
std::copy(d->mSMIMEEncryptToSelfKeys.begin(), d->mSMIMEEncryptToSelfKeys.end(),
std::back_inserter(senderKeys));
#ifndef QT_NO_CURSOR
const KPIM::KCursorSaver idle(KPIM::KBusyPtr::idle());
#endif
QPointer<Kleo::KeyApprovalDialog> dlg = new Kleo::KeyApprovalDialog(items, senderKeys);
if (dlg->exec() == QDialog::Rejected) {
delete dlg;
return Kleo::Canceled;
}
items = dlg->items();
senderKeys = dlg->senderKeys();
const bool prefsChanged = dlg->preferencesChanged();
delete dlg;
if (prefsChanged) {
for (uint i = 0; i < items.size(); ++i) {
ContactPreferences pref = lookupContactPreferences(items[i].address);
pref.encryptionPreference = items[i].pref;
pref.pgpKeyFingerprints.clear();
pref.smimeCertFingerprints.clear();
const std::vector<GpgME::Key> &keys = items[i].keys;
for (std::vector<GpgME::Key>::const_iterator it = keys.begin(), end = keys.end(); it != end; ++it) {
if (it->protocol() == GpgME::OpenPGP) {
if (const char *fpr = it->primaryFingerprint()) {
pref.pgpKeyFingerprints.push_back(QLatin1String(fpr));
}
} else if (it->protocol() == GpgME::CMS) {
if (const char *fpr = it->primaryFingerprint()) {
pref.smimeCertFingerprints.push_back(QLatin1String(fpr));
}
}
}
saveContactPreference(items[i].address, pref);
}
}
// show a warning if the user didn't select an encryption key for
// herself:
if (encryptToSelf() && senderKeys.empty()) {
const QString msg = i18n("You did not select an encryption key for yourself "
"(encrypt to self). You will not be able to decrypt "
"your own message if you encrypt it.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Missing Key Warning"),
KGuiItem(i18n("&Encrypt")))
== KMessageBox::Cancel) {
return Kleo::Canceled;
} else {
mEncryptToSelf = false;
}
}
// count empty key ID lists
const unsigned int emptyListCount
= std::count_if(items.begin(), items.end(), EmptyKeyList);
// show a warning if the user didn't select an encryption key for
// some of the recipients
if (items.size() == emptyListCount) {
const QString msg = (d->mPrimaryEncryptionKeys.size()
+d->mSecondaryEncryptionKeys.size() == 1)
? i18n("You did not select an encryption key for the "
"recipient of this message; therefore, the message "
"will not be encrypted.")
: i18n("You did not select an encryption key for any of the "
"recipients of this message; therefore, the message "
"will not be encrypted.");
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Missing Key Warning"),
KGuiItem(i18n("Send &Unencrypted")))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
finalySendUnencrypted = true;
} else if (emptyListCount > 0) {
const QString msg = (emptyListCount == 1)
? i18n("You did not select an encryption key for one of "
"the recipients: this person will not be able to "
"decrypt the message if you encrypt it.")
: i18n("You did not select encryption keys for some of "
"the recipients: these persons will not be able to "
"decrypt the message if you encrypt it.");
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver idle(KPIM::KBusyPtr::idle());
#endif
if (KMessageBox::warningContinueCancel(nullptr, msg,
i18n("Missing Key Warning"),
KGuiItem(i18n("&Encrypt")))
== KMessageBox::Cancel) {
return Kleo::Canceled;
}
}
std::transform(d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
items.begin(),
d->mPrimaryEncryptionKeys.begin(),
CopyKeysAndEncryptionPreferences);
std::transform(d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
items.begin() + d->mPrimaryEncryptionKeys.size(),
d->mSecondaryEncryptionKeys.begin(),
CopyKeysAndEncryptionPreferences);
d->mOpenPGPEncryptToSelfKeys.clear();
d->mSMIMEEncryptToSelfKeys.clear();
std::remove_copy_if(senderKeys.begin(), senderKeys.end(),
std::back_inserter(d->mOpenPGPEncryptToSelfKeys),
NotValidTrustedOpenPGPEncryptionKey); // -= trusted (see above, too)?
std::remove_copy_if(senderKeys.begin(), senderKeys.end(),
std::back_inserter(d->mSMIMEEncryptToSelfKeys),
NotValidTrustedSMIMEEncryptionKey); // -= trusted (see above, too)?
return Kleo::Ok;
}
std::vector<Kleo::KeyResolver::SplitInfo> Kleo::KeyResolver::encryptionItems(Kleo::CryptoMessageFormat f) const
{
dump();
std::map<CryptoMessageFormat, FormatInfo>::const_iterator it
= d->mFormatInfoMap.find(f);
return it != d->mFormatInfoMap.end() ? it->second.splitInfos : std::vector<SplitInfo>();
}
std::vector<GpgME::Key> Kleo::KeyResolver::signingKeys(CryptoMessageFormat f) const
{
dump();
std::map<CryptoMessageFormat, FormatInfo>::const_iterator it
= d->mFormatInfoMap.find(f);
return it != d->mFormatInfoMap.end() ? it->second.signKeys : std::vector<GpgME::Key>();
}
//
//
// Private helper methods below:
//
//
std::vector<GpgME::Key> Kleo::KeyResolver::selectKeys(
const QString &person, const QString &msg, const std::vector<GpgME::Key> &selectedKeys) const
{
const bool opgp = containsOpenPGP(mCryptoMessageFormats);
const bool x509 = containsSMIME(mCryptoMessageFormats);
QPointer<Kleo::KeySelectionDialog> dlg
= new Kleo::KeySelectionDialog(
- i18n("Encryption Key Selection"),
- msg, KEmailAddress::extractEmailAddress(person), selectedKeys,
- Kleo::KeySelectionDialog::ValidEncryptionKeys
- & ~(opgp ? 0 : Kleo::KeySelectionDialog::OpenPGPKeys)
- & ~(x509 ? 0 : Kleo::KeySelectionDialog::SMIMEKeys),
- true, true); // multi-selection and "remember choice" box
+ i18n("Encryption Key Selection"),
+ msg, KEmailAddress::extractEmailAddress(person), selectedKeys,
+ Kleo::KeySelectionDialog::ValidEncryptionKeys
+ & ~(opgp ? 0 : Kleo::KeySelectionDialog::OpenPGPKeys)
+ & ~(x509 ? 0 : Kleo::KeySelectionDialog::SMIMEKeys),
+ true, true); // multi-selection and "remember choice" box
if (dlg->exec() != QDialog::Accepted) {
delete dlg;
return std::vector<GpgME::Key>();
}
std::vector<GpgME::Key> keys = dlg->selectedKeys();
keys.erase(std::remove_if(keys.begin(), keys.end(),
NotValidEncryptionKey),
keys.end());
if (!keys.empty() && dlg->rememberSelection()) {
setKeysForAddress(person, dlg->pgpKeyFingerprints(), dlg->smimeFingerprints());
}
delete dlg;
return keys;
}
std::vector<GpgME::Key> Kleo::KeyResolver::getEncryptionKeys(const QString &person, bool quiet) const
{
const QString address = canonicalAddress(person).toLower();
// First look for this person's address in the address->key dictionary
const QStringList fingerprints = keysForAddress(address);
if (!fingerprints.empty()) {
qCDebug(MESSAGECOMPOSER_LOG) << "Using encryption keys 0x"
<< fingerprints.join(QStringLiteral(", 0x"))
<< "for" << person;
std::vector<GpgME::Key> keys = lookup(fingerprints);
if (!keys.empty()) {
// Check if all of the keys are trusted and valid encryption keys
if (std::find_if(keys.begin(), keys.end(),
NotValidTrustedEncryptionKey) != keys.end()) { // -= trusted?
// not ok, let the user select: this is not conditional on !quiet,
// since it's a bug in the configuration and the user should be
// notified about it as early as possible:
keys = selectKeys(person,
i18nc("if in your language something like "
"'certificate(s)' is not possible please "
"use the plural in the translation",
"There is a problem with the "
"encryption certificate(s) for \"%1\".\n\n"
"Please re-select the certificate(s) which should "
"be used for this recipient.", person),
keys);
}
bool canceled = false;
keys = trustedOrConfirmed(keys, address, canceled);
if (canceled) {
return std::vector<GpgME::Key>();
}
if (!keys.empty()) {
return keys;
}
// keys.empty() is considered cancel by callers, so go on
}
}
// Now search all public keys for matching keys
std::vector<GpgME::Key> matchingKeys = lookup(QStringList(address));
matchingKeys.erase(std::remove_if(matchingKeys.begin(), matchingKeys.end(),
NotValidEncryptionKey), matchingKeys.end());
// if called with quite == true (from EncryptionPreferenceCounter), we only want to
// check if there are keys for this recipients, not (yet) their validity, so
// don't show the untrusted encryption key warning in that case
bool canceled = false;
if (!quiet) {
matchingKeys = trustedOrConfirmed(matchingKeys, address, canceled);
}
if (canceled) {
return std::vector<GpgME::Key>();
}
if (quiet || matchingKeys.size() == 1) {
return matchingKeys;
}
// no match until now, or more than one key matches; let the user
// choose the key(s)
// FIXME: let user get the key from keyserver
return trustedOrConfirmed(selectKeys(person,
matchingKeys.empty()
? i18nc("if in your language something like "
"'certificate(s)' is not possible please "
"use the plural in the translation",
"<qt>No valid and trusted encryption certificate was "
"found for \"%1\".<br/><br/>"
"Select the certificate(s) which should "
"be used for this recipient. If there is no suitable certificate in the list "
"you can also search for external certificates by clicking the button: "
"search for external certificates.</qt>",
person.toHtmlEscaped())
: i18nc("if in your language something like "
"'certificate(s)' is not possible please "
"use the plural in the translation",
"More than one certificate matches \"%1\".\n\n"
"Select the certificate(s) which should "
"be used for this recipient.", person.toHtmlEscaped()),
matchingKeys), address, canceled);
// we can ignore 'canceled' here, since trustedOrConfirmed() returns
// an empty vector when canceled == true, and we'd just do the same
}
std::vector<GpgME::Key> Kleo::KeyResolver::lookup(const QStringList &patterns, bool secret) const
{
if (patterns.empty()) {
return std::vector<GpgME::Key>();
}
qCDebug(MESSAGECOMPOSER_LOG) << "( \"" << patterns.join(QStringLiteral("\", \"")) << "\"," << secret << ")";
std::vector<GpgME::Key> result;
if (mCryptoMessageFormats & (InlineOpenPGPFormat | OpenPGPMIMEFormat)) {
if (const QGpgME::Protocol *p = QGpgME::openpgp()) {
std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true)); // use validating keylisting
if (job.get()) {
std::vector<GpgME::Key> keys;
job->exec(patterns, secret, keys);
result.insert(result.end(), keys.begin(), keys.end());
}
}
}
if (mCryptoMessageFormats & (SMIMEFormat | SMIMEOpaqueFormat)) {
if (const QGpgME::Protocol *p = QGpgME::smime()) {
std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true)); // use validating keylisting
if (job.get()) {
std::vector<GpgME::Key> keys;
job->exec(patterns, secret, keys);
result.insert(result.end(), keys.begin(), keys.end());
}
}
}
qCDebug(MESSAGECOMPOSER_LOG) << " returned" << result.size() << "keys";
return result;
}
void Kleo::KeyResolver::addKeys(const std::vector<Item> &items, CryptoMessageFormat f)
{
dump();
for (std::vector<Item>::const_iterator it = items.begin(); it != items.end(); ++it) {
SplitInfo si(QStringList(it->address));
std::remove_copy_if(it->keys.begin(), it->keys.end(),
std::back_inserter(si.keys), IsNotForFormat(f));
dump();
if (si.keys.empty()) {
qCWarning(MESSAGECOMPOSER_LOG)
<< "Kleo::KeyResolver::addKeys(): Fix EncryptionFormatPreferenceCounter."
<< "It detected a common format, but the list of such keys for recipient \""
<< it->address << "\" is empty!";
}
d->mFormatInfoMap[ f ].splitInfos.push_back(si);
}
dump();
}
void Kleo::KeyResolver::addKeys(const std::vector<Item> &items)
{
dump();
for (std::vector<Item>::const_iterator it = items.begin(); it != items.end(); ++it) {
SplitInfo si(QStringList(it->address));
CryptoMessageFormat f = AutoFormat;
for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
const CryptoMessageFormat fmt = concreteCryptoMessageFormats[i];
if ((fmt & it->format)
&& kdtools::any(it->keys.begin(), it->keys.end(), IsForFormat(fmt))) {
f = fmt;
break;
}
}
if (f == AutoFormat) {
qCWarning(MESSAGECOMPOSER_LOG) << "Something went wrong. Didn't find a format for \""
<< it->address << "\"";
} else {
std::remove_copy_if(it->keys.begin(), it->keys.end(),
std::back_inserter(si.keys), IsNotForFormat(f));
}
d->mFormatInfoMap[ f ].splitInfos.push_back(si);
}
dump();
}
Kleo::KeyResolver::ContactPreferences Kleo::KeyResolver::lookupContactPreferences(const QString &address) const
{
#ifdef HAVE_A_FIX_FOR_LOCK
const Private::ContactPreferencesMap::iterator it
= d->mContactPreferencesMap.find(address);
if (it != d->mContactPreferencesMap.end()) {
return it->second;
}
Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
job->setLimit(1);
job->setQuery(Akonadi::ContactSearchJob::Email, address);
job->exec();
const KContacts::Addressee::List res = job->contacts();
ContactPreferences pref;
if (!res.isEmpty()) {
KContacts::Addressee addr = res.at(0);
QString encryptPref = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOENCRYPTPREF"));
pref.encryptionPreference = Kleo::stringToEncryptionPreference(encryptPref);
QString signPref = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOSIGNPREF"));
pref.signingPreference = Kleo::stringToSigningPreference(signPref);
QString cryptoFormats = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOPROTOPREF"));
pref.cryptoMessageFormat = Kleo::stringToCryptoMessageFormat(cryptoFormats);
pref.pgpKeyFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("OPENPGPFP")).split(QLatin1Char(','), QString::SkipEmptyParts);
pref.smimeCertFingerprints = addr.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("SMIMEFP")).split(QLatin1Char(','), QString::SkipEmptyParts);
}
// insert into map and grab resulting iterator
d->mContactPreferencesMap.insert(std::make_pair(address, pref));
return pref;
#else
Q_UNUSED(address)
return ContactPreferences();
#endif
}
void Kleo::KeyResolver::saveContactPreference(const QString &email, const ContactPreferences &pref) const
{
d->mContactPreferencesMap.insert(std::make_pair(email, pref));
MessageComposer::SaveContactPreferenceJob *saveContactPreferencesJob = new MessageComposer::SaveContactPreferenceJob(email, pref);
saveContactPreferencesJob->start();
}
Kleo::KeyResolver::ContactPreferences::ContactPreferences()
: encryptionPreference(UnknownPreference)
, signingPreference(UnknownSigningPreference)
, cryptoMessageFormat(AutoFormat)
{
}
QStringList Kleo::KeyResolver::keysForAddress(const QString &address) const
{
if (address.isEmpty()) {
return QStringList();
}
const QString addr = canonicalAddress(address).toLower();
const ContactPreferences pref = lookupContactPreferences(addr);
return pref.pgpKeyFingerprints + pref.smimeCertFingerprints;
}
void Kleo::KeyResolver::setKeysForAddress(const QString &address, const QStringList &pgpKeyFingerprints, const QStringList &smimeCertFingerprints) const
{
if (address.isEmpty()) {
return;
}
const QString addr = canonicalAddress(address).toLower();
ContactPreferences pref = lookupContactPreferences(addr);
pref.pgpKeyFingerprints = pgpKeyFingerprints;
pref.smimeCertFingerprints = smimeCertFingerprints;
saveContactPreference(addr, pref);
}
bool Kleo::KeyResolver::encryptToSelf() const
{
return mEncryptToSelf;
}
bool Kleo::KeyResolver::showApprovalDialog() const
{
return mShowApprovalDialog;
}
int Kleo::KeyResolver::encryptKeyNearExpiryWarningThresholdInDays() const
{
return mEncryptKeyNearExpiryWarningThreshold;
}
int Kleo::KeyResolver::signingKeyNearExpiryWarningThresholdInDays() const
{
return mSigningKeyNearExpiryWarningThreshold;
}
int Kleo::KeyResolver::encryptRootCertNearExpiryWarningThresholdInDays() const
{
return mEncryptRootCertNearExpiryWarningThreshold;
}
int Kleo::KeyResolver::signingRootCertNearExpiryWarningThresholdInDays() const
{
return mSigningRootCertNearExpiryWarningThreshold;
}
int Kleo::KeyResolver::encryptChainCertNearExpiryWarningThresholdInDays() const
{
return mEncryptChainCertNearExpiryWarningThreshold;
}
int Kleo::KeyResolver::signingChainCertNearExpiryWarningThresholdInDays() const
{
return mSigningChainCertNearExpiryWarningThreshold;
}
diff --git a/messagecomposer/src/composer/keyresolver.h b/messagecomposer/src/composer/keyresolver.h
index 9777d677..b3429f9a 100644
--- a/messagecomposer/src/composer/keyresolver.h
+++ b/messagecomposer/src/composer/keyresolver.h
@@ -1,316 +1,314 @@
/* -*- c++ -*-
keyresolver.h
This file is part of libkleopatra, the KDE keymanagement library
Copyright (c) 2004 Klarälvdalens Datakonsult AB
Based on kpgp.h
Copyright (C) 2001,2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
Libkleopatra 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.
Libkleopatra 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.
*/
#ifndef KLEO_KEYRESOLVER_H
#define KLEO_KEYRESOLVER_H
#include "messagecomposer_export.h"
#include <Libkleo/KeyApprovalDialog>
#include <Libkleo/Enum>
#include <gpgme++/key.h>
#include <vector>
class QStringList;
namespace Kleo {
enum Result {
Failure = 0,
Ok = 1,
Canceled = 2
};
/**
\short A class to resolve signing/encryption keys w.r.t. per-recipient preferences
\section Step 1: Set the information needed
The constructor takes some basic options as arguments, such as
whether or not encryption was actually requested. Recipient and
sender information is then set by using \c
setEncryptToSelfKeys(), \c setSigningKeys(), \c
setPrimaryRecipients() (To/Cc) and \c setSecondaryRecipients()
(Bcc).
\section Step 2: Lookup and check per-recipient crypto preferences / Opportunistic Encryption
First, \c checkSigningPreferences() goes through all recipient's
- signing perferences, to determine whether or not to sign. It also
+ signing preferences, to determine whether or not to sign. It also
takes into account the available signing keys and whether or not
the user explicitly requested signing.
\c checkEncryptionPreferences() does the same for encryption
preferences. If opportunistic encryption is enabled, recipients
without encryption preferences set are treated as if they had a
preference of \c AskWheneverPossible.
In both cases an Action code is returned, with the following
meanings:
<dl><dt>Conflict</dt><dd>A conflict was detected. E.g. one
recipient's preference was set to "always encrypt", while another
one's preference was set to "never encrypt". You should ask the
user what to do.</dd></dt>
<dt>DoIt, DontDoIt</dt><dd>Do/Don't sign/encrypt</dd>
<dt>Ask</dt><dd>(Some) crypto preferences request to prompt the
user, so do it.</dd>
<dt>Impossible</dt><dd>Signing or encryption is impossible,
e.g. due to missing keys or unsupported formats.</dd> </dl>
\section Step 3: Resolve all keys.
In case signing or encryption was implicitly or explicitly
requested by the user, \c resolveAllKeys() tries to find signing
keys for each required format, as well as encryption keys for all
recipients (incl. the sender, if encrypt-to-self is set).
\section Step 4: Get signing keys.
If, after key resolving, signing is still requested and
apparently possible, you can get the result of all this by
iterating over the available message formats and retrieving the
set of signing keys to use with a call to \c signingKeys().
- \section Step 5: Get encrytion key sets.
+ \section Step 5: Get encryption key sets.
If after key resolving, encryption is still requested and
apparently possible, you can get the result of all this by
calling \c encryptionItems() with the current message format at
hand as its argument.
This will return a list of recipient-list/key-list pairs that
each describe a copy of the (possibly signed) message to be
encrypted independantly.
Note that it's only necessary to sign the message once for each
message format, although it might be necessary to create more
than one message when encrypting. This is because encryption
allows the recipients to learn about the other recipients the
message was encrypted to, so each secondary (BCC) recipient need
a copy of it's own to hide the other secondary recipients.
*/
class KeyResolver
{
public:
- KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int format, int encrKeyNearExpiryThresholdDays, int signKeyNearExpiryThresholdDays,
- int encrRootCertNearExpiryThresholdDays, int signRootCertNearExpiryThresholdDays, int encrChainCertNearExpiryThresholdDays, int signChainCertNearExpiryThresholdDays);
+ KeyResolver(bool encToSelf, bool showApproval, bool oppEncryption, unsigned int format, int encrKeyNearExpiryThresholdDays, int signKeyNearExpiryThresholdDays, int encrRootCertNearExpiryThresholdDays, int signRootCertNearExpiryThresholdDays, int encrChainCertNearExpiryThresholdDays, int signChainCertNearExpiryThresholdDays);
~KeyResolver();
struct ContactPreferences {
ContactPreferences();
Kleo::EncryptionPreference encryptionPreference;
Kleo::SigningPreference signingPreference;
Kleo::CryptoMessageFormat cryptoMessageFormat;
QStringList pgpKeyFingerprints;
QStringList smimeCertFingerprints;
};
struct Item : public KeyApprovalDialog::Item {
Item()
: KeyApprovalDialog::Item()
, signPref(UnknownSigningPreference)
, format(AutoFormat)
, needKeys(true)
{
}
Item(const QString &a, EncryptionPreference e, SigningPreference s, CryptoMessageFormat f)
: KeyApprovalDialog::Item(a, std::vector<GpgME::Key>(), e)
, signPref(s)
, format(f)
, needKeys(true)
{
}
Item(const QString &a, const std::vector<GpgME::Key> &k, EncryptionPreference e, SigningPreference s, CryptoMessageFormat f)
: KeyApprovalDialog::Item(a, k, e)
, signPref(s)
, format(f)
, needKeys(false)
{
}
SigningPreference signPref;
CryptoMessageFormat format;
bool needKeys;
};
/**
Set the fingerprints of keys to be used for encrypting to
self. Also looks them up and complains if they're not usable or
found.
*/
Q_REQUIRED_RESULT Kleo::Result setEncryptToSelfKeys(const QStringList &fingerprints);
/**
Set the fingerprints of keys to be used for signing. Also
looks them up and complains if they're not usable or found.
*/
Q_REQUIRED_RESULT Kleo::Result setSigningKeys(const QStringList &fingerprints);
/**
Set the list of primary (To/CC) recipient addresses. Also looks
up possible keys, but doesn't interact with the user.
*/
void setPrimaryRecipients(const QStringList &addresses);
/**
Set the list of secondary (BCC) recipient addresses. Also looks
up possible keys, but doesn't interact with the user.
*/
void setSecondaryRecipients(const QStringList &addresses);
/**
Determine whether to sign or not, depending on the
per-recipient signing preferences, as well as the availability
of usable signing keys.
*/
Q_REQUIRED_RESULT Action checkSigningPreferences(bool signingRequested) const;
/**
Determine whether to encrypt or not, depending on the
per-recipient encryption preferences, as well as the availability
of usable encryption keys.
*/
Q_REQUIRED_RESULT Action checkEncryptionPreferences(bool encryptionRequested) const;
/**
Queries the user for missing keys and displays a key approval
dialog if needed.
*/
Q_REQUIRED_RESULT Kleo::Result resolveAllKeys(bool &signingRequested, bool &encryptionRequested);
/**
@return the signing keys to use (if any) for the given message
format.
*/
Q_REQUIRED_RESULT std::vector<GpgME::Key> signingKeys(CryptoMessageFormat f) const;
struct SplitInfo {
SplitInfo()
{
}
SplitInfo(const QStringList &r) : recipients(r)
{
}
SplitInfo(const QStringList &r, const std::vector<GpgME::Key> &k)
: recipients(r)
, keys(k)
{
}
QStringList recipients;
std::vector<GpgME::Key> keys;
};
/** @return the found distinct sets of items for format \a f. The
returned vector will contain more than one item only if
secondary recipients have been specified.
*/
Q_REQUIRED_RESULT std::vector<SplitInfo> encryptionItems(CryptoMessageFormat f) const;
private:
void dump() const;
std::vector<Item> getEncryptionItems(const QStringList &recipients);
std::vector<GpgME::Key> getEncryptionKeys(const QString &recipient, bool quiet) const;
Kleo::Result showKeyApprovalDialog(bool &finalySendUnencrypted);
bool encryptionPossible() const;
bool signingPossible() const;
Kleo::Result resolveEncryptionKeys(bool signingRequested, bool &finalySendUnencrypted);
Kleo::Result resolveSigningKeysForEncryption();
Kleo::Result resolveSigningKeysForSigningOnly();
- Kleo::Result checkKeyNearExpiry(const GpgME::Key &key, const char *dontAskAgainName, bool mine, bool sign, bool ca = false, int recurse_limit = 100,
- const GpgME::Key &orig_key = GpgME::Key::null) const;
+ Kleo::Result checkKeyNearExpiry(const GpgME::Key &key, const char *dontAskAgainName, bool mine, bool sign, bool ca = false, int recurse_limit = 100, const GpgME::Key &orig_key = GpgME::Key::null) const;
void collapseAllSplitInfos();
void addToAllSplitInfos(const std::vector<GpgME::Key> &keys, unsigned int formats);
void addKeys(const std::vector<Item> &items, CryptoMessageFormat f);
void addKeys(const std::vector<Item> &items);
QStringList allRecipients() const;
std::vector<GpgME::Key> signingKeysFor(CryptoMessageFormat f) const;
std::vector<GpgME::Key> encryptToSelfKeysFor(CryptoMessageFormat f) const;
std::vector<GpgME::Key> lookup(const QStringList &patterns, bool secret = false) const;
bool haveTrustedEncryptionKey(const QString &person) const;
std::vector<GpgME::Key> selectKeys(const QString &person, const QString &msg, const std::vector<GpgME::Key> &selectedKeys = std::vector<GpgME::Key>()) const;
QStringList keysForAddress(const QString &address) const;
void setKeysForAddress(const QString &address, const QStringList &pgpKeyFingerprints, const QStringList &smimeCertFingerprints) const;
bool encryptToSelf() const;
bool showApprovalDialog() const;
int encryptKeyNearExpiryWarningThresholdInDays() const;
int signingKeyNearExpiryWarningThresholdInDays() const;
int encryptRootCertNearExpiryWarningThresholdInDays() const;
int signingRootCertNearExpiryWarningThresholdInDays() const;
int encryptChainCertNearExpiryWarningThresholdInDays() const;
int signingChainCertNearExpiryWarningThresholdInDays() const;
ContactPreferences lookupContactPreferences(const QString &address) const;
void saveContactPreference(const QString &email, const ContactPreferences &pref) const;
private:
class EncryptionPreferenceCounter;
friend class ::Kleo::KeyResolver::EncryptionPreferenceCounter;
class SigningPreferenceCounter;
friend class ::Kleo::KeyResolver::SigningPreferenceCounter;
struct Private;
Private *d;
bool mEncryptToSelf;
const bool mShowApprovalDialog : 1;
const bool mOpportunisticEncyption : 1;
const unsigned int mCryptoMessageFormats;
const int mEncryptKeyNearExpiryWarningThreshold;
const int mSigningKeyNearExpiryWarningThreshold;
const int mEncryptRootCertNearExpiryWarningThreshold;
const int mSigningRootCertNearExpiryWarningThreshold;
const int mEncryptChainCertNearExpiryWarningThreshold;
const int mSigningChainCertNearExpiryWarningThreshold;
};
} // namespace Kleo
#endif // KLEO_KEYRESOLVER_H
diff --git a/messagecomposer/src/composer/signaturecontroller.h b/messagecomposer/src/composer/signaturecontroller.h
index 9879085f..958ca0d4 100644
--- a/messagecomposer/src/composer/signaturecontroller.h
+++ b/messagecomposer/src/composer/signaturecontroller.h
@@ -1,104 +1,104 @@
/*
* Copyright (c) 2010 Volker Krause <vkrause@kde.org>
*
* Based on kmail/kmcomposewin.cpp
* Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
*
* Based on KMail code by:
* Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
*
* 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.
*/
#ifndef MESSAGECOMPSER_SIGNATURECONTROLLER_H
#define MESSAGECOMPSER_SIGNATURECONTROLLER_H
#include "messagecomposer_export.h"
#include <QObject>
#include <KIdentityManagement/kidentitymanagement/signature.h>
namespace KIdentityManagement {
class IdentityCombo;
}
namespace MessageComposer {
class RichTextComposerNg;
/** Controls signature (the footer thing, not the crypto thing) operations
- * happening on a KMEditor triggerd by identity selection or menu actions.
+ * happening on a KMEditor triggered by identity selection or menu actions.
* @since 4.5
*/
class SignatureControllerPrivate;
class MESSAGECOMPOSER_EXPORT SignatureController : public QObject
{
Q_OBJECT
public:
explicit SignatureController(QObject *parent = nullptr);
~SignatureController();
void setEditor(MessageComposer::RichTextComposerNg *editor);
void setIdentityCombo(KIdentityManagement::IdentityCombo *combo);
/** Temporarily disable identity tracking, useful for initial loading for example. */
void suspend();
/** Resume identity change tracking after a previous call to suspend(). */
void resume();
/** Adds the given signature to the editor, taking user preferences into account.
*/
void applySignature(const KIdentityManagement::Signature &signature);
public Q_SLOTS:
/**
* Append signature to the end of the text in the editor.
*/
void appendSignature();
/**
* Prepend signature at the beginning of the text in the editor.
*/
void prependSignature();
/**
* Insert signature at the cursor position of the text in the editor.
*/
void insertSignatureAtCursor();
void cleanSpace();
Q_SIGNALS:
/**
* A HTML signature is about to be inserted, so enable HTML support in the editor.
*/
void enableHtml();
void signatureAdded();
private:
/**
* Helper to insert the signature of the current identity arbitrarily
* in the editor, connecting slot functions to KMeditor::insertSignature().
* @param placement the position of the signature
*/
void insertSignatureHelper(KIdentityManagement::Signature::Placement placement);
private Q_SLOTS:
void identityChanged(uint id);
private:
SignatureControllerPrivate *const d;
};
}
#endif
diff --git a/messagecomposer/src/config-messagecomposer.h.cmake b/messagecomposer/src/config-messagecomposer.h.cmake
deleted file mode 100644
index 37dd0e3a..00000000
--- a/messagecomposer/src/config-messagecomposer.h.cmake
+++ /dev/null
@@ -1 +0,0 @@
-#cmakedefine KDEPIM_TEMPLATEPARSER_ASYNC_BUILD 1
diff --git a/messagecomposer/src/followupreminder/followupremindercreatejob.cpp b/messagecomposer/src/followupreminder/followupremindercreatejob.cpp
index 52871f10..cd4fdaf6 100644
--- a/messagecomposer/src/followupreminder/followupremindercreatejob.cpp
+++ b/messagecomposer/src/followupreminder/followupremindercreatejob.cpp
@@ -1,124 +1,124 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "followupremindercreatejob.h"
#include "messagecomposer_debug.h"
#include "FollowupReminder/FollowUpReminderUtil"
#include <KCalCore/Todo>
#include <KLocalizedString>
#include <AkonadiCore/ItemCreateJob>
using namespace MessageComposer;
class MessageComposer::FollowupReminderCreateJobPrivate
{
public:
FollowupReminderCreateJobPrivate()
: mInfo(new FollowUpReminder::FollowUpReminderInfo)
{
}
~FollowupReminderCreateJobPrivate()
{
delete mInfo;
}
Akonadi::Collection mCollection;
FollowUpReminder::FollowUpReminderInfo *mInfo = nullptr;
};
FollowupReminderCreateJob::FollowupReminderCreateJob(QObject *parent)
: KJob(parent)
, d(new MessageComposer::FollowupReminderCreateJobPrivate)
{
}
FollowupReminderCreateJob::~FollowupReminderCreateJob()
{
delete d;
}
void FollowupReminderCreateJob::setFollowUpReminderDate(const QDate &date)
{
d->mInfo->setFollowUpReminderDate(date);
}
void FollowupReminderCreateJob::setOriginalMessageItemId(Akonadi::Item::Id value)
{
d->mInfo->setOriginalMessageItemId(value);
}
void FollowupReminderCreateJob::setMessageId(const QString &messageId)
{
d->mInfo->setMessageId(messageId);
}
void FollowupReminderCreateJob::setTo(const QString &to)
{
d->mInfo->setTo(to);
}
void FollowupReminderCreateJob::setSubject(const QString &subject)
{
d->mInfo->setSubject(subject);
}
void FollowupReminderCreateJob::setCollectionToDo(const Akonadi::Collection &collection)
{
d->mCollection = collection;
}
void FollowupReminderCreateJob::start()
{
if (d->mInfo->isValid()) {
if (d->mCollection.isValid()) {
KCalCore::Todo::Ptr todo(new KCalCore::Todo);
todo->setSummary(i18n("Wait answer from \"%1\" send to \"%2\"", d->mInfo->subject(), d->mInfo->to()));
- todo->setDtDue(QDateTime(d->mInfo->followUpReminderDate(), QTime(0,0,0)));
+ todo->setDtDue(QDateTime(d->mInfo->followUpReminderDate(), QTime(0, 0, 0)));
Akonadi::Item newTodoItem;
newTodoItem.setMimeType(KCalCore::Todo::todoMimeType());
newTodoItem.setPayload<KCalCore::Todo::Ptr>(todo);
Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(newTodoItem, d->mCollection);
connect(createJob, &Akonadi::ItemCreateJob::result, this, &FollowupReminderCreateJob::slotCreateNewTodo);
} else {
writeFollowupReminderInfo();
}
} else {
qCDebug(MESSAGECOMPOSER_LOG) << "FollowupReminderCreateJob info not valid " << *d->mInfo;
emitResult();
return;
}
}
void FollowupReminderCreateJob::slotCreateNewTodo(KJob *job)
{
if (job->error()) {
qCDebug(MESSAGECOMPOSER_LOG) << "Error during create new Todo " << job->errorString();
} else {
Akonadi::ItemCreateJob *createJob = qobject_cast<Akonadi::ItemCreateJob *>(job);
d->mInfo->setTodoId(createJob->item().id());
}
writeFollowupReminderInfo();
}
void FollowupReminderCreateJob::writeFollowupReminderInfo()
{
FollowUpReminder::FollowUpReminderUtil::writeFollowupReminderInfo(FollowUpReminder::FollowUpReminderUtil::defaultConfig(), d->mInfo, true);
emitResult();
}
diff --git a/messagecomposer/src/followupreminder/followupremindercreatejob.h b/messagecomposer/src/followupreminder/followupremindercreatejob.h
index 4ff17914..f546708b 100644
--- a/messagecomposer/src/followupreminder/followupremindercreatejob.h
+++ b/messagecomposer/src/followupreminder/followupremindercreatejob.h
@@ -1,61 +1,61 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef FOLLOWUPREMINDERCREATEJOB_H
#define FOLLOWUPREMINDERCREATEJOB_H
#include <QObject>
#include <QDate>
#include <AkonadiCore/Item>
#include <AkonadiCore/Collection>
#include "FollowupReminder/FollowUpReminderInfo"
#include <KJob>
#include "messagecomposer_export.h"
namespace MessageComposer {
class FollowupReminderCreateJobPrivate;
class MESSAGECOMPOSER_EXPORT FollowupReminderCreateJob : public KJob
{
Q_OBJECT
public:
explicit FollowupReminderCreateJob(QObject *parent = nullptr);
~FollowupReminderCreateJob() override;
void setFollowUpReminderDate(const QDate &date);
void setOriginalMessageItemId(Akonadi::Item::Id value);
void setMessageId(const QString &messageId);
void setTo(const QString &to);
void setSubject(const QString &subject);
void setCollectionToDo(const Akonadi::Collection &collection);
void start() override;
private Q_SLOTS:
void slotCreateNewTodo(KJob *job);
private:
void writeFollowupReminderInfo();
FollowupReminderCreateJobPrivate *const d;
};
}
#endif // FOLLOWUPREMINDERCREATEJOB_H
diff --git a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp
index f82d90ac..6924fc0f 100644
--- a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp
+++ b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.cpp
@@ -1,137 +1,137 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "followupreminderselectdatedialog.h"
#include <KLocalizedString>
#include <KSharedConfig>
#include <KMessageBox>
#include <KDateComboBox>
#include <AkonadiWidgets/CollectionComboBox>
#include <KCalCore/Todo>
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QFormLayout>
#include <QLineEdit>
#include <kdatecombobox.h>
using namespace MessageComposer;
class MessageComposer::FollowUpReminderSelectDateDialogPrivate
{
public:
FollowUpReminderSelectDateDialogPrivate()
{
}
KDateComboBox *mDateComboBox = nullptr;
Akonadi::CollectionComboBox *mCollectionCombobox = nullptr;
QPushButton *mOkButton = nullptr;
};
FollowUpReminderSelectDateDialog::FollowUpReminderSelectDateDialog(QWidget *parent, QAbstractItemModel *model)
: QDialog(parent)
, d(new MessageComposer::FollowUpReminderSelectDateDialogPrivate)
{
setWindowTitle(i18n("Select Date"));
QVBoxLayout *topLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
d->mOkButton = buttonBox->button(QDialogButtonBox::Ok);
d->mOkButton->setObjectName(QStringLiteral("ok_button"));
d->mOkButton->setDefault(true);
d->mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::accepted, this, &FollowUpReminderSelectDateDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &FollowUpReminderSelectDateDialog::reject);
setModal(true);
QWidget *mainWidget = new QWidget(this);
topLayout->addWidget(mainWidget);
topLayout->addWidget(buttonBox);
QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget);
- mainLayout->setMargin(0);
+ mainLayout->setContentsMargins(0, 0, 0, 0);
QFormLayout *formLayout = new QFormLayout;
- formLayout->setMargin(0);
+ formLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(formLayout);
d->mDateComboBox = new KDateComboBox;
d->mDateComboBox->setMinimumDate(QDate::currentDate());
d->mDateComboBox->setObjectName(QStringLiteral("datecombobox"));
QDate currentDate = QDate::currentDate();
d->mDateComboBox->setDate(currentDate);
formLayout->addRow(i18n("Date:"), d->mDateComboBox);
d->mCollectionCombobox = new Akonadi::CollectionComboBox(model);
d->mCollectionCombobox->setMinimumWidth(250);
d->mCollectionCombobox->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
d->mCollectionCombobox->setMimeTypeFilter(QStringList() << KCalCore::Todo::todoMimeType());
d->mCollectionCombobox->setObjectName(QStringLiteral("collectioncombobox"));
formLayout->addRow(i18n("Store ToDo in:"), d->mCollectionCombobox);
connect(d->mDateComboBox->lineEdit(), &QLineEdit::textChanged, this, &FollowUpReminderSelectDateDialog::slotDateChanged);
- connect(d->mCollectionCombobox, QOverload<int>::of(&Akonadi::CollectionComboBox::currentIndexChanged), this, &FollowUpReminderSelectDateDialog::updateOkButton);
+ connect(d->mCollectionCombobox, qOverload<int>(&Akonadi::CollectionComboBox::currentIndexChanged), this, &FollowUpReminderSelectDateDialog::updateOkButton);
updateOkButton();
}
FollowUpReminderSelectDateDialog::~FollowUpReminderSelectDateDialog()
{
delete d;
}
void FollowUpReminderSelectDateDialog::updateOkButton()
{
d->mOkButton->setEnabled(!d->mDateComboBox->lineEdit()->text().isEmpty()
&& d->mDateComboBox->date().isValid()
&& (d->mCollectionCombobox->count() > 0)
&& d->mCollectionCombobox->currentCollection().isValid());
}
void FollowUpReminderSelectDateDialog::slotDateChanged()
{
updateOkButton();
}
QDate FollowUpReminderSelectDateDialog::selectedDate() const
{
return d->mDateComboBox->date();
}
Akonadi::Collection FollowUpReminderSelectDateDialog::collection() const
{
return d->mCollectionCombobox->currentCollection();
}
void FollowUpReminderSelectDateDialog::accept()
{
const QDate date = selectedDate();
if (date <= QDate::currentDate()) {
KMessageBox::error(this, i18n("The selected date must be greater than the current date."), i18n("Invalid date"));
return;
}
if (!d->mCollectionCombobox->currentCollection().isValid()) {
KMessageBox::error(this, i18n("The selected folder is not valid."), i18n("Invalid folder"));
return;
}
QDialog::accept();
}
diff --git a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.h b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.h
index eb73e5de..d24089ac 100644
--- a/messagecomposer/src/followupreminder/followupreminderselectdatedialog.h
+++ b/messagecomposer/src/followupreminder/followupreminderselectdatedialog.h
@@ -1,51 +1,51 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef FOLLOWUPREMINDERSELECTDATEDIALOG_H
#define FOLLOWUPREMINDERSELECTDATEDIALOG_H
#include <QDialog>
#include <QPushButton>
#include <AkonadiCore/Collection>
#include "messagecomposer_export.h"
class QAbstractItemModel;
namespace MessageComposer {
class FollowUpReminderSelectDateDialogPrivate;
class MESSAGECOMPOSER_EXPORT FollowUpReminderSelectDateDialog : public QDialog
{
Q_OBJECT
public:
explicit FollowUpReminderSelectDateDialog(QWidget *parent = nullptr, QAbstractItemModel *model = nullptr);
~FollowUpReminderSelectDateDialog() override;
Q_REQUIRED_RESULT QDate selectedDate() const;
void accept() override;
Akonadi::Collection collection() const;
private Q_SLOTS:
void slotDateChanged();
void updateOkButton();
private:
FollowUpReminderSelectDateDialogPrivate *const d;
};
}
#endif // FOLLOWUPREMINDERSELECTDATEDIALOG_H
diff --git a/messagecomposer/src/helper/messagefactoryforwardjob.cpp b/messagecomposer/src/helper/messagefactoryforwardjob.cpp
index 9d6fdd38..85c7f033 100644
--- a/messagecomposer/src/helper/messagefactoryforwardjob.cpp
+++ b/messagecomposer/src/helper/messagefactoryforwardjob.cpp
@@ -1,87 +1,87 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "messagefactoryforwardjob.h"
#include "settings/messagecomposersettings.h"
#include <TemplateParser/TemplateParserJob>
#include <KIdentityManagement/IdentityManager>
using namespace MessageComposer;
MessageFactoryForwardJob::MessageFactoryForwardJob(QObject *parent)
: QObject(parent)
, mMsg(nullptr)
, mOrigMsg(nullptr)
, mIdentityManager(nullptr)
{
}
MessageFactoryForwardJob::~MessageFactoryForwardJob()
{
}
void MessageFactoryForwardJob::start()
{
- TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(mMsg, TemplateParser::TemplateParserJob::Forward);
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(mMsg, TemplateParser::TemplateParserJob::Forward, this);
connect(parser, &TemplateParser::TemplateParserJob::parsingDone, this, &MessageFactoryForwardJob::slotParsingDone);
parser->setIdentityManager(mIdentityManager);
parser->setCharsets(MessageComposerSettings::self()->preferredCharsets());
parser->setSelection(mSelection);
if (!mTemplate.isEmpty()) {
parser->process(mTemplate, mOrigMsg);
} else {
parser->process(mOrigMsg, mCollection.id());
}
}
void MessageFactoryForwardJob::slotParsingDone()
{
Q_EMIT forwardDone(mMsg);
deleteLater();
}
void MessageFactoryForwardJob::setCollection(const Akonadi::Collection &collection)
{
mCollection = collection;
}
void MessageFactoryForwardJob::setMsg(const KMime::Message::Ptr &msg)
{
mMsg = msg;
}
void MessageFactoryForwardJob::setTemplate(const QString &tmpl)
{
mTemplate = tmpl;
}
void MessageFactoryForwardJob::setSelection(const QString &selection)
{
mSelection = selection;
}
void MessageFactoryForwardJob::setOrigMsg(const KMime::Message::Ptr &origMsg)
{
mOrigMsg = origMsg;
}
void MessageFactoryForwardJob::setIdentityManager(KIdentityManagement::IdentityManager *identityManager)
{
mIdentityManager = identityManager;
}
diff --git a/messagecomposer/src/helper/messagefactoryforwardjob.h b/messagecomposer/src/helper/messagefactoryforwardjob.h
index c21cf320..58cfa128 100644
--- a/messagecomposer/src/helper/messagefactoryforwardjob.h
+++ b/messagecomposer/src/helper/messagefactoryforwardjob.h
@@ -1,65 +1,65 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MESSAGEFACTORYFORWARDJOB_H
#define MESSAGEFACTORYFORWARDJOB_H
#include <QObject>
#include <KMime/Message>
#include <AkonadiCore/Collection>
namespace KIdentityManagement {
class IdentityManager;
}
namespace MessageComposer {
class MessageFactoryForwardJob : public QObject
{
Q_OBJECT
public:
explicit MessageFactoryForwardJob(QObject *parent = nullptr);
~MessageFactoryForwardJob();
void start();
void setMsg(const KMime::Message::Ptr &msg);
void setTemplate(const QString &tmpl);
void setSelection(const QString &selection);
void setOrigMsg(const KMime::Message::Ptr &origMsg);
void setIdentityManager(KIdentityManagement::IdentityManager *identityManager);
void setCollection(const Akonadi::Collection &collection);
Q_SIGNALS:
void forwardDone(const KMime::Message::Ptr &msg);
private:
void slotParsingDone();
QString mSelection;
QString mTemplate;
KMime::Message::Ptr mMsg;
KMime::Message::Ptr mOrigMsg;
Akonadi::Collection mCollection;
KIdentityManagement::IdentityManager *mIdentityManager = nullptr;
};
}
#endif // MESSAGEFACTORYFORWARDJOB_H
diff --git a/messagecomposer/src/helper/messagefactoryng.cpp b/messagecomposer/src/helper/messagefactoryng.cpp
index 330ea5b4..6d1ab66e 100644
--- a/messagecomposer/src/helper/messagefactoryng.cpp
+++ b/messagecomposer/src/helper/messagefactoryng.cpp
@@ -1,1069 +1,1068 @@
/*
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@kde.org>
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.
*/
#include "messagefactoryng.h"
#include "settings/messagecomposersettings.h"
#include "messagefactoryforwardjob.h"
#include "messagefactoryreplyjob.h"
#include "MessageComposer/Util"
#include <AkonadiCore/item.h>
#ifndef QT_NO_CURSOR
#include <Libkdepim/KCursorSaver>
#endif
#include <KIdentityManagement/kidentitymanagement/identitymanager.h>
#include <KIdentityManagement/kidentitymanagement/identity.h>
#include <Akonadi/KMime/MessageStatus>
#include <kmime/kmime_dateformatter.h>
#include <KEmailAddress>
#include <MessageCore/MailingList>
#include <MessageCore/StringUtil>
#include "helper/messagehelper.h"
#include <KLocalizedString>
#include "messagecomposer_debug.h"
#include <kcharsets.h>
#include <QTextCodec>
#include <KCharsets>
using namespace MessageComposer;
namespace KMime {
namespace Types {
static bool operator==(const KMime::Types::Mailbox &left, const KMime::Types::Mailbox &right)
{
return left.addrSpec().asString() == right.addrSpec().asString();
}
}
}
/**
* Strips all the user's addresses from an address list. This is used
* when replying.
*/
static KMime::Types::Mailbox::List stripMyAddressesFromAddressList(const KMime::Types::Mailbox::List &list, const KIdentityManagement::IdentityManager *manager)
{
KMime::Types::Mailbox::List addresses(list);
for (KMime::Types::Mailbox::List::Iterator it = addresses.begin(); it != addresses.end();) {
if (manager->thatIsMe(it->prettyAddress())) {
it = addresses.erase(it);
} else {
++it;
}
}
return addresses;
}
MessageFactoryNG::MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col, QObject *parent)
: QObject(parent)
, m_identityManager(nullptr)
, m_origMsg(origMsg)
, m_folderId(0)
, m_parentFolderId(0)
, m_collection(col)
, m_replyStrategy(MessageComposer::ReplySmart)
, m_quote(true)
, m_id(id)
{
}
MessageFactoryNG::~MessageFactoryNG()
{
}
void MessageFactoryNG::slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll)
{
applyCharset(msg);
MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusReplied());
if (m_parentFolderId > 0) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc");
header->fromUnicodeString(QString::number(m_parentFolderId), "utf-8");
msg->setHeader(header);
}
if (auto hrd = m_origMsg->headerByType("X-KMail-EncryptActionEnabled")) {
if (hrd->as7BitString(false).contains("true")) {
auto header = new KMime::Headers::Generic("X-KMail-EncryptActionEnabled");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
msg->setHeader(header);
}
}
msg->assemble();
MessageReply reply;
reply.msg = msg;
reply.replyAll = replyAll;
Q_EMIT createReplyDone(reply);
}
void MessageFactoryNG::createReplyAsync()
{
KMime::Message::Ptr msg(new KMime::Message);
QByteArray refStr;
bool replyAll = true;
KMime::Types::Mailbox::List toList;
KMime::Types::Mailbox::List replyToList;
const uint originalIdentity = identityUoid(m_origMsg);
MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity);
replyToList = m_origMsg->replyTo()->mailboxes();
msg->contentType()->setCharset("utf-8");
if (auto hdr = m_origMsg->headerByType("List-Post")) {
const QString hdrListPost = hdr->asUnicodeString();
if (hdrListPost.contains(QLatin1String("mailto:"), Qt::CaseInsensitive)) {
QRegExp rx(QStringLiteral("<mailto:([^@>]+)@([^>]+)>"), Qt::CaseInsensitive);
if (rx.indexIn(hdrListPost, 0) != -1) { // matched
KMime::Types::Mailbox mailbox;
mailbox.fromUnicodeString(rx.cap(1) + QLatin1Char('@') + rx.cap(2));
m_mailingListAddresses << mailbox;
}
}
}
switch (m_replyStrategy) {
case MessageComposer::ReplySmart:
{
if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) {
toList << KMime::Types::Mailbox::listFrom7BitString(hdr->as7BitString(false));
} else if (!replyToList.isEmpty()) {
toList = replyToList;
// use the ReplyAll template only when it's a reply to a mailing list
if (m_mailingListAddresses.isEmpty()) {
replyAll = false;
}
} else if (!m_mailingListAddresses.isEmpty()) {
toList = (KMime::Types::Mailbox::List() << m_mailingListAddresses.at(0));
} else {
// doesn't seem to be a mailing list, reply to From: address
toList = m_origMsg->from()->mailboxes();
if (m_identityManager->thatIsMe(KMime::Types::Mailbox::listToUnicodeString(toList))) {
// sender seems to be one of our own identities, so we assume that this
// is a reply to a "sent" mail where the users wants to add additional
// information for the recipient.
toList = m_origMsg->to()->mailboxes();
}
replyAll = false;
}
// strip all my addresses from the list of recipients
const KMime::Types::Mailbox::List recipients = toList;
toList = stripMyAddressesFromAddressList(recipients, m_identityManager);
// ... unless the list contains only my addresses (reply to self)
if (toList.isEmpty() && !recipients.isEmpty()) {
toList << recipients.first();
}
break;
}
case MessageComposer::ReplyList:
{
if (auto hdr = m_origMsg->headerByType("Mail-Followup-To")) {
KMime::Types::Mailbox mailbox;
mailbox.from7BitString(hdr->as7BitString(false));
toList << mailbox;
} else if (!m_mailingListAddresses.isEmpty()) {
toList << m_mailingListAddresses[ 0 ];
} else if (!replyToList.isEmpty()) {
// assume a Reply-To header mangling mailing list
toList = replyToList;
}
// strip all my addresses from the list of recipients
const KMime::Types::Mailbox::List recipients = toList;
toList = stripMyAddressesFromAddressList(recipients, m_identityManager);
break;
}
case MessageComposer::ReplyAll:
{
KMime::Types::Mailbox::List recipients;
KMime::Types::Mailbox::List ccRecipients;
QString sender;
if (auto hrd = m_origMsg->sender(false)) {
sender = hrd->asUnicodeString();
}
// add addresses from the Reply-To header to the list of recipients
if (!replyToList.isEmpty()) {
recipients = replyToList;
// strip all possible mailing list addresses from the list of Reply-To addresses
for (const KMime::Types::Mailbox &mailbox : qAsConst(m_mailingListAddresses)) {
foreach (const KMime::Types::Mailbox &recipient, recipients) { //Don't use for(...:...)
if (mailbox == recipient) {
recipients.removeAll(recipient);
}
}
}
}
bool stripMyAddresses = true;
if (!m_mailingListAddresses.isEmpty()) {
// this is a mailing list message
if (recipients.isEmpty() && !m_origMsg->from()->asUnicodeString().isEmpty()) {
// The sender didn't set a Reply-to address, so we add the From
// address to the list of CC recipients.
ccRecipients += m_origMsg->from()->mailboxes();
qCDebug(MESSAGECOMPOSER_LOG) << "Added" << m_origMsg->from()->asUnicodeString() << "to the list of CC recipients";
}
// if it is a mailing list, add the posting address
recipients.prepend(m_mailingListAddresses[ 0 ]);
} else {
const QString fromAddress = m_origMsg->from()->asUnicodeString();
if (!fromAddress.isEmpty()) {
if (!sender.isEmpty() && m_identityManager->thatIsMe(fromAddress)) {
// strip all my addresses from the list of recipients
toList = recipients;
toList += m_origMsg->from()->mailboxes();
stripMyAddresses = false;
} else {
// this is a normal message
if (recipients.isEmpty()) {
// in case of replying to a normal message only then add the From
// address to the list of recipients if there was no Reply-to address
recipients += m_origMsg->from()->mailboxes();
qCDebug(MESSAGECOMPOSER_LOG) << "Added" << m_origMsg->from()->asUnicodeString() << "to the list of recipients";
}
}
}
}
if (stripMyAddresses) {
// strip all my addresses from the list of recipients
toList = stripMyAddressesFromAddressList(recipients, m_identityManager);
}
// merge To header and CC header into a list of CC recipients
if (!m_origMsg->cc()->asUnicodeString().isEmpty() || !m_origMsg->to()->asUnicodeString().isEmpty()) {
KMime::Types::Mailbox::List list;
if (!m_origMsg->to()->asUnicodeString().isEmpty()) {
list += m_origMsg->to()->mailboxes();
}
if (!m_origMsg->cc()->asUnicodeString().isEmpty()) {
list += m_origMsg->cc()->mailboxes();
}
for (const KMime::Types::Mailbox &mailbox : qAsConst(list)) {
if (!recipients.contains(mailbox)
&& !ccRecipients.contains(mailbox)) {
ccRecipients += mailbox;
qCDebug(MESSAGECOMPOSER_LOG) << "Added" << mailbox.prettyAddress() << "to the list of CC recipients";
}
}
}
if (!ccRecipients.isEmpty()) {
// strip all my addresses from the list of CC recipients
if (stripMyAddresses) {
ccRecipients = stripMyAddressesFromAddressList(ccRecipients, m_identityManager);
}
// in case of a reply to self, toList might be empty. if that's the case
// then propagate a cc recipient to To: (if there is any).
if (toList.isEmpty() && !ccRecipients.isEmpty()) {
toList << ccRecipients.at(0);
ccRecipients.pop_front();
}
for (const KMime::Types::Mailbox &mailbox : qAsConst(ccRecipients)) {
msg->cc()->addAddress(mailbox);
}
}
if (toList.isEmpty() && !recipients.isEmpty()) {
// reply to self without other recipients
toList << recipients.at(0);
}
break;
}
case MessageComposer::ReplyAuthor:
if (!replyToList.isEmpty()) {
KMime::Types::Mailbox::List recipients = replyToList;
// strip the mailing list post address from the list of Reply-To
// addresses since we want to reply in private
for (const KMime::Types::Mailbox &mailbox : qAsConst(m_mailingListAddresses)) {
foreach (const KMime::Types::Mailbox &recipient, recipients) { //Don't use for(...:...)
if (mailbox == recipient) {
recipients.removeAll(recipient);
}
}
}
if (!recipients.isEmpty()) {
toList = recipients;
} else {
// there was only the mailing list post address in the Reply-To header,
// so use the From address instead
toList = m_origMsg->from()->mailboxes();
}
} else if (!m_origMsg->from()->asUnicodeString().isEmpty()) {
toList = m_origMsg->from()->mailboxes();
}
replyAll = false;
break;
case MessageComposer::ReplyNone:
// the addressees will be set by the caller
break;
default:
Q_UNREACHABLE();
}
for (const KMime::Types::Mailbox &mailbox : qAsConst(toList)) {
msg->to()->addAddress(mailbox);
}
refStr = getRefStr(m_origMsg);
if (!refStr.isEmpty()) {
msg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
}
//In-Reply-To = original msg-id
msg->inReplyTo()->from7BitString(m_origMsg->messageID()->as7BitString(false));
msg->subject()->fromUnicodeString(MessageCore::StringUtil::replySubject(m_origMsg.data()), "utf-8");
// If the reply shouldn't be blank, apply the template to the message
if (m_quote) {
MessageFactoryReplyJob *job = new MessageFactoryReplyJob;
connect(job, &MessageFactoryReplyJob::replyDone, this, &MessageFactoryNG::slotCreateReplyDone);
job->setMsg(msg);
job->setReplyAll(replyAll);
job->setIdentityManager(m_identityManager);
job->setSelection(m_selection);
job->setTemplate(m_template);
job->setOrigMsg(m_origMsg);
job->setCollection(m_collection);
job->start();
} else {
slotCreateReplyDone(msg, replyAll);
}
}
void MessageFactoryNG::slotCreateForwardDone(const KMime::Message::Ptr &msg)
{
applyCharset(msg);
MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded());
msg->assemble();
Q_EMIT createForwardDone(msg);
}
void MessageFactoryNG::createForwardAsync()
{
KMime::Message::Ptr msg(new KMime::Message);
// This is a non-multipart, non-text mail (e.g. text/calendar). Construct
// a multipart/mixed mail and add the original body as an attachment.
if (!m_origMsg->contentType()->isMultipart()
&& (!m_origMsg->contentType()->isText()
|| (m_origMsg->contentType()->isText() && m_origMsg->contentType()->subType() != "html"
&& m_origMsg->contentType()->subType() != "plain"))) {
const uint originalIdentity = identityUoid(m_origMsg);
MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity);
msg->removeHeader<KMime::Headers::ContentType>();
msg->removeHeader<KMime::Headers::ContentTransferEncoding>();
msg->contentType()->setMimeType("multipart/mixed");
//TODO: Andras: somebody should check if this is correct. :)
// empty text part
KMime::Content *msgPart = new KMime::Content;
msgPart->contentType()->setMimeType("text/plain");
msg->addContent(msgPart);
// the old contents of the mail
KMime::Content *secondPart = new KMime::Content;
secondPart->contentType()->setMimeType(m_origMsg->contentType()->mimeType());
secondPart->setBody(m_origMsg->body());
// use the headers of the original mail
secondPart->setHead(m_origMsg->head());
msg->addContent(secondPart);
msg->assemble();
}
// Normal message (multipart or text/plain|html)
// Just copy the message, the template parser will do the hard work of
// replacing the body text in TemplateParser::addProcessedBodyToMessage()
else {
//TODO Check if this is ok
msg->setHead(m_origMsg->head());
msg->setBody(m_origMsg->body());
QString oldContentType = msg->contentType()->asUnicodeString();
const uint originalIdentity = identityUoid(m_origMsg);
MessageHelper::initFromMessage(msg, m_origMsg, m_identityManager, originalIdentity);
// restore the content type, MessageHelper::initFromMessage() sets the contents type to
// text/plain, via initHeader(), for unclear reasons
msg->contentType()->fromUnicodeString(oldContentType, "utf-8");
msg->assemble();
}
msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(m_origMsg.data()), "utf-8");
MessageFactoryForwardJob *job = new MessageFactoryForwardJob;
connect(job, &MessageFactoryForwardJob::forwardDone, this, &MessageFactoryNG::slotCreateForwardDone);
job->setIdentityManager(m_identityManager);
job->setMsg(msg);
job->setSelection(m_selection);
job->setTemplate(m_template);
job->setOrigMsg(m_origMsg);
job->setCollection(m_collection);
job->start();
}
QPair< KMime::Message::Ptr, QList< KMime::Content * > > MessageFactoryNG::createAttachedForward(const Akonadi::Item::List &items)
{
// create forwarded message with original message as attachment
// remove headers that shouldn't be forwarded
KMime::Message::Ptr msg(new KMime::Message);
QList< KMime::Content * > attachments;
const int numberOfItems(items.count());
if (numberOfItems >= 2) {
// don't respect X-KMail-Identity headers because they might differ for
// the selected mails
MessageHelper::initHeader(msg, m_identityManager, 0);
} else if (numberOfItems == 1) {
KMime::Message::Ptr firstMsg = MessageComposer::Util::message(items.first());
const uint originalIdentity = identityUoid(firstMsg);
MessageHelper::initFromMessage(msg, firstMsg, m_identityManager, originalIdentity);
msg->subject()->fromUnicodeString(MessageCore::StringUtil::forwardSubject(firstMsg.data()), "utf-8");
}
MessageHelper::setAutomaticFields(msg, true);
#ifndef QT_NO_CURSOR
KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy());
#endif
if (numberOfItems == 0) {
attachments << createForwardAttachmentMessage(m_origMsg);
MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded());
} else {
// iterate through all the messages to be forwarded
attachments.reserve(items.count());
for (const Akonadi::Item &item : qAsConst(items)) {
attachments << createForwardAttachmentMessage(MessageComposer::Util::message(item));
MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
}
}
applyCharset(msg);
//msg->assemble();
return QPair< KMime::Message::Ptr, QList< KMime::Content * > >(msg, QList< KMime::Content * >() << attachments);
}
KMime::Content *MessageFactoryNG::createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg)
{
// remove headers that shouldn't be forwarded
MessageCore::StringUtil::removePrivateHeaderFields(fwdMsg);
fwdMsg->removeHeader<KMime::Headers::Bcc>();
fwdMsg->assemble();
// set the part
KMime::Content *msgPart = new KMime::Content(fwdMsg.data());
msgPart->contentType()->setMimeType("message/rfc822");
msgPart->contentDisposition()->setParameter(QStringLiteral("filename"), i18n("forwarded message"));
msgPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
msgPart->contentDescription()->fromUnicodeString(fwdMsg->from()->asUnicodeString() + QLatin1String(": ") + fwdMsg->subject()->asUnicodeString(), "utf-8");
msgPart->setBody(fwdMsg->encodedContent());
msgPart->assemble();
MessageComposer::Util::addLinkInformation(fwdMsg, 0, Akonadi::MessageStatus::statusForwarded());
return msgPart;
}
KMime::Message::Ptr MessageFactoryNG::createResend()
{
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(m_origMsg->encodedContent());
msg->parse();
msg->removeHeader<KMime::Headers::MessageID>();
uint originalIdentity = identityUoid(m_origMsg);
// Set the identity from above
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(originalIdentity), "utf-8");
msg->setHeader(header);
// Restore the original bcc field as this is overwritten in applyIdentity
msg->bcc(m_origMsg->bcc());
return msg;
}
KMime::Message::Ptr MessageFactoryNG::createRedirect(const QString &toStr, const QString &ccStr, const QString &bccStr, int transportId, const QString &fcc, int identity)
{
if (!m_origMsg) {
return KMime::Message::Ptr();
}
// copy the message 1:1
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(m_origMsg->encodedContent());
msg->parse();
uint id = identity;
if (identity == -1) {
if (auto hrd = msg->headerByType("X-KMail-Identity")) {
const QString strId = hrd->asUnicodeString().trimmed();
if (!strId.isEmpty()) {
id = strId.toUInt();
}
}
}
const KIdentityManagement::Identity &ident
= m_identityManager->identityForUoidOrDefault(id);
// X-KMail-Redirect-From: content
const QString strByWayOf = QString::fromLocal8Bit("%1 (by way of %2 <%3>)")
.arg(m_origMsg->from()->asUnicodeString(), ident.fullName(), ident.primaryEmailAddress());
// Resent-From: content
const QString strFrom = QString::fromLocal8Bit("%1 <%2>")
.arg(ident.fullName(), ident.primaryEmailAddress());
// format the current date to be used in Resent-Date:
// FIXME: generate datetime the same way as KMime, otherwise we get inconsistency
// in unit-tests. Unfortunatelly RFC2822Date is not enough for us, we need the
// composition hack below
const QDateTime dt = QDateTime::currentDateTime();
const QString newDate = QLocale::c().toString(dt, QStringLiteral("ddd, "))
+dt.toString(Qt::RFC2822Date);
// Clean up any resent headers
msg->removeHeader("Resent-Cc");
msg->removeHeader("Resent-Bcc");
msg->removeHeader("Resent-Sender");
// date, from to and id will be set anyway
// prepend Resent-*: headers (c.f. RFC2822 3.6.6)
QString msgIdSuffix;
if (MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix()) {
msgIdSuffix = MessageComposer::MessageComposerSettings::customMsgIDSuffix();
}
KMime::Headers::Generic *header = new KMime::Headers::Generic("Resent-Message-ID");
header->fromUnicodeString(MessageCore::StringUtil::generateMessageId(msg->sender()->asUnicodeString(), msgIdSuffix), "utf-8");
msg->setHeader(header);
header = new KMime::Headers::Generic("Resent-Date");
header->fromUnicodeString(newDate, "utf-8");
msg->setHeader(header);
header = new KMime::Headers::Generic("Resent-From");
header->fromUnicodeString(strFrom, "utf-8");
msg->setHeader(header);
if (msg->to(false)) {
KMime::Headers::To *headerT = new KMime::Headers::To;
headerT->fromUnicodeString(m_origMsg->to()->asUnicodeString(), "utf-8");
msg->setHeader(headerT);
}
header = new KMime::Headers::Generic("Resent-To");
header->fromUnicodeString(toStr, "utf-8");
msg->setHeader(header);
if (!ccStr.isEmpty()) {
header = new KMime::Headers::Generic("Resent-Cc");
header->fromUnicodeString(ccStr, "utf-8");
msg->setHeader(header);
}
if (!bccStr.isEmpty()) {
header = new KMime::Headers::Generic("Resent-Bcc");
header->fromUnicodeString(bccStr, "utf-8");
msg->setHeader(header);
}
header = new KMime::Headers::Generic("X-KMail-Redirect-From");
header->fromUnicodeString(strByWayOf, "utf-8");
msg->setHeader(header);
if (transportId != -1) {
header = new KMime::Headers::Generic("X-KMail-Transport");
header->fromUnicodeString(QString::number(transportId), "utf-8");
msg->setHeader(header);
}
if (!fcc.isEmpty()) {
header = new KMime::Headers::Generic("X-KMail-Fcc");
header->fromUnicodeString(fcc, "utf-8");
msg->setHeader(header);
}
const bool fccIsDisabled = ident.disabledFcc();
if (fccIsDisabled) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
msg->setHeader(header);
} else {
msg->removeHeader("X-KMail-FccDisabled");
}
msg->assemble();
MessageComposer::Util::addLinkInformation(msg, m_id, Akonadi::MessageStatus::statusForwarded());
return msg;
}
KMime::Message::Ptr MessageFactoryNG::createDeliveryReceipt()
{
QString receiptTo;
if (auto hrd = m_origMsg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return KMime::Message::Ptr();
}
receiptTo.remove(QChar::fromLatin1('\n'));
KMime::Message::Ptr receipt(new KMime::Message);
const uint originalIdentity = identityUoid(m_origMsg);
MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, originalIdentity);
receipt->to()->fromUnicodeString(receiptTo, QStringLiteral("utf-8").toLatin1());
receipt->subject()->fromUnicodeString(i18n("Receipt: ") + m_origMsg->subject()->asUnicodeString(), "utf-8");
QString str = QStringLiteral("Your message was successfully delivered.");
str += QLatin1String("\n\n---------- Message header follows ----------\n");
str += QString::fromLatin1(m_origMsg->head());
str += QLatin1String("--------------------------------------------\n");
// Conversion to toLatin1 is correct here as Mail headers should contain
// ascii only
receipt->setBody(str.toLatin1());
MessageHelper::setAutomaticFields(receipt);
receipt->assemble();
return receipt;
}
-KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal,
- const QVector<KMime::MDN::DispositionModifier> &m)
+KMime::Message::Ptr MessageFactoryNG::createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal, const QVector<KMime::MDN::DispositionModifier> &m)
{
// extract where to send to:
QString receiptTo;
if (auto hrd = m_origMsg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return KMime::Message::Ptr(new KMime::Message);
}
receiptTo.remove(QChar::fromLatin1('\n'));
QString special; // fill in case of error, warning or failure
// extract where to send from:
QString finalRecipient = m_identityManager->identityForUoidOrDefault(identityUoid(m_origMsg)).fullEmailAddr();
//
// Generate message:
//
KMime::Message::Ptr receipt(new KMime::Message());
const uint originalIdentity = identityUoid(m_origMsg);
MessageHelper::initFromMessage(receipt, m_origMsg, m_identityManager, originalIdentity);
receipt->contentType()->from7BitString("multipart/report");
receipt->contentType()->setBoundary(KMime::multiPartBoundary());
receipt->contentType()->setCharset("us-ascii");
receipt->removeHeader<KMime::Headers::ContentTransferEncoding>();
// Modify the ContentType directly (replaces setAutomaticFields(true))
receipt->contentType()->setParameter(QStringLiteral("report-type"), QStringLiteral("disposition-notification"));
QString description = replaceHeadersInString(m_origMsg, KMime::MDN::descriptionFor(d, m));
// text/plain part:
KMime::Content *firstMsgPart = new KMime::Content(m_origMsg.data());
firstMsgPart->contentType()->setMimeType("text/plain");
firstMsgPart->contentType()->setCharset("utf-8");
firstMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
firstMsgPart->setBody(description.toUtf8());
receipt->addContent(firstMsgPart);
// message/disposition-notification part:
KMime::Content *secondMsgPart = new KMime::Content(m_origMsg.data());
secondMsgPart->contentType()->setMimeType("message/disposition-notification");
secondMsgPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
QByteArray originalRecipient = "";
if (auto hrd = m_origMsg->headerByType("Original-Recipient")) {
originalRecipient = hrd->as7BitString(false);
}
secondMsgPart->setBody(KMime::MDN::dispositionNotificationBodyContent(
finalRecipient,
originalRecipient,
m_origMsg->messageID()->as7BitString(false), /* Message-ID */
d, a, s, m, special));
receipt->addContent(secondMsgPart);
if (mdnQuoteOriginal < 0 || mdnQuoteOriginal > 2) {
mdnQuoteOriginal = 0;
}
/* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/
KMime::Content *thirdMsgPart = new KMime::Content(m_origMsg.data());
switch (mdnQuoteOriginal) {
case 1:
thirdMsgPart->contentType()->setMimeType("message/rfc822");
thirdMsgPart->setBody(MessageCore::StringUtil::asSendableString(m_origMsg));
receipt->addContent(thirdMsgPart);
break;
case 2:
thirdMsgPart->contentType()->setMimeType("text/rfc822-headers");
thirdMsgPart->setBody(MessageCore::StringUtil::headerAsSendableString(m_origMsg));
receipt->addContent(thirdMsgPart);
break;
case 0:
default:
delete thirdMsgPart;
break;
}
receipt->to()->fromUnicodeString(receiptTo, "utf-8");
//Laurent: We don't translate subject ?
receipt->subject()->from7BitString("Message Disposition Notification");
KMime::Headers::InReplyTo *header = new KMime::Headers::InReplyTo;
header->fromUnicodeString(m_origMsg->messageID()->asUnicodeString(), "utf-8");
receipt->setHeader(header);
receipt->references()->from7BitString(getRefStr(m_origMsg));
receipt->assemble();
qCDebug(MESSAGECOMPOSER_LOG) << "final message:" + receipt->encodedContent();
receipt->assemble();
return receipt;
}
QPair< KMime::Message::Ptr, KMime::Content * > MessageFactoryNG::createForwardDigestMIME(const Akonadi::Item::List &items)
{
KMime::Message::Ptr msg(new KMime::Message);
KMime::Content *digest = new KMime::Content(msg.data());
QString mainPartText = i18n("\nThis is a MIME digest forward. The content of the"
" message is contained in the attachment(s).\n\n\n");
digest->contentType()->setMimeType("multipart/digest");
digest->contentType()->setBoundary(KMime::multiPartBoundary());
digest->contentDescription()->fromUnicodeString(QStringLiteral("Digest of %1 messages.").arg(items.count()), "utf8");
digest->contentDisposition()->setFilename(QStringLiteral("digest"));
digest->fromUnicodeString(mainPartText);
int id = 0;
for (const Akonadi::Item &item : qAsConst(items)) {
KMime::Message::Ptr fMsg = MessageComposer::Util::message(item);
if (id == 0) {
if (auto hrd = fMsg->headerByType("X-KMail-Identity")) {
id = hrd->asUnicodeString().toInt();
}
}
MessageCore::StringUtil::removePrivateHeaderFields(fMsg);
fMsg->removeHeader<KMime::Headers::Bcc>();
fMsg->assemble();
KMime::Content *part = new KMime::Content(digest);
part->contentType()->setMimeType("message/rfc822");
part->contentType()->setCharset(fMsg->contentType()->charset());
part->contentID()->setIdentifier(fMsg->contentID()->identifier());
part->contentDescription()->fromUnicodeString(fMsg->contentDescription()->asUnicodeString(), "utf8");
part->contentDisposition()->setParameter(QStringLiteral("name"), i18n("forwarded message"));
part->fromUnicodeString(QString::fromLatin1(fMsg->encodedContent()));
part->assemble();
MessageComposer::Util::addLinkInformation(msg, item.id(), Akonadi::MessageStatus::statusForwarded());
digest->addContent(part);
}
digest->assemble();
id = m_folderId;
MessageHelper::initHeader(msg, m_identityManager, id);
// qCDebug(MESSAGECOMPOSER_LOG) << "digest:" << digest->contents().size() << digest->encodedContent();
return QPair< KMime::Message::Ptr, KMime::Content * >(msg, digest);
}
void MessageFactoryNG::setIdentityManager(KIdentityManagement::IdentityManager *ident)
{
m_identityManager = ident;
}
void MessageFactoryNG::setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy)
{
m_replyStrategy = replyStrategy;
}
void MessageFactoryNG::setSelection(const QString &selection)
{
m_selection = selection;
}
void MessageFactoryNG::setQuote(bool quote)
{
m_quote = quote;
}
void MessageFactoryNG::setTemplate(const QString &templ)
{
m_template = templ;
}
void MessageFactoryNG::setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses)
{
m_mailingListAddresses << listAddresses;
}
void MessageFactoryNG::setFolderIdentity(Akonadi::Collection::Id folderIdentityId)
{
m_folderId = folderIdentityId;
}
void MessageFactoryNG::putRepliesInSameFolder(Akonadi::Collection::Id parentColId)
{
m_parentFolderId = parentColId;
}
bool MessageFactoryNG::MDNRequested(const KMime::Message::Ptr &msg)
{
// extract where to send to:
QString receiptTo;
if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return false;
}
receiptTo.remove(QChar::fromLatin1('\n'));
return !receiptTo.isEmpty();
}
bool MessageFactoryNG::MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg)
{
// extract where to send to:
QString receiptTo;
if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return false;
}
receiptTo.remove(QChar::fromLatin1('\n'));
// RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
// MDN sent) ] if there is more than one distinct address in the
// Disposition-Notification-To header.
qCDebug(MESSAGECOMPOSER_LOG) << "KEmailAddress::splitAddressList(receiptTo):"
<< KEmailAddress::splitAddressList(receiptTo).join(QLatin1Char('\n'));
return KEmailAddress::splitAddressList(receiptTo).count() > 1;
}
bool MessageFactoryNG::MDNReturnPathEmpty(const KMime::Message::Ptr &msg)
{
// extract where to send to:
QString receiptTo;
if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return false;
}
receiptTo.remove(QChar::fromLatin1('\n'));
// RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
// the Disposition-Notification-To header differs from the address
// in the Return-Path header. [...] Confirmation from the user
// SHOULD be obtained (or no MDN sent) if there is no Return-Path
// header in the message [...]
KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, "Return-Path");
QString returnPath = returnPathList.isEmpty() ? QString()
: returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
return returnPath.isEmpty();
}
bool MessageFactoryNG::MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg)
{
// extract where to send to:
QString receiptTo;
if (auto hrd = msg->headerByType("Disposition-Notification-To")) {
receiptTo = hrd->asUnicodeString();
}
if (receiptTo.trimmed().isEmpty()) {
return false;
}
receiptTo.remove(QChar::fromLatin1('\n'));
// RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
// the Disposition-Notification-To header differs from the address
// in the Return-Path header. [...] Confirmation from the user
// SHOULD be obtained (or no MDN sent) if there is no Return-Path
// header in the message [...]
KMime::Types::AddrSpecList returnPathList = MessageHelper::extractAddrSpecs(msg, QStringLiteral("Return-Path").toLatin1());
QString returnPath = returnPathList.isEmpty() ? QString()
: returnPathList.front().localPart + QChar::fromLatin1('@') + returnPathList.front().domain;
qCDebug(MESSAGECOMPOSER_LOG) << "clean return path:" << returnPath;
return !receiptTo.contains(returnPath, Qt::CaseSensitive);
}
bool MessageFactoryNG::MDNMDNUnknownOption(const KMime::Message::Ptr &msg)
{
// RFC 2298: An importance of "required" indicates that
// interpretation of the parameter is necessary for proper
// generation of an MDN in response to this request. If a UA does
// not understand the meaning of the parameter, it MUST NOT generate
// an MDN with any disposition type other than "failed" in response
// to the request.
QString notificationOptions;
if (auto hrd = msg->headerByType("Disposition-Notification-Options")) {
notificationOptions = hrd->asUnicodeString();
}
if (notificationOptions.contains(QLatin1String("required"), Qt::CaseSensitive)) {
// ### hacky; should parse...
// There is a required option that we don't understand. We need to
// ask the user what we should do:
return true;
}
return false;
}
uint MessageFactoryNG::identityUoid(const KMime::Message::Ptr &msg)
{
QString idString;
if (auto hdr = msg->headerByType("X-KMail-Identity")) {
idString = hdr->asUnicodeString().trimmed();
}
bool ok = false;
uint id = idString.toUInt(&ok);
if (!ok || id == 0) {
id = m_identityManager->identityForAddress(msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid();
}
if (id == 0 && m_folderId > 0) {
id = m_folderId;
}
return id;
}
QString MessageFactoryNG::replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s)
{
QString result = s;
QRegExp rx(QStringLiteral("\\$\\{([a-z0-9-]+)\\}"), Qt::CaseInsensitive);
Q_ASSERT(rx.isValid());
QRegExp rxDate(QStringLiteral("\\$\\{date\\}"));
Q_ASSERT(rxDate.isValid());
const QString sDate = KMime::DateFormatter::formatDate(
KMime::DateFormatter::Localized, msg->date()->dateTime().toSecsSinceEpoch());
qCDebug(MESSAGECOMPOSER_LOG) << "creating mdn date:" << msg->date()->dateTime().toSecsSinceEpoch() << sDate;
int idx = 0;
if ((idx = rxDate.indexIn(result, idx)) != -1) {
result.replace(idx, rxDate.matchedLength(), sDate);
}
idx = 0;
while ((idx = rx.indexIn(result, idx)) != -1) {
const QByteArray ba = rx.cap(1).toLatin1();
QString replacement;
if (auto hrd = msg->headerByType(ba.constData())) {
replacement = hrd->asUnicodeString();
}
result.replace(idx, rx.matchedLength(), replacement);
idx += replacement.length();
}
return result;
}
void MessageFactoryNG::applyCharset(const KMime::Message::Ptr msg)
{
if (MessageComposer::MessageComposerSettings::forceReplyCharset()) {
// first convert the body from its current encoding to unicode representation
QTextCodec *bodyCodec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
if (!bodyCodec) {
bodyCodec = KCharsets::charsets()->codecForName(QStringLiteral("UTF-8"));
}
const QString body = bodyCodec->toUnicode(msg->body());
// then apply the encoding of the original message
msg->contentType()->setCharset(m_origMsg->contentType()->charset());
QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(msg->contentType()->charset()));
if (!codec) {
qCCritical(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << msg->contentType()->charset();
} else if (!codec->canEncode(body)) { // charset can't encode body, fall back to preferred
const QStringList charsets = MessageComposer::MessageComposerSettings::preferredCharsets();
QList<QByteArray> chars;
chars.reserve(charsets.count());
for (const QString &charset : charsets) {
chars << charset.toLatin1();
}
QByteArray fallbackCharset = MessageComposer::Util::selectCharset(chars, body);
if (fallbackCharset.isEmpty()) { // UTF-8 as fall-through
fallbackCharset = "UTF-8";
}
codec = KCharsets::charsets()->codecForName(QString::fromLatin1(fallbackCharset));
msg->setBody(codec->fromUnicode(body));
} else {
msg->setBody(codec->fromUnicode(body));
}
}
}
QByteArray MessageFactoryNG::getRefStr(const KMime::Message::Ptr &msg)
{
QByteArray firstRef, lastRef, refStr, retRefStr;
int i, j;
if (auto hdr = msg->references(false)) {
refStr = hdr->as7BitString(false).trimmed();
}
if (refStr.isEmpty()) {
return msg->messageID()->as7BitString(false);
}
i = refStr.indexOf('<');
j = refStr.indexOf('>');
firstRef = refStr.mid(i, j - i + 1);
if (!firstRef.isEmpty()) {
retRefStr = firstRef + ' ';
}
i = refStr.lastIndexOf('<');
j = refStr.lastIndexOf('>');
lastRef = refStr.mid(i, j - i + 1);
if (!lastRef.isEmpty() && lastRef != firstRef) {
retRefStr += lastRef + ' ';
}
retRefStr += msg->messageID()->as7BitString(false);
return retRefStr;
}
diff --git a/messagecomposer/src/helper/messagefactoryng.h b/messagecomposer/src/helper/messagefactoryng.h
index c296ddaa..d3fb5ed8 100644
--- a/messagecomposer/src/helper/messagefactoryng.h
+++ b/messagecomposer/src/helper/messagefactoryng.h
@@ -1,271 +1,269 @@
/*
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@kde.org>
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.
*/
#ifndef MESSAGECOMPOSER_MESSAGE_FACTORYNG_H
#define MESSAGECOMPOSER_MESSAGE_FACTORYNG_H
#include "messagecomposer_export.h"
#include <kmime/kmime_message.h>
#include <kmime/kmime_mdn.h>
#include <Item>
#include <Collection>
#include <Akonadi/KMime/MessageStatus>
namespace KIdentityManagement {
class IdentityManager;
}
namespace MessageComposer {
/**
* Enumeration that defines the available reply "modes"
*/
enum ReplyStrategy {
ReplySmart = 0, //< Attempt to automatically guess the best recipient for the reply
ReplyAuthor, //< Reply to the author of the message (possibly NOT the mailing list, if any)
ReplyList, //< Reply to the mailing list (and not the author of the message)
ReplyAll, //< Reply to author and all the recipients in CC
ReplyNone //< Don't set reply addresses: they will be set manually
};
enum MDNAdvice {
MDNIgnore,
MDNSendDenied,
MDNSend
};
/**
* Contains various factory methods for creating new messages such as replies, MDNs, forwards, etc.
*/
class MESSAGECOMPOSER_EXPORT MessageFactoryNG : public QObject
{
Q_OBJECT
public:
/// Small helper structure which encapsulates the KMime::Message created when creating a reply, and
/// the reply mode
struct MessageReply {
KMime::Message::Ptr msg; ///< The actual reply message
bool replyAll; ///< If true, the "reply all" template was used, otherwise the normal reply
/// template
};
explicit MessageFactoryNG(const KMime::Message::Ptr &origMsg, Akonadi::Item::Id id, const Akonadi::Collection &col = Akonadi::Collection(), QObject *parent = nullptr);
~MessageFactoryNG() override;
/**
* Create a new message that is a reply to this message, filling all
* required header fields with the proper values. The returned message
* is not stored in any folder. Marks this message as replied.
*
*/
void createReplyAsync();
/** Create a new message that is a forward of this message, filling all
required header fields with the proper values. The returned message
is not stored in any folder. Marks this message as forwarded. */
void createForwardAsync();
/**
* Create a forward from the given list of messages, attaching each
* message to be forwarded to the new forwarded message.
*
* If no list is passed, use the original message passed in the MessageFactoryNG
* constructor.
*/
Q_REQUIRED_RESULT QPair< KMime::Message::Ptr, QList< KMime::Content * > > createAttachedForward(const Akonadi::Item::List &items = Akonadi::Item::List());
/** Create a new message that is a redirect to this message, filling all
required header fields with the proper values. The returned message
is not stored in any folder. Marks this message as replied.
Redirects differ from forwards so they are forwarded to some other
user, mail is not changed and the reply-to field is set to
the email address of the original sender.
*/
- Q_REQUIRED_RESULT KMime::Message::Ptr createRedirect(const QString &toStr, const QString &ccStr = QString(), const QString &bccStr = QString(), int transportId = -1,
- const QString &fcc = QString(), int identity = -1);
+ Q_REQUIRED_RESULT KMime::Message::Ptr createRedirect(const QString &toStr, const QString &ccStr = QString(), const QString &bccStr = QString(), int transportId = -1, const QString &fcc = QString(), int identity = -1);
Q_REQUIRED_RESULT KMime::Message::Ptr createResend();
/** Create a new message that is a delivery receipt of this message,
filling required header fileds with the proper values. The
returned message is not stored in any folder. */
Q_REQUIRED_RESULT KMime::Message::Ptr createDeliveryReceipt();
/** Create a new message that is a MDN for this message, filling all
required fields with proper values. The returned message is not
stored in any folder.
@param a Use AutomaticAction for filtering and ManualAction for
user-induced events.
@param d See docs for KMime::MDN::DispositionType
@param s See docs for KMime::MDN::SendingMode (in KMail, use MDNAdvideDialog to ask the user for this parameter)
@param m See docs for KMime::MDN::DispositionModifier
@return The notification message or 0, if none should be sent, as well as the state of the MDN operation.
**/
- Q_REQUIRED_RESULT KMime::Message::Ptr createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal = 0,
- const QVector<KMime::MDN::DispositionModifier> &m = QVector<KMime::MDN::DispositionModifier>());
+ Q_REQUIRED_RESULT KMime::Message::Ptr createMDN(KMime::MDN::ActionMode a, KMime::MDN::DispositionType d, KMime::MDN::SendingMode s, int mdnQuoteOriginal = 0, const QVector<KMime::MDN::DispositionModifier> &m = QVector<KMime::MDN::DispositionModifier>());
/**
* Create a new forwarded MIME digest. If the user is trying to forward multiple messages
* at once all inline, they can choose to have them be compiled into a single digest
* message.
*
* This will return a header message and individual message parts to be set as
* attachments.
*
* @param msgs List of messages to be composed into a digest
*/
Q_REQUIRED_RESULT QPair< KMime::Message::Ptr, KMime::Content * > createForwardDigestMIME(const Akonadi::Item::List &items);
/**
* Set the identity manager to be used when creating messages.
* Required to be set before create* is called, otherwise the created messages
* might have the wrong identity data.
*/
void setIdentityManager(KIdentityManagement::IdentityManager *ident);
/**
* Set the reply strategy to use. Default is ReplySmart.
*/
void setReplyStrategy(MessageComposer::ReplyStrategy replyStrategy);
/**
* Set the selection to be used to base the reply on.
*/
void setSelection(const QString &selection);
/**
* Whether to quote the original message in the reply.
* Default is to quote.
*/
void setQuote(bool quote);
/**
* Set the template to be used when creating the reply. Default is to not
* use any template at all.
*/
void setTemplate(const QString &templ);
/**
* Set extra mailinglist addresses to send the created message to.
* Any mailing-list addresses specified in the original message
* itself will be added by MessageFactoryNG, so no need to add those manually.
*/
void setMailingListAddresses(const KMime::Types::Mailbox::List &listAddresses);
/**
* Set the identity that is set for the folder in which the given message is.
* It is used as a fallback when finding the identity if it can't be found in
* any other way.
* Also used if putRepliesInSameFolder is set to true.
*/
void setFolderIdentity(Akonadi::Item::Id folderIdentityId);
/**
* Whether or not to put the reply to a message in the same folder as the message itself.
* If so, specify the folder id in which to put them. Default is -1, which means to not put
* replies in the same folder at all.
*/
void putRepliesInSameFolder(Akonadi::Item::Id parentColId = -1);
/**
* When creating MDNs, the user needs to be asked for confirmation in specific
* cases according to RFC 2298.
*/
Q_REQUIRED_RESULT static bool MDNRequested(const KMime::Message::Ptr &msg);
/**
* If sending an MDN requires confirmation due to multiple addresses.
*
* RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
* MDN sent) ] if there is more than one distinct address in the
* Disposition-Notification-To header.
*/
Q_REQUIRED_RESULT static bool MDNConfirmMultipleRecipients(const KMime::Message::Ptr &msg);
/**
*
* If sending an MDN requires confirmation due to discrepancy between MDN
* header and Return-Path header.
*
* RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
* the Disposition-Notification-To header differs from the address
* in the Return-Path header. [...] Confirmation from the user
* SHOULD be obtained (or no MDN sent) if there is no Return-Path
* header in the message [...]
*/
Q_REQUIRED_RESULT static bool MDNReturnPathEmpty(const KMime::Message::Ptr &msg);
Q_REQUIRED_RESULT static bool MDNReturnPathNotInRecieptTo(const KMime::Message::Ptr &msg);
/**
* If the MDN headers contain options that KMail can't parse
*/
Q_REQUIRED_RESULT static bool MDNMDNUnknownOption(const KMime::Message::Ptr &msg);
Q_SIGNALS:
void createReplyDone(const MessageComposer::MessageFactoryNG::MessageReply &reply);
void createForwardDone(const KMime::Message::Ptr &msg);
private Q_SLOTS:
void slotCreateReplyDone(const KMime::Message::Ptr &msg, bool replyAll);
void slotCreateForwardDone(const KMime::Message::Ptr &msg);
private:
/** @return the UOID of the identity for this message.
Searches the "x-kmail-identity" header and if that fails,
searches with KIdentityManagement::IdentityManager::identityForAddress()
**/
uint identityUoid(const KMime::Message::Ptr &msg);
QString replaceHeadersInString(const KMime::Message::Ptr &msg, const QString &s);
/*
* If force charset option is enabled, try to set the original charset
* in the newly created message. If unable to encode, fall back to
* preferred charsets, and if all fail, use UTF-8.
*/
void applyCharset(const KMime::Message::Ptr msg);
QByteArray getRefStr(const KMime::Message::Ptr &msg);
KMime::Content *createForwardAttachmentMessage(const KMime::Message::Ptr &fwdMsg);
KIdentityManagement::IdentityManager *m_identityManager = nullptr;
// Required parts to create messages
KMime::Message::Ptr m_origMsg;
Akonadi::Item::Id m_folderId;
Akonadi::Item::Id m_parentFolderId;
Akonadi::Collection m_collection;
// Optional settings the calling class may set
MessageComposer::ReplyStrategy m_replyStrategy;
QString m_selection, m_template;
bool m_quote = false;
KMime::Types::Mailbox::List m_mailingListAddresses;
Akonadi::Item::Id m_id;
};
}
Q_DECLARE_METATYPE(MessageComposer::ReplyStrategy)
Q_DECLARE_METATYPE(MessageComposer::MessageFactoryNG::MessageReply)
#endif
diff --git a/messagecomposer/src/helper/messagefactoryreplyjob.cpp b/messagecomposer/src/helper/messagefactoryreplyjob.cpp
index 6dfb9450..a3b4d9c7 100644
--- a/messagecomposer/src/helper/messagefactoryreplyjob.cpp
+++ b/messagecomposer/src/helper/messagefactoryreplyjob.cpp
@@ -1,93 +1,93 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "messagefactoryreplyjob.h"
#include "settings/messagecomposersettings.h"
#include <TemplateParser/TemplateParserJob>
#include <KIdentityManagement/IdentityManager>
using namespace MessageComposer;
MessageFactoryReplyJob::MessageFactoryReplyJob(QObject *parent)
: QObject(parent)
{
}
MessageFactoryReplyJob::~MessageFactoryReplyJob()
{
}
void MessageFactoryReplyJob::start()
{
- TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(mMsg, (mReplyAll ? TemplateParser::TemplateParserJob::ReplyAll : TemplateParser::TemplateParserJob::Reply));
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(mMsg, (mReplyAll ? TemplateParser::TemplateParserJob::ReplyAll : TemplateParser::TemplateParserJob::Reply), this);
connect(parser, &TemplateParser::TemplateParserJob::parsingDone, this, &MessageFactoryReplyJob::slotReplyDone);
parser->setIdentityManager(mIdentityManager);
parser->setCharsets(MessageComposerSettings::self()->preferredCharsets());
parser->setWordWrap(MessageComposerSettings::wordWrap(), MessageComposerSettings::lineWrapWidth());
if (MessageComposer::MessageComposerSettings::quoteSelectionOnly()) {
parser->setSelection(mSelection);
}
parser->setAllowDecryption(true);
if (!mTemplate.isEmpty()) {
parser->process(mTemplate, mOrigMsg);
} else {
parser->process(mOrigMsg, mCollection.id());
}
}
void MessageFactoryReplyJob::slotReplyDone()
{
Q_EMIT replyDone(mMsg, mReplyAll);
deleteLater();
}
void MessageFactoryReplyJob::setCollection(const Akonadi::Collection &collection)
{
mCollection = collection;
}
void MessageFactoryReplyJob::setReplyAll(bool replyAll)
{
mReplyAll = replyAll;
}
void MessageFactoryReplyJob::setMsg(const KMime::Message::Ptr &msg)
{
mMsg = msg;
}
void MessageFactoryReplyJob::setTemplate(const QString &tmpl)
{
mTemplate = tmpl;
}
void MessageFactoryReplyJob::setSelection(const QString &selection)
{
mSelection = selection;
}
void MessageFactoryReplyJob::setOrigMsg(const KMime::Message::Ptr &origMsg)
{
mOrigMsg = origMsg;
}
void MessageFactoryReplyJob::setIdentityManager(KIdentityManagement::IdentityManager *identityManager)
{
mIdentityManager = identityManager;
}
diff --git a/messagecomposer/src/helper/messagefactoryreplyjob.h b/messagecomposer/src/helper/messagefactoryreplyjob.h
index b81ee5d3..9a131b11 100644
--- a/messagecomposer/src/helper/messagefactoryreplyjob.h
+++ b/messagecomposer/src/helper/messagefactoryreplyjob.h
@@ -1,66 +1,66 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MESSAGEFACTORYREPLYJOB_H
#define MESSAGEFACTORYREPLYJOB_H
#include <QObject>
#include <KMime/Message>
#include <KIdentityManagement/IdentityManager>
#include <AkonadiCore/Collection>
namespace MessageComposer {
class MessageFactoryReplyJob : public QObject
{
Q_OBJECT
public:
explicit MessageFactoryReplyJob(QObject *parent = nullptr);
~MessageFactoryReplyJob();
void start();
void setMsg(const KMime::Message::Ptr &msg);
void setTemplate(const QString &tmpl);
void setSelection(const QString &selection);
void setOrigMsg(const KMime::Message::Ptr &origMsg);
void setIdentityManager(KIdentityManagement::IdentityManager *identityManager);
void setReplyAll(bool replyAll);
void setCollection(const Akonadi::Collection &collection);
Q_SIGNALS:
void replyDone(const KMime::Message::Ptr &msg, bool replyAll);
private:
void slotReplyDone();
QString mSelection;
QString mTemplate;
KMime::Message::Ptr mMsg = nullptr;
KMime::Message::Ptr mOrigMsg = nullptr;
Akonadi::Collection mCollection;
bool mReplyAll = false;
KIdentityManagement::IdentityManager *mIdentityManager = nullptr;
};
}
#endif // MESSAGEFACTORYREPLYJOB_H
diff --git a/messagecomposer/src/helper/messagehelper.cpp b/messagecomposer/src/helper/messagehelper.cpp
index 39f4870b..f220de37 100644
--- a/messagecomposer/src/helper/messagehelper.cpp
+++ b/messagecomposer/src/helper/messagehelper.cpp
@@ -1,175 +1,173 @@
/*
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#include "messagehelper.h"
#include "messagecomposer-version.h"
#include "utils/util.h"
#include "settings/messagecomposersettings.h"
#include "MessageCore/MailingList"
#include "MessageCore/StringUtil"
#include <KMime/Message>
#include <kmime/kmime_mdn.h>
#include <kmime/kmime_dateformatter.h>
#include <kmime/kmime_headers.h>
#include <kidentitymanagement/identitymanager.h>
#include <kidentitymanagement/identity.h>
#include "messagecomposer_debug.h"
using namespace MessageCore;
namespace MessageHelper {
void initHeader(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id)
{
applyIdentity(message, identMan, id);
message->removeHeader<KMime::Headers::To>();
message->removeHeader<KMime::Headers::Subject>();
message->date()->setDateTime(QDateTime::currentDateTime());
// This will allow to change Content-Type:
message->contentType()->setMimeType("text/plain");
}
void initFromMessage(const KMime::Message::Ptr &msg, const KMime::Message::Ptr &origMsg, KIdentityManagement::IdentityManager *identMan, uint id, bool idHeaders)
{
if (idHeaders) {
MessageHelper::initHeader(msg, identMan, id);
} else {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(id), "utf-8");
msg->setHeader(header);
}
if (auto hdr = origMsg->headerByType("X-KMail-Transport")) {
const QString transport = hdr->asUnicodeString();
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport");
header->fromUnicodeString(transport, "utf-8");
msg->setHeader(header);
}
}
void applyIdentity(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id)
{
const KIdentityManagement::Identity &ident
= identMan->identityForUoidOrDefault(id);
if (ident.fullEmailAddr().isEmpty()) {
message->removeHeader<KMime::Headers::From>();
} else {
message->from()->addAddress(ident.primaryEmailAddress().toUtf8(), ident.fullName());
}
if (ident.replyToAddr().isEmpty()) {
message->removeHeader<KMime::Headers::ReplyTo>();
} else {
message->replyTo()->addAddress(ident.replyToAddr().toUtf8());
}
if (ident.bcc().isEmpty()) {
message->removeHeader<KMime::Headers::Bcc>();
} else {
const auto mailboxes = KMime::Types::Mailbox::listFromUnicodeString(ident.bcc());
for (const KMime::Types::Mailbox &mailbox : mailboxes) {
message->bcc()->addAddress(mailbox);
}
}
if (ident.cc().isEmpty()) {
message->removeHeader<KMime::Headers::Cc>();
} else {
const auto mailboxes = KMime::Types::Mailbox::listFromUnicodeString(ident.cc());
for (const KMime::Types::Mailbox &mailbox : mailboxes) {
message->cc()->addAddress(mailbox);
}
}
if (ident.organization().isEmpty()) {
message->removeHeader<KMime::Headers::Organization>();
} else {
KMime::Headers::Organization *const organization = new KMime::Headers::Organization;
organization->fromUnicodeString(ident.organization(), "utf-8");
message->setHeader(organization);
}
if (ident.isDefault()) {
message->removeHeader("X-KMail-Identity");
} else {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Identity");
header->fromUnicodeString(QString::number(ident.uoid()), "utf-8");
message->setHeader(header);
}
if (ident.transport().isEmpty()) {
message->removeHeader("X-KMail-Transport");
} else {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport");
header->fromUnicodeString(ident.transport(), "utf-8");
message->setHeader(header);
}
if (ident.fcc().isEmpty()) {
message->removeHeader("X-KMail-Fcc");
} else {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc");
header->fromUnicodeString(ident.fcc(), "utf-8");
message->setHeader(header);
}
if (ident.disabledFcc()) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled");
header->fromUnicodeString(QStringLiteral("true"), "utf-8");
message->setHeader(header);
} else {
message->removeHeader("X-KMail-FccDisabled");
}
}
KMime::Types::AddrSpecList extractAddrSpecs(const KMime::Message::Ptr &msg, const QByteArray &header)
{
KMime::Types::AddrSpecList result;
if (auto hrd = msg->headerByType(header.constData())) {
KMime::Types::AddressList al
= MessageCore::StringUtil::splitAddressField(hrd->asUnicodeString().toUtf8());
KMime::Types::AddressList::const_iterator alend(al.constEnd());
for (KMime::Types::AddressList::const_iterator ait = al.constBegin(); ait != alend; ++ait) {
KMime::Types::MailboxList::const_iterator mitEnd((*ait).mailboxList.constEnd());
for (KMime::Types::MailboxList::const_iterator mit = (*ait).mailboxList.constBegin(); mit != mitEnd; ++mit) {
result.push_back((*mit).addrSpec());
}
}
}
return result;
}
-
void setAutomaticFields(const KMime::Message::Ptr &msg, bool aIsMulti)
{
auto header = msg->header<KMime::Headers::MIMEVersion>(true);
header->from7BitString("1.0");
if (aIsMulti || msg->contents().size() > 1) {
// Set the type to 'Multipart' and the subtype to 'Mixed'
msg->contentType()->setMimeType("multipart/mixed");
// Create a random printable string and set it as the boundary parameter
msg->contentType()->setBoundary(KMime::multiPartBoundary());
}
}
-
}
diff --git a/messagecomposer/src/helper/messagehelper.h b/messagecomposer/src/helper/messagehelper.h
index 50ed7cc4..8d240137 100644
--- a/messagecomposer/src/helper/messagehelper.h
+++ b/messagecomposer/src/helper/messagehelper.h
@@ -1,67 +1,65 @@
/*
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#ifndef KMAIL_MESSAGE_HELPER_H
#define KMAIL_MESSAGE_HELPER_H
#include "messagecomposer_export.h"
#include <kmime/kmime_headers.h>
#include <kmime/kmime_message.h>
#include <item.h>
namespace KIdentityManagement {
class IdentityManager;
}
namespace KMime {
class Message;
}
/**
* Contains random helper methods when dealing with messages.
* TODO: cleanup and organize, along with similar methods in messageviewer.
*/
namespace MessageHelper {
/** Initialize header fields. Should be called on new messages
if they are not set manually. E.g. before composing. Calling
of setAutomaticFields(), see below, is still required. */
void MESSAGECOMPOSER_EXPORT initHeader(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id = 0);
-/** Set the from, to, cc, bcc, encrytion etc headers as specified in the
+/** Set the from, to, cc, bcc, encryption etc headers as specified in the
* given identity. */
void applyIdentity(const KMime::Message::Ptr &message, const KIdentityManagement::IdentityManager *identMan, uint id);
/** Initialize headers fields according to the identity and the transport
header of the given original message */
void initFromMessage(const KMime::Message::Ptr &msg, const KMime::Message::Ptr &orgiMsg, KIdentityManagement::IdentityManager *, uint id, bool idHeaders = true);
MESSAGECOMPOSER_EXPORT KMime::Types::AddrSpecList extractAddrSpecs(const KMime::Message::Ptr &msg, const QByteArray &header);
-
/** Set fields that are either automatically set (Message-id)
or that do not change from one message to another (MIME-Version).
Call this method before sending *after* all changes to the message
are done because this method does things different if there are
attachments / multiple body parts. */
void setAutomaticFields(const KMime::Message::Ptr &msg, bool isMultipart = false);
-
}
#endif
diff --git a/messagecomposer/src/imagescaling/autotests/imagescalingtest.cpp b/messagecomposer/src/imagescaling/autotests/imagescalingtest.cpp
index bceab637..bcab67f6 100644
--- a/messagecomposer/src/imagescaling/autotests/imagescalingtest.cpp
+++ b/messagecomposer/src/imagescaling/autotests/imagescalingtest.cpp
@@ -1,113 +1,113 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescalingtest.h"
#include "settings/messagecomposersettings.h"
#include "../imagescaling.h"
-#include <qtest.h>
+#include <QTest>
#include <QStandardPaths>
ImageScalingTest::ImageScalingTest(QObject *parent)
: QObject(parent)
{
}
ImageScalingTest::~ImageScalingTest()
{
}
void ImageScalingTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
}
void ImageScalingTest::shouldHaveDefaultValue()
{
MessageComposer::ImageScaling scaling;
//Image is empty
QVERIFY(!scaling.resizeImage());
QVERIFY(scaling.generateNewName().isEmpty());
}
void ImageScalingTest::shouldHaveRenameFile_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
QTest::addColumn<QByteArray>("format");
QTest::addColumn<QString>("saveasformat");
QTest::newRow("no rename png when file is empty") << QString() << QString() << QByteArray("image/png") << QStringLiteral("PNG");
QTest::newRow("no rename jpg when file is empty") << QString() << QString() << QByteArray("image/jpeg") << QStringLiteral("PNG");
QTest::newRow("no rename to png") << QStringLiteral("foo.jpeg") << QStringLiteral("foo.jpeg") << QByteArray("image/jpeg") << QStringLiteral("PNG");
QTest::newRow("no rename to jpeg") << QStringLiteral("foo.png") << QStringLiteral("foo.png") << QByteArray("image/png") << QStringLiteral("JPG");
QTest::newRow("rename to jpeg") << QStringLiteral("foo.png") << QStringLiteral("foo.jpg") << QByteArray("image/mng") << QStringLiteral("JPG");
QTest::newRow("rename to png") << QStringLiteral("foo.jpg") << QStringLiteral("foo.png") << QByteArray("image/mng") << QStringLiteral("PNG");
}
void ImageScalingTest::shouldHaveRenameFile()
{
QFETCH(QString, input);
QFETCH(QString, output);
QFETCH(QByteArray, format);
QFETCH(QString, saveasformat);
MessageComposer::MessageComposerSettings::self()->setWriteFormat(saveasformat);
MessageComposer::MessageComposerSettings::self()->save();
MessageComposer::ImageScaling scaling;
scaling.setName(input);
scaling.setMimetype(format);
QCOMPARE(scaling.generateNewName(), output);
}
void ImageScalingTest::shouldHaveChangeMimetype_data()
{
QTest::addColumn<QByteArray>("initialmimetype");
QTest::addColumn<QByteArray>("newmimetype");
QTest::addColumn<QString>("format");
QTest::newRow("no change mimetype when empty") << QByteArray() << QByteArray() << QStringLiteral("PNG");
QTest::newRow("no change mimetype when empty jpeg") << QByteArray() << QByteArray() << QStringLiteral("JPG");
QTest::newRow("no change mimetype when jpeg (same)") << QByteArray("image/jpeg") << QByteArray("image/jpeg") << QStringLiteral("JPG");
QTest::newRow("no change mimetype when jpeg") << QByteArray("image/jpeg") << QByteArray("image/jpeg") << QStringLiteral("PNG");
QTest::newRow("no change mimetype when png (same)") << QByteArray("image/png") << QByteArray("image/png") << QStringLiteral("JPG");
QTest::newRow("no change mimetype when png") << QByteArray("image/png") << QByteArray("image/png") << QStringLiteral("PNG");
QTest::newRow("change mimetype when png") << QByteArray("image/mng") << QByteArray("image/png") << QStringLiteral("PNG");
QTest::newRow("change mimetype when jpeg") << QByteArray("image/mng") << QByteArray("image/jpeg") << QStringLiteral("JPG");
QTest::newRow("When format is not defined but png") << QByteArray("image/png") << QByteArray("image/png") << QString();
QTest::newRow("When format is not defined but jpeg") << QByteArray("image/jpeg") << QByteArray("image/jpeg") << QString();
QTest::newRow("When format is not defined but other mimetype (return png)") << QByteArray("image/mng") << QByteArray("image/png") << QString();
}
void ImageScalingTest::shouldHaveChangeMimetype()
{
QFETCH(QByteArray, initialmimetype);
QFETCH(QByteArray, newmimetype);
QFETCH(QString, format);
MessageComposer::MessageComposerSettings::self()->setWriteFormat(format);
MessageComposer::MessageComposerSettings::self()->save();
MessageComposer::ImageScaling scaling;
scaling.setMimetype(initialmimetype);
QCOMPARE(scaling.mimetype(), newmimetype);
}
QTEST_MAIN(ImageScalingTest)
diff --git a/messagecomposer/src/imagescaling/autotests/imagescalingtest.h b/messagecomposer/src/imagescaling/autotests/imagescalingtest.h
index a79cf8f0..f12bb64d 100644
--- a/messagecomposer/src/imagescaling/autotests/imagescalingtest.h
+++ b/messagecomposer/src/imagescaling/autotests/imagescalingtest.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGESCALINGTEST_H
#define IMAGESCALINGTEST_H
#include <QObject>
class ImageScalingTest : public QObject
{
Q_OBJECT
public:
explicit ImageScalingTest(QObject *parent = nullptr);
~ImageScalingTest();
private Q_SLOTS:
void initTestCase();
void shouldHaveDefaultValue();
void shouldHaveRenameFile_data();
void shouldHaveRenameFile();
void shouldHaveChangeMimetype_data();
void shouldHaveChangeMimetype();
};
#endif // IMAGESCALINGTEST_H
diff --git a/messagecomposer/src/imagescaling/imagescaling.cpp b/messagecomposer/src/imagescaling/imagescaling.cpp
index 2bbaae57..097ea87b 100644
--- a/messagecomposer/src/imagescaling/imagescaling.cpp
+++ b/messagecomposer/src/imagescaling/imagescaling.cpp
@@ -1,192 +1,192 @@
/*
- Copyright (C) 2012-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2012-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescaling.h"
#include "settings/messagecomposersettings.h"
using namespace MessageComposer;
class MessageComposer::ImageScalingPrivate
{
public:
ImageScalingPrivate()
{
}
QImage mImage;
QBuffer mBuffer;
QString mName;
QByteArray mMimeType;
};
ImageScaling::ImageScaling()
: d(new MessageComposer::ImageScalingPrivate)
{
}
ImageScaling::~ImageScaling()
{
delete d;
}
bool ImageScaling::loadImageFromData(const QByteArray &data)
{
if (!d->mImage.loadFromData(data)) {
return false;
}
return true;
}
bool ImageScaling::resizeImage()
{
if (d->mImage.isNull()) {
return false;
}
const int width = d->mImage.width();
const int height = d->mImage.height();
int newWidth = -1;
int newHeight = -1;
if (MessageComposer::MessageComposerSettings::self()->reduceImageToMaximum()) {
int maximumWidth = MessageComposer::MessageComposerSettings::self()->maximumWidth();
if (maximumWidth == -1) {
maximumWidth = MessageComposer::MessageComposerSettings::self()->customMaximumWidth();
}
int maximumHeight = MessageComposer::MessageComposerSettings::self()->maximumHeight();
if (maximumHeight == -1) {
maximumHeight = MessageComposer::MessageComposerSettings::self()->customMaximumHeight();
}
if (width > maximumWidth) {
newWidth = maximumWidth;
} else {
newWidth = width;
}
if (height > maximumHeight) {
newHeight = maximumHeight;
} else {
newHeight = height;
}
} else {
newHeight = height;
newWidth = width;
}
if (MessageComposer::MessageComposerSettings::self()->enlargeImageToMinimum()) {
int minimumWidth = MessageComposer::MessageComposerSettings::self()->minimumWidth();
if (minimumWidth == -1) {
minimumWidth = MessageComposer::MessageComposerSettings::self()->customMinimumWidth();
}
int minimumHeight = MessageComposer::MessageComposerSettings::self()->minimumHeight();
if (minimumHeight == -1) {
minimumHeight = MessageComposer::MessageComposerSettings::self()->customMinimumHeight();
}
if (newWidth < minimumWidth) {
newWidth = minimumWidth;
}
if (newHeight < minimumHeight) {
newHeight = minimumHeight;
}
}
if ((newHeight != height) || (newWidth != width)) {
d->mBuffer.open(QIODevice::WriteOnly);
d->mImage = d->mImage.scaled(newWidth, newHeight, MessageComposer::MessageComposerSettings::self()->keepImageRatio() ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio);
QByteArray format;
if (d->mMimeType == "image/jpeg") {
format = QByteArrayLiteral("JPG");
} else if (d->mMimeType == "image/png") {
format = QByteArrayLiteral("PNG");
} else {
format = MessageComposer::MessageComposerSettings::self()->writeFormat().toLocal8Bit();
if (format.isEmpty()) {
format = QByteArrayLiteral("PNG");
}
}
const bool result = d->mImage.save(&d->mBuffer, format.constData());
d->mBuffer.close();
return result;
} else {
return false;
}
return true;
}
QByteArray ImageScaling::mimetype() const
{
if (d->mMimeType.isEmpty()) {
return QByteArray();
}
if ((d->mMimeType == "image/jpeg") || (d->mMimeType == "image/png")) {
return d->mMimeType;
} else {
//Add more mimetype if a day we add more saving format.
const QString type = MessageComposer::MessageComposerSettings::self()->writeFormat();
if (type == QLatin1String("JPG")) {
return "image/jpeg";
} else {
return "image/png";
}
}
}
void ImageScaling::setMimetype(const QByteArray &mimetype)
{
d->mMimeType = mimetype;
}
void ImageScaling::setName(const QString &name)
{
d->mName = name;
}
QByteArray ImageScaling::imageArray() const
{
return d->mBuffer.data();
}
QString ImageScaling::generateNewName()
{
if (d->mName.isEmpty()) {
return QString();
}
// Don't rename it.
if ((d->mMimeType == "image/jpeg") || (d->mMimeType == "image/png")) {
return d->mName;
}
QString type = MessageComposer::MessageComposerSettings::self()->writeFormat();
if (type.isEmpty()) {
type = QStringLiteral("PNG");
}
if (d->mName.endsWith(QLatin1String(".png"))) {
if (type != QLatin1String("PNG")) {
d->mName.replace(QLatin1String(".png"), QLatin1String(".jpg"));
}
} else if (d->mName.endsWith(QLatin1String(".jpg"))) {
if (type != QLatin1String("JPG")) {
d->mName.replace(QLatin1String(".jpg"), QLatin1String(".png"));
}
} else {
if (type == QLatin1String("PNG")) {
d->mName += QLatin1String(".png");
} else {
d->mName += QLatin1String(".jpg");
}
}
return d->mName;
}
diff --git a/messagecomposer/src/imagescaling/imagescaling.h b/messagecomposer/src/imagescaling/imagescaling.h
index 87bb15f0..e64826fd 100644
--- a/messagecomposer/src/imagescaling/imagescaling.h
+++ b/messagecomposer/src/imagescaling/imagescaling.h
@@ -1,70 +1,70 @@
/*
- Copyright (C) 2012-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2012-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGESCALING_H
#define IMAGESCALING_H
#include "messagecomposer_export.h"
#include <QByteArray>
#include <QImage>
#include <QBuffer>
namespace MessageComposer {
class ImageScalingPrivate;
class MESSAGECOMPOSER_EXPORT ImageScaling
{
public:
ImageScaling();
~ImageScaling();
/**
* @brief loadImageFromData
* @param data
* @return true if we can load image.
*/
Q_REQUIRED_RESULT bool loadImageFromData(const QByteArray &data);
/**
* @brief resizeImage
* @return true if we are able to resize image
*/
Q_REQUIRED_RESULT bool resizeImage();
/**
* @brief imageArray
* @return data from image after saving
*/
Q_REQUIRED_RESULT QByteArray imageArray() const;
/**
* @brief mimetype
* @return new image mimetype after saving.
*/
Q_REQUIRED_RESULT QByteArray mimetype() const;
void setMimetype(const QByteArray &mimetype);
void setName(const QString &name);
Q_REQUIRED_RESULT QString generateNewName();
private:
ImageScalingPrivate *const d;
};
}
#endif // IMAGESCALING_H
diff --git a/messagecomposer/src/imagescaling/imagescalingselectformat.cpp b/messagecomposer/src/imagescaling/imagescalingselectformat.cpp
index 5a6cd060..33cdb177 100644
--- a/messagecomposer/src/imagescaling/imagescalingselectformat.cpp
+++ b/messagecomposer/src/imagescaling/imagescalingselectformat.cpp
@@ -1,136 +1,136 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescalingselectformat.h"
#include <QLineEdit>
#include <QPushButton>
#include <KLocalizedString>
#include <QListWidget>
#include <QHBoxLayout>
#include <QPointer>
#include <QDialogButtonBox>
#include <QVBoxLayout>
using namespace MessageComposer;
ImageScalingSelectFormatDialog::ImageScalingSelectFormatDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18nc("@title:window", "Select Image Format"));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mListWidget = new QListWidget(this);
mainLayout->addWidget(mListWidget);
mainLayout->addWidget(buttonBox);
initialize();
}
ImageScalingSelectFormatDialog::~ImageScalingSelectFormatDialog()
{
}
void ImageScalingSelectFormatDialog::addImageFormat(const QString &format, const QString &mimetype)
{
QListWidgetItem *item = new QListWidgetItem(format);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setData(ImageScalingSelectFormatDialog::ImageRole, mimetype);
item->setCheckState(Qt::Unchecked);
mListWidget->addItem(item);
}
void ImageScalingSelectFormatDialog::initialize()
{
addImageFormat(QStringLiteral("PNG"), QStringLiteral("image/png"));
addImageFormat(QStringLiteral("JPEG"), QStringLiteral("image/jpeg"));
addImageFormat(QStringLiteral("GIF"), QStringLiteral("image/gif"));
addImageFormat(QStringLiteral("BMP"), QStringLiteral("image/bmp"));
}
QString ImageScalingSelectFormatDialog::format() const
{
const int numberOfElement(mListWidget->count());
QString formatStr;
for (int i = 0; i < numberOfElement; ++i) {
if (mListWidget->item(i)->checkState() == Qt::Checked) {
if (!formatStr.isEmpty()) {
formatStr += QLatin1Char(';');
}
formatStr += mListWidget->item(i)->data(ImageScalingSelectFormatDialog::ImageRole).toString();
}
}
return formatStr;
}
void ImageScalingSelectFormatDialog::setFormat(const QString &format)
{
const QStringList listFormat = format.split(QLatin1Char(';'));
const int numberOfElement(mListWidget->count());
for (int i = 0; i < numberOfElement; ++i) {
QListWidgetItem *item = mListWidget->item(i);
if (listFormat.contains(item->data(ImageScalingSelectFormatDialog::ImageRole).toString())) {
item->setCheckState(Qt::Checked);
}
}
}
ImageScalingSelectFormat::ImageScalingSelectFormat(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
- lay->setMargin(0);
+ lay->setContentsMargins(0, 0, 0, 0);
mFormat = new QLineEdit;
connect(mFormat, &QLineEdit::textChanged, this, &ImageScalingSelectFormat::textChanged);
mFormat->setReadOnly(true);
lay->addWidget(mFormat);
mSelectFormat = new QPushButton(i18n("Select Format..."));
connect(mSelectFormat, &QPushButton::clicked, this, &ImageScalingSelectFormat::slotSelectFormat);
lay->addWidget(mSelectFormat);
}
ImageScalingSelectFormat::~ImageScalingSelectFormat()
{
}
void ImageScalingSelectFormat::slotSelectFormat()
{
QPointer<ImageScalingSelectFormatDialog> dialog = new ImageScalingSelectFormatDialog(this);
dialog->setFormat(mFormat->text());
if (dialog->exec()) {
mFormat->setText(dialog->format());
}
delete dialog;
}
void ImageScalingSelectFormat::setFormat(const QString &format)
{
mFormat->setText(format);
}
QString ImageScalingSelectFormat::format() const
{
return mFormat->text();
}
diff --git a/messagecomposer/src/imagescaling/imagescalingselectformat.h b/messagecomposer/src/imagescaling/imagescalingselectformat.h
index da39b05b..1a9cb5e7 100644
--- a/messagecomposer/src/imagescaling/imagescalingselectformat.h
+++ b/messagecomposer/src/imagescaling/imagescalingselectformat.h
@@ -1,71 +1,71 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGESCALINGSELECTFORMAT_H
#define IMAGESCALINGSELECTFORMAT_H
#include <QDialog>
class QLineEdit;
class QPushButton;
class QListWidget;
namespace MessageComposer {
class ImageScalingSelectFormatDialog : public QDialog
{
Q_OBJECT
public:
explicit ImageScalingSelectFormatDialog(QWidget *parent);
~ImageScalingSelectFormatDialog();
void setFormat(const QString &format);
QString format() const;
private:
enum {
ImageRole = Qt::UserRole + 1
};
void initialize();
void addImageFormat(const QString &format, const QString &mimetype);
QListWidget *mListWidget = nullptr;
};
class ImageScalingSelectFormat : public QWidget
{
Q_OBJECT
public:
explicit ImageScalingSelectFormat(QWidget *parent);
~ImageScalingSelectFormat();
void setFormat(const QString &format);
Q_REQUIRED_RESULT QString format() const;
Q_SIGNALS:
void textChanged(const QString &);
private Q_SLOTS:
void slotSelectFormat();
private:
QLineEdit *mFormat = nullptr;
QPushButton *mSelectFormat = nullptr;
};
}
#endif // IMAGESCALINGSELECTFORMAT_H
diff --git a/messagecomposer/src/imagescaling/imagescalingutils.cpp b/messagecomposer/src/imagescaling/imagescalingutils.cpp
index 23616507..6768d9e9 100644
--- a/messagecomposer/src/imagescaling/imagescalingutils.cpp
+++ b/messagecomposer/src/imagescaling/imagescalingutils.cpp
@@ -1,195 +1,195 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescalingutils.h"
#include "settings/messagecomposersettings.h"
#include <QFileInfo>
using namespace MessageComposer;
bool Utils::resizeImage(MessageCore::AttachmentPart::Ptr part)
{
const QString filename = part->fileName();
const QString pattern = MessageComposer::MessageComposerSettings::self()->filterSourcePattern();
if (!pattern.isEmpty()) {
//TODO use regexp ?
const QStringList lstPattern = pattern.split(QLatin1Char(';'));
for (const QString &patternStr : lstPattern) {
switch (MessageComposer::MessageComposerSettings::self()->filterSourceType()) {
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::NoFilter:
break;
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::IncludeFilesWithPattern:
if (!filename.contains(patternStr)) {
return false;
}
break;
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::ExcludeFilesWithPattern:
if (filename.contains(patternStr)) {
return false;
}
break;
}
}
}
if (MessageComposer::MessageComposerSettings::self()->resizeImagesWithFormats()) {
const QString formatsType = MessageComposer::MessageComposerSettings::self()->resizeImagesWithFormatsType();
if (!formatsType.isEmpty()) {
const QStringList lstFormat = formatsType.split(QLatin1Char(';'));
bool willResizeImage = false;
for (const QString &type : lstFormat) {
if (QString::fromLatin1(part->mimeType()) == type) {
willResizeImage = true;
break;
}
}
if (!willResizeImage) {
return false;
}
}
}
if (MessageComposer::MessageComposerSettings::self()->skipImageLowerSizeEnabled()) {
if (part->size() > MessageComposer::MessageComposerSettings::self()->skipImageLowerSize() * 1024) {
if (hasImage(part->mimeType())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
return true;
}
void Utils::changeFileName(MessageCore::AttachmentPart::Ptr part)
{
if (MessageComposer::MessageComposerSettings::self()->renameResizedImages()) {
QString pattern = MessageComposer::MessageComposerSettings::self()->renameResizedImagesPattern();
if (!pattern.isEmpty()) {
const QString filename = part->fileName();
pattern.replace(QLatin1String("%t"), QTime::currentTime().toString());
pattern.replace(QLatin1String("%d"), QDate::currentDate().toString());
pattern.replace(QLatin1String("%n"), filename); //Original name
pattern.replace(QLatin1String("%e"), QFileInfo(filename).completeSuffix()); //Original extension
const QString type = MessageComposer::MessageComposerSettings::self()->writeFormat();
QString newExtension;
if (type == QLatin1String("JPG")) {
newExtension = QStringLiteral("jpg");
} else if (type == QLatin1String("PNG")) {
newExtension = QStringLiteral("png");
}
if (!newExtension.isEmpty()) {
pattern.replace(QLatin1String("%x"), newExtension); //new Extension
}
//Need to define pattern type.
part->setFileName(pattern);
part->setName(pattern);
}
}
}
bool Utils::filterRecipients(const QStringList &recipients)
{
if (recipients.isEmpty()) {
return false;
}
if (MessageComposer::MessageComposerSettings::self()->filterRecipientType() == MessageComposer::MessageComposerSettings::EnumFilterRecipientType::NoFilter) {
return true;
}
const QString doNotResizeEmailsPattern = MessageComposer::MessageComposerSettings::self()->doNotResizeEmailsPattern();
const QString resizeEmailsPattern = MessageComposer::MessageComposerSettings::self()->resizeEmailsPattern();
if (doNotResizeEmailsPattern.isEmpty() && resizeEmailsPattern.isEmpty()) {
return true;
}
switch (MessageComposer::MessageComposerSettings::self()->filterRecipientType()) {
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::NoFilter:
return true;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeEachEmailsContainsPattern:
if (resizeEmailsPattern.isEmpty()) {
return false;
}
for (const QString &emails : recipients) {
if (!emails.contains(resizeEmailsPattern)) {
return false;
}
}
return true;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeOneEmailContainsPattern:
if (resizeEmailsPattern.isEmpty()) {
return false;
}
for (const QString &emails : recipients) {
if (emails.contains(resizeEmailsPattern)) {
return true;
}
}
return false;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeEachEmailsContainsPattern:
if (doNotResizeEmailsPattern.isEmpty()) {
return false;
}
for (const QString &emails : recipients) {
if (!emails.contains(doNotResizeEmailsPattern)) {
return false;
}
}
return true;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeOneEmailContainsPattern:
if (doNotResizeEmailsPattern.isEmpty()) {
return false;
}
for (const QString &emails : recipients) {
if (emails.contains(doNotResizeEmailsPattern)) {
return true;
}
}
return false;
}
return false;
}
bool Utils::hasImage(const QByteArray &mimetype)
{
if (mimetype == "image/gif"
|| mimetype == "image/jpeg"
|| mimetype == "image/png") {
return true;
}
return false;
}
bool Utils::containsImage(const MessageCore::AttachmentPart::List &parts)
{
for (const MessageCore::AttachmentPart::Ptr &part : parts) {
if (hasImage(part->mimeType())) {
return true;
}
}
return false;
}
diff --git a/messagecomposer/src/imagescaling/imagescalingutils.h b/messagecomposer/src/imagescaling/imagescalingutils.h
index 34ea4e16..33174876 100644
--- a/messagecomposer/src/imagescaling/imagescalingutils.h
+++ b/messagecomposer/src/imagescaling/imagescalingutils.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGESCALINGUTILS_H
#define IMAGESCALINGUTILS_H
#include <MessageCore/AttachmentPart>
namespace MessageComposer {
class Utils
{
public:
Q_REQUIRED_RESULT bool containsImage(const MessageCore::AttachmentPart::List &parts);
Q_REQUIRED_RESULT bool resizeImage(MessageCore::AttachmentPart::Ptr part);
void changeFileName(MessageCore::AttachmentPart::Ptr part);
Q_REQUIRED_RESULT bool filterRecipients(const QStringList &recipients);
Q_REQUIRED_RESULT bool hasImage(const QByteArray &mimetype);
};
}
#endif // IMAGESCALINGUTILS_H
diff --git a/messagecomposer/src/imagescaling/imagescalingwidget.cpp b/messagecomposer/src/imagescaling/imagescalingwidget.cpp
index 0317ffc9..aab6e3a7 100644
--- a/messagecomposer/src/imagescaling/imagescalingwidget.cpp
+++ b/messagecomposer/src/imagescaling/imagescalingwidget.cpp
@@ -1,349 +1,349 @@
/*
- Copyright (C) 2012-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2012-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescalingwidget.h"
#include "ui_imagescalingwidget.h"
#include "settings/messagecomposersettings.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <QButtonGroup>
#include <QComboBox>
#include <QImageWriter>
#include <QWhatsThis>
using namespace MessageComposer;
class MessageComposer::ImageScalingWidgetPrivate
{
public:
ImageScalingWidgetPrivate()
: ui(new Ui::ImageScalingWidget)
{
}
~ImageScalingWidgetPrivate()
{
delete ui;
}
Ui::ImageScalingWidget *ui = nullptr;
QButtonGroup *mSourceFilenameFilterGroup = nullptr;
QButtonGroup *mRecipientFilterGroup = nullptr;
bool mWasChanged = false;
};
ImageScalingWidget::ImageScalingWidget(QWidget *parent)
: QWidget(parent)
, d(new MessageComposer::ImageScalingWidgetPrivate)
{
d->ui->setupUi(this);
initComboBox(d->ui->CBMaximumWidth);
initComboBox(d->ui->CBMaximumHeight);
initComboBox(d->ui->CBMinimumWidth);
initComboBox(d->ui->CBMinimumHeight);
initWriteImageFormat();
connect(d->ui->enabledAutoResize, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->KeepImageRatio, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->AskBeforeResizing, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->EnlargeImageToMinimum, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->ReduceImageToMaximum, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
- connect(d->ui->customMaximumWidth, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
- connect(d->ui->customMaximumHeight, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
- connect(d->ui->customMinimumWidth, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
- connect(d->ui->customMinimumHeight, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
+ connect(d->ui->customMaximumWidth, qOverload<int>(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
+ connect(d->ui->customMaximumHeight, qOverload<int>(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
+ connect(d->ui->customMinimumWidth, qOverload<int>(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
+ connect(d->ui->customMinimumHeight, qOverload<int>(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
connect(d->ui->skipImageSizeLower, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
- connect(d->ui->imageSize, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
+ connect(d->ui->imageSize, qOverload<int>(&QSpinBox::valueChanged), this, &ImageScalingWidget::changed);
connect(d->ui->pattern, &KLineEdit::textChanged, this, &ImageScalingWidget::changed);
- connect(d->ui->CBMaximumWidth, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
- connect(d->ui->CBMaximumHeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
- connect(d->ui->CBMinimumWidth, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
- connect(d->ui->CBMinimumHeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
- connect(d->ui->WriteToImageFormat, QOverload<int>::of(&QComboBox::activated), this, &ImageScalingWidget::changed);
+ connect(d->ui->CBMaximumWidth, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
+ connect(d->ui->CBMaximumHeight, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
+ connect(d->ui->CBMinimumWidth, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
+ connect(d->ui->CBMinimumHeight, qOverload<int>(&QComboBox::currentIndexChanged), this, &ImageScalingWidget::slotComboboxChanged);
+ connect(d->ui->WriteToImageFormat, qOverload<int>(&QComboBox::activated), this, &ImageScalingWidget::changed);
connect(d->ui->renameResizedImage, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->renameResizedImage, &QCheckBox::clicked, d->ui->renameResizedImagePattern, &KLineEdit::setEnabled);
connect(d->ui->renameResizedImagePattern, &KLineEdit::textChanged, this, &ImageScalingWidget::changed);
connect(d->ui->resizeEmailsPattern, &KLineEdit::textChanged, this, &ImageScalingWidget::changed);
connect(d->ui->doNotResizePattern, &KLineEdit::textChanged, this, &ImageScalingWidget::changed);
connect(d->ui->resizeImageWithFormatsType, &MessageComposer::ImageScalingSelectFormat::textChanged, this, &ImageScalingWidget::changed);
connect(d->ui->resizeImageWithFormats, &QCheckBox::clicked, this, &ImageScalingWidget::changed);
connect(d->ui->resizeImageWithFormats, &QCheckBox::clicked, d->ui->resizeImageWithFormatsType, &MessageComposer::ImageScalingSelectFormat::setEnabled);
d->ui->resizeImageWithFormatsType->setEnabled(false);
d->ui->pattern->setEnabled(false);
d->mSourceFilenameFilterGroup = new QButtonGroup(d->ui->filterSourceGroupBox);
- connect(d->mSourceFilenameFilterGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &ImageScalingWidget::slotSourceFilterClicked);
+ connect(d->mSourceFilenameFilterGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImageScalingWidget::slotSourceFilterClicked);
d->mSourceFilenameFilterGroup->addButton(d->ui->notFilterFilename, MessageComposer::MessageComposerSettings::EnumFilterSourceType::NoFilter);
d->mSourceFilenameFilterGroup->addButton(d->ui->includeFilesWithPattern, MessageComposer::MessageComposerSettings::EnumFilterSourceType::IncludeFilesWithPattern);
d->mSourceFilenameFilterGroup->addButton(d->ui->excludeFilesWithPattern, MessageComposer::MessageComposerSettings::EnumFilterSourceType::ExcludeFilesWithPattern);
d->mRecipientFilterGroup = new QButtonGroup(d->ui->tab_4);
- connect(d->mRecipientFilterGroup, QOverload<int>::of(&QButtonGroup::buttonClicked), this, &ImageScalingWidget::slotRecipientFilterClicked);
+ connect(d->mRecipientFilterGroup, qOverload<int>(&QButtonGroup::buttonClicked), this, &ImageScalingWidget::slotRecipientFilterClicked);
d->ui->doNotResizePattern->setEnabled(false);
d->ui->resizeEmailsPattern->setEnabled(false);
d->mRecipientFilterGroup->addButton(d->ui->doNotFilterRecipients, MessageComposer::MessageComposerSettings::EnumFilterRecipientType::NoFilter);
d->mRecipientFilterGroup->addButton(d->ui->resizeEachEmails, MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeEachEmailsContainsPattern);
d->mRecipientFilterGroup->addButton(d->ui->resizeOneEmails, MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeOneEmailContainsPattern);
d->mRecipientFilterGroup->addButton(d->ui->doNotResizeEachEmails, MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeEachEmailsContainsPattern);
d->mRecipientFilterGroup->addButton(d->ui->doNotResizeOneEmails, MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeOneEmailContainsPattern);
d->ui->help->setText(i18n("<a href=\"whatsthis\">How does this work?</a>"));
connect(d->ui->help, &QLabel::linkActivated, this, &ImageScalingWidget::slotHelpLinkClicked);
d->ui->help->setContextMenuPolicy(Qt::NoContextMenu);
}
ImageScalingWidget::~ImageScalingWidget()
{
delete d;
}
void ImageScalingWidget::slotHelpLinkClicked(const QString &)
{
const QString help
= i18n("<qt>"
"<p>Here you can define image filename. "
"You can use:</p>"
"<ul>"
"<li>%t set current time</li>"
"<li>%d set current date</li>"
"<li>%n original filename</li>"
"<li>%e original extension</li>"
"<li>%x new extension</li>"
"</ul>"
"</qt>");
QWhatsThis::showText(QCursor::pos(), help);
}
void ImageScalingWidget::slotSourceFilterClicked(int button)
{
d->ui->pattern->setEnabled(button != 0);
Q_EMIT changed();
}
void ImageScalingWidget::slotRecipientFilterClicked(int button)
{
d->ui->resizeEmailsPattern->setEnabled((button == MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeEachEmailsContainsPattern)
|| (button == MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeOneEmailContainsPattern));
d->ui->doNotResizePattern->setEnabled((button == MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeEachEmailsContainsPattern)
|| (button == MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeOneEmailContainsPattern));
Q_EMIT changed();
}
void ImageScalingWidget::slotComboboxChanged(int index)
{
QComboBox *combo = qobject_cast< QComboBox * >(sender());
if (combo) {
const bool isCustom = combo->itemData(index) == -1;
if (combo == d->ui->CBMaximumWidth) {
d->ui->customMaximumWidth->setEnabled(isCustom);
} else if (combo == d->ui->CBMaximumHeight) {
d->ui->customMaximumHeight->setEnabled(isCustom);
} else if (combo == d->ui->CBMinimumWidth) {
d->ui->customMinimumWidth->setEnabled(isCustom);
} else if (combo == d->ui->CBMinimumHeight) {
d->ui->customMinimumHeight->setEnabled(isCustom);
}
Q_EMIT changed();
}
}
void ImageScalingWidget::initComboBox(QComboBox *combo)
{
const QList<int> size = { 240, 320, 512, 640, 800, 1024, 1600, 2048 };
for (int val : size) {
combo->addItem(QString::number(val), val);
}
combo->addItem(i18n("Custom"), -1);
}
void ImageScalingWidget::initWriteImageFormat()
{
/* Too many format :)
QList<QByteArray> listWriteFormat = QImageWriter::supportedImageFormats();
Q_FOREACH(const QByteArray& format, listWriteFormat) {
d->ui->WriteToImageFormat->addItem(QString::fromLatin1(format));
}
*/
//known by several mailer.
d->ui->WriteToImageFormat->addItem(QStringLiteral("JPG"));
d->ui->WriteToImageFormat->addItem(QStringLiteral("PNG"));
}
void ImageScalingWidget::updateSettings()
{
d->ui->enabledAutoResize->setChecked(MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled());
d->ui->KeepImageRatio->setChecked(MessageComposer::MessageComposerSettings::self()->keepImageRatio());
d->ui->AskBeforeResizing->setChecked(MessageComposer::MessageComposerSettings::self()->askBeforeResizing());
d->ui->EnlargeImageToMinimum->setChecked(MessageComposer::MessageComposerSettings::self()->enlargeImageToMinimum());
d->ui->ReduceImageToMaximum->setChecked(MessageComposer::MessageComposerSettings::self()->reduceImageToMaximum());
d->ui->skipImageSizeLower->setChecked(MessageComposer::MessageComposerSettings::self()->skipImageLowerSizeEnabled());
d->ui->imageSize->setValue(MessageComposer::MessageComposerSettings::self()->skipImageLowerSize());
d->ui->customMaximumWidth->setValue(MessageComposer::MessageComposerSettings::self()->customMaximumWidth());
d->ui->customMaximumHeight->setValue(MessageComposer::MessageComposerSettings::self()->customMaximumHeight());
d->ui->customMinimumWidth->setValue(MessageComposer::MessageComposerSettings::self()->customMinimumWidth());
d->ui->customMinimumHeight->setValue(MessageComposer::MessageComposerSettings::self()->customMinimumHeight());
int index = qMax(0, d->ui->CBMaximumWidth->findData(MessageComposer::MessageComposerSettings::self()->maximumWidth()));
d->ui->CBMaximumWidth->setCurrentIndex(index);
d->ui->customMaximumWidth->setEnabled(d->ui->CBMaximumWidth->itemData(index) == -1);
index = qMax(0, d->ui->CBMaximumHeight->findData(MessageComposer::MessageComposerSettings::self()->maximumHeight()));
d->ui->CBMaximumHeight->setCurrentIndex(index);
d->ui->customMaximumHeight->setEnabled(d->ui->CBMaximumHeight->itemData(index) == -1);
index = qMax(0, d->ui->CBMinimumWidth->findData(MessageComposer::MessageComposerSettings::self()->minimumWidth()));
d->ui->CBMinimumWidth->setCurrentIndex(index);
d->ui->customMinimumWidth->setEnabled(d->ui->CBMinimumWidth->itemData(index) == -1);
index = qMax(0, d->ui->CBMinimumHeight->findData(MessageComposer::MessageComposerSettings::self()->minimumHeight()));
d->ui->CBMinimumHeight->setCurrentIndex(index);
d->ui->customMinimumHeight->setEnabled(d->ui->CBMinimumHeight->itemData(index) == -1);
index = d->ui->WriteToImageFormat->findData(MessageComposer::MessageComposerSettings::self()->writeFormat());
if (index == -1) {
d->ui->WriteToImageFormat->setCurrentIndex(0);
} else {
d->ui->WriteToImageFormat->setCurrentIndex(index);
}
d->ui->pattern->setText(MessageComposer::MessageComposerSettings::self()->filterSourcePattern());
d->ui->renameResizedImage->setChecked(MessageComposer::MessageComposerSettings::self()->renameResizedImages());
d->ui->renameResizedImagePattern->setText(MessageComposer::MessageComposerSettings::self()->renameResizedImagesPattern());
d->ui->renameResizedImagePattern->setEnabled(d->ui->renameResizedImage->isChecked());
d->ui->doNotResizePattern->setText(MessageComposer::MessageComposerSettings::self()->doNotResizeEmailsPattern());
d->ui->resizeEmailsPattern->setText(MessageComposer::MessageComposerSettings::self()->resizeEmailsPattern());
d->ui->resizeImageWithFormats->setChecked(MessageComposer::MessageComposerSettings::self()->resizeImagesWithFormats());
d->ui->resizeImageWithFormatsType->setFormat(MessageComposer::MessageComposerSettings::self()->resizeImagesWithFormatsType());
d->ui->resizeImageWithFormatsType->setEnabled(d->ui->resizeImageWithFormats->isChecked());
updateFilterSourceTypeSettings();
updateEmailsFilterTypeSettings();
}
void ImageScalingWidget::loadConfig()
{
updateSettings();
d->mWasChanged = false;
}
void ImageScalingWidget::updateFilterSourceTypeSettings()
{
switch (MessageComposer::MessageComposerSettings::self()->filterSourceType()) {
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::NoFilter:
d->ui->notFilterFilename->setChecked(true);
d->ui->pattern->setEnabled(false);
break;
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::IncludeFilesWithPattern:
d->ui->includeFilesWithPattern->setChecked(true);
d->ui->pattern->setEnabled(true);
break;
case MessageComposer::MessageComposerSettings::EnumFilterSourceType::ExcludeFilesWithPattern:
d->ui->excludeFilesWithPattern->setChecked(true);
d->ui->pattern->setEnabled(true);
break;
}
}
void ImageScalingWidget::updateEmailsFilterTypeSettings()
{
d->ui->doNotResizePattern->setEnabled(false);
d->ui->resizeEmailsPattern->setEnabled(false);
switch (MessageComposer::MessageComposerSettings::self()->filterRecipientType()) {
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::NoFilter:
d->ui->doNotFilterRecipients->setChecked(true);
break;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeEachEmailsContainsPattern:
d->ui->resizeEachEmails->setChecked(true);
d->ui->resizeEmailsPattern->setEnabled(true);
break;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::ResizeOneEmailContainsPattern:
d->ui->resizeOneEmails->setChecked(true);
d->ui->resizeEmailsPattern->setEnabled(true);
break;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeEachEmailsContainsPattern:
d->ui->doNotResizeEachEmails->setChecked(true);
d->ui->doNotResizePattern->setEnabled(true);
break;
case MessageComposer::MessageComposerSettings::EnumFilterRecipientType::DontResizeOneEmailContainsPattern:
d->ui->doNotResizeOneEmails->setChecked(true);
d->ui->doNotResizePattern->setEnabled(false);
break;
}
}
void ImageScalingWidget::writeConfig()
{
if (d->ui->EnlargeImageToMinimum->isChecked() && d->ui->ReduceImageToMaximum->isChecked()) {
if ((d->ui->customMinimumWidth->value() >= d->ui->customMaximumWidth->value())
|| (d->ui->customMinimumHeight->value() >= d->ui->customMaximumHeight->value())) {
KMessageBox::error(this, i18n("Please verify minimum and maximum values."), i18n("Error in minimum Maximum value"));
return;
}
}
MessageComposer::MessageComposerSettings::self()->setAutoResizeImageEnabled(d->ui->enabledAutoResize->isChecked());
MessageComposer::MessageComposerSettings::self()->setKeepImageRatio(d->ui->KeepImageRatio->isChecked());
MessageComposer::MessageComposerSettings::self()->setAskBeforeResizing(d->ui->AskBeforeResizing->isChecked());
MessageComposer::MessageComposerSettings::self()->setEnlargeImageToMinimum(d->ui->EnlargeImageToMinimum->isChecked());
MessageComposer::MessageComposerSettings::self()->setReduceImageToMaximum(d->ui->ReduceImageToMaximum->isChecked());
MessageComposer::MessageComposerSettings::self()->setCustomMaximumWidth(d->ui->customMaximumWidth->value());
MessageComposer::MessageComposerSettings::self()->setCustomMaximumHeight(d->ui->customMaximumHeight->value());
MessageComposer::MessageComposerSettings::self()->setCustomMinimumWidth(d->ui->customMinimumWidth->value());
MessageComposer::MessageComposerSettings::self()->setCustomMinimumHeight(d->ui->customMinimumHeight->value());
MessageComposer::MessageComposerSettings::self()->setMaximumWidth(d->ui->CBMaximumWidth->itemData(d->ui->CBMaximumWidth->currentIndex()).toInt());
MessageComposer::MessageComposerSettings::self()->setMaximumHeight(d->ui->CBMaximumHeight->itemData(d->ui->CBMaximumHeight->currentIndex()).toInt());
MessageComposer::MessageComposerSettings::self()->setMinimumWidth(d->ui->CBMinimumWidth->itemData(d->ui->CBMinimumWidth->currentIndex()).toInt());
MessageComposer::MessageComposerSettings::self()->setMinimumHeight(d->ui->CBMinimumHeight->itemData(d->ui->CBMinimumHeight->currentIndex()).toInt());
MessageComposer::MessageComposerSettings::self()->setWriteFormat(d->ui->WriteToImageFormat->currentText());
MessageComposer::MessageComposerSettings::self()->setSkipImageLowerSizeEnabled(d->ui->skipImageSizeLower->isChecked());
MessageComposer::MessageComposerSettings::self()->setSkipImageLowerSize(d->ui->imageSize->value());
MessageComposer::MessageComposerSettings::self()->setFilterSourcePattern(d->ui->pattern->text());
MessageComposer::MessageComposerSettings::self()->setFilterSourceType(d->mSourceFilenameFilterGroup->checkedId());
MessageComposer::MessageComposerSettings::self()->setRenameResizedImages(d->ui->renameResizedImage->isChecked());
MessageComposer::MessageComposerSettings::self()->setRenameResizedImagesPattern(d->ui->renameResizedImagePattern->text());
MessageComposer::MessageComposerSettings::self()->setDoNotResizeEmailsPattern(d->ui->doNotResizePattern->text());
MessageComposer::MessageComposerSettings::self()->setResizeEmailsPattern(d->ui->resizeEmailsPattern->text());
MessageComposer::MessageComposerSettings::self()->setFilterRecipientType(d->mRecipientFilterGroup->checkedId());
MessageComposer::MessageComposerSettings::self()->setResizeImagesWithFormats(d->ui->resizeImageWithFormats->isChecked());
MessageComposer::MessageComposerSettings::self()->setResizeImagesWithFormatsType(d->ui->resizeImageWithFormatsType->format());
d->mWasChanged = false;
}
void ImageScalingWidget::resetToDefault()
{
const bool bUseDefaults = MessageComposer::MessageComposerSettings::self()->useDefaults(true);
updateSettings();
MessageComposer::MessageComposerSettings::self()->useDefaults(bUseDefaults);
}
diff --git a/messagecomposer/src/imagescaling/imagescalingwidget.h b/messagecomposer/src/imagescaling/imagescalingwidget.h
index 7775ef30..d99f48bf 100644
--- a/messagecomposer/src/imagescaling/imagescalingwidget.h
+++ b/messagecomposer/src/imagescaling/imagescalingwidget.h
@@ -1,62 +1,62 @@
/*
- Copyright (C) 2012-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2012-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef IMAGESCALINGWIDGET_H
#define IMAGESCALINGWIDGET_H
#include <QWidget>
#include "messagecomposer_export.h"
class QComboBox;
namespace Ui {
class ImageScalingWidget;
}
namespace MessageComposer {
class ImageScalingWidgetPrivate;
class MESSAGECOMPOSER_EXPORT ImageScalingWidget : public QWidget
{
Q_OBJECT
public:
explicit ImageScalingWidget(QWidget *parent = nullptr);
~ImageScalingWidget();
void loadConfig();
void writeConfig();
void resetToDefault();
Q_SIGNALS:
void changed();
private Q_SLOTS:
void slotComboboxChanged(int index);
void slotSourceFilterClicked(int);
void slotRecipientFilterClicked(int);
void slotHelpLinkClicked(const QString &);
private:
void updateFilterSourceTypeSettings();
void initComboBox(QComboBox *combo);
void initWriteImageFormat();
void updateEmailsFilterTypeSettings();
void updateSettings();
ImageScalingWidgetPrivate *const d;
};
}
#endif // IMAGESCALINGWIDGET_H
diff --git a/messagecomposer/src/imagescaling/tests/imagescaling_gui.cpp b/messagecomposer/src/imagescaling/tests/imagescaling_gui.cpp
index 65073c2a..a2babc2b 100644
--- a/messagecomposer/src/imagescaling/tests/imagescaling_gui.cpp
+++ b/messagecomposer/src/imagescaling/tests/imagescaling_gui.cpp
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "imagescaling_gui.h"
#include "MessageComposer/ImageScalingWidget"
#include <QHBoxLayout>
#include <QApplication>
#include <QCommandLineParser>
#include <QStandardPaths>
ImageScalingTestWidget::ImageScalingTestWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
lay->addWidget(new MessageComposer::ImageScalingWidget(this));
}
ImageScalingTestWidget::~ImageScalingTestWidget()
{
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
ImageScalingTestWidget *w = new ImageScalingTestWidget();
w->resize(800, 600);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/messagecomposer/src/imagescaling/tests/imagescaling_gui.h b/messagecomposer/src/imagescaling/tests/imagescaling_gui.h
index 42032922..ffb810f1 100644
--- a/messagecomposer/src/imagescaling/tests/imagescaling_gui.h
+++ b/messagecomposer/src/imagescaling/tests/imagescaling_gui.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2013-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef TEST_IMAGESCALING_GUI_H
#define TEST_IMAGESCALING_GUI_H
#include <QWidget>
class ImageScalingTestWidget : public QWidget
{
Q_OBJECT
public:
explicit ImageScalingTestWidget(QWidget *parent = nullptr);
~ImageScalingTestWidget();
};
#endif
diff --git a/messagecomposer/src/job/attachmentclipboardjob.cpp b/messagecomposer/src/job/attachmentclipboardjob.cpp
index 7025e7ec..1d0eb4f0 100644
--- a/messagecomposer/src/job/attachmentclipboardjob.cpp
+++ b/messagecomposer/src/job/attachmentclipboardjob.cpp
@@ -1,64 +1,65 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "attachmentclipboardjob.h"
#include <KLocalizedString>
#include <QApplication>
#include <QClipboard>
-#include <qinputdialog.h>
+#include <QInputDialog>
using namespace MessageComposer;
AttachmentClipBoardJob::AttachmentClipBoardJob(QObject *parent)
: MessageCore::AttachmentLoadJob(parent)
{
}
AttachmentClipBoardJob::~AttachmentClipBoardJob()
{
}
void AttachmentClipBoardJob::addAttachment(const QByteArray &data, const QString &attachmentName)
{
MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
if (!data.isEmpty()) {
attachment->setName(attachmentName);
attachment->setFileName(attachmentName);
attachment->setData(data);
attachment->setMimeType("text/plain");
// TODO what about the other fields?
}
setAttachmentPart(attachment);
emitResult(); // Success.
}
void AttachmentClipBoardJob::doStart()
{
QClipboard *clip = QApplication::clipboard();
const QString clipText = clip->text();
if (clipText.isEmpty()) {
setError(KJob::UserDefinedError);
setErrorText(i18n("No text found in Clipboard"));
emitResult();
} else {
QString attachmentName = QInputDialog::getText(nullptr, i18n("Define Attachment Name"), i18n("Attachment Name:"));
if (attachmentName.isEmpty()) {
attachmentName = i18n("Clipboard Text");
}
addAttachment(clipText.toUtf8(), attachmentName);
}
}
diff --git a/messagecomposer/src/job/attachmentclipboardjob.h b/messagecomposer/src/job/attachmentclipboardjob.h
index ffaee38e..f51acf9a 100644
--- a/messagecomposer/src/job/attachmentclipboardjob.h
+++ b/messagecomposer/src/job/attachmentclipboardjob.h
@@ -1,38 +1,39 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef AttachmentClipBoardJob_H
#define AttachmentClipBoardJob_H
#include "MessageCore/AttachmentLoadJob"
#include "messagecomposer_export.h"
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT AttachmentClipBoardJob : public MessageCore::AttachmentLoadJob
{
Q_OBJECT
public:
explicit AttachmentClipBoardJob(QObject *parent = nullptr);
~AttachmentClipBoardJob() override;
protected Q_SLOTS:
void doStart() override;
private:
void addAttachment(const QByteArray &data, const QString &attachmentName);
};
}
#endif // AttachmentClipBoardJob_H
diff --git a/messagecomposer/src/job/attachmentfrompublickeyjob.cpp b/messagecomposer/src/job/attachmentfrompublickeyjob.cpp
index d42fca46..91ac8290 100644
--- a/messagecomposer/src/job/attachmentfrompublickeyjob.cpp
+++ b/messagecomposer/src/job/attachmentfrompublickeyjob.cpp
@@ -1,128 +1,128 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Based on KMail code by:
Various authors.
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "attachmentfrompublickeyjob.h"
#include <KDialogJobUiDelegate>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <Libkleo/ProgressDialog>
using namespace MessageComposer;
using MessageCore::AttachmentPart;
class Q_DECL_HIDDEN MessageComposer::AttachmentFromPublicKeyJob::Private
{
public:
Private(AttachmentFromPublicKeyJob *qq);
void exportResult(const GpgME::Error &error, const QByteArray &keyData); // slot
void emitGpgError(const GpgME::Error &error);
AttachmentFromPublicKeyJob *const q;
QString fingerprint;
QByteArray data;
};
AttachmentFromPublicKeyJob::Private::Private(AttachmentFromPublicKeyJob *qq)
: q(qq)
{
}
void AttachmentFromPublicKeyJob::Private::exportResult(const GpgME::Error &error, const QByteArray &keyData)
{
if (error) {
emitGpgError(error);
return;
}
// Create the AttachmentPart.
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
part->setName(i18n("OpenPGP key 0x%1", fingerprint.right(8)));
part->setFileName(QString::fromLatin1(QByteArray(QByteArray("0x") + fingerprint.toLatin1() + QByteArray(".asc"))));
part->setMimeType("application/pgp-keys");
part->setData(keyData);
q->setAttachmentPart(part);
q->emitResult(); // Success.
}
void AttachmentFromPublicKeyJob::Private::emitGpgError(const GpgME::Error &error)
{
Q_ASSERT(error);
const QString msg = i18n("<p>An error occurred while trying to export "
"the key from the backend:</p>"
"<p><b>%1</b></p>",
QString::fromLocal8Bit(error.asString()));
q->setError(KJob::UserDefinedError);
q->setErrorText(msg);
q->emitResult();
}
AttachmentFromPublicKeyJob::AttachmentFromPublicKeyJob(const QString &fingerprint, QObject *parent)
: AttachmentLoadJob(parent)
, d(new Private(this))
{
d->fingerprint = fingerprint;
}
AttachmentFromPublicKeyJob::~AttachmentFromPublicKeyJob()
{
delete d;
}
QString AttachmentFromPublicKeyJob::fingerprint() const
{
return d->fingerprint;
}
void AttachmentFromPublicKeyJob::setFingerprint(const QString &fingerprint)
{
d->fingerprint = fingerprint;
}
void AttachmentFromPublicKeyJob::doStart()
{
QGpgME::ExportJob *job = QGpgME::openpgp()->publicKeyExportJob(true);
Q_ASSERT(job);
connect(job, &QGpgME::ExportJob::result, this, [this](const GpgME::Error &error, const QByteArray &ba) {
d->exportResult(error, ba);
});
const GpgME::Error error = job->start(QStringList(d->fingerprint));
if (error) {
d->emitGpgError(error);
// TODO check autodeletion policy of Kleo::Jobs...
return;
} else if (uiDelegate()) {
Q_ASSERT(dynamic_cast<KDialogJobUiDelegate *>(uiDelegate()));
KDialogJobUiDelegate *delegate = static_cast<KDialogJobUiDelegate *>(uiDelegate());
(void)new Kleo::ProgressDialog(job, i18n("Exporting key..."),
delegate->window());
}
}
#include "moc_attachmentfrompublickeyjob.cpp"
diff --git a/messagecomposer/src/job/attachmentfrompublickeyjob.h b/messagecomposer/src/job/attachmentfrompublickeyjob.h
index bb57623f..60ee9a02 100644
--- a/messagecomposer/src/job/attachmentfrompublickeyjob.h
+++ b/messagecomposer/src/job/attachmentfrompublickeyjob.h
@@ -1,53 +1,53 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGE_ATTACHMENTFROMPUBLICKEYJOB_H
#define MESSAGE_ATTACHMENTFROMPUBLICKEYJOB_H
#include <MessageCore/AttachmentLoadJob>
#include "messagecomposer_export.h"
namespace MessageComposer {
/**
*/
// TODO I have no idea how to test this. Have a fake keyring???
class MESSAGECOMPOSER_EXPORT AttachmentFromPublicKeyJob : public MessageCore::AttachmentLoadJob
{
Q_OBJECT
public:
explicit AttachmentFromPublicKeyJob(const QString &fingerprint, QObject *parent = nullptr);
~AttachmentFromPublicKeyJob() override;
Q_REQUIRED_RESULT QString fingerprint() const;
void setFingerprint(const QString &fingerprint);
protected Q_SLOTS:
void doStart() override;
private:
class Private;
friend class Private;
Private *const d;
};
} //
#endif
diff --git a/messagecomposer/src/job/attachmentvcardfromaddressbookjob.cpp b/messagecomposer/src/job/attachmentvcardfromaddressbookjob.cpp
index 2a0435cc..f4ed3bf0 100644
--- a/messagecomposer/src/job/attachmentvcardfromaddressbookjob.cpp
+++ b/messagecomposer/src/job/attachmentvcardfromaddressbookjob.cpp
@@ -1,119 +1,120 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "attachmentvcardfromaddressbookjob.h"
#include <KLocalizedString>
#include <KContacts/Addressee>
#include <KContacts/ContactGroup>
#include <KContacts/VCardConverter>
#include <akonadi/contact/contactgroupexpandjob.h>
using namespace MessageComposer;
class MessageComposer::AttachmentVcardFromAddressBookJobPrivate
{
public:
explicit AttachmentVcardFromAddressBookJobPrivate(const Akonadi::Item &item)
: mItem(item)
{
}
Akonadi::Item mItem;
};
AttachmentVcardFromAddressBookJob::AttachmentVcardFromAddressBookJob(const Akonadi::Item &item, QObject *parent)
: MessageCore::AttachmentLoadJob(parent)
, d(new MessageComposer::AttachmentVcardFromAddressBookJobPrivate(item))
{
}
AttachmentVcardFromAddressBookJob::~AttachmentVcardFromAddressBookJob()
{
delete d;
}
void AttachmentVcardFromAddressBookJob::addAttachment(const QByteArray &data, const QString &attachmentName)
{
MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart());
if (!data.isEmpty()) {
attachment->setName(attachmentName);
attachment->setFileName(attachmentName);
attachment->setData(data);
attachment->setMimeType("text/x-vcard");
// TODO what about the other fields?
}
setAttachmentPart(attachment);
emitResult(); // Success.
}
void AttachmentVcardFromAddressBookJob::doStart()
{
if (d->mItem.isValid()) {
if (d->mItem.hasPayload<KContacts::Addressee>()) {
const KContacts::Addressee contact = d->mItem.payload<KContacts::Addressee>();
if (contact.isEmpty()) {
invalidContact();
} else {
const QString contactRealName(contact.realName());
const QString attachmentName = (contactRealName.isEmpty() ? QStringLiteral("vcard") : contactRealName) + QLatin1String(".vcf");
QByteArray data = d->mItem.payloadData();
//Workaround about broken kaddressbook fields.
KContacts::adaptIMAttributes(data);
addAttachment(data, attachmentName);
}
} else if (d->mItem.hasPayload<KContacts::ContactGroup>()) {
const KContacts::ContactGroup group = d->mItem.payload<KContacts::ContactGroup>();
const QString groupName(group.name());
const QString attachmentName = (groupName.isEmpty() ? QStringLiteral("vcard") : groupName) + QLatin1String(".vcf");
Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group, this);
expandJob->setProperty("groupName", attachmentName);
connect(expandJob, &KJob::result, this, &AttachmentVcardFromAddressBookJob::slotExpandGroupResult);
expandJob->start();
} else {
setError(KJob::UserDefinedError);
setErrorText(i18n("Unknown Contact Type"));
emitResult();
}
} else {
invalidContact();
}
}
void AttachmentVcardFromAddressBookJob::invalidContact()
{
setError(KJob::UserDefinedError);
setErrorText(i18n("Invalid Contact"));
emitResult();
}
void AttachmentVcardFromAddressBookJob::slotExpandGroupResult(KJob *job)
{
Akonadi::ContactGroupExpandJob *expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job);
Q_ASSERT(expandJob);
const QString attachmentName = expandJob->property("groupName").toString();
KContacts::VCardConverter converter;
const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0);
if (!groupData.isEmpty()) {
addAttachment(groupData, attachmentName);
} else {
setError(KJob::UserDefinedError);
setErrorText(i18n("Impossible to generate vCard."));
emitResult();
}
}
diff --git a/messagecomposer/src/job/attachmentvcardfromaddressbookjob.h b/messagecomposer/src/job/attachmentvcardfromaddressbookjob.h
index 77cdb38b..f1fa24a2 100644
--- a/messagecomposer/src/job/attachmentvcardfromaddressbookjob.h
+++ b/messagecomposer/src/job/attachmentvcardfromaddressbookjob.h
@@ -1,45 +1,46 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef ATTACHMENTVCARDFROMADDRESSBOOKJOB_H
#define ATTACHMENTVCARDFROMADDRESSBOOKJOB_H
#include "MessageCore/AttachmentLoadJob"
#include <AkonadiCore/Item>
#include "messagecomposer_export.h"
namespace MessageComposer {
class AttachmentVcardFromAddressBookJobPrivate;
class MESSAGECOMPOSER_EXPORT AttachmentVcardFromAddressBookJob : public MessageCore::AttachmentLoadJob
{
Q_OBJECT
public:
explicit AttachmentVcardFromAddressBookJob(const Akonadi::Item &item, QObject *parent = nullptr);
~AttachmentVcardFromAddressBookJob() override;
protected Q_SLOTS:
void doStart() override;
private Q_SLOTS:
void slotExpandGroupResult(KJob *job);
private:
void invalidContact();
void addAttachment(const QByteArray &data, const QString &attachmentName);
AttachmentVcardFromAddressBookJobPrivate *const d;
};
}
#endif // ATTACHMENTVCARDFROMADDRESSBOOKJOB_H
diff --git a/messagecomposer/src/job/emailaddressresolvejob.cpp b/messagecomposer/src/job/emailaddressresolvejob.cpp
index 24c70c7d..be6c32e2 100644
--- a/messagecomposer/src/job/emailaddressresolvejob.cpp
+++ b/messagecomposer/src/job/emailaddressresolvejob.cpp
@@ -1,187 +1,206 @@
/*
* This file is part of KMail.
*
* Copyright (c) 2010 KDAB
*
* Authors: Tobias Koenig <tokoe@kde.org>
* Leo Franchi <lfranchi@kde.org>
*
* 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.
*/
#include "job/emailaddressresolvejob.h"
#include "aliasesexpandjob.h"
#include "settings/messagecomposersettings.h"
#include "MessageComposer/Composer"
#include "MessageComposer/InfoPart"
#include <KEmailAddress>
+#include <QVariant>
using namespace MessageComposer;
class MessageComposer::EmailAddressResolveJobPrivate
{
public:
EmailAddressResolveJobPrivate()
{
}
QVariantMap mResultMap;
+ QString mFrom;
QStringList mTo;
QStringList mCc;
QStringList mBcc;
- QString mFrom;
+ QStringList mReplyTo;
QString mDefaultDomainName;
int mJobCount = 0;
};
EmailAddressResolveJob::EmailAddressResolveJob(QObject *parent)
: KJob(parent)
, d(new MessageComposer::EmailAddressResolveJobPrivate)
{
}
EmailAddressResolveJob::~EmailAddressResolveJob()
{
delete d;
}
static inline bool containsAliases(const QString &address)
{
// an valid email is defined as foo@foo.extension
return !(address.contains(QLatin1Char('@')) && address.contains(QLatin1Char('.')));
}
static bool containsAliases(const QStringList &addresses)
{
for (const QString &address : addresses) {
if (containsAliases(address)) {
return true;
}
}
return false;
}
void EmailAddressResolveJob::setDefaultDomainName(const QString &domainName)
{
d->mDefaultDomainName = domainName;
}
void EmailAddressResolveJob::start()
{
QVector<AliasesExpandJob *> jobs;
if (containsAliases(d->mFrom)) {
AliasesExpandJob *job = new AliasesExpandJob(d->mFrom, d->mDefaultDomainName, this);
job->setProperty("id", QStringLiteral("infoPartFrom"));
connect(job, &AliasesExpandJob::result, this, &EmailAddressResolveJob::slotAliasExpansionDone);
jobs << job;
}
if (containsAliases(d->mTo)) {
AliasesExpandJob *job = new AliasesExpandJob(d->mTo.join(QStringLiteral(", ")), d->mDefaultDomainName, this);
job->setProperty("id", QStringLiteral("infoPartTo"));
connect(job, &AliasesExpandJob::result, this, &EmailAddressResolveJob::slotAliasExpansionDone);
jobs << job;
}
if (containsAliases(d->mCc)) {
AliasesExpandJob *job = new AliasesExpandJob(d->mCc.join(QStringLiteral(", ")), d->mDefaultDomainName, this);
job->setProperty("id", QStringLiteral("infoPartCc"));
connect(job, &AliasesExpandJob::result, this, &EmailAddressResolveJob::slotAliasExpansionDone);
jobs << job;
}
if (containsAliases(d->mBcc)) {
AliasesExpandJob *job = new AliasesExpandJob(d->mBcc.join(QStringLiteral(", ")), d->mDefaultDomainName, this);
job->setProperty("id", QStringLiteral("infoPartBcc"));
connect(job, &AliasesExpandJob::result, this, &EmailAddressResolveJob::slotAliasExpansionDone);
jobs << job;
}
+ if (containsAliases(d->mReplyTo)) {
+ AliasesExpandJob *job = new AliasesExpandJob(d->mReplyTo.join(QStringLiteral(", ")), d->mDefaultDomainName, this);
+ job->setProperty("id", QStringLiteral("infoPartReplyTo"));
+ connect(job, &AliasesExpandJob::result, this, &EmailAddressResolveJob::slotAliasExpansionDone);
+ jobs << job;
+ }
d->mJobCount = jobs.count();
if (d->mJobCount == 0) {
emitResult();
} else {
for (AliasesExpandJob *job : qAsConst(jobs)) {
job->start();
}
}
}
void EmailAddressResolveJob::slotAliasExpansionDone(KJob *job)
{
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
emitResult();
return;
}
const AliasesExpandJob *expandJob = qobject_cast<AliasesExpandJob *>(job);
d->mResultMap.insert(expandJob->property("id").toString(), expandJob->addresses());
d->mJobCount--;
if (d->mJobCount == 0) {
emitResult();
}
}
void EmailAddressResolveJob::setFrom(const QString &from)
{
d->mFrom = from;
d->mResultMap.insert(QStringLiteral("infoPartFrom"), from);
}
void EmailAddressResolveJob::setTo(const QStringList &to)
{
d->mTo = to;
d->mResultMap.insert(QStringLiteral("infoPartTo"), to.join(QStringLiteral(", ")));
}
void EmailAddressResolveJob::setCc(const QStringList &cc)
{
d->mCc = cc;
d->mResultMap.insert(QStringLiteral("infoPartCc"), cc.join(QStringLiteral(", ")));
}
void EmailAddressResolveJob::setBcc(const QStringList &bcc)
{
d->mBcc = bcc;
d->mResultMap.insert(QStringLiteral("infoPartBcc"), bcc.join(QStringLiteral(", ")));
}
+void EmailAddressResolveJob::setReplyTo(const QStringList &replyTo)
+{
+ d->mReplyTo = replyTo;
+ d->mResultMap.insert(QStringLiteral("infoPartReplyTo"), replyTo.join(QStringLiteral(", ")));
+}
+
QString EmailAddressResolveJob::expandedFrom() const
{
return d->mResultMap.value(QStringLiteral("infoPartFrom")).toString();
}
QStringList EmailAddressResolveJob::expandedTo() const
{
return KEmailAddress::splitAddressList(d->mResultMap.value(QStringLiteral("infoPartTo")).toString());
}
QStringList EmailAddressResolveJob::expandedCc() const
{
return KEmailAddress::splitAddressList(d->mResultMap.value(QStringLiteral("infoPartCc")).toString());
}
QStringList EmailAddressResolveJob::expandedBcc() const
{
return KEmailAddress::splitAddressList(d->mResultMap.value(QStringLiteral("infoPartBcc")).toString());
}
+
+QStringList EmailAddressResolveJob::expandedReplyTo() const
+{
+ return KEmailAddress::splitAddressList(d->mResultMap.value(QStringLiteral("infoPartReplyTo")).toString());
+}
diff --git a/messagecomposer/src/job/emailaddressresolvejob.h b/messagecomposer/src/job/emailaddressresolvejob.h
index 3239f861..05de32db 100644
--- a/messagecomposer/src/job/emailaddressresolvejob.h
+++ b/messagecomposer/src/job/emailaddressresolvejob.h
@@ -1,111 +1,119 @@
/*
* This file is part of KMail.
*
* Copyright (c) 2010 KDAB
*
* Authors: Tobias Koenig <tokoe@kde.org>
* Leo Franchi <lfranchi@kde.org>
*
* 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.
*/
#ifndef EMAILADDRESSRESOLVEJOB_H
#define EMAILADDRESSRESOLVEJOB_H
#include "messagecomposer_export.h"
#include <kjob.h>
#include <QStringList>
namespace MessageComposer {
class Composer;
/**
* @short A job to resolve nicknames, distribution lists and email addresses for queued emails.
*/
class EmailAddressResolveJobPrivate;
class MESSAGECOMPOSER_EXPORT EmailAddressResolveJob : public KJob
{
Q_OBJECT
public:
/**
* Creates a new email address resolve job.
*
* @param parent The parent object.
*/
explicit EmailAddressResolveJob(QObject *parent = nullptr);
/**
* Destroys the email address resolve job.
*/
~EmailAddressResolveJob() override;
/**
* Starts the job.
*/
void start() override;
/**
* Sets the from address to expand.
*/
- virtual void setFrom(const QString &from);
+ void setFrom(const QString &from);
/**
* Sets the from address to expand.
*/
- virtual void setTo(const QStringList &from);
+ void setTo(const QStringList &from);
/**
* Sets the from address to expand.
*/
- virtual void setCc(const QStringList &from);
+ void setCc(const QStringList &from);
/**
* Sets the from address to expand.
*/
- virtual void setBcc(const QStringList &from);
+ void setBcc(const QStringList &from);
+ /**
+ * Sets the Reply-To address to expand.
+ */
+ void setReplyTo(const QStringList &replyTo);
/**
* Returns the expanded From field
*/
- Q_REQUIRED_RESULT virtual QString expandedFrom() const;
+ Q_REQUIRED_RESULT QString expandedFrom() const;
/**
* Returns the expanded To field
*/
- Q_REQUIRED_RESULT virtual QStringList expandedTo() const;
+ Q_REQUIRED_RESULT QStringList expandedTo() const;
/**
* Returns the expanded CC field
*/
- Q_REQUIRED_RESULT virtual QStringList expandedCc() const;
+ Q_REQUIRED_RESULT QStringList expandedCc() const;
/**
* Returns the expanded Bcc field
*/
- Q_REQUIRED_RESULT virtual QStringList expandedBcc() const;
+ Q_REQUIRED_RESULT QStringList expandedBcc() const;
void setDefaultDomainName(const QString &domainName);
+ /**
+ * Returns the expanded Reply-To field
+ */
+ Q_REQUIRED_RESULT QStringList expandedReplyTo() const;
private Q_SLOTS:
void slotAliasExpansionDone(KJob *);
private:
EmailAddressResolveJobPrivate *const d;
};
}
#endif
diff --git a/messagecomposer/src/job/savecontactpreferencejob.cpp b/messagecomposer/src/job/savecontactpreferencejob.cpp
index 319a0fc3..626c7da2 100644
--- a/messagecomposer/src/job/savecontactpreferencejob.cpp
+++ b/messagecomposer/src/job/savecontactpreferencejob.cpp
@@ -1,122 +1,122 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "savecontactpreferencejob.h"
#include <QInputDialog>
#include <KLocalizedString>
#include <Akonadi/Contact/ContactSearchJob>
#include <AkonadiWidgets/CollectionDialog>
#include <AkonadiCore/ItemCreateJob>
#include <AkonadiCore/ItemModifyJob>
#include <QPointer>
#include "messagecomposer_debug.h"
using namespace MessageComposer;
SaveContactPreferenceJob::SaveContactPreferenceJob(const QString &email, const Kleo::KeyResolver::ContactPreferences &pref, QObject *parent)
: QObject(parent)
, mEmail(email)
, mPref(pref)
{
}
SaveContactPreferenceJob::~SaveContactPreferenceJob()
{
}
void SaveContactPreferenceJob::start()
{
Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(this);
connect(job, &Akonadi::ContactSearchJob::result, this, &SaveContactPreferenceJob::slotSearchContact);
job->setLimit(1);
job->setQuery(Akonadi::ContactSearchJob::Email, mEmail);
job->start();
}
void SaveContactPreferenceJob::slotSearchContact(KJob *job)
{
Akonadi::ContactSearchJob *contactSearchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
const Akonadi::Item::List items = contactSearchJob->items();
if (items.isEmpty()) {
bool ok = true;
const QString fullName
= QInputDialog::getText(nullptr, i18n("Name Selection"), i18n("Which name shall the contact '%1' have in your address book?", mEmail), QLineEdit::Normal, QString(), &ok);
if (!ok) {
deleteLater();
return;
}
QPointer<Akonadi::CollectionDialog> dlg
= new Akonadi::CollectionDialog(Akonadi::CollectionDialog::KeepTreeExpanded);
dlg->setMimeTypeFilter(QStringList() << KContacts::Addressee::mimeType());
dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
dlg->setDescription(i18n("Select the address book folder to store the new contact in:"));
if (!dlg->exec()) {
delete dlg;
deleteLater();
return;
}
const Akonadi::Collection targetCollection = dlg->selectedCollection();
delete dlg;
KContacts::Addressee contact;
contact.setNameFromString(fullName);
contact.insertEmail(mEmail, true);
writeCustomContactProperties(contact, mPref);
Akonadi::Item item(KContacts::Addressee::mimeType());
item.setPayload<KContacts::Addressee>(contact);
Akonadi::ItemCreateJob *job = new Akonadi::ItemCreateJob(item, targetCollection);
connect(job, &Akonadi::ContactSearchJob::result, this, &SaveContactPreferenceJob::slotModifyCreateItem);
} else {
Akonadi::Item item = items.first();
KContacts::Addressee contact = item.payload<KContacts::Addressee>();
writeCustomContactProperties(contact, mPref);
item.setPayload<KContacts::Addressee>(contact);
Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(item);
connect(job, &Akonadi::ContactSearchJob::result, this, &SaveContactPreferenceJob::slotModifyCreateItem);
}
}
void SaveContactPreferenceJob::writeCustomContactProperties(KContacts::Addressee &contact, const Kleo::KeyResolver::ContactPreferences &pref) const
{
contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOENCRYPTPREF"), QLatin1String(Kleo::encryptionPreferenceToString(pref.encryptionPreference)));
contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOSIGNPREF"), QLatin1String(Kleo::signingPreferenceToString(pref.signingPreference)));
contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("CRYPTOPROTOPREF"), QLatin1String(cryptoMessageFormatToString(pref.cryptoMessageFormat)));
contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("OPENPGPFP"), pref.pgpKeyFingerprints.join(QLatin1Char(',')));
contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("SMIMEFP"), pref.smimeCertFingerprints.join(QLatin1Char(',')));
}
void SaveContactPreferenceJob::slotModifyCreateItem(KJob *job)
{
if (job->error()) {
qCDebug(MESSAGECOMPOSER_LOG) << "modify item failed " << job->errorString();
}
deleteLater();
}
diff --git a/messagecomposer/src/job/savecontactpreferencejob.h b/messagecomposer/src/job/savecontactpreferencejob.h
index 46496ab4..a5405232 100644
--- a/messagecomposer/src/job/savecontactpreferencejob.h
+++ b/messagecomposer/src/job/savecontactpreferencejob.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2014-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef SAVECONTACTPREFERENCEJOB_H
#define SAVECONTACTPREFERENCEJOB_H
#include <KJob>
#include <KContacts/Addressee>
#include "composer/keyresolver.h"
namespace MessageComposer {
class SaveContactPreferenceJob : public QObject
{
Q_OBJECT
public:
explicit SaveContactPreferenceJob(const QString &email, const Kleo::KeyResolver::ContactPreferences &pref, QObject *parent = nullptr);
~SaveContactPreferenceJob();
void start();
private Q_SLOTS:
void slotSearchContact(KJob *job);
void slotModifyCreateItem(KJob *job);
private:
void writeCustomContactProperties(KContacts::Addressee &contact, const Kleo::KeyResolver::ContactPreferences &pref) const;
QString mEmail;
Kleo::KeyResolver::ContactPreferences mPref;
};
}
#endif // SAVECONTACTPREFERENCEJOB_H
diff --git a/messagecomposer/src/job/signencryptjob.cpp b/messagecomposer/src/job/signencryptjob.cpp
index e713e437..db03c18e 100644
--- a/messagecomposer/src/job/signencryptjob.cpp
+++ b/messagecomposer/src/job/signencryptjob.cpp
@@ -1,215 +1,215 @@
/*
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#include "job/signencryptjob.h"
#include "contentjobbase_p.h"
#include "utils/util.h"
#include "utils/util_p.h"
#include <Libkleo/Enum>
#include <QGpgME/Protocol>
#include <QGpgME/SignEncryptJob>
#include "messagecomposer_debug.h"
#include <kmime/kmime_message.h>
#include <kmime/kmime_content.h>
#include <kmime/kmime_headers.h>
#include <gpgme++/global.h>
#include <gpgme++/signingresult.h>
#include <gpgme++/encryptionresult.h>
#include <sstream>
using namespace MessageComposer;
class MessageComposer::SignEncryptJobPrivate : public ContentJobBasePrivate
{
public:
SignEncryptJobPrivate(SignEncryptJob *qq)
: ContentJobBasePrivate(qq)
{
}
std::vector<GpgME::Key> signers;
std::vector<GpgME::Key> encKeys;
QStringList recipients;
Kleo::CryptoMessageFormat format;
KMime::Content *content = nullptr;
// copied from messagecomposer.cpp
bool binaryHint(Kleo::CryptoMessageFormat f)
{
switch (f) {
case Kleo::SMIMEFormat:
case Kleo::SMIMEOpaqueFormat:
return true;
default:
case Kleo::OpenPGPMIMEFormat:
case Kleo::InlineOpenPGPFormat:
return false;
}
}
Q_DECLARE_PUBLIC(SignEncryptJob)
};
SignEncryptJob::SignEncryptJob(QObject *parent)
: ContentJobBase(*new SignEncryptJobPrivate(this), parent)
{
}
SignEncryptJob::~SignEncryptJob()
{
}
void SignEncryptJob::setContent(KMime::Content *content)
{
Q_D(SignEncryptJob);
Q_ASSERT(content);
d->content = content;
}
void SignEncryptJob::setCryptoMessageFormat(Kleo::CryptoMessageFormat format)
{
Q_D(SignEncryptJob);
// There *must* be a concrete format set at this point.
Q_ASSERT(format == Kleo::OpenPGPMIMEFormat
|| format == Kleo::InlineOpenPGPFormat
|| format == Kleo::SMIMEFormat
|| format == Kleo::SMIMEOpaqueFormat);
d->format = format;
}
void SignEncryptJob::setSigningKeys(std::vector<GpgME::Key> &signers)
{
Q_D(SignEncryptJob);
d->signers = signers;
}
KMime::Content *SignEncryptJob::origContent()
{
Q_D(SignEncryptJob);
return d->content;
}
void SignEncryptJob::setEncryptionKeys(const std::vector<GpgME::Key> &keys)
{
Q_D(SignEncryptJob);
d->encKeys = keys;
}
void SignEncryptJob::setRecipients(const QStringList &recipients)
{
Q_D(SignEncryptJob);
d->recipients = recipients;
}
QStringList SignEncryptJob::recipients() const
{
Q_D(const SignEncryptJob);
return d->recipients;
}
std::vector<GpgME::Key> SignEncryptJob::encryptionKeys() const
{
Q_D(const SignEncryptJob);
return d->encKeys;
}
void SignEncryptJob::process()
{
Q_D(SignEncryptJob);
Q_ASSERT(d->resultContent == nullptr); // Not processed before.
// if setContent hasn't been called, we assume that a subjob was added
// and we want to use that
if (!d->content || !d->content->hasContent()) {
Q_ASSERT(d->subjobContents.size() == 1);
d->content = d->subjobContents.first();
}
const QGpgME::Protocol *proto = nullptr;
if (d->format & Kleo::AnyOpenPGP) {
proto = QGpgME::openpgp();
} else if (d->format & Kleo::AnySMIME) {
proto = QGpgME::smime();
} else {
return;
}
Q_ASSERT(proto);
//d->resultContent = new KMime::Content;
qCDebug(MESSAGECOMPOSER_LOG) << "creating signencrypt from:" << proto->name() << proto->displayName();
std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(!d->binaryHint(d->format), d->format == Kleo::InlineOpenPGPFormat));
QByteArray encBody;
d->content->assemble();
// replace simple LFs by CRLFs for all MIME supporting CryptPlugs
// according to RfC 2633, 3.1.1 Canonicalization
QByteArray content;
if (d->format & Kleo::InlineOpenPGPFormat) {
content = d->content->body();
} else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
content = KMime::LFtoCRLF(d->content->encodedContent());
} else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
content = d->content->encodedContent();
}
// FIXME: Make this async
const std::pair<GpgME::SigningResult, GpgME::EncryptionResult> res = job->exec(d->signers, d->encKeys,
content,
false,
encBody);
// exec'ed jobs don't delete themselves
job->deleteLater();
if (res.first.error()) {
qCDebug(MESSAGECOMPOSER_LOG) << "signing failed:" << res.first.error().asString();
setError(res.first.error().code());
setErrorText(QString::fromLocal8Bit(res.first.error().asString()));
emitResult();
return;
}
if (res.second.error()) {
- qCDebug(MESSAGECOMPOSER_LOG) << "encrypyting failed:" << res.second.error().asString();
+ qCDebug(MESSAGECOMPOSER_LOG) << "encrypting failed:" << res.second.error().asString();
setError(res.second.error().code());
setErrorText(QString::fromLocal8Bit(res.second.error().asString()));
emitResult();
return;
}
QByteArray signatureHashAlgo = res.first.createdSignature(0).hashAlgorithmAsString();
d->resultContent = MessageComposer::Util::composeHeadersAndBody(d->content, encBody, d->format, true, signatureHashAlgo);
emitResult();
}
diff --git a/messagecomposer/src/job/signjob.h b/messagecomposer/src/job/signjob.h
index 9a2433ae..3c0e937b 100644
--- a/messagecomposer/src/job/signjob.h
+++ b/messagecomposer/src/job/signjob.h
@@ -1,65 +1,65 @@
/*
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@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.
*/
#ifndef MESSAGECOMPOSER_SIGNJOB_H
#define MESSAGECOMPOSER_SIGNJOB_H
#include "contentjobbase.h"
-#include "part/infopart.h"
#include "messagecomposer_export.h"
+#include <MessageComposer/InfoPart>
#include <Libkleo/Enum>
#include <gpgme++/key.h>
#include <vector>
namespace KMime {
class Content;
}
namespace MessageComposer {
class SignJobPrivate;
/**
Signs the contents of a message.
Used as a subjob of CryptoMessage
*/
class MESSAGECOMPOSER_EXPORT SignJob : public ContentJobBase
{
Q_OBJECT
public:
explicit SignJob(QObject *parent = nullptr);
~SignJob() override;
void setContent(KMime::Content *content);
void setCryptoMessageFormat(Kleo::CryptoMessageFormat format);
void setSigningKeys(std::vector<GpgME::Key> &signers);
Q_REQUIRED_RESULT KMime::Content *origContent();
protected Q_SLOTS:
void process() override;
private:
Q_DECLARE_PRIVATE(SignJob)
};
}
#endif
diff --git a/messagecomposer/src/job/skeletonmessagejob.cpp b/messagecomposer/src/job/skeletonmessagejob.cpp
index 3b2a28ad..c452e78a 100644
--- a/messagecomposer/src/job/skeletonmessagejob.cpp
+++ b/messagecomposer/src/job/skeletonmessagejob.cpp
@@ -1,280 +1,290 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "settings/messagecomposersettings.h"
#include "job/skeletonmessagejob.h"
#include "messagecomposer-version.h"
#include "part/infopart.h"
#include "part/globalpart.h"
#include "job/jobbase_p.h"
#include <QHostInfo>
#include <QUrl>
#include <KCharsets>
#include "messagecomposer_debug.h"
#include <kmime/kmime_message.h>
#include <KEmailAddress>
using namespace MessageComposer;
class MessageComposer::SkeletonMessageJobPrivate : public JobBasePrivate
{
public:
SkeletonMessageJobPrivate(SkeletonMessageJob *qq)
: JobBasePrivate(qq)
{
}
void doStart(); // slot
InfoPart *infoPart = nullptr;
GlobalPart *globalPart = nullptr;
KMime::Message *message = nullptr;
Q_DECLARE_PUBLIC(SkeletonMessageJob)
};
void SkeletonMessageJobPrivate::doStart()
{
Q_Q(SkeletonMessageJob);
Q_ASSERT(infoPart);
Q_ASSERT(message == nullptr);
message = new KMime::Message;
// From:
{
KMime::Headers::From *from = new KMime::Headers::From;
KMime::Types::Mailbox address;
address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from()));
from->fromUnicodeString(QString::fromLatin1(address.as7BitString("utf-8")), "utf-8");
message->setHeader(from);
}
// To:
{
KMime::Headers::To *to = new KMime::Headers::To;
QByteArray sTo;
const QStringList lstTo = infoPart->to();
for (const QString &a : lstTo) {
KMime::Types::Mailbox address;
address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(a));
if (!sTo.isEmpty()) {
sTo.append(",");
}
sTo.append(address.as7BitString("utf-8"));
}
to->fromUnicodeString(QString::fromLatin1(sTo), "utf-8");
message->setHeader(to);
}
// Reply To:
if (!infoPart->replyTo().isEmpty()) {
KMime::Headers::ReplyTo *replyTo = new KMime::Headers::ReplyTo;
- KMime::Types::Mailbox address;
- address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->replyTo()));
- replyTo->fromUnicodeString(QString::fromLatin1(address.as7BitString("utf-8")), "utf-8");
+ const QStringList lstReplyTo = infoPart->replyTo();
+ QByteArray sReplyTo;
+ for (const QString &a : lstReplyTo) {
+ KMime::Types::Mailbox address;
+ address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(a));
+ if (!sReplyTo.isEmpty()) {
+ sReplyTo.append(",");
+ }
+ sReplyTo.append(address.as7BitString("utf-8"));
+ }
+ replyTo->fromUnicodeString(QString::fromLatin1(sReplyTo), "utf-8");
message->setHeader(replyTo);
}
// Cc:
{
KMime::Headers::Cc *cc = new KMime::Headers::Cc;
QByteArray sCc;
const QStringList lstCc = infoPart->cc();
for (const QString &a : lstCc) {
KMime::Types::Mailbox address;
address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(a));
if (!sCc.isEmpty()) {
sCc.append(",");
}
sCc.append(address.as7BitString("utf-8"));
}
cc->fromUnicodeString(QString::fromLatin1(sCc), "utf-8");
message->setHeader(cc);
}
// Bcc:
{
KMime::Headers::Bcc *bcc = new KMime::Headers::Bcc;
QByteArray sBcc;
const QStringList lstBcc = infoPart->bcc();
for (const QString &a : lstBcc) {
KMime::Types::Mailbox address;
address.fromUnicodeString(KEmailAddress::normalizeAddressesAndEncodeIdn(a));
if (!sBcc.isEmpty()) {
sBcc.append(",");
}
sBcc.append(address.as7BitString("utf-8"));
}
bcc->fromUnicodeString(QString::fromLatin1(sBcc), "utf-8");
message->setHeader(bcc);
}
// Subject:
{
KMime::Headers::Subject *subject = new KMime::Headers::Subject;
subject->fromUnicodeString(infoPart->subject(), "utf-8");
// TODO should we be more specific about the charset?
message->setHeader(subject);
}
// Date:
{
KMime::Headers::Date *date = new KMime::Headers::Date;
date->setDateTime(QDateTime::currentDateTime());
message->setHeader(date);
}
// Fcc:
if (!infoPart->fcc().isEmpty()) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Fcc");
header->fromUnicodeString(infoPart->fcc(), "utf-8");
message->setHeader(header);
}
//Transport:
if (infoPart->transportId() > -1) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport");
header->fromUnicodeString(QString::number(infoPart->transportId()), "utf-8");
message->setHeader(header);
}
// Message-ID
{
KMime::Headers::MessageID *messageId = new KMime::Headers::MessageID();
QByteArray fqdn;
if (MessageComposer::MessageComposerSettings::self()->useCustomMessageIdSuffix()) {
fqdn = QUrl::toAce(MessageComposer::MessageComposerSettings::self()->customMsgIDSuffix());
}
if (fqdn.isEmpty()) {
fqdn = QUrl::toAce(QHostInfo::localHostName());
}
if (fqdn.isEmpty()) {
qCWarning(MESSAGECOMPOSER_LOG) << "Unable to generate a Message-ID, falling back to 'localhost.localdomain'.";
fqdn = "local.domain";
}
messageId->generate(fqdn);
message->setHeader(messageId);
}
// Extras
foreach (KMime::Headers::Base *extra, infoPart->extraHeaders()) {
message->setHeader(extra);
}
- // Request Delevery Confirmation
+ // Request Delivery Confirmation
{
if (globalPart->requestDeleveryConfirmation()) {
- const QString addr = infoPart->replyTo().isEmpty() ? infoPart->from() : infoPart->replyTo();
+ //TODO fix me multi address
+ const QString addr = infoPart->replyTo().isEmpty() ? infoPart->from() : infoPart->replyTo().at(0);
KMime::Headers::Generic *requestDeleveryConfirmation = new KMime::Headers::Generic("Return-Receipt-To");
requestDeleveryConfirmation->fromUnicodeString(addr, "utf-8");
message->setHeader(requestDeleveryConfirmation);
}
}
// MDN
{
if (globalPart->MDNRequested()) {
- const QString addr = infoPart->replyTo().isEmpty() ? infoPart->from() : infoPart->replyTo();
+ //TODO fix me multi address
+ const QString addr = infoPart->replyTo().isEmpty() ? infoPart->from() : infoPart->replyTo().at(0);
KMime::Headers::Generic *mdn = new KMime::Headers::Generic("Disposition-Notification-To");
mdn->fromUnicodeString(addr, "utf-8");
message->setHeader(mdn);
}
}
// Urgent header
if (infoPart->urgent()) {
KMime::Headers::Generic *urg1 = new KMime::Headers::Generic("X-PRIORITY");
urg1->fromUnicodeString(QStringLiteral("2 (High)"), "utf-8");
KMime::Headers::Generic *urg2 = new KMime::Headers::Generic("Priority");
urg2->fromUnicodeString(QStringLiteral("urgent"), "utf-8");
message->setHeader(urg1);
message->setHeader(urg2);
}
// In-Reply-To
if (!infoPart->inReplyTo().isEmpty()) {
KMime::Headers::InReplyTo *header = new KMime::Headers::InReplyTo;
header->fromUnicodeString(infoPart->inReplyTo(), "utf-8");
message->setHeader(header);
}
// References
if (!infoPart->references().isEmpty()) {
KMime::Headers::References *header = new KMime::Headers::References;
header->fromUnicodeString(infoPart->references(), "utf-8");
message->setHeader(header);
}
q->emitResult(); // Success.
}
SkeletonMessageJob::SkeletonMessageJob(InfoPart *infoPart, GlobalPart *globalPart, QObject *parent)
: JobBase(*new SkeletonMessageJobPrivate(this), parent)
{
Q_D(SkeletonMessageJob);
d->infoPart = infoPart;
d->globalPart = globalPart;
}
SkeletonMessageJob::~SkeletonMessageJob()
{
}
InfoPart *SkeletonMessageJob::infoPart() const
{
Q_D(const SkeletonMessageJob);
return d->infoPart;
}
void SkeletonMessageJob::setInfoPart(InfoPart *part)
{
Q_D(SkeletonMessageJob);
d->infoPart = part;
}
GlobalPart *SkeletonMessageJob::globalPart() const
{
Q_D(const SkeletonMessageJob);
return d->globalPart;
}
void SkeletonMessageJob::setGlobalPart(GlobalPart *part)
{
Q_D(SkeletonMessageJob);
d->globalPart = part;
}
KMime::Message *SkeletonMessageJob::message() const
{
Q_D(const SkeletonMessageJob);
return d->message;
}
void SkeletonMessageJob::start()
{
Q_D(SkeletonMessageJob);
d->doStart();
}
#include "moc_skeletonmessagejob.cpp"
diff --git a/messagecomposer/src/part/infopart.cpp b/messagecomposer/src/part/infopart.cpp
index b016d11b..ea4f5d1c 100644
--- a/messagecomposer/src/part/infopart.cpp
+++ b/messagecomposer/src/part/infopart.cpp
@@ -1,185 +1,185 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "infopart.h"
using namespace MessageComposer;
class Q_DECL_HIDDEN InfoPart::Private
{
public:
Private()
{
}
KMime::Headers::Base::List extraHeaders;
QStringList to;
QStringList cc;
QStringList bcc;
+ QStringList replyTo;
QString subject;
QString from;
QString fcc;
- QString replyTo;
QString userAgent;
QString inReplyTo;
QString references;
int transportId = 0;
bool urgent = false;
};
InfoPart::InfoPart(QObject *parent)
: MessagePart(parent)
, d(new Private)
{
}
InfoPart::~InfoPart()
{
delete d;
}
QString InfoPart::from() const
{
return d->from;
}
void InfoPart::setFrom(const QString &from)
{
d->from = from;
}
QStringList InfoPart::to() const
{
return d->to;
}
void InfoPart::setTo(const QStringList &to)
{
d->to = to;
}
QStringList InfoPart::cc() const
{
return d->cc;
}
void InfoPart::setCc(const QStringList &cc)
{
d->cc = cc;
}
QStringList InfoPart::bcc() const
{
return d->bcc;
}
void InfoPart::setBcc(const QStringList &bcc)
{
d->bcc = bcc;
}
QString InfoPart::subject() const
{
return d->subject;
}
void InfoPart::setSubject(const QString &subject)
{
d->subject = subject;
}
-QString InfoPart::replyTo() const
+QStringList InfoPart::replyTo() const
{
return d->replyTo;
}
-void InfoPart::setReplyTo(const QString &replyTo)
+void InfoPart::setReplyTo(const QStringList &replyTo)
{
d->replyTo = replyTo;
}
int InfoPart::transportId() const
{
return d->transportId;
}
void InfoPart::setTransportId(int tid)
{
d->transportId = tid;
}
void InfoPart::setFcc(const QString &fcc)
{
d->fcc = fcc;
}
QString InfoPart::fcc() const
{
return d->fcc;
}
bool InfoPart::urgent() const
{
return d->urgent;
}
void InfoPart::setUrgent(bool urgent)
{
d->urgent = urgent;
}
QString InfoPart::inReplyTo() const
{
return d->inReplyTo;
}
void InfoPart::setInReplyTo(const QString &inReplyTo)
{
d->inReplyTo = inReplyTo;
}
QString InfoPart::references() const
{
return d->references;
}
void InfoPart::setReferences(const QString &references)
{
d->references = references;
}
void InfoPart::setExtraHeaders(const KMime::Headers::Base::List &headers)
{
d->extraHeaders = headers;
}
KMime::Headers::Base::List InfoPart::extraHeaders() const
{
return d->extraHeaders;
}
QString InfoPart::userAgent() const
{
return d->userAgent;
}
void InfoPart::setUserAgent(const QString &userAgent)
{
d->userAgent = userAgent;
}
diff --git a/messagecomposer/src/part/infopart.h b/messagecomposer/src/part/infopart.h
index 0168edd8..49be59ce 100644
--- a/messagecomposer/src/part/infopart.h
+++ b/messagecomposer/src/part/infopart.h
@@ -1,80 +1,80 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
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.
*/
#ifndef MESSAGECOMPOSER_INFOPART_H
#define MESSAGECOMPOSER_INFOPART_H
#include "messagepart.h"
#include <QStringList>
#include <kmime/kmime_message.h>
#include <kmime/kmime_headers.h>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT InfoPart : public MessageComposer::MessagePart
{
Q_OBJECT
public:
explicit InfoPart(QObject *parent = nullptr);
~InfoPart() override;
Q_REQUIRED_RESULT QString from() const;
void setFrom(const QString &from);
Q_REQUIRED_RESULT QStringList to() const;
void setTo(const QStringList &to);
Q_REQUIRED_RESULT QStringList cc() const;
void setCc(const QStringList &cc);
Q_REQUIRED_RESULT QStringList bcc() const;
void setBcc(const QStringList &bcc);
- Q_REQUIRED_RESULT QString replyTo() const;
- void setReplyTo(const QString &replyTo);
+ Q_REQUIRED_RESULT QStringList replyTo() const;
+ void setReplyTo(const QStringList &replyTo);
Q_REQUIRED_RESULT QString subject() const;
void setSubject(const QString &subject);
Q_REQUIRED_RESULT QString fcc() const;
void setFcc(const QString &fcc);
Q_REQUIRED_RESULT QString userAgent() const;
void setUserAgent(const QString &userAgent);
Q_REQUIRED_RESULT bool urgent() const;
void setUrgent(bool);
Q_REQUIRED_RESULT QString inReplyTo() const;
void setInReplyTo(const QString &inReplyTo);
Q_REQUIRED_RESULT QString references() const;
void setReferences(const QString &references);
void setExtraHeaders(const KMime::Headers::Base::List &headers);
Q_REQUIRED_RESULT KMime::Headers::Base::List extraHeaders() const;
Q_REQUIRED_RESULT int transportId() const;
void setTransportId(int tid);
private:
class Private;
Private *const d;
};
} // namespace MessageComposer
#endif // MESSAGECOMPOSER_INFOPART_H
diff --git a/messagecomposer/src/plugineditor/pluginactiontype.cpp b/messagecomposer/src/plugineditor/pluginactiontype.cpp
index 65b35a3c..8926c8e3 100644
--- a/messagecomposer/src/plugineditor/pluginactiontype.cpp
+++ b/messagecomposer/src/plugineditor/pluginactiontype.cpp
@@ -1,66 +1,65 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "pluginactiontype.h"
using namespace MessageComposer;
PluginActionType::PluginActionType()
{
}
PluginActionType::PluginActionType(QAction *action, PluginActionType::Type type)
: mAction(action)
, mType(type)
{
}
QAction *PluginActionType::action() const
{
return mAction;
}
PluginActionType::Type PluginActionType::type() const
{
return mType;
}
-
QString PluginActionType::actionXmlExtension(PluginActionType::Type type)
{
switch (type) {
case MessageComposer::PluginActionType::Tools:
return QStringLiteral("_plugins_tools");
case MessageComposer::PluginActionType::Edit:
return QStringLiteral("_plugins_edit");
case MessageComposer::PluginActionType::File:
return QStringLiteral("_plugins_file");
case MessageComposer::PluginActionType::Action:
return QStringLiteral("_plugins_actions");
case MessageComposer::PluginActionType::PopupMenu:
return QStringLiteral("_popupmenu_actions");
case MessageComposer::PluginActionType::ToolBar:
return QStringLiteral("_toolbar_actions");
case MessageComposer::PluginActionType::Options:
return QStringLiteral("_plugins_options");
case MessageComposer::PluginActionType::None:
return QString();
}
return {};
}
diff --git a/messagecomposer/src/plugineditor/pluginactiontype.h b/messagecomposer/src/plugineditor/pluginactiontype.h
index fffdfd5a..00a4dd47 100644
--- a/messagecomposer/src/plugineditor/pluginactiontype.h
+++ b/messagecomposer/src/plugineditor/pluginactiontype.h
@@ -1,54 +1,55 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINACTIONTYPE_H
#define PLUGINACTIONTYPE_H
#include "messagecomposer_export.h"
#include <QString>
class QAction;
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginActionType
{
public:
enum Type {
Tools = 0,
Edit = 1,
File = 2,
Action = 3,
PopupMenu = 4,
ToolBar = 5,
Options = 6,
None = 7
};
PluginActionType();
PluginActionType(QAction *action, Type type);
Q_REQUIRED_RESULT QAction *action() const;
Q_REQUIRED_RESULT Type type() const;
static QString actionXmlExtension(PluginActionType::Type type);
private:
QAction *mAction = nullptr;
Type mType = Tools;
};
}
+Q_DECLARE_TYPEINFO(MessageComposer::PluginActionType, Q_MOVABLE_TYPE);
-#endif // PLUGINEDITORINTERFACE_H
+#endif
diff --git a/messagecomposer/src/plugineditor/plugineditor.cpp b/messagecomposer/src/plugineditor/plugineditor.cpp
index 1014e8ad..b11b6773 100644
--- a/messagecomposer/src/plugineditor/plugineditor.cpp
+++ b/messagecomposer/src/plugineditor/plugineditor.cpp
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "plugineditor.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorPrivate
{
public:
PluginEditorPrivate()
{
}
int order = 0;
};
PluginEditor::PluginEditor(QObject *parent)
: PimCommon::AbstractGenericPlugin(parent)
, d(new PluginEditorPrivate)
{
}
PluginEditor::~PluginEditor()
{
delete d;
}
void PluginEditor::setOrder(int order)
{
d->order = order;
}
int PluginEditor::order() const
{
return d->order;
}
diff --git a/messagecomposer/src/plugineditor/plugineditor.h b/messagecomposer/src/plugineditor/plugineditor.h
index cb35b401..dfd37a82 100644
--- a/messagecomposer/src/plugineditor/plugineditor.h
+++ b/messagecomposer/src/plugineditor/plugineditor.h
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITOR_H
#define PLUGINEDITOR_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/AbstractGenericPlugin>
namespace MessageComposer {
class PluginEditorPrivate;
class MESSAGECOMPOSER_EXPORT PluginEditor : public PimCommon::AbstractGenericPlugin
{
Q_OBJECT
public:
explicit PluginEditor(QObject *parent = nullptr);
~PluginEditor();
void setOrder(int order);
Q_REQUIRED_RESULT int order() const;
private:
PluginEditorPrivate *const d;
};
}
#endif // PLUGINEDITOR_H
diff --git a/messagecomposer/src/plugineditor/plugineditorinterface.cpp b/messagecomposer/src/plugineditor/plugineditorinterface.cpp
index f3e0f489..59ace361 100644
--- a/messagecomposer/src/plugineditor/plugineditorinterface.cpp
+++ b/messagecomposer/src/plugineditor/plugineditorinterface.cpp
@@ -1,88 +1,88 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "plugineditorinterface.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorInterfacePrivate
{
public:
PluginEditorInterfacePrivate()
{
}
PluginActionType mActionType;
QWidget *mParentWidget = nullptr;
KPIMTextEdit::RichTextEditor *mRichTextEditor = nullptr;
PluginEditor *plugin = nullptr;
QWidget *statusBarWidget = nullptr;
bool mSelectedText = false;
};
PluginEditorInterface::PluginEditorInterface(QObject *parent)
: PimCommon::AbstractGenericPluginInterface(parent)
, d(new MessageComposer::PluginEditorInterfacePrivate)
{
}
PluginEditorInterface::~PluginEditorInterface()
{
delete d;
}
void PluginEditorInterface::setActionType(PluginActionType type)
{
d->mActionType = type;
}
PluginActionType PluginEditorInterface::actionType() const
{
return d->mActionType;
}
KPIMTextEdit::RichTextEditor *PluginEditorInterface::richTextEditor() const
{
return d->mRichTextEditor;
}
void PluginEditorInterface::setRichTextEditor(KPIMTextEdit::RichTextEditor *richTextEditor)
{
d->mRichTextEditor = richTextEditor;
}
void PluginEditorInterface::setNeedSelectedText(bool b)
{
d->mSelectedText = b;
}
bool PluginEditorInterface::needSelectedText() const
{
return d->mSelectedText;
}
void PluginEditorInterface::setStatusBarWidget(QWidget *w)
{
d->statusBarWidget = w;
}
QWidget *PluginEditorInterface::statusBarWidget() const
{
return d->statusBarWidget;
}
diff --git a/messagecomposer/src/plugineditor/plugineditorinterface.h b/messagecomposer/src/plugineditor/plugineditorinterface.h
index 95636d73..1a5db86c 100644
--- a/messagecomposer/src/plugineditor/plugineditorinterface.h
+++ b/messagecomposer/src/plugineditor/plugineditorinterface.h
@@ -1,63 +1,64 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORINTERFACE_H
#define PLUGINEDITORINTERFACE_H
#include <QObject>
#include <PimCommon/AbstractGenericPluginInterface>
#include <MessageComposer/PluginActionType>
#include "messagecomposer_export.h"
class QAction;
namespace KPIMTextEdit {
class RichTextEditor;
}
namespace MessageComposer {
class PluginEditorInterfacePrivate;
class PluginEditor;
class MESSAGECOMPOSER_EXPORT PluginEditorInterface : public PimCommon::AbstractGenericPluginInterface
{
Q_OBJECT
public:
explicit PluginEditorInterface(QObject *parent = nullptr);
~PluginEditorInterface();
void setActionType(PluginActionType type);
Q_REQUIRED_RESULT PluginActionType actionType() const;
Q_REQUIRED_RESULT KPIMTextEdit::RichTextEditor *richTextEditor() const;
void setRichTextEditor(KPIMTextEdit::RichTextEditor *richTextEditor);
void setNeedSelectedText(bool b);
Q_REQUIRED_RESULT bool needSelectedText() const;
void setStatusBarWidget(QWidget *w);
Q_REQUIRED_RESULT QWidget *statusBarWidget() const;
Q_SIGNALS:
void emitPluginActivated(MessageComposer::PluginEditorInterface *interface);
+ void insertText(const QString &str);
private:
PluginEditorInterfacePrivate *const d;
};
}
#endif // PLUGINEDITORINTERFACE_H
diff --git a/messagecomposer/src/plugineditor/plugineditormanager.cpp b/messagecomposer/src/plugineditor/plugineditormanager.cpp
index 75a562fb..ecd38385 100644
--- a/messagecomposer/src/plugineditor/plugineditormanager.cpp
+++ b/messagecomposer/src/plugineditor/plugineditormanager.cpp
@@ -1,214 +1,214 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "plugineditor.h"
#include "messagecomposer_debug.h"
#include "plugineditormanager.h"
#include <PimCommon/PluginUtil>
#include <QFileInfo>
#include <QSet>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <KPluginFactory>
using namespace MessageComposer;
class PluginEditorInfo
{
public:
PluginEditorInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
PluginEditor *plugin = nullptr;
int order = 0;
bool isEnabled = true;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
class MessageComposer::PluginEditorManagerPrivate
{
public:
PluginEditorManagerPrivate(PluginEditorManager *qq)
: q(qq)
{
initializePlugins();
}
void loadPlugin(PluginEditorInfo *item);
QVector<PluginEditor *> pluginsList() const;
QVector<PimCommon::PluginUtilData> pluginDataList() const;
bool initializePlugins();
QString configGroupName() const;
QString configPrefixSettingKey() const;
PluginEditor *pluginFromIdentifier(const QString &id);
private:
QVector<PluginEditorInfo> mPluginList;
QVector<PimCommon::PluginUtilData> mPluginDataList;
PluginEditorManager *q;
};
bool PluginEditorManagerPrivate::initializePlugins()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kmail"), [](const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("KMailEditor/Plugin"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
PluginEditorInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
const QVariant p = data.rawData().value(QStringLiteral("X-KDE-KMailEditor-Order")).toVariant();
int order = -1;
if (p.isValid()) {
order = p.toInt();
}
info.order = order;
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<PluginEditorInfo>::iterator end(mPluginList.end());
for (QVector<PluginEditorInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
void PluginEditorManagerPrivate::loadPlugin(PluginEditorInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<PluginEditor>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
item->plugin->setOrder(item->order);
mPluginDataList.append(item->pluginData);
}
}
QVector<PluginEditor *> PluginEditorManagerPrivate::pluginsList() const
{
QVector<PluginEditor *> lst;
QVector<PluginEditorInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
QVector<PimCommon::PluginUtilData> PluginEditorManagerPrivate::pluginDataList() const
{
return mPluginDataList;
}
QString PluginEditorManagerPrivate::configGroupName() const
{
return QStringLiteral("KMailPluginEditor");
}
QString PluginEditorManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("KMailEditorPlugin");
}
PluginEditor *PluginEditorManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<PluginEditorInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
PluginEditorManager::PluginEditorManager(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorManagerPrivate(this))
{
}
PluginEditorManager::~PluginEditorManager()
{
delete d;
}
PluginEditorManager *PluginEditorManager::self()
{
static PluginEditorManager s_self;
return &s_self;
}
QVector<PluginEditor *> PluginEditorManager::pluginsList() const
{
return d->pluginsList();
}
QVector<PimCommon::PluginUtilData> PluginEditorManager::pluginsDataList() const
{
return d->pluginDataList();
}
QString PluginEditorManager::configGroupName() const
{
return d->configGroupName();
}
QString PluginEditorManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
PluginEditor *PluginEditorManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messagecomposer/src/plugineditor/plugineditormanager.h b/messagecomposer/src/plugineditor/plugineditormanager.h
index 153e102b..c910c076 100644
--- a/messagecomposer/src/plugineditor/plugineditormanager.h
+++ b/messagecomposer/src/plugineditor/plugineditormanager.h
@@ -1,50 +1,50 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORMANAGER_H
#define PLUGINEDITORMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageComposer {
class PluginEditorManagerPrivate;
class PluginEditor;
class MESSAGECOMPOSER_EXPORT PluginEditorManager : public QObject
{
Q_OBJECT
public:
explicit PluginEditorManager(QObject *parent = nullptr);
~PluginEditorManager();
static PluginEditorManager *self();
Q_REQUIRED_RESULT QVector<PluginEditor *> pluginsList() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT PluginEditor *pluginFromIdentifier(const QString &id);
private:
PluginEditorManagerPrivate *const d;
};
}
#endif // PLUGINEDITORMANAGER_H
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp
index 2a669d5c..44430507 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.cpp
@@ -1,73 +1,73 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesend.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorCheckBeforeSendPrivate
{
public:
PluginEditorCheckBeforeSendPrivate()
{
}
bool mIsEnabled = false;
};
PluginEditorCheckBeforeSend::PluginEditorCheckBeforeSend(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorCheckBeforeSendPrivate)
{
}
PluginEditorCheckBeforeSend::~PluginEditorCheckBeforeSend()
{
delete d;
}
bool PluginEditorCheckBeforeSend::hasConfigureDialog() const
{
return false;
}
void PluginEditorCheckBeforeSend::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
}
void PluginEditorCheckBeforeSend::emitConfigChanged()
{
Q_EMIT configChanged();
}
QString PluginEditorCheckBeforeSend::description() const
{
return {};
}
void PluginEditorCheckBeforeSend::setIsEnabled(bool enabled)
{
d->mIsEnabled = enabled;
}
bool PluginEditorCheckBeforeSend::isEnabled() const
{
return d->mIsEnabled;
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.h b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.h
index b174c0bb..4e2c46c3 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.h
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesend.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESEND_H
#define PLUGINEDITORCHECKBEFORESEND_H
#include <QObject>
#include "messagecomposer_export.h"
namespace MessageComposer {
class PluginEditorCheckBeforeSendPrivate;
class PluginEditorCheckBeforeSendInterface;
class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSend : public QObject
{
Q_OBJECT
public:
explicit PluginEditorCheckBeforeSend(QObject *parent = nullptr);
~PluginEditorCheckBeforeSend();
virtual PluginEditorCheckBeforeSendInterface *createInterface(QObject *parent) = 0;
Q_REQUIRED_RESULT virtual bool hasConfigureDialog() const;
virtual void showConfigureDialog(QWidget *parent = nullptr);
void emitConfigChanged();
Q_REQUIRED_RESULT virtual QString description() const;
void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
Q_SIGNALS:
void configChanged();
private:
PluginEditorCheckBeforeSendPrivate *const d;
};
}
#endif // PLUGINEDITORCHECKBEFORESEND_H
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp
index c85f08df..982fcdf3 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesendconfigurewidget.h"
using namespace MessageComposer;
PluginEditorCheckBeforeSendConfigureWidget::PluginEditorCheckBeforeSendConfigureWidget(QWidget *parent)
: QWidget(parent)
{
}
PluginEditorCheckBeforeSendConfigureWidget::~PluginEditorCheckBeforeSendConfigureWidget()
{
}
QString PluginEditorCheckBeforeSendConfigureWidget::helpAnchor() const
{
return QString();
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h
index bfaa8e31..83ad28d2 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
#define PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
#include "messagecomposer_export.h"
#include <QWidget>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSendConfigureWidget : public QWidget
{
Q_OBJECT
public:
explicit PluginEditorCheckBeforeSendConfigureWidget(QWidget *parent = nullptr);
~PluginEditorCheckBeforeSendConfigureWidget();
virtual void loadSettings() = 0;
virtual void saveSettings() = 0;
virtual void resetSettings() = 0;
virtual QString helpAnchor() const;
Q_SIGNALS:
void configureChanged();
};
}
#endif // PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp
index 513c4ebb..778814d9 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesendinterface.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorCheckBeforeSendInterfacePrivate
{
public:
PluginEditorCheckBeforeSendInterfacePrivate()
{
}
MessageComposer::PluginEditorCheckBeforeSendParams parameters;
QWidget *mParentWidget = nullptr;
};
PluginEditorCheckBeforeSendInterface::PluginEditorCheckBeforeSendInterface(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorCheckBeforeSendInterfacePrivate)
{
}
PluginEditorCheckBeforeSendInterface::~PluginEditorCheckBeforeSendInterface()
{
delete d;
}
void PluginEditorCheckBeforeSendInterface::setParentWidget(QWidget *parent)
{
d->mParentWidget = parent;
}
QWidget *PluginEditorCheckBeforeSendInterface::parentWidget() const
{
return d->mParentWidget;
}
void PluginEditorCheckBeforeSendInterface::setParameters(const MessageComposer::PluginEditorCheckBeforeSendParams &params)
{
d->parameters = params;
}
MessageComposer::PluginEditorCheckBeforeSendParams PluginEditorCheckBeforeSendInterface::parameters() const
{
return d->parameters;
}
void PluginEditorCheckBeforeSendInterface::reloadConfig()
{
//Reimplement it
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.h b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.h
index 176300fe..14b3989d 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.h
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendinterface.h
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESENDINTERFACE_H
#define PLUGINEDITORCHECKBEFORESENDINTERFACE_H
#include <QObject>
#include "messagecomposer_export.h"
#include "plugineditorcheckbeforesendparams.h"
namespace MessageComposer {
class PluginEditorCheckBeforeSendInterfacePrivate;
class PluginEditorCheckBeforeSendParams;
class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSendInterface : public QObject
{
Q_OBJECT
public:
explicit PluginEditorCheckBeforeSendInterface(QObject *parent = nullptr);
~PluginEditorCheckBeforeSendInterface();
virtual bool exec(const MessageComposer::PluginEditorCheckBeforeSendParams &params) = 0;
void setParentWidget(QWidget *parent);
Q_REQUIRED_RESULT QWidget *parentWidget() const;
void setParameters(const MessageComposer::PluginEditorCheckBeforeSendParams &params);
Q_REQUIRED_RESULT MessageComposer::PluginEditorCheckBeforeSendParams parameters() const;
public Q_SLOTS:
virtual void reloadConfig();
private:
PluginEditorCheckBeforeSendInterfacePrivate *const d;
};
}
#endif // PLUGINEDITORCHECKBEFORESENDINTERFACE_H
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp
index d9ede7c1..973ebb6e 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.cpp
@@ -1,204 +1,204 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesendmanager.h"
#include "plugineditorcheckbeforesend.h"
#include "messagecomposer_debug.h"
#include <QFileInfo>
#include <QSet>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <KPluginFactory>
using namespace MessageComposer;
class PluginEditorCheckBeforeSendInfo
{
public:
PluginEditorCheckBeforeSendInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
PluginEditorCheckBeforeSend *plugin = nullptr;
bool isEnabled = true;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
class MessageComposer::PluginEditorCheckBeforeSendManagerPrivate
{
public:
PluginEditorCheckBeforeSendManagerPrivate(PluginEditorCheckBeforeSendManager *qq)
: q(qq)
{
initializePlugins();
}
void loadPlugin(PluginEditorCheckBeforeSendInfo *item);
QVector<PluginEditorCheckBeforeSend *> pluginsList() const;
bool initializePlugins();
QVector<PluginEditorCheckBeforeSendInfo> mPluginList;
QString configPrefixSettingKey() const;
QString configGroupName() const;
QVector<PimCommon::PluginUtilData> pluginsDataList() const;
PluginEditorCheckBeforeSend *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
PluginEditorCheckBeforeSendManager *q;
};
QString PluginEditorCheckBeforeSendManagerPrivate::configGroupName() const
{
return QStringLiteral("KMailPluginCheckBefore");
}
QString PluginEditorCheckBeforeSendManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("PluginCheckBefore");
}
QVector<PimCommon::PluginUtilData> PluginEditorCheckBeforeSendManagerPrivate::pluginsDataList() const
{
return mPluginDataList;
}
bool PluginEditorCheckBeforeSendManagerPrivate::initializePlugins()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kmail"), [](const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("KMailEditor/PluginCheckBeforeSend"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
PluginEditorCheckBeforeSendInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<PluginEditorCheckBeforeSendInfo>::iterator end(mPluginList.end());
for (QVector<PluginEditorCheckBeforeSendInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
void PluginEditorCheckBeforeSendManagerPrivate::loadPlugin(PluginEditorCheckBeforeSendInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<PluginEditorCheckBeforeSend>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
QVector<PluginEditorCheckBeforeSend *> PluginEditorCheckBeforeSendManagerPrivate::pluginsList() const
{
QVector<PluginEditorCheckBeforeSend *> lst;
QVector<PluginEditorCheckBeforeSendInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorCheckBeforeSendInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
PluginEditorCheckBeforeSend *PluginEditorCheckBeforeSendManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<PluginEditorCheckBeforeSendInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorCheckBeforeSendInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
PluginEditorCheckBeforeSendManager::PluginEditorCheckBeforeSendManager(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorCheckBeforeSendManagerPrivate(this))
{
}
PluginEditorCheckBeforeSendManager::~PluginEditorCheckBeforeSendManager()
{
delete d;
}
PluginEditorCheckBeforeSendManager *PluginEditorCheckBeforeSendManager::self()
{
static PluginEditorCheckBeforeSendManager s_self;
return &s_self;
}
QVector<PluginEditorCheckBeforeSend *> PluginEditorCheckBeforeSendManager::pluginsList() const
{
return d->pluginsList();
}
QString PluginEditorCheckBeforeSendManager::configGroupName() const
{
return d->configGroupName();
}
QString PluginEditorCheckBeforeSendManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
QVector<PimCommon::PluginUtilData> PluginEditorCheckBeforeSendManager::pluginsDataList() const
{
return d->pluginsDataList();
}
PluginEditorCheckBeforeSend *PluginEditorCheckBeforeSendManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.h b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.h
index f54ad921..5b4d7b74 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.h
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendmanager.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESENDMANAGER_H
#define PLUGINEDITORCHECKBEFORESENDMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageComposer {
class PluginEditorCheckBeforeSendManagerPrivate;
class PluginEditorCheckBeforeSend;
class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSendManager : public QObject
{
Q_OBJECT
public:
explicit PluginEditorCheckBeforeSendManager(QObject *parent = nullptr);
~PluginEditorCheckBeforeSendManager();
static PluginEditorCheckBeforeSendManager *self();
Q_REQUIRED_RESULT QVector<PluginEditorCheckBeforeSend *> pluginsList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT PluginEditorCheckBeforeSend *pluginFromIdentifier(const QString &id);
private:
PluginEditorCheckBeforeSendManagerPrivate *const d;
};
}
#endif // PLUGINEDITORCHECKBEFORESENDMANAGER_H
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp
index b83309f4..ddc3ef35 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.cpp
@@ -1,188 +1,188 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "plugineditorcheckbeforesendparams.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorCheckBeforeSendParamsPrivate
{
public:
PluginEditorCheckBeforeSendParamsPrivate()
{
}
QString ccAddresses;
QString bccAddresses;
QString toAddresses;
QString plainText;
QString subject;
QString defaultDomain;
int transportId = -1;
bool isHtml = false;
uint identity = 0;
bool hasAttachment = false;
};
PluginEditorCheckBeforeSendParams::PluginEditorCheckBeforeSendParams()
: d(new MessageComposer::PluginEditorCheckBeforeSendParamsPrivate)
{
}
PluginEditorCheckBeforeSendParams::PluginEditorCheckBeforeSendParams(const PluginEditorCheckBeforeSendParams &other)
: d(new MessageComposer::PluginEditorCheckBeforeSendParamsPrivate)
{
(*this) = other;
}
PluginEditorCheckBeforeSendParams::~PluginEditorCheckBeforeSendParams()
{
delete d;
}
PluginEditorCheckBeforeSendParams &PluginEditorCheckBeforeSendParams::operator=(const PluginEditorCheckBeforeSendParams &other)
{
if (this != &other) {
d->subject = other.subject();
d->identity = other.identity();
d->isHtml = other.isHtmlMail();
d->plainText = other.plainText();
d->defaultDomain = other.defaultDomain();
d->hasAttachment = other.hasAttachment();
d->transportId = other.transportId();
d->bccAddresses = other.bccAddresses();
d->ccAddresses = other.ccAddresses();
d->toAddresses = other.toAddresses();
}
return *this;
}
bool PluginEditorCheckBeforeSendParams::operator ==(const PluginEditorCheckBeforeSendParams &other) const
{
return (d->subject == other.subject())
&& (d->identity == other.identity())
&& (d->isHtml == other.isHtmlMail())
&& (d->plainText == other.plainText())
&& (d->defaultDomain == other.defaultDomain())
&& (d->hasAttachment == other.hasAttachment())
&& (d->transportId == other.transportId())
&& (d->bccAddresses == other.bccAddresses())
&& (d->ccAddresses == other.ccAddresses())
&& (d->toAddresses == other.toAddresses());
}
void PluginEditorCheckBeforeSendParams::setSubject(const QString &subject)
{
d->subject = subject;
}
QString PluginEditorCheckBeforeSendParams::subject() const
{
return d->subject;
}
void PluginEditorCheckBeforeSendParams::setIdentity(uint currentIdentity)
{
d->identity = currentIdentity;
}
uint PluginEditorCheckBeforeSendParams::identity() const
{
return d->identity;
}
bool PluginEditorCheckBeforeSendParams::isHtmlMail() const
{
return d->isHtml;
}
void PluginEditorCheckBeforeSendParams::setHtmlMail(bool html)
{
d->isHtml = html;
}
void PluginEditorCheckBeforeSendParams::setPlainText(const QString &text)
{
d->plainText = text;
}
QString PluginEditorCheckBeforeSendParams::plainText() const
{
return d->plainText;
}
void PluginEditorCheckBeforeSendParams::setBccAddresses(const QString &lst)
{
d->bccAddresses = lst;
}
QString PluginEditorCheckBeforeSendParams::bccAddresses() const
{
return d->bccAddresses;
}
void PluginEditorCheckBeforeSendParams::setToAddresses(const QString &lst)
{
d->toAddresses = lst;
}
QString PluginEditorCheckBeforeSendParams::toAddresses() const
{
return d->toAddresses;
}
void PluginEditorCheckBeforeSendParams::setCcAddresses(const QString &lst)
{
d->ccAddresses = lst;
}
QString PluginEditorCheckBeforeSendParams::ccAddresses() const
{
return d->ccAddresses;
}
void PluginEditorCheckBeforeSendParams::setDefaultDomain(const QString &domain)
{
d->defaultDomain = domain;
}
QString PluginEditorCheckBeforeSendParams::defaultDomain() const
{
return d->defaultDomain;
}
bool PluginEditorCheckBeforeSendParams::hasAttachment() const
{
return d->hasAttachment;
}
void PluginEditorCheckBeforeSendParams::setHasAttachment(bool b)
{
d->hasAttachment = b;
}
int PluginEditorCheckBeforeSendParams::transportId() const
{
return d->transportId;
}
void PluginEditorCheckBeforeSendParams::setTransportId(int id)
{
d->transportId = id;
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.h b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.h
index a7004fa8..7c4226db 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.h
+++ b/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendparams.h
@@ -1,71 +1,71 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCHECKBEFORESENDPARAMS_H
#define PLUGINEDITORCHECKBEFORESENDPARAMS_H
#include "messagecomposer_export.h"
#include <QString>
namespace MessageComposer {
class PluginEditorCheckBeforeSendParamsPrivate;
class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSendParams
{
public:
PluginEditorCheckBeforeSendParams();
PluginEditorCheckBeforeSendParams(const PluginEditorCheckBeforeSendParams &other);
~PluginEditorCheckBeforeSendParams();
void setSubject(const QString &subject);
Q_REQUIRED_RESULT QString subject() const;
void setIdentity(uint currentIdentity);
Q_REQUIRED_RESULT uint identity() const;
Q_REQUIRED_RESULT bool isHtmlMail() const;
void setHtmlMail(bool html);
void setPlainText(const QString &text);
Q_REQUIRED_RESULT QString plainText() const;
void setBccAddresses(const QString &lst);
Q_REQUIRED_RESULT QString bccAddresses() const;
void setToAddresses(const QString &lst);
Q_REQUIRED_RESULT QString toAddresses() const;
void setCcAddresses(const QString &lst);
Q_REQUIRED_RESULT QString ccAddresses() const;
void setDefaultDomain(const QString &domain);
Q_REQUIRED_RESULT QString defaultDomain() const;
Q_REQUIRED_RESULT bool hasAttachment() const;
void setHasAttachment(bool b);
Q_REQUIRED_RESULT int transportId() const;
void setTransportId(int id);
PluginEditorCheckBeforeSendParams &operator =(const PluginEditorCheckBeforeSendParams &other);
Q_REQUIRED_RESULT bool operator ==(const PluginEditorCheckBeforeSendParams &other) const;
private:
PluginEditorCheckBeforeSendParamsPrivate *const d;
};
}
#endif // PLUGINEDITORCHECKBEFORESENDPARAMS_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp
index 0739dba5..7fb340e4 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.cpp
@@ -1,45 +1,44 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverterbeforeconvertingdata.h"
using namespace MessageComposer;
PluginEditorConverterBeforeConvertingData::PluginEditorConverterBeforeConvertingData()
{
-
}
bool PluginEditorConverterBeforeConvertingData::newMessage() const
{
return mNewMessage;
}
void PluginEditorConverterBeforeConvertingData::setNewMessage(bool newMessage)
{
mNewMessage = newMessage;
}
KMime::Message::Ptr PluginEditorConverterBeforeConvertingData::message() const
{
return mMessage;
}
void PluginEditorConverterBeforeConvertingData::setMessage(const KMime::Message::Ptr &message)
{
mMessage = message;
}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.h
index f6203c9d..92610787 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverterbeforeconvertingdata.h
@@ -1,43 +1,43 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCONVERTERBEFORECONVERTINGDATA_H
#define PLUGINEDITORCONVERTERBEFORECONVERTINGDATA_H
#include "messagecomposer_export.h"
#include <KMime/Message>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginEditorConverterBeforeConvertingData
{
public:
PluginEditorConverterBeforeConvertingData();
Q_REQUIRED_RESULT bool newMessage() const;
void setNewMessage(bool newMessage);
Q_REQUIRED_RESULT KMime::Message::Ptr message() const;
void setMessage(const KMime::Message::Ptr &message);
private:
KMime::Message::Ptr mMessage;
bool mNewMessage = true;
};
}
#endif // PLUGINEDITORCONVERTERBEFORECONVERTINGDATA_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.cpp
index 25245b47..f5a40109 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.cpp
@@ -1,47 +1,46 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverterinitialdata.h"
using namespace MessageComposer;
PluginEditorConverterInitialData::PluginEditorConverterInitialData()
{
-
}
bool PluginEditorConverterInitialData::newMessage() const
{
return mNewMessage;
}
void PluginEditorConverterInitialData::setNewMessage(bool newMessage)
{
mNewMessage = newMessage;
}
KMime::Message::Ptr PluginEditorConverterInitialData::mewMsg() const
{
return mMewMsg;
}
void PluginEditorConverterInitialData::setMewMsg(const KMime::Message::Ptr &mewMsg)
{
mMewMsg = mewMsg;
}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.h
index 33f13198..6cfa4438 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverterinitialdata.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCONVERTERINITIALDATA_H
#define PLUGINEDITORCONVERTERINITIALDATA_H
#include "messagecomposer_export.h"
#include <KMime/Message>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginEditorConverterInitialData
{
public:
PluginEditorConverterInitialData();
Q_REQUIRED_RESULT bool newMessage() const;
void setNewMessage(bool newMessage);
Q_REQUIRED_RESULT KMime::Message::Ptr mewMsg() const;
void setMewMsg(const KMime::Message::Ptr &mewMsg);
private:
KMime::Message::Ptr mMewMsg;
bool mNewMessage = true;
};
}
#endif // PLUGINEDITORCONVERTERINITIALDATA_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.cpp
index 4e02731a..b1dcfda6 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.cpp
@@ -1,73 +1,93 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverttext.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorConverttextPrivate
{
public:
PluginEditorConverttextPrivate()
{
}
bool mIsEnabled = false;
};
PluginEditorConvertText::PluginEditorConvertText(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorConverttextPrivate)
{
}
PluginEditorConvertText::~PluginEditorConvertText()
{
delete d;
}
bool PluginEditorConvertText::hasConfigureDialog() const
{
return false;
}
void PluginEditorConvertText::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
}
void PluginEditorConvertText::emitConfigChanged()
{
Q_EMIT configChanged();
}
QString PluginEditorConvertText::description() const
{
return {};
}
void PluginEditorConvertText::setIsEnabled(bool enabled)
{
d->mIsEnabled = enabled;
}
bool PluginEditorConvertText::isEnabled() const
{
return d->mIsEnabled;
}
+
+bool PluginEditorConvertText::canWorkOnHtml() const
+{
+ return true;
+}
+
+bool PluginEditorConvertText::hasToolBarSupport() const
+{
+ return false;
+}
+
+bool PluginEditorConvertText::hasStatusBarSupport() const
+{
+ return false;
+}
+
+bool PluginEditorConvertText::hasPopupMenuSupport() const
+{
+ return false;
+}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.h
index c5acac6c..faa01676 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttext.h
@@ -1,57 +1,65 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCONVERTTEXT_H
#define PLUGINEDITORCONVERTTEXT_H
#include <QObject>
#include "messagecomposer_export.h"
class KActionCollection;
namespace MessageComposer {
class PluginEditorConverttextPrivate;
class PluginEditorConvertTextInterface;
class MESSAGECOMPOSER_EXPORT PluginEditorConvertText : public QObject
{
Q_OBJECT
public:
explicit PluginEditorConvertText(QObject *parent = nullptr);
~PluginEditorConvertText();
- virtual PluginEditorConvertTextInterface *createInterface(KActionCollection *ac, QObject *parent) = 0;
+ virtual PluginEditorConvertTextInterface *createInterface(QObject *parent) = 0;
Q_REQUIRED_RESULT virtual bool hasConfigureDialog() const;
virtual void showConfigureDialog(QWidget *parent = nullptr);
void emitConfigChanged();
Q_REQUIRED_RESULT virtual QString description() const;
void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
+ Q_REQUIRED_RESULT virtual bool canWorkOnHtml() const;
+
+ Q_REQUIRED_RESULT virtual bool hasStatusBarSupport() const;
+
+ Q_REQUIRED_RESULT virtual bool hasPopupMenuSupport() const;
+
+ Q_REQUIRED_RESULT virtual bool hasToolBarSupport() const;
+
Q_SIGNALS:
void configChanged();
private:
PluginEditorConverttextPrivate *const d;
};
}
#endif // PLUGINEDITORCONVERTTEXT_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp
index e9b732b8..3665a239 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverttextconfigurewidget.h"
using namespace MessageComposer;
PluginEditorConvertTextConfigureWidget::PluginEditorConvertTextConfigureWidget(QWidget *parent)
: QWidget(parent)
{
}
PluginEditorConvertTextConfigureWidget::~PluginEditorConvertTextConfigureWidget()
{
}
QString PluginEditorConvertTextConfigureWidget::helpAnchor() const
{
return QString();
}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.h
index cd827fef..48c10fd6 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextconfigurewidget.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PluginEditorConvertTextConfigureWidget_H
#define PluginEditorConvertTextConfigureWidget_H
#include "messagecomposer_export.h"
#include <QWidget>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginEditorConvertTextConfigureWidget : public QWidget
{
Q_OBJECT
public:
explicit PluginEditorConvertTextConfigureWidget(QWidget *parent = nullptr);
~PluginEditorConvertTextConfigureWidget();
virtual void loadSettings() = 0;
virtual void saveSettings() = 0;
virtual void resetSettings() = 0;
virtual QString helpAnchor() const;
Q_SIGNALS:
void configureChanged();
};
}
#endif // PluginEditorConvertTextConfigureWidget_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.cpp
index 240e2327..ce12f2fc 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.cpp
@@ -1,116 +1,149 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverttextinterface.h"
#include "plugineditorconverterinitialdata.h"
#include "plugineditorconverterbeforeconvertingdata.h"
#include <KActionCollection>
using namespace MessageComposer;
class MessageComposer::PluginEditorConvertTextInterfacePrivate
{
public:
PluginEditorConvertTextInterfacePrivate()
{
}
- PluginActionType mActionType;
+
+ QVector<PluginActionType> mActionTypes;
QWidget *mParentWidget = nullptr;
+ QWidget *statusBarWidget = nullptr;
+ PluginEditorConvertText *plugin = nullptr;
KPIMTextEdit::RichTextComposer *mEditor = nullptr;
PluginEditorConverterInitialData mInitialData;
PluginEditorConverterBeforeConvertingData mBeforeConvertingData;
};
PluginEditorConvertTextInterface::PluginEditorConvertTextInterface(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorConvertTextInterfacePrivate)
{
}
PluginEditorConvertTextInterface::~PluginEditorConvertTextInterface()
{
delete d;
}
bool PluginEditorConvertTextInterface::reformatText()
{
return false;
}
void PluginEditorConvertTextInterface::setParentWidget(QWidget *parent)
{
d->mParentWidget = parent;
}
QWidget *PluginEditorConvertTextInterface::parentWidget() const
{
return d->mParentWidget;
}
-void PluginEditorConvertTextInterface::setActionType(PluginActionType type)
+void PluginEditorConvertTextInterface::setActionType(const QVector<PluginActionType> &type)
{
- d->mActionType = type;
+ d->mActionTypes = type;
}
-PluginActionType PluginEditorConvertTextInterface::actionType() const
+QVector<PluginActionType> PluginEditorConvertTextInterface::actionTypes() const
{
- return d->mActionType;
+ return d->mActionTypes;
+}
+
+void PluginEditorConvertTextInterface::addActionType(const PluginActionType &type)
+{
+ d->mActionTypes += type;
}
void PluginEditorConvertTextInterface::createAction(KActionCollection *ac)
{
Q_UNUSED(ac);
}
void PluginEditorConvertTextInterface::setInitialData(const PluginEditorConverterInitialData &data)
{
d->mInitialData = data;
}
PluginEditorConverterInitialData PluginEditorConvertTextInterface::initialData() const
{
return d->mInitialData;
}
void PluginEditorConvertTextInterface::setBeforeConvertingData(const PluginEditorConverterBeforeConvertingData &data)
{
d->mBeforeConvertingData = data;
}
PluginEditorConverterBeforeConvertingData PluginEditorConvertTextInterface::beforeConvertingData() const
{
return d->mBeforeConvertingData;
}
KPIMTextEdit::RichTextComposer *PluginEditorConvertTextInterface::richTextEditor() const
{
return d->mEditor;
}
void PluginEditorConvertTextInterface::setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor)
{
d->mEditor = richTextEditor;
}
void PluginEditorConvertTextInterface::reloadConfig()
{
//Reimplement it
}
+
+void PluginEditorConvertTextInterface::enableDisablePluginActions(bool richText)
+{
+ Q_UNUSED(richText);
+}
+
+void PluginEditorConvertTextInterface::setStatusBarWidget(QWidget *w)
+{
+ d->statusBarWidget = w;
+}
+
+QWidget *PluginEditorConvertTextInterface::statusBarWidget() const
+{
+ return d->statusBarWidget;
+}
+
+void PluginEditorConvertTextInterface::setPlugin(PluginEditorConvertText *plugin)
+{
+ d->plugin = plugin;
+}
+
+PluginEditorConvertText *PluginEditorConvertTextInterface::plugin() const
+{
+ return d->plugin;
+}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.h
index a737d317..58304ef5 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextinterface.h
@@ -1,77 +1,93 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCONVERTTEXTINTERFACE_H
#define PLUGINEDITORCONVERTTEXTINTERFACE_H
#include <QObject>
#include "messagecomposer_export.h"
#include <MessageComposer/PluginActionType>
#include <KMime/Message>
-
namespace KPIMTextEdit {
class RichTextComposer;
}
class KActionCollection;
namespace MessageComposer {
class TextPart;
class PluginEditorConvertTextInterfacePrivate;
class PluginEditorConverterInitialData;
class PluginEditorConverterBeforeConvertingData;
+class PluginEditorConvertText;
class MESSAGECOMPOSER_EXPORT PluginEditorConvertTextInterface : public QObject
{
Q_OBJECT
public:
explicit PluginEditorConvertTextInterface(QObject *parent = nullptr);
~PluginEditorConvertTextInterface();
+ enum class ConvertTextStatus {
+ NotConverted,
+ Converted,
+ Error
+ };
+
virtual bool reformatText();
- virtual bool convertTextToFormat(MessageComposer::TextPart *textPart) = 0;
+ virtual PluginEditorConvertTextInterface::ConvertTextStatus convertTextToFormat(MessageComposer::TextPart *textPart) = 0;
void setParentWidget(QWidget *parent);
Q_REQUIRED_RESULT QWidget *parentWidget() const;
Q_REQUIRED_RESULT KPIMTextEdit::RichTextComposer *richTextEditor() const;
void setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor);
- void setActionType(PluginActionType type);
- Q_REQUIRED_RESULT PluginActionType actionType() const;
+ void setActionType(const QVector<PluginActionType> &type);
+ void addActionType(const PluginActionType &type);
+ Q_REQUIRED_RESULT QVector<PluginActionType> actionTypes() const;
virtual void createAction(KActionCollection *ac);
virtual void setInitialData(const PluginEditorConverterInitialData &data);
Q_REQUIRED_RESULT PluginEditorConverterInitialData initialData() const;
virtual void setBeforeConvertingData(const PluginEditorConverterBeforeConvertingData &data);
Q_REQUIRED_RESULT PluginEditorConverterBeforeConvertingData beforeConvertingData() const;
+ virtual void enableDisablePluginActions(bool richText);
+
+ void setStatusBarWidget(QWidget *w);
+
+ QWidget *statusBarWidget() const;
+
+ void setPlugin(PluginEditorConvertText *plugin);
+ Q_REQUIRED_RESULT PluginEditorConvertText *plugin() const;
+
public Q_SLOTS:
virtual void reloadConfig();
Q_SIGNALS:
void textReformated();
private:
PluginEditorConvertTextInterfacePrivate *const d;
};
}
#endif // PLUGINEDITORCONVERTTEXTINTERFACE_H
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.cpp b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.cpp
index 7888894a..d975ad60 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.cpp
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.cpp
@@ -1,204 +1,204 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorconverttextmanager.h"
#include "plugineditorconverttext.h"
#include "messagecomposer_debug.h"
#include <QFileInfo>
#include <QSet>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <KPluginFactory>
using namespace MessageComposer;
class PluginEditorConvertTextInfo
{
public:
PluginEditorConvertTextInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
PluginEditorConvertText *plugin = nullptr;
bool isEnabled = true;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
class MessageComposer::PluginEditorConvertTextManagerPrivate
{
public:
PluginEditorConvertTextManagerPrivate(PluginEditorConvertTextManager *qq)
: q(qq)
{
initializePlugins();
}
void loadPlugin(PluginEditorConvertTextInfo *item);
QVector<PluginEditorConvertText *> pluginsList() const;
bool initializePlugins();
QVector<PluginEditorConvertTextInfo> mPluginList;
QString configPrefixSettingKey() const;
QString configGroupName() const;
QVector<PimCommon::PluginUtilData> pluginsDataList() const;
PluginEditorConvertText *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
PluginEditorConvertTextManager *q;
};
QString PluginEditorConvertTextManagerPrivate::configGroupName() const
{
return QStringLiteral("KMailPluginEditorConvertText");
}
QString PluginEditorConvertTextManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("PluginEditorConvertText");
}
QVector<PimCommon::PluginUtilData> PluginEditorConvertTextManagerPrivate::pluginsDataList() const
{
return mPluginDataList;
}
bool PluginEditorConvertTextManagerPrivate::initializePlugins()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kmail"), [](const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("KMailEditor/PluginEditorConvertText"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
PluginEditorConvertTextInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<PluginEditorConvertTextInfo>::iterator end(mPluginList.end());
for (QVector<PluginEditorConvertTextInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
void PluginEditorConvertTextManagerPrivate::loadPlugin(PluginEditorConvertTextInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<PluginEditorConvertText>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
QVector<PluginEditorConvertText *> PluginEditorConvertTextManagerPrivate::pluginsList() const
{
QVector<PluginEditorConvertText *> lst;
QVector<PluginEditorConvertTextInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorConvertTextInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
PluginEditorConvertText *PluginEditorConvertTextManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<PluginEditorConvertTextInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorConvertTextInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
PluginEditorConvertTextManager::PluginEditorConvertTextManager(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorConvertTextManagerPrivate(this))
{
}
PluginEditorConvertTextManager::~PluginEditorConvertTextManager()
{
delete d;
}
PluginEditorConvertTextManager *PluginEditorConvertTextManager::self()
{
static PluginEditorConvertTextManager s_self;
return &s_self;
}
QVector<PluginEditorConvertText *> PluginEditorConvertTextManager::pluginsList() const
{
return d->pluginsList();
}
QString PluginEditorConvertTextManager::configGroupName() const
{
return d->configGroupName();
}
QString PluginEditorConvertTextManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
QVector<PimCommon::PluginUtilData> PluginEditorConvertTextManager::pluginsDataList() const
{
return d->pluginsDataList();
}
PluginEditorConvertText *PluginEditorConvertTextManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h
index cfd595ef..93c724ef 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h
+++ b/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORCONVERTTEXTMANAGER_H
#define PLUGINEDITORCONVERTTEXTMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageComposer {
class PluginEditorConvertTextManagerPrivate;
class PluginEditorConvertText;
class MESSAGECOMPOSER_EXPORT PluginEditorConvertTextManager : public QObject
{
Q_OBJECT
public:
explicit PluginEditorConvertTextManager(QObject *parent = nullptr);
~PluginEditorConvertTextManager();
static PluginEditorConvertTextManager *self();
Q_REQUIRED_RESULT QVector<PluginEditorConvertText *> pluginsList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT PluginEditorConvertText *pluginFromIdentifier(const QString &id);
private:
PluginEditorConvertTextManagerPrivate *const d;
};
}
#endif // PLUGINEDITORCONVERTTEXTMANAGER_H
diff --git a/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.cpp b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.cpp
new file mode 100644
index 00000000..b821cd6f
--- /dev/null
+++ b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.cpp
@@ -0,0 +1,126 @@
+/*
+ Copyright (C) 2019 Montel Laurent <montel@kde.org>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "plugineditorgrammarcustomtoolsviewinterface.h"
+#include <KPIMTextEdit/RichTextComposer>
+using namespace MessageComposer;
+class MessageComposer::PluginEditorGrammarCustomToolsViewInterfacePrivate
+{
+public:
+ PluginEditorGrammarCustomToolsViewInterfacePrivate()
+ {
+ }
+
+ QWidget *mParentWidget = nullptr;
+ KPIMTextEdit::RichTextComposer *mEditor = nullptr;
+};
+
+PluginEditorGrammarCustomToolsViewInterface::PluginEditorGrammarCustomToolsViewInterface(QWidget *parent)
+ : PimCommon::CustomToolsViewInterface(parent)
+ , d(new PluginEditorGrammarCustomToolsViewInterfacePrivate)
+{
+}
+
+PluginEditorGrammarCustomToolsViewInterface::~PluginEditorGrammarCustomToolsViewInterface()
+{
+}
+
+void PluginEditorGrammarCustomToolsViewInterface::setParentWidget(QWidget *parent)
+{
+ d->mParentWidget = parent;
+}
+
+QWidget *PluginEditorGrammarCustomToolsViewInterface::parentWidget() const
+{
+ return d->mParentWidget;
+}
+
+KPIMTextEdit::RichTextComposer *PluginEditorGrammarCustomToolsViewInterface::richTextEditor() const
+{
+ return d->mEditor;
+}
+
+void PluginEditorGrammarCustomToolsViewInterface::setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor)
+{
+ d->mEditor = richTextEditor;
+}
+
+PluginGrammarAction::PluginGrammarAction()
+{
+}
+
+QString PluginGrammarAction::replacement() const
+{
+ return mReplacement;
+}
+
+void PluginGrammarAction::setReplacement(const QString &replacement)
+{
+ mReplacement = replacement;
+}
+
+int PluginGrammarAction::start() const
+{
+ return mStart;
+}
+
+void PluginGrammarAction::setStart(int start)
+{
+ mStart = start;
+}
+
+int PluginGrammarAction::length() const
+{
+ return mLength;
+}
+
+void PluginGrammarAction::setLength(int end)
+{
+ mLength = end;
+}
+
+QStringList PluginGrammarAction::suggestions() const
+{
+ return mSuggestions;
+}
+
+void PluginGrammarAction::setSuggestions(const QStringList &suggestions)
+{
+ mSuggestions = suggestions;
+}
+
+int PluginGrammarAction::blockId() const
+{
+ return mBlockId;
+}
+
+void PluginGrammarAction::setBlockId(int blockId)
+{
+ mBlockId = blockId;
+}
+
+QDebug operator <<(QDebug d, const PluginGrammarAction &t)
+{
+ d << "start " << t.start();
+ d << "length " << t.length();
+ d << "blockId " << t.blockId();
+ d << "suggestion " << t.suggestions();
+ d << "replacement " << t.replacement();
+ return d;
+}
diff --git a/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.h b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.h
new file mode 100644
index 00000000..269e0037
--- /dev/null
+++ b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarcustomtoolsviewinterface.h
@@ -0,0 +1,82 @@
+/*
+ Copyright (C) 2019 Montel Laurent <montel@kde.org>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef PLUGINEDITORGRAMMARCUSTOMTOOLSVIEWINTERFACE_H
+#define PLUGINEDITORGRAMMARCUSTOMTOOLSVIEWINTERFACE_H
+
+#include <PimCommon/CustomToolsViewInterface>
+#include "messagecomposer_export.h"
+#include <QDebug>
+namespace KPIMTextEdit {
+class RichTextComposer;
+}
+namespace MessageComposer {
+class PluginEditorGrammarCustomToolsViewInterfacePrivate;
+
+class MESSAGECOMPOSER_EXPORT PluginGrammarAction
+{
+public:
+ PluginGrammarAction();
+
+ Q_REQUIRED_RESULT QString replacement() const;
+ void setReplacement(const QString &replacement);
+
+ Q_REQUIRED_RESULT int start() const;
+ void setStart(int start);
+
+ Q_REQUIRED_RESULT int length() const;
+ void setLength(int length);
+
+ Q_REQUIRED_RESULT QStringList suggestions() const;
+ void setSuggestions(const QStringList &suggestions);
+
+ Q_REQUIRED_RESULT int blockId() const;
+ void setBlockId(int blockId);
+
+private:
+ QStringList mSuggestions;
+ QString mReplacement;
+ int mStart = -1;
+ int mLength = -1;
+ int mBlockId = -1;
+};
+
+class MESSAGECOMPOSER_EXPORT PluginEditorGrammarCustomToolsViewInterface : public PimCommon::CustomToolsViewInterface
+{
+ Q_OBJECT
+public:
+ explicit PluginEditorGrammarCustomToolsViewInterface(QWidget *parent = nullptr);
+ ~PluginEditorGrammarCustomToolsViewInterface();
+
+ void setParentWidget(QWidget *parent);
+ Q_REQUIRED_RESULT QWidget *parentWidget() const;
+
+ Q_REQUIRED_RESULT KPIMTextEdit::RichTextComposer *richTextEditor() const;
+ void setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor);
+
+Q_SIGNALS:
+ void replaceText(const MessageComposer::PluginGrammarAction &act);
+
+private:
+ PluginEditorGrammarCustomToolsViewInterfacePrivate *const d;
+};
+}
+Q_DECLARE_METATYPE(MessageComposer::PluginGrammarAction)
+MESSAGECOMPOSER_EXPORT QDebug operator <<(QDebug d, const MessageComposer::PluginGrammarAction &t);
+#endif // PLUGINEDITORGRAMMARCUSTOMTOOLSVIEWINTERFACE_H
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.cpp
similarity index 57%
copy from messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp
copy to messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.cpp
index aa2e53c7..9c15d1a8 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp
+++ b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.cpp
@@ -1,204 +1,206 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2019 Laurent Montel <montel@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.
*/
-#include "plugineditorinitmanager.h"
-#include "plugineditorinit.h"
+#include "plugineditorgrammarmanager.h"
+#include <PimCommon/CustomToolsPlugin>
#include "messagecomposer_debug.h"
+#include "plugineditorgrammarcustomtoolsviewinterface.h"
#include <QFileInfo>
#include <QSet>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <KPluginFactory>
using namespace MessageComposer;
-class PluginEditorInitInfo
+class PluginEditorGrammarInfo
{
public:
- PluginEditorInitInfo()
+ PluginEditorGrammarInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
- PluginEditorInit *plugin = nullptr;
+ PimCommon::CustomToolsPlugin *plugin = nullptr;
bool isEnabled = true;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
-class MessageComposer::PluginEditorInitManagerPrivate
+class MessageComposer::PluginEditorGrammarManagerPrivate
{
public:
- PluginEditorInitManagerPrivate(PluginEditorInitManager *qq)
+ PluginEditorGrammarManagerPrivate(PluginEditorGrammarManager *qq)
: q(qq)
{
initializePlugins();
}
- void loadPlugin(PluginEditorInitInfo *item);
- QVector<PluginEditorInit *> pluginsList() const;
+ void loadPlugin(PluginEditorGrammarInfo *item);
+ QVector<PimCommon::CustomToolsPlugin *> pluginsList() const;
bool initializePlugins();
- QVector<PluginEditorInitInfo> mPluginList;
+ QVector<PluginEditorGrammarInfo> mPluginList;
QString configPrefixSettingKey() const;
QString configGroupName() const;
QVector<PimCommon::PluginUtilData> pluginsDataList() const;
- PluginEditorInit *pluginFromIdentifier(const QString &id);
+ PimCommon::CustomToolsPlugin *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
- PluginEditorInitManager *q;
+ PluginEditorGrammarManager *q;
};
-QString PluginEditorInitManagerPrivate::configGroupName() const
+QString PluginEditorGrammarManagerPrivate::configGroupName() const
{
- return QStringLiteral("KMailPluginEditorInit");
+ return QStringLiteral("KMailPluginEditorGrammar");
}
-QString PluginEditorInitManagerPrivate::configPrefixSettingKey() const
+QString PluginEditorGrammarManagerPrivate::configPrefixSettingKey() const
{
- return QStringLiteral("PluginEditorInit");
+ return QStringLiteral("PluginEditorGrammar");
}
-QVector<PimCommon::PluginUtilData> PluginEditorInitManagerPrivate::pluginsDataList() const
+QVector<PimCommon::PluginUtilData> PluginEditorGrammarManagerPrivate::pluginsDataList() const
{
return mPluginDataList;
}
-bool PluginEditorInitManagerPrivate::initializePlugins()
+bool PluginEditorGrammarManagerPrivate::initializePlugins()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kmail"), [](const KPluginMetaData &md) {
- return md.serviceTypes().contains(QLatin1String("KMailEditor/PluginEditorInit"));
+ return md.serviceTypes().contains(QLatin1String("KMailEditor/PluginEditorGrammar"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
- PluginEditorInitInfo info;
+ PluginEditorGrammarInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
- QVector<PluginEditorInitInfo>::iterator end(mPluginList.end());
- for (QVector<PluginEditorInitInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
+ QVector<PluginEditorGrammarInfo>::iterator end(mPluginList.end());
+ for (QVector<PluginEditorGrammarInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
-void PluginEditorInitManagerPrivate::loadPlugin(PluginEditorInitInfo *item)
+void PluginEditorGrammarManagerPrivate::loadPlugin(PluginEditorGrammarInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
- item->plugin = pluginLoader.factory()->create<PluginEditorInit>(q, QVariantList() << item->metaDataFileNameBaseName);
+ item->plugin = pluginLoader.factory()->create<PimCommon::CustomToolsPlugin>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
-QVector<PluginEditorInit *> PluginEditorInitManagerPrivate::pluginsList() const
+QVector<PimCommon::CustomToolsPlugin *> PluginEditorGrammarManagerPrivate::pluginsList() const
{
- QVector<PluginEditorInit *> lst;
- QVector<PluginEditorInitInfo>::ConstIterator end(mPluginList.constEnd());
- for (QVector<PluginEditorInitInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
+ QVector<PimCommon::CustomToolsPlugin *> lst;
+ QVector<PluginEditorGrammarInfo>::ConstIterator end(mPluginList.constEnd());
+ for (QVector<PluginEditorGrammarInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
-PluginEditorInit *PluginEditorInitManagerPrivate::pluginFromIdentifier(const QString &id)
+PimCommon::CustomToolsPlugin *PluginEditorGrammarManagerPrivate::pluginFromIdentifier(const QString &id)
{
- QVector<PluginEditorInitInfo>::ConstIterator end(mPluginList.constEnd());
- for (QVector<PluginEditorInitInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
+ QVector<PluginEditorGrammarInfo>::ConstIterator end(mPluginList.constEnd());
+ for (QVector<PluginEditorGrammarInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
-PluginEditorInitManager::PluginEditorInitManager(QObject *parent)
+PluginEditorGrammarManager::PluginEditorGrammarManager(QObject *parent)
: QObject(parent)
- , d(new MessageComposer::PluginEditorInitManagerPrivate(this))
+ , d(new MessageComposer::PluginEditorGrammarManagerPrivate(this))
{
+ qRegisterMetaType<MessageComposer::PluginGrammarAction>();
}
-PluginEditorInitManager::~PluginEditorInitManager()
+PluginEditorGrammarManager::~PluginEditorGrammarManager()
{
delete d;
}
-PluginEditorInitManager *PluginEditorInitManager::self()
+PluginEditorGrammarManager *PluginEditorGrammarManager::self()
{
- static PluginEditorInitManager s_self;
+ static PluginEditorGrammarManager s_self;
return &s_self;
}
-QVector<PluginEditorInit *> PluginEditorInitManager::pluginsList() const
+QVector<PimCommon::CustomToolsPlugin *> PluginEditorGrammarManager::pluginsList() const
{
return d->pluginsList();
}
-QString PluginEditorInitManager::configGroupName() const
+QString PluginEditorGrammarManager::configGroupName() const
{
return d->configGroupName();
}
-QString PluginEditorInitManager::configPrefixSettingKey() const
+QString PluginEditorGrammarManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
-QVector<PimCommon::PluginUtilData> PluginEditorInitManager::pluginsDataList() const
+QVector<PimCommon::PluginUtilData> PluginEditorGrammarManager::pluginsDataList() const
{
return d->pluginsDataList();
}
-PluginEditorInit *PluginEditorInitManager::pluginFromIdentifier(const QString &id)
+PimCommon::CustomToolsPlugin *PluginEditorGrammarManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.h
similarity index 62%
copy from messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h
copy to messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.h
index cfd595ef..47eb2ef6 100644
--- a/messagecomposer/src/plugineditorconverttext/plugineditorconverttextmanager.h
+++ b/messagecomposer/src/plugineditorgrammar/plugineditorgrammarmanager.h
@@ -1,48 +1,51 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2019 Laurent Montel <montel@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.
*/
-#ifndef PLUGINEDITORCONVERTTEXTMANAGER_H
-#define PLUGINEDITORCONVERTTEXTMANAGER_H
+#ifndef PLUGINEDITORGRAMMARMANAGER_H
+#define PLUGINEDITORGRAMMARMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/PluginUtil>
+namespace PimCommon {
+class CustomToolsPlugin;
+}
namespace MessageComposer {
-class PluginEditorConvertTextManagerPrivate;
+class PluginEditorGrammarManagerPrivate;
class PluginEditorConvertText;
-class MESSAGECOMPOSER_EXPORT PluginEditorConvertTextManager : public QObject
+class MESSAGECOMPOSER_EXPORT PluginEditorGrammarManager : public QObject
{
Q_OBJECT
public:
- explicit PluginEditorConvertTextManager(QObject *parent = nullptr);
- ~PluginEditorConvertTextManager();
+ explicit PluginEditorGrammarManager(QObject *parent = nullptr);
+ ~PluginEditorGrammarManager();
- static PluginEditorConvertTextManager *self();
+ static PluginEditorGrammarManager *self();
- Q_REQUIRED_RESULT QVector<PluginEditorConvertText *> pluginsList() const;
+ Q_REQUIRED_RESULT QVector<PimCommon::CustomToolsPlugin *> pluginsList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
- Q_REQUIRED_RESULT PluginEditorConvertText *pluginFromIdentifier(const QString &id);
+ Q_REQUIRED_RESULT PimCommon::CustomToolsPlugin *pluginFromIdentifier(const QString &id);
private:
- PluginEditorConvertTextManagerPrivate *const d;
+ PluginEditorGrammarManagerPrivate *const d;
};
}
-#endif // PLUGINEDITORCONVERTTEXTMANAGER_H
+#endif // PLUGINEDITORGRAMMARMANAGER_H
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinit.cpp b/messagecomposer/src/plugineditorinit/plugineditorinit.cpp
index 5bae7eeb..d13e3f58 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinit.cpp
+++ b/messagecomposer/src/plugineditorinit/plugineditorinit.cpp
@@ -1,73 +1,73 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorinit.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorInitPrivate
{
public:
PluginEditorInitPrivate()
{
}
bool mIsEnabled = false;
};
PluginEditorInit::PluginEditorInit(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorInitPrivate)
{
}
PluginEditorInit::~PluginEditorInit()
{
delete d;
}
bool PluginEditorInit::hasConfigureDialog() const
{
return false;
}
void PluginEditorInit::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
}
void PluginEditorInit::emitConfigChanged()
{
Q_EMIT configChanged();
}
QString PluginEditorInit::description() const
{
return {};
}
void PluginEditorInit::setIsEnabled(bool enabled)
{
d->mIsEnabled = enabled;
}
bool PluginEditorInit::isEnabled() const
{
return d->mIsEnabled;
}
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinit.h b/messagecomposer/src/plugineditorinit/plugineditorinit.h
index 27c44c16..063acfed 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinit.h
+++ b/messagecomposer/src/plugineditorinit/plugineditorinit.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORINIT_H
#define PLUGINEDITORINIT_H
#include <QObject>
#include "messagecomposer_export.h"
namespace MessageComposer {
class PluginEditorInitPrivate;
class PluginEditorInitInterface;
class MESSAGECOMPOSER_EXPORT PluginEditorInit : public QObject
{
Q_OBJECT
public:
explicit PluginEditorInit(QObject *parent = nullptr);
~PluginEditorInit();
virtual PluginEditorInitInterface *createInterface(QObject *parent) = 0;
Q_REQUIRED_RESULT virtual bool hasConfigureDialog() const;
virtual void showConfigureDialog(QWidget *parent = nullptr);
void emitConfigChanged();
Q_REQUIRED_RESULT virtual QString description() const;
void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
Q_SIGNALS:
void configChanged();
private:
PluginEditorInitPrivate *const d;
};
}
#endif // PLUGINEDITORINIT_H
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.cpp b/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.cpp
index 0abf9c33..277670a9 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.cpp
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorinitconfigurewidget.h"
using namespace MessageComposer;
PluginEditorInitConfigureWidget::PluginEditorInitConfigureWidget(QWidget *parent)
: QWidget(parent)
{
}
PluginEditorInitConfigureWidget::~PluginEditorInitConfigureWidget()
{
}
QString PluginEditorInitConfigureWidget::helpAnchor() const
{
return QString();
}
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.h b/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.h
index 64b7da0e..6a7094f2 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.h
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitconfigurewidget.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef PluginEditorInitConfigureWidget_H
#define PluginEditorInitConfigureWidget_H
#include "messagecomposer_export.h"
#include <QWidget>
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT PluginEditorInitConfigureWidget : public QWidget
{
Q_OBJECT
public:
explicit PluginEditorInitConfigureWidget(QWidget *parent = nullptr);
~PluginEditorInitConfigureWidget();
virtual void loadSettings() = 0;
virtual void saveSettings() = 0;
virtual void resetSettings() = 0;
Q_REQUIRED_RESULT virtual QString helpAnchor() const;
Q_SIGNALS:
void configureChanged();
};
}
#endif // PluginEditorInitConfigureWidget_H
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitinterface.cpp b/messagecomposer/src/plugineditorinit/plugineditorinitinterface.cpp
index 4ec24b9b..4d2682b2 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitinterface.cpp
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitinterface.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorinitinterface.h"
using namespace MessageComposer;
class MessageComposer::PluginEditorInitInterfacePrivate
{
public:
PluginEditorInitInterfacePrivate()
{
}
QWidget *mParentWidget = nullptr;
KPIMTextEdit::RichTextComposer *mEditor = nullptr;
};
PluginEditorInitInterface::PluginEditorInitInterface(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorInitInterfacePrivate)
{
}
PluginEditorInitInterface::~PluginEditorInitInterface()
{
delete d;
}
void PluginEditorInitInterface::setParentWidget(QWidget *parent)
{
d->mParentWidget = parent;
}
QWidget *PluginEditorInitInterface::parentWidget() const
{
return d->mParentWidget;
}
KPIMTextEdit::RichTextComposer *PluginEditorInitInterface::richTextEditor() const
{
return d->mEditor;
}
void PluginEditorInitInterface::setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor)
{
d->mEditor = richTextEditor;
}
void PluginEditorInitInterface::reloadConfig()
{
//Reimplement it
}
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitinterface.h b/messagecomposer/src/plugineditorinit/plugineditorinitinterface.h
index 1a451d63..5a3446f9 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitinterface.h
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitinterface.h
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORINITINTERFACE_H
#define PLUGINEDITORINITINTERFACE_H
#include <QObject>
#include "messagecomposer_export.h"
namespace KPIMTextEdit {
class RichTextComposer;
}
namespace MessageComposer {
class PluginEditorInitInterfacePrivate;
class MESSAGECOMPOSER_EXPORT PluginEditorInitInterface : public QObject
{
Q_OBJECT
public:
explicit PluginEditorInitInterface(QObject *parent = nullptr);
~PluginEditorInitInterface();
virtual bool exec() = 0;
void setParentWidget(QWidget *parent);
Q_REQUIRED_RESULT QWidget *parentWidget() const;
Q_REQUIRED_RESULT KPIMTextEdit::RichTextComposer *richTextEditor() const;
void setRichTextEditor(KPIMTextEdit::RichTextComposer *richTextEditor);
public Q_SLOTS:
virtual void reloadConfig();
private:
PluginEditorInitInterfacePrivate *const d;
};
}
#endif // PLUGINEDITORINITINTERFACE_H
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp b/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp
index aa2e53c7..ade8d442 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitmanager.cpp
@@ -1,204 +1,204 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "plugineditorinitmanager.h"
#include "plugineditorinit.h"
#include "messagecomposer_debug.h"
#include <QFileInfo>
#include <QSet>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <KPluginFactory>
using namespace MessageComposer;
class PluginEditorInitInfo
{
public:
PluginEditorInitInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
PluginEditorInit *plugin = nullptr;
bool isEnabled = true;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
class MessageComposer::PluginEditorInitManagerPrivate
{
public:
PluginEditorInitManagerPrivate(PluginEditorInitManager *qq)
: q(qq)
{
initializePlugins();
}
void loadPlugin(PluginEditorInitInfo *item);
QVector<PluginEditorInit *> pluginsList() const;
bool initializePlugins();
QVector<PluginEditorInitInfo> mPluginList;
QString configPrefixSettingKey() const;
QString configGroupName() const;
QVector<PimCommon::PluginUtilData> pluginsDataList() const;
PluginEditorInit *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
PluginEditorInitManager *q;
};
QString PluginEditorInitManagerPrivate::configGroupName() const
{
return QStringLiteral("KMailPluginEditorInit");
}
QString PluginEditorInitManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("PluginEditorInit");
}
QVector<PimCommon::PluginUtilData> PluginEditorInitManagerPrivate::pluginsDataList() const
{
return mPluginDataList;
}
bool PluginEditorInitManagerPrivate::initializePlugins()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("kmail"), [](const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("KMailEditor/PluginEditorInit"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
PluginEditorInitInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<PluginEditorInitInfo>::iterator end(mPluginList.end());
for (QVector<PluginEditorInitInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
void PluginEditorInitManagerPrivate::loadPlugin(PluginEditorInitInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<PluginEditorInit>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
QVector<PluginEditorInit *> PluginEditorInitManagerPrivate::pluginsList() const
{
QVector<PluginEditorInit *> lst;
QVector<PluginEditorInitInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorInitInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
PluginEditorInit *PluginEditorInitManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<PluginEditorInitInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<PluginEditorInitInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
PluginEditorInitManager::PluginEditorInitManager(QObject *parent)
: QObject(parent)
, d(new MessageComposer::PluginEditorInitManagerPrivate(this))
{
}
PluginEditorInitManager::~PluginEditorInitManager()
{
delete d;
}
PluginEditorInitManager *PluginEditorInitManager::self()
{
static PluginEditorInitManager s_self;
return &s_self;
}
QVector<PluginEditorInit *> PluginEditorInitManager::pluginsList() const
{
return d->pluginsList();
}
QString PluginEditorInitManager::configGroupName() const
{
return d->configGroupName();
}
QString PluginEditorInitManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
QVector<PimCommon::PluginUtilData> PluginEditorInitManager::pluginsDataList() const
{
return d->pluginsDataList();
}
PluginEditorInit *PluginEditorInitManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.h b/messagecomposer/src/plugineditorinit/plugineditorinitmanager.h
index 6d6eeefd..fdff9f99 100644
--- a/messagecomposer/src/plugineditorinit/plugineditorinitmanager.h
+++ b/messagecomposer/src/plugineditorinit/plugineditorinitmanager.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef PLUGINEDITORINITMANAGER_H
#define PLUGINEDITORINITMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageComposer {
class PluginEditorInitManagerPrivate;
class PluginEditorInit;
class MESSAGECOMPOSER_EXPORT PluginEditorInitManager : public QObject
{
Q_OBJECT
public:
explicit PluginEditorInitManager(QObject *parent = nullptr);
~PluginEditorInitManager();
static PluginEditorInitManager *self();
Q_REQUIRED_RESULT QVector<PluginEditorInit *> pluginsList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT PluginEditorInit *pluginFromIdentifier(const QString &id);
private:
PluginEditorInitManagerPrivate *const d;
};
}
#endif // PLUGINEDITORINITMANAGER_H
diff --git a/messagecomposer/src/recipient/distributionlistdialog.cpp b/messagecomposer/src/recipient/distributionlistdialog.cpp
index 39b3e80b..a86cb85d 100644
--- a/messagecomposer/src/recipient/distributionlistdialog.cpp
+++ b/messagecomposer/src/recipient/distributionlistdialog.cpp
@@ -1,358 +1,360 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
This file was part of KMail.
Copyright (c) 2005 Cornelius Schumacher <schumacher@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.
*/
#include "distributionlistdialog.h"
#include <AkonadiWidgets/collectiondialog.h>
#include <Akonadi/Contact/ContactGroupSearchJob>
#include <Akonadi/Contact/ContactSearchJob>
#include <AkonadiCore/itemcreatejob.h>
#include <KEmailAddress>
#include <KLocalizedString>
#include "messagecomposer_debug.h"
#include <QLineEdit>
#include <KMessageBox>
#include <QInputDialog>
#include <QLabel>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <KConfigGroup>
#include <QPushButton>
using namespace MessageComposer;
namespace MessageComposer {
class DistributionListItem : public QTreeWidgetItem
{
public:
explicit DistributionListItem(QTreeWidget *tree)
: QTreeWidgetItem(tree)
{
setFlags(flags() | Qt::ItemIsUserCheckable);
}
void setAddressee(const KContacts::Addressee &a, const QString &email)
{
init(a, email);
}
void init(const KContacts::Addressee &a, const QString &email)
{
mAddressee = a;
mEmail = email;
mId = -1;
setText(0, mAddressee.realName());
setText(1, mEmail);
}
KContacts::Addressee addressee() const
{
return mAddressee;
}
QString email() const
{
return mEmail;
}
bool isTransient() const
{
return mId == -1;
}
void setId(Akonadi::Item::Id id)
{
mId = id;
}
Akonadi::Item::Id id() const
{
return mId;
}
private:
KContacts::Addressee mAddressee;
QString mEmail;
Akonadi::Item::Id mId = -1;
};
}
DistributionListDialog::DistributionListDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18nc("@title:window", "Save Distribution List"));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QWidget *topFrame = new QWidget(this);
mainLayout->addWidget(topFrame);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
mUser1Button = new QPushButton;
buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
mUser1Button->setText(i18nc("@action:button", "Save List"));
mUser1Button->setEnabled(false);
mUser1Button->setDefault(true);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DistributionListDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DistributionListDialog::reject);
mainLayout->addWidget(buttonBox);
setModal(false);
QBoxLayout *topLayout = new QVBoxLayout(topFrame);
- topLayout->setMargin(0);
+ topLayout->setContentsMargins(0, 0, 0, 0);
QBoxLayout *titleLayout = new QHBoxLayout();
topLayout->addItem(titleLayout);
QLabel *label = new QLabel(
i18nc("@label:textbox Name of the distribution list.", "&Name:"), topFrame);
titleLayout->addWidget(label);
mTitleEdit = new QLineEdit(topFrame);
titleLayout->addWidget(mTitleEdit);
mTitleEdit->setFocus();
mTitleEdit->setClearButtonEnabled(true);
label->setBuddy(mTitleEdit);
mRecipientsList = new QTreeWidget(topFrame);
mRecipientsList->setHeaderLabels(
QStringList() << i18nc("@title:column Name of the recipient", "Name")
<< i18nc("@title:column Email of the recipient", "Email")
);
mRecipientsList->setRootIsDecorated(false);
mRecipientsList->header()->setSectionsMovable(false);
topLayout->addWidget(mRecipientsList);
connect(mUser1Button, &QPushButton::clicked, this, &DistributionListDialog::slotUser1);
connect(mTitleEdit, &QLineEdit::textChanged, this, &DistributionListDialog::slotTitleChanged);
readConfig();
}
DistributionListDialog::~DistributionListDialog()
{
writeConfig();
}
// This starts one ContactSearchJob for each of the specified recipients.
void DistributionListDialog::setRecipients(const Recipient::List &recipients)
{
Recipient::List::ConstIterator end(recipients.constEnd());
for (Recipient::List::ConstIterator it = recipients.constBegin(); it != end; ++it) {
const QStringList emails = KEmailAddress::splitAddressList((*it)->email());
QStringList::ConstIterator end2(emails.constEnd());
for (QStringList::ConstIterator it2 = emails.constBegin(); it2 != end2; ++it2) {
QString name;
QString email;
KContacts::Addressee::parseEmailAddress(*it2, name, email);
if (!email.isEmpty()) {
Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob(this);
job->setQuery(Akonadi::ContactSearchJob::Email, email.toLower(), Akonadi::ContactSearchJob::ExactMatch);
job->setProperty("name", name);
job->setProperty("email", email);
connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedSetRecipients);
}
}
}
}
// This result slot will be called once for each of the original recipients.
// There could potentially be more than one Akonadi item returned per
// recipient, in the case where email addresses are duplicated between contacts.
void DistributionListDialog::slotDelayedSetRecipients(KJob *job)
{
const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
const Akonadi::Item::List akItems = searchJob->items();
const QString email = searchJob->property("email").toString();
QString name = searchJob->property("name").toString();
if (name.isEmpty()) {
const int index = email.indexOf(QLatin1Char('@'));
if (index != -1) {
name = email.left(index);
} else {
name = email;
}
}
if (akItems.isEmpty()) {
KContacts::Addressee contact;
contact.setNameFromString(name);
contact.insertEmail(email);
DistributionListItem *item = new DistributionListItem(mRecipientsList);
item->setAddressee(contact, email);
item->setCheckState(0, Qt::Checked);
} else {
bool isFirst = true;
for (const Akonadi::Item &akItem : qAsConst(akItems)) {
if (akItem.hasPayload<KContacts::Addressee>()) {
const KContacts::Addressee contact = akItem.payload<KContacts::Addressee>();
DistributionListItem *item = new DistributionListItem(mRecipientsList);
item->setAddressee(contact, email);
// Need to record the Akonadi ID of the contact, so that
// it can be added as a reference later. Setting an ID
// makes the item non-transient.
item->setId(akItem.id());
// If there were multiple contacts returned for an email address,
// then check the first one and uncheck any subsequent ones.
if (isFirst) {
item->setCheckState(0, Qt::Checked);
isFirst = false;
} else {
// Need this to create an unchecked item, as otherwise the
// item will have no checkbox at all.
item->setCheckState(0, Qt::Unchecked);
}
}
}
}
}
void DistributionListDialog::slotUser1()
{
bool isEmpty = true;
const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
for (int i = 0; i < numberOfTopLevel; ++i) {
DistributionListItem *item = static_cast<DistributionListItem *>(
mRecipientsList->topLevelItem(i));
if (item && item->checkState(0) == Qt::Checked) {
isEmpty = false;
break;
}
}
if (isEmpty) {
KMessageBox::information(this,
i18nc("@info", "There are no recipients in your list. "
"First select some recipients, "
"then try again."));
return;
}
QString name = mTitleEdit->text();
if (name.isEmpty()) {
bool ok = false;
name = QInputDialog::getText(this, i18nc("@title:window", "New Distribution List"),
i18nc("@label:textbox", "Please enter name:"), QLineEdit::Normal, QString(), &ok);
if (!ok || name.isEmpty()) {
return;
}
}
Akonadi::ContactGroupSearchJob *job = new Akonadi::ContactGroupSearchJob();
job->setQuery(Akonadi::ContactGroupSearchJob::Name, name);
job->setProperty("name", name);
+ qDebug() << " name " << name;
connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedUser1);
}
void DistributionListDialog::slotDelayedUser1(KJob *job)
{
const Akonadi::ContactGroupSearchJob *searchJob = qobject_cast<Akonadi::ContactGroupSearchJob *>(job);
const QString name = searchJob->property("name").toString();
if (!searchJob->contactGroups().isEmpty()) {
+ qDebug() << " searchJob->contactGroups()" << searchJob->contactGroups().count();
KMessageBox::information(this,
xi18nc("@info", "<para>Distribution list with the given name <resource>%1</resource> "
"already exists. Please select a different name.</para>", name));
return;
}
QPointer<Akonadi::CollectionDialog> dlg
= new Akonadi::CollectionDialog(Akonadi::CollectionDialog::KeepTreeExpanded, nullptr, this);
dlg->setMimeTypeFilter(QStringList() << KContacts::Addressee::mimeType()
<< KContacts::ContactGroup::mimeType());
dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
dlg->setWindowTitle(i18nc("@title:window", "Select Address Book"));
dlg->setDescription(i18n("Select the address book folder to store the contact group in:"));
if (dlg->exec()) {
const Akonadi::Collection targetCollection = dlg->selectedCollection();
delete dlg;
KContacts::ContactGroup group(name);
const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
for (int i = 0; i < numberOfTopLevel; ++i) {
DistributionListItem *item = static_cast<DistributionListItem *>(mRecipientsList->topLevelItem(i));
if (item && item->checkState(0) == Qt::Checked) {
qCDebug(MESSAGECOMPOSER_LOG) << item->addressee().fullEmail() << item->addressee().uid();
if (item->isTransient()) {
group.append(KContacts::ContactGroup::Data(item->addressee().realName(), item->email()));
} else {
KContacts::ContactGroup::ContactReference reference(QString::number(item->id()));
if (item->email() != item->addressee().preferredEmail()) {
reference.setPreferredEmail(item->email());
}
group.append(reference);
}
}
}
Akonadi::Item groupItem(KContacts::ContactGroup::mimeType());
groupItem.setPayload<KContacts::ContactGroup>(group);
Akonadi::Job *createJob = new Akonadi::ItemCreateJob(groupItem, targetCollection);
connect(createJob, &Akonadi::ItemCreateJob::result, this, &DistributionListDialog::slotContactGroupCreateJobResult);
}
delete dlg;
}
void DistributionListDialog::slotContactGroupCreateJobResult(KJob *job)
{
if (job->error()) {
KMessageBox::information(this, i18n("Unable to create distribution list: %1", job->errorString()));
qCWarning(MESSAGECOMPOSER_LOG) << "Unable to create distribution list:" << job->errorText();
} else {
accept();
}
}
void DistributionListDialog::slotTitleChanged(const QString &text)
{
mUser1Button->setEnabled(!text.trimmed().isEmpty());
}
void DistributionListDialog::readConfig()
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
KConfigGroup group(cfg, "DistributionListDialog");
const QSize size = group.readEntry("Size", QSize());
if (!size.isEmpty()) {
resize(size);
}
mRecipientsList->header()->restoreState(group.readEntry("Header", QByteArray()));
}
void DistributionListDialog::writeConfig()
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
KConfigGroup group(cfg, "DistributionListDialog");
group.writeEntry("Size", size());
group.writeEntry("Header", mRecipientsList->header()->saveState());
}
diff --git a/messagecomposer/src/recipient/recipient.cpp b/messagecomposer/src/recipient/recipient.cpp
index 0a7c6f4b..c22cbe61 100644
--- a/messagecomposer/src/recipient/recipient.cpp
+++ b/messagecomposer/src/recipient/recipient.cpp
@@ -1,157 +1,160 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
Based in kmail/recipientseditor.h/cpp
Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#include "recipient.h"
#include <KLocalizedString>
using namespace KPIM;
using namespace MessageComposer;
class MessageComposer::RecipientPrivate
{
public:
RecipientPrivate(const QString &email, Recipient::Type type)
- : mEmail(email)
- , mType(type)
+ : mType(type)
+ , mEmail(email)
{
}
+ Kleo::Action mEncryptionAction = Kleo::Impossible;
+ MessageComposer::Recipient::Type mType;
QString mEmail;
QString mName;
- Kleo::Action mEncryptionAction = Kleo::Impossible;
GpgME::Key mKey;
- MessageComposer::Recipient::Type mType;
};
Recipient::Recipient(const QString &email, Recipient::Type type)
: d(new MessageComposer::RecipientPrivate(email, type))
{
}
Recipient::~Recipient()
{
delete d;
}
void Recipient::setType(Type type)
{
d->mType = type;
}
Recipient::Type Recipient::type() const
{
return d->mType;
}
void Recipient::setEmail(const QString &email)
{
d->mEmail = email;
}
QString Recipient::email() const
{
return d->mEmail;
}
void Recipient::setName(const QString &name)
{
d->mName = name;
}
QString Recipient::name() const
{
return d->mName;
}
bool Recipient::isEmpty() const
{
return d->mEmail.isEmpty();
}
void Recipient::clear()
{
d->mEmail.clear();
d->mType = Recipient::To;
}
int Recipient::typeToId(Recipient::Type type)
{
return static_cast<int>(type);
}
Recipient::Type Recipient::idToType(int id)
{
return static_cast<Type>(id);
}
QString Recipient::typeLabel() const
{
return typeLabel(d->mType);
}
QString Recipient::typeLabel(Recipient::Type type)
{
switch (type) {
case To:
return i18nc("@label:listbox Recipient of an email message.", "To");
case Cc:
return i18nc("@label:listbox Carbon Copy recipient of an email message.", "CC");
case Bcc:
return i18nc("@label:listbox Blind carbon copy recipient of an email message.", "BCC");
+ case ReplyTo:
+ return i18nc("@label:listbox Reply-To recipient of an email message.", "Reply-To");
case Undefined:
break;
}
return xi18nc("@label:listbox", "<placeholder>Undefined Recipient Type</placeholder>");
}
QStringList Recipient::allTypeLabels()
{
QStringList types;
types.append(typeLabel(To));
types.append(typeLabel(Cc));
types.append(typeLabel(Bcc));
+ types.append(typeLabel(ReplyTo));
return types;
}
GpgME::Key Recipient::key() const
{
return d->mKey;
}
void Recipient::setKey(const GpgME::Key &key)
{
d->mKey = key;
}
Kleo::Action MessageComposer::Recipient::encryptionAction() const
{
return d->mEncryptionAction;
}
void MessageComposer::Recipient::setEncryptionAction(const Kleo::Action action)
{
d->mEncryptionAction = action;
}
diff --git a/messagecomposer/src/recipient/recipient.h b/messagecomposer/src/recipient/recipient.h
index 2f8c1e4e..9719a31f 100644
--- a/messagecomposer/src/recipient/recipient.h
+++ b/messagecomposer/src/recipient/recipient.h
@@ -1,82 +1,82 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
Based in kmail/recipientseditor.h/cpp
Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#ifndef MESSAGECOMPOSER_RECIPIENT_H
#define MESSAGECOMPOSER_RECIPIENT_H
#include "messagecomposer_export.h"
#include <Libkdepim/MultiplyingLine>
#include <gpgme++/key.h>
#include <libkleo/enum.h>
#include <QString>
#include <QSharedPointer>
namespace MessageComposer {
/** Represents a mail recipient. */
class RecipientPrivate;
class MESSAGECOMPOSER_EXPORT Recipient : public KPIM::MultiplyingLineData
{
public:
typedef QSharedPointer<Recipient> Ptr;
typedef QList<Recipient::Ptr> List;
enum Type {
- To, Cc, Bcc, Undefined
+ To, Cc, Bcc, ReplyTo, Undefined
};
Recipient(const QString &email = QString(), Type type = To); //krazy:exclude=explicit
~Recipient() override;
void setType(Type type);
Q_REQUIRED_RESULT Type type() const;
void setEmail(const QString &email);
Q_REQUIRED_RESULT QString email() const;
void setName(const QString &name);
Q_REQUIRED_RESULT QString name() const;
Q_REQUIRED_RESULT bool isEmpty() const override;
void clear() override;
Q_REQUIRED_RESULT static int typeToId(Type type);
Q_REQUIRED_RESULT static Type idToType(int id);
Q_REQUIRED_RESULT QString typeLabel() const;
Q_REQUIRED_RESULT static QString typeLabel(Type type);
Q_REQUIRED_RESULT static QStringList allTypeLabels();
void setEncryptionAction(const Kleo::Action action);
Q_REQUIRED_RESULT Kleo::Action encryptionAction() const;
void setKey(const GpgME::Key &key);
Q_REQUIRED_RESULT GpgME::Key key() const;
private:
RecipientPrivate *const d;
};
}
#endif
diff --git a/messagecomposer/src/recipient/recipientline.cpp b/messagecomposer/src/recipient/recipientline.cpp
index e964c1f0..99aad01b 100644
--- a/messagecomposer/src/recipient/recipientline.cpp
+++ b/messagecomposer/src/recipient/recipientline.cpp
@@ -1,309 +1,309 @@
/*
Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "recipientline.h"
#include <KEmailAddress>
#include <KLocalizedString>
#include <QKeyEvent>
#include <QHBoxLayout>
using namespace MessageComposer;
using namespace KPIM;
RecipientComboBox::RecipientComboBox(QWidget *parent)
: KComboBox(parent)
{
}
void RecipientComboBox::keyPressEvent(QKeyEvent *ev)
{
if (ev->key() == Qt::Key_Right) {
Q_EMIT rightPressed();
} else {
KComboBox::keyPressEvent(ev);
}
}
RecipientLineEdit::RecipientLineEdit(QWidget *parent) : ComposerLineEdit(parent)
{
setExpandIntern(false);
}
void RecipientLineEdit::keyPressEvent(QKeyEvent *ev)
{
//Laurent Bug:280153
/*if ( ev->key() == Qt::Key_Backspace && text().isEmpty() ) {
ev->accept();
Q_EMIT deleteMe();
} else */
if (ev->key() == Qt::Key_Left && cursorPosition() == 0
&& !ev->modifiers().testFlag(Qt::ShiftModifier)) { // Shift would be pressed during selection
Q_EMIT leftPressed();
} else if (ev->key() == Qt::Key_Right && cursorPosition() == text().length()
&& !ev->modifiers().testFlag(Qt::ShiftModifier)) { // Shift would be pressed during selection
Q_EMIT rightPressed();
} else {
MessageComposer::ComposerLineEdit::keyPressEvent(ev);
}
}
RecipientLineNG::RecipientLineNG(QWidget *parent)
: MultiplyingLine(parent)
, mRecipientsCount(0)
, mModified(false)
, mData(new Recipient)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QBoxLayout *topLayout = new QHBoxLayout(this);
- topLayout->setMargin(0);
+ topLayout->setContentsMargins(0, 0, 0, 0);
const QStringList recipientTypes = Recipient::allTypeLabels();
mCombo = new RecipientComboBox(this);
mCombo->addItems(recipientTypes);
topLayout->addWidget(mCombo);
mCombo->setToolTip(i18nc("@label:listbox", "Select type of recipient"));
mEdit = new RecipientLineEdit(this);
mEdit->setToolTip(i18n("Set the list of email addresses to receive this message"));
mEdit->setClearButtonEnabled(true);
topLayout->addWidget(mEdit);
mEdit->installEventFilter(this);
connect(mEdit, &RecipientLineEdit::returnPressed, this, &RecipientLineNG::slotReturnPressed);
connect(mEdit, &RecipientLineEdit::deleteMe, this, &RecipientLineNG::slotPropagateDeletion);
connect(mEdit, &QLineEdit::textChanged,
this, &RecipientLineNG::analyzeLine);
connect(mEdit, &RecipientLineEdit::focusUp, this, &RecipientLineNG::slotFocusUp);
connect(mEdit, &RecipientLineEdit::focusDown, this, &RecipientLineNG::slotFocusDown);
connect(mEdit, &RecipientLineEdit::rightPressed, this, &RecipientLineNG::rightPressed);
connect(mEdit, &RecipientLineEdit::iconClicked, this, &RecipientLineNG::iconClicked);
- connect(mEdit, &RecipientLineEdit::leftPressed, mCombo, QOverload<>::of(&QWidget::setFocus));
+ connect(mEdit, &RecipientLineEdit::leftPressed, mCombo, qOverload<>(&QWidget::setFocus));
connect(mEdit, &RecipientLineEdit::editingFinished, this, &RecipientLineNG::slotEditingFinished);
connect(mEdit, &RecipientLineEdit::clearButtonClicked, this, &RecipientLineNG::slotPropagateDeletion);
- connect(mCombo, &RecipientComboBox::rightPressed, mEdit, QOverload<>::of(&QWidget::setFocus));
+ connect(mCombo, &RecipientComboBox::rightPressed, mEdit, qOverload<>(&QWidget::setFocus));
- connect(mCombo, QOverload<int>::of(&RecipientComboBox::activated),
+ connect(mCombo, qOverload<int>(&RecipientComboBox::activated),
this, &RecipientLineNG::slotTypeModified);
connect(mEdit, &RecipientLineEdit::addAddress, this, &RecipientLineNG::slotAddRecipient);
}
void RecipientLineNG::slotEditingFinished()
{
if (mEdit->text().isEmpty()) {
Q_EMIT deleteLine(this);
}
}
void RecipientLineNG::slotAddRecipient(const QString &email)
{
Q_EMIT addRecipient(this, email);
slotReturnPressed();
}
void RecipientLineNG::slotTypeModified()
{
mModified = true;
Q_EMIT typeModified(this);
}
void RecipientLineNG::analyzeLine(const QString &text)
{
const QStringList r = KEmailAddress::splitAddressList(text);
mRecipientsCount = r.count();
mModified = true;
Q_EMIT countChanged();
}
int RecipientLineNG::recipientsCount() const
{
return mRecipientsCount;
}
void RecipientLineNG::setData(const MultiplyingLineData::Ptr &data)
{
Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(data);
if (!rec) {
return;
}
mData = rec;
fieldsFromData();
}
MultiplyingLineData::Ptr RecipientLineNG::data() const
{
if (isModified()) {
const_cast<RecipientLineNG *>(this)->dataFromFields();
}
return mData;
}
void RecipientLineNG::dataFromFields()
{
if (!mData) {
return;
}
const QString editStr(mEdit->text());
QString displayName, addrSpec, comment;
if (KEmailAddress::splitAddress(editStr, displayName, addrSpec, comment) == KEmailAddress::AddressOk) {
mData->setName(displayName);
}
mData->setEmail(editStr);
mData->setType(Recipient::idToType(mCombo->currentIndex()));
mModified = false;
}
void RecipientLineNG::fieldsFromData()
{
if (!mData) {
return;
}
mCombo->setCurrentIndex(Recipient::typeToId(mData->type()));
mEdit->setText(mData->email());
}
void RecipientLineNG::activate()
{
mEdit->setFocus();
}
bool RecipientLineNG::isActive() const
{
return mEdit->hasFocus();
}
bool RecipientLineNG::isEmpty() const
{
return mEdit->text().isEmpty();
}
bool RecipientLineNG::isModified() const
{
return mModified || mEdit->isModified();
}
void RecipientLineNG::clearModified()
{
mModified = false;
mEdit->setModified(false);
}
int RecipientLineNG::setColumnWidth(int w)
{
w = qMax(w, mCombo->sizeHint().width());
mCombo->setFixedWidth(w);
mCombo->updateGeometry();
parentWidget()->updateGeometry();
return w;
}
void RecipientLineNG::fixTabOrder(QWidget *previous)
{
setTabOrder(previous, mCombo);
setTabOrder(mCombo, mEdit);
}
QWidget *RecipientLineNG::tabOut() const
{
return mEdit;
}
void RecipientLineNG::clear()
{
mRecipientsCount = 0;
mEdit->clear();
}
bool RecipientLineNG::canDeleteLineEdit() const
{
return mEdit->canDeleteLineEdit();
}
void RecipientLineNG::setCompletionMode(KCompletion::CompletionMode mode)
{
mEdit->setCompletionMode(mode);
}
Recipient::Type RecipientLineNG::recipientType() const
{
return Recipient::idToType(mCombo->currentIndex());
}
void RecipientLineNG::setRecipientType(Recipient::Type type)
{
mCombo->setCurrentIndex(Recipient::typeToId(type));
slotTypeModified();
}
void RecipientLineNG::setRecentAddressConfig(KConfig *config)
{
mEdit->setRecentAddressConfig(config);
}
Recipient::Ptr RecipientLineNG::recipient() const
{
return qSharedPointerDynamicCast<Recipient>(data());
}
void RecipientLineNG::setIcon(const QIcon &icon, const QString &tooltip)
{
mEdit->setIcon(icon, tooltip);
}
void RecipientLineNG::setEnableIndexSearch(bool enableIndexSearch)
{
mEdit->setEnableBalooSearch(enableIndexSearch);
}
bool RecipientLineNG::enableIndexSearch() const
{
return mEdit->enableBalooSearch();
}
void RecipientLineNG::setEnableAkonadiSearch(bool enableAkonadiSearch)
{
mEdit->setEnableAkonadiSearch(enableAkonadiSearch);
}
bool RecipientLineNG::enableAkonadiSearch() const
{
return mEdit->enableAkonadiSearch();
}
QString RecipientLineNG::rawData() const
{
return mEdit->text();
}
bool RecipientLineNG::eventFilter(QObject *watched, QEvent *event)
{
if (watched == mEdit) {
if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) {
Q_EMIT activeChanged();
}
}
return false;
}
diff --git a/messagecomposer/src/recipient/recipientseditor.cpp b/messagecomposer/src/recipient/recipientseditor.cpp
index 6f1f5080..89e3417c 100644
--- a/messagecomposer/src/recipient/recipientseditor.cpp
+++ b/messagecomposer/src/recipient/recipientseditor.cpp
@@ -1,370 +1,374 @@
/*
Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
Refactored from earlier code by:
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
Copyright (c) 2004 Cornelius Schumacher <schumacher@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.
*/
#include "recipientseditor.h"
#include "recipient.h"
#include "recipientline.h"
#include "recipientseditorsidewidget.h"
#include "settings/messagecomposersettings.h"
-#include <MessageComposer/DistributionListDialog>
+#include "distributionlistdialog.h"
#include "messagecomposer_debug.h"
#include <KMime/Headers>
#include <KLocalizedString>
#include <KMessageBox>
#include <KEmailAddress>
#include <QLayout>
#include <QKeyEvent>
using namespace MessageComposer;
using namespace KPIM;
RecipientLineFactory::RecipientLineFactory(QObject *parent)
: KPIM::MultiplyingLineFactory(parent)
{
}
KPIM::MultiplyingLine *RecipientLineFactory::newLine(QWidget *p)
{
RecipientLineNG *line = new RecipientLineNG(p);
if (qobject_cast<RecipientsEditor *>(parent())) {
connect(line, SIGNAL(addRecipient(RecipientLineNG*,QString)), qobject_cast<RecipientsEditor *>(parent()), SLOT(addRecipient(RecipientLineNG*,QString)));
} else {
qCWarning(MESSAGECOMPOSER_LOG) << "RecipientLineFactory::newLine: We can't connect to new line" << parent();
}
return line;
}
int RecipientLineFactory::maximumRecipients()
{
return MessageComposer::MessageComposerSettings::self()->maximumRecipients();
}
class MessageComposer::RecipientsEditorPrivate
{
public:
RecipientsEditorPrivate()
{
}
KConfig *mRecentAddressConfig = nullptr;
RecipientsEditorSideWidget *mSideWidget = nullptr;
bool mSkipTotal = false;
};
RecipientsEditor::RecipientsEditor(QWidget *parent)
: RecipientsEditor(new RecipientLineFactory(nullptr), parent)
{
}
RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
: MultiplyingLineEditor(lineFactory, parent)
, d(new MessageComposer::RecipientsEditorPrivate)
{
factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
d->mSideWidget = new RecipientsEditorSideWidget(this, this);
layout()->addWidget(d->mSideWidget);
// Install global event filter and listen for keypress events for RecipientLineEdits.
// Unfortunately we can't install ourselves directly as event filter for the edits,
// because the RecipientLineEdit has its own event filter installed into QApplication
// and so it would eat the event before it would reach us.
qApp->installEventFilter(this);
connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);
connect(this, &RecipientsEditor::lineAdded, this, &RecipientsEditor::slotLineAdded);
connect(this, &RecipientsEditor::lineDeleted, this, &RecipientsEditor::slotLineDeleted);
- addData(); // one defaut line
+ addData(); // one default line
}
RecipientsEditor::~RecipientsEditor()
{
delete d;
}
bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
{
return addData(Recipient::Ptr(new Recipient(recipient, type)));
}
void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
{
addRecipient(recipient, line->recipientType());
}
void RecipientsEditor::setRecipientString(const QVector< KMime::Types::Mailbox > &mailboxes, Recipient::Type type)
{
int count = 1;
for (const KMime::Types::Mailbox &mailbox : mailboxes) {
if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
KMessageBox::sorry(this,
i18ncp("@info:status",
"Truncating recipients list to %2 of %1 entry.",
"Truncating recipients list to %2 of %1 entries.",
mailboxes.count(),
MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
break;
}
addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type);
}
}
Recipient::List RecipientsEditor::recipients() const
{
const QList<MultiplyingLineData::Ptr> dataList = allData();
Recipient::List recList;
for (const MultiplyingLineData::Ptr &datum : dataList) {
Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(datum);
if (!rec) {
continue;
}
recList << rec;
}
return recList;
}
Recipient::Ptr RecipientsEditor::activeRecipient() const
{
return qSharedPointerDynamicCast<Recipient>(activeData());
}
QString RecipientsEditor::recipientString(Recipient::Type type) const
{
return recipientStringList(type).join(QStringLiteral(", "));
}
QStringList RecipientsEditor::recipientStringList(Recipient::Type type) const
{
QStringList selectedRecipients;
- foreach (const Recipient::Ptr &r, recipients()) {
+ for (const Recipient::Ptr &r : recipients()) {
if (r->type() == type) {
selectedRecipients << r->email();
}
}
return selectedRecipients;
}
void RecipientsEditor::removeRecipient(const QString &recipient, Recipient::Type type)
{
// search a line which matches recipient and type
QListIterator<MultiplyingLine *> it(lines());
MultiplyingLine *line = nullptr;
while (it.hasNext()) {
line = it.next();
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
if (rec) {
if ((rec->recipient()->email() == recipient)
&& (rec->recipientType() == type)) {
break;
}
}
}
if (line) {
line->slotPropagateDeletion();
}
}
void RecipientsEditor::saveDistributionList()
{
std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
dlg->setRecipients(recipients());
dlg->exec();
}
void RecipientsEditor::selectRecipients()
{
d->mSideWidget->pickRecipient();
}
void MessageComposer::RecipientsEditor::setRecentAddressConfig(KConfig *config)
{
d->mRecentAddressConfig = config;
if (config) {
MultiplyingLine *line;
foreach (line, lines()) {
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
if (rec) {
rec->setRecentAddressConfig(config);
}
}
}
}
void MessageComposer::RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
{
Recipient::Type t = rec.type();
tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
mModified = true;
}
RecipientsPicker *RecipientsEditor::picker() const
{
return d->mSideWidget->picker();
}
void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
{
// subtract 1 here, because we want the number of lines
// before this line was added.
int count = lines().size() - 1;
RecipientLineNG *rec = qobject_cast<RecipientLineNG *>(line);
if (!rec) {
return;
}
if (d->mRecentAddressConfig) {
rec->setRecentAddressConfig(d->mRecentAddressConfig);
}
if (count > 0) {
if (count == 1) {
RecipientLineNG *last_rec = qobject_cast< RecipientLineNG * >(lines().first());
- if (last_rec && last_rec->recipientType() == Recipient::Bcc) {
+ if (last_rec && (last_rec->recipientType() == Recipient::Bcc || last_rec->recipientType() == Recipient::ReplyTo)) {
rec->setRecipientType(Recipient::To);
} else {
rec->setRecipientType(Recipient::Cc);
}
} else {
RecipientLineNG *last_rec = qobject_cast< RecipientLineNG * >(lines().at(lines().count() - 2));
if (last_rec) {
- rec->setRecipientType(last_rec->recipientType());
+ if (last_rec->recipientType() == Recipient::ReplyTo) {
+ rec->setRecipientType(Recipient::To);
+ } else {
+ rec->setRecipientType(last_rec->recipientType());
+ }
}
}
line->fixTabOrder(lines().constLast()->tabOut());
}
connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
}
void RecipientsEditor::slotLineDeleted(int pos)
{
bool atLeastOneToLine = false;
int firstCC = -1;
for (int i = pos, total = lines().count(); i < total; ++i) {
MultiplyingLine *line = lines().at(i);
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
if (rec) {
if (rec->recipientType() == Recipient::To) {
atLeastOneToLine = true;
} else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
firstCC = i;
}
}
}
if (!atLeastOneToLine && (firstCC >= 0)) {
RecipientLineNG *firstCCLine = qobject_cast< RecipientLineNG * >(lines().at(firstCC));
if (firstCCLine) {
firstCCLine->setRecipientType(Recipient::To);
}
}
slotCalculateTotal();
}
bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
auto ke = static_cast<QKeyEvent *>(event);
// Treats comma or semicolon as email separator, will automatically move focus
// to a new line, basically preventing user from inputting more than one
// email address per line, which breaks our opportunistic crypto in composer
if (ke->key() == Qt::Key_Comma || (
ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
auto line = qobject_cast<RecipientLineNG *>(object->parent());
const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
if (split.size() > 1) {
addRecipient(QString(), line->recipientType());
setFocusBottom();
return true;
}
}
}
return false;
}
void RecipientsEditor::slotCalculateTotal()
{
// Prevent endless recursion when splitting recipient
if (d->mSkipTotal) {
return;
}
int empty = 0;
MultiplyingLine *line = nullptr;
foreach (line, lines()) {
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
if (rec) {
if (rec->isEmpty()) {
++empty;
} else {
const int recipientsCount = rec->recipientsCount();
if (recipientsCount > 1) {
// Ensure we always have only one recipient per line
d->mSkipTotal = true;
Recipient::Ptr recipient = rec->recipient();
const auto split = KEmailAddress::splitAddressList(recipient->email());
for (int i = 1 /* sic! */; i < split.count(); ++i) {
addRecipient(split[i], rec->recipientType());
}
recipient->setEmail(split[0]);
rec->setData(recipient);
setFocusBottom(); // focus next empty entry
d->mSkipTotal = false;
}
}
}
}
// We always want at least one empty line
if (empty == 0) {
addData();
}
int count = 0;
foreach (line, lines()) {
RecipientLineNG *rec = qobject_cast< RecipientLineNG * >(line);
if (rec) {
if (!rec->isEmpty()) {
count++;
}
}
}
// update the side widget
d->mSideWidget->setTotal(count, lines().count());
}
RecipientLineNG *RecipientsEditor::activeLine() const
{
MultiplyingLine *line = MultiplyingLineEditor::activeLine();
return qobject_cast< RecipientLineNG * >(line);
}
diff --git a/messagecomposer/src/recipient/recipientseditormanager.cpp b/messagecomposer/src/recipient/recipientseditormanager.cpp
index e02bcacb..620b8b3f 100644
--- a/messagecomposer/src/recipient/recipientseditormanager.cpp
+++ b/messagecomposer/src/recipient/recipientseditormanager.cpp
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "recipientseditormanager.h"
#include <Akonadi/Contact/EmailAddressSelectionModel>
#include <Akonadi/Contact/ContactsTreeModel>
using namespace MessageComposer;
RecipientsEditorManager::RecipientsEditorManager(QObject *parent)
: QObject(parent)
{
}
RecipientsEditorManager::~RecipientsEditorManager()
{
}
RecipientsEditorManager *RecipientsEditorManager::self()
{
static RecipientsEditorManager s_self;
return &s_self;
}
Akonadi::EmailAddressSelectionModel *RecipientsEditorManager::model()
{
if (!mModel) {
mModel = new Akonadi::EmailAddressSelectionModel(this);
}
return mModel;
}
diff --git a/messagecomposer/src/recipient/recipientseditormanager.h b/messagecomposer/src/recipient/recipientseditormanager.h
index 34dfb0d4..4d1dbbb9 100644
--- a/messagecomposer/src/recipient/recipientseditormanager.h
+++ b/messagecomposer/src/recipient/recipientseditormanager.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef RECIPIENTSEDITORMANAGER_H
#define RECIPIENTSEDITORMANAGER_H
#include <QObject>
#include "messagecomposer_export.h"
namespace Akonadi {
class EmailAddressSelectionModel;
}
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT RecipientsEditorManager : public QObject
{
Q_OBJECT
public:
explicit RecipientsEditorManager(QObject *parent = nullptr);
~RecipientsEditorManager();
static RecipientsEditorManager *self();
Q_REQUIRED_RESULT Akonadi::EmailAddressSelectionModel *model();
private:
Akonadi::EmailAddressSelectionModel *mModel = nullptr;
};
}
#endif // RECIPIENTSEDITORMANAGER_H
diff --git a/messagecomposer/src/recipient/recipientseditorsidewidget.cpp b/messagecomposer/src/recipient/recipientseditorsidewidget.cpp
index 6f4ea8d4..d519988e 100644
--- a/messagecomposer/src/recipient/recipientseditorsidewidget.cpp
+++ b/messagecomposer/src/recipient/recipientseditorsidewidget.cpp
@@ -1,170 +1,176 @@
/*
Copyright (C) 2010 Casey Link <unnamedrambler@gmail.com>
Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
Refactored from earlier code by:
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
Copyright (c) 2004 Cornelius Schumacher <schumacher@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.
*/
#include "recipientseditorsidewidget.h"
#include "recipientspicker.h"
#include "kwindowpositioner.h"
#include <KLocalizedString>
#include <QPushButton>
#include <QLabel>
#include <QBoxLayout>
using namespace MessageComposer;
RecipientsEditorSideWidget::RecipientsEditorSideWidget(RecipientsEditor *view, QWidget *parent)
: QWidget(parent)
, mEditor(view)
{
QBoxLayout *topLayout = new QVBoxLayout(this);
- topLayout->setMargin(0);
+ topLayout->setContentsMargins(0, 0, 0, 0);
topLayout->addStretch(1);
mTotalLabel = new QLabel(this);
mTotalLabel->setAlignment(Qt::AlignCenter);
mTotalLabel->setTextFormat(Qt::PlainText);
topLayout->addWidget(mTotalLabel);
mTotalLabel->hide();
topLayout->addStretch(1);
mDistributionListButton = new QPushButton(
i18nc("@action:button", "Save List..."), this);
topLayout->addWidget(mDistributionListButton);
mDistributionListButton->hide();
connect(mDistributionListButton, &QAbstractButton::clicked,
this, &RecipientsEditorSideWidget::saveDistributionList);
mDistributionListButton->setToolTip(
i18nc("@info:tooltip", "Save recipients as distribution list"));
mSelectButton = new QPushButton(
i18nc("@action:button Open recipient selection dialog.", "Se&lect..."), this);
topLayout->addWidget(mSelectButton);
connect(mSelectButton, &QPushButton::clicked, this, &RecipientsEditorSideWidget::pickRecipient);
mSelectButton->setToolTip(i18nc("@info:tooltip", "Select recipients from address book"));
updateTotalToolTip();
}
RecipientsEditorSideWidget::~RecipientsEditorSideWidget()
{
}
RecipientsPicker *RecipientsEditorSideWidget::picker() const
{
if (!mRecipientPicker) {
// hacks to allow picker() to be const in the presence of lazy loading
RecipientsEditorSideWidget *non_const_this = const_cast<RecipientsEditorSideWidget *>(this);
mRecipientPicker = new RecipientsPicker(non_const_this);
connect(mRecipientPicker, &RecipientsPicker::pickedRecipient,
non_const_this, &RecipientsEditorSideWidget::pickedRecipient);
mPickerPositioner = new KWindowPositioner(mSelectButton, mRecipientPicker);
}
return mRecipientPicker;
}
void RecipientsEditorSideWidget::setFocus()
{
mSelectButton->setFocus();
}
void RecipientsEditorSideWidget::setTotal(int recipients, int lines)
{
QString labelText;
if (recipients == 0) {
labelText = i18nc("@info:status No recipients selected",
"No recipients");
} else {
labelText = i18ncp("@info:status Number of recipients selected",
"1 recipient", "%1 recipients", recipients);
}
if (lines > 3) {
mTotalLabel->setText(labelText);
mTotalLabel->show();
updateTotalToolTip();
} else {
mTotalLabel->hide();
}
if (lines > 2) {
mDistributionListButton->show();
} else {
mDistributionListButton->hide();
}
}
void RecipientsEditorSideWidget::updateTotalToolTip()
{
- QString text = QStringLiteral("<qt>");
+ QString text;
QString to;
QString cc;
QString bcc;
+ QString replyTo;
Recipient::List recipients = mEditor->recipients();
Recipient::List::ConstIterator it;
Recipient::List::ConstIterator end(recipients.constEnd());
for (it = recipients.constBegin(); it != end; ++it) {
QString emailLine = QLatin1String("&nbsp;&nbsp;") + (*it)->email().toHtmlEscaped() + QLatin1String("<br/>");
switch ((*it)->type()) {
case Recipient::To:
to += emailLine;
break;
case Recipient::Cc:
cc += emailLine;
break;
case Recipient::Bcc:
bcc += emailLine;
break;
+ case Recipient::ReplyTo:
+ replyTo += emailLine;
+ break;
default:
break;
}
}
- text += xi18nc("@info:tooltip %1 list of emails", "<interface>To:</interface><nl/>%1", to);
+ text += i18nc("@info:tooltip %1 list of emails", "To:%1", to);
if (!cc.isEmpty()) {
- text += xi18nc("@info:tooltip %1 list of emails", "<interface>CC:</interface><nl/>%1", cc);
+ text += i18nc("@info:tooltip %1 list of emails", "CC:%1", cc);
}
if (!bcc.isEmpty()) {
- text += xi18nc("@info:tooltip %1 list of emails", "<interface>BCC:</interface><nl/>%1", bcc);
+ text += i18nc("@info:tooltip %1 list of emails", "BCC:%1", bcc);
+ }
+ if (!replyTo.isEmpty()) {
+ text += i18nc("@info:tooltip %1 list of emails", "Reply-To:%1", replyTo);
}
- text.append(QLatin1String("</qt>"));
- mTotalLabel->setToolTip(text);
+ mTotalLabel->setToolTip(QStringLiteral("<html><head><body>%1</body></head></html>").arg(text));
}
void RecipientsEditorSideWidget::pickRecipient()
{
MessageComposer::RecipientsPicker *p = picker();
Recipient::Ptr rec = mEditor->activeRecipient();
if (rec) {
p->setDefaultType(rec->type());
p->setRecipients(mEditor->recipients());
mPickerPositioner->reposition();
p->show();
}
}
diff --git a/messagecomposer/src/recipient/recipientspicker.cpp b/messagecomposer/src/recipient/recipientspicker.cpp
index 54e4791a..11860cbe 100644
--- a/messagecomposer/src/recipient/recipientspicker.cpp
+++ b/messagecomposer/src/recipient/recipientspicker.cpp
@@ -1,225 +1,237 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
This file was part of KMail.
Copyright (c) 2005 Cornelius Schumacher <schumacher@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.
*/
#include "recipientspicker.h"
#include "recipientspickerwidget.h"
#include "settings/messagecomposersettings.h"
#include <Akonadi/Contact/EmailAddressSelectionWidget>
#include <kcontacts/contactgroup.h>
#include <Libkdepim/LdapSearchDialog>
#include <kconfiggroup.h>
#include <KLocalizedString>
#include <kmessagebox.h>
#include <QLineEdit>
#include <QPushButton>
#include "messagecomposer_debug.h"
#include <QKeyEvent>
#include <QTreeView>
#include <QVBoxLayout>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <KConfigGroup>
using namespace MessageComposer;
RecipientsPicker::RecipientsPicker(QWidget *parent)
: QDialog(parent)
, mLdapSearchDialog(nullptr)
{
setObjectName(QStringLiteral("RecipientsPicker"));
setWindowTitle(i18n("Select Recipient"));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mView = new RecipientsPickerWidget(this);
mainLayout->addWidget(mView);
mainLayout->setStretchFactor(mView, 1);
connect(mView->view()->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &RecipientsPicker::slotSelectionChanged);
connect(mView->view(), &QAbstractItemView::doubleClicked,
this, &RecipientsPicker::slotPicked);
QPushButton *searchLDAPButton = new QPushButton(i18n("Search &Directory Service"), this);
connect(searchLDAPButton, &QPushButton::clicked, this, &RecipientsPicker::slotSearchLDAP);
mainLayout->addWidget(searchLDAPButton);
KConfig config(QStringLiteral("kabldaprc"));
KConfigGroup group = config.group("LDAP");
int numHosts = group.readEntry("NumSelectedHosts", 0);
if (!numHosts) {
searchLDAPButton->setVisible(false);
}
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
mUser1Button = new QPushButton;
buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
mUser2Button = new QPushButton;
buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
mUser3Button = new QPushButton;
buttonBox->addButton(mUser3Button, QDialogButtonBox::ActionRole);
+ mUser4Button = new QPushButton;
+ buttonBox->addButton(mUser4Button, QDialogButtonBox::ActionRole);
+
connect(buttonBox, &QDialogButtonBox::rejected, this, &RecipientsPicker::reject);
mainLayout->addWidget(buttonBox);
+ mUser4Button->setText(i18n("Add as &Reply-To"));
mUser3Button->setText(i18n("Add as &To"));
mUser2Button->setText(i18n("Add as CC"));
mUser1Button->setText(i18n("Add as &BCC"));
connect(mUser1Button, &QPushButton::clicked, this, &RecipientsPicker::slotBccClicked);
connect(mUser2Button, &QPushButton::clicked, this, &RecipientsPicker::slotCcClicked);
connect(mUser3Button, &QPushButton::clicked, this, &RecipientsPicker::slotToClicked);
+ connect(mUser4Button, &QPushButton::clicked, this, &RecipientsPicker::slotReplyToClicked);
mView->emailAddressSelectionWidget()->searchLineEdit()->setFocus();
readConfig();
slotSelectionChanged();
}
RecipientsPicker::~RecipientsPicker()
{
writeConfig();
}
void RecipientsPicker::slotSelectionChanged()
{
const bool hasSelection = !mView->emailAddressSelectionWidget()->selectedAddresses().isEmpty();
mUser1Button->setEnabled(hasSelection);
mUser2Button->setEnabled(hasSelection);
mUser3Button->setEnabled(hasSelection);
+ mUser4Button->setEnabled(hasSelection);
}
void RecipientsPicker::setRecipients(const Recipient::List &)
{
mView->view()->selectionModel()->clear();
}
void RecipientsPicker::setDefaultType(Recipient::Type type)
{
mDefaultType = type;
mUser1Button->setDefault(type == Recipient::To);
mUser2Button->setDefault(type == Recipient::Cc);
mUser3Button->setDefault(type == Recipient::Bcc);
+ mUser4Button->setDefault(type == Recipient::ReplyTo);
}
void RecipientsPicker::slotToClicked()
{
pick(Recipient::To);
}
+void RecipientsPicker::slotReplyToClicked()
+{
+ pick(Recipient::ReplyTo);
+}
+
void RecipientsPicker::slotCcClicked()
{
pick(Recipient::Cc);
}
void RecipientsPicker::slotBccClicked()
{
pick(Recipient::Bcc);
}
void RecipientsPicker::slotPicked()
{
pick(mDefaultType);
}
void RecipientsPicker::pick(Recipient::Type type)
{
qCDebug(MESSAGECOMPOSER_LOG) << int(type);
const Akonadi::EmailAddressSelection::List selections = mView->emailAddressSelectionWidget()->selectedAddresses();
const int count = selections.count();
if (count == 0) {
return;
}
if (count > MessageComposerSettings::self()->maximumRecipients()) {
KMessageBox::sorry(this,
i18np("You selected 1 recipient. The maximum supported number of "
"recipients is %2. Please adapt the selection.",
"You selected %1 recipients. The maximum supported number of "
"recipients is %2. Please adapt the selection.", count,
MessageComposerSettings::self()->maximumRecipients()));
return;
}
bool tooManyAddress = false;
for (const Akonadi::EmailAddressSelection &selection : selections) {
Recipient recipient;
recipient.setType(type);
recipient.setEmail(selection.quotedEmail());
Q_EMIT pickedRecipient(recipient, tooManyAddress);
if (tooManyAddress) {
break;
}
}
}
void RecipientsPicker::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
close();
}
QDialog::keyPressEvent(event);
}
void RecipientsPicker::readConfig()
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
KConfigGroup group(cfg, "RecipientsPicker");
QSize size = group.readEntry("Size", QSize());
if (!size.isEmpty()) {
resize(size);
}
}
void RecipientsPicker::writeConfig()
{
KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
KConfigGroup group(cfg, "RecipientsPicker");
group.writeEntry("Size", size());
}
void RecipientsPicker::slotSearchLDAP()
{
if (!mLdapSearchDialog) {
mLdapSearchDialog = new KLDAP::LdapSearchDialog(this);
connect(mLdapSearchDialog, &KLDAP::LdapSearchDialog::contactsAdded, this, &RecipientsPicker::ldapSearchResult);
}
mLdapSearchDialog->setSearchText(mView->emailAddressSelectionWidget()->searchLineEdit()->text());
mLdapSearchDialog->show();
}
void RecipientsPicker::ldapSearchResult()
{
const KContacts::Addressee::List contacts = mLdapSearchDialog->selectedContacts();
for (const KContacts::Addressee &contact : contacts) {
bool tooManyAddress = false;
Q_EMIT pickedRecipient(Recipient(contact.fullEmail(), Recipient::Undefined), tooManyAddress);
if (tooManyAddress) {
break;
}
}
}
diff --git a/messagecomposer/src/recipient/recipientspicker.h b/messagecomposer/src/recipient/recipientspicker.h
index c52ffa0b..2749073d 100644
--- a/messagecomposer/src/recipient/recipientspicker.h
+++ b/messagecomposer/src/recipient/recipientspicker.h
@@ -1,81 +1,83 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
This file was part of KMail.
Copyright (c) 2005 Cornelius Schumacher <schumacher@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.
*/
#ifndef MESSAGECOMPOSER_RECIPIENTSPICKER_H
#define MESSAGECOMPOSER_RECIPIENTSPICKER_H
#include <MessageComposer/Recipient>
#include <kcontacts/addressee.h>
#include <QDialog>
class QPushButton;
namespace KLDAP {
class LdapSearchDialog;
}
namespace MessageComposer {
class RecipientsPickerWidget;
class RecipientsPicker : public QDialog
{
Q_OBJECT
public:
explicit RecipientsPicker(QWidget *parent);
~RecipientsPicker() override;
void setRecipients(const Recipient::List &);
void setDefaultType(Recipient::Type);
Q_SIGNALS:
void pickedRecipient(const Recipient &, bool &);
protected:
void readConfig();
void writeConfig();
void pick(Recipient::Type);
void keyPressEvent(QKeyEvent *) override;
protected Q_SLOTS:
void slotToClicked();
void slotCcClicked();
void slotBccClicked();
+ void slotReplyToClicked();
void slotPicked();
void slotSearchLDAP();
void ldapSearchResult();
void slotSelectionChanged();
private:
MessageComposer::RecipientsPickerWidget *mView = nullptr;
KLDAP::LdapSearchDialog *mLdapSearchDialog = nullptr;
Recipient::Type mDefaultType;
+ QPushButton *mUser4Button = nullptr;
QPushButton *mUser3Button = nullptr;
QPushButton *mUser2Button = nullptr;
QPushButton *mUser1Button = nullptr;
};
}
#endif
diff --git a/messagecomposer/src/recipient/recipientspickerwidget.cpp b/messagecomposer/src/recipient/recipientspickerwidget.cpp
index 0d4107ac..0eb17116 100644
--- a/messagecomposer/src/recipient/recipientspickerwidget.cpp
+++ b/messagecomposer/src/recipient/recipientspickerwidget.cpp
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "recipientspickerwidget.h"
#include "recipientseditormanager.h"
#include <QHBoxLayout>
#include <QAbstractItemView>
#include <QTreeView>
#include <Akonadi/Contact/EmailAddressSelectionWidget>
#include <Akonadi/Contact/EmailAddressSelectionModel>
#include <Akonadi/Contact/ContactsTreeModel>
using namespace MessageComposer;
RecipientsPickerWidget::RecipientsPickerWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout(this);
- layout->setMargin(0);
+ layout->setContentsMargins(0, 0, 0, 0);
mView = new Akonadi::EmailAddressSelectionWidget(false, MessageComposer::RecipientsEditorManager::self()->model()->model(), this);
layout->addWidget(mView);
mView->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
mView->view()->setAlternatingRowColors(true);
mView->view()->setSortingEnabled(true);
mView->view()->sortByColumn(0, Qt::AscendingOrder);
}
RecipientsPickerWidget::~RecipientsPickerWidget()
{
}
QTreeView *RecipientsPickerWidget::view() const
{
return mView->view();
}
Akonadi::EmailAddressSelectionWidget *RecipientsPickerWidget::emailAddressSelectionWidget() const
{
return mView;
}
diff --git a/messagecomposer/src/recipient/recipientspickerwidget.h b/messagecomposer/src/recipient/recipientspickerwidget.h
index 1821326d..6bfa9a40 100644
--- a/messagecomposer/src/recipient/recipientspickerwidget.h
+++ b/messagecomposer/src/recipient/recipientspickerwidget.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef RECIPIENTSPICKERWIDGET_H
#define RECIPIENTSPICKERWIDGET_H
#include <QWidget>
class QTreeView;
namespace Akonadi {
class EmailAddressSelectionWidget;
}
namespace MessageComposer {
class RecipientsPickerWidget : public QWidget
{
Q_OBJECT
public:
explicit RecipientsPickerWidget(QWidget *parent = nullptr);
~RecipientsPickerWidget();
Q_REQUIRED_RESULT QTreeView *view() const;
Q_REQUIRED_RESULT Akonadi::EmailAddressSelectionWidget *emailAddressSelectionWidget() const;
private:
Akonadi::EmailAddressSelectionWidget *mView = nullptr;
};
}
#endif // RECIPIENTSPICKERWIDGET_H
diff --git a/messagecomposer/src/settings/messagecomposer.kcfg.cmake b/messagecomposer/src/settings/messagecomposer.kcfg.cmake
index ebbbf5ad..c8abc607 100644
--- a/messagecomposer/src/settings/messagecomposer.kcfg.cmake
+++ b/messagecomposer/src/settings/messagecomposer.kcfg.cmake
@@ -1,279 +1,275 @@
<?xml version="1.0" encoding="utf-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name="mailcomposerrc" />
<group name="Composer">
<entry name="CustomMsgIDSuffix" type="String" key="myMessageIdSuffix">
<default></default>
</entry>
<entry name="UseCustomMessageIdSuffix" type="Bool" key="useCustomMessageIdSuffix">
<default>false</default>
</entry>
<entry name="QuoteSelectionOnly" type="Bool">
<label>Only quote selected text when replying</label>
<default>true</default>
</entry>
<entry name="ForceReplyCharset" type="Bool" key="force-reply-charset">
<label>Keep original charset when replying or forwarding if possible</label>
<default>false</default>
</entry>
<entry name="PreferredCharsets" key="pref-charsets" type="StringList">
<default>us-ascii,iso-8859-1,locale,utf-8</default>
</entry>
<entry name="AutoTextSignature" type="String" key="signature">
<label>A&amp;utomatically insert signature</label>
<default>auto</default>
</entry>
<entry name="PrependSignature" type="Bool" key="prepend-signature">
<label>Insert signature above quoted text</label>
<default>false</default>
</entry>
<entry name="DashDashSignature" type="Bool" key="dash-dash-signature">
<label>Prepend separator to signature</label>
<default>true</default>
</entry>
<entry name="ShowBalooSearchInComposer" type="Bool" key="showBalooSearchInComposer">
<label>Use addresses indexed from emails for autocompletion</label>
<whatsthis>Disable this option if you only want contacts from your addressbooks to appear in the autocompletion list in the composer's address fields.</whatsthis>
<default>true</default>
</entry>
<entry name="AllowSemicolonAsAddressSeparator" type="Bool">
<default>${ALLOW_SEMICOLON_AS_ADDRESS_SEPARATOR_DEFAULT}</default>
<label>Allow the semicolon character (';') to be used as separator in the message composer</label>
</entry>
<entry name="ShowRecentAddressesInComposer" type="Bool" key="showRecentAddressesInComposer">
<label>Use recent addresses for autocompletion</label>
<whatsthis>Disable this option if you do not want recently used addresses to appear in the autocompletion list in the composer's address fields.</whatsthis>
<default>true</default>
</entry>
<entry name="MaximumRecipients" type="Int">
<label>Maximum number of recipient entries:</label>
<default>200</default>
</entry>
<entry name="OutlookCompatibleAttachments" type="Bool" key="outlook-compatible-attachments">
<label>Outlook-compatible attachment naming</label>
<whatsthis>Turn this option on to make Outlook &#8482; understand attachment names containing non-English characters</whatsthis>
<default>false</default>
</entry>
<entry name="WordWrap" type="Bool" key="word-wrap">
<label>Word &amp;wrap at column:</label>
<default>true</default>
</entry>
<entry name="LineWrapWidth" type="Int" key="break-at">
<label></label>
<default>78</default>
<min>30</min>
<max>998</max>
</entry>
<entry name="ImprovePlainTextOfHtmlMessage" type="Bool" key="improve-plain-text-html-message">
<label>Improve plain text version of HTML message</label>
<default>true</default>
</entry>
<entry name="CryptoWarningUnencrypted" type="Bool" key="crypto-warning-unencrypted">
<label>Warn before sending unencrypted messages</label>
<default>false</default>
</entry>
<entry name="CryptoWarningUnsigned" type="Bool" key="crypto-warning-unsigned">
<label>Warn before sending unsigned messages</label>
<default>false</default>
</entry>
- <entry name="CryptoWarnRecvNotInCert" type="Bool" key="crypto-warn-recv-not-in-cert">
- <label>Warn if the receiver's address is not in the certificate</label>
- <default>true</default>
- </entry>
<entry name="CryptoWarnWhenNearExpire" type="Bool" key="crypto-warn-when-near-expire">
<label>Warn if certificates/keys expire soon (configure thresholds below)</label>
<default>true</default>
</entry>
<entry name="CryptoWarnSignKeyNearExpiryThresholdDays" type="Int" key="crypto-warn-sign-key-near-expire-int">
<label>The minimum number of days that the signature certificate should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoWarnSignChaincertNearExpiryThresholdDays" type="Int" key="crypto-warn-sign-chaincert-near-expire-int">
<label>The minimum number of days that the CA certificate should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoWarnSignRootNearExpiryThresholdDays" type="Int" key="crypto-warn-sign-root-near-expire-int">
<label>The minimum number of days that the root certificate should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoWarnEncrKeyNearExpiryThresholdDays" type="Int" key="crypto-warn-encr-key-near-expire-int">
<label>The minimum number of days that the encryption certificate should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoWarnEncrChaincertNearExpiryThresholdDays" type="Int" key="crypto-warn-encr-chaincert-near-expire-int">
<label>The minimum number of days that all certificates in the chain should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoWarnEncrRootNearExpiryThresholdDays" type="Int" key="crypto-warn-encr-root-near-expire-int">
<label>The minimum number of days that the root certificate should be valid before issuing a warning</label>
<default>14</default>
</entry>
<entry name="CryptoEncryptToSelf" type="Bool" key="crypto-encrypt-to-self">
<label>When encrypting emails, always also encrypt to the certificate of my own identity</label>
<default>true</default>
</entry>
<entry name="CryptoShowKeysForApproval" type="Bool" key="crypto-show-keys-for-approval">
<label>Always show the list of encryption keys to select the one which will be used</label>
<default>true</default>
</entry>
</group>
<group name="sending mail">
<entry name="SendImmediate" type="Bool" key="Immediate">
<default>true</default>
</entry>
</group>
<group name="Autocorrect">
<entry name="Enabled" type="Bool" key="enabled">
<default>false</default>
</entry>
<entry name="UppercaseFirstCharOfSentence" type="Bool" key="upper-case-first-char-of-sentence">
<default>false</default>
</entry>
<entry name="FixTwoUppercaseChars" type="Bool" key="fix-two-upper-case-chars">
<default>false</default>
</entry>
<entry name="SingleSpaces" type="Bool" key="single-spaces">
<default>false</default>
</entry>
<entry name="AutoFractions" type="Bool" key="auto-fractions">
<default>false</default>
</entry>
<entry name="CapitalizeWeekDays" type="Bool" key="capitalize-week-days">
<default>false</default>
</entry>
<entry name="AdvancedAutocorrect" type="Bool" key="advanced-autocorrect">
<default>false</default>
</entry>
<entry name="ReplaceDoubleQuotes" type="Bool" key="replace-double-quotes">
<default>false</default>
</entry>
<entry name="ReplaceSingleQuotes" type="Bool" key="replace-single-quotes">
<default>false</default>
</entry>
<entry name="AutoFormatUrl" type="Bool" key="autoformat-url">
<default>false</default>
</entry>
<entry name="AutoBoldUnderline" type="Bool" key="auto-bold-underline">
<default>false</default>
</entry>
<entry name="SuperScript" type="Bool" key="super-script">
<default>false</default>
</entry>
<entry name="AddNonBreakingSpaceInFrench" type="Bool" key="add-non-breaking-space">
<default>false</default>
</entry>
</group>
<group name="AutoResizeImage">
<entry name="AutoResizeImageEnabled" type="Bool" key="auto-resize-image-enabled">
<default>false</default>
</entry>
<entry name="KeepImageRatio" type="Bool" key="keep-image-ratio">
<default>false</default>
</entry>
<entry name="AskBeforeResizing" type="Bool" key="ask-before-resizing">
<default>true</default>
</entry>
<entry name="ReduceImageToMaximum" type="Bool" key="reduce-image-to-maximum">
<default>false</default>
</entry>
<entry name="EnlargeImageToMinimum" type="Bool" key="enlarge-image-to-minimum">
<default>false</default>
</entry>
<entry name="CustomMinimumHeight" type="Int" key="custom-minimum-height">
<default>520</default>
</entry>
<entry name="CustomMinimumWidth" type="Int" key="custom-minimum-width">
<default>520</default>
</entry>
<entry name="CustomMaximumHeight" type="Int" key="custom-maximum-height">
<default>520</default>
</entry>
<entry name="CustomMaximumWidth" type="Int" key="custom-maximum-width">
<default>520</default>
</entry>
<entry name="MaximumHeight" type="Int" key="maximum-height">
<default>0</default>
</entry>
<entry name="MaximumWidth" type="Int" key="maximum-width">
<default>0</default>
</entry>
<entry name="MinimumHeight" type="Int" key="minimum-height">
<default>0</default>
</entry>
<entry name="MinimumWidth" type="Int" key="minimum-width">
<default>0</default>
</entry>
<entry name="WriteFormat" type="String" key="write-format">
<default></default>
</entry>
<entry name="SkipImageLowerSizeEnabled" type="Bool" key="skip-image-lower-size-enabled">
<default>false</default>
</entry>
<entry name="SkipImageLowerSize" type="Int" key="skip-image-lower-size">
<default>220</default>
</entry>
<entry name="FilterSourcePattern" type="String" key="filter-source-pattern">
<default></default>
</entry>
<entry name="FilterSourceType" type="Enum">
<label></label>
<choices>
<choice name="NoFilter"/>
<choice name="IncludeFilesWithPattern"/>
<choice name="ExcludeFilesWithPattern"/>
</choices>
<default>NoFilter</default>
</entry>
<entry name="RenameResizedImages" type="Bool" key="rename-resized-images">
<default>false</default>
</entry>
<entry name="RenameResizedImagesPattern" type="String" key="rename-resized-images-pattern">
<default></default>
</entry>
<entry name="FilterRecipientType" type="Enum">
<label></label>
<choices>
<choice name="NoFilter"/>
<choice name="ResizeEachEmailsContainsPattern"/>
<choice name="ResizeOneEmailContainsPattern"/>
<choice name="DontResizeEachEmailsContainsPattern"/>
<choice name="DontResizeOneEmailContainsPattern"/>
</choices>
<default>NoFilter</default>
</entry>
<entry name="DoNotResizeEmailsPattern" type="String" key="do-not-resize-emails-pattern">
<default></default>
</entry>
<entry name="ResizeEmailsPattern" type="String" key="resize-emails-pattern">
<default></default>
</entry>
<entry name="ResizeImagesWithFormats" type="Bool" key="resize-image-with-formats">
<default>false</default>
</entry>
<entry name="ResizeImagesWithFormatsType" type="String" key="resize-image-with-formats-type">
<default></default>
</entry>
</group>
</kcfg>
diff --git a/messagecomposer/src/settings/messagecomposersettings.cpp b/messagecomposer/src/settings/messagecomposersettings.cpp
index 8a417a0f..3983c100 100644
--- a/messagecomposer/src/settings/messagecomposersettings.cpp
+++ b/messagecomposer/src/settings/messagecomposersettings.cpp
@@ -1,62 +1,63 @@
/*
This file is part of KMail.
Copyright (c) 2005 David Faure <faure@kde.org>
- This program 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.
+ 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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#include "messagecomposersettings.h"
#include <QTimer>
using namespace MessageComposer;
MessageComposerSettings *MessageComposerSettings::mSelf = nullptr;
MessageComposerSettings *MessageComposerSettings::self()
{
if (!mSelf) {
mSelf = new MessageComposerSettings();
mSelf->load();
}
return mSelf;
}
MessageComposerSettings::MessageComposerSettings()
{
mConfigSyncTimer = new QTimer(this);
mConfigSyncTimer->setSingleShot(true);
connect(mConfigSyncTimer, &QTimer::timeout, this, &MessageComposerSettings::slotSyncNow);
}
void MessageComposerSettings::requestSync()
{
if (!mConfigSyncTimer->isActive()) {
mConfigSyncTimer->start(0);
}
}
void MessageComposerSettings::slotSyncNow()
{
config()->sync();
}
MessageComposerSettings::~MessageComposerSettings()
{
}
diff --git a/messagecomposer/src/settings/messagecomposersettings.h b/messagecomposer/src/settings/messagecomposersettings.h
index 9a7e807d..e4b2324f 100644
--- a/messagecomposer/src/settings/messagecomposersettings.h
+++ b/messagecomposer/src/settings/messagecomposersettings.h
@@ -1,58 +1,59 @@
/*
This file is part of KMail.
Copyright (c) 2005 David Faure <faure@kde.org>
- This program 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.
+ 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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#ifndef MESSAGECOMPOSERSETTINGS_H
#define MESSAGECOMPOSERSETTINGS_H
#include "messagecomposer_export.h"
#include "messagecomposersettings_base.h"
class QTimer;
namespace MessageComposer {
class MESSAGECOMPOSER_EXPORT MessageComposerSettings : public MessageComposer::MessageComposerSettingsBase
{
Q_OBJECT
public:
static MessageComposerSettings *self();
/** Call this slot instead of directly @ref KConfig::sync() to
minimize the overall config writes. Calling this slot will
schedule a sync of the application config file using a timer, so
that many consecutive calls can be condensed into a single
sync, which is more efficient. */
void requestSync();
private Q_SLOTS:
void slotSyncNow();
private:
MessageComposerSettings();
~MessageComposerSettings() override;
static MessageComposerSettings *mSelf;
QTimer *mConfigSyncTimer = nullptr;
};
}
#endif /* MESSAGECOMPOSER_GLOBALSETTINGS_H */
diff --git a/messagecomposer/src/utils/kleo_util.h b/messagecomposer/src/utils/kleo_util.h
index bfc84d3e..7ce02c0e 100644
--- a/messagecomposer/src/utils/kleo_util.h
+++ b/messagecomposer/src/utils/kleo_util.h
@@ -1,95 +1,96 @@
/* -*- c++ -*-
kleo_util.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2004 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef KDEPIM_KMAIL_KLEO_UTIL_H
#define KDEPIM_KMAIL_KLEO_UTIL_H
#include "libkleo/enum.h"
static const Kleo::CryptoMessageFormat cryptoMessageFormats[] = {
Kleo::AutoFormat,
Kleo::InlineOpenPGPFormat,
Kleo::OpenPGPMIMEFormat,
Kleo::SMIMEFormat,
Kleo::SMIMEOpaqueFormat
};
static const int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats;
static const Kleo::CryptoMessageFormat concreteCryptoMessageFormats[] = {
Kleo::OpenPGPMIMEFormat,
Kleo::SMIMEFormat,
Kleo::SMIMEOpaqueFormat,
Kleo::InlineOpenPGPFormat
};
static const unsigned int numConcreteCryptoMessageFormats
= sizeof concreteCryptoMessageFormats / sizeof *concreteCryptoMessageFormats;
static inline Kleo::CryptoMessageFormat cb2format(int idx)
{
return cryptoMessageFormats[ idx >= 0 && idx < numCryptoMessageFormats ? idx : 0 ];
}
static inline int format2cb(Kleo::CryptoMessageFormat f)
{
for (int i = 0; i < numCryptoMessageFormats; ++i) {
if (f == cryptoMessageFormats[i]) {
return i;
}
}
return 0;
}
//
// some helper functions indicating the need for CryptoMessageFormat
// to be a class type :)
//
static inline bool isSMIME(Kleo::CryptoMessageFormat f)
{
return f == Kleo::SMIMEFormat || f == Kleo::SMIMEOpaqueFormat;
}
static inline bool isOpenPGP(Kleo::CryptoMessageFormat f)
{
return f == Kleo::InlineOpenPGPFormat || f == Kleo::OpenPGPMIMEFormat;
}
static inline bool containsSMIME(unsigned int f)
{
return f & (Kleo::SMIMEFormat | Kleo::SMIMEOpaqueFormat);
}
static inline bool containsOpenPGP(unsigned int f)
{
return f & (Kleo::OpenPGPMIMEFormat | Kleo::InlineOpenPGPFormat);
}
#endif // KDEPIM_KMAIL_KLEO_UTIL_H
diff --git a/messagecomposer/src/utils/util.cpp b/messagecomposer/src/utils/util.cpp
index 36b4c5d3..3ebb1ef3 100644
--- a/messagecomposer/src/utils/util.cpp
+++ b/messagecomposer/src/utils/util.cpp
@@ -1,459 +1,459 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>
Parts based on KMail code by:
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "utils/util.h"
#include "util_p.h"
#include <QRegularExpression>
#include "composer/composer.h"
#include "job/singlepartjob.h"
#include <QTextCodec>
#include <QTextDocument>
#include <QTextBlock>
#include <KCharsets>
#include "messagecomposer_debug.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <kmime/kmime_content.h>
#include <kmime/kmime_headers.h>
#include <mailtransportakonadi/messagequeuejob.h>
#include <AkonadiCore/agentinstance.h>
#include <AkonadiCore/agentinstancecreatejob.h>
#include <AkonadiCore/agentmanager.h>
#include <MessageCore/StringUtil>
KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret)
{
MessageComposer::Composer composer;
MessageComposer::SinglepartJob cteJob(&composer);
cteJob.contentType()->setMimeType(contentType->mimeType());
cteJob.contentType()->setCharset(contentType->charset());
cteJob.setData(encodedBody);
cteJob.exec();
cteJob.content()->assemble();
ret->contentTransferEncoding()->setEncoding(cteJob.contentTransferEncoding()->encoding());
ret->setBody(cteJob.content()->encodedBody());
return ret;
}
KMime::Content *MessageComposer::Util::composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, Kleo::CryptoMessageFormat format, bool sign, const QByteArray &hashAlgo)
{
KMime::Content *result = new KMime::Content;
// called should have tested that the signing/encryption failed
Q_ASSERT(!encodedBody.isEmpty());
if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message
qCDebug(MESSAGECOMPOSER_LOG) << "making MIME message, format:" << format;
makeToplevelContentType(result, format, sign, hashAlgo);
if (makeMultiMime(format, sign)) { // sign/enc PGPMime, sign SMIME
const QByteArray boundary = KMime::multiPartBoundary();
result->contentType()->setBoundary(boundary);
result->assemble();
//qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
// Build the encapsulated MIME parts.
// Build a MIME part holding the code information
// taking the body contents returned in ciphertext.
KMime::Content *code = new KMime::Content;
setNestedContentType(code, format, sign);
setNestedContentDisposition(code, format, sign);
if (sign) { // sign PGPMime, sign SMIME
if (format & Kleo::AnySMIME) { // sign SMIME
code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
code->contentTransferEncoding()->needToEncode();
code->setBody(encodedBody);
} else { // sign PGPMmime
setBodyAndCTE(encodedBody, orig->contentType(), code);
}
result->addContent(orig);
result->addContent(code);
} else { // enc PGPMime
setBodyAndCTE(encodedBody, orig->contentType(), code);
// Build a MIME part holding the version information
// taking the body contents returned in
// structuring.data.bodyTextVersion.
KMime::Content *vers = new KMime::Content;
vers->contentType()->setMimeType("application/pgp-encrypted");
vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
vers->setBody("Version: 1");
result->addContent(vers);
result->addContent(code);
}
} else { //enc SMIME, sign/enc SMIMEOpaque
result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
result->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
result->contentDisposition()->setFilename(QStringLiteral("smime.p7m"));
result->assemble();
//qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
result->setBody(encodedBody);
}
} else { // sign/enc PGPInline
result->setHead(orig->head());
result->parse();
// fixing ContentTransferEncoding
setBodyAndCTE(encodedBody, orig->contentType(), result);
}
return result;
}
// set the correct top-level ContentType on the message
void MessageComposer::Util::makeToplevelContentType(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign, const QByteArray &hashAlgo)
{
switch (format) {
default:
case Kleo::InlineOpenPGPFormat:
case Kleo::OpenPGPMIMEFormat:
if (sign) {
content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
- content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pgp-signature"));
+ content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower());
} else {
content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted"));
- content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pgp-encrypted"));
+ content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
}
return;
case Kleo::SMIMEFormat:
if (sign) {
qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME";
content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
- content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromLatin1("application/pkcs7-signature"));
+ content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pkcs7-signature"));
content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(hashAlgo).toLower());
return;
}
// fall through (for encryption, there's no difference between
// SMIME and SMIMEOpaque, since there is no mp/encrypted for
// S/MIME)
Q_FALLTHROUGH();
case Kleo::SMIMEOpaqueFormat:
qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque";
content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime"));
if (sign) {
- content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromLatin1("signed-data"));
+ content->contentType()->setParameter(QStringLiteral("smime-type"), QStringLiteral("signed-data"));
} else {
- content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromLatin1("enveloped-data"));
+ content->contentType()->setParameter(QStringLiteral("smime-type"), QStringLiteral("enveloped-data"));
}
- content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("smime.p7m"));
+ content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("smime.p7m"));
}
}
void MessageComposer::Util::setNestedContentType(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign)
{
switch (format) {
case Kleo::OpenPGPMIMEFormat:
if (sign) {
content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature"));
- content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc"));
+ content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("signature.asc"));
content->contentDescription()->from7BitString("This is a digitally signed message part.");
} else {
content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream"));
}
return;
case Kleo::SMIMEFormat:
if (sign) {
content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature"));
- content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("smime.p7s"));
+ content->contentType()->setParameter(QStringLiteral("name"), QStringLiteral("smime.p7s"));
return;
}
Q_FALLTHROUGH();
// fall through:
default:
case Kleo::InlineOpenPGPFormat:
case Kleo::SMIMEOpaqueFormat:
;
}
}
void MessageComposer::Util::setNestedContentDisposition(KMime::Content *content, Kleo::CryptoMessageFormat format, bool sign)
{
if (!sign && format & Kleo::OpenPGPMIMEFormat) {
content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
content->contentDisposition()->setFilename(QStringLiteral("msg.asc"));
} else if (sign && format & Kleo::SMIMEFormat) {
content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
content->contentDisposition()->setFilename(QStringLiteral("smime.p7s"));
}
}
bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign)
{
switch (format) {
default:
case Kleo::InlineOpenPGPFormat:
case Kleo::SMIMEOpaqueFormat:
return false;
case Kleo::OpenPGPMIMEFormat:
return true;
case Kleo::SMIMEFormat:
return sign; // only on sign - there's no mp/encrypted for S/MIME
}
}
QByteArray MessageComposer::Util::selectCharset(const QList<QByteArray> &charsets, const QString &text)
{
for (const QByteArray &name : charsets) {
// We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because
// the former knows us-ascii is latin1.
QTextCodec *codec = KCharsets::charsets()->codecForName(QString::fromLatin1(name));
if (!codec) {
qCWarning(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << name;
continue;
}
if (codec->canEncode(text)) {
// Special check for us-ascii (needed because us-ascii is not exactly latin1).
if (name == "us-ascii" && !KMime::isUsAscii(text)) {
continue;
}
qCDebug(MESSAGECOMPOSER_LOG) << "Chosen charset" << name;
return name;
}
}
qCDebug(MESSAGECOMPOSER_LOG) << "No appropriate charset found.";
return QByteArray();
}
QStringList MessageComposer::Util::AttachmentKeywords()
{
return i18nc(
"comma-separated list of keywords that are used to detect whether "
"the user forgot to attach his attachment. Do not add space between words.",
"attachment,attached").split(QLatin1Char(','));
}
QString MessageComposer::Util::cleanedUpHeaderString(const QString &s)
{
// remove invalid characters from the header strings
QString res(s);
res.remove(QChar::fromLatin1('\r'));
res.replace(QChar::fromLatin1('\n'), QLatin1Char(' '));
return res.trimmed();
}
void MessageComposer::Util::addSendReplyForwardAction(const KMime::Message::Ptr &message, MailTransport::MessageQueueJob *qjob)
{
QList<Akonadi::Item::Id> originalMessageId;
QList<Akonadi::MessageStatus> linkStatus;
if (MessageComposer::Util::getLinkInformation(message, originalMessageId, linkStatus)) {
for (Akonadi::Item::Id id : qAsConst(originalMessageId)) {
if (linkStatus.first() == Akonadi::MessageStatus::statusReplied()) {
qjob->sentActionAttribute().addAction(MailTransport::SentActionAttribute::Action::MarkAsReplied, QVariant(id));
} else if (linkStatus.first() == Akonadi::MessageStatus::statusForwarded()) {
qjob->sentActionAttribute().addAction(MailTransport::SentActionAttribute::Action::MarkAsForwarded, QVariant(id));
}
}
}
}
bool MessageComposer::Util::sendMailDispatcherIsOnline(QWidget *parent)
{
Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(QStringLiteral("akonadi_maildispatcher_agent"));
if (!instance.isValid()) {
const int rc = KMessageBox::warningYesNo(parent, i18n("The mail dispatcher is not set up, so mails cannot be sent. Do you want to create a mail dispatcher?"),
i18n("No mail dispatcher."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("no_maildispatcher"));
if (rc == KMessageBox::Yes) {
const Akonadi::AgentType type = Akonadi::AgentManager::self()->type(QStringLiteral("akonadi_maildispatcher_agent"));
Q_ASSERT(type.isValid());
Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob(type); // async. We'll have to try again later.
job->start();
}
return false;
}
if (instance.isOnline()) {
return true;
} else {
const int rc = KMessageBox::warningYesNo(parent, i18n("The mail dispatcher is offline, so mails cannot be sent. Do you want to make it online?"),
i18n("Mail dispatcher offline."), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("maildispatcher_put_online"));
if (rc == KMessageBox::Yes) {
instance.setIsOnline(true);
return true;
}
}
return false;
}
void MessageComposer::Util::removeNotNecessaryHeaders(const KMime::Message::Ptr &msg)
{
msg->removeHeader("X-KMail-SignatureActionEnabled");
msg->removeHeader("X-KMail-EncryptActionEnabled");
msg->removeHeader("X-KMail-CryptoMessageFormat");
}
KMime::Content *MessageComposer::Util::findTypeInMessage(KMime::Content *data, const QByteArray &mimeType, const QByteArray &subType)
{
if (!data->contentType()->isEmpty()) {
if (mimeType.isEmpty() || subType.isEmpty()) {
return data;
}
if ((mimeType == data->contentType()->mediaType())
&& (subType == data->contentType()->subType())) {
return data;
}
}
foreach (auto child, data->contents()) {
if ((!child->contentType()->isEmpty())
&& (mimeType == child->contentType()->mimeType())
&& (subType == child->contentType()->subType())) {
return child;
}
auto ret = findTypeInMessage(child, mimeType, subType);
if (ret) {
return ret;
}
}
return nullptr;
}
void MessageComposer::Util::addLinkInformation(const KMime::Message::Ptr &msg, Akonadi::Item::Id id, Akonadi::MessageStatus status)
{
Q_ASSERT(status.isReplied() || status.isForwarded() || status.isDeleted());
QString message;
if (auto hrd = msg->headerByType("X-KMail-Link-Message")) {
message = hrd->asUnicodeString();
}
if (!message.isEmpty()) {
message += QChar::fromLatin1(',');
}
QString type;
if (auto hrd = msg->headerByType("X-KMail-Link-Type")) {
type = hrd->asUnicodeString();
}
if (!type.isEmpty()) {
type += QChar::fromLatin1(',');
}
message += QString::number(id);
if (status.isReplied()) {
type += QLatin1String("reply");
} else if (status.isForwarded()) {
type += QLatin1String("forward");
}
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Link-Message");
header->fromUnicodeString(message, "utf-8");
msg->setHeader(header);
header = new KMime::Headers::Generic("X-KMail-Link-Type");
header->fromUnicodeString(type, "utf-8");
msg->setHeader(header);
}
bool MessageComposer::Util::getLinkInformation(const KMime::Message::Ptr &msg, QList<Akonadi::Item::Id> &id, QList<Akonadi::MessageStatus> &status)
{
auto hrdLinkMsg = msg->headerByType("X-KMail-Link-Message");
auto hrdLinkType = msg->headerByType("X-KMail-Link-Type");
if (!hrdLinkMsg || !hrdLinkType) {
return false;
}
const QStringList messages = hrdLinkMsg->asUnicodeString().split(QLatin1Char(','), QString::SkipEmptyParts);
const QStringList types = hrdLinkType->asUnicodeString().split(QLatin1Char(','), QString::SkipEmptyParts);
if (messages.isEmpty() || types.isEmpty()) {
return false;
}
for (const QString &idStr : messages) {
id << idStr.toLongLong();
}
for (const QString &typeStr : types) {
if (typeStr == QLatin1String("reply")) {
status << Akonadi::MessageStatus::statusReplied();
} else if (typeStr == QLatin1String("forward")) {
status << Akonadi::MessageStatus::statusForwarded();
}
}
return true;
}
bool MessageComposer::Util::isStandaloneMessage(const Akonadi::Item &item)
{
// standalone message have a valid payload, but are not, themselves valid items
return item.hasPayload<KMime::Message::Ptr>() && !item.isValid();
}
KMime::Message::Ptr MessageComposer::Util::message(const Akonadi::Item &item)
{
if (!item.hasPayload<KMime::Message::Ptr>()) {
qCWarning(MESSAGECOMPOSER_LOG) << "Payload is not a MessagePtr!";
return KMime::Message::Ptr();
}
return item.payload<KMime::Message::Ptr>();
}
bool MessageComposer::Util::hasMissingAttachments(const QStringList &attachmentKeywords, QTextDocument *doc, const QString &subj)
{
if (!doc) {
return false;
}
QStringList attachWordsList = attachmentKeywords;
QRegularExpression rx(QLatin1String("\\b")
+attachWordsList.join(QStringLiteral("\\b|\\b"))
+QLatin1String("\\b"), QRegularExpression::CaseInsensitiveOption);
// check whether the subject contains one of the attachment key words
// unless the message is a reply or a forwarded message
bool gotMatch = (MessageCore::StringUtil::stripOffPrefixes(subj) == subj) && (rx.match(subj).hasMatch());
if (!gotMatch) {
// check whether the non-quoted text contains one of the attachment key
// words
QRegularExpression quotationRx(QStringLiteral("^([ \\t]*([|>:}#]|[A-Za-z]+>))+"));
QTextBlock end(doc->end());
for (QTextBlock it = doc->begin(); it != end; it = it.next()) {
const QString line = it.text();
gotMatch = (!quotationRx.match(line).hasMatch())
&& (rx.match(line).hasMatch());
if (gotMatch) {
break;
}
}
}
if (!gotMatch) {
return false;
}
return true;
}
diff --git a/messagecomposer/tests/recipientseditortest.cpp b/messagecomposer/tests/recipientseditortest.cpp
index 3ed51e5e..cfc9fb79 100644
--- a/messagecomposer/tests/recipientseditortest.cpp
+++ b/messagecomposer/tests/recipientseditortest.cpp
@@ -1,110 +1,110 @@
/*
This file is part of KMail.
Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#include "recipientseditortest.h"
#include <MessageComposer/RecipientsEditor>
-#include <qdebug.h>
+#include <QDebug>
#include <kmessagebox.h>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QGridLayout>
#include <QApplication>
#include <QCommandLineParser>
using namespace MessageComposer;
Composer::Composer(QWidget *parent)
: QWidget(parent)
{
QGridLayout *topLayout = new QGridLayout(this);
- topLayout->setMargin(4);
+ topLayout->setContentsMargins(4, 4, 4, 4);
topLayout->setSpacing(4);
QLabel *label = new QLabel(QStringLiteral("From:"), this);
topLayout->addWidget(label, 0, 0);
QLineEdit *edit = new QLineEdit(this);
topLayout->addWidget(edit, 0, 1);
mRecipients = new RecipientsEditor(this);
topLayout->addWidget(mRecipients, 1, 0, 1, 2);
qDebug() << "SIZEHINT:" << mRecipients->sizeHint();
// mRecipients->setFixedHeight( 10 );
QTextEdit *editor = new QTextEdit(this);
topLayout->addWidget(editor, 2, 0, 1, 2);
topLayout->setRowStretch(2, 1);
QPushButton *button = new QPushButton(QStringLiteral("&Close"), this);
topLayout->addWidget(button, 3, 0, 1, 2);
connect(button, &QPushButton::clicked, this, &Composer::slotClose);
}
void Composer::slotClose()
{
#if 0
QString text;
text += "<qt>";
Recipient::List recipients = mRecipients->recipients();
Recipient::List::ConstIterator it;
for (it = recipients.begin(); it != recipients.end(); ++it) {
text += "<b>" + (*it).typeLabel() + ":</b> " + (*it).email() + "<br/>";
}
text += "</qt>";
KMessageBox::information(this, text);
#endif
close();
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
QWidget *wid = new Composer(nullptr);
wid->show();
int ret = app.exec();
delete wid;
return ret;
}
diff --git a/messagecomposer/tests/testcomposerlineedit.cpp b/messagecomposer/tests/testcomposerlineedit.cpp
index 506c303f..a2a9e968 100644
--- a/messagecomposer/tests/testcomposerlineedit.cpp
+++ b/messagecomposer/tests/testcomposerlineedit.cpp
@@ -1,52 +1,52 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#include <QWidget>
#include <QVBoxLayout>
#include <klocalizedstring.h>
#include <QApplication>
#include <QCommandLineParser>
#include "composer/composerlineedit.h"
using namespace MessageComposer;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
QWidget *w = new QWidget;
QVBoxLayout *vbox = new QVBoxLayout(w);
ComposerLineEdit *kale1 = new ComposerLineEdit(0);
// Add menu for completion
kale1->enableCompletion(true);
vbox->addWidget(kale1);
ComposerLineEdit *kale2 = new ComposerLineEdit(0);
vbox->addWidget(kale2);
vbox->addStretch();
w->resize(400, 400);
w->show();
return app.exec();
}
diff --git a/messagecore/autotests/attachmentcompressjobtest.cpp b/messagecore/autotests/attachmentcompressjobtest.cpp
index d25b3b85..57e0a27e 100644
--- a/messagecore/autotests/attachmentcompressjobtest.cpp
+++ b/messagecore/autotests/attachmentcompressjobtest.cpp
@@ -1,104 +1,104 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentcompressjobtest.h"
#include "qtest_messagecore.h"
#include <QBuffer>
#include "messagecore_debug.h"
#include <KZip>
-#include <qtest.h>
+#include <QTest>
#include <MessageCore/AttachmentCompressJob>
using namespace MessageCore;
QTEST_MAIN(AttachmentCompressJobTest)
void AttachmentCompressJobTest::testCompress()
{
// Some data.
QByteArray data;
for (int i = 0; i < 100; ++i) {
data += "This is some highly compressible text...\n";
}
const QString name = QStringLiteral("name");
const QString fileName = QStringLiteral("name.txt");
const QString description = QStringLiteral("description");
// Create the original part.
AttachmentPart::Ptr origPart = AttachmentPart::Ptr(new AttachmentPart);
origPart->setName(name);
origPart->setFileName(fileName);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
QVERIFY(!origPart->isAutoEncoding());
origPart->setData(data);
QVERIFY(!origPart->isCompressed());
// Compress the part and verify it.
AttachmentCompressJob *cjob = new AttachmentCompressJob(origPart, this);
VERIFYEXEC(cjob);
QCOMPARE(cjob->originalPart(), origPart);
AttachmentPart::Ptr zipPart = cjob->compressedPart();
//qCDebug(MESSAGECORE_LOG) << data;
//qCDebug(MESSAGECORE_LOG) << zipPart->data();
QVERIFY(zipPart->isAutoEncoding());
QVERIFY(zipPart->isCompressed());
QCOMPARE(zipPart->name(), QString(name + QString::fromLatin1(".zip")));
QCOMPARE(zipPart->fileName(), QString(fileName + QString::fromLatin1(".zip")));
QCOMPARE(zipPart->description(), description);
QCOMPARE(zipPart->mimeType(), QByteArrayLiteral("application/zip"));
// Uncompress the data and verify it.
// (Stuff below is stolen from KMail code.)
QByteArray zipData = zipPart->data();
QBuffer buffer(&zipData);
KZip zip(&buffer);
QVERIFY(zip.open(QIODevice::ReadOnly));
const KArchiveDirectory *dir = zip.directory();
QCOMPARE(dir->entries().count(), 1);
const KZipFileEntry *entry = (KZipFileEntry *)dir->entry(dir->entries().at(0));
QCOMPARE(entry->data(), data);
QCOMPARE(entry->name(), name);
zip.close();
}
void AttachmentCompressJobTest::testCompressedSizeLarger()
{
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
AttachmentPart::Ptr origPart = AttachmentPart::Ptr(new AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
QVERIFY(!origPart->isAutoEncoding());
origPart->setData(data);
QVERIFY(!origPart->isCompressed());
// Compress the part and verify that it is aware of its folly.
AttachmentCompressJob *cjob = new AttachmentCompressJob(origPart, this);
VERIFYEXEC(cjob);
QVERIFY(cjob->isCompressedPartLarger());
}
diff --git a/messagecore/autotests/attachmentfrommimecontentjobtest.cpp b/messagecore/autotests/attachmentfrommimecontentjobtest.cpp
index 6e6b0045..9431169a 100644
--- a/messagecore/autotests/attachmentfrommimecontentjobtest.cpp
+++ b/messagecore/autotests/attachmentfrommimecontentjobtest.cpp
@@ -1,76 +1,76 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentfrommimecontentjobtest.h"
#include "qtest_messagecore.h"
#include "messagecore_debug.h"
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
#include <kmime/kmime_headers.h>
using namespace KMime;
-#include <MessageCore/AttachmentFromMimeContentJob>
+#include "attachment/attachmentfrommimecontentjob.h"
using namespace MessageCore;
QTEST_MAIN(AttachmentFromMimeContentJobTest)
void AttachmentFromMimeContentJobTest::testAttachment()
{
const QByteArray mimeType("x-some/x-type");
const QString name = QStringLiteral("name abcd");
const QString description = QStringLiteral("description");
const QByteArray charset("utf-8");
const QString fileName = QStringLiteral("filename abcd");
const Headers::contentEncoding encoding = Headers::CEquPr;
const Headers::contentDisposition disposition = Headers::CDinline;
const QByteArray data("ocean soul");
Content *content = new Content;
content->contentType()->setMimeType(mimeType);
content->contentType()->setName(name, charset);
content->contentType()->setCharset(charset);
content->contentTransferEncoding()->setEncoding(encoding);
content->contentDisposition()->setDisposition(disposition);
content->contentDisposition()->setFilename(fileName);
content->contentDescription()->fromUnicodeString(description, charset);
content->setBody(data);
content->assemble();
//qCDebug(MESSAGECORE_LOG) << "Encoded content:" << content->encodedContent();
//qCDebug(MESSAGECORE_LOG) << "Decoded content:" << content->decodedContent();
AttachmentFromMimeContentJob *job = new AttachmentFromMimeContentJob(content, this);
QVERIFY(job->uiDelegate() == nullptr); // No GUI thankyouverymuch.
VERIFYEXEC(job);
delete content;
content = nullptr;
AttachmentPart::Ptr part = job->attachmentPart();
delete job;
job = nullptr;
QCOMPARE(part->mimeType(), mimeType);
QCOMPARE(part->name(), name);
QCOMPARE(part->description(), description);
//QCOMPARE( part->charset(), charset ); // TODO will probably need charsets in AttachmentPart :(
QCOMPARE(part->fileName(), fileName);
QVERIFY(part->encoding() == encoding);
QVERIFY(part->isInline());
QCOMPARE(part->data(), data);
}
diff --git a/messagecore/autotests/attachmentfromurljobtest.cpp b/messagecore/autotests/attachmentfromurljobtest.cpp
index f92e6f57..ac32eeee 100644
--- a/messagecore/autotests/attachmentfromurljobtest.cpp
+++ b/messagecore/autotests/attachmentfromurljobtest.cpp
@@ -1,105 +1,105 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentfromurljobtest.h"
#include "qtest_messagecore.h"
-#include <qtest.h>
+#include <QTest>
-#include <MessageCore/AttachmentFromUrlJob>
+#include "attachment/attachmentfromurljob.h"
#include <MessageCore/StringUtil>
using namespace MessageCore;
QTEST_MAIN(AttachmentFromUrlJobTest)
#define PATH_ATTACHMENTS QLatin1String(KDESRCDIR "/attachments/")
void AttachmentFromUrlJobTest::initTestCase()
{
qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher
}
void AttachmentFromUrlJobTest::testAttachments_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QString>("filename");
QTest::addColumn<QByteArray>("mimetype");
// PATH_ATTACHMENTS is defined by CMake.
- QTest::newRow("png image") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("image.png"))
- << QString::fromLatin1("image.png")
+ QTest::newRow("png image") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("image.png"))
+ << QStringLiteral("image.png")
<< QByteArray("image/png");
- QTest::newRow("pdf doc") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("doc.pdf"))
- << QString::fromLatin1("doc.pdf")
+ QTest::newRow("pdf doc") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("doc.pdf"))
+ << QStringLiteral("doc.pdf")
<< QByteArray("application/pdf");
- QTest::newRow("text file") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"))
- << QString::fromLatin1("file.txt")
+ QTest::newRow("text file") << QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"))
+ << QStringLiteral("file.txt")
<< QByteArray("text/plain");
}
void AttachmentFromUrlJobTest::testAttachments()
{
QFETCH(QUrl, url);
QFETCH(QString, filename);
QFETCH(QByteArray, mimetype);
QFile file(url.path());
QVERIFY(file.open(QIODevice::ReadOnly));
QByteArray data = file.readAll();
file.close();
AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this);
VERIFYEXEC(ljob);
AttachmentPart::Ptr part = ljob->attachmentPart();
delete ljob;
ljob = nullptr;
QCOMPARE(part->name(), filename);
QCOMPARE(part->fileName(), filename);
QVERIFY(!part->isInline());
QCOMPARE(part->mimeType(), mimetype);
QCOMPARE(part->data(), data);
}
void AttachmentFromUrlJobTest::testAttachmentTooBig()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("doc.pdf"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("doc.pdf"));
AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this);
ljob->setMaximumAllowedSize(1024); // 1KiB, whereas the file is >9KiB.
QVERIFY(!ljob->exec());
}
void AttachmentFromUrlJobTest::testAttachmentCharset()
{
const QByteArray charset("iso-8859-2");
- const QString filename = QString::fromLatin1("file.txt");
+ const QString filename = QStringLiteral("file.txt");
QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + filename);
MessageCore::StringUtil::setEncodingFile(url, QString::fromLatin1(charset));
AttachmentFromUrlJob *ljob = new AttachmentFromUrlJob(url, this);
VERIFYEXEC(ljob);
AttachmentPart::Ptr part = ljob->attachmentPart();
delete ljob;
ljob = nullptr;
QCOMPARE(part->name(), filename);
QCOMPARE(part->fileName(), filename);
QCOMPARE(part->charset(), charset);
}
diff --git a/messagecore/autotests/attachmentparttest.cpp b/messagecore/autotests/attachmentparttest.cpp
index dc23ec41..39cd21ed 100644
--- a/messagecore/autotests/attachmentparttest.cpp
+++ b/messagecore/autotests/attachmentparttest.cpp
@@ -1,67 +1,67 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentparttest.h"
#include "qtest_messagecore.h"
#include <QHash>
#include "messagecore_debug.h"
-#include <qtest.h>
+#include <QTest>
#include <MessageCore/AttachmentPart>
using namespace MessageCore;
QTEST_MAIN(AttachmentPartTest)
void AttachmentPartTest::testApi()
{
const QString str = QStringLiteral("test");
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
// Test that an AttachmentPart::Ptr can be put in a QHash.
QHash<AttachmentPart::Ptr, QString> hash;
hash[ part ] = str;
QVERIFY(hash.contains(part));
// Test that an AttachmentPart::Ptr can be put in a QVariant.
QVariant variant = QVariant::fromValue(part);
QVERIFY(variant.isValid());
QVERIFY(variant.canConvert<AttachmentPart::Ptr>());
QVERIFY(variant.value<AttachmentPart::Ptr>() == part);
}
void AttachmentPartTest::shouldHaveDefaultValue()
{
AttachmentPart part;
QCOMPARE(part.size(), (qint64) - 1);
QVERIFY(!part.isInline());
QVERIFY(part.isAutoEncoding());
QVERIFY(!part.isCompressed());
QVERIFY(!part.isEncrypted());
QVERIFY(!part.isSigned());
QCOMPARE(part.encoding(), KMime::Headers::CE7Bit);
QVERIFY(!part.url().isValid());
QVERIFY(part.name().isEmpty());
QVERIFY(part.fileName().isEmpty());
QVERIFY(part.description().isEmpty());
QVERIFY(part.charset().isEmpty());
QVERIFY(part.mimeType().isEmpty());
QVERIFY(part.data().isEmpty());
}
diff --git a/messagecore/autotests/attachmentpropertiesdialogtest.cpp b/messagecore/autotests/attachmentpropertiesdialogtest.cpp
index 1da60583..4dac1f22 100644
--- a/messagecore/autotests/attachmentpropertiesdialogtest.cpp
+++ b/messagecore/autotests/attachmentpropertiesdialogtest.cpp
@@ -1,186 +1,186 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentpropertiesdialogtest.h"
#include "qtest_messagecore.h"
#include <QCheckBox>
#include "messagecore_debug.h"
#include <QComboBox>
#include <QLineEdit>
-#include <qtest.h>
+#include <QTest>
#include <kmime/kmime_content.h>
using namespace KMime;
#include <MessageCore/AttachmentPart>
#include <MessageCore/AttachmentPropertiesDialog>
using namespace MessageCore;
QTEST_MAIN(AttachmentPropertiesDialogTest)
AttachmentPropertiesDialogTest::AttachmentPropertiesDialogTest(QObject *parent)
: QObject(parent)
{
}
void AttachmentPropertiesDialogTest::testAttachmentPartReadWrite()
{
// Sample data.
const QString name = QStringLiteral("old name");
const QString newName = QStringLiteral("new name");
const QString description = QStringLiteral("old description");
const QString newDescription = QStringLiteral("new description");
const QByteArray data("12345");
const QByteArray mimeType("text/plain");
const QByteArray newMimeType("x-weird/x-type");
const Headers::contentEncoding encoding = Headers::CEquPr;
const Headers::contentEncoding newEncoding = Headers::CE7Bit;
const bool autoDisplay = true;
const bool encrypt = true;
const bool sign = true;
// Create the part.
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
part->setName(name);
part->setDescription(description);
part->setData(data);
part->setMimeType(mimeType);
part->setEncoding(encoding);
part->setInline(autoDisplay);
part->setEncrypted(encrypt);
part->setSigned(sign);
// Show the dialog and verify that it is accurate.
AttachmentPropertiesDialog *dialog = new AttachmentPropertiesDialog(part);
dialog->show();
QLineEdit *nameEdit = dialog->findChild<QLineEdit *>(QStringLiteral("name"));
Q_ASSERT(nameEdit);
QCOMPARE(nameEdit->text(), name);
QLineEdit *descriptionEdit = dialog->findChild<QLineEdit *>(QStringLiteral("description"));
Q_ASSERT(descriptionEdit);
QCOMPARE(descriptionEdit->text(), description);
QComboBox *mimeTypeCombo = dialog->findChild<QComboBox *>(QStringLiteral("mimeType"));
Q_ASSERT(mimeTypeCombo);
QCOMPARE(mimeTypeCombo->currentText().toLatin1(), mimeType);
QComboBox *encodingCombo = dialog->findChild<QComboBox *>(QStringLiteral("encoding"));
Q_ASSERT(encodingCombo);
QCOMPARE(encodingCombo->currentIndex(), int(encoding));
QCheckBox *autoDisplayCheck = dialog->findChild<QCheckBox *>(QStringLiteral("autoDisplay"));
Q_ASSERT(autoDisplayCheck);
QCOMPARE(autoDisplayCheck->isChecked(), autoDisplay);
QCheckBox *encryptCheck = dialog->findChild<QCheckBox *>(QStringLiteral("encrypt"));
Q_ASSERT(encryptCheck);
QCOMPARE(encryptCheck->isChecked(), encrypt);
QCheckBox *signCheck = dialog->findChild<QCheckBox *>(QStringLiteral("sign"));
Q_ASSERT(signCheck);
QCOMPARE(signCheck->isChecked(), sign);
//QTest::qWait( 5000 );
// Make some changes in the dialog.
nameEdit->setText(newName);
descriptionEdit->setText(newDescription);
const QString comboBoxMimeType = QString::fromLatin1(newMimeType);
int index = mimeTypeCombo->findText(comboBoxMimeType);
if (index == -1) {
mimeTypeCombo->insertItem(0, comboBoxMimeType);
mimeTypeCombo->setCurrentIndex(0);
} else {
mimeTypeCombo->setCurrentIndex(index);
}
encodingCombo->setCurrentIndex(int(newEncoding));
autoDisplayCheck->setChecked(!autoDisplay);
encryptCheck->setChecked(!encrypt);
signCheck->setChecked(!sign);
// Click on 'OK' and verify changes.
dialog->accept();
delete dialog;
QCOMPARE(part->name(), newName);
QCOMPARE(part->description(), newDescription);
QCOMPARE(part->data(), data); // Unchanged.
QCOMPARE(part->mimeType(), newMimeType);
QCOMPARE(int(part->encoding()), int(newEncoding));
QCOMPARE(part->isInline(), !autoDisplay);
QCOMPARE(part->isEncrypted(), !encrypt);
QCOMPARE(part->isSigned(), !sign);
}
void AttachmentPropertiesDialogTest::testAttachmentPartReadOnly()
{
// Sample data.
const QString name = QStringLiteral("old name");
// Create the part.
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
part->setName(name);
// Show the (read-only) dialog and do some changes.
AttachmentPropertiesDialog *dialog = new AttachmentPropertiesDialog(part, true, nullptr);
dialog->show();
// Click on 'OK'. No changes should have been made.
dialog->accept();
delete dialog;
QCOMPARE(part->name(), name); // No change.
}
void AttachmentPropertiesDialogTest::testAttachmentPartCancel()
{
// Sample data.
const QString name = QStringLiteral("old name");
const QString newName = QStringLiteral("new name");
// Create the part.
AttachmentPart::Ptr part = AttachmentPart::Ptr(new AttachmentPart);
part->setName(name);
// Show the (read-write) dialog and do some changes.
AttachmentPropertiesDialog *dialog = new AttachmentPropertiesDialog(part);
dialog->show();
QLineEdit *nameEdit = dialog->findChild<QLineEdit *>(QStringLiteral("name"));
Q_ASSERT(nameEdit);
nameEdit->setText(newName);
// Click on 'Cancel'. No changes should have been made.
dialog->reject();
delete dialog;
QCOMPARE(part->name(), name); // No change.
}
void AttachmentPropertiesDialogTest::testMimeContentReadOnly()
{
// Sample data.
const QString name = QStringLiteral("old name");
const QByteArray charset("us-ascii");
// Create the MIME Content.
Content *content = new Content;
content->contentType()->setName(name, charset);
const Content *constContent = content;
// Show the (read-only) dialog and do some changes.
AttachmentPropertiesDialog *dialog = new AttachmentPropertiesDialog(constContent);
dialog->show();
// Click on 'OK'. The MIME Content should be untouched.
dialog->accept();
delete dialog;
QCOMPARE(content->contentType()->name(), name); // No change.
}
diff --git a/messagecore/autotests/attachmentupdatejobtest.cpp b/messagecore/autotests/attachmentupdatejobtest.cpp
index cb2b015e..e1b67970 100644
--- a/messagecore/autotests/attachmentupdatejobtest.cpp
+++ b/messagecore/autotests/attachmentupdatejobtest.cpp
@@ -1,223 +1,223 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "attachmentupdatejobtest.h"
#include <attachment/attachmentupdatejob.h>
#include <MessageCore/AttachmentPart>
-#include <qtest.h>
+#include <QTest>
#include "qtest_messagecore.h"
#define PATH_ATTACHMENTS QLatin1String(KDESRCDIR "/attachments/")
AttachmentUpdateJobTest::AttachmentUpdateJobTest(QObject *parent)
: QObject(parent)
{
qputenv("KDE_FORK_SLAVES", "yes"); // To avoid a runtime dependency on klauncher
}
AttachmentUpdateJobTest::~AttachmentUpdateJobTest()
{
}
void AttachmentUpdateJobTest::shouldHaveDefaultValue()
{
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
QCOMPARE(origPart, job->originalPart());
QVERIFY(!job->updatedPart());
delete job;
}
void AttachmentUpdateJobTest::shouldUpdateAttachment()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
origPart->setData(data);
origPart->setUrl(url);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QVERIFY(origPart->size() != job->updatedPart()->size());
QVERIFY(origPart->data() != job->updatedPart()->data());
}
void AttachmentUpdateJobTest::shouldHaveSameNameDescriptionAfterUpdate()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
- const QString filename = QString::fromLatin1("filename");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
+ const QString filename = QStringLiteral("filename");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setFileName(filename);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
origPart->setData(data);
origPart->setUrl(url);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QCOMPARE(origPart->name(), job->updatedPart()->name());
QCOMPARE(origPart->description(), job->updatedPart()->description());
QCOMPARE(origPart->fileName(), job->updatedPart()->fileName());
}
void AttachmentUpdateJobTest::shouldHaveSameCryptoSignStatusAfterUpdate()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
origPart->setData(data);
origPart->setUrl(url);
origPart->setSigned(true);
origPart->setEncrypted(true);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QCOMPARE(origPart->isSigned(), job->updatedPart()->isSigned());
QCOMPARE(origPart->isEncrypted(), job->updatedPart()->isEncrypted());
}
void AttachmentUpdateJobTest::shouldHaveSameEncodingAfterUpdate()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/pdf");
origPart->setEncoding(KMime::Headers::CE8Bit);
origPart->setData(data);
origPart->setUrl(url);
origPart->setSigned(true);
origPart->setEncrypted(true);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QCOMPARE(origPart->encoding(), job->updatedPart()->encoding());
}
void AttachmentUpdateJobTest::shouldHaveSameMimetypeAfterUpdate()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/pdf");
origPart->setEncoding(KMime::Headers::CE8Bit);
origPart->setData(data);
origPart->setUrl(url);
origPart->setSigned(true);
origPart->setEncrypted(true);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QCOMPARE(origPart->mimeType(), job->updatedPart()->mimeType());
}
void AttachmentUpdateJobTest::shouldNotUpdateWhenUrlIsEmpty()
{
QByteArray data("This is short enough that compressing it is not efficient.");
- const QString name = QString::fromLatin1("name.txt");
- const QString description = QString::fromLatin1("description");
+ const QString name = QStringLiteral("name.txt");
+ const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/plain");
origPart->setEncoding(KMime::Headers::CE7Bit);
origPart->setData(data);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
job->exec();
QVERIFY(!job->updatedPart());
}
void AttachmentUpdateJobTest::shouldHaveSameInlineStatus()
{
- const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QString::fromLatin1("file.txt"));
+ const QUrl url = QUrl::fromLocalFile(PATH_ATTACHMENTS + QLatin1String("file.txt"));
// Some data.
QByteArray data("This is short enough that compressing it is not efficient.");
const QString name = QStringLiteral("name.txt");
const QString description = QStringLiteral("description");
// Create the original part.
MessageCore::AttachmentPart::Ptr origPart = MessageCore::AttachmentPart::Ptr(new MessageCore::AttachmentPart);
origPart->setName(name);
origPart->setDescription(description);
origPart->setMimeType("text/pdf");
origPart->setEncoding(KMime::Headers::CE8Bit);
origPart->setData(data);
origPart->setUrl(url);
origPart->setSigned(true);
origPart->setEncrypted(true);
origPart->setInline(true);
MessageCore::AttachmentUpdateJob *job = new MessageCore::AttachmentUpdateJob(origPart, this);
VERIFYEXEC(job);
QCOMPARE(origPart->isInline(), job->updatedPart()->isInline());
}
QTEST_MAIN(AttachmentUpdateJobTest)
diff --git a/messagecore/autotests/attachmentupdatejobtest.h b/messagecore/autotests/attachmentupdatejobtest.h
index 313eec6b..b99a92b9 100644
--- a/messagecore/autotests/attachmentupdatejobtest.h
+++ b/messagecore/autotests/attachmentupdatejobtest.h
@@ -1,41 +1,41 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef ATTACHMENTUPDATEJOBTEST_H
#define ATTACHMENTUPDATEJOBTEST_H
#include <QObject>
class AttachmentUpdateJobTest : public QObject
{
Q_OBJECT
public:
explicit AttachmentUpdateJobTest(QObject *parent = nullptr);
~AttachmentUpdateJobTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldUpdateAttachment();
void shouldHaveSameNameDescriptionAfterUpdate();
void shouldHaveSameCryptoSignStatusAfterUpdate();
void shouldHaveSameEncodingAfterUpdate();
void shouldHaveSameMimetypeAfterUpdate();
void shouldNotUpdateWhenUrlIsEmpty();
void shouldHaveSameInlineStatus();
};
#endif // ATTACHMENTUPDATEJOBTEST_H
diff --git a/messagecore/autotests/mailinglisttest.cpp b/messagecore/autotests/mailinglisttest.cpp
index 6d4a0db2..01f43726 100644
--- a/messagecore/autotests/mailinglisttest.cpp
+++ b/messagecore/autotests/mailinglisttest.cpp
@@ -1,116 +1,116 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "mailinglisttest.h"
#include "misc/mailinglist.h"
-#include <qtest.h>
+#include <QTest>
#include <KConfigGroup>
#include "messagecore_debug.h"
#include <KSharedConfig>
//TODO add test for static MailingList detect( const KMime::Message::Ptr &message ); and static QString name( ... );
MailingListTest::MailingListTest(QObject *parent)
: QObject(parent)
{
}
MailingListTest::~MailingListTest()
{
}
void MailingListTest::shouldHaveDefaultValue()
{
MessageCore::MailingList ml;
QVERIFY(ml.postUrls().isEmpty());
QVERIFY(ml.subscribeUrls().isEmpty());
QVERIFY(ml.unsubscribeUrls().isEmpty());
QVERIFY(ml.helpUrls().isEmpty());
QVERIFY(ml.archiveUrls().isEmpty());
QVERIFY(ml.ownerUrls().isEmpty());
QVERIFY(ml.archivedAtUrls().isEmpty());
QVERIFY(ml.id().isEmpty());
QVERIFY(ml.features() == MessageCore::MailingList::None);
QVERIFY(ml.handler() == MessageCore::MailingList::KMail);
}
void MailingListTest::shouldRestoreFromSettings()
{
MessageCore::MailingList ml;
QList<QUrl> lst;
lst << QUrl(QStringLiteral("http://www.kde.org")) << QUrl(QStringLiteral("http://www.koffice.org"));
ml.setPostUrls(lst);
lst << QUrl(QStringLiteral("mailto://www.kde2.org")) << QUrl(QStringLiteral("http://www.koffice2.org"));
ml.setSubscribeUrls(lst);
lst << QUrl(QStringLiteral("mailto://www.kde3.org")) << QUrl(QStringLiteral("http://www.koffice3.org"));
ml.setUnsubscribeUrls(lst);
lst << QUrl(QStringLiteral("mailto://www.kde4.org")) << QUrl(QStringLiteral("http://www.koffice4.org"));
ml.setHelpUrls(lst);
/* Note: mArchivedAtUrl deliberately not saved here as it refers to a single
* instance of a message rather than an element of a general mailing list.
* http://reviewboard.kde.org/r/1768/#review2783
*/
//normal that we don't save it.
//ml.setArchivedAtUrls(lst);
lst << QUrl(QStringLiteral("mailto://www.kde5.org")) << QUrl(QStringLiteral("http://www.koffice5.org"));
ml.setArchiveUrls(lst);
lst << QUrl(QStringLiteral("mailto://www.kde6.org")) << QUrl(QStringLiteral("http://www.koffice6.org"));
ml.setOwnerUrls(lst);
ml.setId(QStringLiteral("ID"));
ml.setHandler(MessageCore::MailingList::Browser);
KConfigGroup grp(KSharedConfig::openConfig(), "testsettings");
ml.writeConfig(grp);
MessageCore::MailingList restoreMl;
restoreMl.readConfig(grp);
QCOMPARE(ml, restoreMl);
}
void MailingListTest::shouldCopyReminderInfo()
{
MessageCore::MailingList ml;
QList<QUrl> lst;
lst << QUrl(QStringLiteral("http://www.kde.org")) << QUrl(QStringLiteral("http://www.koffice.org"));
ml.setPostUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde2.org")) << QUrl(QStringLiteral("http://www.koffice2.org"));
ml.setSubscribeUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde3.org")) << QUrl(QStringLiteral("http://www.koffice3.org"));
ml.setUnsubscribeUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde4.org")) << QUrl(QStringLiteral("http://www.koffice4.org"));
ml.setHelpUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde5.org")) << QUrl(QStringLiteral("http://www.koffice5.org"));
ml.setArchivedAtUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde5.org")) << QUrl(QStringLiteral("http://www.koffice6.org"));
ml.setArchiveUrls(lst);
lst << QUrl(QStringLiteral("http://www.kde6.org")) << QUrl(QStringLiteral("http://www.koffice6.org"));
ml.setOwnerUrls(lst);
ml.setPostUrls(lst);
ml.setSubscribeUrls(lst);
ml.setUnsubscribeUrls(lst);
ml.setHelpUrls(lst);
ml.setArchivedAtUrls(lst);
ml.setArchiveUrls(lst);
ml.setOwnerUrls(lst);
ml.setId(QStringLiteral("ID"));
ml.setHandler(MessageCore::MailingList::Browser);
MessageCore::MailingList restoreMl(ml);
QCOMPARE(ml, restoreMl);
}
QTEST_APPLESS_MAIN(MailingListTest)
diff --git a/messagecore/autotests/mailinglisttest.h b/messagecore/autotests/mailinglisttest.h
index 7d9914fa..7a9e4d28 100644
--- a/messagecore/autotests/mailinglisttest.h
+++ b/messagecore/autotests/mailinglisttest.h
@@ -1,36 +1,36 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef MAILINGLISTTEST_H
#define MAILINGLISTTEST_H
#include <QObject>
class MailingListTest : public QObject
{
Q_OBJECT
public:
explicit MailingListTest(QObject *parent = nullptr);
~MailingListTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldRestoreFromSettings();
void shouldCopyReminderInfo();
};
#endif // MAILINGLISTTEST_H
diff --git a/messagecore/autotests/stringutiltest.cpp b/messagecore/autotests/stringutiltest.cpp
index e38de497..3e5d4c43 100644
--- a/messagecore/autotests/stringutiltest.cpp
+++ b/messagecore/autotests/stringutiltest.cpp
@@ -1,476 +1,557 @@
/* Copyright 2009 Thomas McGuire <mcguire@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 ) version 3 or, at the discretion of KDE e.V.
( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "stringutiltest.h"
#include "../src/utils/stringutil.h"
#include "qtest.h"
#include <QUrl>
using namespace MessageCore;
QTEST_MAIN(StringUtilTest)
#define lineLength 40
void StringUtilTest::test_SmartQuote()
{
QFETCH(QString, originalString);
QFETCH(QString, quotedString);
QEXPECT_FAIL("hard linebreak", "Currently no way to differentiate between hard and soft line breaks", Continue);
const QString result = StringUtil::smartQuote(originalString, lineLength);
const QStringList resultList = result.split(QLatin1Char('\n'));
const QStringList expectedList = quotedString.split(QLatin1Char('\n'));
qDebug() << "result :" << resultList;
qDebug() << "expected:" << expectedList;
QCOMPARE(resultList, expectedList);
//QCOMPARE( result, quotedString );
}
void StringUtilTest::test_SmartQuote_data()
{
QTest::addColumn<QString>("originalString");
QTest::addColumn<QString>("quotedString");
QTest::newRow("") << ""
<< "";
QTest::newRow("1") << "Some short text"
<< "Some short text";
// 40
// ↓
QTest::newRow("2") << "Some much longer text that exceeds our limit by far."
<< "Some much longer text that exceeds our\nlimit by far.";
QTest::newRow("3") << " Space at start."
<< " Space at start.";
// 40
// ↓
- QTest::newRow("4") << " Space at start, but also two lines in this long sentennce."
- << " Space at start, but also two lines in\nthis long sentennce.";
+ QTest::newRow("4") << " Space at start, but also two lines in this long sentence."
+ << " Space at start, but also two lines in\nthis long sentence.";
QTest::newRow("5") << " Space at start and end. "
<< " Space at start and end.";
QTest::newRow("6") << "Space at end of pre-broken line. \n"
"Yet another line of text."
<< "Space at end of pre-broken line.\n"
"Yet another line of text.";
// 40
// ↓
QTest::newRow("7") << "Long long line, followed by another line starting with a space.\n"
" Starts with a space."
<< "Long long line, followed by another line\n"
"starting with a space. Starts with a\n"
"space.";
// 40
// ↓
QTest::newRow("8") << "Two lines that don't need to be\nchanged in any way."
<< "Two lines that don't need to be\nchanged in any way.";
// 40
// ↓
QTest::newRow("9") << "Many lines.\n"
"Only one needs to be broken.\n"
"This is the very much too long line that needs to be broken.\n"
"This line is ok again."
<< "Many lines.\n"
"Only one needs to be broken.\n"
"This is the very much too long line that\n"
"needs to be broken. This line is ok\n"
"again.";
// 40
// ↓
QTest::newRow("10") << "> >Very long quoted line, that is very very long"
<< "> >Very long quoted line, that is very\n"
"> >very long";
// 40
// ↓
QTest::newRow("11") << "> > Very long quoted line, that is very very long"
<< "> > Very long quoted line, that is very\n"
"> > very long";
// 40
// ↓
QTest::newRow("12") << "> > Very long quoted line, that is very very long. \n"
"> > Another line here."
<< "> > Very long quoted line, that is very\n"
"> > very long. Another line here.";
// 40
// ↓
QTest::newRow("13") << "> > Very long quoted line, that is very very long. \n"
"Unquoted line, for a change.\n"
"> > Another line here."
<< "> > Very long quoted line, that is very\n"
"> > very long.\n"
"Unquoted line, for a change.\n"
"\n"
"> > Another line here.";
// 40
// ↓
QTest::newRow("14") << "> Quote level 1 with long long long long text, that is long.\n"
"> Quote level 1 still.\n"
"> > Quote level 2 now, also with a long long long long text.\n"
"> > Quote level 2 still.\n"
"No quotes."
<< "> Quote level 1 with long long long long\n"
"> text, that is long. Quote level 1\n"
"> still.\n"
"> \n"
"> > Quote level 2 now, also with a long\n"
"> > long long long text. Quote level 2\n"
"> > still.\n"
"\n"
"No quotes.";
// 40
// ↓
QTest::newRow("15") << "Some much longer text that exceeds our limit by far.\n"
"\n"
"Line after an empty one."
<< "Some much longer text that exceeds our\n"
"limit by far.\n"
"\n"
"Line after an empty one.";
// Make sure the "You wrote:" line is not broken, that would look strange
// 40
// ↓
QTest::newRow("16") << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
"> Bla Bla Bla Bla..."
<< "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
"> Bla Bla Bla Bla...";
// 40
// ↓
QTest::newRow("17") << "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
"\n"
"> Bla Bla Bla Bla..."
<< "Yesterday, Hans Martin Ulrich Franz August wrote:\n"
"> Bla Bla Bla Bla...";
// This test shows a fundamental flaw when breaking lines: The table header gets broken,
// which is ok. However, the following line is appended to the table header, which leads
// the data columns and the table header to by not aligned anymore.
// The problem is that the linebreak after the table header is supposed to be a hard line break,
// but there is no way to know that, so we treat it as a soft line break instead.
//
// We can't treat every linebreak as hard linebreaks, as quoting natural text would look strange then.
// The problem here is that the sender word-wraps the email with hard linebreaks.
// This is only possible to fix correctly by supporting "flowed" text, as per RFC 2646
//
// (this is marked as QEXPECT_FAIL above)
//
// The solution would be to let the caller remove soft linebreaks manually (as only the caller
// can now), and let smartQuote() treat all linebreaks as hard linebreaks, which would fix this.
// 40
// ↓
QTest::newRow("hard linebreak") << "==== Date ======== Amount ======= Type ======\n"
" 12.12.09 5 Car \n"
<< "==== Date ======== Amount ======= Type\n"
"======\n"
" 12.12.09 5 Car \n";
}
void StringUtilTest::test_signatureStripping()
{
//QStringList tests;
const QString test1 = QStringLiteral(
"text1\n"
"-- \n"
"Signature Block1\n"
"Signature Block1\n\n"
"> text2\n"
"> -- \n"
"> Signature Block 2\n"
"> Signature Block 2\n"
">> text3 -- not a signature block\n"
">> text3\n"
">>> text4\n"
"> -- \n"
"> Signature Block 4\n"
"> Signature Block 4\n"
">>-------------\n"
">>-- text5 --\n"
">>-------------------\n"
">>-- \n"
">>\n"
">> Signature Block 5\n"
"text6\n"
"-- \n"
"Signature Block 6\n");
const QString test1Result = QStringLiteral(
"text1\n"
"> text2\n"
">> text3 -- not a signature block\n"
">> text3\n"
">>> text4\n"
">>-------------\n"
">>-- text5 --\n"
">>-------------------\n"
"text6\n");
QCOMPARE(StringUtil::stripSignature(test1), test1Result);
const QString test2 = QStringLiteral(
"text1\n"
"> text2\n"
">> text3 -- not a signature block\n"
">> text3\n"
">>> text4\n"
">>-------------\n"
">>-- text5 --\n"
">>-------------------\n"
"text6\n");
// No actual signature - should stay the same
QCOMPARE(StringUtil::stripSignature(test2), test2);
const QString test3 = QStringLiteral(
"text1\n"
"-- \n"
"Signature Block1\n"
"Signature Block1\n\n"
">text2\n"
">-- \n"
">Signature Block 2\n"
">Signature Block 2\n"
"> >text3\n"
"> >text3\n"
"> >-- \n"
">>Not Signature Block 3\n"
"> > Not Signature Block 3\n"
">text4\n"
">-- \n"
">Signature Block 4\n"
">Signature Block 4\n"
"text5\n"
"-- \n"
"Signature Block 5");
const QString test3Result = QStringLiteral(
"text1\n"
">text2\n"
"> >text3\n"
"> >text3\n"
">>Not Signature Block 3\n"
"> > Not Signature Block 3\n"
">text4\n"
"text5\n");
QCOMPARE(StringUtil::stripSignature(test3), test3Result);
const QString test4 = QStringLiteral(
"Text 1\n"
"-- \n"
"First sign\n\n\n"
"> From: bla\n"
"> Texto 2\n\n"
"> Aqui algo de texto.\n\n"
">> --\n"
">> Not Signature Block 2\n\n"
"> Adios\n\n"
">> Texto 3\n\n"
">> --\n"
">> Not Signature block 3\n");
const QString test4Result = QStringLiteral(
"Text 1\n"
"> From: bla\n"
"> Texto 2\n\n"
"> Aqui algo de texto.\n\n"
">> --\n"
">> Not Signature Block 2\n\n"
"> Adios\n\n"
">> Texto 3\n\n"
">> --\n"
">> Not Signature block 3\n");
QCOMPARE(StringUtil::stripSignature(test4), test4Result);
const QString test5 = QStringLiteral(
"-- \n"
"-- ACME, Inc\n"
"-- Joe User\n"
"-- PHB\n"
"-- Tel.: 555 1234\n"
"--");
QCOMPARE(StringUtil::stripSignature(test5), QString());
const QString test6 = QStringLiteral(
"Text 1\n\n\n\n"
"> From: bla\n"
"> Texto 2\n\n"
"> Aqui algo de texto.\n\n"
">> Not Signature Block 2\n\n"
"> Adios\n\n"
">> Texto 3\n\n"
">> --\n"
">> Not Signature block 3\n");
// Again, no actual signature in here
QCOMPARE(StringUtil::stripSignature(test6), test6);
}
void StringUtilTest::test_stripOffMessagePrefixBenchmark()
{
const QString subject = QStringLiteral("Fwd: Hello World Subject");
QBENCHMARK {
QString result = StringUtil::stripOffPrefixes(subject);
Q_UNUSED(result);
}
}
void StringUtilTest::test_parseMailtoUrl_data()
{
QTest::addColumn<QString>("mailToUrl");
QTest::addColumn<bool>("toIsNotEmpty");
QTest::addColumn<int>("numberOfTo");
+ QTest::addColumn<int>("numberElement");
- QTest::newRow("1 mailto") << QStringLiteral("mailto:foo@kde.org") << true << 1;
- QTest::newRow("invalid (not mailto)") << QStringLiteral("http://www.kde.org") << false << 0;
- QTest::newRow("invalid (no email address") << QStringLiteral("mailto:") << false << 0;
- QTest::newRow("2 address") << QStringLiteral("mailto:foo@kde.org?to=foo2@kde.org") << true << 2;
- QTest::newRow("invalid") << QStringLiteral("fookde.org") << false << 0;
+ QTest::newRow("1 mailto") << QStringLiteral("mailto:foo@kde.org") << true << 1 << 1;
+ QTest::newRow("invalid (not mailto)") << QStringLiteral("http://www.kde.org") << false << 0 << 0;
+ QTest::newRow("invalid (no email address") << QStringLiteral("mailto:") << false << 0 << 0;
+ QTest::newRow("2 address") << QStringLiteral("mailto:foo@kde.org?to=foo2@kde.org") << true << 2 << 1;
+ QTest::newRow("invalid") << QStringLiteral("fookde.org") << false << 0 << 0;
}
void StringUtilTest::test_parseMailtoUrl()
{
QFETCH(QString, mailToUrl);
QFETCH(bool, toIsNotEmpty);
QFETCH(int, numberOfTo);
+ QFETCH(int, numberElement);
QUrl url(mailToUrl);
- QCOMPARE(!StringUtil::parseMailtoUrl(url).value(QStringLiteral("to")).isEmpty(), toIsNotEmpty);
- QCOMPARE(StringUtil::parseMailtoUrl(url).value(QStringLiteral("to")).split(QLatin1String(", "), QString::SkipEmptyParts).count(), numberOfTo);
+ auto list = StringUtil::parseMailtoUrl(url);
+ //qDebug() << " list "<< list;
+ QCOMPARE(list.count(), numberElement);
+ if (numberOfTo > 0) {
+ QCOMPARE(!list.at(0).second.isEmpty(), toIsNotEmpty);
+ QCOMPARE(list.at(0).second.split(QLatin1String(", "), QString::SkipEmptyParts).count(), numberOfTo);
+ }
}
void StringUtilTest::test_parseMailtoUrlExtra()
{
const QByteArray ba("mailto:someone@example.com?subject=This%20is%20the%20subject&cc=someone_else@example.com&body=This%20is%20the%20body");
QUrl url = QUrl(QUrl::fromPercentEncoding(ba));
- QMap<QString, QString> data = StringUtil::parseMailtoUrl(url);
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(url);
QCOMPARE(data.size(), 4);
- QCOMPARE(data.value(QLatin1String("to")), QLatin1String("someone@example.com"));
- QCOMPARE(data.value(QLatin1String("subject")), QLatin1String("This is the subject"));
- QCOMPARE(data.value(QLatin1String("cc")), QLatin1String("someone_else@example.com"));
- QCOMPARE(data.value(QLatin1String("body")), QLatin1String("This is the body"));
+ for (int i = 0; i < 4; ++i) {
+ if (data.at(i).first == QLatin1String("to")) {
+ QCOMPARE(data.at(i).second, QLatin1String("someone@example.com"));
+ } else if (data.at(i).first == QLatin1String("subject")) {
+ QCOMPARE(data.at(i).second, QLatin1String("This is the subject"));
+ } else if (data.at(i).first == QLatin1String("cc")) {
+ QCOMPARE(data.at(i).second, QLatin1String("someone_else@example.com"));
+ } else if (data.at(i).first == QLatin1String("body")) {
+ QCOMPARE(data.at(i).second, QLatin1String("This is the body"));
+ }
+ }
}
void StringUtilTest::test_parseMailToBug366981()
{
const QString ba(QStringLiteral("mailto:test@test.com?subject=test&body=line1%0D%0Aline2"));
QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
- QMap<QString, QString> data = StringUtil::parseMailtoUrl(urlDecoded);
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(urlDecoded);
QCOMPARE(data.size(), 3);
- QCOMPARE(data.value(QLatin1String("to")), QLatin1String("test@test.com"));
- QCOMPARE(data.value(QLatin1String("subject")), QLatin1String("test"));
- QCOMPARE(data.value(QLatin1String("body")), QLatin1String("line1\r\nline2"));
+ for (int i = 0; i < 3; ++i) {
+ if (data.at(i).first == QLatin1String("to")) {
+ QCOMPARE(i, 0);
+ QCOMPARE(data.at(i).second, QLatin1String("test@test.com"));
+ } else if (data.at(i).first == QLatin1String("subject")) {
+ QCOMPARE(i, 1);
+ QCOMPARE(data.at(i).second, QLatin1String("test"));
+ } else if (data.at(i).first == QLatin1String("body")) {
+ QCOMPARE(i, 2);
+ QCOMPARE(data.at(i).second, QLatin1String("line1\r\nline2"));
+ }
+ }
}
void StringUtilTest::test_parseDuplicateQueryItems()
{
const QString ba(QStringLiteral("mailto:test@test.com?subject=test&body=line1%0D%0Aline2&cc=someone_else@example.com&cc=someone_else2@example.com"));
QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
- QMap<QString, QString> values = StringUtil::parseMailtoUrl(urlDecoded);
+ QList<QPair<QString, QString> > values = StringUtil::parseMailtoUrl(urlDecoded);
QCOMPARE(values.size(), 5);
- for (auto it = values.cbegin(), end = values.cend(); it != end; ++it) {
- if (it.key() == QLatin1Literal("to")) {
- QCOMPARE(it.value(), QLatin1String("test@test.com"));
- } else if (it.key() == QLatin1Literal("subject")) {
- QCOMPARE(it.value(), QLatin1String("test"));
- } else if (it.key() == QLatin1Literal("body")) {
- QCOMPARE(it.value(), QLatin1String("line1\r\nline2"));
- } else if (it.key() == QLatin1Literal("cc")) {
- const QString ccVal = it.value();
+ int valueCC = 0;
+ for (int i = 0; i < values.size(); ++i) {
+ if (values.at(i).first == QLatin1Literal("to")) {
+ QCOMPARE(values.at(i).second, QLatin1String("test@test.com"));
+ } else if (values.at(i).first == QLatin1Literal("subject")) {
+ QCOMPARE(values.at(i).second, QLatin1String("test"));
+ } else if (values.at(i).first == QLatin1Literal("body")) {
+ QCOMPARE(values.at(i).second, QLatin1String("line1\r\nline2"));
+ } else if (values.at(i).first == QLatin1Literal("cc")) {
+ const QString ccVal = values.at(i).second;
+ valueCC++;
if ((ccVal != QLatin1String("someone_else@example.com")) && (ccVal != QLatin1String("someone_else2@example.com"))) {
QVERIFY(false);
}
}
}
- QCOMPARE(values.values(QStringLiteral("cc")).size(), 2);
+ QCOMPARE(valueCC, 2);
+}
+
+void StringUtilTest::test_parseMAilToBug402378()
+{
+ const QString ba(QStringLiteral(
+ "mailto:?body=infotbm.com https://www.infotbm.com/fr/routes/id=-0.624162|44.849958&type=address/datetime=20181226T143038&id=stop_area:TBT:SA:HTLEV&time_type=arrival&type=stop_area/0&subject=Votre itineraire avec TBM"));
+ QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(urlDecoded);
+ QCOMPARE(data.size(), 6);
+ QCOMPARE(data.at(0).first, QLatin1String("body"));
+ QCOMPARE(data.at(0).second, QLatin1String(
+ "infotbm.com https://www.infotbm.com/fr/routes/id=-0.624162|44.849958"));
+ QCOMPARE(data.at(1).first, QLatin1String("type"));
+ QCOMPARE(data.at(1).second, QLatin1String("address/datetime=20181226T143038"));
+ QCOMPARE(data.at(2).first, QLatin1String("id"));
+ QCOMPARE(data.at(2).second, QLatin1String("stop_area:TBT:SA:HTLEV"));
+ QCOMPARE(data.at(3).first, QLatin1String("time_type"));
+ QCOMPARE(data.at(3).second, QLatin1String("arrival"));
+ QCOMPARE(data.at(4).first, QLatin1String("type"));
+ QCOMPARE(data.at(4).second, QLatin1String("stop_area/0"));
+ QCOMPARE(data.at(5).first, QLatin1String("subject"));
+ QCOMPARE(data.at(5).second, QLatin1String("Votre itineraire avec TBM"));
+}
+
+void StringUtilTest::test_parseMailToBug406208()
+{
+ {
+ QString ba(QStringLiteral(
+ "mailto:?body=http%3A%2F%2Fwww.lecourrierdelarchitecte.com%2Farticle_8428&subject=Le%20Courrier%20l'effet%20%23metoo%20%3F"));
+ QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
+ qDebug() << " urlDecoded" << urlDecoded.authority(QUrl::FullyDecoded);
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(urlDecoded);
+ QCOMPARE(data.size(), 2);
+ QCOMPARE(data.at(0).first, QLatin1String("body"));
+ QCOMPARE(data.at(0).second, QLatin1String(
+ "http://www.lecourrierdelarchitecte.com/article_8428"));
+ QCOMPARE(data.at(1).first, QLatin1String("subject"));
+ QCOMPARE(data.at(1).second, QLatin1String("Le Courrier l'effet #metoo ?"));
+ }
+ {
+ QString ba(QStringLiteral(
+ "mailto:?body=http%3A%2F%2Fwww.lecourrierdelarchitecte.com%2Farticle_8428%20%23%23bla&subject=Le%20Courrier%20l'effet%20%23metoo%20%3F"));
+ QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
+ qDebug() << " urlDecoded" << urlDecoded.authority(QUrl::FullyDecoded);
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(urlDecoded);
+ QCOMPARE(data.size(), 2);
+ QCOMPARE(data.at(0).first, QLatin1String("body"));
+ QCOMPARE(data.at(0).second, QLatin1String(
+ "http://www.lecourrierdelarchitecte.com/article_8428 ##bla"));
+ QCOMPARE(data.at(1).first, QLatin1String("subject"));
+ QCOMPARE(data.at(1).second, QLatin1String("Le Courrier l'effet #metoo ?"));
+ }
}
void StringUtilTest::test_parseMailToBug832795()
{
const QString ba(QStringLiteral(
"mailto:832795@bugs.debian.org?In-Reply-To=%3C146974194340.26747.4814466130640572267.reportbug%40portux.lan.naturalnet.de%3E&subject=Re%3A%20kmail%3A%20unescaping%20mailto%3A%20links%20broken&body=On%20Thu%2C%2028%20Jul%202016References=%3C146974194340.26747.4814466130640572267.reportbug%40portux.lan.naturalnet.de%3Ebody=On%20Thu%2C%2028%20Jul%202016%2023%3A39%3A03%20%2B0200%20Dominik%20George%20%3Cnik%40naturalnet.de%3E%20wrote%3A%0A%3E%20Package%3A%20kmail%0A%3E%20Version%3A%204%3A16.04.3-1%0A"));
QUrl urlDecoded(QUrl::fromPercentEncoding(ba.toUtf8()));
- QMap<QString, QString> data = StringUtil::parseMailtoUrl(urlDecoded);
+ QList<QPair<QString, QString> > data = StringUtil::parseMailtoUrl(urlDecoded);
QCOMPARE(data.size(), 4);
- QCOMPARE(data.value(QLatin1String("to")), QLatin1String("832795@bugs.debian.org"));
- QCOMPARE(data.value(QLatin1String("subject")), QLatin1String("Re: kmail: unescaping mailto: links broken"));
- QCOMPARE(data.value(QLatin1String("body")),
- QLatin1String(
+ QCOMPARE(data.at(0).first, QLatin1String("to"));
+ QCOMPARE(data.at(0).second, QLatin1String("832795@bugs.debian.org"));
+ QCOMPARE(data.at(1).first, QLatin1String("In-Reply-To"));
+ QCOMPARE(data.at(1).second, QLatin1String("<146974194340.26747.4814466130640572267.reportbug@portux.lan.naturalnet.de>"));
+ QCOMPARE(data.at(2).first, QLatin1String("subject"));
+ QCOMPARE(data.at(2).second, QLatin1String("Re: kmail: unescaping mailto: links broken"));
+ QCOMPARE(data.at(3).first, QLatin1String("body"));
+ QCOMPARE(data.at(3).second, QLatin1String(
"On Thu, 28 Jul 2016References=<146974194340.26747.4814466130640572267.reportbug@portux.lan.naturalnet.de>body=On Thu, 28 Jul 2016 23:39:03 +0200 Dominik George <nik@naturalnet.de> wrote:\n> Package: kmail\n> Version: 4:16.04.3-1\n"));
}
void StringUtilTest::test_stripOffMessagePrefix_data()
{
QTest::addColumn<QString>("subject");
QTest::addColumn<QString>("result");
QTest::newRow("no strip needed") << QStringLiteral("Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("No default reply forward") << QStringLiteral("AA: Hello World Subject") << QStringLiteral("AA: Hello World Subject");
QTest::newRow("Default Reply Re:") << QStringLiteral("Re: Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("Default Forward FW:") << QStringLiteral("FW: Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("Default Forward Fwd:") << QStringLiteral("Fwd: Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("Default Reply Re :") << QStringLiteral("Re : Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("Default Reply Re1:") << QStringLiteral("Re1: Hello World Subject") << QStringLiteral("Hello World Subject");
QTest::newRow("Default Reply Re[2]:") << QStringLiteral("Re[2]: Hello World Subject") << QStringLiteral("Hello World Subject");
}
void StringUtilTest::test_stripOffMessagePrefix()
{
QFETCH(QString, subject);
QFETCH(QString, result);
const QString subjectAfterStrip = StringUtil::stripOffPrefixes(subject);
QCOMPARE(subjectAfterStrip, result);
}
void StringUtilTest::test_formatQuotePrefix_data()
{
QTest::addColumn<QString>("quotePattern");
QTest::addColumn<QString>("from");
QTest::addColumn<QString>("result");
QTest::newRow("empty") << QString() << QString() << QString();
QTest::newRow("default") << QStringLiteral("> ") << QStringLiteral("Jon Doe") << QStringLiteral("> ");
QTest::newRow("initials") << QStringLiteral("| %f |") << QStringLiteral("Jon Doe") << QStringLiteral("| JD |");
QTest::newRow("initials one name") << QStringLiteral("| %f |") << QStringLiteral("Jon") << QStringLiteral("| Jo |");
QTest::newRow("initials one letter") << QStringLiteral("| %f |") << QStringLiteral("J") << QStringLiteral("| J |");
QTest::newRow("initials empty name") << QStringLiteral("| %f |") << QString() << QStringLiteral("| |");
QTest::newRow("percent") << QStringLiteral("%% %_ %a") << QString() << QStringLiteral("% %a");
}
void StringUtilTest::test_formatQuotePrefix()
{
QFETCH(QString, quotePattern);
QFETCH(QString, from);
QFETCH(QString, result);
QCOMPARE(MessageCore::StringUtil::formatQuotePrefix(quotePattern, from), result);
}
diff --git a/messagecore/autotests/stringutiltest.h b/messagecore/autotests/stringutiltest.h
index fb03e965..b2526bb6 100644
--- a/messagecore/autotests/stringutiltest.h
+++ b/messagecore/autotests/stringutiltest.h
@@ -1,45 +1,47 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef STRINGUTILTEST_H
#define STRINGUTILTEST_H
-#include <qobject.h>
+#include <QObject>
class StringUtilTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void test_parseMailToBug832795();
void test_SmartQuote_data();
void test_SmartQuote();
void test_signatureStripping();
void test_stripOffMessagePrefixBenchmark();
void test_parseMailtoUrl_data();
void test_parseMailtoUrl();
void test_parseMailtoUrlExtra();
void test_stripOffMessagePrefix_data();
void test_stripOffMessagePrefix();
void test_formatQuotePrefix_data();
void test_formatQuotePrefix();
void test_parseMailToBug366981();
void test_parseDuplicateQueryItems();
+ void test_parseMAilToBug402378();
+ void test_parseMailToBug406208();
};
#endif
diff --git a/messagecore/metainfo.yaml b/messagecore/metainfo.yaml
index 814e552f..93e6eaaf 100644
--- a/messagecore/metainfo.yaml
+++ b/messagecore/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer: mlaurent
description: MessageCore Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: MessageCore
cmake: "KF5::MessageCore"
- cmakename: KF5MessageCore
+cmakename: KF5MessageCore
diff --git a/messagecore/src/CMakeLists.txt b/messagecore/src/CMakeLists.txt
index 3ba3103a..1eac0d79 100644
--- a/messagecore/src/CMakeLists.txt
+++ b/messagecore/src/CMakeLists.txt
@@ -1,179 +1,176 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libmessagecore\")
########### next target ###############
set(messagecore_attachment_LIB_SRCS
attachment/attachmentcompressjob.cpp
attachment/attachmentfromfolderjob.cpp
attachment/attachmentfrommimecontentjob.cpp
attachment/attachmentfromurlbasejob.cpp
attachment/attachmentfromurljob.cpp
attachment/attachmentloadjob.cpp
attachment/attachmentpart.cpp
attachment/attachmentpropertiesdialog.cpp
attachment/attachmentupdatejob.cpp
attachment/attachmentfromurlutils.cpp
)
set(messagecore_misc_LIB_SRCS
misc/imagecollector.cpp
misc/mailinglist.cpp
)
set(messagecore_helper_LIB_SRCS
helpers/nodehelper.cpp
)
set(messagecore_LIB_SRCS
${messagecore_attachment_LIB_SRCS}
${messagecore_misc_LIB_SRCS}
${messagecore_helper_LIB_SRCS}
utils/stringutil.cpp
messagecoreutil.cpp
settings/messagecoresettings.cpp
)
kconfig_add_kcfg_files(messagecore_LIB_SRCS
settings/globalsettings_messagecore.kcfgc
)
ki18n_wrap_ui(messagecore_LIB_SRCS
attachment/ui/attachmentpropertiesdialog.ui
attachment/ui/attachmentpropertiesdialog_readonly.ui
)
ecm_qt_declare_logging_category(messagecore_LIB_SRCS HEADER messagecore_debug.h IDENTIFIER MESSAGECORE_LOG CATEGORY_NAME org.kde.pim.messagecore)
add_library(KF5MessageCore ${messagecore_LIB_SRCS})
generate_export_header(KF5MessageCore BASE_NAME messagecore)
add_library(KF5::MessageCore ALIAS KF5MessageCore)
target_link_libraries(KF5MessageCore
PUBLIC
KF5::Mime
PRIVATE
KF5::KIOCore
KF5::Codecs
KF5::PimTextEdit
KF5::Archive
KF5::ConfigWidgets
KF5::IconThemes
Qt5::Network
KF5::I18n
KF5::IdentityManagement
KF5::PimCommon
)
target_include_directories(KF5MessageCore INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/MessageCore/;${KDE_INSTALL_INCLUDEDIR_KF5}/messagecore>")
set_target_properties(KF5MessageCore PROPERTIES
VERSION ${MESSAGECORE_VERSION_STRING}
SOVERSION ${MESSAGECORE_SOVERSION}
EXPORT_NAME MessageCore
)
install(TARGETS
KF5MessageCore
EXPORT KF5MessageCoreTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK}
)
ecm_generate_headers(MessageCore_Camelcasemain_HEADERS
HEADER_NAMES
MessageCoreUtil
REQUIRED_HEADERS MessageCore_main_HEADERS
PREFIX MessageCore
)
ecm_generate_headers(MessageCore_Camelcasemisc_HEADERS
HEADER_NAMES
ImageCollector
MailingList
REQUIRED_HEADERS MessageCore_misc_HEADERS
PREFIX MessageCore
RELATIVE misc
)
ecm_generate_headers(MessageCore_Camelcaseutils_HEADERS
HEADER_NAMES
StringUtil
REQUIRED_HEADERS MessageCore_utils_HEADERS
PREFIX MessageCore
RELATIVE utils
)
ecm_generate_headers(MessageCore_Camelcasesettings_HEADERS
HEADER_NAMES
MessageCoreSettings
REQUIRED_HEADERS MessageCore_settings_HEADERS
PREFIX MessageCore
RELATIVE settings
)
ecm_generate_headers(MessageCore_Camelcaseattachment_HEADERS
HEADER_NAMES
- AttachmentFromMimeContentJob
AttachmentFromUrlBaseJob
- AttachmentFromUrlJob
- AttachmentLoadJob
AttachmentPart
AttachmentPropertiesDialog
AttachmentCompressJob
- AttachmentFromFolderJob
AttachmentUpdateJob
- AttachmentFromUrlUtils
+ AttachmentFromUrlUtils
+ AttachmentLoadJob
REQUIRED_HEADERS MessageCore_attachment_HEADERS
PREFIX MessageCore
RELATIVE attachment
)
ecm_generate_headers(MessageCore_Camelcasehelpers_HEADERS
HEADER_NAMES
NodeHelper
REQUIRED_HEADERS MessageCore_helpers_HEADERS
PREFIX MessageCore
RELATIVE helpers
)
ecm_generate_pri_file(BASE_NAME MessageCore
LIB_NAME KF5MessageCore
DEPS "Mime" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageCore
)
install(FILES
${MessageCore_Camelcasehelpers_HEADERS}
${MessageCore_Camelcasesettings_HEADERS}
${MessageCore_Camelcaseutils_HEADERS}
${MessageCore_Camelcaseattachment_HEADERS}
${MessageCore_Camelcasemisc_HEADERS}
${MessageCore_Camelcasemain_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageCore
COMPONENT Devel
)
install(FILES
${MessageCore_HEADERS}
${MessageCore_helpers_HEADERS}
${MessageCore_settings_HEADERS}
${MessageCore_utils_HEADERS}
${MessageCore_main_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/messagecore_export.h
${CMAKE_CURRENT_BINARY_DIR}/globalsettings_messagecore.h
${MessageCore_attachment_HEADERS}
${MessageCore_misc_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/messagecore
COMPONENT Devel
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
diff --git a/messagecore/src/attachment/attachmentfrommimecontentjob.h b/messagecore/src/attachment/attachmentfrommimecontentjob.h
index 3b90f2da..7e2a9758 100644
--- a/messagecore/src/attachment/attachmentfrommimecontentjob.h
+++ b/messagecore/src/attachment/attachmentfrommimecontentjob.h
@@ -1,76 +1,76 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
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.
*/
#ifndef MESSAGECORE_ATTACHMENTFROMMIMECONTENTJOB_H
#define MESSAGECORE_ATTACHMENTFROMMIMECONTENTJOB_H
-#include "messagecore_export.h"
+#include "messagecore_private_export.h"
#include "attachmentloadjob.h"
namespace KMime {
class Content;
}
namespace MessageCore {
/**
* @short A job to load an attachment from a mime content.
*
* @author Constantin Berzan <exit3219@gmail.com>
*/
-class MESSAGECORE_EXPORT AttachmentFromMimeContentJob : public AttachmentLoadJob
+class MESSAGECORE_TESTS_EXPORT AttachmentFromMimeContentJob : public AttachmentLoadJob
{
Q_OBJECT
public:
/**
* Creates a new job.
*
* @param content The mime content to load the attachment from.
* @param parent The parent object.
*/
explicit AttachmentFromMimeContentJob(const KMime::Content *content, QObject *parent = nullptr);
/**
* Destroys the job.
*/
~AttachmentFromMimeContentJob() override;
/**
* Sets the mime @p content to load the attachment from.
*/
void setMimeContent(const KMime::Content *content);
/**
* Returns the mime content to load the attachment from.
*/
Q_REQUIRED_RESULT const KMime::Content *mimeContent() const;
protected Q_SLOTS:
void doStart() override;
private:
//@cond PRIVATE
class Private;
Private *const d;
//@endcond
};
}
#endif
diff --git a/messagecore/src/attachment/attachmentfromurlutils.cpp b/messagecore/src/attachment/attachmentfromurlutils.cpp
index f9d78c19..2865fd86 100644
--- a/messagecore/src/attachment/attachmentfromurlutils.cpp
+++ b/messagecore/src/attachment/attachmentfromurlutils.cpp
@@ -1,45 +1,45 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "attachmentfromfolderjob.h"
#include "attachmentfromurljob.h"
#include "attachmentfromurlutils.h"
#include "MessageCore/MessageCoreSettings"
#include "messagecore_debug.h"
#include <QMimeDatabase>
namespace MessageCore {
MessageCore::AttachmentFromUrlBaseJob *AttachmentFromUrlUtils::createAttachmentJob(const QUrl &url, QObject *parent)
{
MessageCore::AttachmentFromUrlBaseJob *ajob = nullptr;
QMimeDatabase db;
if (db.mimeTypeForUrl(url).name() == QLatin1String("inode/directory")) {
qCDebug(MESSAGECORE_LOG) << "Creating attachment from folder";
ajob = new MessageCore::AttachmentFromFolderJob(url, parent);
} else {
ajob = new MessageCore::AttachmentFromUrlJob(url, parent);
qCDebug(MESSAGECORE_LOG) << "Creating attachment from file";
}
if (MessageCore::MessageCoreSettings::maximumAttachmentSize() > 0) {
ajob->setMaximumAllowedSize(MessageCore::MessageCoreSettings::maximumAttachmentSize());
}
return ajob;
}
}
diff --git a/messagecore/src/attachment/attachmentfromurlutils.h b/messagecore/src/attachment/attachmentfromurlutils.h
index 51459c57..abb9a1d4 100644
--- a/messagecore/src/attachment/attachmentfromurlutils.h
+++ b/messagecore/src/attachment/attachmentfromurlutils.h
@@ -1,31 +1,31 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef ATTACHMENTFROMURLUTILS_H
#define ATTACHMENTFROMURLUTILS_H
#include <QObject>
#include "messagecore_export.h"
namespace MessageCore {
class AttachmentFromUrlBaseJob;
namespace AttachmentFromUrlUtils {
Q_REQUIRED_RESULT MESSAGECORE_EXPORT MessageCore::AttachmentFromUrlBaseJob *createAttachmentJob(const QUrl &url, QObject *parent);
}
}
#endif // ATTACHMENTFROMURLUTILS_H
diff --git a/messagecore/src/attachment/attachmentpropertiesdialog.cpp b/messagecore/src/attachment/attachmentpropertiesdialog.cpp
index 252d0c8e..27692ef9 100644
--- a/messagecore/src/attachment/attachmentpropertiesdialog.cpp
+++ b/messagecore/src/attachment/attachmentpropertiesdialog.cpp
@@ -1,392 +1,394 @@
/*
Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
Based on KMail code by various authors (kmmsgpartdlg).
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "attachmentpropertiesdialog.h"
#include "attachmentfrommimecontentjob.h"
#include "ui_attachmentpropertiesdialog.h"
#include "ui_attachmentpropertiesdialog_readonly.h"
#include <KAboutData>
#include "messagecore_debug.h"
#include <KIconLoader>
#include <PimCommon/PimUtil>
#include <kmime/kmime_content.h>
#include <kmime/kmime_headers.h>
#include <KLocalizedString>
#include <KFormat>
#include <QMimeDatabase>
#include <QMimeType>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
using namespace MessageCore;
class Q_DECL_HIDDEN MessageCore::AttachmentPropertiesDialog::Private
{
public:
Private(AttachmentPropertiesDialog *qq)
: q(qq)
{
}
~Private()
{
delete ui;
delete uiReadOnly;
}
void init(const AttachmentPart::Ptr &part, bool readOnly);
void polishUi();
void mimeTypeChanged(const QString &type); // slot
void populateEncodings();
void populateMimeTypes();
void populateWhatsThis();
void loadFromPart();
void saveToPart();
AttachmentPropertiesDialog *const q;
AttachmentPart::Ptr mPart;
Ui::AttachmentPropertiesDialog *ui = nullptr;
Ui::AttachmentPropertiesDialogReadOnly *uiReadOnly = nullptr;
QVBoxLayout *mainLayout = nullptr;
bool mReadOnly = false;
};
void AttachmentPropertiesDialog::Private::init(const AttachmentPart::Ptr &part, bool readOnly)
{
mReadOnly = readOnly;
mPart = part;
QWidget *widget = new QWidget(q);
mainLayout = new QVBoxLayout;
q->setLayout(mainLayout);
mainLayout->addWidget(widget);
if (mReadOnly) {
uiReadOnly = new Ui::AttachmentPropertiesDialogReadOnly;
uiReadOnly->setupUi(widget);
} else {
ui = new Ui::AttachmentPropertiesDialog;
ui->setupUi(widget);
}
polishUi();
q->setModal(true);
loadFromPart();
}
void AttachmentPropertiesDialog::Private::polishUi()
{
// Tweak the dialog, depending on whether it is read-only or not.
QDialogButtonBox *buttonBox = nullptr;
if (mReadOnly) {
buttonBox = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close, q);
} else {
// Update the icon when the selected mime type changes.
- connect(ui->mimeType, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
- q, [this](const QString &str) { mimeTypeChanged(str); });
+ connect(ui->mimeType, qOverload<const QString &>(&QComboBox::currentIndexChanged),
+ q, [this](const QString &str) {
+ mimeTypeChanged(str);
+ });
populateMimeTypes();
populateEncodings();
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help, q);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
}
q->connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &AttachmentPropertiesDialog::slotHelp);
q->connect(buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
q->connect(buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
mainLayout->addWidget(buttonBox);
populateWhatsThis();
}
void AttachmentPropertiesDialog::Private::mimeTypeChanged(const QString &type)
{
QMimeDatabase db;
const QMimeType mimeType = db.mimeTypeForName(type);
QPixmap pix;
if (mimeType.isValid()) {
pix = KIconLoader::global()->loadMimeTypeIcon(mimeType.iconName(), KIconLoader::Desktop);
} else {
pix = QIcon::fromTheme(QStringLiteral("unknown")).pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop));
}
if (mReadOnly) {
uiReadOnly->mimeIcon->setPixmap(pix);
} else {
ui->mimeIcon->setPixmap(pix);
}
}
void AttachmentPropertiesDialog::Private::populateWhatsThis()
{
// FIXME These are such a mess... Make them straightforward and pretty.
const QString msgMimeType = i18n("<p>The <em>MIME type</em> of the file:</p>"
"<p>Normally, you do not need to touch this setting, since the "
"type of the file is automatically checked; but, sometimes, %1 "
"may not detect the type correctly -- here is where you can fix "
"that.</p>", KAboutData::applicationData().componentName());
const QString msgSize = i18n("<p>The estimated size of the attachment:</p>"
"<p>Note that, in an email message, a binary file encoded with "
"base64 will take up four thirds the actual size of the file.</p>");
const QString msgName = i18n("<p>The file name of the part:</p>"
"<p>Although this defaults to the name of the attached file, "
"it does not specify the file to be attached; rather, it "
"suggests a file name to be used by the recipient's mail agent "
"when saving the part to disk.</p>");
const QString msgDescription = i18n("<p>A description of the part:</p>"
"<p>This is just an informational description of the part, "
"much like the Subject is for the whole message; most "
"mail agents will show this information in their message "
"previews alongside the attachment's icon.</p>");
const QString msgEncoding = i18n("<p>The transport encoding of this part:</p>"
"<p>Normally, you do not need to change this, since %1 will use "
"a decent default encoding, depending on the MIME type; yet, "
"sometimes, you can significantly reduce the size of the "
"resulting message, e.g. if a PostScript file does not contain "
"binary data, but consists of pure text -- in this case, choosing "
"\"quoted-printable\" over the default \"base64\" will save up "
"to 25% in resulting message size.</p>",
KAboutData::applicationData().componentName());
const QString msgAutoDisplay = i18n("<p>Check this option if you want to suggest to the "
"recipient the automatic (inline) display of this part in the "
"message preview, instead of the default icon view;</p>"
"<p>Technically, this is carried out by setting this part's "
"<em>Content-Disposition</em> header field to \"inline\" "
"instead of the default \"attachment\".</p>");
const QString msgSign = i18n("<p>Check this option if you want this message part to be "
"signed.</p>"
"<p>The signature will be made with the key that you associated "
"with the currently-selected identity.</p>");
const QString msgEncrypt = i18n("<p>Check this option if you want this message part to be "
"encrypted.</p>"
"<p>The part will be encrypted for the recipients of this "
"message.</p>");
if (mReadOnly) {
uiReadOnly->size->setWhatsThis(msgSize);
uiReadOnly->name->setWhatsThis(msgName);
uiReadOnly->encoding->setWhatsThis(msgEncoding);
} else {
ui->mimeType->setWhatsThis(msgMimeType);
ui->size->setWhatsThis(msgSize);
ui->name->setWhatsThis(msgName);
ui->encrypt->setWhatsThis(msgEncrypt);
ui->sign->setWhatsThis(msgSign);
ui->autoDisplay->setWhatsThis(msgAutoDisplay);
ui->encoding->setWhatsThis(msgEncoding);
ui->description->setWhatsThis(msgDescription);
}
}
void AttachmentPropertiesDialog::Private::populateEncodings()
{
using namespace KMime;
using namespace KMime::Headers;
ui->encoding->clear();
ui->encoding->addItem(nameForEncoding(CE7Bit), int(CE7Bit));
ui->encoding->addItem(nameForEncoding(CE8Bit), int(CE8Bit));
ui->encoding->addItem(nameForEncoding(CEquPr), int(CEquPr));
ui->encoding->addItem(nameForEncoding(CEbase64), int(CEbase64));
// TODO 8bit should be disabled if it is disabled in Settings.
// Also, if it's a message/* part, base64 and qp should be disabled.
// But since this is a dialog for power users anyway, let them shoot
// themselves in the foot. (The AttachmentJob will fail when they
// try to compose the message.)
}
void AttachmentPropertiesDialog::Private::populateMimeTypes()
{
const QStringList list = QStringList() << QStringLiteral("text/html")
<< QStringLiteral("text/plain")
<< QStringLiteral("image/gif")
<< QStringLiteral("image/jpeg")
<< QStringLiteral("image/png")
<< QStringLiteral("application/octet-stream")
<< QStringLiteral("application/x-gunzip")
<< QStringLiteral("application/zip");
ui->mimeType->addItems(list);
}
void AttachmentPropertiesDialog::Private::loadFromPart()
{
Q_ASSERT(mPart);
if (mReadOnly) {
uiReadOnly->mimeType->setText(QString::fromLatin1(mPart->mimeType()));
mimeTypeChanged(QString::fromLatin1(mPart->mimeType()));
uiReadOnly->size->setText(KFormat().formatByteSize(mPart->size()));
uiReadOnly->name->setText(mPart->name().isEmpty() ? mPart->fileName() : mPart->name());
if (mPart->description().isEmpty()) {
uiReadOnly->description->hide();
uiReadOnly->descriptionLabel->hide();
} else {
uiReadOnly->description->setText(mPart->description());
}
uiReadOnly->encoding->setText(KMime::nameForEncoding(mPart->encoding()));
} else {
const QString mimeType = QString::fromLatin1(mPart->mimeType());
const int index = ui->mimeType->findText(mimeType);
if (index == -1) {
ui->mimeType->insertItem(0, mimeType);
ui->mimeType->setCurrentIndex(0);
} else {
ui->mimeType->setCurrentIndex(index);
}
ui->size->setText(KFormat().formatByteSize(mPart->size()));
ui->name->setText(mPart->name().isEmpty() ? mPart->fileName() : mPart->name());
ui->description->setText(mPart->description());
ui->encoding->setCurrentIndex(int(mPart->encoding()));
ui->autoDisplay->setChecked(mPart->isInline());
ui->encrypt->setChecked(mPart->isEncrypted());
ui->sign->setChecked(mPart->isSigned());
}
}
static QString removeNewlines(const QString &input)
{
QString ret(input);
ret.replace(QLatin1Char('\n'), QLatin1Char(' '));
return ret;
}
void AttachmentPropertiesDialog::Private::saveToPart()
{
Q_ASSERT(mPart);
Q_ASSERT(!mReadOnly);
if (mReadOnly) {
return;
}
mPart->setMimeType(ui->mimeType->currentText().toLatin1());
const QString name = removeNewlines(ui->name->text());
mPart->setName(name);
mPart->setFileName(name);
mPart->setDescription(removeNewlines(ui->description->text()));
mPart->setInline(ui->autoDisplay->isChecked());
mPart->setSigned(ui->sign->isChecked());
mPart->setEncrypted(ui->encrypt->isChecked());
mPart->setInline(ui->autoDisplay->isChecked());
if (ui->mimeType->currentText().startsWith(QLatin1String("message"))
&& ui->encoding->itemData(ui->encoding->currentIndex()) != KMime::Headers::CE7Bit
&& ui->encoding->itemData(ui->encoding->currentIndex()) != KMime::Headers::CE8Bit) {
qCWarning(MESSAGECORE_LOG) << "Encoding on message/rfc822 must be \"7bit\" or \"8bit\".";
}
mPart->setEncoding(KMime::Headers::contentEncoding(
ui->encoding->itemData(ui->encoding->currentIndex()).toInt()));
}
AttachmentPropertiesDialog::AttachmentPropertiesDialog(const AttachmentPart::Ptr &part, bool readOnly, QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
d->init(part, readOnly);
setWindowTitle(i18n("Attachment Properties"));
}
AttachmentPropertiesDialog::AttachmentPropertiesDialog(const KMime::Content *content, QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
AttachmentFromMimeContentJob *job = new AttachmentFromMimeContentJob(content, this);
job->exec();
if (job->error()) {
qCCritical(MESSAGECORE_LOG) << "AttachmentFromMimeContentJob failed." << job->errorString();
}
const AttachmentPart::Ptr part = job->attachmentPart();
d->init(part, true);
setWindowTitle(i18n("Attachment Properties"));
}
AttachmentPropertiesDialog::~AttachmentPropertiesDialog()
{
delete d;
}
AttachmentPart::Ptr AttachmentPropertiesDialog::attachmentPart() const
{
return d->mPart;
}
bool AttachmentPropertiesDialog::isEncryptEnabled() const
{
if (d->ui) {
return d->ui->encrypt->isEnabled();
}
return false;
}
void AttachmentPropertiesDialog::setEncryptEnabled(bool enabled)
{
if (d->ui) {
d->ui->encrypt->setEnabled(enabled);
}
}
bool AttachmentPropertiesDialog::isSignEnabled() const
{
if (d->ui) {
return d->ui->sign->isEnabled();
}
return false;
}
void AttachmentPropertiesDialog::setSignEnabled(bool enabled)
{
if (d->ui) {
d->ui->sign->setEnabled(enabled);
}
}
void AttachmentPropertiesDialog::accept()
{
if (!d->mReadOnly) {
d->saveToPart();
}
QDialog::accept();
}
void AttachmentPropertiesDialog::slotHelp()
{
PimCommon::Util::invokeHelp(QStringLiteral("kmail2/the-composer-window.html"), QStringLiteral("attachments"));
}
#include "moc_attachmentpropertiesdialog.cpp"
diff --git a/messagecore/src/attachment/attachmentupdatejob.cpp b/messagecore/src/attachment/attachmentupdatejob.cpp
index 537e59c3..48ac85bf 100644
--- a/messagecore/src/attachment/attachmentupdatejob.cpp
+++ b/messagecore/src/attachment/attachmentupdatejob.cpp
@@ -1,118 +1,118 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "attachmentfromfolderjob.h"
#include "MessageCore/AttachmentFromUrlBaseJob"
#include "attachmentfromurljob.h"
#include "attachmentupdatejob.h"
#include "attachmentfromurlutils.h"
#include "messagecore_debug.h"
#include <QTimer>
#include <KLocalizedString>
using namespace MessageCore;
class Q_DECL_HIDDEN MessageCore::AttachmentUpdateJob::Private
{
public:
Private(AttachmentUpdateJob *qq);
void doStart(); // slot
void loadJobResult(KJob *);
AttachmentUpdateJob *const q;
AttachmentPart::Ptr mOriginalPart;
AttachmentPart::Ptr mUpdatedPart;
};
AttachmentUpdateJob::Private::Private(AttachmentUpdateJob *qq)
: q(qq)
{
}
void AttachmentUpdateJob::Private::doStart()
{
Q_ASSERT(mOriginalPart);
if (mOriginalPart->url().isEmpty()) {
qCDebug(MESSAGECORE_LOG) << " url is empty. We can't update file";
q->setError(KJob::UserDefinedError);
q->setErrorText(i18n("URL is empty."));
q->emitResult();
return;
}
MessageCore::AttachmentFromUrlBaseJob *job = MessageCore::AttachmentFromUrlUtils::createAttachmentJob(mOriginalPart->url(), q);
connect(job, &AttachmentFromUrlBaseJob::result, q, [this](KJob *job) {
loadJobResult(job);
});
job->start();
}
void AttachmentUpdateJob::Private::loadJobResult(KJob *job)
{
if (job->error()) {
q->setError(KJob::UserDefinedError);
q->setErrorText(job->errorString());
q->emitResult();
return;
}
Q_ASSERT(dynamic_cast<AttachmentLoadJob *>(job));
AttachmentLoadJob *ajob = static_cast<AttachmentLoadJob *>(job);
mUpdatedPart = ajob->attachmentPart();
mUpdatedPart->setName(q->originalPart()->name());
mUpdatedPart->setFileName(q->originalPart()->fileName());
mUpdatedPart->setDescription(q->originalPart()->description());
mUpdatedPart->setSigned(q->originalPart()->isSigned());
mUpdatedPart->setEncrypted(q->originalPart()->isEncrypted());
mUpdatedPart->setEncoding(q->originalPart()->encoding());
mUpdatedPart->setMimeType(q->originalPart()->mimeType());
mUpdatedPart->setInline(q->originalPart()->isInline());
q->emitResult(); // Success.
}
AttachmentUpdateJob::AttachmentUpdateJob(const AttachmentPart::Ptr &part, QObject *parent)
: KJob(parent)
, d(new Private(this))
{
d->mOriginalPart = part;
}
AttachmentUpdateJob::~AttachmentUpdateJob()
{
delete d;
}
void AttachmentUpdateJob::start()
{
QTimer::singleShot(0, this, [this]() {
d->doStart();
});
}
AttachmentPart::Ptr AttachmentUpdateJob::originalPart() const
{
return d->mOriginalPart;
}
AttachmentPart::Ptr AttachmentUpdateJob::updatedPart() const
{
return d->mUpdatedPart;
}
#include "moc_attachmentupdatejob.cpp"
diff --git a/messagecore/src/attachment/attachmentupdatejob.h b/messagecore/src/attachment/attachmentupdatejob.h
index 93f9936e..253e1641 100644
--- a/messagecore/src/attachment/attachmentupdatejob.h
+++ b/messagecore/src/attachment/attachmentupdatejob.h
@@ -1,45 +1,45 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef ATTACHMENTUPDATEJOB_H
#define ATTACHMENTUPDATEJOB_H
#include "messagecore_export.h"
#include <KJob>
#include "attachmentpart.h"
namespace MessageCore {
class MESSAGECORE_EXPORT AttachmentUpdateJob : public KJob
{
Q_OBJECT
public:
explicit AttachmentUpdateJob(const AttachmentPart::Ptr &part, QObject *parent = nullptr);
~AttachmentUpdateJob() override;
void start() override;
Q_REQUIRED_RESULT AttachmentPart::Ptr originalPart() const;
Q_REQUIRED_RESULT AttachmentPart::Ptr updatedPart() const;
private:
//@cond PRIVATE
class Private;
Private *const d;
};
}
#endif // ATTACHMENTUPDATEJOB_H
diff --git a/messagecore/src/helpers/nodehelper.h b/messagecore/src/helpers/nodehelper.h
index 8caac2b4..90512468 100644
--- a/messagecore/src/helpers/nodehelper.h
+++ b/messagecore/src/helpers/nodehelper.h
@@ -1,58 +1,58 @@
/*
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
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.
*/
#ifndef MESSAGECORE_NODEHELPER_H
#define MESSAGECORE_NODEHELPER_H
#include "messagecore_export.h"
namespace KMime {
class Content;
class Message;
}
namespace MessageCore {
/**
- * @short Contains some static functions for nagivating in KMime::Node trees.
+ * @short Contains some static functions for navigating in KMime::Node trees.
*/
namespace NodeHelper {
/**
* Returns the next sibling node of the given @p node.
* If there is no sibling node @c 0 is returned.
*/
MESSAGECORE_EXPORT KMime::Content *nextSibling(const KMime::Content *node);
/**
* Returns the next node (child, sibling or parent) of the given @p node.
*
* @param node The start node for iteration.
* @param allowChildren If @c true child nodes will be returned, otherwise only sibling or parent nodes.
*/
MESSAGECORE_EXPORT KMime::Content *next(KMime::Content *node, bool allowChildren = true);
/**
* Returns the first child node of the given @p node.
* If there is no child node @c 0 is returned.
*/
MESSAGECORE_EXPORT KMime::Content *firstChild(const KMime::Content *node);
}
}
#endif
diff --git a/messagelist/src/messagelist_private_export.h b/messagecore/src/messagecore_private_export.h
similarity index 76%
copy from messagelist/src/messagelist_private_export.h
copy to messagecore/src/messagecore_private_export.h
index 41b9438d..9efae9f6 100644
--- a/messagelist/src/messagelist_private_export.h
+++ b/messagecore/src/messagecore_private_export.h
@@ -1,34 +1,34 @@
/* This file is part of the KDE project
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2019 Laurent Montel <montel@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.
*/
-#ifndef MESSAGELISTPRIVATE_EXPORT_H
-#define MESSAGELISTPRIVATE_EXPORT_H
+#ifndef MESSAGECOREPRIVATE_EXPORT_H
+#define MESSAGECOREPRIVATE_EXPORT_H
-#include "messagelist_export.h"
+#include "messagecore_export.h"
/* Classes which are exported only for unit tests */
#ifdef BUILD_TESTING
-# ifndef MESSAGELIST_TESTS_EXPORT
-# define MESSAGELIST_TESTS_EXPORT MESSAGELIST_EXPORT
+# ifndef MESSAGECORE_TESTS_EXPORT
+# define MESSAGECORE_TESTS_EXPORT MESSAGECORE_EXPORT
# endif
#else /* not compiling tests */
-# define MESSAGELIST_TESTS_EXPORT
+# define MESSAGECORE_TESTS_EXPORT
#endif
#endif
diff --git a/messagecore/src/messagecoreutil.cpp b/messagecore/src/messagecoreutil.cpp
index 61287c90..19ccd7a9 100644
--- a/messagecore/src/messagecoreutil.cpp
+++ b/messagecore/src/messagecoreutil.cpp
@@ -1,143 +1,143 @@
/*
* Copyright (C) 2015 Daniel Vrátil <dvratil@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "messagecoreutil.h"
#include <KColorScheme>
#include <QApplication>
using namespace MessageCore;
static bool isLightTheme()
{
- return qApp->palette().color(QPalette::Background).value() >= 128;
+ return qApp->palette().color(QPalette::Window).value() >= 128;
}
Q_GLOBAL_STATIC(ColorUtil, s_self)
ColorUtil *ColorUtil::self()
{
return s_self;
}
ColorUtil::ColorUtil()
{
initializeColors();
}
void ColorUtil::updateColors()
{
initializeColors();
}
void ColorUtil::initializeColors()
{
KColorScheme scheme(QPalette::Active, KColorScheme::View);
mMisspelledDefaultTextColor = scheme.foreground(KColorScheme::NegativeText).color().lighter();
auto base = scheme.foreground(KColorScheme::PositiveText).color();
if (isLightTheme()) {
mQuoteLevel1DefaultTextColor = base.darker(120);
mQuoteLevel2DefaultTextColor = base.darker(150);
mQuoteLevel3DefaultTextColor = base.darker(200);
} else {
mQuoteLevel1DefaultTextColor = base.lighter(200);
mQuoteLevel2DefaultTextColor = base.lighter(170);
mQuoteLevel3DefaultTextColor = base.lighter(140);
}
if (isLightTheme()) {
mPgpEncryptedMessageColor = QColor(0x00, 0x80, 0xFF).lighter(180);
mPgpEncryptedTextColor = QColor(0x00, 0x80, 0xFF).darker(200);
} else {
mPgpEncryptedMessageColor = QColor(0x00, 0x80, 0xFF).darker(300);
mPgpEncryptedTextColor = QColor(0x00, 0x80, 0xFF).lighter(170);
}
mPgpSignedTrustedMessageColor = scheme.background(KColorScheme::PositiveBackground).color();
mPgpSignedTrustedTextColor = scheme.foreground(KColorScheme::PositiveText).color();
mPgpSignedUntrustedMessageColor = scheme.background(KColorScheme::NeutralBackground).color();
mPgpSignedUntrustedTextColor = scheme.foreground(KColorScheme::NeutralText).color();
mPgpSignedBadMessageColor = scheme.background(KColorScheme::NegativeBackground).color();
mPgpSignedBadTextColor = scheme.foreground(KColorScheme::NegativeText).color();
mLinkColor = scheme.foreground(KColorScheme::LinkText).color();
}
QColor ColorUtil::misspelledDefaultTextColor() const
{
return mMisspelledDefaultTextColor;
}
QColor ColorUtil::quoteLevel1DefaultTextColor() const
{
return mQuoteLevel1DefaultTextColor;
}
QColor ColorUtil::quoteLevel2DefaultTextColor() const
{
return mQuoteLevel2DefaultTextColor;
}
QColor ColorUtil::quoteLevel3DefaultTextColor() const
{
return mQuoteLevel3DefaultTextColor;
}
QColor ColorUtil::pgpSignedTrustedMessageColor() const
{
return mPgpSignedTrustedMessageColor;
}
QColor ColorUtil::pgpSignedTrustedTextColor() const
{
return mPgpSignedTrustedTextColor;
}
QColor ColorUtil::pgpSignedUntrustedMessageColor() const
{
return mPgpSignedUntrustedMessageColor;
}
QColor ColorUtil::pgpSignedUntrustedTextColor() const
{
return mPgpSignedUntrustedTextColor;
}
QColor ColorUtil::pgpSignedBadMessageColor() const
{
return mPgpSignedBadMessageColor;
}
QColor ColorUtil::pgpSignedBadTextColor() const
{
return mPgpSignedBadTextColor;
}
QColor ColorUtil::pgpEncryptedMessageColor() const
{
return mPgpEncryptedMessageColor;
}
QColor ColorUtil::pgpEncryptedTextColor() const
{
return mPgpEncryptedTextColor;
}
QColor ColorUtil::linkColor() const
{
return mLinkColor;
}
diff --git a/messagecore/src/misc/imagecollector.cpp b/messagecore/src/misc/imagecollector.cpp
index 6d0708d4..4f325db5 100644
--- a/messagecore/src/misc/imagecollector.cpp
+++ b/messagecore/src/misc/imagecollector.cpp
@@ -1,105 +1,106 @@
/* -*- c++ -*-
* imagecollector.cpp
*
* This file is part of KMail, the KDE mail client.
* Copyright (c) 2004 Marc Mutz <mutz@kde.org>
* Copyright (c) 2011 Torgny Nyblom <nyblom@kde.org>
*
* 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.
+ * 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.
*
* 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.
*/
#include "imagecollector.h"
#include "MessageCore/NodeHelper"
#include "messagecore_debug.h"
#include <kmime/kmime_content.h>
static bool isInExclusionList(KMime::Content *node)
{
if (!node) {
return true;
}
if (node->contentType()->mediaType() != "image") {
return true;
}
if (node->contentType()->isMultipart()) {
return true;
}
return false;
}
class Q_DECL_HIDDEN MessageCore::ImageCollector::Private
{
public:
std::vector<KMime::Content *> mImages;
};
MessageCore::ImageCollector::ImageCollector()
: d(new Private)
{
}
MessageCore::ImageCollector::~ImageCollector()
{
delete d;
}
void MessageCore::ImageCollector::collectImagesFrom(KMime::Content *node)
{
KMime::Content *parent = nullptr;
while (node) {
parent = node->parent();
if (node->topLevel()->textContent() == node) {
node = MessageCore::NodeHelper::next(node);
continue;
}
if (isInExclusionList(node)) {
node = MessageCore::NodeHelper::next(node);
continue;
}
if (parent && parent->contentType()->isMultipart()
&& parent->contentType()->subType() == "related") {
qCWarning(MESSAGECORE_LOG) << "Adding image" << node->contentID();
d->mImages.push_back(node);
node = MessageCore::NodeHelper::next(node); // skip embedded images
continue;
}
node = MessageCore::NodeHelper::next(node);
}
}
const std::vector<KMime::Content *> &MessageCore::ImageCollector::images() const
{
return d->mImages;
}
diff --git a/messagecore/src/misc/imagecollector.h b/messagecore/src/misc/imagecollector.h
index 90c31b6c..7a47f93a 100644
--- a/messagecore/src/misc/imagecollector.h
+++ b/messagecore/src/misc/imagecollector.h
@@ -1,88 +1,89 @@
/* -*- c++ -*-
* imagecollector.h
*
* This file is part of KMail, the KDE mail client.
* Copyright (c) 2004 Marc Mutz <mutz@kde.org>
* Copyright (c) 2011 Torgny Nyblom <nyblom@kde.org>
*
* 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.
+ * 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.
*
* 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.
*/
#ifndef MESSAGECORE_IMAGECOLLECTOR_H
#define MESSAGECORE_IMAGECOLLECTOR_H
#include "messagecore_export.h"
#include <vector>
#include <QObject>
namespace KMime {
class Content;
}
namespace MessageCore {
/**
* @short A helper class to collect the embedded images of a email.
*
* @author Marc Mutz <mutz@kde.org>
* @author Torgny Nyblom <nyblom@kde.org>
* @todo: Make it a simple static method?!?
*/
class MESSAGECORE_EXPORT ImageCollector
{
public:
/**
* Creates a new image collector.
*/
ImageCollector();
/**
* Destroys the image collector.
*/
~ImageCollector();
/**
* Starts collecting the images.
*
* @param content The email content that contains the images.
*/
void collectImagesFrom(KMime::Content *content);
/**
* Returns the collected images.
*/
Q_REQUIRED_RESULT const std::vector<KMime::Content *> &images() const;
private:
//@cond PRIVATE
class Private;
Private *const d;
Q_DISABLE_COPY(ImageCollector)
//@endcond
};
}
#endif
diff --git a/messagecore/src/misc/mailinglist.cpp b/messagecore/src/misc/mailinglist.cpp
index 450be3ff..70c2670e 100644
--- a/messagecore/src/misc/mailinglist.cpp
+++ b/messagecore/src/misc/mailinglist.cpp
@@ -1,614 +1,613 @@
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "mailinglist.h"
#include <kconfig.h>
#include <kconfiggroup.h>
#include <QUrl>
#include "messagecore_debug.h"
#include <QSharedData>
#include <QStringList>
using namespace MessageCore;
typedef QString (*MagicDetectorFunc)(const KMime::Message::Ptr &, QByteArray &, QString &);
/* Sender: (owner-([^@]+)|([^@+]-owner)@ */
static QString check_sender(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header = message->sender()->asUnicodeString();
if (header.isEmpty()) {
return QString();
}
if (header.left(6) == QLatin1String("owner-")) {
headerName = "Sender";
headerValue = header;
header = header.mid(6, header.indexOf(QLatin1Char('@')) - 6);
} else {
const int index = header.indexOf(QLatin1String("-owner@ "));
if (index == -1) {
return QString();
}
header.truncate(index);
headerName = "Sender";
headerValue = header;
}
return header;
}
/* X-BeenThere: ([^@]+) */
static QString check_x_beenthere(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("X-BeenThere")) {
header = hrd->asUnicodeString();
}
if (header.isNull() || header.indexOf(QLatin1Char('@')) == -1) {
return QString();
}
headerName = "X-BeenThere";
headerValue = header;
header.truncate(header.indexOf(QLatin1Char('@')));
return header;
}
/* Delivered-To:: <([^@]+) */
static QString check_delivered_to(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("Delivered-To")) {
header = hrd->asUnicodeString();
}
if (header.isNull()
|| header.left(13) != QLatin1String("mailing list")
|| header.indexOf(QLatin1Char('@')) == -1) {
return QString();
}
headerName = "Delivered-To";
headerValue = header;
return header.mid(13, header.indexOf(QLatin1Char('@')) - 13);
}
/* X-Mailing-List: <?([^@]+) */
static QString check_x_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("X-Mailing-List")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
if (header.indexOf(QLatin1Char('@')) < 1) {
return QString();
}
headerName = "X-Mailing-List";
headerValue = header;
if (header[0] == QLatin1Char('<')) {
header = header.mid(1, header.indexOf(QLatin1Char('@')) - 1);
} else {
header.truncate(header.indexOf(QLatin1Char('@')));
}
return header;
}
/* List-Id: [^<]* <([^.]+) */
static QString check_list_id(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("List-Id")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
const int leftAnglePos = header.indexOf(QLatin1Char('<'));
if (leftAnglePos < 0) {
return QString();
}
const int firstDotPos = header.indexOf(QLatin1Char('.'), leftAnglePos);
if (firstDotPos < 0) {
return QString();
}
headerName = "List-Id";
headerValue = header.mid(leftAnglePos);
header = header.mid(leftAnglePos + 1, firstDotPos - leftAnglePos - 1);
return header;
}
/* List-Post: <mailto:[^< ]*>) */
static QString check_list_post(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("List-Post")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
int leftAnglePos = header.indexOf(QLatin1String("<mailto:"));
if (leftAnglePos < 0) {
return QString();
}
headerName = "List-Post";
headerValue = header;
header = header.mid(leftAnglePos + 8, header.length());
header.truncate(header.indexOf(QLatin1Char('@')));
return header;
}
/* Mailing-List: list ([^@]+) */
static QString check_mailing_list(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("Mailing-List")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
if (header.left(5) != QLatin1String("list ")
|| header.indexOf(QLatin1Char('@')) < 5) {
return QString();
}
headerName = "Mailing-List";
headerValue = header;
header = header.mid(5, header.indexOf(QLatin1Char('@')) - 5);
return header;
}
/* X-Loop: ([^@]+) */
static QString check_x_loop(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("X-Loop")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
const int indexOfHeader(header.indexOf(QLatin1Char('@')));
if (indexOfHeader < 2) {
return QString();
}
headerName = "X-Loop";
headerValue = header;
header.truncate(indexOfHeader);
return header;
}
/* X-ML-Name: (.+) */
static QString check_x_ml_name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString header;
if (auto hrd = message->headerByType("X-ML-Name")) {
header = hrd->asUnicodeString();
}
if (header.isEmpty()) {
return QString();
}
headerName = "X-ML-Name";
headerValue = header;
header.truncate(header.indexOf(QLatin1Char('@')));
return header;
}
static const MagicDetectorFunc magic_detector[] = {
check_list_id,
check_list_post,
check_sender,
check_x_mailing_list,
check_mailing_list,
check_delivered_to,
check_x_beenthere,
check_x_loop,
check_x_ml_name
};
static const int num_detectors = sizeof(magic_detector) / sizeof(magic_detector[0]);
static QStringList headerToAddress(const QString &header)
{
-
QStringList addresses;
if (header.isEmpty()) {
return addresses;
}
int start = 0;
while ((start = header.indexOf(QLatin1Char('<'), start)) != -1) {
int end = 0;
if ((end = header.indexOf(QLatin1Char('>'), ++start)) == -1) {
qCWarning(MESSAGECORE_LOG) << "Serious mailing list header parsing error!";
return addresses;
}
addresses.append(header.mid(start, end - start));
}
return addresses;
}
class Q_DECL_HIDDEN MessageCore::MailingList::Private : public QSharedData
{
public:
Private()
: mFeatures(None)
, mHandler(KMail)
{
}
Private(const Private &other)
: QSharedData(other)
{
mFeatures = other.mFeatures;
mHandler = other.mHandler;
mPostUrls = other.mPostUrls;
mSubscribeUrls = other.mSubscribeUrls;
mUnsubscribeUrls = other.mUnsubscribeUrls;
mHelpUrls = other.mHelpUrls;
mArchiveUrls = other.mArchiveUrls;
mOwnerUrls = other.mOwnerUrls;
mArchivedAtUrls = other.mArchivedAtUrls;
mId = other.mId;
}
Features mFeatures;
Handler mHandler;
QList<QUrl> mPostUrls;
QList<QUrl> mSubscribeUrls;
QList<QUrl> mUnsubscribeUrls;
QList<QUrl> mHelpUrls;
QList<QUrl> mArchiveUrls;
QList<QUrl> mOwnerUrls;
QList<QUrl> mArchivedAtUrls;
QString mId;
};
MailingList MailingList::detect(const KMime::Message::Ptr &message)
{
MailingList mailingList;
if (auto hrd = message->headerByType("List-Post")) {
mailingList.setPostUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Help")) {
mailingList.setHelpUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Subscribe")) {
mailingList.setSubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Unsubscribe")) {
mailingList.setUnsubscribeUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Archive")) {
mailingList.setArchiveUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Owner")) {
mailingList.setOwnerUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("Archived-At")) {
mailingList.setArchivedAtUrls(QUrl::fromStringList(headerToAddress(hrd->asUnicodeString())));
}
if (auto hrd = message->headerByType("List-Id")) {
mailingList.setId(hrd->asUnicodeString());
}
return mailingList;
}
QString MailingList::name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue)
{
QString mailingList;
headerName = QByteArray();
headerValue.clear();
if (!message) {
return QString();
}
for (int i = 0; i < num_detectors; ++i) {
mailingList = magic_detector[i](message, headerName, headerValue);
if (!mailingList.isNull()) {
return mailingList;
}
}
return QString();
}
MailingList::MailingList()
: d(new Private)
{
}
MailingList::MailingList(const MailingList &other)
: d(other.d)
{
}
MailingList &MailingList::operator=(const MailingList &other)
{
if (this != &other) {
d = other.d;
}
return *this;
}
bool MailingList::operator==(const MailingList &other) const
{
return other.features() == d->mFeatures
&& other.handler() == d->mHandler
&& other.postUrls() == d->mPostUrls
&& other.subscribeUrls() == d->mSubscribeUrls
&& other.unsubscribeUrls() == d->mUnsubscribeUrls
&& other.helpUrls() == d->mHelpUrls
&& other.archiveUrls() == d->mArchiveUrls
&& other.ownerUrls() == d->mOwnerUrls
&& other.archivedAtUrls() == d->mArchivedAtUrls
&& other.id() == d->mId;
}
MailingList::~MailingList()
{
}
MailingList::Features MailingList::features() const
{
return d->mFeatures;
}
void MailingList::setHandler(MailingList::Handler handler)
{
d->mHandler = handler;
}
MailingList::Handler MailingList::handler() const
{
return d->mHandler;
}
void MailingList::setPostUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Post;
if (urls.empty()) {
d->mFeatures ^= Post;
}
d->mPostUrls = urls;
}
QList<QUrl> MailingList::postUrls() const
{
return d->mPostUrls;
}
void MailingList::setSubscribeUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Subscribe;
if (urls.empty()) {
d->mFeatures ^= Subscribe;
}
d->mSubscribeUrls = urls;
}
QList<QUrl> MailingList::subscribeUrls() const
{
return d->mSubscribeUrls;
}
void MailingList::setUnsubscribeUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Unsubscribe;
if (urls.empty()) {
d->mFeatures ^= Unsubscribe;
}
d->mUnsubscribeUrls = urls;
}
QList<QUrl> MailingList::unsubscribeUrls() const
{
return d->mUnsubscribeUrls;
}
void MailingList::setHelpUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Help;
if (urls.empty()) {
d->mFeatures ^= Help;
}
d->mHelpUrls = urls;
}
QList<QUrl> MailingList::helpUrls() const
{
return d->mHelpUrls;
}
void MailingList::setArchiveUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Archive;
if (urls.empty()) {
d->mFeatures ^= Archive;
}
d->mArchiveUrls = urls;
}
QList<QUrl> MailingList::archiveUrls() const
{
return d->mArchiveUrls;
}
void MailingList::setOwnerUrls(const QList<QUrl> &urls)
{
d->mFeatures |= Owner;
if (urls.empty()) {
d->mFeatures ^= Owner;
}
d->mOwnerUrls = urls;
}
QList<QUrl> MailingList::ownerUrls() const
{
return d->mOwnerUrls;
}
void MailingList::setArchivedAtUrls(const QList<QUrl> &urls)
{
d->mFeatures |= ArchivedAt;
if (urls.isEmpty()) {
d->mFeatures ^= ArchivedAt;
}
d->mArchivedAtUrls = urls;
}
QList<QUrl> MailingList::archivedAtUrls() const
{
return d->mArchivedAtUrls;
}
void MailingList::setId(const QString &id)
{
d->mFeatures |= Id;
if (id.isEmpty()) {
d->mFeatures ^= Id;
}
d->mId = id;
}
QString MailingList::id() const
{
return d->mId;
}
void MailingList::writeConfig(KConfigGroup &group) const
{
group.writeEntry("MailingListFeatures", static_cast<int>(d->mFeatures));
group.writeEntry("MailingListHandler", static_cast<int>(d->mHandler));
group.writeEntry("MailingListId", d->mId);
QStringList lst = QUrl::toStringList(d->mPostUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListPostingAddress", lst);
} else {
group.deleteEntry("MailingListPostingAddress");
}
lst = QUrl::toStringList(d->mSubscribeUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListSubscribeAddress", lst);
} else {
group.deleteEntry("MailingListSubscribeAddress");
}
lst = QUrl::toStringList(d->mUnsubscribeUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListUnsubscribeAddress", lst);
} else {
group.deleteEntry("MailingListUnsubscribeAddress");
}
lst = QUrl::toStringList(d->mArchiveUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListArchiveAddress", lst);
} else {
group.deleteEntry("MailingListArchiveAddress");
}
lst = QUrl::toStringList(d->mOwnerUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListOwnerAddress", lst);
} else {
group.deleteEntry("MailingListOwnerAddress");
}
lst = QUrl::toStringList(d->mHelpUrls);
if (!lst.isEmpty()) {
group.writeEntry("MailingListHelpAddress", lst);
} else {
group.deleteEntry("MailingListHelpAddress");
}
/* Note: mArchivedAtUrl deliberately not saved here as it refers to a single
* instance of a message rather than an element of a general mailing list.
* http://reviewboard.kde.org/r/1768/#review2783
*/
}
void MailingList::readConfig(const KConfigGroup &group)
{
d->mFeatures = static_cast<MailingList::Features>(group.readEntry("MailingListFeatures", 0));
d->mHandler = static_cast<MailingList::Handler>(group.readEntry("MailingListHandler",
static_cast<int>(MailingList::KMail)));
d->mId = group.readEntry("MailingListId");
d->mPostUrls = QUrl::fromStringList(group.readEntry("MailingListPostingAddress", QStringList()));
d->mSubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListSubscribeAddress", QStringList()));
d->mUnsubscribeUrls = QUrl::fromStringList(group.readEntry("MailingListUnsubscribeAddress", QStringList()));
d->mArchiveUrls = QUrl::fromStringList(group.readEntry("MailingListArchiveAddress", QStringList()));
d->mOwnerUrls = QUrl::fromStringList(group.readEntry("MailingListOwnerAddress", QStringList()));
d->mHelpUrls = QUrl::fromStringList(group.readEntry("MailingListHelpAddress", QStringList()));
}
diff --git a/messagecore/src/misc/mailinglist.h b/messagecore/src/misc/mailinglist.h
index 44929a1c..ca2e2198 100644
--- a/messagecore/src/misc/mailinglist.h
+++ b/messagecore/src/misc/mailinglist.h
@@ -1,214 +1,214 @@
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef MESSAGECORE_MAILINGLIST_H
#define MESSAGECORE_MAILINGLIST_H
#include "messagecore_export.h"
#include <kmime/kmime_message.h>
#include <QUrl>
#include <QByteArray>
#include <QSharedDataPointer>
#include <QString>
class KConfigGroup;
namespace MessageCore {
/**
* @short A class to extract information about mailing lists from emails.
*
* The mailing list header fields are defined as the following:
* - "List-*" in RFC2369
* - "List-ID" in RFC2919.
* - "Archive-At" in RFC5064
*
* @author Zack Rusin <zack@kde.org>
*/
class MESSAGECORE_EXPORT MailingList
{
public:
/**
* Defines what entity should manage the mailing list.
*/
enum Handler {
KMail, ///< The list is handled by KMail
Browser ///< The list is handled by a browser.
};
/**
- * Defines the features a mailinglist can suppport.
+ * Defines the features a mailinglist can support.
*/
enum Feature {
None = 0 << 0, ///< No mailing list fields exist.
Post = 1 << 0, ///< List-Post header exists.
Subscribe = 1 << 1, ///< List-Subscribe header exists.
Unsubscribe = 1 << 2, ///< List-Unsubscribe header exists.
Help = 1 << 3, ///< List-Help header exists.
Archive = 1 << 4, ///< List-Archive header exists.
Id = 1 << 5, ///< List-ID header exists.
Owner = 1 << 6, ///< List-Owner header exists.
ArchivedAt = 1 << 7 ///< Archive-At header exists.
};
Q_DECLARE_FLAGS(Features, Feature)
public:
/**
* Extracts the information about a mailing list from the given @p message.
*/
static MailingList detect(const KMime::Message::Ptr &message);
static QString name(const KMime::Message::Ptr &message, QByteArray &headerName, QString &headerValue);
public:
/**
* Creates an empty mailing list.
*/
MailingList();
/**
* Creates a mailing list from an @p other mailing list.
*/
MailingList(const MailingList &other);
/**
* Overwrites this mailing list with an @p other mailing list.
*/
MailingList &operator=(const MailingList &other);
Q_REQUIRED_RESULT bool operator==(const MailingList &other) const;
/**
* Destroys the mailing list.
*/
~MailingList();
/**
* Returns the features the mailing list supports.
*/
Q_REQUIRED_RESULT Features features() const;
/**
* Sets the @p handler for the mailing list.
*/
void setHandler(Handler handler);
/**
* Returns the handler for the mailing list.
*/
Q_REQUIRED_RESULT Handler handler() const;
/**
* Sets the list of List-Post @p urls.
*/
void setPostUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Post urls.
*/
Q_REQUIRED_RESULT QList<QUrl> postUrls() const;
/**
* Sets the list of List-Subscribe @p urls.
*/
void setSubscribeUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Subscribe urls.
*/
Q_REQUIRED_RESULT QList<QUrl> subscribeUrls() const;
/**
* Sets the list of List-Unsubscribe @p urls.
*/
void setUnsubscribeUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Unsubscribe urls.
*/
Q_REQUIRED_RESULT QList<QUrl> unsubscribeUrls() const;
/**
* Sets the list of List-Help @p urls.
*/
void setHelpUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Help urls.
*/
Q_REQUIRED_RESULT QList<QUrl> helpUrls() const;
/**
* Sets the list of List-Archive @p urls.
*/
void setArchiveUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Archive urls.
*/
Q_REQUIRED_RESULT QList<QUrl> archiveUrls() const;
/**
* Sets the list of List-Owner @p urls.
*/
void setOwnerUrls(const QList<QUrl> &urls);
/**
* Returns the list of List-Owner urls.
*/
Q_REQUIRED_RESULT QList<QUrl> ownerUrls() const;
/**
* Sets the Archived-At @p url.
*/
void setArchivedAtUrls(const QList<QUrl> &url);
/**
* Returns the Archived-At @p url.
*/
Q_REQUIRED_RESULT QList<QUrl> archivedAtUrls() const;
/**
* Sets the @p id of the mailing list.
*/
void setId(const QString &id);
/**
* Returns the @p id of the mailing list.
*/
Q_REQUIRED_RESULT QString id() const;
/**
* Saves the configuration for the mailing list to the config @p group.
*/
void writeConfig(KConfigGroup &group) const;
/**
* Restores the configuration for the mailing list from the config @p group.
*/
void readConfig(const KConfigGroup &group);
private:
class Private;
QSharedDataPointer<Private> d;
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(MessageCore::MailingList::Features)
Q_DECLARE_METATYPE(MessageCore::MailingList::Features)
#endif
diff --git a/messagecore/src/settings/messagecore.kcfg b/messagecore/src/settings/messagecore.kcfg
index be1d24c3..174b23d7 100644
--- a/messagecore/src/settings/messagecore.kcfg
+++ b/messagecore/src/settings/messagecore.kcfg
@@ -1,70 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<include>qtextcodec.h</include>
<include>QFontDatabase</include>
<kcfgfile name="messagecorerc" />
<group name="General">
<entry name="dateFormat" type="Int">
<default code="true">int( KMime::DateFormatter::Fancy )</default>
</entry>
<entry name="customDateFormat" type="String">
</entry>
</group>
<group name="Fonts">
<entry name="UseDefaultFonts" type="Bool" key="defaultFonts">
<default>true</default>
</entry>
</group>
<group name="Reader">
<entry name="UseDefaultColors" type="Bool" key="defaultColors">
<default>true</default>
</entry>
+ <entry name="UseRealHtmlMailColor" type="Bool" key="realHtmlMailColor">
+ <default>false</default>
+ </entry>
+
<entry name="OverrideCharacterEncoding" type="String" key="encoding">
<default></default>
<whatsthis>Changing this from its default 'Auto' will force the use of the specified encoding for all emails, regardless of what they specify themselves.</whatsthis>
</entry>
<entry name="QuotedText3" type="Color">
<label>This is the color used in the 3rd level of quoted text</label>
<default code="true">MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor()</default>
</entry>
<entry name="QuotedText2" type="Color">
<label>This is the color used in the 2nd level of quoted text</label>
<default code="true">MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor()</default>
</entry>
<entry name="QuotedText1" type="Color">
<label>This is the color used in the 1st level of quoted text</label>
<default code="true">MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor()</default>
</entry>
</group>
<group name="Composer">
<entry name="ReplyPrefixes" type="StringList" key="reply-prefixes">
<default>Re\\s*:,Re\\[\\d+\\]:,Re\\d+:</default>
</entry>
<entry name="ReplaceReplyPrefix" type="Bool" key="replace-reply-prefix">
<label>Replace recognized prefi&amp;x with "Re:"</label>
<default>true</default>
</entry>
<entry name="ForwardPrefixes" type="StringList" key="forward-prefixes">
<default>Fwd:,FW:</default>
</entry>
<entry name="ReplaceForwardPrefix" type="Bool" key="replace-forward-prefix">
<label>Replace recognized prefix with "&amp;Fwd:"</label>
<default>true</default>
</entry>
<entry name="MaximumAttachmentSize" type="Int">
<label>The maximum size in bits that email attachments are allowed to have (-1 for no limit)</label>
<default>-1</default>
</entry>
</group>
</kcfg>
diff --git a/messagecore/src/utils/stringutil.cpp b/messagecore/src/utils/stringutil.cpp
index 604e4041..a4d820fb 100644
--- a/messagecore/src/utils/stringutil.cpp
+++ b/messagecore/src/utils/stringutil.cpp
@@ -1,778 +1,806 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@kde.org>
Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "stringutil.h"
#include "config-enterprise.h"
#include "MessageCore/MessageCoreSettings"
#include <kmime/kmime_header_parsing.h>
#include <kmime/kmime_headers.h>
#include <kmime/kmime_message.h>
#include <KEmailAddress>
#include <KLocalizedString>
#include "messagecore_debug.h"
#include <KUser>
#include <KIdentityManagement/IdentityManager>
#include <KIdentityManagement/Identity>
#include <QHostInfo>
#include <QRegExp>
#include <QRegularExpression>
#include <QStringList>
#include <QUrlQuery>
#include <kpimtextedit/textutils.h>
using namespace KMime;
using namespace KMime::Types;
using namespace KMime::HeaderParsing;
namespace MessageCore {
namespace StringUtil {
// Removes trailing spaces and tabs at the end of the line
static void removeTrailingSpace(QString &line)
{
int i = line.length() - 1;
while ((i >= 0) && ((line[i] == QLatin1Char(' ')) || (line[i] == QLatin1Char('\t')))) {
i--;
}
line.truncate(i + 1);
}
// Splits the line off in two parts: The quote prefixes and the actual text of the line.
// For example, for the string "> > > Hello", it would be split up in "> > > " as the quote
// prefix, and "Hello" as the actual text.
// The actual text is written back to the "line" parameter, and the quote prefix is returned.
static QString splitLine(QString &line)
{
removeTrailingSpace(line);
int i = 0;
int startOfActualText = -1;
// TODO: Replace tabs with spaces first.
// Loop through the chars in the line to find the place where the quote prefix stops
const int lineLength(line.length());
while (i < lineLength) {
const QChar c = line[i];
const bool isAllowedQuoteChar = (c == QLatin1Char('>')) || (c == QLatin1Char(':')) || (c == QLatin1Char('|'))
|| (c == QLatin1Char(' ')) || (c == QLatin1Char('\t'));
if (isAllowedQuoteChar) {
startOfActualText = i + 1;
} else {
break;
}
++i;
}
// If the quote prefix only consists of whitespace, don't consider it as a quote prefix at all
if (line.left(startOfActualText).trimmed().isEmpty()) {
startOfActualText = 0;
}
// No quote prefix there -> nothing to do
if (startOfActualText <= 0) {
return QString();
}
// Entire line consists of only the quote prefix
if (i == line.length()) {
const QString quotePrefix = line.left(startOfActualText);
line.clear();
return quotePrefix;
}
// Line contains both the quote prefix and the actual text, really split it up now
const QString quotePrefix = line.left(startOfActualText);
line = line.mid(startOfActualText);
return quotePrefix;
}
// Writes all lines/text parts contained in the "textParts" list to the output text, "msg".
// Quote characters are added in front of each line, and no line is longer than
// maxLength.
//
// Although the lines in textParts are considered separate lines, they can actually be run
// together into a single line in some cases. This is basically the main difference to flowText().
//
// Example:
// textParts = "Hello World, this is a test.", "Really"
// indent = ">"
// maxLength = 20
// Result: "> Hello World, this\n
// > is a test. Really"
// Notice how in this example, the text line "Really" is no longer a separate line, it was run
// together with a previously broken line.
//
// "textParts" is cleared upon return.
static bool flushPart(QString &msg, QStringList &textParts, const QString &indent, int maxLength)
{
if (maxLength < 20) {
maxLength = 20;
}
// Remove empty lines at end of quote
while (!textParts.isEmpty() && textParts.last().isEmpty()) {
textParts.removeLast();
}
QString text;
for (const QString &line : textParts) {
// An empty line in the input means that an empty line should be in the output as well.
// Therefore, we write all of our text so far to the msg.
if (line.isEmpty()) {
if (!text.isEmpty()) {
msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength) + QLatin1Char('\n');
}
msg += indent + QLatin1Char('\n');
} else {
if (text.isEmpty()) {
text = line;
} else {
text += QLatin1Char(' ') + line.trimmed();
}
// If the line doesn't need to be wrapped at all, just write it out as-is.
// When a line exceeds the maximum length and therefore needs to be broken, this statement
// if false, and therefore we keep adding lines to our text, so they get ran together in the
// next flowText call, as "text" contains several text parts/lines then.
if ((text.length() < maxLength) || (line.length() < (maxLength - 10))) {
msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength) + QLatin1Char('\n');
}
}
}
// Write out pending text to the msg
if (!text.isEmpty()) {
msg += KPIMTextEdit::TextUtils::flowText(text, indent, maxLength);
}
const bool appendEmptyLine = !textParts.isEmpty();
textParts.clear();
return appendEmptyLine;
}
-QMap<QString, QString> parseMailtoUrl(const QUrl &url)
+QList<QPair<QString, QString> > parseMailtoUrl(const QUrl &url)
{
- QMap<QString, QString> values;
+ QList<QPair<QString, QString> > values;
if (url.scheme() != QLatin1String("mailto")) {
return values;
}
- QUrlQuery query(url);
- Q_FOREACH (const auto &queryItem, query.queryItems(QUrl::FullyDecoded)) {
- values.insertMulti(queryItem.first, queryItem.second);
+ QString str = url.toString();
+ QString toStr;
+ int i = 0;
+ int indexTo = -1;
+ //Workaround line with # see bug 406208
+ const int indexOf = str.indexOf(QLatin1Char('?'));
+ if (indexOf != -1) {
+ str = str.right(str.length() - indexOf - 1);
+ QUrlQuery query(str);
+ const auto listQuery = query.queryItems(QUrl::FullyDecoded);
+ for (const auto &queryItem : listQuery) {
+ if (queryItem.first == QLatin1String("to")) {
+ toStr = queryItem.second;
+ indexTo = i;
+ } else {
+ QPair<QString, QString> pairElement;
+ pairElement.first = queryItem.first;
+ pairElement.second = queryItem.second;
+ values.append(pairElement);
+ }
+ i++;
+ }
}
-
QStringList to = {KEmailAddress::decodeMailtoUrl(url)};
- const QString toStr = values.value(QStringLiteral("to"));
if (!toStr.isEmpty()) {
to << toStr;
}
-
- values.insert(QStringLiteral("to"), to.join(QStringLiteral(", ")));
+ const QString fullTo = to.join(QStringLiteral(", "));
+ if (!fullTo.isEmpty()) {
+ QPair<QString, QString> pairElement;
+ pairElement.first = QStringLiteral("to");
+ pairElement.second = fullTo;
+ if (indexTo != -1) {
+ values.insert(indexTo, pairElement);
+ } else {
+ values.prepend(pairElement);
+ }
+ }
return values;
}
QString stripSignature(const QString &msg)
{
// Following RFC 3676, only > before --
// I prefer to not delete a SB instead of delete good mail content.
const QRegularExpression sbDelimiterSearch(QStringLiteral("(^|\n)[> ]*-- \n"));
// The regular expression to look for prefix change
const QRegularExpression commonReplySearch(QStringLiteral("^[ ]*>"));
QString res = msg;
int posDeletingStart = 1; // to start looking at 0
// While there are SB delimiters (start looking just before the deleted SB)
while ((posDeletingStart = res.indexOf(sbDelimiterSearch, posDeletingStart - 1)) >= 0) {
QString prefix; // the current prefix
QString line; // the line to check if is part of the SB
int posNewLine = -1;
// Look for the SB beginning
int posSignatureBlock = res.indexOf(QLatin1Char('-'), posDeletingStart);
// The prefix before "-- "$
if (res.at(posDeletingStart) == QLatin1Char('\n')) {
++posDeletingStart;
}
prefix = res.mid(posDeletingStart, posSignatureBlock - posDeletingStart);
posNewLine = res.indexOf(QLatin1Char('\n'), posSignatureBlock) + 1;
// now go to the end of the SB
while (posNewLine < res.size() && posNewLine > 0) {
// handle the undefined case for mid ( x , -n ) where n>1
int nextPosNewLine = res.indexOf(QLatin1Char('\n'), posNewLine);
if (nextPosNewLine < 0) {
nextPosNewLine = posNewLine - 1;
}
line = res.mid(posNewLine, nextPosNewLine - posNewLine);
// check when the SB ends:
// * does not starts with prefix or
// * starts with prefix+(any substring of prefix)
if ((prefix.isEmpty() && line.indexOf(commonReplySearch) < 0)
|| (!prefix.isEmpty() && line.startsWith(prefix)
&& line.mid(prefix.size()).indexOf(commonReplySearch) < 0)) {
posNewLine = res.indexOf(QLatin1Char('\n'), posNewLine) + 1;
} else {
break; // end of the SB
}
}
// remove the SB or truncate when is the last SB
if (posNewLine > 0) {
res.remove(posDeletingStart, posNewLine - posDeletingStart);
} else {
res.truncate(posDeletingStart);
}
}
return res;
}
AddressList splitAddressField(const QByteArray &text)
{
AddressList result;
const char *begin = text.begin();
if (!begin) {
return result;
}
const char *const end = text.begin() + text.length();
if (!parseAddressList(begin, end, result)) {
qCDebug(MESSAGECORE_LOG) << "Error in address splitting: parseAddressList returned false!";
}
return result;
}
QString generateMessageId(const QString &address, const QString &suffix)
{
const QDateTime dateTime = QDateTime::currentDateTime();
QString msgIdStr = QLatin1Char('<') + dateTime.toString(QStringLiteral("yyyyMMddhhmm.sszzz"));
if (!suffix.isEmpty()) {
msgIdStr += QLatin1Char('@') + suffix;
} else {
msgIdStr += QLatin1Char('.') + KEmailAddress::toIdn(address);
}
msgIdStr += QLatin1Char('>');
return msgIdStr;
}
QString quoteHtmlChars(const QString &str, bool removeLineBreaks)
{
QString result;
int strLength(str.length());
result.reserve(6 * strLength); // maximal possible length
for (int i = 0; i < strLength; ++i) {
switch (str[i].toLatin1()) {
case '<':
result += QLatin1String("&lt;");
break;
case '>':
result += QLatin1String("&gt;");
break;
case '&':
result += QLatin1String("&amp;");
break;
case '"':
result += QLatin1String("&quot;");
break;
case '\n':
if (!removeLineBreaks) {
result += QLatin1String("<br>");
}
break;
case '\r':
// ignore CR
break;
default:
result += str[i];
}
}
result.squeeze();
return result;
}
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
{
message->removeHeader("Status");
message->removeHeader("X-Status");
message->removeHeader("X-KMail-EncryptionState");
message->removeHeader("X-KMail-SignatureState");
message->removeHeader("X-KMail-Redirect-From");
message->removeHeader("X-KMail-Link-Message");
message->removeHeader("X-KMail-Link-Type");
message->removeHeader("X-KMail-QuotePrefix");
message->removeHeader("X-KMail-CursorPos");
message->removeHeader("X-KMail-Templates");
message->removeHeader("X-KMail-Drafts");
message->removeHeader("X-KMail-UnExpanded-To");
message->removeHeader("X-KMail-UnExpanded-CC");
message->removeHeader("X-KMail-UnExpanded-BCC");
+ message->removeHeader("X-KMail-UnExpanded-Reply-To");
message->removeHeader("X-KMail-FccDisabled");
if (cleanUpHeader) {
message->removeHeader("X-KMail-Fcc");
message->removeHeader("X-KMail-Transport");
message->removeHeader("X-KMail-Identity");
+ message->removeHeader("X-KMail-Transport-Name");
+ message->removeHeader("X-KMail-Identity-Name");
message->removeHeader("X-KMail-Dictionary");
}
}
QByteArray asSendableString(const KMime::Message::Ptr &originalMessage)
{
KMime::Message::Ptr message(new KMime::Message);
message->setContent(originalMessage->encodedContent());
removePrivateHeaderFields(message);
message->removeHeader<KMime::Headers::Bcc>();
return message->encodedContent();
}
QByteArray headerAsSendableString(const KMime::Message::Ptr &originalMessage)
{
KMime::Message::Ptr message(new KMime::Message);
message->setContent(originalMessage->encodedContent());
removePrivateHeaderFields(message);
message->removeHeader<KMime::Headers::Bcc>();
return message->head();
}
QString emailAddrAsAnchor(const KMime::Types::Mailbox::List &mailboxList, Display display, const QString &cssStyle, Link link, AddressMode expandable, const QString &fieldName, int collapseNumber)
{
QString result;
int numberAddresses = 0;
bool expandableInserted = false;
KIdentityManagement::IdentityManager *im = KIdentityManagement::IdentityManager::self();
const QString i18nMe = i18nc("signal that this email is defined in my identity", "Me");
const bool onlyOneIdentity = (im->identities().count() == 1);
for (const KMime::Types::Mailbox &mailbox : mailboxList) {
const QString prettyAddressStr = mailbox.prettyAddress();
if (!prettyAddressStr.isEmpty()) {
numberAddresses++;
if (expandable == ExpandableAddresses && !expandableInserted && numberAddresses > collapseNumber) {
const QString actualListAddress = result;
QString shortListAddress = actualListAddress;
if (link == ShowLink) {
shortListAddress.truncate(result.length() - 2);
}
result = QStringLiteral("<span><input type=\"checkbox\" class=\"addresslist_checkbox\" id=\"%1\" checked=\"checked\"/><span class=\"short%1\">").arg(fieldName) + shortListAddress;
result += QStringLiteral("<label class=\"addresslist_label_short\" for=\"%1\"></label></span>").arg(fieldName);
expandableInserted = true;
result += QStringLiteral("<span class=\"full%1\">").arg(fieldName) + actualListAddress;
}
if (link == ShowLink) {
result += QLatin1String("<a href=\"mailto:")
+ QString::fromLatin1(QUrl::toPercentEncoding(KEmailAddress::encodeMailtoUrl(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary)).path()))
+ QLatin1String("\" ") + cssStyle + QLatin1Char('>');
}
const bool foundMe = onlyOneIdentity && (im->identityForAddress(prettyAddressStr) != KIdentityManagement::Identity::null());
if (display == DisplayNameOnly) {
if (!mailbox.name().isEmpty()) { // Fallback to the email address when the name is not set.
result += foundMe ? i18nMe : quoteHtmlChars(mailbox.name(), true);
} else {
result += foundMe ? i18nMe : quoteHtmlChars(prettyAddressStr, true);
}
} else {
result += foundMe ? i18nMe : quoteHtmlChars(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), true);
}
if (link == ShowLink) {
result += QLatin1String("</a>, ");
}
}
}
if (link == ShowLink) {
result.truncate(result.length() - 2);
}
if (expandableInserted) {
result += QStringLiteral("<label class=\"addresslist_label_full\" for=\"%1\"></label></span></span>").arg(fieldName);
}
return result;
}
QString emailAddrAsAnchor(const KMime::Headers::Generics::MailboxList *mailboxList, Display display, const QString &cssStyle, Link link, AddressMode expandable, const QString &fieldName, int collapseNumber)
{
Q_ASSERT(mailboxList);
return emailAddrAsAnchor(mailboxList->mailboxes(), display, cssStyle, link, expandable, fieldName, collapseNumber);
}
QString emailAddrAsAnchor(const KMime::Headers::Generics::AddressList *addressList, Display display, const QString &cssStyle, Link link, AddressMode expandable, const QString &fieldName, int collapseNumber)
{
Q_ASSERT(addressList);
return emailAddrAsAnchor(addressList->mailboxes(), display, cssStyle, link, expandable, fieldName, collapseNumber);
}
bool addressIsInAddressList(const QString &address, const QStringList &addresses)
{
const QString addrSpec = KEmailAddress::extractEmailAddress(address);
QStringList::ConstIterator end(addresses.constEnd());
for (QStringList::ConstIterator it = addresses.constBegin(); it != end; ++it) {
if (qstricmp(addrSpec.toUtf8().data(),
KEmailAddress::extractEmailAddress(*it).toUtf8().data()) == 0) {
return true;
}
}
return false;
}
QString guessEmailAddressFromLoginName(const QString &loginName)
{
if (loginName.isEmpty()) {
return QString();
}
QString address = loginName;
address += QLatin1Char('@');
address += QHostInfo::localHostName();
// try to determine the real name
const KUser user(loginName);
if (user.isValid()) {
const QString fullName = user.property(KUser::FullName).toString();
address = KEmailAddress::quoteNameIfNecessary(fullName) + QLatin1String(" <") + address + QLatin1Char('>');
}
return address;
}
QString smartQuote(const QString &msg, int maxLineLength)
{
// The algorithm here is as follows:
// We split up the incoming msg into lines, and then iterate over each line.
// We keep adding lines with the same indent ( = quote prefix, e.g. "> " ) to a
// "textParts" list. So the textParts list contains only lines with the same quote
// prefix.
//
// When all lines with the same indent are collected in "textParts", we write those out
// to the result by calling flushPart(), which does all the nice formatting for us.
QStringList textParts;
QString oldIndent;
bool firstPart = true;
QString result;
int lineStart = 0;
int lineEnd = msg.indexOf(QLatin1Char('\n'));
bool needToContinue = true;
for (; needToContinue; lineStart = lineEnd + 1, lineEnd = msg.indexOf(QLatin1Char('\n'), lineStart)) {
QString line;
if (lineEnd == -1) {
if (lineStart == 0) {
line = msg;
needToContinue = false;
} else if (lineStart != 0 && lineStart != msg.length()) {
line = msg.mid(lineStart, msg.length() - lineStart);
needToContinue = false;
} else {
needToContinue = false;
}
} else {
line = msg.mid(lineStart, lineEnd - lineStart);
}
// Split off the indent from the line
const QString indent = splitLine(line);
if (line.isEmpty()) {
if (!firstPart) {
textParts.append(QString());
}
continue;
}
if (firstPart) {
oldIndent = indent;
firstPart = false;
}
// The indent changed, that means we have to write everything contained in textParts to the
// result, which we do by calling flushPart().
if (oldIndent != indent) {
// Check if the last non-blank line is a "From" line. A from line is the line containing the
// attribution to a quote, e.g. "Yesterday, you wrote:". We'll just check for the last colon
// here, to simply things.
// If there is a From line, remove it from the textParts to that flushPart won't break it.
// We'll manually add it to the result afterwards.
QString fromLine;
if (!textParts.isEmpty()) {
for (int i = textParts.count() - 1; i >= 0; i--) {
// Check if we have found the From line
const QString textPartElement(textParts[i]);
if (textPartElement.endsWith(QLatin1Char(':'))) {
fromLine = oldIndent + textPartElement + QLatin1Char('\n');
textParts.removeAt(i);
break;
}
// Abort on first non-empty line
if (!textPartElement.trimmed().isEmpty()) {
break;
}
}
}
// Write out all lines with the same indent using flushPart(). The textParts list
// is cleared for us.
if (flushPart(result, textParts, oldIndent, maxLineLength)) {
if (oldIndent.length() > indent.length()) {
result += indent + QLatin1Char('\n');
} else {
result += oldIndent + QLatin1Char('\n');
}
}
if (!fromLine.isEmpty()) {
result += fromLine;
}
oldIndent = indent;
}
textParts.append(line);
}
// Write out anything still pending
flushPart(result, textParts, oldIndent, maxLineLength);
// Remove superfluous newline which was appended in flowText
if (!result.isEmpty() && result.endsWith(QLatin1Char('\n'))) {
result.chop(1);
}
return result;
}
QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString)
{
QString result;
if (wildString.isEmpty()) {
return wildString;
}
int strLength(wildString.length());
for (int i = 0; i < strLength;) {
QChar ch = wildString[i++];
if (ch == QLatin1Char('%') && i < strLength) {
ch = wildString[i++];
switch (ch.toLatin1()) {
case 'f':
- { // sender's initals
+ { // sender's initials
if (fromDisplayString.isEmpty()) {
break;
}
int j = 0;
const int strLength(fromDisplayString.length());
for (; j < strLength && fromDisplayString[j] > QLatin1Char(' '); ++j) {
}
for (; j < strLength && fromDisplayString[j] <= QLatin1Char(' '); ++j) {
}
result += fromDisplayString[0];
if (j < strLength && fromDisplayString[j] > QLatin1Char(' ')) {
result += fromDisplayString[j];
} else if (strLength > 1) {
if (fromDisplayString[1] > QLatin1Char(' ')) {
result += fromDisplayString[1];
}
}
break;
}
case '_':
result += QLatin1Char(' ');
break;
case '%':
result += QLatin1Char('%');
break;
default:
result += QLatin1Char('%');
result += ch;
break;
}
} else {
result += ch;
}
}
return result;
}
QString cleanFileName(const QString &name)
{
QString fileName = name.trimmed();
// We need to replace colons with underscores since those cause problems with
// KFileDialog (bug in KFileDialog though) and also on Windows filesystems.
// We also look at the special case of ": ", since converting that to "_ "
// would look strange, simply "_" looks better.
// https://issues.kolab.org/issue3805
fileName.replace(QLatin1String(": "), QStringLiteral("_"));
// replace all ':' with '_' because ':' isn't allowed on FAT volumes
fileName.replace(QLatin1Char(':'), QLatin1Char('_'));
// better not use a dir-delimiter in a filename
fileName.replace(QLatin1Char('/'), QLatin1Char('_'));
fileName.replace(QLatin1Char('\\'), QLatin1Char('_'));
#ifdef KDEPIM_ENTERPRISE_BUILD
// replace all '.' with '_', not just at the start of the filename
// but don't replace the last '.' before the file extension.
int i = fileName.lastIndexOf(QLatin1Char('.'));
if (i != -1) {
i = fileName.lastIndexOf(QLatin1Char('.'), i - 1);
}
while (i != -1) {
fileName.replace(i, 1, QLatin1Char('_'));
i = fileName.lastIndexOf(QLatin1Char('.'), i - 1);
}
#endif
// replace all '~' with '_', not just leading '~' either.
fileName.replace(QLatin1Char('~'), QLatin1Char('_'));
return fileName;
}
QString cleanSubject(KMime::Message *msg)
{
return cleanSubject(msg, MessageCore::MessageCoreSettings::self()->replyPrefixes() + MessageCore::MessageCoreSettings::self()->forwardPrefixes(),
true, QString()).trimmed();
}
QString cleanSubject(KMime::Message *msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
{
return replacePrefixes(msg->subject()->asUnicodeString(), prefixRegExps, replace,
newPrefix);
}
QString forwardSubject(KMime::Message *msg)
{
return cleanSubject(msg, MessageCore::MessageCoreSettings::self()->forwardPrefixes(),
MessageCore::MessageCoreSettings::self()->replaceForwardPrefix(), QStringLiteral("Fwd:"));
}
QString replySubject(KMime::Message *msg)
{
return cleanSubject(msg, MessageCore::MessageCoreSettings::self()->replyPrefixes(),
MessageCore::MessageCoreSettings::self()->replaceReplyPrefix(), QStringLiteral("Re:"));
}
QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, bool replace, const QString &newPrefix)
{
bool recognized = false;
// construct a big regexp that
// 1. is anchored to the beginning of str (sans whitespace)
// 2. matches at least one of the part regexps in prefixRegExps
QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*")
.arg(prefixRegExps.join(QStringLiteral(")|(?:")));
QRegExp rx(bigRegExp, Qt::CaseInsensitive);
if (rx.isValid()) {
QString tmp = str;
if (rx.indexIn(tmp) == 0) {
recognized = true;
if (replace) {
return tmp.replace(0, rx.matchedLength(), newPrefix + QLatin1Char(' '));
}
}
} else {
qCWarning(MESSAGECORE_LOG) << "bigRegExp = \""
- << bigRegExp << "\"\n"
- << "prefix regexp is invalid!";
+ << bigRegExp << "\"\n"
+ << "prefix regexp is invalid!";
// try good ole Re/Fwd:
recognized = str.startsWith(newPrefix);
}
if (!recognized) {
return newPrefix + QLatin1Char(' ') + str;
} else {
return str;
}
}
-
QString stripOffPrefixes(const QString &subject)
{
const QStringList replyPrefixes = MessageCoreSettings::self()->replyPrefixes();
const QStringList forwardPrefixes = MessageCoreSettings::self()->forwardPrefixes();
const QStringList prefixRegExps = replyPrefixes + forwardPrefixes;
// construct a big regexp that
// 1. is anchored to the beginning of str (sans whitespace)
// 2. matches at least one of the part regexps in prefixRegExps
const QString bigRegExp = QStringLiteral("^(?:\\s+|(?:%1))+\\s*").arg(prefixRegExps.join(QStringLiteral(")|(?:")));
static QString regExpPattern;
static QRegExp regExp;
regExp.setCaseSensitivity(Qt::CaseInsensitive);
if (regExpPattern != bigRegExp) {
// the prefixes have changed, so update the regexp
regExpPattern = bigRegExp;
regExp.setPattern(regExpPattern);
}
if (regExp.isValid()) {
QString tmp = subject;
if (regExp.indexIn(tmp) == 0) {
return tmp.remove(0, regExp.matchedLength());
}
} else {
qCWarning(MESSAGECORE_LOG) << "bigRegExp = \""
<< bigRegExp << "\"\n"
<< "prefix regexp is invalid!";
}
return subject;
}
void setEncodingFile(QUrl &url, const QString &encoding)
{
QUrlQuery query;
query.addQueryItem(QStringLiteral("charset"), encoding);
url.setQuery(query);
}
}
}
diff --git a/messagecore/src/utils/stringutil.h b/messagecore/src/utils/stringutil.h
index 7809d889..1a784492 100644
--- a/messagecore/src/utils/stringutil.h
+++ b/messagecore/src/utils/stringutil.h
@@ -1,230 +1,222 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGECORE_STRINGUTIL_H
#define MESSAGECORE_STRINGUTIL_H
#include "messagecore_export.h"
#include "kmime/kmime_message.h"
#include "kmime/kmime_header_parsing.h"
#include <QStringList>
class QUrl;
namespace KMime {
namespace Types {
struct Address;
typedef QVector<Address> AddressList;
class Mailbox;
}
namespace Headers {
namespace Generics {
class MailboxList;
class AddressList;
}
}
}
namespace MessageCore {
/**
* This namespace contain helper functions for string manipulation
*/
namespace StringUtil {
/**
* Parses a mailto: url and extracts the information in the QMap (field name as key).
*/
-Q_REQUIRED_RESULT MESSAGECORE_EXPORT QMap<QString, QString> parseMailtoUrl(const QUrl &url);
+Q_REQUIRED_RESULT MESSAGECORE_EXPORT QList<QPair<QString, QString> > parseMailtoUrl(const QUrl &url);
/**
* Strips the signature blocks from a message text. "-- " is considered as a signature block separator.
*
* @param message The message to remove the signature block from.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString stripSignature(const QString &message);
/**
* Splits the given address list @p text into separate addresses.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT KMime::Types::AddressList splitAddressField(const QByteArray &text);
/**
* Generates the Message-Id. It uses either the Message-Id @p suffix
* defined by the user or the given email address as suffix. The @p address
* must be given as addr-spec as defined in RFC 2822.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString generateMessageId(const QString &address, const QString &suffix);
/**
* Quotes the following characters which have a special meaning in HTML:
* '<' '>' '&' '"'. Additionally '\\n' is converted to "<br />" if
* @p removeLineBreaks is false. If @p removeLineBreaks is true, then
* '\\n' is removed. Last but not least '\\r' is removed.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString quoteHtmlChars(const QString &text, bool removeLineBreaks = false);
/**
* Removes all private header fields (e.g. *Status: and X-KMail-*) from the given @p message.
* if cleanUpHeader is false don't remove X-KMail-Identity and X-KMail-Dictionary which is useful when we want restore mail.
*/
MESSAGECORE_EXPORT void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader = true);
/**
* Returns the @p message contents with the headers that should not be sent stripped off.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QByteArray asSendableString(const KMime::Message::Ptr &message);
/**
* Return the message header with the headers that should not be sent stripped off.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QByteArray headerAsSendableString(const KMime::Message::Ptr &message);
/**
* Used to determine if the visible part of the anchor contains
* only the name part and not the given emailAddr or the full address.
*/
enum Display {
DisplayNameOnly,
DisplayFullAddress
};
/**
* Used to determine if the address should be a link or not.
*/
enum Link {
ShowLink,
HideLink
};
/**
* Used to determine if the address field should be expandable/collapsable.
*/
enum AddressMode {
ExpandableAddresses,
FullAddresses
};
/**
* Converts the email address(es) to (a) nice HTML mailto: anchor(s).
* @p display determines if only the name part or the entire address should be returned.
* @p cssStyle a custom css template.
* @p link determines if the result should be a html link or not.
* @p expandable determines if a long list of addresses should be expandable or shown
* in full.
* @p fieldName the name that the divs should be based on if expandable is set to ExpanableAddesses.
* @p The number of addresses to show before collapsing the rest, if expandable is set to
* ExpandableAddresses.
*/
-Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const KMime::Headers::Generics::MailboxList *mailboxList, Display display = DisplayNameOnly,
- const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses,
- const QString &fieldName = QString(), int collapseNumber = 4);
+Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const KMime::Headers::Generics::MailboxList *mailboxList, Display display = DisplayNameOnly, const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses, const QString &fieldName = QString(), int collapseNumber = 4);
/**
* Same as above method, only for AddressList headers.
*/
-Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const KMime::Headers::Generics::AddressList *addressList, Display display = DisplayNameOnly,
- const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses,
- const QString &fieldName = QString(), int collapseNumber = 4);
+Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const KMime::Headers::Generics::AddressList *addressList, Display display = DisplayNameOnly, const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses, const QString &fieldName = QString(), int collapseNumber = 4);
/**
* Same as the above, only for Mailbox::List types.
*/
-Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const QVector<KMime::Types::Mailbox> &mailboxList, Display display = DisplayNameOnly,
- const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses,
- const QString &fieldName = QString(), int collapseNumber = 4);
+Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString emailAddrAsAnchor(const QVector<KMime::Types::Mailbox> &mailboxList, Display display = DisplayNameOnly, const QString &cssStyle = QString(), Link link = ShowLink, AddressMode expandable = FullAddresses, const QString &fieldName = QString(), int collapseNumber = 4);
/**
* Returns true if the given address is contained in the given address list.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT bool addressIsInAddressList(const QString &address, const QStringList &addresses);
/**
* Uses the hostname as domain part and tries to determine the real name
* from the entries in the password file.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString guessEmailAddressFromLoginName(const QString &userName);
/**
- * Relayouts the given string so that the invidual lines don't exceed the given
+ * Relayouts the given string so that the individual lines don't exceed the given
* maximal length.
*
- * As the name of the function implies, it it smart, which means it deals with quoting
+ * As the name of the function implies, it is smart, which means it deals with quoting
* correctly. This means if a line already starts with quote characters and needs to be
* broken, the same quote characters are prepended to the next line as well.
*
* This does _not_ add new quote characters in front of every line, that is the responsibility
* of the caller.
*
* @param message The string which it to be relayouted
* @param maxLineLength reformat text to be this amount of columns at maximum. Note that this
* also includes the trailing \n!
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString smartQuote(const QString &message, int maxLineLength);
/**
* Convert quote wildcards into the final quote prefix.
* @param wildString the string to be converted
* @param fromDisplayString displayable string of the from email address
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString formatQuotePrefix(const QString &wildString, const QString &fromDisplayString);
/**
* Cleans a filename by replacing characters not allowed or wanted on the filesystem
* e.g. ':', '/', '\' with '_'
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString cleanFileName(const QString &fileName);
/**
* Removes the forward and reply markes (e.g. Re: or Fwd:) from a @p subject string.
* Additional markers to act on can be specified in the MessageCore::GlobalSettings
* object.
*/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString stripOffPrefixes(const QString &subject);
MESSAGECORE_EXPORT void setEncodingFile(QUrl &url, const QString &encoding);
/** Check for prefixes @p prefixRegExps in #subject(). If none
is found, @p newPrefix + ' ' is prepended to the subject and the
resulting string is returned. If @p replace is true, any
sequence of whitespace-delimited prefixes at the beginning of
#subject() is replaced by @p newPrefix
**/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString cleanSubject(KMime::Message *msg, const QStringList &prefixRegExps, bool replace, const QString &newPrefix);
/** Return this mails subject, with all "forward" and "reply"
prefixes removed */
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString cleanSubject(KMime::Message *msg);
/** Return this mails subject, formatted for "forward" mails */
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString forwardSubject(KMime::Message *msg);
/** Return this mails subject, formatted for "reply" mails */
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString replySubject(KMime::Message *msg);
/** Check for prefixes @p prefixRegExps in @p str. If none
is found, @p newPrefix + ' ' is prepended to @p str and the
resulting string is returned. If @p replace is true, any
sequence of whitespace-delimited prefixes at the beginning of
@p str is replaced by @p newPrefix.
**/
Q_REQUIRED_RESULT MESSAGECORE_EXPORT QString replacePrefixes(const QString &str, const QStringList &prefixRegExps, bool replace, const QString &newPrefix);
-
-
}
}
#endif
diff --git a/messagelist/autotests/itemsizetest.cpp b/messagelist/autotests/itemsizetest.cpp
index 74e8aaa5..f2b33e97 100644
--- a/messagelist/autotests/itemsizetest.cpp
+++ b/messagelist/autotests/itemsizetest.cpp
@@ -1,47 +1,47 @@
/*
Copyright (c) 2011 Volker Krause <vkrause@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.
*/
#include "../src/core/messageitem_p.h"
#include <QDebug>
-#include <qtest.h>
+#include <QTest>
#include <QObject>
using namespace MessageList::Core;
class ItemSizeTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testItemSize()
{
qDebug() << sizeof(Item);
QVERIFY(sizeof(Item) <= 16);
qDebug() << sizeof(ItemPrivate);
QVERIFY(sizeof(ItemPrivate) <= 120);
qDebug() << sizeof(MessageItem);
QVERIFY(sizeof(MessageItem) <= 32);
qDebug() << sizeof(MessageItemPrivate);
QVERIFY(sizeof(MessageItemPrivate) <= 192);
}
};
QTEST_MAIN(ItemSizeTest)
#include "itemsizetest.moc"
diff --git a/messagelist/autotests/quicksearchwarningtest.cpp b/messagelist/autotests/quicksearchwarningtest.cpp
index a3bd96a0..9dd67de4 100644
--- a/messagelist/autotests/quicksearchwarningtest.cpp
+++ b/messagelist/autotests/quicksearchwarningtest.cpp
@@ -1,67 +1,67 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "quicksearchwarningtest.h"
#include "../src/core/widgets/quicksearchwarning.h"
#include <QAction>
-#include <qtest.h>
+#include <QTest>
QuickSearchWarningTest::QuickSearchWarningTest(QObject *parent)
: QObject(parent)
{
}
QuickSearchWarningTest::~QuickSearchWarningTest()
{
}
void QuickSearchWarningTest::shouldHaveDefaultValue()
{
MessageList::Core::QuickSearchWarning w;
QVERIFY(!w.isVisible());
QAction *act = w.findChild<QAction *>(QStringLiteral("donotshowagain"));
QVERIFY(act);
}
void QuickSearchWarningTest::shouldSetVisible()
{
MessageList::Core::QuickSearchWarning w;
w.setSearchText(QStringLiteral("1"));
QVERIFY(w.isVisible());
}
void QuickSearchWarningTest::shouldSetSearchText()
{
QFETCH(QString, input);
QFETCH(bool, visible);
MessageList::Core::QuickSearchWarning w;
w.setSearchText(input);
QCOMPARE(w.isVisible(), visible);
}
void QuickSearchWarningTest::shouldSetSearchText_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<bool>("visible");
QTest::newRow("bigword") << QStringLiteral("foofoofoo") << false;
QTest::newRow("1character") << QStringLiteral("f") << true;
QTest::newRow("multibigword") << QStringLiteral("foo foo foo") << false;
QTest::newRow("multibigwordwithasmallone") << QStringLiteral("foo foo foo 1") << true;
QTest::newRow("aspace") << QStringLiteral(" ") << false;
QTest::newRow("multispace") << QStringLiteral(" ") << false;
}
QTEST_MAIN(QuickSearchWarningTest)
diff --git a/messagelist/autotests/quicksearchwarningtest.h b/messagelist/autotests/quicksearchwarningtest.h
index f8714831..2007c5ec 100644
--- a/messagelist/autotests/quicksearchwarningtest.h
+++ b/messagelist/autotests/quicksearchwarningtest.h
@@ -1,36 +1,36 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef QUICKSEARCHWARNINGTEST_H
#define QUICKSEARCHWARNINGTEST_H
#include <QObject>
class QuickSearchWarningTest : public QObject
{
Q_OBJECT
public:
explicit QuickSearchWarningTest(QObject *parent = nullptr);
~QuickSearchWarningTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldSetVisible();
void shouldSetSearchText();
void shouldSetSearchText_data();
};
#endif // QUICKSEARCHWARNINGTEST_H
diff --git a/messagelist/autotests/searchcollectionindexingwarningtest.cpp b/messagelist/autotests/searchcollectionindexingwarningtest.cpp
index f242e884..16140fa0 100644
--- a/messagelist/autotests/searchcollectionindexingwarningtest.cpp
+++ b/messagelist/autotests/searchcollectionindexingwarningtest.cpp
@@ -1,41 +1,41 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#include "searchcollectionindexingwarningtest.h"
#include "../src/core/widgets/searchcollectionindexingwarning.h"
#include <QTest>
SearchCollectionIndexingWarningTest::SearchCollectionIndexingWarningTest(QObject *parent)
: QObject(parent)
{
}
SearchCollectionIndexingWarningTest::~SearchCollectionIndexingWarningTest()
{
}
void SearchCollectionIndexingWarningTest::shouldHaveDefaultValue()
{
MessageList::Core::SearchCollectionIndexingWarning w;
QVERIFY(!w.isVisible());
QVERIFY(w.wordWrap());
}
QTEST_MAIN(SearchCollectionIndexingWarningTest)
diff --git a/messagelist/autotests/searchcollectionindexingwarningtest.h b/messagelist/autotests/searchcollectionindexingwarningtest.h
index 49a1306f..45663ec8 100644
--- a/messagelist/autotests/searchcollectionindexingwarningtest.h
+++ b/messagelist/autotests/searchcollectionindexingwarningtest.h
@@ -1,36 +1,36 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#ifndef SEARCHCOLLECTIONINDEXINGWARNINGTEST_H
#define SEARCHCOLLECTIONINDEXINGWARNINGTEST_H
#include <QObject>
class SearchCollectionIndexingWarningTest : public QObject
{
Q_OBJECT
public:
explicit SearchCollectionIndexingWarningTest(QObject *parent = nullptr);
~SearchCollectionIndexingWarningTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // SEARCHCOLLECTIONINDEXINGWARNINGTEST_H
diff --git a/messagelist/autotests/searchlinestatustest.cpp b/messagelist/autotests/searchlinestatustest.cpp
index 8406ab39..957d5aea 100644
--- a/messagelist/autotests/searchlinestatustest.cpp
+++ b/messagelist/autotests/searchlinestatustest.cpp
@@ -1,76 +1,76 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "searchlinestatustest.h"
#include "../src/core/widgets/searchlinestatus.h"
#include <QMenu>
#include <QTest>
#include <QCompleter>
SearchLineStatusTest::SearchLineStatusTest(QObject *parent)
: QObject(parent)
{
}
SearchLineStatusTest::~SearchLineStatusTest()
{
}
void SearchLineStatusTest::shouldHaveDefaultValue()
{
MessageList::Core::SearchLineStatus w;
QVERIFY(!w.containsOutboundMessages());
QVERIFY(!w.locked());
QMenu *filterMenu = w.findChild<QMenu *>(QStringLiteral("filtermenu"));
QVERIFY(filterMenu);
QVERIFY(!filterMenu->actions().isEmpty());
QVERIFY(w.completer());
QVERIFY(w.completer()->model());
QCOMPARE(w.completer()->model()->rowCount(), 0);
//Verify if qt qlineedit name changed
- QAction *act = w.findChild<QAction *>(QLatin1String("_q_qlineeditclearaction"));
+ QAction *act = w.findChild<QAction *>(QStringLiteral("_q_qlineeditclearaction"));
QVERIFY(act);
}
void SearchLineStatusTest::shouldAddCompletionItem()
{
MessageList::Core::SearchLineStatus w;
w.addCompletionItem(QStringLiteral("ff"));
QCOMPARE(w.completer()->model()->rowCount(), 1);
//Don't add same element
w.addCompletionItem(QStringLiteral("ff"));
QCOMPARE(w.completer()->model()->rowCount(), 1);
w.addCompletionItem(QStringLiteral("ffss"));
QCOMPARE(w.completer()->model()->rowCount(), 2);
}
void SearchLineStatusTest::shouldClearCompleter()
{
MessageList::Core::SearchLineStatus w;
for (int i = 0; i < 10; ++i) {
w.addCompletionItem(QStringLiteral("ff%1").arg(i));
}
QCOMPARE(w.completer()->model()->rowCount(), 10);
w.slotClearHistory();
QCOMPARE(w.completer()->model()->rowCount(), 0);
}
QTEST_MAIN(SearchLineStatusTest)
diff --git a/messagelist/autotests/searchlinestatustest.h b/messagelist/autotests/searchlinestatustest.h
index 7843b675..b8e8e38e 100644
--- a/messagelist/autotests/searchlinestatustest.h
+++ b/messagelist/autotests/searchlinestatustest.h
@@ -1,36 +1,36 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef SEARCHLINESTATUSTEST_H
#define SEARCHLINESTATUSTEST_H
#include <QObject>
class SearchLineStatusTest : public QObject
{
Q_OBJECT
public:
explicit SearchLineStatusTest(QObject *parent = nullptr);
~SearchLineStatusTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAddCompletionItem();
void shouldClearCompleter();
};
#endif // SEARCHLINESTATUSTEST_H
diff --git a/messagelist/metainfo.yaml b/messagelist/metainfo.yaml
index 3414b06a..9eb05e57 100644
--- a/messagelist/metainfo.yaml
+++ b/messagelist/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer:
description: MessageList Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: MessageList
cmake: "KF5::MessageList"
- cmakename: KF5MessageList
+cmakename: KF5MessageList
diff --git a/messagelist/src/core/aggregation.cpp b/messagelist/src/core/aggregation.cpp
index 55fe5837..f84f5af9 100644
--- a/messagelist/src/core/aggregation.cpp
+++ b/messagelist/src/core/aggregation.cpp
@@ -1,287 +1,286 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/aggregation.h"
#include <QDataStream>
#include <KLocalizedString>
using namespace MessageList::Core;
static const int gAggregationCurrentVersion = 0x1009; // increase if you add new fields of change the meaning of some
Aggregation::Aggregation(
- const QString &name, const QString &description, Grouping grouping, GroupExpandPolicy groupExpandPolicy, Threading threading, ThreadLeader threadLeader, ThreadExpandPolicy threadExpandPolicy,
- FillViewStrategy fillViewStrategy, bool readOnly)
+ const QString &name, const QString &description, Grouping grouping, GroupExpandPolicy groupExpandPolicy, Threading threading, ThreadLeader threadLeader, ThreadExpandPolicy threadExpandPolicy, FillViewStrategy fillViewStrategy, bool readOnly)
: OptionSet(name, description, readOnly)
, mGrouping(grouping)
, mGroupExpandPolicy(groupExpandPolicy)
, mThreading(threading)
, mThreadLeader(threadLeader)
, mThreadExpandPolicy(threadExpandPolicy)
, mFillViewStrategy(fillViewStrategy)
{
}
Aggregation::Grouping Aggregation::grouping() const
{
return mGrouping;
}
Aggregation::Aggregation(
const Aggregation &opt)
: OptionSet(opt)
, mGrouping(opt.mGrouping)
, mGroupExpandPolicy(opt.mGroupExpandPolicy)
, mThreading(opt.mThreading)
, mThreadLeader(opt.mThreadLeader)
, mThreadExpandPolicy(opt.mThreadExpandPolicy)
, mFillViewStrategy(opt.mFillViewStrategy)
{
}
Aggregation::Aggregation()
: OptionSet()
, mGrouping(NoGrouping)
, mGroupExpandPolicy(NeverExpandGroups)
, mThreading(NoThreading)
, mThreadLeader(TopmostMessage)
, mThreadExpandPolicy(NeverExpandThreads)
, mFillViewStrategy(FavorInteractivity)
{
}
bool Aggregation::load(QDataStream &stream)
{
int val;
stream >> val;
if (val != gAggregationCurrentVersion) {
return false; // b0rken (invalid version)
}
stream >> val;
mGrouping = static_cast<Grouping>(val);
switch (mGrouping) {
case NoGrouping:
case GroupByDate:
case GroupByDateRange:
case GroupBySenderOrReceiver:
case GroupBySender:
case GroupByReceiver:
// ok
break;
default:
// b0rken
return false;
}
stream >> val; // Formerly contained group sorting
stream >> val; // Formerly contained group sorting direction
stream >> val;
mGroupExpandPolicy = (GroupExpandPolicy)val;
switch (mGroupExpandPolicy) {
case NeverExpandGroups:
case ExpandRecentGroups:
case AlwaysExpandGroups:
// ok
break;
default:
// b0rken
return false;
break;
}
stream >> val;
mThreading = (Threading)val;
switch (mThreading) {
case NoThreading:
case PerfectOnly:
case PerfectAndReferences:
case PerfectReferencesAndSubject:
// ok
break;
default:
// b0rken
return false;
}
stream >> val;
mThreadLeader = (ThreadLeader)val;
switch (mThreadLeader) {
case MostRecentMessage:
case TopmostMessage:
// ok
// FIXME: Should check that thread leaders setting matches grouping and threading settings.
break;
default:
// b0rken
return false;
}
stream >> val;
mThreadExpandPolicy = (ThreadExpandPolicy)val;
switch (mThreadExpandPolicy) {
case NeverExpandThreads:
case ExpandThreadsWithNewMessages:
case ExpandThreadsWithUnreadMessages:
case ExpandThreadsWithUnreadOrImportantMessages:
case AlwaysExpandThreads:
// ok
break;
default:
// b0rken
return false;
}
- stream >> val; // Formely contained message sorting
- stream >> val; // Formely contained message sort direction
+ stream >> val; // Formerly contained message sorting
+ stream >> val; // Formerly contained message sort direction
stream >> val;
mFillViewStrategy = (FillViewStrategy)val;
switch (mFillViewStrategy) {
case FavorSpeed:
case FavorInteractivity:
case BatchNoInteractivity:
// ok
break;
default:
// b0rken
return false;
}
return true;
}
void Aggregation::save(QDataStream &stream) const
{
stream << (int)gAggregationCurrentVersion;
stream << (int)mGrouping;
stream << 0; // Formerly group sorting
stream << 0; // Formerly group sort direction
stream << (int)mGroupExpandPolicy;
stream << (int)mThreading;
stream << (int)mThreadLeader;
stream << (int)mThreadExpandPolicy;
stream << 0; // Formerly message sorting
stream << 0; // Formerly message sort direction
stream << (int)mFillViewStrategy;
}
QList< QPair< QString, int > > Aggregation::enumerateGroupingOptions()
{
return { {
- i18nc("No grouping of messages", "None"), NoGrouping
- },
- {
- i18n("By Exact Date (of Thread Leaders)"), GroupByDate
- },
- {
- i18n("By Smart Date Ranges (of Thread Leaders)"), GroupByDateRange
- },
- {
- i18n("By Smart Sender/Receiver"), GroupBySenderOrReceiver
- },
- {
- i18n("By Sender"), GroupBySender
- },
- {
- i18n("By Receiver"), GroupByReceiver
- } };
+ i18nc("No grouping of messages", "None"), NoGrouping
+ },
+ {
+ i18n("By Exact Date (of Thread Leaders)"), GroupByDate
+ },
+ {
+ i18n("By Smart Date Ranges (of Thread Leaders)"), GroupByDateRange
+ },
+ {
+ i18n("By Smart Sender/Receiver"), GroupBySenderOrReceiver
+ },
+ {
+ i18n("By Sender"), GroupBySender
+ },
+ {
+ i18n("By Receiver"), GroupByReceiver
+ } };
}
QList< QPair< QString, int > > Aggregation::enumerateGroupExpandPolicyOptions(Grouping g)
{
QList< QPair< QString, int > > ret;
if (g == NoGrouping) {
return ret;
}
ret.append({ i18n("Never Expand Groups"), NeverExpandGroups });
if ((g == GroupByDate) || (g == GroupByDateRange)) {
ret.append({ i18n("Expand Recent Groups"), ExpandRecentGroups });
}
ret.append({ i18n("Always Expand Groups"), AlwaysExpandGroups });
return ret;
}
QList< QPair< QString, int > > Aggregation::enumerateThreadingOptions()
{
return { {
- i18nc("No threading of messages", "None"), NoThreading
- },
- {
- i18n("Perfect Only"), PerfectOnly
- },
- {
- i18n("Perfect and by References"), PerfectAndReferences
- },
- {
- i18n("Perfect, by References and by Subject"), PerfectReferencesAndSubject
- } };
+ i18nc("No threading of messages", "None"), NoThreading
+ },
+ {
+ i18n("Perfect Only"), PerfectOnly
+ },
+ {
+ i18n("Perfect and by References"), PerfectAndReferences
+ },
+ {
+ i18n("Perfect, by References and by Subject"), PerfectReferencesAndSubject
+ } };
}
QList< QPair< QString, int > > Aggregation::enumerateThreadLeaderOptions(Grouping g, Threading t)
{
QList< QPair< QString, int > > ret;
if (t == NoThreading) {
return ret;
}
ret.append({ i18n("Topmost Message"), TopmostMessage });
if ((g != GroupByDate) && (g != GroupByDateRange)) {
return ret;
}
ret.append({ i18n("Most Recent Message"), MostRecentMessage });
return ret;
}
QList< QPair< QString, int > > Aggregation::enumerateThreadExpandPolicyOptions(Threading t)
{
if (t == NoThreading) {
return {};
}
return { {
- i18n("Never Expand Threads"), NeverExpandThreads
- },
- {
- i18n("Expand Threads With Unread Messages"), ExpandThreadsWithUnreadMessages
- },
- {
- i18n("Expand Threads With Unread or Important Messages"), ExpandThreadsWithUnreadOrImportantMessages
- },
- {
- i18n("Always Expand Threads"), AlwaysExpandThreads
- } };
+ i18n("Never Expand Threads"), NeverExpandThreads
+ },
+ {
+ i18n("Expand Threads With Unread Messages"), ExpandThreadsWithUnreadMessages
+ },
+ {
+ i18n("Expand Threads With Unread or Important Messages"), ExpandThreadsWithUnreadOrImportantMessages
+ },
+ {
+ i18n("Always Expand Threads"), AlwaysExpandThreads
+ } };
}
QList< QPair< QString, int > > Aggregation::enumerateFillViewStrategyOptions()
{
return { {
- i18n("Favor Interactivity"), FavorInteractivity
- },
- {
- i18n("Favor Speed"), FavorSpeed
- },
- {
- i18n("Batch Job (No Interactivity)"), BatchNoInteractivity
- } };
+ i18n("Favor Interactivity"), FavorInteractivity
+ },
+ {
+ i18n("Favor Speed"), FavorSpeed
+ },
+ {
+ i18n("Batch Job (No Interactivity)"), BatchNoInteractivity
+ } };
}
diff --git a/messagelist/src/core/aggregation.h b/messagelist/src/core/aggregation.h
index 2db87627..5de8c6f2 100644
--- a/messagelist/src/core/aggregation.h
+++ b/messagelist/src/core/aggregation.h
@@ -1,299 +1,298 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_AGGREGATION_H
#define MESSAGELIST_CORE_AGGREGATION_H
class QDataStream;
#include <QList>
#include <QPair>
#include <QString>
#include "core/optionset.h"
namespace MessageList {
namespace Core {
/**
* A set of aggregation options that can be applied to the MessageList::Model in a single shot.
* The set defines the behaviours related to the population of the model, threading
* of messages and grouping.
*/
class Aggregation : public OptionSet
{
public:
/**
* Message grouping.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum Grouping {
NoGrouping, ///< Don't group messages at all
GroupByDate, ///< Group the messages by the date of the thread leader
GroupByDateRange, ///< Use smart (thread leader) date ranges ("Today","Yesterday","Last Week"...)
GroupBySenderOrReceiver, ///< Group by sender (incoming) or receiver (outgoing) field
GroupBySender, ///< Group by sender, always
GroupByReceiver ///< Group by receiver, always
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
// TODO: Group by message status: "Important messages", "Urgent messages", "To reply", "To do" etc...
// TODO: Group by message unread status: "Unread messages", "Read messages" (maybe "New" ?)
};
/**
* The available group expand policies.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum GroupExpandPolicy {
NeverExpandGroups, ///< Never expand groups during a view fill algorithm
ExpandRecentGroups, ///< Makes sense only with GroupByDate or GroupByDateRange
AlwaysExpandGroups ///< All groups are expanded as they are inserted
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
/**
* The available threading methods.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum Threading {
NoThreading, ///< Perform no threading at all
PerfectOnly, ///< Thread by "In-Reply-To" field only
PerfectAndReferences, ///< Thread by "In-Reply-To" and "References" fields
PerfectReferencesAndSubject ///< Thread by all of the above and try to match subjects too
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
/**
* The available thread leading options. Meaningless when threading is set to NoThreading.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum ThreadLeader {
TopmostMessage, ///< The thread grouping is computed from the topmost message (very similar to least recent, but might be different if timezones or machine clocks are screwed)
MostRecentMessage ///< The thread grouping is computed from the most recent message
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
/**
* The available thread expand policies. Meaningless when threading is set to NoThreading.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum ThreadExpandPolicy {
NeverExpandThreads, ///< Never expand any thread, this is fast
ExpandThreadsWithNewMessages, ///< DEPRECATED. New message status no longer exists.
ExpandThreadsWithUnreadMessages, ///< Expand threads with unread messages (this includes new)
AlwaysExpandThreads, ///< Expand all threads (this might be very slow)
ExpandThreadsWithUnreadOrImportantMessages ///< Expand threads with "hot" messages (this includes new, unread, important, todo)
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
/**
* The available fill view strategies.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum FillViewStrategy {
FavorInteractivity, ///< Do small chunks of work, small intervals between chunks to allow for UI event processing
FavorSpeed, ///< Do larger chunks of work, zero intervals between chunks
BatchNoInteractivity ///< Do one large chunk, no interactivity at all
// Warning: Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
private:
Grouping mGrouping;
GroupExpandPolicy mGroupExpandPolicy;
Threading mThreading;
ThreadLeader mThreadLeader;
ThreadExpandPolicy mThreadExpandPolicy;
FillViewStrategy mFillViewStrategy;
public:
explicit Aggregation();
explicit Aggregation(const Aggregation &opt);
explicit Aggregation(
- const QString &name, const QString &description, Grouping grouping, GroupExpandPolicy groupExpandPolicy, Threading threading, ThreadLeader threadLeader, ThreadExpandPolicy threadExpandPolicy,
- FillViewStrategy fillViewStrategy, bool readOnly);
+ const QString &name, const QString &description, Grouping grouping, GroupExpandPolicy groupExpandPolicy, Threading threading, ThreadLeader threadLeader, ThreadExpandPolicy threadExpandPolicy, FillViewStrategy fillViewStrategy, bool readOnly);
static bool compareName(Aggregation *agg1, Aggregation *agg2)
{
return agg1->name() < agg2->name();
}
public:
/**
* Returns the currently set Grouping option.
*/
Q_REQUIRED_RESULT Grouping grouping() const;
/**
* Sets the Grouping option.
*/
void setGrouping(Grouping g)
{
mGrouping = g;
}
/**
* Enumerates the available grouping options as a QList of
* pairs in that the first item is the localized description of the
* option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateGroupingOptions();
/**
* Returns the current GroupExpandPolicy.
*/
Q_REQUIRED_RESULT GroupExpandPolicy groupExpandPolicy() const
{
return mGroupExpandPolicy;
}
/**
* Sets the GroupExpandPolicy for the groups.
* Note that this option has no meaning if grouping is set to NoGrouping.
*/
void setGroupExpandPolicy(GroupExpandPolicy groupExpandPolicy)
{
mGroupExpandPolicy = groupExpandPolicy;
}
/**
* Enumerates the group sort direction options compatible with the specified Grouping.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateGroupExpandPolicyOptions(Grouping g);
/**
* Returns the current threading method.
*/
Q_REQUIRED_RESULT Threading threading() const
{
return mThreading;
}
/**
* Sets the threading method option.
*/
void setThreading(Threading t)
{
mThreading = t;
}
/**
* Enumerates the available threading method options.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateThreadingOptions();
/**
* Returns the current thread leader determination method.
*/
Q_REQUIRED_RESULT ThreadLeader threadLeader() const
{
return mThreadLeader;
}
/**
* Sets the current thread leader determination method.
* Please note that when Threading is set to NoThreading this value is meaningless
* and by policy should be set to TopmostMessage.
*/
void setThreadLeader(ThreadLeader tl)
{
mThreadLeader = tl;
}
/**
* Enumerates the thread leader determination methods compatible with the specified Threading
- * and the specified Gouping options.
+ * and the specified Grouping options.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateThreadLeaderOptions(Grouping g, Threading t);
/**
* Returns the current thread expand policy.
*/
ThreadExpandPolicy threadExpandPolicy() const
{
return mThreadExpandPolicy;
}
/**
* Sets the current thread expand policy.
* Please note that when Threading is set to NoThreading this value is meaningless
* and by policy should be set to NeverExpandThreads.
*/
void setThreadExpandPolicy(ThreadExpandPolicy threadExpandPolicy)
{
mThreadExpandPolicy = threadExpandPolicy;
}
/**
* Enumerates the thread expand policies compatible with the specified Threading option.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateThreadExpandPolicyOptions(Threading t);
/**
* Returns the current fill view strategy.
*/
Q_REQUIRED_RESULT FillViewStrategy fillViewStrategy() const
{
return mFillViewStrategy;
}
/**
* Sets the current fill view strategy.
*/
void setFillViewStrategy(FillViewStrategy fillViewStrategy)
{
mFillViewStrategy = fillViewStrategy;
}
/**
* Enumerates the fill view strategies.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateFillViewStrategyOptions();
/**
* Pure virtual reimplemented from OptionSet.
*/
void save(QDataStream &stream) const override;
/**
* Pure virtual reimplemented from OptionSet.
*/
bool load(QDataStream &stream) override;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_AGGREGATION_H
diff --git a/messagelist/src/core/item.h b/messagelist/src/core/item.h
index ad215f62..bf6a7c1f 100644
--- a/messagelist/src/core/item.h
+++ b/messagelist/src/core/item.h
@@ -1,413 +1,413 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_ITEM_H
#define MESSAGELIST_CORE_ITEM_H
#include <QList>
#include <QString>
#include <time.h> // for time_t
#include <kmime/kmime_headers.h>
#include <Akonadi/KMime/MessageStatus>
#include <core/model.h>
#include <messagelist_export.h>
namespace MessageList {
namespace Core {
class ItemPrivate;
/**
* A single item of the MessageList tree managed by MessageList::Model.
*
* This class stores basic information needed in all the subclasses which
* at the moment of writing are MessageItem and GroupHeaderItem.
*/
class MESSAGELIST_EXPORT Item
{
friend class Model;
friend class ModelPrivate;
public:
/**
* The type of the Item.
*/
enum Type {
GroupHeader, ///< This item is a GroupHeaderItem
Message, ///< This item is a MessageItem
InvisibleRoot ///< This item is just Item and it's the only InvisibleRoot per Model.
};
/**
* Specifies the initial expand status for the item that should be applied
* when it's attached to the viewable tree. Needed as a workaround for
* QTreeView limitations in handling item expansion.
*/
enum InitialExpandStatus {
ExpandNeeded, ///< Must expand when this item becomes viewable
NoExpandNeeded, ///< No expand needed at all
ExpandExecuted ///< Item already expanded
};
protected:
/**
* Creates an Item. Only derived classes and MessageList::Model should access this.
*/
Item(Type type);
Item(Type type, ItemPrivate *dd);
public:
/**
* Destroys the Item. Should be protected just like the constructor but the QList<>
* helpers need to access it, so it's public actually.
*/
virtual ~Item();
/**
* Returns the type of this item. The Type can be set only in the constructor.
*/
Q_REQUIRED_RESULT Type type() const;
/**
- * The initial expand status we have to honor when attacching to the viewable root.
+ * The initial expand status we have to honor when attaching to the viewable root.
*/
Q_REQUIRED_RESULT InitialExpandStatus initialExpandStatus() const;
/**
- * Set the initial expand status we have to honor when attacching to the viewable root.
+ * Set the initial expand status we have to honor when attaching to the viewable root.
*/
void setInitialExpandStatus(InitialExpandStatus initialExpandStatus);
/**
* Is this item attached to the viewable root ?
*/
Q_REQUIRED_RESULT bool isViewable() const;
/**
* Return true if Item pointed by it is an ancestor of this item (that is,
* if it is its parent, parent of its parent, parent of its parent of its parent etc...
*/
Q_REQUIRED_RESULT bool hasAncestor(const Item *it) const;
/**
* Makes this item viewable, that is, notifies its existence to any listener
- * attacched to the "rowsInserted()" signal, most notably QTreeView.
+ * attached to the "rowsInserted()" signal, most notably QTreeView.
*
* This will also make all the children viewable.
*/
void setViewable(Model *model, bool bViewable);
/**
* Return the list of child items. May be null.
*/
Q_REQUIRED_RESULT QList< Item * > *childItems() const;
/**
* Returns the child item at position idx or 0 if idx is out of the allowable range.
*/
Q_REQUIRED_RESULT Item *childItem(int idx) const;
/**
* Returns the first child item, if any.
*/
Q_REQUIRED_RESULT Item *firstChildItem() const;
/**
* Returns the item that is visually below the specified child if this item.
* Note that the returned item may belong to a completely different subtree.
*/
Q_REQUIRED_RESULT Item *itemBelowChild(Item *child);
/**
* Returns the item that is visually above the specified child if this item.
* Note that the returned item may belong to a completely different subtree.
*/
Q_REQUIRED_RESULT Item *itemAboveChild(Item *child);
/**
* Returns the deepest item in the subtree originating at this item.
*/
Q_REQUIRED_RESULT Item *deepestItem();
/**
* Returns the item that is visually below this item in the tree.
* Note that the returned item may belong to a completely different subtree.
*/
Q_REQUIRED_RESULT Item *itemBelow();
/**
* Returns the item that is visually above this item in the tree.
* Note that the returned item may belong to a completely different subtree.
*/
Q_REQUIRED_RESULT Item *itemAbove();
/**
* Debug helper. Dumps the structure of this subtree.
*/
void dump(const QString &prefix);
/**
* Returns the number of children of this Item.
*/
Q_REQUIRED_RESULT int childItemCount() const;
/**
* Convenience function that returns true if this item has children.
*/
Q_REQUIRED_RESULT bool hasChildren() const;
/**
* A structure used with MessageList::Item::childItemStats().
* Contains counts of total and unread messages in a subtree.
*/
class ChildItemStats
{
public:
unsigned int mTotalChildCount; // total
unsigned int mUnreadChildCount; // unread only
public:
ChildItemStats()
: mTotalChildCount(0)
, mUnreadChildCount(0)
{
}
};
/**
* Gathers statistics about child items.
* For performance purposes assumes that this item has children.
* You MUST check it before calling it.
*/
void childItemStats(ChildItemStats &stats) const;
/**
* Returns the actual index of the child Item item or -1 if
* item is not a child of this Item.
*/
int indexOfChildItem(Item *item) const;
/**
* Sets the cached guess for the index of this item in the parent's child list.
*
* This is used to speed up the index lookup with the following algorithm:
* Ask the parent if this item is at the position specified by index guess (this costs ~O(1)).
* If the position matches we have finished, if it doesn't then perform
* a linear search via indexOfChildItem() (which costs ~O(n)).
*/
void setIndexGuess(int index);
/**
* Returns the parent Item in the tree, or 0 if this item isn't attached to the tree.
* Please note that even if this item has a non-zero parent, it can be still non viewable.
* That is: the topmost parent of this item may be not attached to the viewable root.
*/
Q_REQUIRED_RESULT Item *parent() const;
/**
* Sets the parent for this item. You should also take care of inserting
* this item in the parent's child list.
*/
void setParent(Item *pParent);
/**
* Returns the topmost parent item that is not a Root item (that is, is a Message or GroupHeader).
*/
Q_REQUIRED_RESULT Item *topmostNonRoot();
/**
* Returns the status associated to this Item.
*/
const Akonadi::MessageStatus &status() const;
/**
* Sets the status associated to this Item.
*/
void setStatus(Akonadi::MessageStatus status);
/**
* Returns a string describing the status e.g: "Read, Forwarded, Important"
*/
Q_REQUIRED_RESULT QString statusDescription() const;
/**
* Returns the size of this item (size of the Message, mainly)
*/
size_t size() const;
/**
* Sets the size of this item (size of the Message, mainly)
*/
void setSize(size_t size);
/**
* A string with a text rappresentation of size(). This is computed on-the-fly
* and not cached.
*/
Q_REQUIRED_RESULT QString formattedSize() const;
/**
* Returns the date of this item
*/
time_t date() const;
/**
* Sets the date of this item
*/
void setDate(time_t date);
/**
* A string with a text rappresentation of date() obtained via Manager. This is computed on-the-fly
* and not cached.
*/
Q_REQUIRED_RESULT QString formattedDate() const;
/**
* Returns the maximum date in the subtree originating from this item.
* This is kept up-to-date by MessageList::Model.
*/
time_t maxDate() const;
/**
* Sets the maximum date in the subtree originating from this item.
*/
void setMaxDate(time_t date);
/**
* A string with a text rappresentation of maxDate() obtained via Manager. This is computed on-the-fly
* and not cached.
*/
Q_REQUIRED_RESULT QString formattedMaxDate() const;
/**
* Recompute the maximum date from the current children list.
* Return true if the current max date changed and false otherwise.
*/
bool recomputeMaxDate();
/**
* Returns the sender associated to this item.
*/
Q_REQUIRED_RESULT const QString &sender() const;
/**
* Sets the sender associated to this item.
*/
void setSender(const QString &sender);
/**
* Display sender.
*/
Q_REQUIRED_RESULT QString displaySender() const;
/**
* Returns the receiver associated to this item.
*/
const QString &receiver() const;
/**
* Sets the sender associated to this item.
*/
void setReceiver(const QString &receiver);
/**
* Display receiver.
*/
Q_REQUIRED_RESULT QString displayReceiver() const;
/**
* Returns the sender or the receiver, depending on the underlying StorageModel settings.
*/
const QString &senderOrReceiver() const;
/**
* Display sender or receiver.
*/
Q_REQUIRED_RESULT QString displaySenderOrReceiver() const;
/**
* Returns whether sender or receiver is supposed to be displayed.
*/
bool useReceiver() const;
/**
* Returns the subject associated to this Item.
*/
const QString &subject() const;
/**
* Sets the subject associated to this Item.
*/
void setSubject(const QString &subject);
/**
* This is meant to be called right after the constructor.
* It sets up several items at once (so even if not inlined it's still a single call)
* and it skips some calls that can be avoided at constructor time.
*/
void initialSetup(time_t date, size_t size, const QString &sender, const QString &receiver, bool useReceiver);
void setItemId(qint64 id);
Q_REQUIRED_RESULT qint64 itemId() const;
void setParentCollectionId(qint64 id);
Q_REQUIRED_RESULT qint64 parentCollectionId() const;
/**
* This is meant to be called right after the constructor for MessageItem objects.
* It sets up several items at once (so even if not inlined it's still a single call).
*/
void setSubjectAndStatus(const QString &subject, Akonadi::MessageStatus status);
/**
* Appends an Item to this item's child list.
* The Model is used for beginInsertRows()/endInsertRows() calls.
*/
Q_REQUIRED_RESULT int appendChildItem(Model *model, Item *child);
/**
* Appends a child item without inserting it via the model.
* This is useful in ThemeEditor which doesn't use a custom model for the items.
* You shouldn't need to use this function...
*/
void rawAppendChildItem(Item *child);
/**
* Removes a child from this item's child list without deleting it.
* The Model is used for beginRemoveRows()/endRemoveRows() calls.
*/
void takeChildItem(Model *model, Item *child);
/**
* Kills all the child items without emitting any signal, recursively.
* It should be used only when MessageList::Model is reset() afterwards.
*/
void killAllChildItems();
protected:
ItemPrivate *const d_ptr;
Q_DECLARE_PRIVATE(Item)
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_ITEM_H
diff --git a/messagelist/src/core/item_p.h b/messagelist/src/core/item_p.h
index 6f1a2754..cf4f1d88 100644
--- a/messagelist/src/core/item_p.h
+++ b/messagelist/src/core/item_p.h
@@ -1,509 +1,509 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_ITEM_P_H
#define MESSAGELIST_CORE_ITEM_P_H
#include "core/item.h"
#include "MessageCore/StringUtil"
-// See the MessageList::ItemPrivate::insertChildItem() function below for an explaination of this macro.
+// See the MessageList::ItemPrivate::insertChildItem() function below for an explanation of this macro.
#if __GNUC__ >= 3 //krazy:exclude=cpp
#define GCC_DONT_INLINE_THIS __attribute__((noinline))
#else
#define GCC_DONT_INLINE_THIS
#endif
namespace MessageList {
namespace Core {
class ItemPrivate
{
public:
explicit ItemPrivate(Item *owner)
: q(owner)
, mChildItems(nullptr)
, mParent(nullptr)
, mThisItemIndexGuess(0)
, mInitialExpandStatus(Item::NoExpandNeeded)
, mIsViewable(false)
, mUseReceiver(false)
{
}
virtual ~ItemPrivate()
{
}
/**
* Implements "in the middle" insertions of child items.
* The template argument class must export a static inline bool firstGreaterOrEqual( Item *, Item * )
* function which must return true when the first parameter item is considered to be greater
* or equal to the second parameter item and false otherwise.
*
* The insertion function *IS* our very bottleneck on flat views
* (when there are items with a lot of children). This is somewhat pathological...
* beside the binary search based insertion sort we actually can only do "statement level" optimization.
* I've found no better algorithms so far. If someone has a clever idea, please write to pragma
* at kvirc dot net :)
*
* GCC_DONT_INLINE_THIS is a macro defined above to __attribute__((noinline))
* if the current compiler is gcc. Without this attribute gcc attempts to inline THIS
* function inside the callers. The problem is that while inlining this function
* it doesn't inline the INNER comparison functions (which we _WANT_ to be inlined)
* because they would make the caller function too big.
*
* This is what gcc reports with -Winline:
*
* /home/pragma/kmail-soc/kmail/messagelistview/item.h:352: warning: inlining failed in call to
* 'static bool MessageList::ItemSubjectComparator::firstGreaterOrEqual(MessageList::Item*, MessageList::Item*)':
* --param large-function-growth limit reached while inlining the caller
* /home/pragma/kmail-soc/kmail/messagelistview/model.cpp:239: warning: called from here
*
* The comparison functions then appear in the symbol table:
*
* etherea kmail # nm /usr/kde/4.0/lib/libkmailprivate.so | grep Comparator
* 00000000005d2c10 W _ZN5KMail15MessageList18ItemDateComparator19firstGreaterOrEqualEPNS0_4ItemES3_
* 00000000005d2cb0 W _ZN5KMail15MessageList20ItemSenderComparator19firstGreaterOrEqualEPNS0_4ItemES3_
* 00000000005d2c50 W _ZN5KMail15MessageList21ItemSubjectComparator19firstGreaterOrEqualEPNS0_4ItemES3_
* ...
*
* With this attribute, instead, gcc doesn't complain at all and the inner comparisons
* *seem* to be inlined correctly (there is no sign of them in the symbol table).
*/
template< class ItemComparator, bool bAscending > int GCC_DONT_INLINE_THIS insertChildItem(Model *model, Item *child)
{
if (!mChildItems) {
return q->appendChildItem(model, child);
}
int cnt = mChildItems->count();
if (cnt < 1) {
return q->appendChildItem(model, child);
}
int idx;
Item *pivot;
if (bAscending) {
pivot = mChildItems->at(cnt - 1);
if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
return q->appendChildItem(model, child); // this is very likely in date based comparisons (FIXME: not in other ones)
}
// Binary search based insertion
int l = 0;
int h = cnt - 1;
for (;;) {
idx = (l + h) / 2;
pivot = mChildItems->at(idx);
if (ItemComparator::firstGreaterOrEqual(pivot, child)) { // gcc: <-- inline this instead, thnx
if (l < h) {
h = idx - 1;
} else {
break;
}
} else {
if (l < h) {
l = idx + 1;
} else {
idx++;
break;
}
}
}
} else {
pivot = mChildItems->at(0);
if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
idx = 0; // this is very likely in date based comparisons (FIXME: not in other ones)
} else {
// Binary search based insertion
int l = 0;
int h = cnt - 1;
for (;;) {
idx = (l + h) / 2;
pivot = mChildItems->at(idx);
if (ItemComparator::firstGreaterOrEqual(child, pivot)) { // gcc: <-- inline this instead, thnx
if (l < h) {
h = idx - 1;
} else {
break;
}
} else {
if (l < h) {
l = idx + 1;
} else {
idx++;
break;
}
}
}
}
}
Q_ASSERT(idx >= 0);
Q_ASSERT(idx <= mChildItems->count());
if (mIsViewable && model) {
model->beginInsertRows(model->index(q, 0), idx, idx); // BLEAH :D
}
mChildItems->insert(idx, child);
child->setIndexGuess(idx);
if (mIsViewable) {
if (model) {
model->endInsertRows(); // BLEAH :D
}
child->setViewable(model, true);
}
return idx;
}
/**
* Checks if the specified child item is actually in the wrong
* position in the child list and returns true in that case.
* Returns false if the item is already in the right position
* and no re-sorting is needed.
*/
template< class ItemComparator, bool bAscending > bool childItemNeedsReSorting(Item *child)
{
if (!mChildItems) {
return false; // not my child! (ugh... should assert ?)
}
const int idx = q->indexOfChildItem(child);
if (idx > 0) {
Item *prev = mChildItems->at(idx - 1);
if (bAscending) {
// child must be greater or equal to the previous item
if (!ItemComparator::firstGreaterOrEqual(child, prev)) {
return true; // wrong order: needs re-sorting
}
} else {
// previous must be greater or equal to the child item
if (!ItemComparator::firstGreaterOrEqual(prev, child)) {
return true; // wrong order: needs re-sorting
}
}
}
if (idx < (mChildItems->count() - 1)) {
Item *next = mChildItems->at(idx + 1);
if (bAscending) {
// next must be greater or equal to child
if (!ItemComparator::firstGreaterOrEqual(next, child)) {
return true; // wrong order: needs re-sorting
}
} else {
// child must be greater or equal to next
if (!ItemComparator::firstGreaterOrEqual(child, next)) {
return true; // wrong order: needs re-sorting
}
}
}
return false;
}
/**
* Internal handler for managing the children list.
*/
void childItemDead(Item *child);
Item *const q;
QList< Item * > *mChildItems; ///< List of children, may be 0
Item *mParent = nullptr; ///< The parent view item
time_t mMaxDate; ///< The maximum date in the subtree
time_t mDate; ///< The date of the message (or group date)
size_t mSize; ///< The size of the message in bytes
QString mSender; ///< The sender of the message (or group sender)
QString mReceiver; ///< The receiver of the message (or group receiver)
QString mSubject; ///< The subject of the message (or group subject)
qint64 mItemId; ///< The Akonadi item id
qint64 mParentCollectionId; ///< The Akonadi ID of collection that this particular item comes from (can be virtual collection)
Akonadi::MessageStatus mStatus; ///< The status of the message (may be extended to groups in the future)
int mThisItemIndexGuess; ///< The guess for the index in the parent's child list
Item::Type mType : 4; ///< The type of this item
Item::InitialExpandStatus mInitialExpandStatus : 4; ///< The expand status we have to honor when we attach to the viewable root
- bool mIsViewable : 1; ///< Is this item attacched to the viewable root ?
+ bool mIsViewable : 1; ///< Is this item attached to the viewable root ?
bool mUseReceiver : 1; ///< senderOrReceiver() returns receiver rather than sender
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemSizeComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (first->size() < second->size()) {
return false;
}
// When the sizes are equal compare by date too
if (first->size() == second->size()) {
return first->date() >= second->date();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemDateComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
// When the dates are equal compare by subject too
// This is useful, for example, in kernel mailing list where people
// send out multiple messages with patch parts at exactly the same time.
if (first->date() == second->date()) {
return first->subject() >= second->subject();
}
if (first->date() == static_cast<uint>(-1)) { // invalid is always smaller
return false;
}
if (second->date() == static_cast<uint>(-1)) {
return true;
}
if (first->date() < second->date()) {
return false;
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemMaxDateComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (first->maxDate() < second->maxDate()) {
return false;
}
if (first->maxDate() == second->maxDate()) {
return first->subject() >= second->subject();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemSubjectComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
const int ret = MessageCore::StringUtil::stripOffPrefixes(first->subject()).
compare(MessageCore::StringUtil::stripOffPrefixes(second->subject()), Qt::CaseInsensitive);
if (ret < 0) {
return false;
}
// compare by date when subjects are equal
if (ret == 0) {
return first->date() >= second->date();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemSenderComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
const int ret = first->displaySender().compare(
second->displaySender(), Qt::CaseInsensitive);
if (ret < 0) {
return false;
}
// compare by date when senders are equal
if (ret == 0) {
return first->date() >= second->date();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemReceiverComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
const int ret = first->displayReceiver().compare(
second->displayReceiver(), Qt::CaseInsensitive);
if (ret < 0) {
return false;
}
// compare by date when receivers are equal
if (ret == 0) {
return first->date() >= second->date();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemSenderOrReceiverComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
const int ret = first->displaySenderOrReceiver().compare(
second->displaySenderOrReceiver(), Qt::CaseInsensitive);
if (ret < 0) {
return false;
}
// compare by date when sender/receiver are equal
if (ret == 0) {
return first->date() >= second->date();
}
return true;
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemActionItemStatusComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (first->status().isToAct()) {
if (second->status().isToAct()) {
return first->date() >= second->date();
}
return true;
}
if (second->status().isToAct()) {
return false;
}
return first->date() >= second->date();
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemUnreadStatusComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (!first->status().isRead()) {
// fist is unread
if (!second->status().isRead()) {
return first->date() >= second->date(); // both are unread
}
// unread comes always first with respect to non-unread
return true;
}
if (!second->status().isRead()) {
return false;
}
// both are read
return first->date() >= second->date();
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemImportantStatusComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (!first->status().isImportant()) {
// fist is unread
if (!second->status().isImportant()) {
return first->date() >= second->date(); // both are unread
}
// unread comes always first with respect to non-unread
return true;
}
if (!second->status().isImportant()) {
return false;
}
// both are read
return first->date() >= second->date();
}
};
/**
* A helper class used with MessageList::Item::childItemNeedsReSorting() and
* MessageList::Item::insertChildItem().
*/
class ItemAttachmentStatusComparator
{
public:
static inline bool firstGreaterOrEqual(Item *first, Item *second)
{
if (!first->status().hasAttachment()) {
// fist is unread
if (!second->status().hasAttachment()) {
return first->date() >= second->date(); // both are unread
}
// unread comes always first with respect to non-unread
return true;
}
if (!second->status().hasAttachment()) {
return false;
}
// both are read
return first->date() >= second->date();
}
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_ITEM_P_H
diff --git a/messagelist/src/core/manager.cpp b/messagelist/src/core/manager.cpp
index 7b1e9138..ca7a216d 100644
--- a/messagelist/src/core/manager.cpp
+++ b/messagelist/src/core/manager.cpp
@@ -1,977 +1,975 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/manager.h"
#include "core/aggregation.h"
#include "core/theme.h"
#include "core/view.h"
#include "core/widgetbase.h"
#include "core/storagemodelbase.h"
#include "core/model.h"
#include "core/model_p.h"
#include "messagelistsettings.h"
#include "utils/configureaggregationsdialog.h"
#include "utils/configureaggregationsdialog_p.h"
#include "utils/configurethemesdialog.h"
#include "utils/configurethemesdialog_p.h"
#include "MessageCore/MessageCoreSettings"
#include "messagelistutil.h"
#include "messagelistutil_p.h"
#include <kmime/kmime_dateformatter.h> // kdepimlibs
#include <KConfig>
#include "messagelist_debug.h"
#include <KLocalizedString>
using namespace MessageList::Core;
Manager *Manager::mInstance = nullptr;
Manager::Manager()
: QObject()
{
mInstance = this;
mDateFormatter = new KMime::DateFormatter();
mCachedLocalizedUnknownText = i18nc("Unknown date", "Unknown");
loadConfiguration();
connect(MessageListSettings::self(), &MessageListSettings::configChanged,
this, &Manager::reloadGlobalConfiguration);
connect(MessageCore::MessageCoreSettings::self(), &MessageCore::MessageCoreSettings::configChanged,
this, &Manager::reloadGlobalConfiguration);
}
Manager::~Manager()
{
saveConfiguration();
removeAllAggregations();
removeAllThemes();
delete mDateFormatter;
mInstance = nullptr;
}
void Manager::registerWidget(Widget *pWidget)
{
if (!mInstance) {
mInstance = new Manager();
}
mInstance->mWidgetList.append(pWidget);
}
void Manager::unregisterWidget(Widget *pWidget)
{
if (!mInstance) {
qCWarning(MESSAGELIST_LOG) << ("ERROR: MessageList::Manager::unregisterWidget() called when Manager::mInstance is 0");
return;
}
mInstance->mWidgetList.removeAll(pWidget);
if (mInstance->mWidgetList.isEmpty()) {
delete mInstance;
mInstance = nullptr;
}
}
const Aggregation *Manager::aggregation(const QString &id)
{
Aggregation *opt = mAggregations.value(id);
if (opt) {
return opt;
}
return defaultAggregation();
}
const Aggregation *Manager::defaultAggregation()
{
KConfigGroup conf(MessageListSettings::self()->config(),
MessageList::Util::storageModelAggregationsGroup());
const QString aggregationId = conf.readEntry(QStringLiteral("DefaultSet"), "");
Aggregation *opt = nullptr;
if (!aggregationId.isEmpty()) {
opt = mAggregations.value(aggregationId);
}
if (opt) {
return opt;
}
// try just the first one
QMap< QString, Aggregation * >::ConstIterator it = mAggregations.constBegin();
if (it != mAggregations.constEnd()) {
return *it;
}
// aargh
createDefaultAggregations();
return *(mAggregations.constBegin());
}
void Manager::saveAggregationForStorageModel(const Akonadi::Collection &col, const QString &id, bool storageUsesPrivateAggregation)
{
if (!col.isValid()) {
return;
}
saveAggregationForStorageModel(QString::number(col.id()), id, storageUsesPrivateAggregation);
}
void Manager::saveAggregationForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateAggregation)
{
saveAggregationForStorageModel(storageModel->id(), id, storageUsesPrivateAggregation);
}
void Manager::saveAggregationForStorageModel(const QString &modelId, const QString &id, bool storageUsesPrivateAggregation)
{
KConfigGroup conf(MessageListSettings::self()->config(),
MessageList::Util::storageModelAggregationsGroup());
if (storageUsesPrivateAggregation) {
conf.writeEntry(MessageList::Util::setForStorageModelConfigName().arg(modelId), id);
} else {
conf.deleteEntry(MessageList::Util::setForStorageModelConfigName().arg(modelId));
}
if (!storageUsesPrivateAggregation) {
conf.writeEntry(QStringLiteral("DefaultSet"), id);
}
}
const Aggregation *Manager::aggregationForStorageModel(const Akonadi::Collection &col, bool *storageUsesPrivateAggregation)
{
Q_ASSERT(storageUsesPrivateAggregation);
*storageUsesPrivateAggregation = false; // this is by default
if (!col.isValid()) {
return defaultAggregation();
}
return Manager::aggregationForStorageModel(QString::number(col.id()), storageUsesPrivateAggregation);
}
const Aggregation *Manager::aggregationForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateAggregation)
{
Q_ASSERT(storageUsesPrivateAggregation);
*storageUsesPrivateAggregation = false; // this is by default
if (!storageModel) {
return defaultAggregation();
}
return Manager::aggregationForStorageModel(storageModel->id(), storageUsesPrivateAggregation);
}
const Aggregation *Manager::aggregationForStorageModel(const QString &storageId, bool *storageUsesPrivateAggregation)
{
KConfigGroup conf(MessageListSettings::self()->config(),
MessageList::Util::storageModelAggregationsGroup());
const QString aggregationId = conf.readEntry(MessageList::Util::setForStorageModelConfigName().arg(storageId), "");
Aggregation *opt = nullptr;
if (!aggregationId.isEmpty()) {
// a private aggregation was stored
opt = mAggregations.value(aggregationId);
*storageUsesPrivateAggregation = (opt != nullptr);
}
if (opt) {
return opt;
}
// FIXME: If the storageModel is a mailing list, maybe suggest a mailing-list like preset...
// We could even try to guess if the storageModel is a mailing list
return defaultAggregation();
}
void Manager::addAggregation(Aggregation *set)
{
Aggregation *old = mAggregations.value(set->id());
delete old;
mAggregations.insert(set->id(), set);
}
void Manager::createDefaultAggregations()
{
addAggregation(
new Aggregation(
i18n("Current Activity, Threaded"),
i18n("This view uses smart date range groups. " \
"Messages are threaded. " \
"So for example, in \"Today\" you will find all the messages arrived today " \
"and all the threads that have been active today."
),
Aggregation::GroupByDateRange,
Aggregation::ExpandRecentGroups,
Aggregation::PerfectReferencesAndSubject,
Aggregation::MostRecentMessage,
Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Current Activity, Flat"),
i18n("This view uses smart date range groups. " \
"Messages are not threaded. " \
"So for example, in \"Today\" you will simply find all the messages arrived today."
),
Aggregation::GroupByDateRange,
Aggregation::ExpandRecentGroups,
Aggregation::NoThreading,
Aggregation::MostRecentMessage,
Aggregation::NeverExpandThreads,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Activity by Date, Threaded"),
i18n("This view uses day-by-day groups. " \
"Messages are threaded. " \
"So for example, in \"Today\" you will find all the messages arrived today " \
"and all the threads that have been active today."
),
Aggregation::GroupByDate,
Aggregation::ExpandRecentGroups,
Aggregation::PerfectReferencesAndSubject,
Aggregation::MostRecentMessage,
Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Activity by Date, Flat"),
i18n("This view uses day-by-day groups. " \
"Messages are not threaded. " \
"So for example, in \"Today\" you will simply find all the messages arrived today."
),
Aggregation::GroupByDate,
Aggregation::ExpandRecentGroups,
Aggregation::NoThreading,
Aggregation::MostRecentMessage,
Aggregation::NeverExpandThreads,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Standard Mailing List"),
i18n("This is a plain and old mailing list view: no groups and heavy threading."),
Aggregation::NoGrouping,
Aggregation::NeverExpandGroups,
Aggregation::PerfectReferencesAndSubject,
Aggregation::TopmostMessage,
Aggregation::ExpandThreadsWithUnreadOrImportantMessages,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Flat Date View"),
i18n("This is a plain and old list of messages sorted by date: no groups and no threading." \
),
Aggregation::NoGrouping,
Aggregation::NeverExpandGroups,
Aggregation::NoThreading,
Aggregation::TopmostMessage,
Aggregation::NeverExpandThreads,
Aggregation::FavorInteractivity,
true
)
);
addAggregation(
new Aggregation(
i18n("Senders/Receivers, Flat"),
i18n("This view groups the messages by senders or receivers (depending on the folder " \
"type). " \
"Messages are not threaded."
),
Aggregation::GroupBySenderOrReceiver,
Aggregation::NeverExpandGroups,
Aggregation::NoThreading,
Aggregation::TopmostMessage,
Aggregation::NeverExpandThreads,
Aggregation::FavorSpeed,
true
)
);
addAggregation(
new Aggregation(
i18n("Thread Starters"),
i18n("This view groups the messages in threads and then groups the threads by the starting user."),
Aggregation::GroupBySenderOrReceiver,
Aggregation::NeverExpandGroups,
Aggregation::PerfectReferencesAndSubject,
Aggregation::TopmostMessage,
Aggregation::NeverExpandThreads,
Aggregation::FavorSpeed,
true
)
);
/*
FIX THIS
addAggregation(
new Aggregation(
i18n( "Recent Thread Starters" ),
i18n( "This view groups the messages in threads and then groups the threads by the starting user. " \
"Groups are sorted by the date of the first thread start. "
),
Aggregation::GroupBySenderOrReceiver,
Aggregation::SortGroupsByDateTimeOfMostRecent,
Aggregation::Descending,
Aggregation::PerfectReferencesAndSubject,
Aggregation::TopmostMessage,
Aggregation::SortMessagesByDateTime,
Aggregation::Descending
)
);
*/
}
void Manager::removeAllAggregations()
{
QMap< QString, Aggregation * >::ConstIterator end(mAggregations.constEnd());
for (QMap< QString, Aggregation * >::ConstIterator it = mAggregations.constBegin(); it != end; ++it) {
delete(*it);
}
mAggregations.clear();
}
void Manager::aggregationsConfigurationCompleted()
{
if (mAggregations.isEmpty()) {
createDefaultAggregations(); // panic
}
saveConfiguration(); // just to be sure :)
// notify all the widgets that they should reload the option set combos
Q_EMIT aggregationsChanged();
}
const SortOrder Manager::sortOrderForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateSortOrder)
{
Q_ASSERT(storageUsesPrivateSortOrder);
*storageUsesPrivateSortOrder = false; // this is by default
if (!storageModel) {
return SortOrder();
}
KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelSortOrderGroup());
SortOrder ret;
ret.readConfig(conf, storageModel->id(), storageUsesPrivateSortOrder);
return ret;
}
void Manager::saveSortOrderForStorageModel(const StorageModel *storageModel, const SortOrder &order, bool storageUsesPrivateSortOrder)
{
KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelSortOrderGroup());
order.writeConfig(conf, storageModel->id(), storageUsesPrivateSortOrder);
}
const Theme *Manager::theme(const QString &id)
{
Theme *opt = mThemes.value(id);
if (opt) {
return opt;
}
return defaultTheme();
}
const Theme *Manager::defaultTheme()
{
KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
const QString themeId = conf.readEntry(QStringLiteral("DefaultSet"), "");
Theme *opt = nullptr;
if (!themeId.isEmpty()) {
opt = mThemes.value(themeId);
}
if (opt) {
return opt;
}
// try just the first one
QMap< QString, Theme * >::ConstIterator it = mThemes.constBegin();
if (it != mThemes.constEnd()) {
return *it;
}
// aargh
createDefaultThemes();
it = mThemes.constBegin();
Q_ASSERT(it != mThemes.constEnd());
return *it;
}
void Manager::saveThemeForStorageModel(int index, const QString &id, bool storageUsesPrivateTheme)
{
saveThemeForStorageModel(QString::number(index), id, storageUsesPrivateTheme);
}
void Manager::saveThemeForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateTheme)
{
saveThemeForStorageModel(storageModel->id(), id, storageUsesPrivateTheme);
}
void Manager::saveThemeForStorageModel(const QString &storageModelIndex, const QString &id, bool storageUsesPrivateTheme)
{
KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
if (storageUsesPrivateTheme) {
conf.writeEntry(MessageList::Util::setForStorageModelConfigName().arg(storageModelIndex), id);
} else {
conf.deleteEntry(MessageList::Util::setForStorageModelConfigName().arg(storageModelIndex));
}
if (!storageUsesPrivateTheme) {
conf.writeEntry(QStringLiteral("DefaultSet"), id);
}
}
const Theme *Manager::themeForStorageModel(const Akonadi::Collection &col, bool *storageUsesPrivateTheme)
{
Q_ASSERT(storageUsesPrivateTheme);
*storageUsesPrivateTheme = false; // this is by default
if (!col.isValid()) {
return defaultTheme();
}
return Manager::themeForStorageModel(QString::number(col.id()), storageUsesPrivateTheme);
}
const Theme *Manager::themeForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateTheme)
{
Q_ASSERT(storageUsesPrivateTheme);
*storageUsesPrivateTheme = false; // this is by default
if (!storageModel) {
return defaultTheme();
}
return Manager::themeForStorageModel(storageModel->id(), storageUsesPrivateTheme);
}
const Theme *Manager::themeForStorageModel(const QString &id, bool *storageUsesPrivateTheme)
{
KConfigGroup conf(MessageListSettings::self()->config(), MessageList::Util::storageModelThemesGroup());
const QString themeId = conf.readEntry(MessageList::Util::setForStorageModelConfigName().arg(id), "");
Theme *opt = nullptr;
if (!themeId.isEmpty()) {
// a private theme was stored
opt = mThemes.value(themeId);
*storageUsesPrivateTheme = (opt != nullptr);
}
if (opt) {
return opt;
}
// FIXME: If the storageModel is a mailing list, maybe suggest a mailing-list like preset...
// We could even try to guess if the storageModel is a mailing list
// FIXME: Prefer right-to-left themes when application layout is RTL.
return defaultTheme();
}
void Manager::addTheme(Theme *set)
{
Theme *old = mThemes.value(set->id());
delete old;
mThemes.insert(set->id(), set);
}
-static Theme::Column *add_theme_simple_text_column(Theme *s, const QString &name, Theme::ContentItem::Type type, bool visibleByDefault, SortOrder::MessageSorting messageSorting, bool alignRight,
- bool addGroupHeaderItem)
+static Theme::Column *add_theme_simple_text_column(Theme *s, const QString &name, Theme::ContentItem::Type type, bool visibleByDefault, SortOrder::MessageSorting messageSorting, bool alignRight, bool addGroupHeaderItem)
{
Theme::Column *c = new Theme::Column();
c->setLabel(name);
c->setVisibleByDefault(visibleByDefault);
c->setMessageSorting(messageSorting);
Theme::Row *r = new Theme::Row();
Theme::ContentItem *i = new Theme::ContentItem(type);
if (alignRight) {
r->addRightItem(i);
} else {
r->addLeftItem(i);
}
c->addMessageRow(r);
if (addGroupHeaderItem) {
Theme::Row *r = new Theme::Row();
Theme::ContentItem *i = new Theme::ContentItem(type);
if (alignRight) {
r->addRightItem(i);
} else {
r->addLeftItem(i);
}
c->addGroupHeaderRow(r);
}
s->addColumn(c);
return c;
}
-static Theme::Column *add_theme_simple_icon_column(Theme *s, const QString &name, const QString &pixmapName, Theme::ContentItem::Type type, bool visibleByDefault,
- SortOrder::MessageSorting messageSorting)
+static Theme::Column *add_theme_simple_icon_column(Theme *s, const QString &name, const QString &pixmapName, Theme::ContentItem::Type type, bool visibleByDefault, SortOrder::MessageSorting messageSorting)
{
Theme::Column *c = new Theme::Column();
c->setLabel(name);
c->setPixmapName(pixmapName);
c->setVisibleByDefault(visibleByDefault);
c->setMessageSorting(messageSorting);
Theme::Row *r = new Theme::Row();
Theme::ContentItem *i = new Theme::ContentItem(type);
i->setSoftenByBlendingWhenDisabled(true);
r->addLeftItem(i);
c->addMessageRow(r);
s->addColumn(c);
return c;
}
void Manager::createDefaultThemes()
{
Theme *s;
Theme::Column *c;
Theme::Row *r;
Theme::ContentItem *i;
// The "Classic" backward compatible theme
s = new Theme(
i18nc("Default theme name", "Classic"),
i18n("A simple, backward compatible, single row theme"), true /*readOnly*/
);
c = new Theme::Column();
c->setLabel(i18nc("@title:column Subject of messages", "Subject"));
c->setMessageSorting(SortOrder::SortMessagesBySubject);
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::ExpandedStateIcon);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::GroupHeaderLabel);
i->setBold(true);
r->addLeftItem(i);
c->addGroupHeaderRow(r);
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::CombinedReadRepliedStateIcon);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::AttachmentStateIcon);
i->setHideWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::AnnotationIcon);
i->setHideWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::InvitationIcon);
i->setHideWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::SignatureStateIcon);
i->setHideWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::EncryptionStateIcon);
i->setHideWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::Subject);
r->addLeftItem(i);
c->addMessageRow(r);
s->addColumn(c);
c = add_theme_simple_text_column(s, i18n("Sender/Receiver"), Theme::ContentItem::SenderOrReceiver, true, SortOrder::SortMessagesBySenderOrReceiver, false, false);
c->setIsSenderOrReceiver(true);
add_theme_simple_text_column(s, i18nc("Sender of a message", "Sender"), Theme::ContentItem::Sender, false, SortOrder::SortMessagesBySender, false, false);
add_theme_simple_text_column(s, i18nc("Receiver of a message", "Receiver"), Theme::ContentItem::Receiver, false, SortOrder::SortMessagesByReceiver, false, false);
add_theme_simple_text_column(s, i18nc("Date of a message", "Date"), Theme::ContentItem::Date, true, SortOrder::SortMessagesByDateTime, false, false);
add_theme_simple_text_column(s, i18n("Most Recent Date"), Theme::ContentItem::MostRecentDate, false, SortOrder::SortMessagesByDateTimeOfMostRecent, false, true);
add_theme_simple_text_column(s, i18nc("Size of a message", "Size"), Theme::ContentItem::Size, false, SortOrder::SortMessagesBySize, false, false);
add_theme_simple_icon_column(s, i18nc("Attachment indication", "Attachment"), QStringLiteral(
"mail-attachment"), Theme::ContentItem::AttachmentStateIcon, false, SortOrder::SortMessagesByAttachmentStatus);
add_theme_simple_icon_column(s, i18n("Read/Unread"), QStringLiteral("mail-mark-unread-new"), Theme::ContentItem::ReadStateIcon, false, SortOrder::SortMessagesByUnreadStatus);
add_theme_simple_icon_column(s, i18n("Replied"), QStringLiteral("mail-replied"), Theme::ContentItem::RepliedStateIcon, false, SortOrder::NoMessageSorting);
add_theme_simple_icon_column(s, i18nc("Message importance indication", "Important"), QStringLiteral(
"emblem-important"), Theme::ContentItem::ImportantStateIcon, false, SortOrder::SortMessagesByImportantStatus);
add_theme_simple_icon_column(s, i18n("Action Item"), QStringLiteral("mail-task"), Theme::ContentItem::ActionItemStateIcon, false, SortOrder::SortMessagesByActionItemStatus);
add_theme_simple_icon_column(s, i18n("Spam/Ham"), QStringLiteral("mail-mark-junk"), Theme::ContentItem::SpamHamStateIcon, false, SortOrder::NoMessageSorting);
add_theme_simple_icon_column(s, i18n("Watched/Ignored"), QStringLiteral("mail-thread-watch"), Theme::ContentItem::WatchedIgnoredStateIcon, false, SortOrder::NoMessageSorting);
add_theme_simple_icon_column(s, i18n("Encryption"), QStringLiteral("mail-encrypted-full"), Theme::ContentItem::EncryptionStateIcon, false, SortOrder::NoMessageSorting);
add_theme_simple_icon_column(s, i18n("Signature"), QStringLiteral("mail-signed-verified"), Theme::ContentItem::SignatureStateIcon, false, SortOrder::NoMessageSorting);
add_theme_simple_icon_column(s, i18n("Tag List"), QStringLiteral("feed-subscribe"), Theme::ContentItem::TagList, false, SortOrder::NoMessageSorting);
s->resetColumnState(); // so it's initially set from defaults
addTheme(s);
// The Fancy theme
s = new Theme(
i18n("Smart"),
i18n("A smart multiline and multi item theme"), true /*readOnly*/
);
c = new Theme::Column();
c->setLabel(i18n("Message"));
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::ExpandedStateIcon);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::GroupHeaderLabel);
i->setBold(true);
r->addLeftItem(i);
c->addGroupHeaderRow(r);
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::Subject);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::ReadStateIcon);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::RepliedStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::AttachmentStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::AnnotationIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::InvitationIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::EncryptionStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::SignatureStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::TagList);
i->setHideWhenDisabled(true);
r->addRightItem(i);
c->addMessageRow(r);
Theme::Row *firstFancyRow = r; // save it so we can continue adding stuff below (after cloning the theme)
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::SenderOrReceiver);
i->setSoftenByBlending(true);
i->setItalic(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::Date);
i->setSoftenByBlending(true);
i->setItalic(true);
r->addRightItem(i);
c->addMessageRow(r);
s->addColumn(c);
// clone the "Fancy theme" here so we'll use it as starting point for the "Fancy with clickable status"
Theme *fancyWithClickableStatus = new Theme(*s);
fancyWithClickableStatus->detach();
fancyWithClickableStatus->generateUniqueId();
// and continue the "Fancy" specific settings
r = firstFancyRow;
i = new Theme::ContentItem(Theme::ContentItem::ActionItemStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::ImportantStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::SpamHamStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
i = new Theme::ContentItem(Theme::ContentItem::WatchedIgnoredStateIcon);
i->setHideWhenDisabled(true);
r->addRightItem(i);
s->setViewHeaderPolicy(Theme::NeverShowHeader);
s->resetColumnState(); // so it's initially set from defaults
addTheme(s);
// The "Fancy with Clickable Status" theme
s = fancyWithClickableStatus;
s->setName(i18n("Smart with Clickable Status"));
s->setDescription(i18n("A smart multiline and multi item theme with a clickable status column"));
s->setReadOnly(true);
c = new Theme::Column();
c->setLabel(i18n("Status"));
c->setVisibleByDefault(true);
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::ActionItemStateIcon);
i->setSoftenByBlendingWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::ImportantStateIcon);
i->setSoftenByBlendingWhenDisabled(true);
r->addLeftItem(i);
c->addMessageRow(r);
r = new Theme::Row();
i = new Theme::ContentItem(Theme::ContentItem::SpamHamStateIcon);
i->setSoftenByBlendingWhenDisabled(true);
r->addLeftItem(i);
i = new Theme::ContentItem(Theme::ContentItem::WatchedIgnoredStateIcon);
i->setSoftenByBlendingWhenDisabled(true);
r->addLeftItem(i);
c->addMessageRow(r);
s->addColumn(c);
s->resetColumnState(); // so it's initially set from defaults
addTheme(s);
}
void Manager::removeAllThemes()
{
QMap< QString, Theme * >::ConstIterator end(mThemes.constEnd());
for (QMap< QString, Theme * >::ConstIterator it = mThemes.constBegin(); it != end; ++it) {
delete(*it);
}
mThemes.clear();
}
void Manager::themesConfigurationCompleted()
{
if (mThemes.isEmpty()) {
createDefaultThemes(); // panic
}
saveConfiguration(); // just to be sure :)
// notify all the widgets that they should reload the option set combos
Q_EMIT themesChanged();
}
void Manager::reloadAllWidgets()
{
QList< Widget * >::ConstIterator end(mWidgetList.constEnd());
for (QList< Widget * >::ConstIterator it = mWidgetList.constBegin(); it != end; ++it) {
if ((*it)->view()) {
(*it)->view()->reload();
}
}
}
void Manager::reloadGlobalConfiguration()
{
// This is called when configuration changes (probably edited by the options dialog)
const int oldDateFormat = (int)mDateFormatter->format();
const QString oldDateCustomFormat = mDateFormatter->customFormat();
loadGlobalConfiguration();
if (
(oldDateFormat != (int)mDateFormatter->format())
|| (oldDateCustomFormat != mDateFormatter->customFormat())
) {
reloadAllWidgets();
}
}
void Manager::loadGlobalConfiguration()
{
// Load the date format
const KMime::DateFormatter::FormatType type = static_cast<KMime::DateFormatter::FormatType>(
MessageCore::MessageCoreSettings::self()->dateFormat());
mDateFormatter->setCustomFormat(MessageCore::MessageCoreSettings::self()->customDateFormat());
mDateFormatter->setFormat(type);
}
void Manager::loadConfiguration()
{
loadGlobalConfiguration();
{
// load Aggregations
KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Aggregations");
mAggregations.clear();
const int cnt = conf.readEntry("Count", 0);
int idx = 0;
while (idx < cnt) {
const QString data = conf.readEntry(QStringLiteral("Set%1").arg(idx), QString());
if (!data.isEmpty()) {
Aggregation *set = new Aggregation();
if (set->loadFromString(data)) {
if (Aggregation *old = mAggregations.value(set->id())) {
delete old;
}
mAggregations.insert(set->id(), set);
} else {
delete set; // b0rken
}
}
idx++;
}
if (mAggregations.isEmpty()) {
// don't allow zero configuration, create some presets
createDefaultAggregations();
}
}
{
// load Themes
KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Themes");
mThemes.clear();
const int cnt = conf.readEntry("Count", 0);
int idx = 0;
while (idx < cnt) {
const QString data = conf.readEntry(QStringLiteral("Set%1").arg(idx), QString());
if (!data.isEmpty()) {
Theme *set = new Theme();
if (set->loadFromString(data)) {
if (Theme *old = mThemes.value(set->id())) {
delete old;
}
mThemes.insert(set->id(), set);
} else {
qCWarning(MESSAGELIST_LOG) << "Saved theme loading failed";
delete set; // b0rken
}
}
++idx;
}
if (mThemes.isEmpty()) {
// don't allow zero configuration, create some presets
createDefaultThemes();
}
}
}
void Manager::saveGlobalConfiguration()
{
MessageListSettings::self()->save();
}
void Manager::saveConfiguration()
{
saveGlobalConfiguration();
{
// store aggregations
KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Aggregations");
//conf.clear();
conf.writeEntry("Count", mAggregations.count());
int idx = 0;
QMap< QString, Aggregation * >::ConstIterator end(mAggregations.end());
for (QMap< QString, Aggregation * >::ConstIterator it = mAggregations.constBegin(); it != end; ++it) {
conf.writeEntry(QStringLiteral("Set%1").arg(idx), (*it)->saveToString());
++idx;
}
}
{
// store themes
KConfigGroup conf(MessageListSettings::self()->config(), "MessageListView::Themes");
//conf.clear();
conf.writeEntry("Count", mThemes.count());
int idx = 0;
QMap< QString, Theme * >::ConstIterator end(mThemes.constEnd());
for (QMap< QString, Theme * >::ConstIterator it = mThemes.constBegin(); it != end; ++it) {
conf.writeEntry(QStringLiteral("Set%1").arg(idx), (*it)->saveToString());
++idx;
}
}
MessageListSettings::self()->config()->sync();
}
diff --git a/messagelist/src/core/manager.h b/messagelist/src/core/manager.h
index 296eacda..5aeda363 100644
--- a/messagelist/src/core/manager.h
+++ b/messagelist/src/core/manager.h
@@ -1,177 +1,177 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_MANAGER_H
#define MESSAGELIST_CORE_MANAGER_H
#include "core/sortorder.h"
#include <QList>
#include <QMap>
#include <QObject>
#include <collection.h>
namespace KMime {
class DateFormatter;
}
namespace MessageList {
namespace Core {
class Aggregation;
class Theme;
class StorageModel;
class Widget;
/**
* @brief: The manager for all the existing MessageList::Widget objects.
*
* This class is the "central" object of the whole MessageList framework.
* It's a singleton that can be accessed only by the means of static methods,
* is created automatically when the first MessageList::Widget object is created
* and destroyed automatically when the last MessageList::Widget object is destroyed.
*
- * This class takes care of loading/storing/mantaining the settings for the
+ * This class takes care of loading/storing/maintaining the settings for the
* whole MessageList framework. It also keeps track of all the existing
- * MessageList::Widget objects and takes care of uptdating them when settings change.
+ * MessageList::Widget objects and takes care of updating them when settings change.
*/
class Manager : public QObject
{
Q_OBJECT
protected:
explicit Manager();
~Manager();
private:
static Manager *mInstance;
QList< Widget * > mWidgetList;
QMap< QString, Aggregation * > mAggregations;
QMap< QString, Theme * > mThemes;
KMime::DateFormatter *mDateFormatter = nullptr;
QString mCachedLocalizedUnknownText;
public:
// instance management
static Manager *instance()
{
return mInstance;
}
// widget registration
static void registerWidget(Widget *pWidget);
static void unregisterWidget(Widget *pWidget);
const KMime::DateFormatter *dateFormatter() const
{
return mDateFormatter;
}
const QString &cachedLocalizedUnknownText() const
{
return mCachedLocalizedUnknownText;
}
// aggregation sets management
const Aggregation *aggregationForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateAggregation);
const Aggregation *aggregationForStorageModel(const QString &storageModel, bool *storageUsesPrivateAggregation);
const Aggregation *aggregationForStorageModel(const Akonadi::Collection &storageModel, bool *storageUsesPrivateAggregation);
void saveAggregationForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateAggregation);
void saveAggregationForStorageModel(const QString &index, const QString &id, bool storageUsesPrivateAggregation);
void saveAggregationForStorageModel(const Akonadi::Collection &col, const QString &id, bool storageUsesPrivateAggregation);
const Aggregation *defaultAggregation();
const Aggregation *aggregation(const QString &id);
void addAggregation(Aggregation *set);
void removeAllAggregations();
const QMap< QString, Aggregation * > &aggregations() const
{
return mAggregations;
}
/**
* This is called by the aggregation configuration dialog
* once the sets have been changed.
*/
void aggregationsConfigurationCompleted();
// sort order management
const SortOrder sortOrderForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateSortOrder);
void saveSortOrderForStorageModel(const StorageModel *storageModel, const SortOrder &order, bool storageUsesPrivateSortOrder);
// theme sets management
const Theme *themeForStorageModel(const Akonadi::Collection &col, bool *storageUsesPrivateTheme);
const Theme *themeForStorageModel(const StorageModel *storageModel, bool *storageUsesPrivateTheme);
const Theme *themeForStorageModel(const QString &id, bool *storageUsesPrivateTheme);
void saveThemeForStorageModel(const StorageModel *storageModel, const QString &id, bool storageUsesPrivateTheme);
void saveThemeForStorageModel(int index, const QString &id, bool storageUsesPrivateTheme);
void saveThemeForStorageModel(const QString &storageModelIndex, const QString &id, bool storageUsesPrivateTheme);
const Theme *defaultTheme();
const Theme *theme(const QString &id);
void addTheme(Theme *set);
void removeAllThemes();
const QMap< QString, Theme * > &themes() const
{
return mThemes;
}
/**
* This is called by the theme configuration dialog
* once the sets have been changed.
*/
void themesConfigurationCompleted();
protected Q_SLOTS:
/**
* Reloads the global configuration from the config files (so we assume it has changed)
* The settings private to MessageList (like Themes or Aggregations) aren't reloaded.
* If the global configuration has changed then all the views are reloaded.
*/
void reloadGlobalConfiguration();
/**
* Explicitly reloads the contents of all the widgets.
*/
void reloadAllWidgets();
Q_SIGNALS:
void aggregationsChanged();
void themesChanged();
private:
// internal configuration stuff
void loadConfiguration();
void saveConfiguration();
void loadGlobalConfiguration();
void saveGlobalConfiguration();
// internal option set management
void createDefaultAggregations();
void createDefaultThemes();
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_MANAGER_H
diff --git a/messagelist/src/core/model.cpp b/messagelist/src/core/model.cpp
index 2330a236..a3e005b9 100644
--- a/messagelist/src/core/model.cpp
+++ b/messagelist/src/core/model.cpp
@@ -1,4633 +1,4633 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
//
// This class is a rather huge monster. It's something that resembles a QAbstractItemModel
// (because it has to provide the interface for a QTreeView) but isn't entirely one
// (for optimization reasons). It basically manages a tree of items of two types:
// GroupHeaderItem and MessageItem. Be sure to read the docs for ViewItemJob.
//
// A huge credit here goes to Till Adam which seems to have written most
// (if not all) of the original KMail threading code. The KMHeaders implementation,
// the documentation and his clever ideas were my starting points and essential tools.
// This is why I'm adding his copyright entry (copied from headeritem.cpp) here even if
// he didn't write a byte in this file until now :)
//
// Szymon Tomasz Stefanek, 03 Aug 2008 04:50 (am)
//
// This class contains ideas from:
//
// kmheaders.cpp / kmheaders.h, headeritem.cpp / headeritem.h
// Copyright: (c) 2004 Till Adam < adam at kde dot org >
//
#include <config-messagelist.h>
#include "core/model.h"
#include "core/model_p.h"
#include "core/view.h"
#include "core/filter.h"
#include "core/groupheaderitem.h"
#include "core/item_p.h"
#include "core/messageitem.h"
#include "core/modelinvariantrowmapper.h"
#include "core/storagemodelbase.h"
#include "core/theme.h"
#include "core/delegate.h"
#include "core/manager.h"
#include "core/messageitemsetmanager.h"
#include "messagelist_debug.h"
#include <item.h>
#include <Akonadi/KMime/MessageStatus>
#include <AkonadiCore/Item>
#include "MessageCore/StringUtil"
#include <KLocalizedString>
#include <QApplication>
#include <QTimer>
#include <QDateTime>
#include <QScrollBar>
#include <QIcon>
#include <QLocale>
#include <QElapsedTimer>
#include <algorithm>
namespace MessageList {
namespace Core {
Q_GLOBAL_STATIC(QTimer, _k_heartBeatTimer)
/**
* A job in a "View Fill" or "View Cleanup" or "View Update" task.
*
* For a "View Fill" task a job is a set of messages
* that are contiguous in the storage. The set is expressed as a range
* of row indexes. The task "sweeps" the storage in the specified
* range, creates the appropriate Item instances and places them
* in the right position in the tree.
*
* The idea is that in a single instance and for the same StorageModel
* the jobs should never "cover" the same message twice. This assertion
* is enforced all around this source file.
*
* For a "View Cleanup" task the job is a list of ModelInvariantIndex
* objects (that are in fact MessageItem objects) that need to be removed
* from the view.
*
* For a "View Update" task the job is a list of ModelInvariantIndex
* objects (that are in fact MessageItem objects) that need to be updated.
*
* The interesting fact is that all the tasks need
* very similar operations to be performed on the message tree.
*
* For a "View Fill" we have 5 passes.
*
* Pass 1 scans the underlying storage, creates the MessageItem objects
* (which are subclasses of ModelInvariantIndex) and retrieves invariant
* storage indexes for them. It also builds threading caches and
* attempts to do some "easy" threading. If it succeeds in threading
* and some conditions apply then it also attaches the items to the view.
* Any unattached message is placed in a list.
*
* Pass 2 scans the list of messages that haven't been attached in
* the first pass and performs perfect and reference based threading.
* Since grouping of messages may depend on the "shape" of the thread
- * then certain threads aren't attacched to the view yet.
+ * then certain threads aren't attached to the view yet.
* Unassigned messages get stuffed into a list waiting for Pass3
* or directly to a list waiting for Pass4 (that is, Pass3 may be skipped
* if there is no hope to find an imperfect parent by subject based threading).
*
* Pass 3 scans the list of messages that haven't been attached in
* the first and second passes and performs subject based threading.
* Since grouping of messages may depend on the "shape" of the thread
- * then certain threads aren't attacched to the view yet.
+ * then certain threads aren't attached to the view yet.
* Anything unattached gets stuffed into the list waiting for Pass4.
*
* Pass 4 scans the unattached threads and puts them in the appropriate
* groups. After this pass nothing is unattached.
*
* Pass 5 eventually re-sorts the groups and removes the empty ones.
*
* For a "View Cleanup" we still have 5 passes.
*
* Pass 1 scans the list of invalidated ModelInvariantIndex-es, casts
* them to MessageItem objects and detaches them from the view.
* The orphan children of the destroyed items get stuffed in the list
* of unassigned messages that has been used also in the "View Fill" task above.
*
* Pass 2, 3, 4 and 5: same as "View Fill", just operating on the "orphaned"
* messages that need to be reattached to the view.
*
* For a "View Update" we still have 5 passes.
*
* Pass 1 scans the list of ModelInvariantIndex-es that need an update, casts
* them to MessageItem objects and handles the updates from storage.
* The updates may cause a regrouping so items might be stuffed in one
* of the lists for pass 4 or 5.
*
* Pass 2, 3 and 4 are simply empty.
*
* Pass 5: same as "View Fill", just operating on groups that require updates
* after the messages have been moved in pass 1.
*
* That's why we in fact have Pass1Fill, Pass1Cleanup, Pass1Update, Pass2, Pass3, Pass4 and Pass5 below.
* Pass1Fill, Pass1Cleanup and Pass1Update are exclusive and all of them proceed with Pass2 when finished.
*/
class ViewItemJob
{
public:
enum Pass {
Pass1Fill = 0, ///< Build threading caches, *TRY* to do some threading, try to attach something to the view
Pass1Cleanup = 1, ///< Kill messages, build list of orphans
Pass1Update = 2, ///< Update messages
Pass2 = 3, ///< Thread everything by using caches, try to attach more to the view
Pass3 = 4, ///< Do more threading (this time try to guess), try to attach more to the view
- Pass4 = 5, ///< Attach anything is still unattacched
+ Pass4 = 5, ///< Attach anything is still unattached
Pass5 = 6, ///< Eventually Re-sort group headers and remove the empty ones
LastIndex = 7 ///< Keep this at the end, needed to get the size of the enum
};
private:
// Data for "View Fill" jobs
int mStartIndex; ///< The first index (in the underlying storage) of this job
int mCurrentIndex; ///< The current index (in the underlying storage) of this job
int mEndIndex; ///< The last index (in the underlying storage) of this job
// Data for "View Cleanup" jobs
QList< ModelInvariantIndex * > *mInvariantIndexList; ///< Owned list of shallow pointers
// Common data
// The maximum time that we can spend "at once" inside viewItemJobStep() (milliseconds)
// The bigger this value, the larger chunks of work we do at once and less the time
// we loose in "breaking and resuming" the job. On the other side large values tend
// to make the view less responsive up to a "freeze" perception if this value is larger
// than 2000.
int mChunkTimeout;
// The interval between two fillView steps. The larger the interval, the more interactivity
// we have. The shorter the interval the more work we get done per second.
int mIdleInterval;
// The minimum number of messages we process in every viewItemJobStep() call
// The larger this value the less time we loose in checking the timeout every N messages.
// On the other side, making this very large may make the view less responsive
// if we're processing very few messages at a time and very high values (say > 10000) may
// eventually make our job unbreakable until the end.
int mMessageCheckCount;
Pass mCurrentPass;
// If this parameter is true then this job uses a "disconnected" UI.
// It's FAR faster since we don't need to call beginInsertRows()/endInsertRows()
// and we simply Q_EMIT a layoutChanged() at the end. It can be done only as the first
// job though: subsequent jobs can't use layoutChanged() as it looses the expanded
// state of items.
bool mDisconnectUI;
public:
/**
* Creates a "View Fill" operation job
*/
ViewItemJob(int startIndex, int endIndex, int chunkTimeout, int idleInterval, int messageCheckCount, bool disconnectUI = false)
: mStartIndex(startIndex)
, mCurrentIndex(startIndex)
, mEndIndex(endIndex)
, mInvariantIndexList(nullptr)
, mChunkTimeout(chunkTimeout)
, mIdleInterval(idleInterval)
, mMessageCheckCount(messageCheckCount)
, mCurrentPass(Pass1Fill)
, mDisconnectUI(disconnectUI)
{
}
/**
* Creates a "View Cleanup" or "View Update" operation job
*/
ViewItemJob(Pass pass, QList< ModelInvariantIndex * > *invariantIndexList, int chunkTimeout, int idleInterval, int messageCheckCount)
: mStartIndex(0)
, mCurrentIndex(0)
, mEndIndex(invariantIndexList->count() - 1)
, mInvariantIndexList(invariantIndexList)
, mChunkTimeout(chunkTimeout)
, mIdleInterval(idleInterval)
, mMessageCheckCount(messageCheckCount)
, mCurrentPass(pass)
, mDisconnectUI(false)
{
}
~ViewItemJob()
{
delete mInvariantIndexList;
}
public:
int startIndex() const
{
return mStartIndex;
}
void setStartIndex(int startIndex)
{
mStartIndex = startIndex;
mCurrentIndex = startIndex;
}
int currentIndex() const
{
return mCurrentIndex;
}
void setCurrentIndex(int currentIndex)
{
mCurrentIndex = currentIndex;
}
int endIndex() const
{
return mEndIndex;
}
void setEndIndex(int endIndex)
{
mEndIndex = endIndex;
}
Pass currentPass() const
{
return mCurrentPass;
}
void setCurrentPass(Pass pass)
{
mCurrentPass = pass;
}
int idleInterval() const
{
return mIdleInterval;
}
int chunkTimeout() const
{
return mChunkTimeout;
}
int messageCheckCount() const
{
return mMessageCheckCount;
}
QList< ModelInvariantIndex * > *invariantIndexList() const
{
return mInvariantIndexList;
}
bool disconnectUI() const
{
return mDisconnectUI;
}
};
} // namespace Core
} // namespace MessageList
using namespace MessageList::Core;
Model::Model(View *pParent)
: QAbstractItemModel(pParent)
, d(new ModelPrivate(this))
{
d->mRecursionCounterForReset = 0;
d->mStorageModel = nullptr;
d->mView = pParent;
d->mAggregation = nullptr;
d->mTheme = nullptr;
d->mSortOrder = nullptr;
d->mFilter = nullptr;
d->mPersistentSetManager = nullptr;
d->mInLengthyJobBatch = false;
d->mLastSelectedMessageInFolder = nullptr;
d->mLoading = false;
d->mRootItem = new Item(Item::InvisibleRoot);
d->mRootItem->setViewable(nullptr, true);
d->mFillStepTimer.setSingleShot(true);
d->mInvariantRowMapper = new ModelInvariantRowMapper();
d->mModelForItemFunctions = this;
connect(&d->mFillStepTimer, &QTimer::timeout,
this, [this]() {
d->viewItemJobStep();
});
d->mCachedTodayLabel = i18n("Today");
d->mCachedYesterdayLabel = i18n("Yesterday");
d->mCachedUnknownLabel = i18nc("Unknown date",
"Unknown");
d->mCachedLastWeekLabel = i18n("Last Week");
d->mCachedTwoWeeksAgoLabel = i18n("Two Weeks Ago");
d->mCachedThreeWeeksAgoLabel = i18n("Three Weeks Ago");
d->mCachedFourWeeksAgoLabel = i18n("Four Weeks Ago");
d->mCachedFiveWeeksAgoLabel = i18n("Five Weeks Ago");
d->mCachedWatchedOrIgnoredStatusBits = Akonadi::MessageStatus::statusIgnored().toQInt32() | Akonadi::MessageStatus::statusWatched().toQInt32();
connect(_k_heartBeatTimer(), &QTimer::timeout,
this, [this]() {
d->checkIfDateChanged();
});
if (!_k_heartBeatTimer->isActive()) { // First model starts it
_k_heartBeatTimer->start(60000); // 1 minute
}
}
Model::~Model()
{
setStorageModel(nullptr);
d->clearJobList();
d->mOldestItem = nullptr;
d->mNewestItem = nullptr;
d->clearUnassignedMessageLists();
d->clearOrphanChildrenHash();
d->clearThreadingCacheReferencesIdMD5ToMessageItem();
d->clearThreadingCacheMessageSubjectMD5ToMessageItem();
delete d->mPersistentSetManager;
// Delete the invariant row mapper before removing the items.
// It's faster since the items will not need to call the invariant
delete d->mInvariantRowMapper;
delete d->mRootItem;
delete d;
}
void Model::setAggregation(const Aggregation *aggregation)
{
d->mAggregation = aggregation;
d->mView->setRootIsDecorated((d->mAggregation->grouping() == Aggregation::NoGrouping)
&& (d->mAggregation->threading() != Aggregation::NoThreading));
}
void Model::setTheme(const Theme *theme)
{
d->mTheme = theme;
}
void Model::setSortOrder(const SortOrder *sortOrder)
{
d->mSortOrder = sortOrder;
}
const SortOrder *Model::sortOrder() const
{
return d->mSortOrder;
}
void Model::setFilter(const Filter *filter)
{
d->mFilter = filter;
if (d->mFilter) {
connect(d->mFilter, &Filter::finished, this, [this]() {
d->slotApplyFilter();
});
}
d->slotApplyFilter();
}
void ModelPrivate::slotApplyFilter()
{
auto childList = mRootItem->childItems();
if (!childList) {
return;
}
QModelIndex idx; // invalid
QApplication::setOverrideCursor(Qt::WaitCursor);
for (const auto child : qAsConst(*childList)) {
applyFilterToSubtree(child, idx);
}
QApplication::restoreOverrideCursor();
}
bool ModelPrivate::applyFilterToSubtree(Item *item, const QModelIndex &parentIndex)
{
// This function applies the current filter (eventually empty)
// to a message tree starting at "item".
if (!mModelForItemFunctions) {
qCWarning(MESSAGELIST_LOG) << "Cannot apply filter, the UI must be not disconnected.";
return true;
}
Q_ASSERT(item); // the item must obviously be valid
Q_ASSERT(item->isViewable()); // the item must be viewable
// Apply to children first
auto childList = item->childItems();
bool childrenMatch = false;
QModelIndex thisIndex = q->index(item, 0);
if (childList) {
for (const auto child : qAsConst(*childList)) {
if (applyFilterToSubtree(child, thisIndex)) {
childrenMatch = true;
}
}
}
if (!mFilter) { // empty filter always matches (but does not expand items)
mView->setRowHidden(thisIndex.row(), parentIndex, false);
return true;
}
if (childrenMatch) {
mView->setRowHidden(thisIndex.row(), parentIndex, false);
if (!mView->isExpanded(thisIndex)) {
mView->expand(thisIndex);
}
return true;
}
if (item->type() == Item::Message) {
if (mFilter->match((MessageItem *)item)) {
mView->setRowHidden(thisIndex.row(), parentIndex, false);
return true;
}
} // else this is a group header and it never explicitly matches
// filter doesn't match, hide the item
mView->setRowHidden(thisIndex.row(), parentIndex, true);
return false;
}
int Model::columnCount(const QModelIndex &parent) const
{
if (!d->mTheme) {
return 0;
}
if (parent.column() > 0) {
return 0;
}
return d->mTheme->columns().count();
}
QVariant Model::data(const QModelIndex &index, int role) const
{
/// this is called only when Akonadi is using the selectionmodel
/// for item actions. since akonadi uses the ETM ItemRoles, and the
/// messagelist uses its own internal roles, here we respond
/// to the ETM ones.
auto item = static_cast<Item *>(index.internalPointer());
switch (role) {
/// taken from entitytreemodel.h
case Qt::UserRole + 1: //EntityTreeModel::ItemIdRole
if (item->type() == MessageList::Core::Item::Message) {
auto mItem = static_cast<MessageItem *>(item);
return QVariant::fromValue(mItem->akonadiItem().id());
} else {
return QVariant();
}
break;
case Qt::UserRole + 2: //EntityTreeModel::ItemRole
if (item->type() == MessageList::Core::Item::Message) {
auto mItem = static_cast<MessageItem *>(item);
return QVariant::fromValue(mItem->akonadiItem());
} else {
return QVariant();
}
break;
case Qt::UserRole + 3: //EntityTreeModel::MimeTypeRole
if (item->type() == MessageList::Core::Item::Message) {
return QStringLiteral("message/rfc822");
} else {
return QVariant();
}
break;
case Qt::AccessibleTextRole:
if (item->type() == MessageList::Core::Item::Message) {
auto mItem = static_cast<MessageItem *>(item);
return mItem->accessibleText(d->mTheme, index.column());
} else if (item->type() == MessageList::Core::Item::GroupHeader) {
if (index.column() > 0) {
return QString();
}
auto hItem = static_cast<GroupHeaderItem *>(item);
return hItem->label();
}
return QString();
break;
default:
return QVariant();
}
}
QVariant Model::headerData(int section, Qt::Orientation, int role) const
{
if (!d->mTheme) {
return QVariant();
}
auto column = d->mTheme->column(section);
if (!column) {
return QVariant();
}
if (d->mStorageModel && column->isSenderOrReceiver()
&& (role == Qt::DisplayRole)) {
if (d->mStorageModelContainsOutboundMessages) {
return QVariant(i18n("Receiver"));
}
return QVariant(i18n("Sender"));
}
const bool columnPixmapEmpty(column->pixmapName().isEmpty());
if ((role == Qt::DisplayRole) && columnPixmapEmpty) {
return QVariant(column->label());
} else if ((role == Qt::ToolTipRole) && !columnPixmapEmpty) {
return QVariant(column->label());
} else if ((role == Qt::DecorationRole) && !columnPixmapEmpty) {
return QVariant(QIcon::fromTheme(column->pixmapName()));
}
return QVariant();
}
QModelIndex Model::index(Item *item, int column) const
{
if (!d->mModelForItemFunctions) {
return QModelIndex(); // called with disconnected UI: the item isn't known on the Qt side, yet
}
if (!item) {
return QModelIndex();
}
// FIXME: This function is a bottleneck (the caching in indexOfChildItem only works 30% of the time)
auto par = item->parent();
if (!par) {
if (item != d->mRootItem) {
item->dump(QString());
}
return QModelIndex();
}
const int index = par->indexOfChildItem(item);
if (index < 0) {
return QModelIndex(); // BUG
}
return createIndex(index, column, item);
}
QModelIndex Model::index(int row, int column, const QModelIndex &parent) const
{
if (!d->mModelForItemFunctions) {
return QModelIndex(); // called with disconnected UI: the item isn't known on the Qt side, yet
}
#ifdef READD_THIS_IF_YOU_WANT_TO_PASS_MODEL_TEST
if (column < 0) {
return QModelIndex(); // senseless column (we could optimize by skipping this check but ModelTest from trolltech is pedantic)
}
#endif
const Item *item;
if (parent.isValid()) {
item = static_cast<const Item *>(parent.internalPointer());
if (!item) {
return QModelIndex(); // should never happen
}
} else {
item = d->mRootItem;
}
if (parent.column() > 0) {
return QModelIndex(); // parent column is not 0: shouldn't have children (as per Qt documentation)
}
Item *child = item->childItem(row);
if (!child) {
return QModelIndex(); // no such row in parent
}
return createIndex(row, column, child);
}
QModelIndex Model::parent(const QModelIndex &modelIndex) const
{
Q_ASSERT(d->mModelForItemFunctions); // should be never called with disconnected UI
if (!modelIndex.isValid()) {
return QModelIndex(); // should never happen
}
auto item = static_cast<Item *>(modelIndex.internalPointer());
if (!item) {
return QModelIndex();
}
auto par = item->parent();
if (!par) {
return QModelIndex(); // should never happen
}
//return index( par, modelIndex.column() );
return index(par, 0); // parents are always in column 0 (as per Qt documentation)
}
int Model::rowCount(const QModelIndex &parent) const
{
if (!d->mModelForItemFunctions) {
return 0; // called with disconnected UI
}
const Item *item;
if (parent.isValid()) {
item = static_cast<const Item *>(parent.internalPointer());
if (!item) {
return 0; // should never happen
}
} else {
item = d->mRootItem;
}
if (!item->isViewable()) {
return 0;
}
return item->childItemCount();
}
class RecursionPreventer
{
public:
RecursionPreventer(int &counter)
: mCounter(counter)
{
mCounter++;
}
~RecursionPreventer()
{
mCounter--;
}
bool isRecursive() const
{
return mCounter > 1;
}
private:
int &mCounter;
};
StorageModel *Model::storageModel() const
{
return d->mStorageModel;
}
void ModelPrivate::clear()
{
q->beginResetModel();
if (mFillStepTimer.isActive()) {
mFillStepTimer.stop();
}
// Kill pre-selection at this stage
mPreSelectionMode = PreSelectNone;
mLastSelectedMessageInFolder = nullptr;
mOldestItem = nullptr;
mNewestItem = nullptr;
// Reset the row mapper before removing items
// This is faster since the items don't need to access the mapper.
mInvariantRowMapper->modelReset();
clearJobList();
clearUnassignedMessageLists();
clearOrphanChildrenHash();
mGroupHeaderItemHash.clear();
mGroupHeadersThatNeedUpdate.clear();
mThreadingCacheMessageIdMD5ToMessageItem.clear();
mThreadingCacheMessageInReplyToIdMD5ToMessageItem.clear();
clearThreadingCacheReferencesIdMD5ToMessageItem();
clearThreadingCacheMessageSubjectMD5ToMessageItem();
mViewItemJobStepChunkTimeout = 100;
mViewItemJobStepIdleInterval = 10;
mViewItemJobStepMessageCheckCount = 10;
delete mPersistentSetManager;
mPersistentSetManager = nullptr;
mCurrentItemToRestoreAfterViewItemJobStep = nullptr;
mTodayDate = QDate::currentDate();
// FIXME: CLEAR THE FILTER HERE AS WE CAN'T APPLY IT WITH UI DISCONNECTED!
mRootItem->killAllChildItems();
q->endResetModel();
//Q_EMIT headerDataChanged();
mView->selectionModel()->clearSelection();
}
void Model::setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode)
{
// Prevent a case of recursion when opening a folder that has a message and the folder was
// never opened before.
RecursionPreventer preventer(d->mRecursionCounterForReset);
if (preventer.isRecursive()) {
return;
}
d->clear();
if (d->mStorageModel) {
// Disconnect all signals from old storageModel
std::for_each(d->mStorageModelConnections.cbegin(), d->mStorageModelConnections.cend(),
[](const QMetaObject::Connection &c) -> bool {
return QObject::disconnect(c);
});
d->mStorageModelConnections.clear();
}
const bool isReload = (d->mStorageModel == storageModel);
d->mStorageModel = storageModel;
if (!d->mStorageModel) {
return; // no folder: nothing to fill
}
// Save threading cache of the previous folder, but only if the cache was
// enabled and a different folder is being loaded - reload of the same folder
// means change in aggregation in which case we will have to re-build the
// cache so there's no point saving the current threading cache.
if (d->mThreadingCache.isEnabled() && !isReload) {
d->mThreadingCache.save();
} else {
if (isReload) {
qCDebug(MESSAGELIST_LOG) << "Identical folder reloaded, not saving old threading cache";
} else {
qCDebug(MESSAGELIST_LOG) << "Threading disabled in previous folder, not saving threading cache";
}
}
// Load threading cache for the new folder, but only if threading is enabled,
// otherwise we would just be caching a flat list.
if (d->mAggregation->threading() != Aggregation::NoThreading) {
d->mThreadingCache.setEnabled(true);
d->mThreadingCache.load(d->mStorageModel->id(), d->mAggregation);
} else {
// No threading, no cache - don't even bother inserting entries into the
// cache or trying to look them up there
d->mThreadingCache.setEnabled(false);
qCDebug(MESSAGELIST_LOG) << "Threading disabled in folder" << d->mStorageModel->id() << ", not using threading cache";
}
d->mPreSelectionMode = preSelectionMode;
d->mStorageModelContainsOutboundMessages = d->mStorageModel->containsOutboundMessages();
d->mStorageModelConnections = {
connect(d->mStorageModel, &StorageModel::rowsInserted,
this, [this](const QModelIndex &parent, int first, int last)
{
d->slotStorageModelRowsInserted(parent, first, last);
}),
connect(d->mStorageModel, &StorageModel::rowsRemoved,
this, [this](const QModelIndex &parent, int first, int last)
{
d->slotStorageModelRowsRemoved(parent, first, last);
}),
connect(d->mStorageModel, &StorageModel::layoutChanged,
this, [this]()
{
d->slotStorageModelLayoutChanged();
}),
connect(d->mStorageModel, &StorageModel::modelReset,
this, [this]()
{
d->slotStorageModelLayoutChanged();
}),
connect(d->mStorageModel, &StorageModel::dataChanged,
this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
d->slotStorageModelDataChanged(topLeft, bottomRight);
}),
connect(d->mStorageModel, &StorageModel::headerDataChanged,
this, [this](Qt::Orientation orientation, int first, int last)
{
d->slotStorageModelHeaderDataChanged(orientation, first, last);
})
};
if (d->mStorageModel->rowCount() == 0) {
return; // folder empty: nothing to fill
}
// Here we use different strategies based on user preference and the folder size.
// The knobs we can tune are:
//
// - The number of jobs used to scan the whole folder and their order
//
// There are basically two approaches to this. One is the "single big job"
// approach. It scans the folder from the beginning to the end in a single job
// entry. The job passes are done only once. It's advantage is that it's simplier
// and it's less likely to generate imperfect parent threadings. The bad
// side is that since the folders are "sort of" date ordered then the most interesting
// messages show up at the end of the work. Not nice for large folders.
// The other approach uses two jobs. This is a bit slower but smarter strategy.
// First we scan the latest 1000 messages and *then* take care of the older ones.
// This will show up the most interesting messages almost immediately. (Well...
// All this assuming that the underlying storage always appends the newly arrived messages)
// The strategy is slower since it generates some imperfect parent threadings which must be
// adjusted by the second job. For instance, in my kernel mailing list folder this "smart" approach
// generates about 150 additional imperfectly threaded children... but the "today"
// messages show up almost immediately. The two-chunk job also makes computing
// the percentage user feedback a little harder and might break some optimization
// in the insertions (we're able to optimize appends and prepends but a chunked
// job is likely to split our work at a boundary where messages are always inserted
// in the middle of the list).
//
// - The maximum time to spend inside a single job step
//
// The larger this time, the greater the number of messages per second that this
// engine can process but also greater time with frozen UI -> less interactivity.
// Reasonable values start at 50 msecs. Values larger than 300 msecs are very likely
- // to be percieved by the user as UI non-reactivity.
+ // to be perceived by the user as UI non-reactivity.
//
// - The number of messages processed in each job step subchunk.
//
// A job subchunk is processed without checking the maximum time above. This means
// that each job step will process at least the number of messages specified by this value.
// Very low values mean that we respect the maximum time very carefully but we also
// waste time to check if we ran out of time :)
// Very high values are likely to cause the engine to not respect the maximum step time.
// Reasonable values go from 5 to 100.
//
// - The "idle" time between two steps
//
// The lower this time, the greater the number of messages per second that this
// engine can process but also lower time for the UI to process events -> less interactivity.
// A value of 0 here means that Qt will trigger the timer as soon as it has some
// idle time to spend. UI events will be still processed but slowdowns are possible.
// 0 is reasonable though. Values larger than 200 will tend to make the total job
// completion times high.
//
// If we have no filter it seems that we can apply a huge optimization.
// We disconnect the UI for the first huge filling job. This allows us
// to save the extremely expensive beginInsertRows()/endInsertRows() calls
// and call a single layoutChanged() at the end. This slows down a lot item
// expansion. But on the other side if only few items need to be expanded
// then this strategy is better. If filtering is enabled then this strategy
// isn't applicable (because filtering requires interaction with the UI
// while the data is loading).
// So...
// For the very first small chunk it's ok to work with disconnected UI as long
// as we have no filter. The first small chunk is always 1000 messages, so
// even if all of them are expanded, it's still somewhat acceptable.
bool canDoFirstSmallChunkWithDisconnectedUI = !d->mFilter;
// Larger works need a bigger condition: few messages must be expanded in the end.
bool canDoJobWithDisconnectedUI
=// we have no filter
!d->mFilter
&& (
- // we do no threading at all
- (d->mAggregation->threading() == Aggregation::NoThreading)
- || // or we never expand threads
- (d->mAggregation->threadExpandPolicy() == Aggregation::NeverExpandThreads)
- || // or we expand threads but we'll be going to expand really only a few
- (
- // so we don't expand them all
- (d->mAggregation->threadExpandPolicy() != Aggregation::AlwaysExpandThreads)
- && // and we'd expand only a few in fact
- (d->mStorageModel->initialUnreadRowCountGuess() < 1000)
- )
- );
+ // we do no threading at all
+ (d->mAggregation->threading() == Aggregation::NoThreading)
+ || // or we never expand threads
+ (d->mAggregation->threadExpandPolicy() == Aggregation::NeverExpandThreads)
+ || // or we expand threads but we'll be going to expand really only a few
+ (
+ // so we don't expand them all
+ (d->mAggregation->threadExpandPolicy() != Aggregation::AlwaysExpandThreads)
+ && // and we'd expand only a few in fact
+ (d->mStorageModel->initialUnreadRowCountGuess() < 1000)
+ )
+ );
switch (d->mAggregation->fillViewStrategy()) {
case Aggregation::FavorInteractivity:
// favor interactivity
if ((!canDoJobWithDisconnectedUI) && (d->mStorageModel->rowCount() > 3000)) { // empiric value
// First a small job with the most recent messages. Large chunk, small (but non zero) idle interval
// and a larger number of messages to process at once.
auto job1 = new ViewItemJob(d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1,
200, 20, 100, canDoFirstSmallChunkWithDisconnectedUI);
d->mViewItemJobs.append(job1);
// Then a larger job with older messages. Small chunk, bigger idle interval, small number of messages to
// process at once.
auto job2 = new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 100, 50, 10, false);
d->mViewItemJobs.append(job2);
// We could even extremize this by splitting the folder in several
// chunks and scanning them from the newest to the oldest... but the overhead
// due to imperfectly threaded children would be probably too big.
} else {
// small folder or can be done with disconnected UI: single chunk work.
// Lag the CPU a bit more but not too much to destroy even the earliest interactivity.
auto job = new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 150, 30, 30, canDoJobWithDisconnectedUI);
d->mViewItemJobs.append(job);
}
break;
case Aggregation::FavorSpeed:
// More batchy jobs, still interactive to a certain degree
if ((!canDoJobWithDisconnectedUI) && (d->mStorageModel->rowCount() > 3000)) { // empiric value
// large folder, but favor speed
auto job1 = new ViewItemJob(d->mStorageModel->rowCount() - 1000, d->mStorageModel->rowCount() - 1,
250, 0, 100, canDoFirstSmallChunkWithDisconnectedUI);
d->mViewItemJobs.append(job1);
auto job2 = new ViewItemJob(0, d->mStorageModel->rowCount() - 1001, 200, 0, 10, false);
d->mViewItemJobs.append(job2);
} else {
// small folder or can be done with disconnected UI and favor speed: single chunk work.
// Lag the CPU more, get more work done
auto job = new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 250, 0, 100, canDoJobWithDisconnectedUI);
d->mViewItemJobs.append(job);
}
break;
case Aggregation::BatchNoInteractivity:
{
// one large job, never interrupt, block UI
auto job = new ViewItemJob(0, d->mStorageModel->rowCount() - 1, 60000, 0, 100000, canDoJobWithDisconnectedUI);
d->mViewItemJobs.append(job);
break;
}
default:
qCWarning(MESSAGELIST_LOG) << "Unrecognized fill view strategy";
Q_ASSERT(false);
break;
}
d->mLoading = true;
d->viewItemJobStep();
}
void ModelPrivate::checkIfDateChanged()
{
// This function is called by MessageList::Core::Manager once in a while (every 1 minute or sth).
// It is used to check if the current date has changed (with respect to mTodayDate).
//
// Our message items cache the formatted dates (as formatting them
// on the fly would be too expensive). We also cache the labels of the groups which often display dates.
// When the date changes we would need to fix all these strings.
//
// A dedicated algorithm to refresh the labels of the items would be either too complex
// or would block on large trees. Fixing the labels of the groups is also quite hard...
//
// So to keep the things simple we just reload the view.
if (!mStorageModel) {
return; // nothing to do
}
if (mLoading) {
return; // not now
}
if (!mViewItemJobs.isEmpty()) {
return; // not now
}
if (mTodayDate == QDate::currentDate()) {
return; // date not changed
}
// date changed, reload the view (and try to preserve the current selection)
q->setStorageModel(mStorageModel, PreSelectLastSelected);
}
void Model::setPreSelectionMode(PreSelectionMode preSelect)
{
d->mPreSelectionMode = preSelect;
d->mLastSelectedMessageInFolder = nullptr;
}
//
// The "view fill" algorithm implemented in the functions below is quite smart but also quite complex.
// It's governed by the following goals:
//
// - Be flexible: allow different configurations from "unsorted flat list" to a "grouped and threaded
-// list with different sorting algorightms applied to each aggregation level"
+// list with different sorting algorithms applied to each aggregation level"
// - Be reasonably fast
// - Be non blocking: UI shouldn't freeze while the algorithm is running
// - Be interruptible: user must be able to abort the execution and just switch to another folder in the middle
//
void ModelPrivate::clearUnassignedMessageLists()
{
// This is a bit tricky...
// The three unassigned message lists contain messages that have been created
// but not yet attached to the view. There may be two major cases for a message:
// - it has no parent -> it must be deleted and it will delete its children too
// - it has a parent -> it must NOT be deleted since it will be deleted by its parent.
// Sometimes the things get a little complicated since in Pass2 and Pass3
// we have transitional states in that the MessageItem object can be in two of these lists.
// WARNING: This function does NOT fixup mNewestItem and mOldestItem. If one of these
// two messages is in the lists below, it's deleted and the member becomes a dangling pointer.
// The caller must ensure that both mNewestItem and mOldestItem are set to 0
// and this is enforced in the assert below to avoid errors. This basically means
// that this function should be called only when the storage model changes or
// when the model is destroyed.
Q_ASSERT((mOldestItem == nullptr) && (mNewestItem == nullptr));
if (!mUnassignedMessageListForPass2.isEmpty()) {
// We're actually in Pass1* or Pass2: everything is mUnassignedMessageListForPass2
// Something may *also* be in mUnassignedMessageListForPass3 and mUnassignedMessageListForPass4
// but that are duplicates for sure.
// We can't just sweep the list and delete parentless items since each delete
// could kill children which are somewhere AFTER in the list: accessing the children
// would then lead to a SIGSEGV. We first sweep the list gathering parentless
// items and *then* delete them without accessing the parented ones.
QList<MessageItem *> parentless;
for (const auto mi : qAsConst(mUnassignedMessageListForPass2)) {
if (!mi->parent()) {
parentless.append(mi);
}
}
for (const auto mi : qAsConst(parentless)) {
delete mi;
}
mUnassignedMessageListForPass2.clear();
// Any message these list contain was also in mUnassignedMessageListForPass2
mUnassignedMessageListForPass3.clear();
mUnassignedMessageListForPass4.clear();
return;
}
// mUnassignedMessageListForPass2 is empty
if (!mUnassignedMessageListForPass3.isEmpty()) {
// We're actually at the very end of Pass2 or inside Pass3
// Pass2 pushes stuff in mUnassignedMessageListForPass3 *or* mUnassignedMessageListForPass4
// Pass3 pushes stuff from mUnassignedMessageListForPass3 to mUnassignedMessageListForPass4
// So if we're in Pass2 then the two lists contain distinct messages but if we're in Pass3
// then the two lists may contain the same messages.
if (!mUnassignedMessageListForPass4.isEmpty()) {
// We're actually in Pass3: the messiest one.
QSet<MessageItem *> itemsToDelete;
for (const auto mi : qAsConst(mUnassignedMessageListForPass3)) {
if (!mi->parent()) {
itemsToDelete.insert(mi);
}
}
for (const auto mi : qAsConst(mUnassignedMessageListForPass4)) {
if (!mi->parent()) {
itemsToDelete.insert(mi);
}
}
for (const auto mi : qAsConst(itemsToDelete)) {
delete mi;
}
mUnassignedMessageListForPass3.clear();
mUnassignedMessageListForPass4.clear();
return;
}
// mUnassignedMessageListForPass4 is empty so we must be at the end of a very special kind of Pass2
// We have the same problem as in mUnassignedMessageListForPass2.
QList<MessageItem *> parentless;
for (const auto mi : qAsConst(mUnassignedMessageListForPass3)) {
if (!mi->parent()) {
parentless.append(mi);
}
}
for (const auto mi : qAsConst(parentless)) {
delete mi;
}
mUnassignedMessageListForPass3.clear();
return;
}
// mUnassignedMessageListForPass3 is empty
if (!mUnassignedMessageListForPass4.isEmpty()) {
// we're in Pass4.. this is easy.
// We have the same problem as in mUnassignedMessageListForPass2.
QList<MessageItem *> parentless;
for (const auto mi : qAsConst(mUnassignedMessageListForPass4)) {
if (!mi->parent()) {
parentless.append(mi);
}
}
for (const auto mi : qAsConst(parentless)) {
delete mi;
}
mUnassignedMessageListForPass4.clear();
return;
}
}
void ModelPrivate::clearThreadingCacheReferencesIdMD5ToMessageItem()
{
qDeleteAll(mThreadingCacheMessageReferencesIdMD5ToMessageItem);
mThreadingCacheMessageReferencesIdMD5ToMessageItem.clear();
}
void ModelPrivate::clearThreadingCacheMessageSubjectMD5ToMessageItem()
{
qDeleteAll(mThreadingCacheMessageSubjectMD5ToMessageItem);
mThreadingCacheMessageSubjectMD5ToMessageItem.clear();
}
void ModelPrivate::clearOrphanChildrenHash()
{
qDeleteAll(mOrphanChildrenHash);
mOrphanChildrenHash.clear();
}
void ModelPrivate::clearJobList()
{
if (mViewItemJobs.isEmpty()) {
return;
}
if (mInLengthyJobBatch) {
mInLengthyJobBatch = false;
}
qDeleteAll(mViewItemJobs);
mViewItemJobs.clear();
mModelForItemFunctions = q; // make sure it's true, as there remains no job with disconnected UI
}
void ModelPrivate::attachGroup(GroupHeaderItem *ghi)
{
if (ghi->parent()) {
if (
((ghi)->childItemCount() > 0) // has children
&& (ghi)->isViewable() // is actually attached to the viewable root
&& mModelForItemFunctions // the UI is not disconnected
&& mView->isExpanded(q->index(ghi, 0)) // is actually expanded
) {
saveExpandedStateOfSubtree(ghi);
}
// FIXME: This *WILL* break selection and current index... :/
ghi->parent()->takeChildItem(mModelForItemFunctions, ghi);
}
ghi->setParent(mRootItem);
// I'm using a macro since it does really improve readability.
// I'm NOT using a helper function since gcc will refuse to inline some of
// the calls because they make this function grow too much.
#define INSERT_GROUP_WITH_COMPARATOR(_ItemComparator) \
switch (mSortOrder->groupSortDirection()) \
{ \
case SortOrder::Ascending: \
mRootItem->d_ptr->insertChildItem< _ItemComparator, true >(mModelForItemFunctions, ghi); \
break; \
case SortOrder::Descending: \
mRootItem->d_ptr->insertChildItem< _ItemComparator, false >(mModelForItemFunctions, ghi); \
break; \
default: /* should never happen... */ \
mRootItem->appendChildItem(mModelForItemFunctions, ghi); \
break; \
}
switch (mSortOrder->groupSorting()) {
case SortOrder::SortGroupsByDateTime:
INSERT_GROUP_WITH_COMPARATOR(ItemDateComparator)
break;
case SortOrder::SortGroupsByDateTimeOfMostRecent:
INSERT_GROUP_WITH_COMPARATOR(ItemMaxDateComparator)
break;
case SortOrder::SortGroupsBySenderOrReceiver:
INSERT_GROUP_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
break;
case SortOrder::SortGroupsBySender:
INSERT_GROUP_WITH_COMPARATOR(ItemSenderComparator)
break;
case SortOrder::SortGroupsByReceiver:
INSERT_GROUP_WITH_COMPARATOR(ItemReceiverComparator)
break;
case SortOrder::NoGroupSorting:
mRootItem->appendChildItem(mModelForItemFunctions, ghi);
break;
default: // should never happen
mRootItem->appendChildItem(mModelForItemFunctions, ghi);
break;
}
if (ghi->initialExpandStatus() == Item::ExpandNeeded) { // this actually is a "non viewable expanded state"
if (ghi->childItemCount() > 0) {
if (mModelForItemFunctions) { // the UI is not disconnected
syncExpandedStateOfSubtree(ghi);
}
}
}
// A group header is always viewable, when attached: apply the filter, if we have it.
if (mFilter) {
Q_ASSERT(mModelForItemFunctions); // UI must be NOT disconnected
// apply the filter to subtree
applyFilterToSubtree(ghi, QModelIndex());
}
}
void ModelPrivate::saveExpandedStateOfSubtree(Item *root)
{
Q_ASSERT(mModelForItemFunctions); // UI must be NOT disconnected here
Q_ASSERT(root);
root->setInitialExpandStatus(Item::ExpandNeeded);
auto children = root->childItems();
if (!children) {
return;
}
for (const auto mi : qAsConst(*children)) {
if (mi->childItemCount() > 0 // has children
&& mi->isViewable() // is actually attached to the viewable root
&& mView->isExpanded(q->index(mi, 0))) { // is actually expanded
saveExpandedStateOfSubtree(mi);
}
}
}
void ModelPrivate::syncExpandedStateOfSubtree(Item *root)
{
Q_ASSERT(mModelForItemFunctions); // UI must be NOT disconnected here
// WE ASSUME that:
// - the item is viewable
// - its initialExpandStatus() is Item::ExpandNeeded
// - it has at least one children (well.. this is not a strict requirement, but it's a waste of resources to expand items that don't have children)
QModelIndex idx = q->index(root, 0);
//if ( !mView->isExpanded( idx ) ) // this is O(logN!) in Qt.... very ugly... but it should never happen here
mView->expand(idx); // sync the real state in the view
root->setInitialExpandStatus(Item::ExpandExecuted);
auto children = root->childItems();
if (!children) {
return;
}
for (const auto mi : qAsConst(*children)) {
if (mi->initialExpandStatus() == Item::ExpandNeeded) {
if (mi->childItemCount() > 0) {
syncExpandedStateOfSubtree(mi);
}
}
}
}
void ModelPrivate::attachMessageToGroupHeader(MessageItem *mi)
{
QString groupLabel;
time_t date;
// compute the group header label and the date
switch (mAggregation->grouping()) {
case Aggregation::GroupByDate:
case Aggregation::GroupByDateRange:
{
if (mAggregation->threadLeader() == Aggregation::MostRecentMessage) {
date = mi->maxDate();
} else {
date = mi->date();
}
QDateTime dt;
dt.setTime_t(date);
QDate dDate = dt.date();
int daysAgo = -1;
const int daysInWeek = 7;
if (dDate.isValid() && mTodayDate.isValid()) {
daysAgo = dDate.daysTo(mTodayDate);
}
if ((daysAgo < 0) // In the future
|| (static_cast< uint >(date) == static_cast< uint >(-1))) { // Invalid
groupLabel = mCachedUnknownLabel;
} else if (daysAgo == 0) { // Today
groupLabel = mCachedTodayLabel;
} else if (daysAgo == 1) { // Yesterday
groupLabel = mCachedYesterdayLabel;
} else if (daysAgo > 1 && daysAgo < daysInWeek) { // Within last seven days
auto dayName = mCachedDayNameLabel.find(dDate.dayOfWeek()); // non-const call, but non-shared container
if (dayName == mCachedDayNameLabel.end()) {
dayName = mCachedDayNameLabel.insert(dDate.dayOfWeek(), QLocale::system().standaloneDayName(dDate.dayOfWeek()));
}
groupLabel = *dayName;
} else if (mAggregation->grouping() == Aggregation::GroupByDate) { // GroupByDate seven days or more ago
groupLabel = QLocale::system().toString(dDate, QLocale::ShortFormat);
} else if (dDate.month() == mTodayDate.month() // GroupByDateRange within this month
&& dDate.year() == mTodayDate.year()) {
int startOfWeekDaysAgo = (daysInWeek + mTodayDate.dayOfWeek() - QLocale().firstDayOfWeek()) % daysInWeek;
int weeksAgo = ((daysAgo - startOfWeekDaysAgo) / daysInWeek) + 1;
switch (weeksAgo) {
case 0: // This week
groupLabel = QLocale::system().standaloneDayName(dDate.dayOfWeek());
break;
case 1: // 1 week ago
groupLabel = mCachedLastWeekLabel;
break;
case 2:
groupLabel = mCachedTwoWeeksAgoLabel;
break;
case 3:
groupLabel = mCachedThreeWeeksAgoLabel;
break;
case 4:
groupLabel = mCachedFourWeeksAgoLabel;
break;
case 5:
groupLabel = mCachedFiveWeeksAgoLabel;
break;
default: // should never happen
groupLabel = mCachedUnknownLabel;
}
} else if (dDate.year() == mTodayDate.year()) { // GroupByDateRange within this year
auto monthName = mCachedMonthNameLabel.find(dDate.month()); // non-const call, but non-shared container
if (monthName == mCachedMonthNameLabel.end()) {
monthName = mCachedMonthNameLabel.insert(dDate.month(), QLocale::system().standaloneMonthName(dDate.month()));
}
groupLabel = *monthName;
} else { // GroupByDateRange in previous years
auto monthName = mCachedMonthNameLabel.find(dDate.month()); // non-const call, but non-shared container
if (monthName == mCachedMonthNameLabel.end()) {
monthName = mCachedMonthNameLabel.insert(dDate.month(), QLocale::system().standaloneMonthName(dDate.month()));
}
groupLabel = i18nc("Message Aggregation Group Header: Month name and Year number", "%1 %2", *monthName,
QLocale::system().toString(dDate, QLatin1Literal("yyyy")));
}
break;
}
case Aggregation::GroupBySenderOrReceiver:
date = mi->date();
groupLabel = mi->displaySenderOrReceiver();
break;
case Aggregation::GroupBySender:
date = mi->date();
groupLabel = mi->displaySender();
break;
case Aggregation::GroupByReceiver:
date = mi->date();
groupLabel = mi->displayReceiver();
break;
case Aggregation::NoGrouping:
// append directly to root
attachMessageToParent(mRootItem, mi);
return;
default:
// should never happen
attachMessageToParent(mRootItem, mi);
return;
}
GroupHeaderItem *ghi;
ghi = mGroupHeaderItemHash.value(groupLabel, nullptr);
if (!ghi) {
// not found
ghi = new GroupHeaderItem(groupLabel);
ghi->initialSetup(date, mi->size(), mi->sender(), mi->receiver(), mi->useReceiver());
switch (mAggregation->groupExpandPolicy()) {
case Aggregation::NeverExpandGroups:
// nothing to do
break;
case Aggregation::AlwaysExpandGroups:
// expand always
ghi->setInitialExpandStatus(Item::ExpandNeeded);
break;
case Aggregation::ExpandRecentGroups:
// expand only if "close" to today
if (mViewItemJobStepStartTime > ghi->date()) {
if ((mViewItemJobStepStartTime - ghi->date()) < (3600 * 72)) {
ghi->setInitialExpandStatus(Item::ExpandNeeded);
}
} else {
if ((ghi->date() - mViewItemJobStepStartTime) < (3600 * 72)) {
ghi->setInitialExpandStatus(Item::ExpandNeeded);
}
}
break;
default:
// b0rken
break;
}
attachMessageToParent(ghi, mi);
attachGroup(ghi); // this will expand the group if required
mGroupHeaderItemHash.insert(groupLabel, ghi);
} else {
// the group was already there (certainly viewable)
// This function may be also called to re-group a message.
// That is, to eventually find a new group for a message that has changed
- // its properties (but was already attacched to a group).
+ // its properties (but was already attached to a group).
// So it may happen that we find out that in fact re-grouping wasn't really
// needed because the message is already in the correct group.
if (mi->parent() == ghi) {
return; // nothing to be done
}
attachMessageToParent(ghi, mi);
}
// Remember this message as a thread leader
mThreadingCache.updateParent(mi, nullptr);
}
MessageItem *ModelPrivate::findMessageParent(MessageItem *mi)
{
Q_ASSERT(mAggregation->threading() != Aggregation::NoThreading); // caller must take care of this
// This function attempts to find a thread parent for the item "mi"
// which actually may already have a children subtree.
// Forged or plain broken message trees are dangerous here.
// For example, a message tree with circular references like
//
// Message mi, Id=1, In-Reply-To=2
// Message childOfMi, Id=2, In-Reply-To=1
//
// is perfectly possible and will cause us to find childOfMi
// as parent of mi. This will then create a loop in the message tree
// (which will then no longer be a tree in fact) and cause us to freeze
// once we attempt to climb the parents. We need to take care of that.
bool bMessageWasThreadable = false;
MessageItem *pParent;
// First of all try to find a "perfect parent", that is the message for that
// we have the ID in the "In-Reply-To" field. This is actually done by using
// MD5 caches of the message ids because of speed. Collisions are very unlikely.
QByteArray md5 = mi->inReplyToIdMD5();
if (!md5.isEmpty()) {
// have an In-Reply-To field MD5
pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5, nullptr);
if (pParent) {
// Take care of circular references
if (
(mi == pParent) // self referencing message
|| (
(mi->childItemCount() > 0) // mi already has children, this is fast to determine
&& pParent->hasAncestor(mi) // pParent is in the mi's children tree
)
) {
qCWarning(MESSAGELIST_LOG) << "Circular In-Reply-To reference loop detected in the message tree";
mi->setThreadingStatus(MessageItem::NonThreadable);
return nullptr; // broken message: throw it away
}
mi->setThreadingStatus(MessageItem::PerfectParentFound);
return pParent; // got a perfect parent for this message
}
// got no perfect parent
bMessageWasThreadable = true; // but the message was threadable
}
if (mAggregation->threading() == Aggregation::PerfectOnly) {
mi->setThreadingStatus(bMessageWasThreadable ? MessageItem::ParentMissing : MessageItem::NonThreadable);
return nullptr; // we're doing only perfect parent matches
}
// Try to use the "References" field. In fact we have the MD5 of the
// (n-1)th entry in References.
//
// Original rationale from KMHeaders:
//
// If we don't have a replyToId, or if we have one and the
// corresponding message is not in this folder, as happens
// if you keep your outgoing messages in an OUTBOX, for
// example, try the list of references, because the second
// to last will likely be in this folder. replyToAuxIdMD5
// contains the second to last one.
md5 = mi->referencesIdMD5();
if (!md5.isEmpty()) {
pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5, nullptr);
if (pParent) {
// Take care of circular references
if (
(mi == pParent) // self referencing message
|| (
(mi->childItemCount() > 0) // mi already has children, this is fast to determine
&& pParent->hasAncestor(mi) // pParent is in the mi's children tree
)
) {
qCWarning(MESSAGELIST_LOG) << "Circular reference loop detected in the message tree";
mi->setThreadingStatus(MessageItem::NonThreadable);
return nullptr; // broken message: throw it away
}
mi->setThreadingStatus(MessageItem::ImperfectParentFound);
return pParent; // got an imperfect parent for this message
}
auto messagesWithTheSameReferences = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(md5, nullptr);
if (messagesWithTheSameReferences) {
Q_ASSERT(!messagesWithTheSameReferences->isEmpty());
pParent = messagesWithTheSameReferences->first();
if (mi != pParent && (mi->childItemCount() == 0 || !pParent->hasAncestor(mi))) {
mi->setThreadingStatus(MessageItem::ImperfectParentFound);
return pParent;
}
}
// got no imperfect parent
bMessageWasThreadable = true; // but the message was threadable
}
if (mAggregation->threading() == Aggregation::PerfectAndReferences) {
mi->setThreadingStatus(bMessageWasThreadable ? MessageItem::ParentMissing : MessageItem::NonThreadable);
return nullptr; // we're doing only perfect parent matches
}
Q_ASSERT(mAggregation->threading() == Aggregation::PerfectReferencesAndSubject);
// We are supposed to do subject based threading but we can't do it now.
// This is because the subject based threading *may* be wrong and waste
// time by creating circular references (that we'd need to detect and fix).
// We first try the perfect and references based threading on all the messages
// and then run subject based threading only on the remaining ones.
mi->setThreadingStatus((bMessageWasThreadable || mi->subjectIsPrefixed()) ? MessageItem::ParentMissing : MessageItem::NonThreadable);
return nullptr;
}
// Subject threading cache stuff
#if 0
// Debug helpers
void dump_iterator_and_list(QList< MessageItem * >::Iterator &iter, QList< MessageItem * > *list)
{
qCDebug(MESSAGELIST_LOG) << "Threading cache part dump";
if (iter == list->end()) {
qCDebug(MESSAGELIST_LOG) << "Iterator pointing to end of the list";
} else {
qCDebug(MESSAGELIST_LOG) << "Iterator pointing to " << *iter << " subject [" << (*iter)->subject() << "] date [" << (*iter)->date() << "]";
}
for (QList< MessageItem * >::Iterator it = list->begin(); it != list->end(); ++it) {
qCDebug(MESSAGELIST_LOG) << "List element " << *it << " subject [" << (*it)->subject() << "] date [" << (*it)->date() << "]";
}
qCDebug(MESSAGELIST_LOG) << "End of threading cache part dump";
}
void dump_list(QList< MessageItem * > *list)
{
qCDebug(MESSAGELIST_LOG) << "Threading cache part dump";
for (QList< MessageItem * >::Iterator it = list->begin(); it != list->end(); ++it) {
qCDebug(MESSAGELIST_LOG) << "List element " << *it << " subject [" << (*it)->subject() << "] date [" << (*it)->date() << "]";
}
qCDebug(MESSAGELIST_LOG) << "End of threading cache part dump";
}
#endif // debug helpers
// a helper class used in a qLowerBound() call below
class MessageLessThanByDate
{
public:
inline bool operator()(const MessageItem *mi1, const MessageItem *mi2) const
{
if (mi1->date() < mi2->date()) { // likely
return true;
}
if (mi1->date() > mi2->date()) { // likely
return false;
}
// dates are equal, compare by pointer
return mi1 < mi2;
}
};
void ModelPrivate::addMessageToReferencesBasedThreadingCache(MessageItem *mi)
{
// Messages in this cache are sorted by date, and if dates are equal then they are sorted by pointer value.
// Sorting by date is used to optimize the parent lookup in guessMessageParent() below.
// WARNING: If the message date changes for some reason (like in the "update" step)
// then the cache may become unsorted. For this reason the message about to
// be changed must be first removed from the cache and then reinserted.
auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(), nullptr);
if (!messagesWithTheSameReference) {
messagesWithTheSameReference = new QList< MessageItem * >();
mThreadingCacheMessageReferencesIdMD5ToMessageItem.insert(mi->referencesIdMD5(), messagesWithTheSameReference);
messagesWithTheSameReference->append(mi);
return;
}
// Found: assert that we have no duplicates in the cache.
Q_ASSERT(!messagesWithTheSameReference->contains(mi));
// Ordered insert: first by date then by pointer value.
auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
messagesWithTheSameReference->insert(it, mi);
}
void ModelPrivate::removeMessageFromReferencesBasedThreadingCache(MessageItem *mi)
{
// We assume that the caller knows what he is doing and the message is actually in the cache.
// If the message isn't in the cache then we should not be called at all.
auto messagesWithTheSameReference = mThreadingCacheMessageReferencesIdMD5ToMessageItem.value(mi->referencesIdMD5(), nullptr);
// We assume that the message is there so the list must be non null.
Q_ASSERT(messagesWithTheSameReference);
// The cache *MUST* be ordered first by date then by pointer value
auto it = std::lower_bound(messagesWithTheSameReference->begin(), messagesWithTheSameReference->end(), mi, MessageLessThanByDate());
// The binary based search must have found a message
Q_ASSERT(it != messagesWithTheSameReference->end());
// and it must have found exactly the message requested
Q_ASSERT(*it == mi);
// Kill it
messagesWithTheSameReference->erase(it);
// And kill the list if it was the last one
if (messagesWithTheSameReference->isEmpty()) {
mThreadingCacheMessageReferencesIdMD5ToMessageItem.remove(mi->referencesIdMD5());
delete messagesWithTheSameReference;
}
}
void ModelPrivate::addMessageToSubjectBasedThreadingCache(MessageItem *mi)
{
// Messages in this cache are sorted by date, and if dates are equal then they are sorted by pointer value.
// Sorting by date is used to optimize the parent lookup in guessMessageParent() below.
// WARNING: If the message date changes for some reason (like in the "update" step)
// then the cache may become unsorted. For this reason the message about to
// be changed must be first removed from the cache and then reinserted.
// Lookup the list of messages with the same stripped subject
auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(), nullptr);
if (!messagesWithTheSameStrippedSubject) {
// Not there yet: create it and append.
messagesWithTheSameStrippedSubject = new QList< MessageItem * >();
mThreadingCacheMessageSubjectMD5ToMessageItem.insert(mi->strippedSubjectMD5(), messagesWithTheSameStrippedSubject);
messagesWithTheSameStrippedSubject->append(mi);
return;
}
// Found: assert that we have no duplicates in the cache.
Q_ASSERT(!messagesWithTheSameStrippedSubject->contains(mi));
// Ordered insert: first by date then by pointer value.
auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
messagesWithTheSameStrippedSubject->insert(it, mi);
}
void ModelPrivate::removeMessageFromSubjectBasedThreadingCache(MessageItem *mi)
{
// We assume that the caller knows what he is doing and the message is actually in the cache.
// If the message isn't in the cache then we should not be called at all.
//
// The game is called "performance"
// Grab the list of all the messages with the same stripped subject (all potential parents)
auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(mi->strippedSubjectMD5(), nullptr);
// We assume that the message is there so the list must be non null.
Q_ASSERT(messagesWithTheSameStrippedSubject);
// The cache *MUST* be ordered first by date then by pointer value
auto it = std::lower_bound(messagesWithTheSameStrippedSubject->begin(), messagesWithTheSameStrippedSubject->end(), mi, MessageLessThanByDate());
// The binary based search must have found a message
Q_ASSERT(it != messagesWithTheSameStrippedSubject->end());
// and it must have found exactly the message requested
Q_ASSERT(*it == mi);
// Kill it
messagesWithTheSameStrippedSubject->erase(it);
// And kill the list if it was the last one
if (messagesWithTheSameStrippedSubject->isEmpty()) {
mThreadingCacheMessageSubjectMD5ToMessageItem.remove(mi->strippedSubjectMD5());
delete messagesWithTheSameStrippedSubject;
}
}
MessageItem *ModelPrivate::guessMessageParent(MessageItem *mi)
{
// This function implements subject based threading
// It attempts to guess a thread parent for the item "mi"
// which actually may already have a children subtree.
// We have all the problems of findMessageParent() plus the fact that
// we're actually guessing (and often we may be *wrong*).
Q_ASSERT(mAggregation->threading() == Aggregation::PerfectReferencesAndSubject); // caller must take care of this
Q_ASSERT(mi->subjectIsPrefixed()); // caller must take care of this
Q_ASSERT(mi->threadingStatus() == MessageItem::ParentMissing);
// Do subject based threading
const QByteArray md5 = mi->strippedSubjectMD5();
if (!md5.isEmpty()) {
auto messagesWithTheSameStrippedSubject = mThreadingCacheMessageSubjectMD5ToMessageItem.value(md5, nullptr);
if (messagesWithTheSameStrippedSubject) {
Q_ASSERT(!messagesWithTheSameStrippedSubject->isEmpty());
// Need to find the message with the maximum date lower than the one of this message
time_t maxTime = (time_t)0;
MessageItem *pParent = nullptr;
// Here'we re really guessing so circular references are possible
// even on perfectly valid trees. This is why we don't consider it
// an error but just continue searching.
// FIXME: This might be speed up with an initial binary search (?)
// ANSWER: No. We can't rely on date order (as it can be updated on the fly...)
for (const auto it : qAsConst(*messagesWithTheSameStrippedSubject)) {
int delta = mi->date() - it->date();
// We don't take into account messages with a delta smaller than 120.
// Assuming that our date() values are correct (that is, they take into
// account timezones etc..) then one usually needs more than 120 seconds
// to answer to a message. Better safe than sorry.
// This check also includes negative deltas so messages later than mi aren't considered
if (delta < 120) {
break; // The list is ordered by date (ascending) so we can stop searching here
}
// About the "magic" 3628899 value here comes a Till's comment from the original KMHeaders:
//
// "Parents more than six weeks older than the message are not accepted. The reasoning being
// that if a new message with the same subject turns up after such a long time, the chances
// that it is still part of the same thread are slim. The value of six weeks is chosen as a
// result of a poll conducted on kde-devel, so it's probably bogus. :)"
if (delta < 3628899) {
// Compute the closest.
if ((maxTime < it->date())) {
// This algorithm *can* be (and often is) wrong.
// Take care of circular threading which is really possible at this level.
// If mi contains "it" inside its children subtree then we have
// found such a circular threading problem.
// Note that here we can't have it == mi because of the delta >= 120 check above.
if ((mi->childItemCount() == 0) || !it->hasAncestor(mi)) {
maxTime = it->date();
pParent = it;
}
}
}
}
if (pParent) {
mi->setThreadingStatus(MessageItem::ImperfectParentFound);
return pParent; // got an imperfect parent for this message
}
}
}
return nullptr;
}
//
// A little template helper, hopefully inlineable.
//
// Return true if the specified message item is in the wrong position
// inside the specified parent and needs re-sorting. Return false otherwise.
// Both parent and messageItem must not be null.
//
// Checking if a message needs re-sorting instead of just re-sorting it
// is very useful since re-sorting is an expensive operation.
//
template< class ItemComparator > static bool messageItemNeedsReSorting(SortOrder::SortDirection messageSortDirection, ItemPrivate *parent, MessageItem *messageItem)
{
if ((messageSortDirection == SortOrder::Ascending)
|| (parent->mType == Item::Message)) {
return parent->childItemNeedsReSorting< ItemComparator, true >(messageItem);
}
return parent->childItemNeedsReSorting< ItemComparator, false >(messageItem);
}
bool ModelPrivate::handleItemPropertyChanges(int propertyChangeMask, Item *parent, Item *item)
{
// The facts:
//
// - If dates changed:
// - If we're sorting messages by min/max date then at each level the messages might need resorting.
// - If the thread leader is the most recent message of a thread then the uppermost
// message of the thread might need re-grouping.
// - If the groups are sorted by min/max date then the group might need re-sorting too.
//
// This function explicitly doesn't re-apply the filter when ActionItemStatus changes.
// This is because filters must be re-applied due to a broader range of status variations:
// this is done in viewItemJobStepInternalForJobPass1Update() instead (which is the only
// place in that ActionItemStatus may be set).
if (parent->type() == Item::InvisibleRoot) {
- // item is either a message or a group attacched to the root.
+ // item is either a message or a group attached to the root.
// It might need resorting.
if (item->type() == Item::GroupHeader) {
- // item is a group header attacched to the root.
+ // item is a group header attached to the root.
if (
(
// max date changed
(propertyChangeMask & MaxDateChanged)
&&// groups sorted by max date
(mSortOrder->groupSorting() == SortOrder::SortGroupsByDateTimeOfMostRecent)
) || (
// date changed
(propertyChangeMask & DateChanged)
&&// groups sorted by date
(mSortOrder->groupSorting() == SortOrder::SortGroupsByDateTime)
)
) {
// This group might need re-sorting.
// Groups are large container of messages so it's likely that
// another message inserted will cause this group to be marked again.
// So we wait until the end to do the grand final re-sorting: it will be done in Pass4.
mGroupHeadersThatNeedUpdate.insert(static_cast< GroupHeaderItem * >(item), static_cast< GroupHeaderItem * >(item));
}
} else {
// item is a message. It might need re-sorting.
// Since sorting is an expensive operation, we first check if it's *really* needed.
// Re-sorting will actually not change min/max dates at all and
// will not climb up the parent's ancestor tree.
switch (mSortOrder->messageSorting()) {
case SortOrder::SortMessagesByDateTime:
if (propertyChangeMask & DateChanged) { // date changed
if (messageItemNeedsReSorting< ItemDateComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else date changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByDateTimeOfMostRecent:
if (propertyChangeMask & MaxDateChanged) { // max date changed
if (messageItemNeedsReSorting< ItemMaxDateComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else max date changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByActionItemStatus:
if (propertyChangeMask & ActionItemStatusChanged) { // todo status changed
if (messageItemNeedsReSorting< ItemActionItemStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else to do status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByUnreadStatus:
if (propertyChangeMask & UnreadStatusChanged) { // new / unread status changed
if (messageItemNeedsReSorting< ItemUnreadStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByImportantStatus:
if (propertyChangeMask & ImportantStatusChanged) { // important status changed
if (messageItemNeedsReSorting< ItemImportantStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByAttachmentStatus:
if (propertyChangeMask & AttachmentStatusChanged) { // attachment status changed
if (messageItemNeedsReSorting< ItemAttachmentStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
break;
default:
// this kind of message sorting isn't affected by the property changes: nothing to do.
break;
}
}
return false; // the invisible root isn't affected by any change.
}
if (parent->type() == Item::GroupHeader) {
- // item is a message attacched to a GroupHeader.
+ // item is a message attached to a GroupHeader.
// It might need re-grouping or re-sorting (within the same group)
// Check re-grouping here.
if (
(
// max date changed
(propertyChangeMask & MaxDateChanged)
&&// thread leader is most recent message
(mAggregation->threadLeader() == Aggregation::MostRecentMessage)
) || (
// date changed
(propertyChangeMask & DateChanged)
&&// thread leader the topmost message
(mAggregation->threadLeader() == Aggregation::TopmostMessage)
)
) {
// Might really need re-grouping.
// attachMessageToGroupHeader() will find the right group for this message
// and if it's different than the current it will move it.
attachMessageToGroupHeader(static_cast< MessageItem * >(item));
// Re-grouping fixes the properties of the involved group headers
// so at exit of attachMessageToGroupHeader() the parent can't be affected
// by the change anymore.
return false;
}
// Re-grouping wasn't needed. Re-sorting might be.
- } // else item is a message attacched to another message and might need re-sorting only.
+ } // else item is a message attached to another message and might need re-sorting only.
// Check if message needs re-sorting.
switch (mSortOrder->messageSorting()) {
case SortOrder::SortMessagesByDateTime:
if (propertyChangeMask & DateChanged) { // date changed
if (messageItemNeedsReSorting< ItemDateComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else date changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByDateTimeOfMostRecent:
if (propertyChangeMask & MaxDateChanged) { // max date changed
if (messageItemNeedsReSorting< ItemMaxDateComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else max date changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByActionItemStatus:
if (propertyChangeMask & ActionItemStatusChanged) { // todo status changed
if (messageItemNeedsReSorting< ItemActionItemStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else to do status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByUnreadStatus:
if (propertyChangeMask & UnreadStatusChanged) { // new / unread status changed
if (messageItemNeedsReSorting< ItemUnreadStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else new/unread status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByImportantStatus:
if (propertyChangeMask & ImportantStatusChanged) { // important status changed
if (messageItemNeedsReSorting< ItemImportantStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else important status changed, but it doesn't match sorting order: no need to re-sort
break;
case SortOrder::SortMessagesByAttachmentStatus:
if (propertyChangeMask & AttachmentStatusChanged) { // attachment status changed
if (messageItemNeedsReSorting< ItemAttachmentStatusComparator >(mSortOrder->messageSortDirection(), parent->d_ptr, static_cast< MessageItem * >(item))) {
attachMessageToParent(parent, static_cast< MessageItem * >(item));
}
} // else important status changed, but it doesn't match sorting order: no need to re-sort
break;
default:
// this kind of message sorting isn't affected by property changes: nothing to do.
break;
}
return true; // parent might be affected too.
}
void ModelPrivate::messageDetachedUpdateParentProperties(Item *oldParent, MessageItem *mi)
{
Q_ASSERT(oldParent);
Q_ASSERT(mi);
Q_ASSERT(oldParent != mRootItem);
// oldParent might have its properties changed because of the child removal.
// propagate the changes up.
for (;;) {
// pParent is not the root item now. This is assured by how we enter this loop
// and by the fact that handleItemPropertyChanges returns false when grandParent
// is Item::InvisibleRoot. We could actually assert it here...
// Check if its dates need an update.
int propertyChangeMask;
if ((mi->maxDate() == oldParent->maxDate()) && oldParent->recomputeMaxDate()) {
propertyChangeMask = MaxDateChanged;
} else {
break; // from the for(;;) loop
}
// One of the oldParent properties has changed for sure
Item *grandParent = oldParent->parent();
- // If there is no grandParent then oldParent isn't attacched to the view.
+ // If there is no grandParent then oldParent isn't attached to the view.
// Re-sorting / re-grouping isn't needed for sure.
if (!grandParent) {
break; // from the for(;;) loop
}
// The following function will return true if grandParent may be affected by the change.
// If the grandParent isn't affected, we stop climbing.
if (!handleItemPropertyChanges(propertyChangeMask, grandParent, oldParent)) {
break; // from the for(;;) loop
}
// Now we need to climb up one level and check again.
oldParent = grandParent;
} // for(;;) loop
// If the last message was removed from a group header then this group will need an update
- // for sure. We will need to remove it (unless a message is attacched back to it)
+ // for sure. We will need to remove it (unless a message is attached back to it)
if (oldParent->type() == Item::GroupHeader) {
if (oldParent->childItemCount() == 0) {
mGroupHeadersThatNeedUpdate.insert(static_cast< GroupHeaderItem * >(oldParent), static_cast< GroupHeaderItem * >(oldParent));
}
}
}
void ModelPrivate::propagateItemPropertiesToParent(Item *item)
{
Item *pParent = item->parent();
Q_ASSERT(pParent);
Q_ASSERT(pParent != mRootItem);
for (;;) {
// pParent is not the root item now. This is assured by how we enter this loop
// and by the fact that handleItemPropertyChanges returns false when grandParent
// is Item::InvisibleRoot. We could actually assert it here...
// Check if its dates need an update.
int propertyChangeMask;
if (item->maxDate() > pParent->maxDate()) {
pParent->setMaxDate(item->maxDate());
propertyChangeMask = MaxDateChanged;
} else {
// No parent dates have changed: no further work is needed. Stop climbing here.
break; // from the for(;;) loop
}
// One of the pParent properties has changed.
Item *grandParent = pParent->parent();
- // If there is no grandParent then pParent isn't attacched to the view.
+ // If there is no grandParent then pParent isn't attached to the view.
// Re-sorting / re-grouping isn't needed for sure.
if (!grandParent) {
break; // from the for(;;) loop
}
// The following function will return true if grandParent may be affected by the change.
// If the grandParent isn't affected, we stop climbing.
if (!handleItemPropertyChanges(propertyChangeMask, grandParent, pParent)) {
break; // from the for(;;) loop
}
// Now we need to climb up one level and check again.
pParent = grandParent;
} // for(;;)
}
void ModelPrivate::attachMessageToParent(Item *pParent, MessageItem *mi, AttachOptions attachOptions)
{
Q_ASSERT(pParent);
Q_ASSERT(mi);
// This function may be called to do a simple "re-sort" of the item inside the parent.
// In that case mi->parent() is equal to pParent.
bool oldParentWasTheSame;
if (mi->parent()) {
Item *oldParent = mi->parent();
// The item already had a parent and this means that we're moving it.
oldParentWasTheSame = oldParent == pParent; // just re-sorting ?
if (mi->isViewable()) { // is actually
// The message is actually attached to the viewable root
// Unfortunately we need to hack the model/view architecture
// since it's somewhat flawed in this. At the moment of writing
// there is simply no way to atomically move a subtree.
// We must detach, call beginRemoveRows()/endRemoveRows(),
// save the expanded state, save the selection, save the current item,
// save the view position (YES! As we are removing items the view
// will hopelessly jump around so we're just FORCED to break
// the isolation from the view)...
// ...*then* reattach, restore the expanded state, restore the selection,
// restore the current item, restore the view position and pray
// that nothing will fail in the (rather complicated) process....
// Yet more unfortunately, while saving the expanded state might stop
// at a certain (unexpanded) point in the tree, saving the selection
// is hopelessly recursive down to the bare leafs.
// Furthermore the expansion of items is a common case while selection
// in the subtree is rare, so saving it would be a huge cost with
// a low revenue.
// This is why we just let the selection screw up. I hereby refuse to call
// yet another expensive recursive function here :D
// The current item saving can be somewhat optimized doing it once for
// a single job step...
if (
((mi)->childItemCount() > 0) // has children
&& mModelForItemFunctions // the UI is not actually disconnected
&& mView->isExpanded(q->index(mi, 0)) // is actually expanded
) {
saveExpandedStateOfSubtree(mi);
}
}
// If the parent is viewable (so mi was viewable too) then the beginRemoveRows()
// and endRemoveRows() functions of this model will be called too.
oldParent->takeChildItem(mModelForItemFunctions, mi);
if ((!oldParentWasTheSame) && (oldParent != mRootItem)) {
messageDetachedUpdateParentProperties(oldParent, mi);
}
} else {
// The item had no parent yet.
oldParentWasTheSame = false;
}
// Take care of perfect / imperfect threading.
// Items that are now perfectly threaded, but already have a different parent
// might have been imperfectly threaded before. Remove them from the caches.
// Items that are now imperfectly threaded must be added to the caches.
//
// If we're just re-sorting the item inside the same parent then the threading
// caches don't need to be updated (since they actually depend on the parent).
if (!oldParentWasTheSame) {
switch (mi->threadingStatus()) {
case MessageItem::PerfectParentFound:
if (!mi->inReplyToIdMD5().isEmpty()) {
mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(mi->inReplyToIdMD5(), mi);
}
if (attachOptions == StoreInCache && pParent->type() == Item::Message) {
mThreadingCache.updateParent(mi, static_cast<MessageItem *>(pParent));
}
break;
case MessageItem::ImperfectParentFound:
case MessageItem::ParentMissing: // may be: temporary or just fallback assignment
if (!mi->inReplyToIdMD5().isEmpty()) {
if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi)) {
mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(mi->inReplyToIdMD5(), mi);
}
}
break;
case MessageItem::NonThreadable: // this also happens when we do no threading at all
// make gcc happy
Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(mi->inReplyToIdMD5(), mi));
break;
}
}
// Set the new parent
mi->setParent(pParent);
// Propagate watched and ignored status
if (
(pParent->status().toQInt32() & mCachedWatchedOrIgnoredStatusBits) // unlikely
&& (pParent->type() == Item::Message) // likely
) {
// the parent is either watched or ignored: propagate to the child
if (pParent->status().isWatched()) {
int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
mi->setStatus(Akonadi::MessageStatus::statusWatched());
mStorageModel->setMessageItemStatus(mi, row, Akonadi::MessageStatus::statusWatched());
} else if (pParent->status().isIgnored()) {
int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(mi);
mi->setStatus(Akonadi::MessageStatus::statusIgnored());
mStorageModel->setMessageItemStatus(mi, row, Akonadi::MessageStatus::statusIgnored());
}
}
// And insert into its child list
// If pParent is viewable then the insert/append functions will call this model's
// beginInsertRows() and endInsertRows() functions. This is EXTREMELY
// expensive and ugly but it's the only way with the Qt4 imposed Model/View method.
// Dude... (citation from Lost, if it wasn't clear).
// I'm using a macro since it does really improve readability.
// I'm NOT using a helper function since gcc will refuse to inline some of
// the calls because they make this function grow too much.
#define INSERT_MESSAGE_WITH_COMPARATOR(_ItemComparator) \
if ((mSortOrder->messageSortDirection() == SortOrder::Ascending) \
|| (pParent->type() == Item::Message)) \
{ \
pParent->d_ptr->insertChildItem< _ItemComparator, true >(mModelForItemFunctions, mi); \
} \
else \
{ \
pParent->d_ptr->insertChildItem< _ItemComparator, false >(mModelForItemFunctions, mi); \
}
// If pParent is viewable then the insertion call will also set the child state to viewable.
// Since mi MAY have children, then this call may make them viewable.
switch (mSortOrder->messageSorting()) {
case SortOrder::SortMessagesByDateTime:
INSERT_MESSAGE_WITH_COMPARATOR(ItemDateComparator)
break;
case SortOrder::SortMessagesByDateTimeOfMostRecent:
INSERT_MESSAGE_WITH_COMPARATOR(ItemMaxDateComparator)
break;
case SortOrder::SortMessagesBySize:
INSERT_MESSAGE_WITH_COMPARATOR(ItemSizeComparator)
break;
case SortOrder::SortMessagesBySenderOrReceiver:
INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderOrReceiverComparator)
break;
case SortOrder::SortMessagesBySender:
INSERT_MESSAGE_WITH_COMPARATOR(ItemSenderComparator)
break;
case SortOrder::SortMessagesByReceiver:
INSERT_MESSAGE_WITH_COMPARATOR(ItemReceiverComparator)
break;
case SortOrder::SortMessagesBySubject:
INSERT_MESSAGE_WITH_COMPARATOR(ItemSubjectComparator)
break;
case SortOrder::SortMessagesByActionItemStatus:
INSERT_MESSAGE_WITH_COMPARATOR(ItemActionItemStatusComparator)
break;
case SortOrder::SortMessagesByUnreadStatus:
INSERT_MESSAGE_WITH_COMPARATOR(ItemUnreadStatusComparator)
break;
case SortOrder::SortMessagesByImportantStatus:
INSERT_MESSAGE_WITH_COMPARATOR(ItemImportantStatusComparator)
break;
case SortOrder::SortMessagesByAttachmentStatus:
INSERT_MESSAGE_WITH_COMPARATOR(ItemAttachmentStatusComparator)
break;
case SortOrder::NoMessageSorting:
pParent->appendChildItem(mModelForItemFunctions, mi);
break;
default: // should never happen
pParent->appendChildItem(mModelForItemFunctions, mi);
break;
}
// Decide if we need to expand parents
bool childNeedsExpanding = (mi->initialExpandStatus() == Item::ExpandNeeded);
if (pParent->initialExpandStatus() == Item::NoExpandNeeded) {
switch (mAggregation->threadExpandPolicy()) {
case Aggregation::NeverExpandThreads:
// just do nothing unless this child has children and is already marked for expansion
if (childNeedsExpanding) {
pParent->setInitialExpandStatus(Item::ExpandNeeded);
}
break;
case Aggregation::ExpandThreadsWithNewMessages: // No more new status. fall through to unread if it exists in config
case Aggregation::ExpandThreadsWithUnreadMessages:
// expand only if unread (or it has children marked for expansion)
if (childNeedsExpanding || !mi->status().isRead()) {
pParent->setInitialExpandStatus(Item::ExpandNeeded);
}
break;
case Aggregation::ExpandThreadsWithUnreadOrImportantMessages:
// expand only if unread, important or todo (or it has children marked for expansion)
// FIXME: Wouldn't it be nice to be able to test for bitmasks in MessageStatus ?
if (childNeedsExpanding || !mi->status().isRead() || mi->status().isImportant() || mi->status().isToAct()) {
pParent->setInitialExpandStatus(Item::ExpandNeeded);
}
break;
case Aggregation::AlwaysExpandThreads:
// expand everything
pParent->setInitialExpandStatus(Item::ExpandNeeded);
break;
default:
// BUG
break;
}
} // else it's already marked for expansion or expansion has been already executed
// expand parent first, if possible
if (pParent->initialExpandStatus() == Item::ExpandNeeded) {
// If UI is not disconnected and parent is viewable, go up and expand
if (mModelForItemFunctions && pParent->isViewable()) {
// Now expand parents as needed
Item *parentToExpand = pParent;
while (parentToExpand) {
if (parentToExpand == mRootItem) {
break; // no need to set it expanded
}
// parentToExpand is surely viewable (because this item is)
if (parentToExpand->initialExpandStatus() == Item::ExpandExecuted) {
break;
}
mView->expand(q->index(parentToExpand, 0));
parentToExpand->setInitialExpandStatus(Item::ExpandExecuted);
parentToExpand = parentToExpand->parent();
}
} else {
// It isn't viewable or UI is disconnected: climb up marking only
Item *parentToExpand = pParent->parent();
while (parentToExpand) {
if (parentToExpand == mRootItem) {
break; // no need to set it expanded
}
parentToExpand->setInitialExpandStatus(Item::ExpandNeeded);
parentToExpand = parentToExpand->parent();
}
}
}
if (mi->isViewable()) {
// mi is now viewable
// sync subtree expanded status
if (childNeedsExpanding) {
if (mi->childItemCount() > 0) {
if (mModelForItemFunctions) { // the UI is not disconnected
syncExpandedStateOfSubtree(mi); // sync the real state in the view
}
}
}
// apply the filter, if needed
if (mFilter) {
Q_ASSERT(mModelForItemFunctions); // the UI must be NOT disconnected here
// apply the filter to subtree
if (applyFilterToSubtree(mi, q->index(pParent, 0))) {
// mi matched, expand parents (unconditionally)
mView->ensureDisplayedWithParentsExpanded(mi);
}
}
}
// Now we need to propagate the property changes the upper levels.
// If we have just inserted a message inside the root then no work needs to be done:
// no grouping is in effect and the message is already in the right place.
if (pParent == mRootItem) {
return;
}
// If we have just removed the item from this parent and re-inserted it
// then this operation was a simple re-sort. The code above didn't update
// the properties when removing the item so we don't actually need
// to make the updates back.
if (oldParentWasTheSame) {
return;
}
// FIXME: OPTIMIZE THIS: First propagate changes THEN syncExpandedStateOfSubtree()
// and applyFilterToSubtree... (needs some thinking though).
// Time to propagate up.
propagateItemPropertiesToParent(mi);
// Aaah.. we're done. Time for a thea ? :)
}
// FIXME: ThreadItem ?
//
// Foo Bar, Joe Thommason, Martin Rox ... Eddie Maiden <date of the thread>
// Title <number of messages>, Last by xxx <inner status>
//
// When messages are added, mark it as dirty only (?)
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass5(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// In this pass we scan the group headers that are in mGroupHeadersThatNeedUpdate.
// Empty groups get deleted while the other ones are re-sorted.
int curIndex = job->currentIndex();
auto it = mGroupHeadersThatNeedUpdate.begin();
auto end = mGroupHeadersThatNeedUpdate.end();
while (it != end) {
if ((*it)->childItemCount() == 0) {
// group with no children, kill it
(*it)->parent()->takeChildItem(mModelForItemFunctions, *it);
mGroupHeaderItemHash.remove((*it)->label());
// If we were going to restore its position after the job step, well.. we can't do it anymore.
if (mCurrentItemToRestoreAfterViewItemJobStep == (*it)) {
mCurrentItemToRestoreAfterViewItemJobStep = nullptr;
}
// bye bye
delete *it;
} else {
// Group with children: probably needs re-sorting.
// Re-sorting here is an expensive operation.
// In fact groups have been put in the QHash above on the assumption
// that re-sorting *might* be needed but no real (expensive) check
// has been done yet. Also by sorting a single group we might actually
// put the others in the right place.
// So finally check if re-sorting is *really* needed.
bool needsReSorting;
// A macro really improves readability here.
#define CHECK_IF_GROUP_NEEDS_RESORTING(_ItemDateComparator) \
switch (mSortOrder->groupSortDirection()) \
{ \
case SortOrder::Ascending: \
needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, true >(*it); \
break; \
case SortOrder::Descending: \
needsReSorting = (*it)->parent()->d_ptr->childItemNeedsReSorting< _ItemDateComparator, false >(*it); \
break; \
default: /* should never happen */ \
needsReSorting = false; \
break; \
}
switch (mSortOrder->groupSorting()) {
case SortOrder::SortGroupsByDateTime:
CHECK_IF_GROUP_NEEDS_RESORTING(ItemDateComparator)
break;
case SortOrder::SortGroupsByDateTimeOfMostRecent:
CHECK_IF_GROUP_NEEDS_RESORTING(ItemMaxDateComparator)
break;
case SortOrder::SortGroupsBySenderOrReceiver:
CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderOrReceiverComparator)
break;
case SortOrder::SortGroupsBySender:
CHECK_IF_GROUP_NEEDS_RESORTING(ItemSenderComparator)
break;
case SortOrder::SortGroupsByReceiver:
CHECK_IF_GROUP_NEEDS_RESORTING(ItemReceiverComparator)
break;
case SortOrder::NoGroupSorting:
needsReSorting = false;
break;
default:
// Should never happen... just assume re-sorting is not needed
needsReSorting = false;
break;
}
if (needsReSorting) {
attachGroup(*it); // it will first detach and then re-attach in the proper place
}
}
it = mGroupHeadersThatNeedUpdate.erase(it);
curIndex++;
// FIXME: In fact a single update is likely to manipulate
// a subtree with a LOT of messages inside. If interactivity is favored
// we should check the time really more often.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (it != mGroupHeadersThatNeedUpdate.end()) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass4(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// In this pass we scan mUnassignedMessageListForPass4 which now
// contains both items with parents and items without parents.
// We scan mUnassignedMessageList for messages without parent (the ones that haven't been
- // attacched to the viewable tree yet) and find a suitable group for them. Then we simply
+ // attached to the viewable tree yet) and find a suitable group for them. Then we simply
// clear mUnassignedMessageList.
// We call this pass "Grouping"
int curIndex = job->currentIndex();
int endIndex = job->endIndex();
while (curIndex <= endIndex) {
MessageItem *mi = mUnassignedMessageListForPass4[curIndex];
if (!mi->parent()) {
// Unassigned item: thread leader, insert into the proper group.
// Locate the group (or root if no grouping requested)
attachMessageToGroupHeader(mi);
} else {
// A parent was already assigned in Pass3: we have nothing to do here
}
curIndex++;
// FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
// a subtree with a LOT of messages inside. If interactivity is favored
// we should check the time really more often.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
mUnassignedMessageListForPass4.clear();
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass3(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// In this pass we scan the mUnassignedMessageListForPass3 and try to do construct the threads
// by using subject based threading. If subject based threading is not in effect then
// this pass turns to a nearly-no-op: at the end of Pass2 we have swapped the lists
// and mUnassignedMessageListForPass3 is actually empty.
// We don't shrink the mUnassignedMessageListForPass3 for two reasons:
// - It would mess up this chunked algorithm by shifting indexes
// - mUnassignedMessageList is a QList which is basically an array. It's faster
// to traverse an array of N entries than to remove K>0 entries one by one and
// to traverse the remaining N-K entries.
int curIndex = job->currentIndex();
int endIndex = job->endIndex();
while (curIndex <= endIndex) {
// If we're here, then threading is requested for sure.
auto mi = mUnassignedMessageListForPass3[curIndex];
if ((!mi->parent()) || (mi->threadingStatus() == MessageItem::ParentMissing)) {
- // Parent is missing (either "physically" with the item being not attacched or "logically"
- // with the item being attacched to a group or directly to the root.
+ // Parent is missing (either "physically" with the item being not attached or "logically"
+ // with the item being attached to a group or directly to the root.
if (mi->subjectIsPrefixed()) {
// We can try to guess it
auto mparent = guessMessageParent(mi);
if (mparent) {
// imperfect parent found
if (mi->isViewable()) {
// mi was already viewable, we're just trying to re-parent it better...
attachMessageToParent(mparent, mi);
if (!mparent->isViewable()) {
// re-attach it immediately (so current item is not lost)
auto topmost = mparent->topmostMessage();
Q_ASSERT(!topmost->parent()); // groups are always viewable!
topmost->setThreadingStatus(MessageItem::ParentMissing);
attachMessageToGroupHeader(topmost);
}
} else {
// mi wasn't viewable yet.. no need to attach parent
attachMessageToParent(mparent, mi);
}
// and we're done for now
} else {
// so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
Q_ASSERT((mi->threadingStatus() == MessageItem::ParentMissing) || (mi->threadingStatus() == MessageItem::NonThreadable));
mUnassignedMessageListForPass4.append(mi); // this is ~O(1)
// and wait for Pass4
}
} else {
// can't guess the parent as the subject isn't prefixed
Q_ASSERT((mi->threadingStatus() == MessageItem::ParentMissing) || (mi->threadingStatus() == MessageItem::NonThreadable));
mUnassignedMessageListForPass4.append(mi); // this is ~O(1)
// and wait for Pass4
}
} else {
// Has a parent: either perfect parent already found or non threadable.
// Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
Q_ASSERT(mi->threadingStatus() != MessageItem::ImperfectParentFound);
Q_ASSERT(mi->isViewable());
}
curIndex++;
// FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
// a subtree with a LOT of messages inside. If interactivity is favored
// we should check the time really more often.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
mUnassignedMessageListForPass3.clear();
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass2(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// In this pass we scan the mUnassignedMessageList and try to do construct the threads.
- // If some thread leader message got attacched to the viewable tree in Pass1Fill then
+ // If some thread leader message got attached to the viewable tree in Pass1Fill then
// we'll also attach all of its children too. The thread leaders we were unable
// to attach in Pass1Fill and their children (which we find here) will make it to the small Pass3
// We don't shrink the mUnassignedMessageList for two reasons:
// - It would mess up this chunked algorithm by shifting indexes
// - mUnassignedMessageList is a QList which is basically an array. It's faster
// to traverse an array of N entries than to remove K>0 entries one by one and
// to traverse the remaining N-K entries.
// We call this pass "Threading"
int curIndex = job->currentIndex();
int endIndex = job->endIndex();
while (curIndex <= endIndex) {
// If we're here, then threading is requested for sure.
auto mi = mUnassignedMessageListForPass2[curIndex];
// The item may or may not have a parent.
// If it has no parent or it has a temporary one (mi->parent() && mi->threadingStatus() == MessageItem::ParentMissing)
// then we attempt to (re-)thread it. Otherwise we just do nothing (the job has already been done by the previous steps).
if ((!mi->parent()) || (mi->threadingStatus() == MessageItem::ParentMissing)) {
qint64 parentId;
auto mparent = mThreadingCache.parentForItem(mi, parentId);
if (mparent) {
mi->setThreadingStatus(MessageItem::PerfectParentFound);
attachMessageToParent(mparent, mi, SkipCacheUpdate);
} else {
if (parentId > 0) {
// In second pass we have all available Items in mThreadingCache already. If
// mThreadingCache.parentForItem() returns null, but returns valid parentId then
// the Item was removed from Akonadi and our threading cache is out-of-date.
mThreadingCache.expireParent(mi);
mparent = findMessageParent(mi);
} else if (parentId < 0) {
mparent = findMessageParent(mi);
} else {
// parentId = 0: this message is a thread leader so don't
// bother resolving parent, it will be moved directly to
// Pass4 in the code below
}
if (mparent) {
// parent found, either perfect or imperfect
if (mi->isViewable()) {
// mi was already viewable, we're just trying to re-parent it better...
attachMessageToParent(mparent, mi);
if (!mparent->isViewable()) {
// re-attach it immediately (so current item is not lost)
auto topmost = mparent->topmostMessage();
Q_ASSERT(!topmost->parent()); // groups are always viewable!
topmost->setThreadingStatus(MessageItem::ParentMissing);
attachMessageToGroupHeader(topmost);
}
} else {
// mi wasn't viewable yet.. no need to attach parent
attachMessageToParent(mparent, mi);
}
// and we're done for now
} else {
// so parent not found, (threadingStatus() is either MessageItem::ParentMissing or MessageItem::NonThreadable)
switch (mi->threadingStatus()) {
case MessageItem::ParentMissing:
if (mAggregation->threading() == Aggregation::PerfectReferencesAndSubject) {
// parent missing but still can be found in Pass3
mUnassignedMessageListForPass3.append(mi); // this is ~O(1)
} else {
// We're not doing subject based threading: will never be threaded, go straight to Pass4
mUnassignedMessageListForPass4.append(mi); // this is ~O(1)
}
break;
case MessageItem::NonThreadable:
// will never be threaded, go straight to Pass4
mUnassignedMessageListForPass4.append(mi); // this is ~O(1)
break;
default:
// a bug for sure
qCWarning(MESSAGELIST_LOG) << "ERROR: Invalid message threading status returned by findMessageParent()!";
Q_ASSERT(false);
break;
}
}
}
} else {
// Has a parent: either perfect parent already found or non threadable.
// Since we don't end here if mi has status of parent missing then mi must not have imperfect parent.
Q_ASSERT(mi->threadingStatus() != MessageItem::ImperfectParentFound);
if (!mi->isViewable()) {
qCWarning(MESSAGELIST_LOG) << "Non viewable message " << mi << " subject " << mi->subject().toUtf8().data();
Q_ASSERT(mi->isViewable());
}
}
curIndex++;
// FIXME: In fact a single call to attachMessageToGroupHeader() is likely to manipulate
// a subtree with a LOT of messages inside. If interactivity is favored
// we should check the time really more often.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
mUnassignedMessageListForPass2.clear();
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// In this pass we scan the a contiguous region of the underlying storage (that is
// assumed to be FLAT) and create the corresponding MessageItem objects.
// The deal is to show items to the user as soon as possible so in this pass we
// *TRY* to attach them to the viewable tree (which is rooted on mRootItem).
// Messages we're unable to attach for some reason (mainly due to threading) get appended
// to mUnassignedMessageList and wait for Pass2.
// We call this pass "Processing"
// Should we use the receiver or the sender field for sorting ?
bool bUseReceiver = mStorageModelContainsOutboundMessages;
// The begin storage index of our work
int curIndex = job->currentIndex();
// The end storage index of our work.
int endIndex = job->endIndex();
unsigned long msgToSelect = mPreSelectionMode == PreSelectLastSelected ? mStorageModel->preSelectedMessage() : 0;
MessageItem *mi = nullptr;
while (curIndex <= endIndex) {
// Create the message item with no parent: we'll set it later
if (!mi) {
mi = new MessageItem();
} else {
// a MessageItem discarded by a previous iteration: reuse it.
Q_ASSERT(mi->parent() == nullptr);
}
if (!mStorageModel->initializeMessageItem(mi, curIndex, bUseReceiver)) {
// ugh
qCWarning(MESSAGELIST_LOG) << "Fill of the MessageItem at storage row index " << curIndex << " failed";
curIndex++;
continue;
}
// If we're supposed to pre-select a specific message, check if it's this one.
if (msgToSelect != 0 && msgToSelect == mi->uniqueId()) {
// Found, it's this one.
// But actually it's not viewable (so not selectable). We must wait
// until the end of the job to be 100% sure. So here we just translate
// the unique id to a MessageItem pointer and wait.
mLastSelectedMessageInFolder = mi;
msgToSelect = 0; // already found, don't bother checking anymore
}
// Update the newest/oldest message, since we might be supposed to select those later
if (mi->date() != static_cast<uint>(-1)) {
if (!mOldestItem || mOldestItem->date() > mi->date()) {
mOldestItem = mi;
}
if (!mNewestItem || mNewestItem->date() < mi->date()) {
mNewestItem = mi;
}
}
// Ok.. it passed the initial checks: we will not be discarding it.
// Make this message item an invariant index to the underlying model storage.
mInvariantRowMapper->createModelInvariantIndex(curIndex, mi);
// Attempt to do threading as soon as possible (to display items to the user)
if (mAggregation->threading() != Aggregation::NoThreading) {
// Threading is requested
// Fetch the data needed for proper threading
// Add the item to the threading caches
switch (mAggregation->threading()) {
case Aggregation::PerfectReferencesAndSubject:
mStorageModel->fillMessageItemThreadingData(mi, curIndex, StorageModel::PerfectThreadingReferencesAndSubject);
// We also need to build the subject/reference-based threading cache
addMessageToReferencesBasedThreadingCache(mi);
addMessageToSubjectBasedThreadingCache(mi);
break;
case Aggregation::PerfectAndReferences:
mStorageModel->fillMessageItemThreadingData(mi, curIndex, StorageModel::PerfectThreadingPlusReferences);
addMessageToReferencesBasedThreadingCache(mi);
break;
default:
mStorageModel->fillMessageItemThreadingData(mi, curIndex, StorageModel::PerfectThreadingOnly);
break;
}
// Perfect/References threading cache
mThreadingCacheMessageIdMD5ToMessageItem.insert(mi->messageIdMD5(), mi);
// Register the current item into the threading cache
mThreadingCache.addItemToCache(mi);
// First of all look into the persistent cache
qint64 parentId;
Item *pParent = mThreadingCache.parentForItem(mi, parentId);
if (pParent) {
// We already have the parent MessageItem. Attach current message
// to it and mark it as perfect
mi->setThreadingStatus(MessageItem::PerfectParentFound);
attachMessageToParent(pParent, mi);
} else if (parentId > 0) {
// We don't have the parent MessageItem yet, but we do know the
// parent: delay for pass 2 when we will have the parent MessageItem
// for sure.
mi->setThreadingStatus(MessageItem::ParentMissing);
mUnassignedMessageListForPass2.append(mi);
} else if (parentId == 0) {
// Message is a thread leader, skip straight to Pass4
mi->setThreadingStatus(MessageItem::NonThreadable);
mUnassignedMessageListForPass4.append(mi);
} else {
// Check if this item is a perfect parent for some imperfectly threaded
- // message (that is actually attacched to it, but not necessairly to the
+ // message (that is actually attached to it, but not necessarily to the
// viewable root). If it is, then remove the imperfect child from its
// current parent rebuild the hierarchy on the fly.
bool needsImmediateReAttach = false;
if (!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.isEmpty()) { // unlikely
const auto lImperfectlyThreaded = mThreadingCacheMessageInReplyToIdMD5ToMessageItem.values(mi->messageIdMD5());
for (const auto it : lImperfectlyThreaded) {
Q_ASSERT(it->parent());
Q_ASSERT(it->parent() != mi);
if (!((it->threadingStatus() == MessageItem::ImperfectParentFound)
|| (it->threadingStatus() == MessageItem::ParentMissing))) {
qCritical() << "Got message " << it << " with threading status" << it->threadingStatus();
Q_ASSERT_X(false, "ModelPrivate::viewItemJobStepInternalForJobPass1Fill", "Wrong threading status");
}
// If the item was already attached to the view then
// re-attach it immediately. This will avoid a message
// being displayed for a short while in the view and then
// disappear until a perfect parent isn't found.
if (it->isViewable()) {
needsImmediateReAttach = true;
}
it->setThreadingStatus(MessageItem::PerfectParentFound);
attachMessageToParent(mi, it);
}
}
// FIXME: Might look by "References" too, here... (?)
// Attempt to do threading with anything we already have in caches until now
// Note that this is likely to work since thread-parent messages tend
// to come before thread-children messages in the folders (simply because of
// date of arrival).
// First of all try to find a "perfect parent", that is the message for that
// we have the ID in the "In-Reply-To" field. This is actually done by using
// MD5 caches of the message ids because of speed. Collisions are very unlikely.
const QByteArray md5 = mi->inReplyToIdMD5();
if (!md5.isEmpty()) {
// Have an In-Reply-To field MD5.
// In well behaved mailing lists 70% of the threadable messages get a parent here :)
pParent = mThreadingCacheMessageIdMD5ToMessageItem.value(md5, nullptr);
if (pParent) { // very likely
// Take care of self-referencing (which is always possible)
// and circular In-Reply-To reference loops which are possible
// in case this item was found to be a perfect parent for some
// imperfectly threaded message just above.
if (
(mi == pParent) // self referencing message
|| (
(mi->childItemCount() > 0) // mi already has children, this is fast to determine
&& pParent->hasAncestor(mi) // pParent is in the mi's children tree
)
) {
// Bad, bad message.. it has In-Reply-To equal to Message-Id
// or it's in a circular In-Reply-To reference loop.
// Will wait for Pass2 with References-Id only
qCWarning(MESSAGELIST_LOG) << "Circular In-Reply-To reference loop detected in the message tree";
mUnassignedMessageListForPass2.append(mi);
} else {
// wow, got a perfect parent for this message!
mi->setThreadingStatus(MessageItem::PerfectParentFound);
attachMessageToParent(pParent, mi);
// we're done with this message (also for Pass2)
}
} else {
// got no parent
// will have to wait Pass2
mUnassignedMessageListForPass2.append(mi);
}
} else {
// No In-Reply-To header.
bool mightHaveOtherMeansForThreading;
switch (mAggregation->threading()) {
case Aggregation::PerfectReferencesAndSubject:
mightHaveOtherMeansForThreading = mi->subjectIsPrefixed() || !mi->referencesIdMD5().isEmpty();
break;
case Aggregation::PerfectAndReferences:
mightHaveOtherMeansForThreading = !mi->referencesIdMD5().isEmpty();
break;
case Aggregation::PerfectOnly:
mightHaveOtherMeansForThreading = false;
break;
default:
// BUG: there shouldn't be other values (NoThreading is excluded in an upper branch)
Q_ASSERT(false);
mightHaveOtherMeansForThreading = false; // make gcc happy
break;
}
if (mightHaveOtherMeansForThreading) {
// We might have other means for threading this message, wait until Pass2
mUnassignedMessageListForPass2.append(mi);
} else {
// No other means for threading this message. This is either
// a standalone message or a thread leader.
// If there is no grouping in effect or thread leaders are just the "topmost"
// messages then we might be done with this one.
if (
(mAggregation->grouping() == Aggregation::NoGrouping)
|| (mAggregation->threadLeader() == Aggregation::TopmostMessage)
) {
// We're done with this message: it will be surely either toplevel (no grouping in effect)
// or a thread leader with a well defined group. Do it :)
//qCDebug(MESSAGELIST_LOG) << "Setting message status from " << mi->threadingStatus() << " to non threadable (1) " << mi;
mi->setThreadingStatus(MessageItem::NonThreadable);
// Locate the parent group for this item
attachMessageToGroupHeader(mi);
// we're done with this message (also for Pass2)
} else {
// Threads belong to the most recent message in the thread. This means
// that we have to wait until Pass2 or Pass3 to assign a group.
mUnassignedMessageListForPass2.append(mi);
}
}
}
if (needsImmediateReAttach && !mi->isViewable()) {
// The item gathered previously viewable children. They must be immediately
// re-shown. So this item must currently be attached to the view.
// This is a temporary measure: it will be probably still moved.
MessageItem *topmost = mi->topmostMessage();
Q_ASSERT(topmost->threadingStatus() == MessageItem::ParentMissing);
attachMessageToGroupHeader(topmost);
}
}
} else {
// else no threading requested: we don't even need Pass2
// set not threadable status (even if it might be not true, but in this mode we don't care)
//qCDebug(MESSAGELIST_LOG) << "Setting message status from " << mi->threadingStatus() << " to non threadable (2) " << mi;
mi->setThreadingStatus(MessageItem::NonThreadable);
// locate the parent group for this item
if (mAggregation->grouping() == Aggregation::NoGrouping) {
attachMessageToParent(mRootItem, mi); // no groups requested, attach directly to root
} else {
attachMessageToGroupHeader(mi);
}
// we're done with this message (also for Pass2)
}
mi = nullptr; // this item was pushed somewhere, create a new one at next iteration
curIndex++;
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
if (mi) {
delete mi;
}
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
Q_ASSERT(mModelForItemFunctions); // UI must be not disconnected here
// In this pass we remove the MessageItem objects that are present in the job
// and put their children in the unassigned message list.
// Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
QList< ModelInvariantIndex * > *invalidatedMessages = job->invariantIndexList();
// We don't shrink the invalidatedMessages because it's basically an array.
// It's faster to traverse an array of N entries than to remove K>0 entries
// one by one and to traverse the remaining N-K entries.
// The begin index of our work
int curIndex = job->currentIndex();
// The end index of our work.
int endIndex = job->endIndex();
if (curIndex == job->startIndex()) {
Q_ASSERT(mOrphanChildrenHash.isEmpty());
}
while (curIndex <= endIndex) {
// Get the underlying storage message data...
auto dyingMessage = dynamic_cast< MessageItem * >(invalidatedMessages->at(curIndex));
// This MUST NOT be null (otherwise we have a bug somewhere in this file).
Q_ASSERT(dyingMessage);
// If we were going to pre-select this message but we were interrupted
// *before* it was actually made viewable, we just clear the pre-selection pointer
// and unique id (abort pre-selection).
if (dyingMessage == mLastSelectedMessageInFolder) {
mLastSelectedMessageInFolder = nullptr;
mPreSelectionMode = PreSelectNone;
}
// remove the message from any pending user job
if (mPersistentSetManager) {
mPersistentSetManager->removeMessageItemFromAllSets(dyingMessage);
if (mPersistentSetManager->setCount() < 1) {
delete mPersistentSetManager;
mPersistentSetManager = nullptr;
}
}
// Remove the message from threading cache before we start moving up the
// children, so that they don't get mislead by the cache
mThreadingCache.expireParent(dyingMessage);
if (dyingMessage->parent()) {
// Handle saving the current selection: if this item was the current before the step
// then zero it out. We have killed it and it's OK for the current item to change.
if (dyingMessage == mCurrentItemToRestoreAfterViewItemJobStep) {
Q_ASSERT(dyingMessage->isViewable());
// Try to select the item below the removed one as it helps in doing a "readon" of emails:
// you read a message, decide to delete it and then go to the next.
// Qt tends to select the message above the removed one instead (this is a hardcoded logic in
// QItemSelectionModelPrivate::_q_rowsAboutToBeRemoved()).
mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemAfter(dyingMessage, MessageTypeAny, false);
if (!mCurrentItemToRestoreAfterViewItemJobStep) {
// There is no item below. Try the item above.
// We still do it better than qt which tends to find the *thread* above
// instead of the item above.
mCurrentItemToRestoreAfterViewItemJobStep = mView->messageItemBefore(dyingMessage, MessageTypeAny, false);
}
Q_ASSERT((!mCurrentItemToRestoreAfterViewItemJobStep) || mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
}
if (
dyingMessage->isViewable()
&& ((dyingMessage)->childItemCount() > 0) // has children
&& mView->isExpanded(q->index(dyingMessage, 0)) // is actually expanded
) {
saveExpandedStateOfSubtree(dyingMessage);
}
auto oldParent = dyingMessage->parent();
oldParent->takeChildItem(q, dyingMessage);
// FIXME: This can generate many message movements.. it would be nicer
// to start from messages that are higher in the hierarchy so
// we would need to move less stuff above.
if (oldParent != mRootItem) {
messageDetachedUpdateParentProperties(oldParent, dyingMessage);
}
// We might have already removed its parent from the view, so it
// might already be in the orphan child hash...
if (dyingMessage->threadingStatus() == MessageItem::ParentMissing) {
mOrphanChildrenHash.remove(dyingMessage); // this can turn to a no-op (dyingMessage not present in fact)
}
} else {
// The dying message had no parent: this should happen only if it's already an orphan
Q_ASSERT(dyingMessage->threadingStatus() == MessageItem::ParentMissing);
Q_ASSERT(mOrphanChildrenHash.contains(dyingMessage));
Q_ASSERT(dyingMessage != mCurrentItemToRestoreAfterViewItemJobStep);
mOrphanChildrenHash.remove(dyingMessage);
}
if (mAggregation->threading() != Aggregation::NoThreading) {
// Threading is requested: remove the message from threading caches.
// Remove from the cache of potential parent items
mThreadingCacheMessageIdMD5ToMessageItem.remove(dyingMessage->messageIdMD5());
// If we also have a cache for subject/reference-based threading then remove the message from there too
if (mAggregation->threading() == Aggregation::PerfectReferencesAndSubject) {
removeMessageFromReferencesBasedThreadingCache(dyingMessage);
removeMessageFromSubjectBasedThreadingCache(dyingMessage);
} else if (mAggregation->threading() == Aggregation::PerfectAndReferences) {
removeMessageFromReferencesBasedThreadingCache(dyingMessage);
}
// If this message wasn't perfectly parented then it might still be in another cache.
switch (dyingMessage->threadingStatus()) {
case MessageItem::ImperfectParentFound:
case MessageItem::ParentMissing:
if (!dyingMessage->inReplyToIdMD5().isEmpty()) {
mThreadingCacheMessageInReplyToIdMD5ToMessageItem.remove(dyingMessage->inReplyToIdMD5());
}
break;
default:
Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(dyingMessage->inReplyToIdMD5(), dyingMessage));
// make gcc happy
break;
}
}
while (auto childItem = dyingMessage->firstChildItem()) {
auto childMessage = dynamic_cast< MessageItem * >(childItem);
Q_ASSERT(childMessage);
dyingMessage->takeChildItem(q, childMessage);
if (mAggregation->threading() != Aggregation::NoThreading) {
if (childMessage->threadingStatus() == MessageItem::PerfectParentFound) {
// If the child message was perfectly parented then now it had
// lost its perfect parent. Add to the cache of imperfectly parented.
if (!childMessage->inReplyToIdMD5().isEmpty()) {
Q_ASSERT(!mThreadingCacheMessageInReplyToIdMD5ToMessageItem.contains(childMessage->inReplyToIdMD5(), childMessage));
mThreadingCacheMessageInReplyToIdMD5ToMessageItem.insert(childMessage->inReplyToIdMD5(), childMessage);
}
}
}
// Parent is gone
childMessage->setThreadingStatus(MessageItem::ParentMissing);
// If the child (or any message in its subtree) is going to be selected,
// then we must immediately reattach it to a temporary group in order for the
// selection to be preserved across multiple steps. Otherwise we could end
// with the child-to-be-selected being non viewable at the end
// of the view job step. Attach to a temporary group.
if (
// child is going to be re-selected
(childMessage == mCurrentItemToRestoreAfterViewItemJobStep)
|| (
// there is a message that is going to be re-selected
mCurrentItemToRestoreAfterViewItemJobStep
&&// that message is in the childMessage subtree
mCurrentItemToRestoreAfterViewItemJobStep->hasAncestor(childMessage)
)
) {
attachMessageToGroupHeader(childMessage);
Q_ASSERT(childMessage->isViewable());
}
mOrphanChildrenHash.insert(childMessage, childMessage);
}
if (mNewestItem == dyingMessage) {
mNewestItem = nullptr;
}
if (mOldestItem == dyingMessage) {
mOldestItem = nullptr;
}
delete dyingMessage;
curIndex++;
// FIXME: Maybe we should check smaller steps here since the
// code above can generate large message tree movements
// for each single item we sweep in the invalidatedMessages list.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
// We looped over the entire deleted message list.
job->setCurrentIndex(endIndex + 1);
// A quick last cleaning pass: this is usually very fast so we don't have a real
// Pass enumeration for it. We just include it as trailer of Pass1Cleanup to be executed
// when job->currentIndex() > job->endIndex();
// We move all the messages from the orphan child hash to the unassigned message
// list and get them ready for the standard Pass2.
auto it = mOrphanChildrenHash.begin();
auto end = mOrphanChildrenHash.end();
curIndex = 0;
while (it != end) {
mUnassignedMessageListForPass2.append(*it);
it = mOrphanChildrenHash.erase(it);
// This is still interruptible
curIndex++;
// FIXME: We could take "larger" steps here
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (it != mOrphanChildrenHash.end()) {
return ViewItemJobInterrupted;
}
}
}
}
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
Q_ASSERT(mModelForItemFunctions); // UI must be not disconnected here
// In this pass we simply update the MessageItem objects that are present in the job.
// Note that this list in fact contains MessageItem objects (we need dynamic_cast<>).
auto messagesThatNeedUpdate = job->invariantIndexList();
// We don't shrink the messagesThatNeedUpdate because it's basically an array.
// It's faster to traverse an array of N entries than to remove K>0 entries
// one by one and to traverse the remaining N-K entries.
// The begin index of our work
int curIndex = job->currentIndex();
// The end index of our work.
int endIndex = job->endIndex();
while (curIndex <= endIndex) {
// Get the underlying storage message data...
auto message = dynamic_cast<MessageItem *>(messagesThatNeedUpdate->at(curIndex));
// This MUST NOT be null (otherwise we have a bug somewhere in this file).
Q_ASSERT(message);
int row = mInvariantRowMapper->modelInvariantIndexToModelIndexRow(message);
if (row < 0) {
// Must have been invalidated (so it's basically about to be deleted)
Q_ASSERT(!message->isValid());
// Skip it here.
curIndex++;
continue;
}
time_t prevDate = message->date();
time_t prevMaxDate = message->maxDate();
bool toDoStatus = message->status().isToAct();
bool prevUnreadStatus = !message->status().isRead();
bool prevImportantStatus = message->status().isImportant();
// The subject/reference based threading cache is sorted by date: we must remove
// the item and re-insert it since updateMessageItemData() may change the date too.
if (mAggregation->threading() == Aggregation::PerfectReferencesAndSubject) {
removeMessageFromReferencesBasedThreadingCache(message);
removeMessageFromSubjectBasedThreadingCache(message);
} else if (mAggregation->threading() == Aggregation::PerfectAndReferences) {
removeMessageFromReferencesBasedThreadingCache(message);
}
// Do update
mStorageModel->updateMessageItemData(message, row);
QModelIndex idx = q->index(message, 0);
Q_EMIT q->dataChanged(idx, idx);
// Reinsert the item to the cache, if needed
if (mAggregation->threading() == Aggregation::PerfectReferencesAndSubject) {
addMessageToReferencesBasedThreadingCache(message);
addMessageToSubjectBasedThreadingCache(message);
} else if (mAggregation->threading() == Aggregation::PerfectAndReferences) {
addMessageToReferencesBasedThreadingCache(message);
}
int propertyChangeMask = 0;
if (prevDate != message->date()) {
propertyChangeMask |= DateChanged;
}
if (prevMaxDate != message->maxDate()) {
propertyChangeMask |= MaxDateChanged;
}
if (toDoStatus != message->status().isToAct()) {
propertyChangeMask |= ActionItemStatusChanged;
}
if (prevUnreadStatus != (!message->status().isRead())) {
propertyChangeMask |= UnreadStatusChanged;
}
if (prevImportantStatus != (!message->status().isImportant())) {
propertyChangeMask |= ImportantStatusChanged;
}
if (propertyChangeMask) {
// Some message data has changed
// now we need to handle the changes that might cause re-grouping/re-sorting
// and propagate them to the parents.
Item *pParent = message->parent();
if (pParent && (pParent != mRootItem)) {
// The following function will return true if itemParent may be affected by the change.
// If the itemParent isn't affected, we stop climbing.
if (handleItemPropertyChanges(propertyChangeMask, pParent, message)) {
Q_ASSERT(message->parent()); // handleItemPropertyChanges() must never leave an item detached
// Note that actually message->parent() may be different than pParent since
// handleItemPropertyChanges() may have re-grouped it.
// Time to propagate up.
propagateItemPropertiesToParent(message);
}
} // else there is no parent so the item isn't attached to the view: re-grouping/re-sorting not needed.
} // else message data didn't change an there is nothing interesting to do
// (re-)apply the filter, if needed
if (mFilter && message->isViewable()) {
// In all the other cases we (re-)apply the filter to the topmost subtree that this message is in.
Item *pTopMostNonRoot = message->topmostNonRoot();
Q_ASSERT(pTopMostNonRoot);
Q_ASSERT(pTopMostNonRoot != mRootItem);
Q_ASSERT(pTopMostNonRoot->parent() == mRootItem);
// FIXME: The call below works, but it's expensive when we are updating
// a lot of items with filtering enabled. This is because the updated
// items are likely to be in the same subtree which we then filter multiple times.
// A point for us is that when filtering there shouldn't be really many
// items in the view so the user isn't going to update a lot of them at once...
// Well... anyway, the alternative would be to write yet another
// specialized routine that would update only the "message" item
// above and climb up eventually hiding parents (without descending the sibling subtrees again).
// If people complain about performance in this particular case I'll consider that solution.
applyFilterToSubtree(pTopMostNonRoot, QModelIndex());
} // otherwise there is no filter or the item isn't viewable: very likely
// left detached while propagating property changes. Will filter it
// on reattach.
// Done updating this message
curIndex++;
// FIXME: Maybe we should check smaller steps here since the
// code above can generate large message tree movements
// for each single item we sweep in the messagesThatNeedUpdate list.
if ((curIndex % mViewItemJobStepMessageCheckCount) == 0) {
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
if (curIndex <= endIndex) {
job->setCurrentIndex(curIndex);
return ViewItemJobInterrupted;
}
}
}
}
return ViewItemJobCompleted;
}
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternalForJob(ViewItemJob *job, const QElapsedTimer &elapsedTimer)
{
// This function does a timed chunk of work for a single Fill View job.
// It attempts to process messages until a timeout forces it to return to the caller.
// A macro would improve readability here but since this is a good point
- // to place debugger breakpoints then we need it explicited.
+ // to place debugger breakpoints then we need it explicitly.
// A (template) helper would need to pass many parameters and would not be inlined...
if (job->currentPass() == ViewItemJob::Pass1Fill) {
// We're in Pass1Fill of the job.
switch (viewItemJobStepInternalForJobPass1Fill(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
break;
case ViewItemJobCompleted:
// pass 1 has been completed
// # TODO: Refactor this, make it virtual or whatever, but switch == bad, code duplication etc
job->setCurrentPass(ViewItemJob::Pass2);
job->setStartIndex(0);
job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
} // else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
} else if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
// We're in Pass1Cleanup of the job.
switch (viewItemJobStepInternalForJobPass1Cleanup(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
break;
case ViewItemJobCompleted:
// pass 1 has been completed
job->setCurrentPass(ViewItemJob::Pass2);
job->setStartIndex(0);
job->setEndIndex(mUnassignedMessageListForPass2.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
} // else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
} else if (job->currentPass() == ViewItemJob::Pass1Update) {
// We're in Pass1Update of the job.
switch (viewItemJobStepInternalForJobPass1Update(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
break;
case ViewItemJobCompleted:
// pass 1 has been completed
// Since Pass2, Pass3 and Pass4 are empty for an Update operation
// we simply skip them. (TODO: Triple-verify this assertion...).
job->setCurrentPass(ViewItemJob::Pass5);
job->setStartIndex(0);
job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
} // else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
}
// Pass1Fill/Pass1Cleanup/Pass1Update has been already completed.
if (job->currentPass() == ViewItemJob::Pass2) {
// We're in Pass2 of the job.
switch (viewItemJobStepInternalForJobPass2(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
break;
case ViewItemJobCompleted:
// pass 2 has been completed
job->setCurrentPass(ViewItemJob::Pass3);
job->setStartIndex(0);
job->setEndIndex(mUnassignedMessageListForPass3.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
}
// else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
}
if (job->currentPass() == ViewItemJob::Pass3) {
// We're in Pass3 of the job.
switch (viewItemJobStepInternalForJobPass3(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
case ViewItemJobCompleted:
// pass 3 has been completed
job->setCurrentPass(ViewItemJob::Pass4);
job->setStartIndex(0);
job->setEndIndex(mUnassignedMessageListForPass4.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
}
// else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
}
if (job->currentPass() == ViewItemJob::Pass4) {
// We're in Pass4 of the job.
switch (viewItemJobStepInternalForJobPass4(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: propagate status to caller
return ViewItemJobInterrupted;
case ViewItemJobCompleted:
// pass 4 has been completed
job->setCurrentPass(ViewItemJob::Pass5);
job->setStartIndex(0);
job->setEndIndex(mGroupHeadersThatNeedUpdate.count() - 1);
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if (elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) {
return ViewItemJobInterrupted;
}
// else proceed with the next pass
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
}
// Pass4 has been already completed. Proceed to Pass5.
return viewItemJobStepInternalForJobPass5(job, elapsedTimer);
}
#ifdef KDEPIM_FOLDEROPEN_PROFILE
// Namespace to collect all the vars and functions for KDEPIM_FOLDEROPEN_PROFILE
namespace Stats {
// Number of existing jobs/passes
static const int numberOfPasses = ViewItemJob::LastIndex;
// The pass in the last call of viewItemJobStepInternal(), used to detect when
// a new pass starts
static int lastPass = -1;
// Total number of messages in the folder
static int totalMessages;
// Per-Job data
static int numElements[numberOfPasses];
static int totalTime[numberOfPasses];
static int chunks[numberOfPasses];
// Time, in msecs for some special operations
static int expandingTreeTime;
static int layoutChangeTime;
// Descriptions of the job, for nicer debug output
static const char *jobDescription[numberOfPasses] = {
"Creating items from messages and simple threading",
"Removing messages",
"Updating messages",
"Additional Threading",
"Subject-Based threading",
"Grouping",
"Group resorting + cleanup"
};
// Timer to track time between start of first job and end of last job
static QTime firstStartTime;
// Timer to track time the current job takes
static QTime currentJobStartTime;
// Zeros the stats, to be called when the first job starts
static void resetStats()
{
totalMessages = 0;
layoutChangeTime = 0;
expandingTreeTime = 0;
lastPass = -1;
for (int i = 0; i < numberOfPasses; ++i) {
numElements[i] = 0;
totalTime[i] = 0;
chunks[i] = 0;
}
}
} // namespace Stats
void ModelPrivate::printStatistics()
{
using namespace Stats;
int totalTotalTime = 0;
int completeTime = firstStartTime.elapsed();
for (int i = 0; i < numberOfPasses; ++i) {
totalTotalTime += totalTime[i];
}
float msgPerSecond = totalMessages / (totalTotalTime / 1000.0f);
float msgPerSecondComplete = totalMessages / (completeTime / 1000.0f);
int messagesWithSameSubjectAvg = 0;
int messagesWithSameSubjectMax = 0;
for (const auto messages : qAsConst(mThreadingCacheMessageSubjectMD5ToMessageItem)) {
if (messages->size() > messagesWithSameSubjectMax) {
messagesWithSameSubjectMax = messages->size();
}
messagesWithSameSubjectAvg += messages->size();
}
messagesWithSameSubjectAvg = messagesWithSameSubjectAvg / (float)mThreadingCacheMessageSubjectMD5ToMessageItem.size();
int totalThreads = 0;
if (!mGroupHeaderItemHash.isEmpty()) {
foreach (const GroupHeaderItem *groupHeader, mGroupHeaderItemHash) {
totalThreads += groupHeader->childItemCount();
}
} else {
totalThreads = mRootItem->childItemCount();
}
qCDebug(MESSAGELIST_LOG) << "Finished filling the view with" << totalMessages << "messages";
qCDebug(MESSAGELIST_LOG) << "That took" << totalTotalTime << "msecs inside the model and"
<< completeTime << "in total.";
qCDebug(MESSAGELIST_LOG) << (totalTotalTime / (float)completeTime) * 100.0f
<< "percent of the time was spent in the model.";
qCDebug(MESSAGELIST_LOG) << "Time for layoutChanged(), in msecs:" << layoutChangeTime
<< "(" << (layoutChangeTime / (float)totalTotalTime) * 100.0f << "percent )";
qCDebug(MESSAGELIST_LOG) << "Time to expand tree, in msecs:" << expandingTreeTime
<< "(" << (expandingTreeTime / (float)totalTotalTime) * 100.0f << "percent )";
qCDebug(MESSAGELIST_LOG) << "Number of messages per second in the model:" << msgPerSecond;
qCDebug(MESSAGELIST_LOG) << "Number of messages per second in total:" << msgPerSecondComplete;
qCDebug(MESSAGELIST_LOG) << "Number of threads:" << totalThreads;
qCDebug(MESSAGELIST_LOG) << "Number of groups:" << mGroupHeaderItemHash.size();
qCDebug(MESSAGELIST_LOG) << "Messages per thread:" << totalMessages / (float)totalThreads;
qCDebug(MESSAGELIST_LOG) << "Threads per group:" << totalThreads / (float)mGroupHeaderItemHash.size();
qCDebug(MESSAGELIST_LOG) << "Messages with the same subject:"
<< "Max:" << messagesWithSameSubjectMax
<< "Avg:" << messagesWithSameSubjectAvg;
qCDebug(MESSAGELIST_LOG);
qCDebug(MESSAGELIST_LOG) << "Now follows a breakdown of the jobs.";
qCDebug(MESSAGELIST_LOG);
for (int i = 0; i < numberOfPasses; ++i) {
if (totalTime[i] == 0) {
continue;
}
float elementsPerSecond = numElements[i] / (totalTime[i] / 1000.0f);
float percent = totalTime[i] / (float)totalTotalTime * 100.0f;
qCDebug(MESSAGELIST_LOG) << "----------------------------------------------";
qCDebug(MESSAGELIST_LOG) << "Job" << i + 1 << "(" << jobDescription[i] << ")";
qCDebug(MESSAGELIST_LOG) << "Share of complete time:" << percent << "percent";
qCDebug(MESSAGELIST_LOG) << "Time in msecs:" << totalTime[i];
qCDebug(MESSAGELIST_LOG) << "Number of elements:" << numElements[i]; // TODO: map of element string
qCDebug(MESSAGELIST_LOG) << "Elements per second:" << elementsPerSecond;
qCDebug(MESSAGELIST_LOG) << "Number of chunks:" << chunks[i];
qCDebug(MESSAGELIST_LOG);
}
qCDebug(MESSAGELIST_LOG) << "==========================================================";
resetStats();
}
#endif
ModelPrivate::ViewItemJobResult ModelPrivate::viewItemJobStepInternal()
{
// This function does a timed chunk of work in our View Fill operation.
// It attempts to do processing until it either runs out of jobs
// to be done or a timeout forces it to interrupt and jump back to the caller.
QElapsedTimer elapsedTimer;
elapsedTimer.start();
while (!mViewItemJobs.isEmpty()) {
// Have a job to do.
ViewItemJob *job = mViewItemJobs.first();
#ifdef KDEPIM_FOLDEROPEN_PROFILE
// Here we check if an old job has just completed or if we are at the start of the
// first job. We then initialize job data stuff and timers based on this.
const int currentPass = job->currentPass();
const bool firstChunk = currentPass != Stats::lastPass;
if (currentPass != Stats::lastPass && Stats::lastPass != -1) {
Stats::totalTime[Stats::lastPass] = Stats::currentJobStartTime.elapsed();
}
const bool firstJob = job->currentPass() == ViewItemJob::Pass1Fill && firstChunk;
const int elements = job->endIndex() - job->startIndex();
if (firstJob) {
Stats::resetStats();
Stats::totalMessages = elements;
Stats::firstStartTime.restart();
}
if (firstChunk) {
Stats::numElements[currentPass] = elements;
Stats::currentJobStartTime.restart();
}
Stats::chunks[currentPass]++;
Stats::lastPass = currentPass;
#endif
mViewItemJobStepIdleInterval = job->idleInterval();
mViewItemJobStepChunkTimeout = job->chunkTimeout();
mViewItemJobStepMessageCheckCount = job->messageCheckCount();
if (job->disconnectUI()) {
mModelForItemFunctions = nullptr; // disconnect the UI for this job
Q_ASSERT(mLoading); // this must be true in the first job
// FIXME: Should assert yet more that this is the very first job for this StorageModel
// Asserting only mLoading is not enough as we could be using a two-jobs loading strategy
// or this could be a job enqueued before the first job has completed.
} else {
// With a connected UI we need to avoid the view to update the scrollbars at EVERY insertion or expansion.
// QTreeViewPrivate::updateScrollBars() is very expensive as it loops through ALL the items in the view every time.
// We can't disable the function directly as it's hidden in the private data object of QTreeView
// but we can disable the parent QTreeView::updateGeometries() instead.
// We will trigger it "manually" at the end of the step.
mView->ignoreUpdateGeometries(true);
// Ok.. I know that this seems unbelieveable but disabling updates actually
// causes a (significant) performance loss in most cases. This is probably because QTreeView
// uses delayed layouts when updates are disabled which should be delayed but in
// fact are "forced" by next item insertions. The delayed layout algorithm, then
// is probably slower than the non-delayed one.
// Disabling the paintEvent() doesn't seem to work either.
//mView->setUpdatesEnabled( false );
}
switch (viewItemJobStepInternalForJob(job, elapsedTimer)) {
case ViewItemJobInterrupted:
// current job interrupted by timeout: will propagate status to caller
// but before this, give some feedback to the user
// FIXME: This is now inaccurate, think of something else
switch (job->currentPass()) {
case ViewItemJob::Pass1Fill:
case ViewItemJob::Pass1Cleanup:
case ViewItemJob::Pass1Update:
Q_EMIT q->statusMessage(i18np("Processed 1 Message of %2",
"Processed %1 Messages of %2",
job->currentIndex() - job->startIndex(),
job->endIndex() - job->startIndex() + 1));
break;
case ViewItemJob::Pass2:
Q_EMIT q->statusMessage(i18np("Threaded 1 Message of %2",
"Threaded %1 Messages of %2",
job->currentIndex() - job->startIndex(),
job->endIndex() - job->startIndex() + 1));
break;
case ViewItemJob::Pass3:
Q_EMIT q->statusMessage(i18np("Threaded 1 Message of %2",
"Threaded %1 Messages of %2",
job->currentIndex() - job->startIndex(),
job->endIndex() - job->startIndex() + 1));
break;
case ViewItemJob::Pass4:
Q_EMIT q->statusMessage(i18np("Grouped 1 Thread of %2",
"Grouped %1 Threads of %2",
job->currentIndex() - job->startIndex(),
job->endIndex() - job->startIndex() + 1));
break;
case ViewItemJob::Pass5:
Q_EMIT q->statusMessage(i18np("Updated 1 Group of %2",
"Updated %1 Groups of %2",
job->currentIndex() - job->startIndex(),
job->endIndex() - job->startIndex() + 1));
break;
default:
break;
}
if (!job->disconnectUI()) {
mView->ignoreUpdateGeometries(false);
// explicit call to updateGeometries() here
mView->updateGeometries();
}
return ViewItemJobInterrupted;
break;
case ViewItemJobCompleted:
// If this job worked with a disconnected UI, Q_EMIT layoutChanged()
// to reconnect it. We go back to normal operation now.
if (job->disconnectUI()) {
mModelForItemFunctions = q;
// This call would destroy the expanded state of items.
// This is why when mModelForItemFunctions was 0 we didn't actually expand them
// but we just set a "ExpandNeeded" mark...
#ifdef KDEPIM_FOLDEROPEN_PROFILE
QTime layoutChangedTimer;
layoutChangedTimer.start();
#endif
mView->modelAboutToEmitLayoutChanged();
Q_EMIT q->layoutChanged();
mView->modelEmittedLayoutChanged();
#ifdef KDEPIM_FOLDEROPEN_PROFILE
Stats::layoutChangeTime = layoutChangedTimer.elapsed();
QTime expandingTime;
expandingTime.start();
#endif
// expand all the items that need it in a single sweep
// FIXME: This takes quite a lot of time, it could be made an interruptible job
auto rootChildItems = mRootItem->childItems();
if (rootChildItems) {
for (const auto it : qAsConst(*rootChildItems)) {
if (it->initialExpandStatus() == Item::ExpandNeeded) {
syncExpandedStateOfSubtree(it);
}
}
}
#ifdef KDEPIM_FOLDEROPEN_PROFILE
Stats::expandingTreeTime = expandingTime.elapsed();
#endif
} else {
mView->ignoreUpdateGeometries(false);
// explicit call to updateGeometries() here
mView->updateGeometries();
}
// this job has been completed
delete mViewItemJobs.takeFirst();
#ifdef KDEPIM_FOLDEROPEN_PROFILE
// Last job finished!
Stats::totalTime[currentPass] = Stats::currentJobStartTime.elapsed();
printStatistics();
#endif
// take care of small jobs which never timeout by themselves because
// of a small number of messages. At the end of each job check
// the time used and if we're timeoutting and there is another job
// then interrupt.
if ((elapsedTimer.elapsed() > mViewItemJobStepChunkTimeout) || (elapsedTimer.elapsed() < 0)) {
if (!mViewItemJobs.isEmpty()) {
return ViewItemJobInterrupted;
}
// else it's completed in fact
} // else proceed with the next job
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
}
// no more jobs
Q_EMIT q->statusMessage(i18nc("@info:status Finished view fill", "Ready"));
return ViewItemJobCompleted;
}
void ModelPrivate::viewItemJobStep()
{
// A single step in the View Fill operation.
// This function wraps viewItemJobStepInternal() which does the step job
// and either completes it or stops because of a timeout.
// If the job is stopped then we start a zero-msecs timer to call us
// back and resume the job. Otherwise we're just done.
mViewItemJobStepStartTime = ::time(nullptr);
if (mFillStepTimer.isActive()) {
mFillStepTimer.stop();
}
if (!mStorageModel) {
return; // nothing more to do
}
// Save the current item in the view as our process may
// cause items to be reparented (and QTreeView will forget the current item in the meantime).
// This machinery is also needed when we're about to remove items from the view in
// a cleanup job: we'll be trying to set as current the item after the one removed.
QModelIndex currentIndexBeforeStep = mView->currentIndex();
Item *currentItemBeforeStep = currentIndexBeforeStep.isValid()
? static_cast< Item * >(currentIndexBeforeStep.internalPointer()) : nullptr;
// mCurrentItemToRestoreAfterViewItemJobStep will be zeroed out if it's killed
mCurrentItemToRestoreAfterViewItemJobStep = currentItemBeforeStep;
// Save the current item position in the viewport as QTreeView fails to keep
// the current item in the sample place when items are added or removed...
QRect rectBeforeViewItemJobStep;
const bool lockView = mView->isScrollingLocked();
// This is generally SLOW AS HELL... (so we avoid it if we lock the view and thus don't need it)
if (mCurrentItemToRestoreAfterViewItemJobStep && (!lockView)) {
rectBeforeViewItemJobStep = mView->visualRect(currentIndexBeforeStep);
}
// FIXME: If the current item is NOT in the view, preserve the position
// of the top visible item. This will make the view move yet less.
// Insulate the View from (very likely spurious) "currentChanged()" signals.
mView->ignoreCurrentChanges(true);
// And go to real work.
switch (viewItemJobStepInternal()) {
case ViewItemJobInterrupted:
// Operation timed out, need to resume in a while
if (!mInLengthyJobBatch) {
mInLengthyJobBatch = true;
}
mFillStepTimer.start(mViewItemJobStepIdleInterval); // this is a single shot timer connected to viewItemJobStep()
// and go dealing with current/selection out of the switch.
break;
case ViewItemJobCompleted:
// done :)
Q_ASSERT(mModelForItemFunctions); // UI must be no (longer) disconnected in this state
// Ask the view to remove the eventual busy indications
if (mInLengthyJobBatch) {
mInLengthyJobBatch = false;
}
if (mLoading) {
mLoading = false;
mView->modelFinishedLoading();
}
// Apply pre-selection, if any
if (mPreSelectionMode != PreSelectNone) {
mView->ignoreCurrentChanges(false);
bool bSelectionDone = false;
switch (mPreSelectionMode) {
case PreSelectLastSelected:
// fall down
break;
case PreSelectFirstUnreadCentered:
bSelectionDone = mView->selectFirstMessageItem(MessageTypeUnreadOnly, true); // center
break;
case PreSelectOldestCentered:
mView->setCurrentMessageItem(mOldestItem, true /* center */);
bSelectionDone = true;
break;
case PreSelectNewestCentered:
mView->setCurrentMessageItem(mNewestItem, true /* center */);
bSelectionDone = true;
break;
case PreSelectNone:
// deal with selection below
break;
default:
qCWarning(MESSAGELIST_LOG) << "ERROR: Unrecognized pre-selection mode " << static_cast<int>(mPreSelectionMode);
break;
}
if ((!bSelectionDone) && (mPreSelectionMode != PreSelectNone)) {
// fallback to last selected, if possible
if (mLastSelectedMessageInFolder) { // we found it in the loading process: select and jump out
mView->setCurrentMessageItem(mLastSelectedMessageInFolder);
bSelectionDone = true;
}
}
if (bSelectionDone) {
mLastSelectedMessageInFolder = nullptr;
mPreSelectionMode = PreSelectNone;
return; // already taken care of current / selection
}
}
// deal with current/selection out of the switch
break;
default:
// This is *really* a BUG
qCWarning(MESSAGELIST_LOG) << "ERROR: returned an invalid result";
Q_ASSERT(false);
break;
}
// Everything else here deals with the selection
// If UI is disconnected then we don't have anything else to do here
if (!mModelForItemFunctions) {
mView->ignoreCurrentChanges(false);
return;
}
// Restore current/selection and/or scrollbar position
if (mCurrentItemToRestoreAfterViewItemJobStep) {
bool stillIgnoringCurrentChanges = true;
// If the assert below fails then the previously current item got detached
// and didn't get reattached in the step: this should never happen.
Q_ASSERT(mCurrentItemToRestoreAfterViewItemJobStep->isViewable());
// Check if the current item changed
QModelIndex currentIndexAfterStep = mView->currentIndex();
Item *currentAfterStep = currentIndexAfterStep.isValid()
? static_cast< Item * >(currentIndexAfterStep.internalPointer()) : nullptr;
if (mCurrentItemToRestoreAfterViewItemJobStep != currentAfterStep) {
// QTreeView lost the current item...
if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
// Some view job code expects us to actually *change* the current item.
// This is done by the cleanup step which removes items and tries
// to set as current the item *after* the removed one, if possible.
// We need the view to handle the change though.
stillIgnoringCurrentChanges = false;
mView->ignoreCurrentChanges(false);
} else {
// we just have to restore the old current item. The code
// outside shouldn't have noticed that we lost it (e.g. the message viewer
// still should have the old message opened). So we don't need to
// actually notify the view of the restored setting.
}
// Restore it
qCDebug(MESSAGELIST_LOG) << "Gonna restore current here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
mView->setCurrentIndex(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
} else {
// The item we're expected to set as current is already current
if (mCurrentItemToRestoreAfterViewItemJobStep != currentItemBeforeStep) {
// But we have changed it in the job step.
// This means that: we have deleted the current item and chosen a
// new candidate as current but Qt also has chosen it as candidate
// and already made it current. The problem is that (as of Qt 4.4)
// it probably didn't select it.
if (!mView->selectionModel()->hasSelection()) {
stillIgnoringCurrentChanges = false;
mView->ignoreCurrentChanges(false);
qCDebug(MESSAGELIST_LOG) << "Gonna restore selection here" << mCurrentItemToRestoreAfterViewItemJobStep->subject();
QItemSelection selection;
selection.append(QItemSelectionRange(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0)));
mView->selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
}
}
// FIXME: If it was selected before the change, then re-select it (it may happen that it's not)
if (!lockView) {
// we prefer to keep the currently selected item steady in the view
QRect rectAfterViewItemJobStep = mView->visualRect(q->index(mCurrentItemToRestoreAfterViewItemJobStep, 0));
if (rectBeforeViewItemJobStep.y() != rectAfterViewItemJobStep.y()) {
// QTreeView lost its position...
mView->verticalScrollBar()->setValue(mView->verticalScrollBar()->value() + rectAfterViewItemJobStep.y() - rectBeforeViewItemJobStep.y());
}
}
// and kill the insulation, if not yet done
if (stillIgnoringCurrentChanges) {
mView->ignoreCurrentChanges(false);
}
return;
}
// Either there was no current item before, or it was lost in a cleanup step and another candidate for
// current item couldn't be found (possibly empty view)
mView->ignoreCurrentChanges(false);
if (currentItemBeforeStep) {
// lost in a cleanup..
// tell the view that we have a new current, this time with no insulation
mView->slotSelectionChanged(QItemSelection(), QItemSelection());
}
}
void ModelPrivate::slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to)
{
if (parent.isValid()) {
return; // ugh... should never happen
}
Q_ASSERT(from <= to);
int count = (to - from) + 1;
mInvariantRowMapper->modelRowsInserted(from, count);
// look if no current job is in the middle
int jobCount = mViewItemJobs.count();
for (int idx = 0; idx < jobCount; idx++) {
ViewItemJob *job = mViewItemJobs.at(idx);
if (job->currentPass() != ViewItemJob::Pass1Fill) {
// The job is a cleanup or in a later pass: the storage has been already accessed
// and the messages created... no need to care anymore: the invariant row mapper will do the job.
continue;
}
if (job->currentIndex() > job->endIndex()) {
// The job finished the Pass1Fill but still waits for the pass indicator to be
// changed. This is unlikely but still may happen if the job has been interrupted
// and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
continue;
}
//
// The following cases are possible:
//
// from to
// | | -> shift up job
// from to
// | | -> shift up job
// from to
// | | -> shift up job
// from to
// | | -> split job
// from to
// | | -> split job
// from to
// | | -> job unaffected
//
//
// FOLDER
// |-------------------------|---------|--------------|
// 0 currentIndex endIndex count
// +-- job --+
//
if (from > job->endIndex()) {
// The change is completely above the job, the job is not affected
continue;
}
if (from > job->currentIndex()) { // and from <= job->endIndex()
// The change starts in the middle of the job in a way that it must be split in two.
// The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1.
// The second part ranges from "from" to job->endIndex() that are now shifted up by count steps.
// First add a new job for the second part.
auto newJob = new ViewItemJob(from + count, job->endIndex() + count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
Q_ASSERT(newJob->currentIndex() <= newJob->endIndex());
idx++; // we can skip this job in the loop, it's already ok
jobCount++; // and our range increases by one.
mViewItemJobs.insert(idx, newJob);
// Then limit the original job to the first part
job->setEndIndex(from - 1);
Q_ASSERT(job->currentIndex() <= job->endIndex());
continue;
}
// The change starts below (or exactly on the beginning of) the job.
// The job must be shifted up.
job->setCurrentIndex(job->currentIndex() + count);
job->setEndIndex(job->endIndex() + count);
Q_ASSERT(job->currentIndex() <= job->endIndex());
}
bool newJobNeeded = true;
// Try to attach to an existing fill job, if any.
// To enforce consistency we can attach only if the Fill job
// is the last one in the list (might be eventually *also* the first,
// and even being already processed but we must make sure that there
// aren't jobs _after_ it).
if (jobCount > 0) {
ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
if (job->currentPass() == ViewItemJob::Pass1Fill) {
if (
// The job ends just before the added rows
(from == (job->endIndex() + 1))
&&// The job didn't reach the end of Pass1Fill yet
(job->currentIndex() <= job->endIndex())
) {
// We can still attach this :)
job->setEndIndex(to);
Q_ASSERT(job->currentIndex() <= job->endIndex());
newJobNeeded = false;
}
}
}
if (newJobNeeded) {
// FIXME: Should take timing options from aggregation here ?
ViewItemJob *job = new ViewItemJob(from, to, 100, 50, 10);
mViewItemJobs.append(job);
}
if (!mFillStepTimer.isActive()) {
mFillStepTimer.start(mViewItemJobStepIdleInterval);
}
}
void ModelPrivate::slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to)
{
// This is called when the underlying StorageModel emits the rowsRemoved signal.
if (parent.isValid()) {
return; // ugh... should never happen
}
// look if no current job is in the middle
Q_ASSERT(from <= to);
const int count = (to - from) + 1;
int jobCount = mViewItemJobs.count();
if (mRootItem && from == 0 && count == mRootItem->childItemCount() && jobCount == 0) {
clear();
return;
}
for (int idx = 0; idx < jobCount; idx++) {
ViewItemJob *job = mViewItemJobs.at(idx);
if (job->currentPass() != ViewItemJob::Pass1Fill) {
// The job is a cleanup or in a later pass: the storage has been already accessed
// and the messages created... no need to care: we will invalidate the messages in a while.
continue;
}
if (job->currentIndex() > job->endIndex()) {
// The job finished the Pass1Fill but still waits for the pass indicator to be
// changed. This is unlikely but still may happen if the job has been interrupted
// and then a call to slotStorageModelRowsRemoved() caused it to be forcibly completed.
continue;
}
//
// The following cases are possible:
//
// from to
// | | -> shift down job
// from to
// | | -> shift down and crop job
// from to
// | | -> kill job
// from to
// | | -> split job, crop and shift
// from to
// | | -> crop job
// from to
// | | -> job unaffected
//
//
// FOLDER
// |-------------------------|---------|--------------|
// 0 currentIndex endIndex count
// +-- job --+
//
if (from > job->endIndex()) {
// The change is completely above the job, the job is not affected
continue;
}
if (from > job->currentIndex()) { // and from <= job->endIndex()
// The change starts in the middle of the job and ends in the middle or after the job.
// The first part is unaffected by the shift and ranges from job->currentIndex() to from - 1
// We use the existing job for this.
job->setEndIndex(from - 1); // stop before the first removed row
Q_ASSERT(job->currentIndex() <= job->endIndex());
if (to < job->endIndex()) {
// The change ends inside the job and a part of it can be completed.
// We create a new job for the shifted remaining part. It would actually
// range from to + 1 up to job->endIndex(), but we need to shift it down by count.
// since count = ( to - from ) + 1 so from = to + 1 - count
auto newJob = new ViewItemJob(from, job->endIndex() - count, job->chunkTimeout(), job->idleInterval(), job->messageCheckCount());
Q_ASSERT(newJob->currentIndex() < newJob->endIndex());
idx++; // we can skip this job in the loop, it's already ok
jobCount++; // and our range increases by one.
mViewItemJobs.insert(idx, newJob);
} // else the change includes completely the end of the job and no other part of it can be completed.
continue;
}
// The change starts below (or exactly on the beginning of) the job. ( from <= job->currentIndex() )
if (to >= job->endIndex()) {
// The change completely covers the job: kill it
// We don't delete the job since we want the other passes to be completed
// This is because the Pass1Fill may have already filled mUnassignedMessageListForPass2
// and may have set mOldestItem and mNewestItem. We *COULD* clear the unassigned
// message list with clearUnassignedMessageLists() but mOldestItem and mNewestItem
// could be still dangling pointers. So we just move the current index of the job
// after the end (so storage model scan terminates) and let it complete spontaneously.
job->setCurrentIndex(job->endIndex() + 1);
continue;
}
if (to >= job->currentIndex()) {
// The change partially covers the job. Only a part of it can be completed
// and it must be shifted down. It would actually
// range from to + 1 up to job->endIndex(), but we need to shift it down by count.
// since count = ( to - from ) + 1 so from = to + 1 - count
job->setCurrentIndex(from);
job->setEndIndex(job->endIndex() - count);
Q_ASSERT(job->currentIndex() <= job->endIndex());
continue;
}
// The change is completely below the job: it must be shifted down.
job->setCurrentIndex(job->currentIndex() - count);
job->setEndIndex(job->endIndex() - count);
}
// This will invalidate the ModelInvariantIndex-es that have been removed and return
// them all in a nice list that we can feed to a view removal job.
auto invalidatedIndexes = mInvariantRowMapper->modelRowsRemoved(from, count);
if (invalidatedIndexes) {
// Try to attach to an existing cleanup job, if any.
// To enforce consistency we can attach only if the Cleanup job
// is the last one in the list (might be eventually *also* the first,
// and even being already processed but we must make sure that there
// aren't jobs _after_ it).
if (jobCount > 0) {
ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
if (job->currentPass() == ViewItemJob::Pass1Cleanup) {
if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
//qCDebug(MESSAGELIST_LOG) << "Appending " << invalidatedIndexes->count() << " invalidated indexes to existing cleanup job";
// We can still attach this :)
*(job->invariantIndexList()) += *invalidatedIndexes;
job->setEndIndex(job->endIndex() + invalidatedIndexes->count());
delete invalidatedIndexes;
invalidatedIndexes = nullptr;
}
}
}
if (invalidatedIndexes) {
// Didn't append to any existing cleanup job.. create a new one
//qCDebug(MESSAGELIST_LOG) << "Creating new cleanup job for " << invalidatedIndexes->count() << " invalidated indexes";
// FIXME: Should take timing options from aggregation here ?
auto job = new ViewItemJob(ViewItemJob::Pass1Cleanup, invalidatedIndexes, 100, 50, 10);
mViewItemJobs.append(job);
}
if (!mFillStepTimer.isActive()) {
mFillStepTimer.start(mViewItemJobStepIdleInterval);
}
}
}
void ModelPrivate::slotStorageModelLayoutChanged()
{
qCDebug(MESSAGELIST_LOG) << "Storage model layout changed";
// need to reset everything...
q->setStorageModel(mStorageModel);
qCDebug(MESSAGELIST_LOG) << "Storage model layout changed done";
}
void ModelPrivate::slotStorageModelDataChanged(const QModelIndex &fromIndex, const QModelIndex &toIndex)
{
Q_ASSERT(mStorageModel); // must exist (and be the sender of the signal connected to this slot)
int from = fromIndex.row();
int to = toIndex.row();
Q_ASSERT(from <= to);
int count = (to - from) + 1;
int jobCount = mViewItemJobs.count();
// This will find out the ModelInvariantIndex-es that need an update and will return
// them all in a nice list that we can feed to a view removal job.
auto indexesThatNeedUpdate = mInvariantRowMapper->modelIndexRowRangeToModelInvariantIndexList(from, count);
if (indexesThatNeedUpdate) {
// Try to attach to an existing update job, if any.
// To enforce consistency we can attach only if the Update job
// is the last one in the list (might be eventually *also* the first,
// and even being already processed but we must make sure that there
// aren't jobs _after_ it).
if (jobCount > 0) {
ViewItemJob *job = mViewItemJobs.at(jobCount - 1);
if (job->currentPass() == ViewItemJob::Pass1Update) {
if ((job->currentIndex() <= job->endIndex()) && job->invariantIndexList()) {
// We can still attach this :)
*(job->invariantIndexList()) += *indexesThatNeedUpdate;
job->setEndIndex(job->endIndex() + indexesThatNeedUpdate->count());
delete indexesThatNeedUpdate;
indexesThatNeedUpdate = nullptr;
}
}
}
if (indexesThatNeedUpdate) {
// Didn't append to any existing update job.. create a new one
// FIXME: Should take timing options from aggregation here ?
auto job = new ViewItemJob(ViewItemJob::Pass1Update, indexesThatNeedUpdate, 100, 50, 10);
mViewItemJobs.append(job);
}
if (!mFillStepTimer.isActive()) {
mFillStepTimer.start(mViewItemJobStepIdleInterval);
}
}
}
void ModelPrivate::slotStorageModelHeaderDataChanged(Qt::Orientation, int, int)
{
if (mStorageModelContainsOutboundMessages != mStorageModel->containsOutboundMessages()) {
mStorageModelContainsOutboundMessages = mStorageModel->containsOutboundMessages();
Q_EMIT q->headerDataChanged(Qt::Horizontal, 0, q->columnCount());
}
}
Qt::ItemFlags Model::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
return Qt::NoItemFlags;
}
Q_ASSERT(d->mModelForItemFunctions); // UI must be connected if a valid index was queried
Item *it = static_cast< Item * >(index.internalPointer());
Q_ASSERT(it);
if (it->type() == Item::GroupHeader) {
return Qt::ItemIsEnabled;
}
Q_ASSERT(it->type() == Item::Message);
if (!static_cast< MessageItem * >(it)->isValid()) {
return Qt::NoItemFlags; // not enabled, not selectable
}
if (static_cast< MessageItem * >(it)->aboutToBeRemoved()) {
return Qt::NoItemFlags; // not enabled, not selectable
}
if (static_cast< MessageItem * >(it)->status().isDeleted()) {
return Qt::NoItemFlags; // not enabled, not selectable
}
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QMimeData *MessageList::Core::Model::mimeData(const QModelIndexList &indexes) const
{
QList< MessageItem * > msgs;
for (const QModelIndex &idx : qAsConst(indexes)) {
if (idx.isValid()) {
Item *item = static_cast<Item *>(idx.internalPointer());
if (item->type() == MessageList::Core::Item::Message) {
msgs << static_cast<MessageItem *>(idx.internalPointer());
}
}
}
return storageModel()->mimeData(msgs);
}
Item *Model::rootItem() const
{
return d->mRootItem;
}
bool Model::isLoading() const
{
return d->mLoading;
}
MessageItem *Model::messageItemByStorageRow(int row) const
{
if (!d->mStorageModel) {
return nullptr;
}
auto idx = d->mInvariantRowMapper->modelIndexRowToModelInvariantIndex(row);
if (!idx) {
return nullptr;
}
return static_cast< MessageItem * >(idx);
}
MessageItemSetReference Model::createPersistentSet(const QList<MessageItem *> &items)
{
if (!d->mPersistentSetManager) {
d->mPersistentSetManager = new MessageItemSetManager();
}
MessageItemSetReference ref = d->mPersistentSetManager->createSet();
for (const auto mi : items) {
d->mPersistentSetManager->addMessageItem(ref, mi);
}
return ref;
}
QList< MessageItem * > Model::persistentSetCurrentMessageItemList(MessageItemSetReference ref)
{
if (d->mPersistentSetManager) {
return d->mPersistentSetManager->messageItems(ref);
}
return QList< MessageItem * >();
}
void Model::deletePersistentSet(MessageItemSetReference ref)
{
if (!d->mPersistentSetManager) {
return;
}
d->mPersistentSetManager->removeSet(ref);
if (d->mPersistentSetManager->setCount() < 1) {
delete d->mPersistentSetManager;
d->mPersistentSetManager = nullptr;
}
}
#include "moc_model.cpp"
diff --git a/messagelist/src/core/model.h b/messagelist/src/core/model.h
index c8fb4590..ab1a0546 100644
--- a/messagelist/src/core/model.h
+++ b/messagelist/src/core/model.h
@@ -1,225 +1,225 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_MODEL_H
#define MESSAGELIST_CORE_MODEL_H
#include <QAbstractItemModel>
#include <QList>
#include <QHash>
#include <QMultiHash>
#include <core/enums.h>
#include <time.h> // time_t
namespace MessageList {
namespace Core {
typedef long int MessageItemSetReference;
class Filter;
class GroupHeaderItem;
class Item;
class Manager;
class MessageItem;
class Theme;
class StorageModel;
class View;
class ModelPrivate;
class SortOrder;
class Aggregation;
/**
* This class manages the huge tree of displayable objects: GroupHeaderItems and MessageItems.
* The tree is exposed via a 'hacked' QAbstractItemModel interface to a QTreeView
* subclass (which is MessageList::View).
*
* The keypoint in this class is that it has to be non-blocking in manipulating the tree:
* fill, cleanup and update operations are performed in timed chunks. Perfect non-blocking
* behaviour is not possible since there are some small operations that basically can't be
* split in chunks. However, these exceptions apply to a minority of tasks and in the
* average case the user will not notice.
*
* The data for building the tree is obtained from a subclass of StorageModel. The
* StorageModel must offer a consistent rappresentation of a "flat" folder containing
* messages.
*/
class Model : public QAbstractItemModel
{
friend class Item;
friend class ItemPrivate;
Q_OBJECT
public:
/**
* Creates the mighty Model attached to the specified View.
*/
explicit Model(View *pParent);
/**
* Destroys the mighty model along with the tree of items it manages.
*/
~Model() override;
/**
* Returns the StorageModel currently set.
*/
StorageModel *storageModel() const;
/**
* Sets the storage model from that the messages to be displayed should be fetched.
* The model is then reset and a new fill operation is started. The fill operation may
* or may not complete before setStorageModel() returns. This depends on the fill
* strategy and the size of the folder. You can check if the fill operation has
* completed by looking at the return value of isLoading().
*
* Pre-selection is the action of automatically selecting a message just after the folder
* has finished loading. We may want to select the message that was selected the last
* time this folder has been open, or we may want to select the first unread message.
* We also may want to do no pre-selection at all (for example, when the user
* starts navigating the view before the pre-selection could actually be made
* and pre-selecting would confuse him). The pre-selection is applied once
* loading is complete.
*/
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode = PreSelectLastSelected);
/**
* Sets the pre-selection mode.
*
* Called with PreSelectNone to abort any pending message pre-selection. This may be done if the user
* starts navigating the view and selecting items before we actually could
* apply the pre-selection.
*/
void setPreSelectionMode(PreSelectionMode preSelect);
/**
* Returns the hidden root item that all the messages are (or will be) attached to.
* The returned value is never 0.
*/
Item *rootItem() const;
/**
* Returns true if the view is currently loading, that is
- * it's in the first (possibly lenghty) job batch after attacching to a StorageModel.
+ * it's in the first (possibly lengthy) job batch after attaching to a StorageModel.
*/
bool isLoading() const;
/**
* Returns the message item that is at the _current_ storage row index
* or zero if no such storage item is found. Please note that this may return 0
* also if the specified storage row hasn't been actually read yet. This may happen
* if isLoading() returns true. In this case the only thing you can do is to retry in a while.
*/
MessageItem *messageItemByStorageRow(int row) const;
/**
* Sets the Aggregation mode.
* Does not reload the model in any way: you need to call setStorageModel( storageModel() ) for this to happen.
* The pointer ownership remains of the caller which must ensure its validity until the next
* call to setAggretation() or until this Model dies. The caller, in fact, is Widget which
* takes care of meeting the above conditions. The aggregation pointer must not be null.
*/
void setAggregation(const Aggregation *aggregation);
/**
* Sets the Theme.
* Does not reload the model in any way: you need to call setStorageModel( storageModel() ) for this to happen.
* The pointer ownership remains of the caller which must ensure its validity until the next
* call to setTheme() or until this Model dies. The caller, in fact, is Widget which
* takes care of meeting the above conditions. The theme pointer must not be null.
*/
void setTheme(const Theme *theme);
/**
* Sets the sort order. As with setTheme() and setAggregation(), this does not reload the
* model in any way.
*/
void setSortOrder(const SortOrder *sortOrder);
/**
* Returns the sort order
*/
const SortOrder *sortOrder() const;
/**
* Sets the Filter to be applied on messages. filter may be null (no filter is applied).
* The pointer ownership remains of the caller which must ensure its validity until the next
* call to setFilter() or until this Model dies. The caller, in fact, is Widget which
* takes care of meeting the above conditions. The Filter pointer may be null.
*/
void setFilter(const Filter *filter);
/**
* Creates a persistent set for the specified MessageItems and
* returns its reference. Later you can use this reference
* to retrieve the list of MessageItems that are still valid.
* See persistentSetActualMessageList() for that.
*
* Persistent sets consume resources (both memory and CPU time
* while manipulating the view) so be sure to call deletePersistentSet()
* when you no longer need it.
*/
MessageItemSetReference createPersistentSet(const QList< MessageItem * > &items);
/**
* Returns the list of MessageItems that are still existing in the
* set pointed by the specified reference. This list will contain
* at most the messages that you have passed to createPersistentSet()
* but may contain less (even 0) if these MessageItem object were removed
* from the view for some reason.
*/
QList< MessageItem * > persistentSetCurrentMessageItemList(MessageItemSetReference ref);
/**
* Deletes the persistent set pointed by the specified reference.
* If the set does not exist anymore, nothing happens.
*/
void deletePersistentSet(MessageItemSetReference ref);
// Mandatory QAbstractItemModel interface.
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(Item *item, int column) const;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
/// Called when user initiates a drag from the messagelist
QMimeData *mimeData(const QModelIndexList &indexes) const override;
Q_SIGNALS:
/**
* Notify the outside when updating the status bar with a message
* could be useful
*/
void statusMessage(const QString &message);
private:
friend class ModelPrivate;
ModelPrivate *const d;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_MODEL_H
diff --git a/messagelist/src/core/model_p.h b/messagelist/src/core/model_p.h
index b182cf4c..0e40b020 100644
--- a/messagelist/src/core/model_p.h
+++ b/messagelist/src/core/model_p.h
@@ -1,463 +1,463 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_MODEL_P_H
#define MESSAGELIST_CORE_MODEL_P_H
#include "model.h"
#include "threadingcache.h"
#include <config-messagelist.h>
#include <QTimer>
class QElapsedTimer;
namespace MessageList {
namespace Core {
class ViewItemJob;
class ModelInvariantRowMapper;
class MessageItemSetManager;
class ModelPrivate
{
public:
explicit ModelPrivate(Model *owner) : q(owner)
{
}
void fillView();
/**
* This is called by MessageList::Manager once in a while.
* It is a good place to check if the date has changed and
* trigger a view reload.
*/
void checkIfDateChanged();
void viewItemJobStep();
/**
* Attempt to find the threading parent for the specified message item.
* Sets the message threading status to the appropriate value.
*
* This function performs In-Reply-To and References threading.
*/
MessageItem *findMessageParent(MessageItem *mi);
/**
* Attempt to find the threading parent for the specified message item.
* Sets the message threading status to the appropriate value.
*
* This function performs Subject based threading.
*/
MessageItem *guessMessageParent(MessageItem *mi);
enum AttachOptions {
SkipCacheUpdate = 0,
StoreInCache = 1
};
void attachMessageToParent(Item *pParent, MessageItem *mi, AttachOptions attachOptions = StoreInCache);
void messageDetachedUpdateParentProperties(Item *oldParent, MessageItem *mi);
void attachMessageToGroupHeader(MessageItem *mi);
void attachGroup(GroupHeaderItem *ghi);
enum ViewItemJobResult {
ViewItemJobCompleted,
ViewItemJobInterrupted
};
ViewItemJobResult viewItemJobStepInternal();
ViewItemJobResult viewItemJobStepInternalForJob(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
// FIXME: Those look like they should be made virtual in some job class! -> Refactor
ViewItemJobResult viewItemJobStepInternalForJobPass1Fill(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass1Cleanup(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass1Update(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass2(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass3(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass4(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
ViewItemJobResult viewItemJobStepInternalForJobPass5(ViewItemJob *job, const QElapsedTimer &elapsedTimer);
void clearJobList();
void clearUnassignedMessageLists();
void clearOrphanChildrenHash();
void clearThreadingCacheReferencesIdMD5ToMessageItem();
void clearThreadingCacheMessageSubjectMD5ToMessageItem();
void addMessageToReferencesBasedThreadingCache(MessageItem *mi);
void removeMessageFromReferencesBasedThreadingCache(MessageItem *mi);
void addMessageToSubjectBasedThreadingCache(MessageItem *mi);
void removeMessageFromSubjectBasedThreadingCache(MessageItem *mi);
void clear();
/**
* Sync the expanded state of the subtree with the specified root.
* This will cause the items that are marked with Item::ExpandNeeded to be
* expanded also in the view. For optimization purposes the specified root
* is assumed to be marked as Item::ExpandNeeded so be sure to check it
* before calling this function.
*/
void syncExpandedStateOfSubtree(Item *root);
/**
* Save the expanded state of the subtree with the specified root.
* The state will be saved in the initialExpandStatus() variable.
* For optimization purposes the specified root is assumed to be expanded
* and viewable.
*/
void saveExpandedStateOfSubtree(Item *root);
#ifdef KDEPIM_FOLDEROPEN_PROFILE
// This prints out all the stats we collected
void printStatistics();
#endif
enum PropertyChanges {
DateChanged = 1,
MaxDateChanged = (1 << 1),
ActionItemStatusChanged = (1 << 2),
UnreadStatusChanged = (1 << 3),
ImportantStatusChanged = (1 << 4),
AttachmentStatusChanged = (1 << 5)
};
/**
* Handle the specified property changes in item. Depending on the item
* position inside the parent and the types of item and parent the item
* might need re-grouping or re-sorting. This function takes care of that.
* It is meant to be called from somewhere inside viewItemJobStepInternal()
* as it postpones group updates to Pass5.
*
* parent and item must not be null. propertyChangeMask should not be zero.
*
* Return true if parent might be affected by the item property changes
* and false otherwise.
*/
bool handleItemPropertyChanges(int propertyChangeMask, Item *parent, Item *item);
/**
* This one checks if the parent of item requires an update due to the
* properties of item (that might have been changed or the item might
* have been simply added to the parent). The properties
* are propagated up to the root item. As optimization we ASSUME that
* the item->parent() exists (is non 0) and is NOT the root item.
* Be sure to check it before calling this function (it will assert in debug mode anyway).
* ... ah... and don't be afraid: this is NOT (directly) recursive :)
*/
void propagateItemPropertiesToParent(Item *item);
/**
* Recursively applies the current filter to the tree originating at the specified item.
* The item is hidden if the filter doesn't match (the item or any children of it)
* and this function returns false.
* If the filter matches somewhere in the subtree then the item isn't hidden
* and this function returns true.
*
* Assumes that the specified item is viewable.
*/
bool applyFilterToSubtree(Item *item, const QModelIndex &parentIndex);
// Slots connected to the underlying StorageModel.
void slotStorageModelRowsInserted(const QModelIndex &parent, int from, int to);
void slotStorageModelRowsRemoved(const QModelIndex &parent, int from, int to);
void slotStorageModelDataChanged(const QModelIndex &fromIndex, const QModelIndex &toIndex);
void slotStorageModelHeaderDataChanged(Qt::Orientation orientation, int first, int last);
void slotStorageModelLayoutChanged();
void slotApplyFilter();
Model *const q;
/** counter to avoid infinite recursions in the setStorageModel() function */
int mRecursionCounterForReset;
/**
* The currently set storage model: shallow pointer.
*/
StorageModel *mStorageModel;
/**
* The currently set aggregation mode: shallow pointer set by Widget
*/
const Aggregation *mAggregation;
/**
* The currently used theme: shallow pointer
*/
const Theme *mTheme;
/**
* The currently used sort order. Pointer not owned by us, but by the Widget.
*/
const SortOrder *mSortOrder;
/**
* The filter to apply on messages. Shallow. Never 0.
*/
const Filter *mFilter;
/**
* The timer involved in breaking the "fill" operation in steps
*/
QTimer mFillStepTimer;
/**
* Group Key (usually the label) -> GroupHeaderItem, used to quickly find groups, pointers are shallow copies
*/
QHash< QString, GroupHeaderItem * > mGroupHeaderItemHash;
/**
* Threading cache.
* MessageIdMD5 -> MessageItem, pointers are shallow copies
*/
QHash< QByteArray, MessageItem * > mThreadingCacheMessageIdMD5ToMessageItem;
/**
* Threading cache.
* MessageInReplyToIdMD5 -> MessageItem, pointers are shallow copies
*/
QMultiHash< QByteArray, MessageItem * > mThreadingCacheMessageInReplyToIdMD5ToMessageItem;
/**
* Threading cache.
* ReferencesIdMD5 -> MessageItem, pointers are shallow copies
*/
QHash< QByteArray, QList< MessageItem * > * > mThreadingCacheMessageReferencesIdMD5ToMessageItem;
/**
* Threading cache.
* SubjectMD5 -> MessageItem, pointers are shallow copies
*/
QHash< QByteArray, QList< MessageItem * > * > mThreadingCacheMessageSubjectMD5ToMessageItem;
/**
* List of group headers that either need to be re-sorted or must be removed because empty
*/
QHash< GroupHeaderItem *, GroupHeaderItem * > mGroupHeadersThatNeedUpdate;
/**
* List of unassigned messages, used to handle threading in two passes, pointers are owned!
*/
QList< MessageItem * > mUnassignedMessageListForPass2;
/**
* List of unassigned messages, used to handle threading in two passes, pointers are owned!
*/
QList< MessageItem * > mUnassignedMessageListForPass3;
/**
* List of unassigned messages, used to handle threading in two passes, pointers are owned!
*/
QList< MessageItem * > mUnassignedMessageListForPass4;
/**
* Hash of orphan children used in Pass1Cleanup.
*/
QHash< MessageItem *, MessageItem * > mOrphanChildrenHash;
/**
* Pending fill view jobs, pointers are owned
*/
QList< ViewItemJob * > mViewItemJobs;
/**
* The today's date. Set when the StorageModel is set and thus grouping is performed.
* This is used to put the today's messages in the "Today" group, for instance.
*/
QDate mTodayDate;
/**
* Owned invisible root item, useful to implement algorithms that not need
* to handle the special case of parentless items. This is never 0.
*/
Item *mRootItem;
/**
- * The view we're attacched to. Shallow pointer (the View owns us).
+ * The view we're attached to. Shallow pointer (the View owns us).
*/
View *mView;
/**
* The time at the current ViewItemJob step started. Used to compute the time we
* spent inside this step and eventually jump out on timeout.
*/
time_t mViewItemJobStepStartTime;
/**
* The timeout for a single ViewItemJob step
*/
int mViewItemJobStepChunkTimeout;
/**
* The idle time between two ViewItemJob steps
*/
int mViewItemJobStepIdleInterval;
/**
* The number of messages we process at once in a ViewItemJob step without
* checking the timeouts above.
*/
int mViewItemJobStepMessageCheckCount;
/**
* Our mighty ModelInvariantRowMapper: used to workaround an
* issue related to the Model/View architecture.
*
* \sa ModelInvariantRowMapper
*/
ModelInvariantRowMapper *mInvariantRowMapper;
/**
* The label for the "Today" group item, cached, so we don't translate it multiple times.
*/
QString mCachedTodayLabel;
/**
* The label for the "Yesterday" group item, cached, so we don't translate it multiple times.
*/
QString mCachedYesterdayLabel;
/**
* The label for the "Unknown" group item, cached, so we don't translate it multiple times.
*/
QString mCachedUnknownLabel;
/**
* The label for the "Last Week" group item, cached, so we don't translate it multiple times.
*/
QString mCachedLastWeekLabel;
/**
* The label for the "Two Weeks Ago" group item, cached, so we don't translate it multiple times.
*/
QString mCachedTwoWeeksAgoLabel;
/**
* The label for the "Three Weeks Ago" group item, cached, so we don't translate it multiple times.
*/
QString mCachedThreeWeeksAgoLabel;
/**
* The label for the "Four Weeks Ago" group item, cached, so we don't translate it multiple times.
*/
QString mCachedFourWeeksAgoLabel;
/**
* The label for the "Five Weeks Ago" group item, cached, so we don't translate it multiple times.
*/
QString mCachedFiveWeeksAgoLabel;
/**
* Cached bits that we use for fast status checks
*/
qint32 mCachedWatchedOrIgnoredStatusBits;
/**
* The labels for week days names group items, cached, so we don't query QLocale multiple times.
*/
QMap<int, QString> mCachedDayNameLabel;
/*
* The labels for month names group items, cached, so we don't query QLocale multiple times.
*/
QMap<int, QString> mCachedMonthNameLabel;
/**
* Flag signaling a possibly long job batch. This is checked by other
* classes and used to display some kind of "please wait" feedback to the user.
*/
bool mInLengthyJobBatch;
/**
* We need to save the current item before each job step. This is because
* our job may cause items to be reparented (thus removed and readded with the current Qt API)
* and QTreeView will loose the current setting. We also use this to force the current
* to a specific item after a cleanup job.
*/
Item *mCurrentItemToRestoreAfterViewItemJobStep;
/**
* Set to true in the first large loading job.
* Reset to false when the job finishes.
*
* Please note that this is NOT set for later jobs: only for the first (possibly huge) one.
*/
bool mLoading;
/**
* Pre-selection is the action of automatically selecting a message just after the folder
* has finished loading. We may want to select the message that was selected the last
* time this folder has been open, or we may want to select the first unread message.
* We also may want to do no pre-selection at all (for example, when the user
* starts navigating the view before the pre-selection could actually be made
* and pre-selecting would confuse him). This member holds the option.
*
* See also setStorageModel() and abortMessagePreSelection()
*/
PreSelectionMode mPreSelectionMode;
// Oldest and newest item while loading the model
// Not valid afterwards anymore. Used for pre-selection of the newest/oldest message
MessageItem *mOldestItem;
MessageItem *mNewestItem;
/**
* The id of the preselected ;essage is "translated" to a message pointer when it's fetched
* from the storage. This message is then selected when it becomes viewable
* (so at the end of the job). 0 if we have no message to select.
*
* See also setStorageModel() and abortMessagePreSelection()
*/
MessageItem *mLastSelectedMessageInFolder;
/**
* The "persistent message item sets" are (guess what?) sets of messages
* that can be referenced globally via a persistent id. The MessageItemSetManager
* and this class keep the persistent sets coherent: messages that are deleted
* are automatically removed from all the sets.
*
* Users of this class typically create persistent sets when they start
* an asynchronous job and they query them back on the way or when the job is terminated.
*
* So mPersistentSetManager is in fact the manager for the outstanding "user" jobs.
* 0 if no jobs are pending (so there are no persistent sets at the moment).
*/
MessageItemSetManager *mPersistentSetManager;
/**
* This pointer is passed to the Item functions that insert children.
* When we work with disconnected UI this pointer becomes 0.
*/
Model *mModelForItemFunctions;
/**
* The cached result of StorageModel::containsOutboundMessages().
* We access this property at each incoming message and StorageModel::containsOutboundMessages() is
* virtual (so it's always an indirect function call). Caching makes sense.
*/
bool mStorageModelContainsOutboundMessages;
/**
* Vector of signal-slot connections between StorageModel and us
*/
QVector<QMetaObject::Connection> mStorageModelConnections;
/**
* Caches child - parent relation based on Akonadi ID and persists the cache
* in a file for each Collection. This allows for very fast reconstruction of
* threading.
*/
ThreadingCache mThreadingCache;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_MODEL_P_H
diff --git a/messagelist/src/core/modelinvariantindex.h b/messagelist/src/core/modelinvariantindex.h
index 19fd637e..a13f2d9a 100644
--- a/messagelist/src/core/modelinvariantindex.h
+++ b/messagelist/src/core/modelinvariantindex.h
@@ -1,74 +1,74 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_MODELINVARIANTINDEX_H
#define MESSAGELIST_CORE_MODELINVARIANTINDEX_H
#include <qglobal.h> // defines uint, at least.
namespace MessageList {
namespace Core {
class ModelInvariantRowMapper;
class ModelInvariantRowMapperPrivate;
class RowShift;
/**
* An invariant index that can be ALWAYS used to reference
* an item inside a QAbstractItemModel.
*
* This class is meant to be used together with ModelInvariantRowMapper.
*/
class ModelInvariantIndex
{
friend class ModelInvariantRowMapper;
friend class ModelInvariantRowMapperPrivate;
friend class RowShift;
public:
explicit ModelInvariantIndex();
virtual ~ModelInvariantIndex();
public:
/**
- * Returns true if this ModelInvariantIndex is valid, that is, it has been attacched
+ * Returns true if this ModelInvariantIndex is valid, that is, it has been attached
* to a ModelInvariantRowMapper. Returns false otherwise.
* An invalid index will always map to the current row -1 (which is invalid as QModelIndex row).
*/
bool isValid() const;
/**
* Returns the current model index row for this invariant index. This function
* calls the mapper and asks it to perform the persistent mapping.
* If this index isn't valid then the returned value is -1.
*
* If you actually own the row mapper then you may save some clock cycles
* by calling the modelInvariantIndexToModelIndexRow() by your own. If you don't
* own the mapper then this function is the only way to go.
*/
int currentModelIndexRow();
private:
class Private;
Private *const d;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_MODELINVARIANTINDEX_H
diff --git a/messagelist/src/core/modelinvariantrowmapper.cpp b/messagelist/src/core/modelinvariantrowmapper.cpp
index 5a0872b3..cb7771cc 100644
--- a/messagelist/src/core/modelinvariantrowmapper.cpp
+++ b/messagelist/src/core/modelinvariantrowmapper.cpp
@@ -1,654 +1,656 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/modelinvariantrowmapper.h"
#include "core/modelinvariantrowmapper_p.h"
#include "core/modelinvariantindex_p.h"
#include <QTimer>
#include <QTime>
#include "messagelist_debug.h"
namespace MessageList {
namespace Core {
class RowShift
{
public:
int mMinimumRowIndex;
int mShift;
QHash< int, ModelInvariantIndex * > *mInvariantHash;
public:
RowShift(int minRowIndex, int shift, QHash< int, ModelInvariantIndex * > *invariantHash)
: mMinimumRowIndex(minRowIndex)
, mShift(shift)
, mInvariantHash(invariantHash)
{
}
~RowShift()
{
for (const auto idx : qAsConst(*mInvariantHash)) {
idx->d->setRowMapper(nullptr);
}
delete mInvariantHash;
}
};
} // namespace Core
} // namespace MessageList
using namespace MessageList::Core;
ModelInvariantRowMapper::ModelInvariantRowMapper()
: d(new ModelInvariantRowMapperPrivate(this))
{
d->mRowShiftList = new QList< RowShift * >();
d->mCurrentShiftSerial = 0;
d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
d->mUpdateTimer = new QTimer(this);
d->mUpdateTimer->setSingleShot(true);
d->mLazyUpdateChunkInterval = 50;
d->mLazyUpdateIdleInterval = 50;
- connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() { d->slotPerformLazyUpdate(); });
+ connect(d->mUpdateTimer, &QTimer::timeout, this, [this]() {
+ d->slotPerformLazyUpdate();
+ });
}
ModelInvariantRowMapper::~ModelInvariantRowMapper()
{
if (d->mUpdateTimer->isActive()) {
d->mUpdateTimer->stop();
}
// FIXME: optimize this (it CAN be optimized)
for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) {
idx->d->setRowMapper(nullptr);
}
delete d->mCurrentInvariantHash;
if (d->mRowShiftList) {
while (!d->mRowShiftList->isEmpty()) {
delete d->mRowShiftList->takeFirst();
}
delete d->mRowShiftList;
}
delete d;
}
void ModelInvariantRowMapperPrivate::killFirstRowShift()
{
RowShift *shift = mRowShiftList->at(0);
Q_ASSERT(shift->mInvariantHash->isEmpty());
delete shift;
mRowShiftList->removeAt(0);
mRemovedShiftCount++;
if (mRowShiftList->isEmpty()) {
delete mRowShiftList;
mRowShiftList = nullptr;
}
}
void ModelInvariantRowMapperPrivate::indexDead(ModelInvariantIndex *invariant)
{
Q_ASSERT(invariant->d->rowMapper() == q);
if (invariant->d->rowMapperSerial() == mCurrentShiftSerial) {
mCurrentInvariantHash->remove(invariant->d->modelIndexRow());
return;
}
Q_ASSERT(invariant->d->rowMapperSerial() < mCurrentShiftSerial);
if (!mRowShiftList) {
return; // not found (not requested yet or invalid index at all)
}
uint invariantShiftIndex = invariant->d->rowMapperSerial() - mRemovedShiftCount;
Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count()));
RowShift *shift = mRowShiftList->at(invariantShiftIndex);
Q_ASSERT(shift);
shift->mInvariantHash->remove(invariant->d->modelIndexRow());
if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
// no more invariants with serial <= invariant->d->rowMapperSerial()
killFirstRowShift();
}
}
void ModelInvariantRowMapperPrivate::updateModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
{
// Here the invariant already belongs to this mapper. We ASSUME that it's somewhere
// in the history and not in the hash belonging to the current serial.
// modelIndexRow is the CURRENT model index row.
Q_ASSERT(invariantToFill->d->rowMapper() == q);
uint invariantShiftIndex = invariantToFill->d->rowMapperSerial() - mRemovedShiftCount;
Q_ASSERT(invariantShiftIndex < static_cast< uint >(mRowShiftList->count()));
RowShift *shift = mRowShiftList->at(invariantShiftIndex);
int count = shift->mInvariantHash->remove(invariantToFill->d->modelIndexRow());
Q_ASSERT(count > 0);
Q_UNUSED(count);
// update and make it belong to the current serial
invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
Q_ASSERT(!mCurrentInvariantHash->contains(invariantToFill->d->modelIndexRow()));
mCurrentInvariantHash->insert(invariantToFill->d->modelIndexRow(), invariantToFill);
if ((shift->mInvariantHash->isEmpty()) && (invariantShiftIndex == 0)) {
// no more invariants with serial <= invariantToFill->rowMapperSerial()
killFirstRowShift();
}
}
ModelInvariantIndex *ModelInvariantRowMapperPrivate::modelIndexRowToModelInvariantIndexInternal(int modelIndexRow, bool updateIfNeeded)
{
// First of all look it up in the current hash
ModelInvariantIndex *invariant = mCurrentInvariantHash->value(modelIndexRow, nullptr);
if (invariant) {
return invariant; // found: was up to date
}
// Go backward in history by unapplying changes
if (!mRowShiftList) {
return nullptr; // not found (not requested yet or invalid index at all)
}
int idx = mRowShiftList->count();
if (idx == 0) {
Q_ASSERT(false);
return nullptr; // should never happen (mRowShiftList should have been 0), but well...
}
idx--;
int previousIndexRow = modelIndexRow;
while (idx >= 0) {
RowShift *shift = mRowShiftList->at(idx);
// this shift has taken "previousModelIndexRow" in the historic state
// and has executed:
//
// if ( previousIndexRow >= shift->mMinimumRowIndex )
// previousIndexRow += shift->mShift;
//
// so inverting it
//
// int potentialPreviousModelIndexRow = modelIndexRow - shift->mShift;
// if ( potentialPreviousModelIndexRow >= shift->mMinimumRowIndex )
// previousIndexRow = potentialPreviousModelIndexRow;
//
- // or by simplyfying...
+ // or by simplifying...
int potentialPreviousModelIndexRow = previousIndexRow - shift->mShift;
if (potentialPreviousModelIndexRow >= shift->mMinimumRowIndex) {
previousIndexRow = potentialPreviousModelIndexRow;
}
invariant = shift->mInvariantHash->value(previousIndexRow, nullptr);
if (invariant) {
// found at this level in history
if (updateIfNeeded) { // update it too
updateModelInvariantIndex(modelIndexRow, invariant);
}
return invariant;
}
idx--;
}
qCWarning(MESSAGELIST_LOG) << "Requested invariant for storage row index "
<< modelIndexRow << " not found in history";
return nullptr; // not found in history
}
void ModelInvariantRowMapper::setLazyUpdateChunkInterval(int chunkInterval)
{
d->mLazyUpdateChunkInterval = chunkInterval;
}
void ModelInvariantRowMapper::setLazyUpdateIdleInterval(int idleInterval)
{
d->mLazyUpdateIdleInterval = idleInterval;
}
int ModelInvariantRowMapper::modelInvariantIndexToModelIndexRow(ModelInvariantIndex *invariant)
{
// the invariant shift serial is the serial this mapper
// had at the time it emitted the invariant.
// mRowShiftList at that time had at most invariantShiftSerial items.
Q_ASSERT(invariant);
if (invariant->d->rowMapper() != this) {
return -1;
}
if (invariant->d->rowMapperSerial() == d->mCurrentShiftSerial) {
Q_ASSERT(d->mCurrentInvariantHash->value(invariant->d->modelIndexRow()) == invariant);
return invariant->d->modelIndexRow(); // this invariant was emitted very recently and isn't affected by any change
}
// If RowShift elements weren't removed from the list then
// we should have mCurrentShiftSerial items in the list.
// But RowShifts ARE removed sequentially from the beginning of the list
// as the invariants are updated in the user's data.
// We are making sure that if a RowShift belonging to a certain
// serial is removed from the list then there are no more
// ModelInvariantIndexinstances with that (or a lower) serial around.
// Thus invariantShiftSerial is >= mRemovedShiftCount.
// Example:
// Initial state, no shifts, current serial 0, removed shifts 0
// Emit ModelInvariantIndexfor model index row 6, with serial 0.
// User asks for model index row of invariant that has row index 10 and serial 0.
// The serial is equal to the current serial and we return the row index unchanged.
// A row arrives at position 4
// We add a RowShift with start index 5 and offset +1
// We increase current serial to 1
// User asks for model index row of invariant that has row index 6 with serial 0.
// We compute the first RowShift index as serial 0 - removed 0 = 0
// We apply the row shifts starting at that index.
// That is, since the requested row index is 6 >= 5
// We apply +1 shift and return row index 7 serial 1
// User asks for model index row of invariant that has row index 7 with serial 1
// The serial is equal to the current serial and we return the row index unchanged still with serial 1
// We update all the invariants in the user's data so that
// there are no more invariants with serial 0.
// We remove the RowShift and increase removed shift count to 1
// User asks for model index row of invariant that has row index 7
// The ModelInvariantIndex MUST have at least serial 1 because of the removal step above.
// The serial is equal to the current serial and we return the row index unchanged still with serial 1
// A row arrives at position 2
// We add a RowShift with start index 3 and offset +1
// We increase current serial to 2
// User asks for model index row of invariant that has row index 7 with serial 1.
// We compute the first RowShift index as serial 1 - removed 1 = 0
// We apply the row shifts starting at that index.
// That is, since the requested row index is 7 >= 3
// We apply +1 shift and return row index 8 serial 2
// User asks for model index row of invariant that has row index 8 and serial 2
// The serial is equal to the current serial and we return the row index unchanged still with serial 2
// Etc...
// So if we can trust that the user doesn't mess up with serials
// and the requested serial is not equal to the current serial
// then we can be 100% sure that mRowShiftList is not null (it contains at least one item).
// The requested serial is surely >= than mRemovedShiftCount too.
// To find the starting index of the RowShifts that apply to this
// serial we need to offset them by the removed rows.
uint invariantShiftIndex = invariant->d->rowMapperSerial() - d->mRemovedShiftCount;
Q_ASSERT(d->mRowShiftList);
// For the reasoning above invariantShiftIndex is surely < than mRowShiftList.count()
const uint count = static_cast< uint >(d->mRowShiftList->count());
Q_ASSERT(invariantShiftIndex < count);
int modelIndexRow = invariant->d->modelIndexRow();
// apply shifts
for (uint idx = invariantShiftIndex; idx < count; idx++) {
RowShift *shift = d->mRowShiftList->at(idx);
if (modelIndexRow >= shift->mMinimumRowIndex) {
modelIndexRow += shift->mShift;
}
}
// Update the invariant on-the-fly too...
d->updateModelInvariantIndex(modelIndexRow, invariant);
return modelIndexRow;
}
void ModelInvariantRowMapper::createModelInvariantIndex(int modelIndexRow, ModelInvariantIndex *invariantToFill)
{
// The user is athemeg for the invariant of the item that is at the CURRENT modelIndexRow.
Q_ASSERT(invariantToFill->d->rowMapper() == nullptr);
// Plain new invariant. Fill it and add to the current hash.
invariantToFill->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, d->mCurrentShiftSerial);
invariantToFill->d->setRowMapper(this);
Q_ASSERT(!d->mCurrentInvariantHash->contains(modelIndexRow));
d->mCurrentInvariantHash->insert(modelIndexRow, invariantToFill);
}
ModelInvariantIndex *ModelInvariantRowMapper::modelIndexRowToModelInvariantIndex(int modelIndexRow)
{
return d->modelIndexRowToModelInvariantIndexInternal(modelIndexRow, false);
}
QList< ModelInvariantIndex * > *ModelInvariantRowMapper::modelIndexRowRangeToModelInvariantIndexList(int startIndexRow, int count)
{
if (!d->mRowShiftList) {
if (d->mCurrentInvariantHash->isEmpty()) {
return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
}
}
// Find the invariants in range.
// It's somewhat impossible to split this in chunks.
auto invariantList = new QList< ModelInvariantIndex * >();
const int end = startIndexRow + count;
for (int idx = startIndexRow; idx < end; idx++) {
ModelInvariantIndex *invariant = d->modelIndexRowToModelInvariantIndexInternal(idx, true);
if (invariant) {
invariantList->append(invariant);
}
}
if (invariantList->isEmpty()) {
delete invariantList;
return nullptr;
}
return invariantList;
}
void ModelInvariantRowMapper::modelRowsInserted(int modelIndexRowPosition, int count)
{
// Some rows were added to the model at modelIndexRowPosition.
// FIXME: If rows are added at the end then we don't need any mapping.
// The fact is that we don't know which is the model's end...
// But maybe we can consider the end being the greatest row
// index emitted until now...
if (!d->mRowShiftList) {
if (d->mCurrentInvariantHash->isEmpty()) {
return; // no invariants emitted, even if rows are changed, no invariant is affected.
}
// some invariants might be affected
d->mRowShiftList = new QList< RowShift * >();
}
RowShift *shift;
if (d->mCurrentInvariantHash->isEmpty()) {
// No invariants updated (all existing are outdated)
Q_ASSERT(d->mRowShiftList->count() > 0); // must be true since it's not null
// Check if we can attach to the last existing shift (very common for consecutive row additions)
shift = d->mRowShiftList->at(d->mRowShiftList->count() - 1);
Q_ASSERT(shift);
if (shift->mShift > 0) { // the shift was positive (addition)
if ((shift->mMinimumRowIndex + shift->mShift) == modelIndexRowPosition) {
// Inserting contiguous blocks of rows, just extend this shift
shift->mShift += count;
Q_ASSERT(d->mUpdateTimer->isActive());
return;
}
}
}
// FIXME: If we have few items, we can just shift the indexes now.
shift = new RowShift(modelIndexRowPosition, count, d->mCurrentInvariantHash);
d->mRowShiftList->append(shift);
d->mCurrentShiftSerial++;
d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
if (d->mRowShiftList->count() > 7) { // 7 is heuristic
// We start loosing performance as the stack is growing too much.
// Start updating NOW and hope we can get it in few sweeps.
if (d->mUpdateTimer->isActive()) {
d->mUpdateTimer->stop();
}
d->slotPerformLazyUpdate();
} else {
// Make sure we'll get a lazy update somewhere in the future
if (!d->mUpdateTimer->isActive()) {
d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
}
}
}
QList< ModelInvariantIndex * > *ModelInvariantRowMapper::modelRowsRemoved(int modelIndexRowPosition, int count)
{
// Some rows were added from the model at modelIndexRowPosition.
// FIXME: If rows are removed from the end, we don't need any mapping.
// The fact is that we don't know which is the model's end...
// But maybe we can consider the end being the greatest row
// index emitted until now...
if (!d->mRowShiftList) {
if (d->mCurrentInvariantHash->isEmpty()) {
return nullptr; // no invariants emitted, even if rows are changed, no invariant is affected.
}
// some invariants might be affected
}
// FIXME: If we have few items, we can just shift the indexes now.
// FIXME: Find a way to "merge" the shifts, if possible
// It OFTEN happens that we remove a lot of items at once (as opposed
// to item addition which is usually an incremental operation).
// FIXME: HUGE PROBLEM
// When the items arent contiguous or are just out of order it's
// impossible to merge the shifts. Deleting many messages
// generates then a very deep delta stack. Since to delete the
// next message you need to traverse the whole stack, this method
// becomes very slow (maybe not as slow as updating all the indexes
// in the general case, but still *slow*).
//
// So one needs to perform updates while rows are being removed
// but that tends to void all your efforts to not update the
// whole list of items every time...
//
// Also deletions don't seem to be asynchronous (or at least
// they eat all the CPU power available for KMail) so the timers
// don't fire and we're not actually processing the model jobs...
//
// It turns out that deleting many items is just slower than
// reloading the view...
// Invalidate the invariants affected by the change
// In most cases it's a relatively small sweep (and it's done once).
// It's somewhat impossible to split this in chunks.
auto deadInvariants = new QList< ModelInvariantIndex * >();
const int end = modelIndexRowPosition + count;
for (int idx = modelIndexRowPosition; idx < end; idx++) {
// FIXME: One could optimize this by joining the retrieval and destruction functions
// that is by making a special indexDead( int modelIndex )..
ModelInvariantIndex *dyingInvariant = d->modelIndexRowToModelInvariantIndexInternal(idx, false);
if (dyingInvariant) {
d->indexDead(dyingInvariant); // will remove from this mapper hashes
dyingInvariant->d->setRowMapper(nullptr); // invalidate!
deadInvariants->append(dyingInvariant);
} else {
// got no dying invariant
qCWarning(MESSAGELIST_LOG) << "Could not find invariant to invalidate at current row " << idx;
}
}
if (!d->mRowShiftList) {
// have no pending shifts, look if we are keeping other invariants
if (d->mCurrentInvariantHash->isEmpty()) {
// no more invariants in this mapper, even if rows are changed, no invariant is affected.
if (deadInvariants->isEmpty()) {
// should never happen, but well...
delete deadInvariants;
return nullptr;
}
return deadInvariants;
}
// still have some invariants inside, must add a shift for them
d->mRowShiftList = new QList< RowShift * >();
} // else already have shifts
// add a shift for this row removal
RowShift *shift = new RowShift(modelIndexRowPosition + count, -count, d->mCurrentInvariantHash);
d->mRowShiftList->append(shift);
d->mCurrentShiftSerial++;
d->mCurrentInvariantHash = new QHash< int, ModelInvariantIndex * >();
// trigger updates
if (d->mRowShiftList->count() > 7) { // 7 is heuristic
// We start loosing performance as the stack is growing too much.
// Start updating NOW and hope we can get it in few sweeps.
if (d->mUpdateTimer->isActive()) {
d->mUpdateTimer->stop();
}
d->slotPerformLazyUpdate();
} else {
// Make sure we'll get a lazy update somewhere in the future
if (!d->mUpdateTimer->isActive()) {
d->mUpdateTimer->start(d->mLazyUpdateIdleInterval);
}
}
if (deadInvariants->isEmpty()) {
// should never happen, but well...
delete deadInvariants;
return nullptr;
}
return deadInvariants;
}
void ModelInvariantRowMapper::modelReset()
{
// FIXME: optimize this (it probably can be optimized by providing a more complex user interface)
QHash< int, ModelInvariantIndex * >::ConstIterator end(d->mCurrentInvariantHash->constEnd());
for (const auto idx : qAsConst(*d->mCurrentInvariantHash)) {
idx->d->setRowMapper(nullptr);
}
d->mCurrentInvariantHash->clear();
if (d->mRowShiftList) {
while (!d->mRowShiftList->isEmpty()) {
delete d->mRowShiftList->takeFirst();
}
delete d->mRowShiftList;
d->mRowShiftList = nullptr;
}
d->mCurrentShiftSerial = 0;
d->mRemovedShiftCount = 0;
}
void ModelInvariantRowMapperPrivate::slotPerformLazyUpdate()
{
// The drawback here is that when one row is removed from the middle (say position 500 of 1000)
// then we require ALL the items to be updated...but:
//
// - We can do it very lazily in the background
// - Optimizing this would mean to ALSO keep the indexes in lists or in a large array
// - The list approach would require to keep the indexes sorted
// so it would cost at least N log (N) / 2.. which is worse than N.
// - We could keep a single (or multiple) array as large as the model
// but then we'd have a large memory consumption and large overhead
// when inserting / removing items from the middle.
//
// So finally I think that the multiple hash approach is a "minimum loss" approach.
QTime startTime = QTime::currentTime();
int curIndex = 0;
while (mRowShiftList) {
// Have at least one row shift
uint count = static_cast< uint >(mRowShiftList->count());
// Grab it
RowShift *shift = mRowShiftList->at(0);
// and update the invariants that belong to it
auto it = shift->mInvariantHash->begin();
auto end = shift->mInvariantHash->end();
while (it != end) {
ModelInvariantIndex *invariant = *it;
it = shift->mInvariantHash->erase(it);
// apply shifts
int modelIndexRow = invariant->d->modelIndexRow();
for (uint idx = 0; idx < count; ++idx) {
RowShift *thatShift = mRowShiftList->at(idx);
if (modelIndexRow >= thatShift->mMinimumRowIndex) {
modelIndexRow += thatShift->mShift;
}
}
// update and make it belong to the current serial
invariant->d->setModelIndexRowAndRowMapperSerial(modelIndexRow, mCurrentShiftSerial);
mCurrentInvariantHash->insert(modelIndexRow, invariant);
// once in a while check if we ran out of time
if ((curIndex % 15) == 0) { // 15 is heuristic
int elapsed = startTime.msecsTo(QTime::currentTime());
if ((elapsed > mLazyUpdateChunkInterval) || (elapsed < 0)) {
// interrupt
//qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
mUpdateTimer->start(mLazyUpdateIdleInterval);
return;
}
}
curIndex++;
}
// no more invariants with serial <= invariantToFill->rowMapperSerial()
killFirstRowShift();
}
//qCDebug(MESSAGELIST_LOG) << "Lazy update fixed " << curIndex << " invariants ";
// if we're here then no more work needs to be done.
}
#include "moc_modelinvariantrowmapper.cpp"
diff --git a/messagelist/src/core/optionset.cpp b/messagelist/src/core/optionset.cpp
index 559d63f6..513f7650 100644
--- a/messagelist/src/core/optionset.cpp
+++ b/messagelist/src/core/optionset.cpp
@@ -1,142 +1,142 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/optionset.h"
#include <time.h> // for ::time( time_t * )
#include <QDataStream>
static const int gOptionSetInitialMarker = 0xcafe; // don't change
static const int gOptionSetFinalMarker = 0xbabe; // don't change
static const int gOptionSetWithReadOnLyModeVersion = 0x1002;
using namespace MessageList::Core;
OptionSet::OptionSet()
: mReadOnly(false)
{
generateUniqueId();
}
OptionSet::OptionSet(const OptionSet &set)
: mId(set.mId)
, mName(set.mName)
, mDescription(set.mDescription)
, mReadOnly(set.mReadOnly)
{
}
OptionSet::OptionSet(const QString &name, const QString &description, bool readOnly)
: mName(name)
, mDescription(description)
, mReadOnly(readOnly)
{
generateUniqueId();
}
OptionSet::~OptionSet()
{
}
void OptionSet::generateUniqueId()
{
static int nextUniqueId = 0;
nextUniqueId++;
- mId = QStringLiteral("%1-%2").arg((unsigned int)::time(0)).arg(nextUniqueId);
+ mId = QStringLiteral("%1-%2").arg((unsigned int)::time(nullptr)).arg(nextUniqueId);
}
QString OptionSet::saveToString() const
{
QByteArray raw;
{
QDataStream s(&raw, QIODevice::WriteOnly);
s << gOptionSetInitialMarker;
s << gOptionSetWithReadOnLyModeVersion;
s << mId;
s << mName;
s << mDescription;
s << mReadOnly;
save(s);
s << gOptionSetFinalMarker;
}
return QString::fromLatin1(raw.toHex());
}
bool OptionSet::loadFromString(const QString &data)
{
QByteArray raw = QByteArray::fromHex(data.toLatin1());
QDataStream s(&raw, QIODevice::ReadOnly);
int marker;
s >> marker;
if (marker != gOptionSetInitialMarker) {
return false; // invalid configuration
}
int currentVersion;
s >> currentVersion;
if (currentVersion > gOptionSetWithReadOnLyModeVersion) {
return false; // invalid configuration
}
s >> mId;
if (mId.isEmpty()) {
return false; // invalid configuration
}
s >> mName;
if (mName.isEmpty()) {
return false; // invalid configuration
}
s >> mDescription;
bool readOnly = false;
if (currentVersion == gOptionSetWithReadOnLyModeVersion) {
s >> readOnly;
}
mReadOnly = readOnly;
if (!load(s)) {
return false; // invalid configuration
}
s >> marker;
if (marker != gOptionSetFinalMarker) {
return false; // invalid configuration
}
return true;
}
diff --git a/messagelist/src/core/sortorder.h b/messagelist/src/core/sortorder.h
index a812eae3..10be203d 100644
--- a/messagelist/src/core/sortorder.h
+++ b/messagelist/src/core/sortorder.h
@@ -1,223 +1,227 @@
/******************************************************************************
*
* Copyright 2009 Thomas McGuire <mcguire@kde.org>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_SORTORDER_H
#define MESSAGELIST_CORE_SORTORDER_H
#include <core/aggregation.h>
#include <KConfigGroup>
namespace MessageList {
namespace Core {
/**
* A class which holds information about sorting, e.g. the sorting and sort direction
* of messages and groups.
*/
class SortOrder
{
Q_GADGET
Q_ENUMS(GroupSorting)
Q_ENUMS(SortDirection)
Q_ENUMS(MessageSorting)
public:
/**
* How to sort the groups
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum GroupSorting {
NoGroupSorting, ///< Don't sort the groups at all, add them as they come in
SortGroupsByDateTime, ///< Sort groups by date/time of the group
SortGroupsByDateTimeOfMostRecent, ///< Sort groups by date/time of the most recent message
SortGroupsBySenderOrReceiver, ///< Sort groups by sender or receiver (makes sense only with GroupBySenderOrReceiver)
SortGroupsBySender, ///< Sort groups by sender (makes sense only with GroupBySender)
SortGroupsByReceiver ///< Sort groups by receiver (makes sense only with GroupByReceiver)
// Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
/**
* The "generic" sort direction: used for groups and for messages
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum SortDirection {
Ascending,
Descending
};
/**
* The available message sorting options.
* If you add values here please look at the implementations of the enumerate* functions
* and add appropriate descriptors.
*/
enum MessageSorting {
NoMessageSorting, ///< Don't sort the messages at all
SortMessagesByDateTime, ///< Sort the messages by date and time
SortMessagesByDateTimeOfMostRecent, ///< Sort the messages by date and time of the most recent message in subtree
SortMessagesBySenderOrReceiver, ///< Sort the messages by sender or receiver
SortMessagesBySender, ///< Sort the messages by sender
SortMessagesByReceiver, ///< Sort the messages by receiver
SortMessagesBySubject, ///< Sort the messages by subject
SortMessagesBySize, ///< Sort the messages by size
SortMessagesByActionItemStatus, ///< Sort the messages by the "Action Item" flag of status
SortMessagesByUnreadStatus, ///< Sort the messages by the "Unread" flags of status
SortMessagesByImportantStatus, /// Sort the messages By "Important" flags of status
SortMessagesByAttachmentStatus /// Sort the messages By "Attachment" flags of status
// Warning: Never add enum entries in the middle: always add them at the end (numeric values are stored in configuration)
};
SortOrder();
/**
* Returns the GroupSorting
*/
GroupSorting groupSorting() const;
/**
* Sets the GroupSorting option.
* This may not have any effect, depending on the Aggregation this sort order
* is used in.
*/
void setGroupSorting(GroupSorting gs);
/**
* Returns the current group SortDirection.
*/
SortDirection groupSortDirection() const;
/**
* Sets the SortDirection for the groups.
* Note that this option has no meaning if group sorting is set to NoGroupSorting.
*/
void setGroupSortDirection(SortDirection groupSortDirection);
/**
* Returns the current message sorting option
*/
MessageSorting messageSorting() const;
/**
* Sets the current message sorting option
*/
void setMessageSorting(MessageSorting ms);
/**
* Returns the current message SortDirection.
*/
SortDirection messageSortDirection() const;
/**
* Sets the SortDirection for the message.
* Note that this option has no meaning if message sorting is set to NoMessageSorting.
*/
void setMessageSortDirection(SortDirection messageSortDirection);
/**
* Enumerates the message sorting options compatible with the specified Threading setting.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateMessageSortingOptions(Aggregation::Threading t);
/**
* Enumerates the available message sorting directions for the specified MessageSorting option.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateMessageSortDirectionOptions(MessageSorting ms);
/**
* Enumerates the group sorting options compatible with the specified Grouping.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateGroupSortingOptions(Aggregation::Grouping g);
/**
* Enumerates the group sort direction options compatible with the specified Grouping and GroupSorting.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
* If the returned list is empty then the value of the option is meaningless in the current context.
*/
static QList< QPair< QString, int > > enumerateGroupSortDirectionOptions(Aggregation::Grouping g, GroupSorting groupSorting);
/**
* Checks if this sort order can be used in combination with the given aggregation.
* Some combinations are not valid, for example the message sorting
* "most recent in subtree" with a non-threaded aggregation.
*/
bool validForAggregation(const Aggregation *aggregation) const;
/**
* Returns the default sort order for the given aggregation.
+ * @param aggregation the given aggregation.
* @param oldSortOrder the previously used sort order. If possible, the new sort order
* will be based on that old sort order, i.e. the message sorting and
* message sort direction is adopted.
*/
static SortOrder defaultForAggregation(const Aggregation *aggregation, const SortOrder &oldSortOrder);
/**
* Returns true if the ms parameter specifies a valid MessageSorting option.
*/
static bool isValidMessageSorting(SortOrder::MessageSorting ms);
/**
* Reads the sort order from a config group.
+ * @param conf the config group.
* @param storageId the id of the folder, which is prepended to each key. This way,
* more than one sort order can be saved in the same config group
* @param storageUsesPrivateSortOrder this boolean will be true if the sort order
* is private for that folder, and false if the
* sort order is the global sort order.
*/
void readConfig(KConfigGroup &conf, const QString &storageId, bool *storageUsesPrivateSortOrder);
/**
* Writes the sort order to a config group.
+ * @param conf the config group.
+ * @param storageId the storage identifier.
* @param storageUsesPrivateSortOrder if false, this sort order will be saved as the
* global sort order.
* @sa readConfig
*/
void writeConfig(KConfigGroup &conf, const QString &storageId, bool storageUsesPrivateSortOrder) const;
private:
// Helper function to convert an enum value to a string and back
static const QString nameForSortDirection(SortDirection sortDirection);
static const QString nameForMessageSorting(MessageSorting messageSorting);
static const QString nameForGroupSorting(GroupSorting groupSorting);
static SortDirection sortDirectionForName(const QString &name);
static MessageSorting messageSortingForName(const QString &name);
static GroupSorting groupSortingForName(const QString &name);
bool readConfigHelper(KConfigGroup &conf, const QString &id);
MessageSorting mMessageSorting;
SortDirection mMessageSortDirection;
GroupSorting mGroupSorting;
SortDirection mGroupSortDirection;
};
} // namespace Core
} // namespace MessageList
#endif // MESSAGELIST_CORE_SORTORDER_H
diff --git a/messagelist/src/core/storagemodelbase.h b/messagelist/src/core/storagemodelbase.h
index 3230ebaa..98fe832a 100644
--- a/messagelist/src/core/storagemodelbase.h
+++ b/messagelist/src/core/storagemodelbase.h
@@ -1,125 +1,125 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_STORAGEMODELBASE_H
#define MESSAGELIST_CORE_STORAGEMODELBASE_H
#include <QAbstractItemModel>
namespace Akonadi {
class MessageStatus;
}
namespace MessageList {
namespace Core {
class MessageItem;
/**
* The QAbstractItemModel based interface that you need
* to provide for your storage to work with MessageList.
*/
class StorageModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit StorageModel(QObject *parent = nullptr);
~StorageModel();
/**
* Returns an unique id for this Storage collection.
* FIXME: This could be embedded in "name()" ?
*/
virtual QString id() const = 0;
/**
* Returns the unique id of the last selected message for this StorageModel.
* Returns 0 if this value isn't stored in the configuration.
*/
unsigned long preSelectedMessage() const;
/**
* Stores in the unique id of the last selected message for the specified StorageModel.
* The uniqueIdOfMessage may be 0 (which means that no selection shall be stored in the configuration).
*/
void savePreSelectedMessage(unsigned long uniqueIdOfMessage);
/**
* Returns true if this StorageModel (folder) contains outbound messages and false otherwise.
*/
virtual bool containsOutboundMessages() const = 0;
/**
* Returns (a guess for) the number of unread messages: must be pessimistic (i.e. if you
* have no idea just return rowCount(), which is also what the default implementation does).
* This must be (and is) queried ONLY when the folder is first opened. It doesn't actually need
* to keep the number of messages in sync as they later arrive to the storage.
*/
virtual int initialUnreadRowCountGuess() const;
/**
* This method should use the inner model implementation to fill in the
* base data for the specified MessageItem from the underlying storage slot at
- * the specified row index. Must return true if the data fetch was succesfull
+ * the specified row index. Must return true if the data fetch was successful
* and false otherwise. For base data we intend: subject, sender, receiver,
* senderOrReceiver, size, date, encryption state, signature state and status.
* If bUseReceiver is true then the "senderOrReceiver"
* field must be set to the receiver, otherwise it must be set to the sender.
*/
virtual bool initializeMessageItem(MessageItem *it, int row, bool bUseReceiver) const = 0;
enum ThreadingDataSubset {
PerfectThreadingOnly, ///< Only the data for messageIdMD5 and inReplyToMD5 is needed
PerfectThreadingPlusReferences, ///< messageIdMD5, inReplyToMD5, referencesIdMD5
PerfectThreadingReferencesAndSubject ///< All of the above plus subject stuff
};
/**
* This method should use the inner model implementation to fill in the specified subset of
* threading data for the specified MessageItem from the underlying storage slot at
* the specified row index.
*/
virtual void fillMessageItemThreadingData(MessageItem *mi, int row, ThreadingDataSubset subset) const = 0;
/**
* This method should use the inner model implementation to re-fill the date, the status,
* the encryption state, the signature state and eventually update the min/max dates
* for the specified MessageItem from the underlying storage slot at the specified row index.
*/
virtual void updateMessageItemData(MessageItem *mi, int row) const = 0;
/**
* This method should use the inner model implementation to associate the new status
* to the specified message item. The new status should be stored (but doesn't need
* to be set as mi->status() itself as the caller is responsable for this).
*/
virtual void setMessageItemStatus(MessageItem *mi, int row, Akonadi::MessageStatus status) = 0;
/**
* The implementation-specific mime data for this list of items.
* Called when the user initiates a drag from the messagelist.
*/
virtual QMimeData *mimeData(const QList< MessageItem * > &) const = 0;
using QAbstractItemModel::mimeData;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_STORAGEMODEL_H
diff --git a/messagelist/src/core/theme.cpp b/messagelist/src/core/theme.cpp
index 7f5223ef..1116207e 100644
--- a/messagelist/src/core/theme.cpp
+++ b/messagelist/src/core/theme.cpp
@@ -1,1287 +1,1288 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/theme.h"
#include <QDataStream>
#include <QPixmap>
#include <QIcon>
#include <QStandardPaths>
#include <QApplication>
#include <KLocalizedString>
#include "messagelist_debug.h"
using namespace MessageList::Core;
//
// Theme versioning
//
-// The themes simply have a DWORD version number attacched.
+// The themes simply have a DWORD version number attached.
// The earliest version we're able to load is 0x1013.
//
// Theme revision history:
//
// Version Date introduced Description
// --------------------------------------------------------------------------------------------------------------
// 0x1013 08.11.2008 Initial theme version, introduced when this piece of code has been moved into trunk.
// 0x1014 12.11.2008 Added runtime column data: width and column visibility
// 0x1015 03.03.2009 Added icon size
// 0x1016 08.03.2009 Added support for sorting by New/Unread status
// 0x1017 16.08.2009 Added support for column icon
// 0x1018 17.01.2010 Added support for annotation icon
// 0x1019 13.07.2010 Added support for invitation icon
//
static const int gThemeCurrentVersion = 0x1019; // increase if you add new fields or change the meaning of some
// you don't need to change the values below, but you might want to add new ones
static const int gThemeMinimumSupportedVersion = 0x1013;
static const int gThemeMinimumVersionWithColumnRuntimeData = 0x1014;
static const int gThemeMinimumVersionWithIconSizeField = 0x1015;
static const int gThemeMinimumVersionWithSortingByUnreadStatusAllowed = 0x1016;
static const int gThemeMinimumVersionWithColumnIcon = 0x1017;
static const int gThemeMinimumVersionWithAnnotationIcon = 0x1018;
static const int gThemeMinimumVersionWithInvitationIcon = 0x1019;
// the default icon size
static const int gThemeDefaultIconSize = 16;
Theme::ContentItem::ContentItem(Type type)
: mType(type)
, mFlags(0)
{
}
Theme::ContentItem::ContentItem(const ContentItem &src)
: mType(src.mType)
, mFlags(src.mFlags)
, mCustomColor(src.mCustomColor)
{
}
Theme::ContentItem::Type Theme::ContentItem::type() const
{
return mType;
}
bool Theme::ContentItem::canBeDisabled() const
{
return (static_cast< int >(mType) & CanBeDisabled) != 0;
}
bool Theme::ContentItem::canUseCustomColor() const
{
return (static_cast< int >(mType) & CanUseCustomColor) != 0;
}
bool Theme::ContentItem::displaysText() const
{
return (static_cast< int >(mType) & DisplaysText) != 0;
}
bool Theme::ContentItem::displaysLongText() const
{
return (static_cast< int >(mType) & LongText) != 0;
}
bool Theme::ContentItem::isIcon() const
{
return (static_cast< int >(mType) & IsIcon) != 0;
}
bool Theme::ContentItem::isClickable() const
{
return (static_cast< int >(mType) & IsClickable) != 0;
}
bool Theme::ContentItem::isSpacer() const
{
return (static_cast< int >(mType) & IsSpacer) != 0;
}
QString Theme::ContentItem::description(Type type)
{
switch (type) {
case Subject:
return i18nc("Description of Type Subject", "Subject");
break;
case Date:
return i18nc("Description of Type Date", "Date");
break;
case SenderOrReceiver:
return i18n("Sender/Receiver");
break;
case Sender:
return i18nc("Description of Type Sender", "Sender");
break;
case Receiver:
return i18nc("Description of Type Receiver", "Receiver");
break;
case Size:
return i18nc("Description of Type Size", "Size");
break;
case ReadStateIcon:
return i18n("Unread/Read Icon");
break;
case AttachmentStateIcon:
return i18n("Attachment Icon");
break;
case RepliedStateIcon:
return i18n("Replied/Forwarded Icon");
break;
case CombinedReadRepliedStateIcon:
return i18n("Combined New/Unread/Read/Replied/Forwarded Icon");
break;
case ActionItemStateIcon:
return i18n("Action Item Icon");
break;
case ImportantStateIcon:
return i18n("Important Icon");
break;
case GroupHeaderLabel:
return i18n("Group Header Label");
break;
case SpamHamStateIcon:
return i18n("Spam/Ham Icon");
break;
case WatchedIgnoredStateIcon:
return i18n("Watched/Ignored Icon");
break;
case ExpandedStateIcon:
return i18n("Group Header Expand/Collapse Icon");
break;
case EncryptionStateIcon:
return i18n("Encryption State Icon");
break;
case SignatureStateIcon:
return i18n("Signature State Icon");
break;
case VerticalLine:
return i18n("Vertical Separation Line");
break;
case HorizontalSpacer:
return i18n("Horizontal Spacer");
break;
case MostRecentDate:
return i18n("Max Date");
break;
case TagList:
return i18n("Message Tags");
break;
case AnnotationIcon:
return i18n("Note Icon");
case InvitationIcon:
return i18n("Invitation Icon");
default:
return i18nc("Description for an Unknown Type", "Unknown");
break;
}
}
bool Theme::ContentItem::useCustomColor() const
{
return mFlags & UseCustomColor;
}
void Theme::ContentItem::setUseCustomColor(bool useCustomColor)
{
if (useCustomColor) {
mFlags |= UseCustomColor;
} else {
mFlags &= ~UseCustomColor;
}
}
bool Theme::ContentItem::isBold() const
{
return mFlags & IsBold;
}
void Theme::ContentItem::setBold(bool isBold)
{
if (isBold) {
mFlags |= IsBold;
} else {
mFlags &= ~IsBold;
}
}
bool Theme::ContentItem::isItalic() const
{
return mFlags & IsItalic;
}
void Theme::ContentItem::setItalic(bool isItalic)
{
if (isItalic) {
mFlags |= IsItalic;
} else {
mFlags &= ~IsItalic;
}
}
bool Theme::ContentItem::hideWhenDisabled() const
{
return mFlags & HideWhenDisabled;
}
void Theme::ContentItem::setHideWhenDisabled(bool hideWhenDisabled)
{
if (hideWhenDisabled) {
mFlags |= HideWhenDisabled;
} else {
mFlags &= ~HideWhenDisabled;
}
}
bool Theme::ContentItem::softenByBlendingWhenDisabled() const
{
return mFlags & SoftenByBlendingWhenDisabled;
}
void Theme::ContentItem::setSoftenByBlendingWhenDisabled(bool softenByBlendingWhenDisabled)
{
if (softenByBlendingWhenDisabled) {
mFlags |= SoftenByBlendingWhenDisabled;
} else {
mFlags &= ~SoftenByBlendingWhenDisabled;
}
}
bool Theme::ContentItem::softenByBlending() const
{
return mFlags & SoftenByBlending;
}
void Theme::ContentItem::setSoftenByBlending(bool softenByBlending)
{
if (softenByBlending) {
mFlags |= SoftenByBlending;
} else {
mFlags &= ~SoftenByBlending;
}
}
const QColor &Theme::ContentItem::customColor() const
{
return mCustomColor;
}
void Theme::ContentItem::setCustomColor(const QColor &clr)
{
mCustomColor = clr;
}
bool Theme::ContentItem::applicableToMessageItems(Type type)
{
return static_cast< int >(type) & ApplicableToMessageItems;
}
bool Theme::ContentItem::applicableToGroupHeaderItems(Type type)
{
return static_cast< int >(type) & ApplicableToGroupHeaderItems;
}
void Theme::ContentItem::save(QDataStream &stream) const
{
stream << (int)mType;
stream << mFlags;
stream << mCustomColor;
}
bool Theme::ContentItem::load(QDataStream &stream, int /*themeVersion*/)
{
int val;
stream >> val;
mType = static_cast< Type >(val);
switch (mType) {
case Subject:
case Date:
case SenderOrReceiver:
case Sender:
case Receiver:
case Size:
case ReadStateIcon:
case AttachmentStateIcon:
case RepliedStateIcon:
case GroupHeaderLabel:
case ActionItemStateIcon:
case ImportantStateIcon:
case SpamHamStateIcon:
case WatchedIgnoredStateIcon:
case ExpandedStateIcon:
case EncryptionStateIcon:
case SignatureStateIcon:
case VerticalLine:
case HorizontalSpacer:
case MostRecentDate:
case CombinedReadRepliedStateIcon:
case TagList:
case AnnotationIcon:
case InvitationIcon:
// ok
break;
default:
qCDebug(MESSAGELIST_LOG) << "Invalid content item type";
return false; // b0rken
break;
}
stream >> mFlags;
stream >> mCustomColor;
if (mFlags & UseCustomColor) {
if (!mCustomColor.isValid()) {
mFlags &= ~UseCustomColor;
}
}
return true;
}
Theme::Row::Row()
{
}
Theme::Row::Row(const Row &src)
{
for (const auto ci : qAsConst(src.mLeftItems)) {
addLeftItem(new ContentItem(*ci));
}
for (const auto ci : qAsConst(src.mRightItems)) {
addRightItem(new ContentItem(*ci));
}
}
Theme::Row::~Row()
{
removeAllLeftItems();
removeAllRightItems();
}
void Theme::Row::removeAllLeftItems()
{
while (!mLeftItems.isEmpty()) {
delete mLeftItems.takeFirst();
}
}
void Theme::Row::addLeftItem(Theme::ContentItem *item)
{
mLeftItems.append(item);
}
void Theme::Row::removeAllRightItems()
{
while (!mRightItems.isEmpty()) {
delete mRightItems.takeFirst();
}
}
void Theme::Row::addRightItem(Theme::ContentItem *item)
{
mRightItems.append(item);
}
void Theme::Row::insertLeftItem(int idx, ContentItem *item)
{
if (idx >= mLeftItems.count()) {
mLeftItems.append(item);
return;
}
mLeftItems.insert(idx, item);
}
void Theme::Row::removeLeftItem(Theme::ContentItem *item)
{
mLeftItems.removeAll(item);
}
const QList<Theme::ContentItem *> &Theme::Row::rightItems() const
{
return mRightItems;
}
void Theme::Row::insertRightItem(int idx, ContentItem *item)
{
if (idx >= mRightItems.count()) {
mRightItems.append(item);
return;
}
mRightItems.insert(idx, item);
}
void Theme::Row::removeRightItem(Theme::ContentItem *item)
{
mRightItems.removeAll(item);
}
bool Theme::Row::containsTextItems() const
{
for (const auto ci : qAsConst(mLeftItems)) {
if (ci->displaysText()) {
return true;
}
}
for (const auto ci : qAsConst(mRightItems)) {
if (ci->displaysText()) {
return true;
}
}
return false;
}
void Theme::Row::save(QDataStream &stream) const
{
stream << (int)mLeftItems.count();
int cnt = mLeftItems.count();
for (int i = 0; i < cnt; ++i) {
ContentItem *ci = mLeftItems.at(i);
ci->save(stream);
}
stream << (int)mRightItems.count();
cnt = mRightItems.count();
for (int i = 0; i < cnt; ++i) {
ContentItem *ci = mRightItems.at(i);
ci->save(stream);
}
}
bool Theme::Row::LoadContentItem(int val, QDataStream &stream, int themeVersion, bool leftItem)
{
if ((val < 0) || (val > 50)) {
return false; // senseless
}
// FIXME: Remove code duplication here
for (int i = 0; i < val; ++i) {
ContentItem *ci = new ContentItem(ContentItem::Subject); // dummy type
if (!ci->load(stream, themeVersion)) {
qCDebug(MESSAGELIST_LOG) << "Left content item loading failed";
delete ci;
return false;
}
if (leftItem) {
addLeftItem(ci);
} else {
addRightItem(ci);
}
// Add the annotation item next to the attachment icon, so that users upgrading from old
// versions don't manually need to set this.
- // Don't do this for the stand-alone attchment column.
+ // Don't do this for the stand-alone attachment column.
if (ci->type() == ContentItem::AttachmentStateIcon
&& themeVersion < gThemeMinimumVersionWithAnnotationIcon
&& val > 1) {
qCDebug(MESSAGELIST_LOG) << "Old theme version detected, adding annotation item next to attachment icon.";
ContentItem *annotationItem = new ContentItem(ContentItem::AnnotationIcon);
annotationItem->setHideWhenDisabled(true);
if (leftItem) {
addLeftItem(annotationItem);
} else {
addRightItem(annotationItem);
}
}
// Same as above, for the invitation icon
if (ci->type() == ContentItem::AttachmentStateIcon
&& themeVersion < gThemeMinimumVersionWithInvitationIcon
&& val > 1) {
qCDebug(MESSAGELIST_LOG) << "Old theme version detected, adding invitation item next to attachment icon.";
ContentItem *invitationItem = new ContentItem(ContentItem::InvitationIcon);
invitationItem->setHideWhenDisabled(true);
if (leftItem) {
addLeftItem(invitationItem);
} else {
addRightItem(invitationItem);
}
}
}
return true;
}
const QList<Theme::ContentItem *> &Theme::Row::leftItems() const
{
return mLeftItems;
}
bool Theme::Row::load(QDataStream &stream, int themeVersion)
{
removeAllLeftItems();
removeAllRightItems();
int val;
// left item count
stream >> val;
if (!LoadContentItem(val, stream, themeVersion, true)) {
return false;
}
// right item count
stream >> val;
if (!LoadContentItem(val, stream, themeVersion, false)) {
return false;
}
return true;
}
Theme::Column::SharedRuntimeData::SharedRuntimeData(bool currentlyVisible, double currentWidth)
: mReferences(0)
, mCurrentlyVisible(currentlyVisible)
, mCurrentWidth(currentWidth)
{
}
Theme::Column::SharedRuntimeData::~SharedRuntimeData()
{
}
void Theme::Column::SharedRuntimeData::addReference()
{
mReferences++;
}
bool Theme::Column::SharedRuntimeData::deleteReference()
{
mReferences--;
Q_ASSERT(mReferences >= 0);
return mReferences > 0;
}
int Theme::Column::SharedRuntimeData::referenceCount() const
{
return mReferences;
}
bool Theme::Column::SharedRuntimeData::currentlyVisible() const
{
return mCurrentlyVisible;
}
void Theme::Column::SharedRuntimeData::setCurrentlyVisible(bool visible)
{
mCurrentlyVisible = visible;
}
double Theme::Column::SharedRuntimeData::currentWidth() const
{
return mCurrentWidth;
}
void Theme::Column::SharedRuntimeData::setCurrentWidth(double currentWidth)
{
mCurrentWidth = currentWidth;
}
void Theme::Column::SharedRuntimeData::save(QDataStream &stream) const
{
stream << mCurrentlyVisible;
stream << mCurrentWidth;
}
bool Theme::Column::SharedRuntimeData::load(QDataStream &stream, int /* themeVersion */)
{
stream >> mCurrentlyVisible;
stream >> mCurrentWidth;
if (mCurrentWidth > 10000) {
qCDebug(MESSAGELIST_LOG) << "Theme has insane column width " << mCurrentWidth << " chopping to 100";
mCurrentWidth = 100; // avoid really insane values
}
return mCurrentWidth >= -1;
}
Theme::Column::Column()
: mVisibleByDefault(true)
, mIsSenderOrReceiver(false)
, mMessageSorting(SortOrder::NoMessageSorting)
{
mSharedRuntimeData = new SharedRuntimeData(true, -1);
mSharedRuntimeData->addReference();
}
Theme::Column::Column(const Column &src)
{
mLabel = src.mLabel;
mPixmapName = src.mPixmapName;
mVisibleByDefault = src.mVisibleByDefault;
mIsSenderOrReceiver = src.mIsSenderOrReceiver;
mMessageSorting = src.mMessageSorting;
mSharedRuntimeData = src.mSharedRuntimeData;
mSharedRuntimeData->addReference();
for (const auto row : qAsConst(src.mMessageRows)) {
addMessageRow(new Row(*row));
}
for (const auto row : qAsConst(src.mGroupHeaderRows)) {
addGroupHeaderRow(new Row(*row));
}
}
Theme::Column::~Column()
{
removeAllMessageRows();
removeAllGroupHeaderRows();
if (!(mSharedRuntimeData->deleteReference())) {
delete mSharedRuntimeData;
}
}
const QString &Theme::Column::label() const
{
return mLabel;
}
void Theme::Column::setLabel(const QString &label)
{
mLabel = label;
}
const QString &Theme::Column::pixmapName() const
{
return mPixmapName;
}
void Theme::Column::setPixmapName(const QString &pixmapName)
{
mPixmapName = pixmapName;
}
bool Theme::Column::isSenderOrReceiver() const
{
return mIsSenderOrReceiver;
}
void Theme::Column::setIsSenderOrReceiver(bool sor)
{
mIsSenderOrReceiver = sor;
}
bool Theme::Column::visibleByDefault() const
{
return mVisibleByDefault;
}
void Theme::Column::setVisibleByDefault(bool vbd)
{
mVisibleByDefault = vbd;
}
void Theme::Column::detach()
{
if (mSharedRuntimeData->referenceCount() < 2) {
return; // nothing to detach
}
mSharedRuntimeData->deleteReference();
mSharedRuntimeData = new SharedRuntimeData(mVisibleByDefault, -1);
mSharedRuntimeData->addReference();
}
SortOrder::MessageSorting Theme::Column::messageSorting() const
{
return mMessageSorting;
}
void Theme::Column::setMessageSorting(SortOrder::MessageSorting ms)
{
mMessageSorting = ms;
}
bool Theme::Column::currentlyVisible() const
{
return mSharedRuntimeData->currentlyVisible();
}
void Theme::Column::setCurrentlyVisible(bool currentlyVisible)
{
mSharedRuntimeData->setCurrentlyVisible(currentlyVisible);
}
double Theme::Column::currentWidth() const
{
return mSharedRuntimeData->currentWidth();
}
void Theme::Column::setCurrentWidth(double currentWidth)
{
mSharedRuntimeData->setCurrentWidth(currentWidth);
}
const QList<Theme::Row *> &Theme::Column::messageRows() const
{
return mMessageRows;
}
void Theme::Column::removeAllMessageRows()
{
while (!mMessageRows.isEmpty()) {
delete mMessageRows.takeFirst();
}
}
void Theme::Column::addMessageRow(Theme::Row *row)
{
mMessageRows.append(row);
}
void Theme::Column::removeAllGroupHeaderRows()
{
while (!mGroupHeaderRows.isEmpty()) {
delete mGroupHeaderRows.takeFirst();
}
}
void Theme::Column::addGroupHeaderRow(Theme::Row *row)
{
mGroupHeaderRows.append(row);
}
void Theme::Column::insertMessageRow(int idx, Row *row)
{
if (idx >= mMessageRows.count()) {
mMessageRows.append(row);
return;
}
mMessageRows.insert(idx, row);
}
void Theme::Column::removeMessageRow(Theme::Row *row)
{
mMessageRows.removeAll(row);
}
const QList<Theme::Row *> &Theme::Column::groupHeaderRows() const
{
return mGroupHeaderRows;
}
void Theme::Column::insertGroupHeaderRow(int idx, Row *row)
{
if (idx >= mGroupHeaderRows.count()) {
mGroupHeaderRows.append(row);
return;
}
mGroupHeaderRows.insert(idx, row);
}
void Theme::Column::removeGroupHeaderRow(Theme::Row *row)
{
mGroupHeaderRows.removeAll(row);
}
bool Theme::Column::containsTextItems() const
{
for (const auto row : qAsConst(mMessageRows)) {
if (row->containsTextItems()) {
return true;
}
}
for (const auto row : qAsConst(mGroupHeaderRows)) {
if (row->containsTextItems()) {
return true;
}
}
return false;
}
void Theme::Column::save(QDataStream &stream) const
{
stream << mLabel;
stream << mPixmapName;
stream << mVisibleByDefault;
stream << mIsSenderOrReceiver;
stream << static_cast<int>(mMessageSorting);
stream << static_cast<int>(mGroupHeaderRows.count());
int cnt = mGroupHeaderRows.count();
for (int i = 0; i < cnt; ++i) {
Row *row = mGroupHeaderRows.at(i);
row->save(stream);
}
cnt = mMessageRows.count();
stream << static_cast<int>(cnt);
for (int i = 0; i < cnt; ++i) {
Row *row = mMessageRows.at(i);
row->save(stream);
}
// added in version 0x1014
mSharedRuntimeData->save(stream);
}
bool Theme::Column::load(QDataStream &stream, int themeVersion)
{
removeAllGroupHeaderRows();
removeAllMessageRows();
stream >> mLabel;
if (themeVersion >= gThemeMinimumVersionWithColumnIcon) {
stream >> mPixmapName;
}
stream >> mVisibleByDefault;
stream >> mIsSenderOrReceiver;
int val;
stream >> val;
mMessageSorting = static_cast< SortOrder::MessageSorting >(val);
if (!SortOrder::isValidMessageSorting(mMessageSorting)) {
qCDebug(MESSAGELIST_LOG) << "Invalid message sorting";
return false;
}
if (themeVersion < gThemeMinimumVersionWithSortingByUnreadStatusAllowed) {
// The default "Classic" theme "Unread" column had sorting disabled here.
// We want to be nice to the existing users and automatically set
// the new sorting method for this column (so they don't have to make the
// complex steps to set it by themselves).
// This piece of code isn't strictly required: it's just a niceness :)
if ((mMessageSorting == SortOrder::NoMessageSorting) && (mLabel == i18n("Unread"))) {
mMessageSorting = SortOrder::SortMessagesByUnreadStatus;
}
}
// group header row count
stream >> val;
if ((val < 0) || (val > 50)) {
qCDebug(MESSAGELIST_LOG) << "Invalid group header row count";
return false; // senseless
}
for (int i = 0; i < val; ++i) {
Row *row = new Row();
if (!row->load(stream, themeVersion)) {
qCDebug(MESSAGELIST_LOG) << "Group header row loading failed";
delete row;
return false;
}
addGroupHeaderRow(row);
}
// message row count
stream >> val;
if ((val < 0) || (val > 50)) {
qCDebug(MESSAGELIST_LOG) << "Invalid message row count";
return false; // senseless
}
for (int i = 0; i < val; ++i) {
Row *row = new Row();
if (!row->load(stream, themeVersion)) {
qCDebug(MESSAGELIST_LOG) << "Message row loading failed";
delete row;
return false;
}
addMessageRow(row);
}
if (themeVersion >= gThemeMinimumVersionWithColumnRuntimeData) {
// starting with version 0x1014 we have runtime data too
if (!mSharedRuntimeData->load(stream, themeVersion)) {
qCDebug(MESSAGELIST_LOG) << "Shared runtime data loading failed";
return false;
}
} else {
// assume default shared data
mSharedRuntimeData->setCurrentlyVisible(mVisibleByDefault);
mSharedRuntimeData->setCurrentWidth(-1);
}
return true;
}
Theme::Theme()
: OptionSet()
{
mGroupHeaderBackgroundMode = AutoColor;
mViewHeaderPolicy = ShowHeaderAlways;
mIconSize = gThemeDefaultIconSize;
mGroupHeaderBackgroundStyle = StyledJoinedRect;
}
Theme::Theme(const QString &name, const QString &description, bool readOnly)
: OptionSet(name, description, readOnly)
{
mGroupHeaderBackgroundMode = AutoColor;
mGroupHeaderBackgroundStyle = StyledJoinedRect;
mViewHeaderPolicy = ShowHeaderAlways;
mIconSize = gThemeDefaultIconSize;
}
Theme::Theme(const Theme &src)
: OptionSet(src)
{
mGroupHeaderBackgroundMode = src.mGroupHeaderBackgroundMode;
mGroupHeaderBackgroundColor = src.mGroupHeaderBackgroundColor;
mGroupHeaderBackgroundStyle = src.mGroupHeaderBackgroundStyle;
mViewHeaderPolicy = src.mViewHeaderPolicy;
mIconSize = src.mIconSize;
for (const auto col : qAsConst(src.mColumns)) {
addColumn(new Column(*col));
}
}
Theme::~Theme()
{
+ clearPixmapCache();
removeAllColumns();
}
void Theme::detach()
{
for (const auto col : qAsConst(mColumns)) {
col->detach();
}
}
void Theme::resetColumnState()
{
for (const auto col : qAsConst(mColumns)) {
col->setCurrentlyVisible(col->visibleByDefault());
col->setCurrentWidth(-1);
}
}
void Theme::resetColumnSizes()
{
for (const auto col : qAsConst(mColumns)) {
col->setCurrentWidth(-1);
}
}
const QList<Theme::Column *> &Theme::columns() const
{
return mColumns;
}
Theme::Column *Theme::column(int idx) const
{
return mColumns.count() > idx ? mColumns.at(idx) : nullptr;
}
void Theme::removeAllColumns()
{
while (!mColumns.isEmpty()) {
delete mColumns.takeFirst();
}
}
void Theme::addColumn(Theme::Column *column)
{
mColumns.append(column);
}
void Theme::insertColumn(int idx, Column *column)
{
if (idx >= mColumns.count()) {
mColumns.append(column);
return;
}
mColumns.insert(idx, column);
}
void Theme::removeColumn(Theme::Column *col)
{
mColumns.removeAll(col);
}
Theme::GroupHeaderBackgroundMode Theme::groupHeaderBackgroundMode() const
{
return mGroupHeaderBackgroundMode;
}
void Theme::moveColumn(int idx, int newPosition)
{
if ((newPosition >= mColumns.count()) || newPosition < 0) {
return;
}
mColumns.move(idx, newPosition);
}
void Theme::setGroupHeaderBackgroundMode(GroupHeaderBackgroundMode bm)
{
mGroupHeaderBackgroundMode = bm;
if ((bm == CustomColor) && !mGroupHeaderBackgroundColor.isValid()) {
mGroupHeaderBackgroundColor = QColor(127, 127, 127); // something neutral
}
}
const QColor &Theme::groupHeaderBackgroundColor() const
{
return mGroupHeaderBackgroundColor;
}
void Theme::setGroupHeaderBackgroundColor(const QColor &clr)
{
mGroupHeaderBackgroundColor = clr;
}
Theme::GroupHeaderBackgroundStyle Theme::groupHeaderBackgroundStyle() const
{
return mGroupHeaderBackgroundStyle;
}
void Theme::setGroupHeaderBackgroundStyle(Theme::GroupHeaderBackgroundStyle groupHeaderBackgroundStyle)
{
mGroupHeaderBackgroundStyle = groupHeaderBackgroundStyle;
}
QList<QPair<QString, int> > Theme::enumerateViewHeaderPolicyOptions()
{
return { {
- i18n("Never Show"), NeverShowHeader
- },
- {
- i18n("Always Show"), ShowHeaderAlways
- } };
+ i18n("Never Show"), NeverShowHeader
+ },
+ {
+ i18n("Always Show"), ShowHeaderAlways
+ } };
}
QList<QPair<QString, int> > Theme::enumerateGroupHeaderBackgroundStyles()
{
return { {
- i18n("Plain Rectangles"), PlainRect
- },
- {
- i18n("Plain Joined Rectangle"), PlainJoinedRect
- },
- {
- i18n("Rounded Rectangles"), RoundedRect
- },
- {
- i18n("Rounded Joined Rectangle"), RoundedJoinedRect
- },
- {
- i18n("Gradient Rectangles"), GradientRect
- },
- {
- i18n("Gradient Joined Rectangle"), GradientJoinedRect
- },
- {
- i18n("Styled Rectangles"), StyledRect
- },
- {
- i18n("Styled Joined Rectangles"), StyledJoinedRect
- } };
+ i18n("Plain Rectangles"), PlainRect
+ },
+ {
+ i18n("Plain Joined Rectangle"), PlainJoinedRect
+ },
+ {
+ i18n("Rounded Rectangles"), RoundedRect
+ },
+ {
+ i18n("Rounded Joined Rectangle"), RoundedJoinedRect
+ },
+ {
+ i18n("Gradient Rectangles"), GradientRect
+ },
+ {
+ i18n("Gradient Joined Rectangle"), GradientJoinedRect
+ },
+ {
+ i18n("Styled Rectangles"), StyledRect
+ },
+ {
+ i18n("Styled Joined Rectangles"), StyledJoinedRect
+ } };
}
Theme::ViewHeaderPolicy Theme::viewHeaderPolicy() const
{
return mViewHeaderPolicy;
}
void Theme::setViewHeaderPolicy(Theme::ViewHeaderPolicy vhp)
{
mViewHeaderPolicy = vhp;
}
int Theme::iconSize() const
{
return mIconSize;
}
void Theme::setIconSize(int iconSize)
{
if (mIconSize != iconSize) {
clearPixmapCache();
mIconSize = iconSize;
if ((mIconSize < 8) || (mIconSize > 64)) {
mIconSize = gThemeDefaultIconSize;
}
}
}
bool Theme::load(QDataStream &stream)
{
removeAllColumns();
int themeVersion;
stream >> themeVersion;
// We support themes starting at version gThemeMinimumSupportedVersion (0x1013 actually)
if (
(themeVersion > gThemeCurrentVersion)
|| (themeVersion < gThemeMinimumSupportedVersion)
) {
qCDebug(MESSAGELIST_LOG) << "Invalid theme version";
return false; // b0rken (invalid version)
}
int val;
stream >> val;
mGroupHeaderBackgroundMode = static_cast<GroupHeaderBackgroundMode>(val);
switch (mGroupHeaderBackgroundMode) {
case Transparent:
case AutoColor:
case CustomColor:
// ok
break;
default:
qCDebug(MESSAGELIST_LOG) << "Invalid theme group header background mode";
return false; // b0rken
}
stream >> mGroupHeaderBackgroundColor;
stream >> val;
mGroupHeaderBackgroundStyle = static_cast<GroupHeaderBackgroundStyle>(val);
switch (mGroupHeaderBackgroundStyle) {
case PlainRect:
case PlainJoinedRect:
case RoundedRect:
case RoundedJoinedRect:
case GradientRect:
case GradientJoinedRect:
case StyledRect:
case StyledJoinedRect:
// ok
break;
default:
qCDebug(MESSAGELIST_LOG) << "Invalid theme group header background style";
return false; // b0rken
}
stream >> val;
mViewHeaderPolicy = (ViewHeaderPolicy)val;
switch (mViewHeaderPolicy) {
case ShowHeaderAlways:
case NeverShowHeader:
// ok
break;
default:
qCDebug(MESSAGELIST_LOG) << "Invalid theme view header policy";
return false; // b0rken
}
if (themeVersion >= gThemeMinimumVersionWithIconSizeField) {
// icon size parameter
stream >> mIconSize;
if ((mIconSize < 8) || (mIconSize > 64)) {
mIconSize = gThemeDefaultIconSize; // limit insane values
}
} else {
mIconSize = gThemeDefaultIconSize;
}
// column count
stream >> val;
if (val < 1 || val > 50) {
return false; // plain b0rken ( negative, zero or more than 50 columns )
}
for (int i = 0; i < val; ++i) {
Column *col = new Column();
if (!col->load(stream, themeVersion)) {
qCDebug(MESSAGELIST_LOG) << "Column loading failed";
delete col;
return false;
}
addColumn(col);
}
return true;
}
void Theme::save(QDataStream &stream) const
{
stream << (int)gThemeCurrentVersion;
stream << (int)mGroupHeaderBackgroundMode;
stream << mGroupHeaderBackgroundColor;
stream << (int)mGroupHeaderBackgroundStyle;
stream << (int)mViewHeaderPolicy;
stream << mIconSize;
const int cnt = mColumns.count();
stream << (int)cnt;
for (int i = 0; i < cnt; ++i) {
Column *col = mColumns.at(i);
col->save(stream);
}
}
void Theme::clearPixmapCache() const
{
qDeleteAll(mPixmaps);
mPixmaps.clear();
}
void Theme::populatePixmapCache() const
{
clearPixmapCache();
mPixmaps.reserve(_IconCount);
// WARNING: The order of those icons must be in sync with order of the
// corresponding enum values in ThemeIcon!
mPixmaps << new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-unread-new")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-unread")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-read")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-deleted")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-replied")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-forwarded-replied")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-queued")).pixmap(mIconSize, mIconSize)) // mail-queue ?
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-task")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-sent")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-forwarded")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("emblem-important")).pixmap(mIconSize, mIconSize)) // "flag"
<< new QPixmap(QIcon::fromTheme(QStringLiteral("messagelist/pics/mail-thread-watch.png")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("messagelist/pics/mail-thread-ignored.png")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-junk")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed-verified")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed-part")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-signed")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted-full")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted-part")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-encrypted")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("text-plain")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-attachment")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("view-pim-notes")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("mail-invitation")).pixmap(mIconSize, mIconSize))
<< ((QApplication::isRightToLeft())
? new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-left")).pixmap(mIconSize, mIconSize))
: new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-right")).pixmap(mIconSize, mIconSize)))
<< new QPixmap(QIcon::fromTheme(QStringLiteral("arrow-down")).pixmap(mIconSize, mIconSize))
<< new QPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("messagelist/pics/mail-vertical-separator-line.png")))
<< new QPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("messagelist/pics/mail-horizontal-space.png")));
}
diff --git a/messagelist/src/core/theme.h b/messagelist/src/core/theme.h
index 4a8992d6..47f7764e 100644
--- a/messagelist/src/core/theme.h
+++ b/messagelist/src/core/theme.h
@@ -1,1024 +1,1024 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_THEME_H
#define MESSAGELIST_CORE_THEME_H
#include <QList>
#include <QPair>
#include <QString>
#include <QColor>
#include <QVector>
#include <core/optionset.h>
#include <core/sortorder.h>
class QPixmap;
namespace MessageList {
namespace Core {
/**
* The Theme class defines the visual appearance of the MessageList.
*
* The core structure of the theme is made up of Column objects which
* are mapped to View (QTreeView) columns. Each theme must provide at least one column.
*
* Each column contains a set of Row objects dedicated to message items
* and a set of Row objects dedicated to group header items. There must be at least
* one message row and one group header row in each column. Rows are visually
* ordered from top to bottom.
*
* Each Row contains a set of left aligned and a set of right aligned ContentItem objects.
* The right aligned items are painted from right to left while the left aligned
* ones are painted from left to right. In Right-To-Left mode the ThemeDelegate
* follows the exact opposite convention.
*
* Each ContentItem object specifies a visual element to be displayed in a View
* row. The visual elements may be pieces of text (Subject, Date) or icons.
*
* The Theme is designed to strictly interoperate with the ThemeDelegate class
* which takes care of rendering the contents when attached to an QAbstractItemView.
*/
class Theme : public OptionSet
{
public:
/**
* The ContentItem class defines a content item inside a Row.
* Content items are data items extracted from a message or a group header:
* they can be text, spacers, separators or icons.
*/
class ContentItem
{
private:
/**
* Bits for composing the Type enumeration members.
* We'll be able to test these bits to quickly figure out item properties.
*/
enum TypePropertyBits {
/**
* Item can use the custom color property
*/
CanUseCustomColor = (1 << 16),
/**
* Item can be in a disabled state (for example the attachment icon when there is no attachment)
*/
CanBeDisabled = (1 << 17),
/**
* Item displays some sort of text
*/
DisplaysText = (1 << 18),
/**
* Item makes sense (and can be applied) for messages
*/
ApplicableToMessageItems = (1 << 19),
/**
* Item makes sense (and can be applied) for group headers
*/
ApplicableToGroupHeaderItems = (1 << 20),
/**
* The item takes more horizontal space than the other text items (at the time of writing it's only the subject)
*/
LongText = (1 << 21),
/**
* The item displays an icon
*/
IsIcon = (1 << 22),
/**
* The item is a small spacer
*/
IsSpacer = (1 << 23),
/**
* The item is clickable
*/
IsClickable = (1 << 24)
};
public:
/**
* The available ContentItem types.
* Note that the values in this enum are unique values or'ed with the TypePropertyBits above.
*/
enum Type {
/**
* Display the subject of the message item. This is a long text.
*/
Subject = 1 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems | LongText,
/**
* Formatted date time of the message/group
*/
Date = 2 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems | ApplicableToGroupHeaderItems,
/**
* From: or To: strip, depending on the folder settings
*/
SenderOrReceiver = 3 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems,
/**
* From: strip, always
*/
Sender = 4 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems,
/**
* To: strip, always
*/
Receiver = 5 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems,
/**
* Formatted size of the message
*/
Size = 6 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems,
/**
* The icon that displays the unread/read state (never disabled)
*/
ReadStateIcon = 7 | ApplicableToMessageItems | IsIcon,
/**
- * The icon that displays the atachment state (may be disabled)
+ * The icon that displays the attachment state (may be disabled)
*/
AttachmentStateIcon = 8 | CanBeDisabled | ApplicableToMessageItems | IsIcon,
/**
* The icon that displays the replied/forwarded state (may be disabled)
*/
RepliedStateIcon = 9 | CanBeDisabled | ApplicableToMessageItems | IsIcon,
/**
* The group header label
*/
GroupHeaderLabel = 10 | DisplaysText | CanUseCustomColor | ApplicableToGroupHeaderItems,
/**
* The ActionItem state icon. May be disabled. Clickable (cycles todo->nothing)
*/
ActionItemStateIcon = 11 | CanBeDisabled | ApplicableToMessageItems | IsIcon | IsClickable,
/**
* The Important tag icon. May be disabled. Clickable (cycles important->nothing)
*/
ImportantStateIcon = 12 | CanBeDisabled | ApplicableToMessageItems | IsIcon | IsClickable,
/**
* The Spam/Ham state icon. May be disabled. Clickable (cycles spam->ham->nothing)
*/
SpamHamStateIcon = 13 | CanBeDisabled | ApplicableToMessageItems | IsIcon | IsClickable,
/**
* The Watched/Ignored state icon. May be disabled. Clickable (cycles watched->ignored->nothing)
*/
WatchedIgnoredStateIcon = 14 | CanBeDisabled | ApplicableToMessageItems | IsIcon | IsClickable,
/**
* The Expanded state icon for group headers. May be disabled. Clickable (expands/collapses the group)
*/
ExpandedStateIcon = 15 | CanBeDisabled | ApplicableToGroupHeaderItems | IsIcon | IsClickable,
/**
* The Encryption state icon for messages. May be disabled (no encryption).
*/
EncryptionStateIcon = 16 | CanBeDisabled | ApplicableToMessageItems | IsIcon,
/**
* The Signature state icon for messages. May be disabled (no signature)
*/
SignatureStateIcon = 17 | CanBeDisabled | ApplicableToMessageItems | IsIcon,
/**
* A vertical separation line.
*/
VerticalLine = 18 | CanUseCustomColor | ApplicableToMessageItems | ApplicableToGroupHeaderItems | IsSpacer,
/**
* A small empty spacer usable as separator.
*/
HorizontalSpacer = 19 | ApplicableToMessageItems | ApplicableToGroupHeaderItems | IsSpacer,
/**
* The date of the most recent message in subtree
*/
MostRecentDate = 20 | DisplaysText | CanUseCustomColor | ApplicableToMessageItems | ApplicableToGroupHeaderItems,
/**
* The combined icon that displays the unread/read/replied/forwarded state (never disabled)
*/
CombinedReadRepliedStateIcon = 21 | ApplicableToMessageItems | IsIcon,
/**
* The list of MessageItem::Tag entries
*/
TagList = 22 | ApplicableToMessageItems | IsIcon,
/**
* Whether the message has a annotation/note
*/
AnnotationIcon = 23 | ApplicableToMessageItems | IsIcon | CanBeDisabled | IsClickable,
/**
* Whether the message is an invitation
*/
InvitationIcon = 24 | ApplicableToMessageItems | IsIcon
#if 0
TotalMessageCount
UnreadMessageCount
NewMessageCount
#endif
};
enum Flags {
HideWhenDisabled = 1, ///< In disabled state the icon should take no space (overrides SoftenByBlendingWhenDisabled)
SoftenByBlendingWhenDisabled = (1 << 1), ///< In disabled state the icon should be still shown, but made very soft by alpha blending
UseCustomColor = (1 << 2), ///< For text and vertical line. If set then always use a custom color, otherwise use default text color
IsBold = (1 << 3), ///< For text items. If set then always show as bold, otherwise use the default font weight
IsItalic = (1 << 4), ///< Fot text items. If set then always show as italic, otherwise use the default font style
SoftenByBlending = (1 << 5) ///< For text items: use 60% opacity.
};
private:
Type mType; ///< The type of item
unsigned int mFlags; ///< The flags of the item
QColor mCustomColor; ///< The color to use with this content item, meaningful only if canUseCustomColor() return true.
public:
/**
* Creates a ContentItem with the specified type.
* A content item must be added to a theme Row.
*/
explicit ContentItem(Type type);
/**
* Creates a ContentItem that is a copy of the content item src.
* A content item must be added to a theme Row.
*/
explicit ContentItem(const ContentItem &src);
public:
/**
* Returns the type of this content item
*/
Type type() const;
/**
* Returns true if this ContentItem can be in a "disabled" state.
* The attachment state icon, for example, can be disabled when the related
* message has no attachments. For such items the HideWhenDisabled
* and SoftenByBlendingWhenDisabled flags are meaningful.
*/
bool canBeDisabled() const;
/**
* Returns true if this ContentItem can make use of a custom color.
*/
bool canUseCustomColor() const;
/**
* Returns true if this item displays some kind of text.
* Items that display text make use of the customFont() setting.
*/
bool displaysText() const;
/**
* Returns true if this item displays a long text.
* The returned value makes sense only if displaysText() returned true.
*/
bool displaysLongText() const;
/**
* Returns true if this item displays an icon.
*/
bool isIcon() const;
/**
* Returns true if clicking on this kind of item can perform an action
*/
bool isClickable() const;
/**
* Returns true if this item is a small spacer
*/
bool isSpacer() const;
/**
* Static test that returns true if an instance of ContentItem with the
* specified type makes sense in a Row for message items.
*/
static bool applicableToMessageItems(Type type);
/**
* Static test that returns true if an instance of ContentItem with the
* specified type makes sense in a Row for group header items.
*/
static bool applicableToGroupHeaderItems(Type type);
/**
* Returns a descriptive name for the specified content item type
*/
static QString description(Type type);
/**
* Returns true if this item uses a custom color.
* The return value of this function is valid only if canUseCustomColor() returns true.
*/
bool useCustomColor() const;
/**
* Makes this item use the custom color that can be set by setCustomColor().
* The custom color is meaningful only if canUseCustomColor() returns true.
*/
void setUseCustomColor(bool useCustomColor);
/**
* Returns true if this item uses a bold text.
* The return value of this function is valid only if displaysText() returns true.
*/
bool isBold() const;
/**
* Makes this item use a bold font.
*/
void setBold(bool isBold);
/**
* Returns true if this item uses an italic text.
* The return value of this function is valid only if displaysText() returns true.
*/
bool isItalic() const;
/**
* Makes this item use italic font.
*/
void setItalic(bool isItalic);
/**
* Returns true if this item should be hidden when in disabled state.
* Hidden content items simply aren't painted and take no space.
* This flag has meaning only on items for that canBeDisabled() returns true.
*/
bool hideWhenDisabled() const;
/**
* Sets the flag that causes this item to be hidden when disabled.
* Hidden content items simply aren't painted and take no space.
* This flag overrides the setSoftenByBlendingWhenDisabled() setting.
* This flag has meaning only on items for that canBeDisabled() returns true.
*/
void setHideWhenDisabled(bool hideWhenDisabled);
/**
* Returns true if this item should be painted in a "soft" fashion when
* in disabled state. Soft icons are painted with very low opacity.
* This flag has meaning only on items for that canBeDisabled() returns true.
*/
bool softenByBlendingWhenDisabled() const;
/**
* Sets the flag that causes this item to be painted "softly" when disabled.
* Soft icons are painted with very low opacity.
* This flag may be overridden by the setHideWhenDisabled() setting.
* This flag has meaning only on items for that canBeDisabled() returns true.
*/
void setSoftenByBlendingWhenDisabled(bool softenByBlendingWhenDisabled);
/**
* Returns true if this item should be always painted in a "soft" fashion.
* Meaningful only for text items.
*/
bool softenByBlending() const;
/**
* Sets the flag that causes this item to be painted "softly".
* Meaningful only for text items.
*/
void setSoftenByBlending(bool softenByBlending);
/**
* Returns the custom color set for this item.
* The return value is meaningful only if canUseCustomColor() returns true
* returns true and setUseCustomColor( true ) has been called.
*/
const QColor &customColor() const;
/**
* Sets the custom color for this item. Meaningful only if canUseCustomColor()
* returns true and you call setUseCustomColor( true )
*/
void setCustomColor(const QColor &clr);
// Stuff used by ThemeDelegate. This section should be protected but some gcc
// versions seem to get confused with nested class and friend declarations
// so for portability we're using a public interface also here.
/**
* Handles content item saving (used by Theme::Row::save())
*/
void save(QDataStream &stream) const;
/**
* Handles content item loading (used by Theme::Row::load())
*/
bool load(QDataStream &stream, int themeVersion);
};
/**
* The Row class defines a row of items inside a Column.
* The Row has a list of left aligned and a list of right aligned ContentItems.
*/
class Row
{
public:
explicit Row();
explicit Row(const Row &src);
~Row();
private:
QList< ContentItem * > mLeftItems; ///< The list of left aligned items
QList< ContentItem * > mRightItems; ///< The list of right aligned items
bool LoadContentItem(int val, QDataStream &stream, int themeVersion, bool leftItem);
public:
/**
* Returns the list of left aligned items for this row
*/
const QList< ContentItem * > &leftItems() const;
/**
* Removes all the left items from this row: the items are deleted.
*/
void removeAllLeftItems();
/**
* Adds a left aligned item to this row. The row takes the ownership
* of the ContentItem pointer.
*/
void addLeftItem(ContentItem *item);
/**
* Adds a left aligned item at the specified position in this row. The row takes the ownership
* of the ContentItem pointer.
*/
void insertLeftItem(int idx, ContentItem *item);
/**
* Removes the specified left aligned content item from this row.
* The item is NOT deleted.
*/
void removeLeftItem(ContentItem *item);
/**
* Returns the list of right aligned items for this row
*/
const QList< ContentItem * > &rightItems() const;
/**
* Removes all the right items from this row. The items are deleted.
*/
void removeAllRightItems();
/**
* Adds a right aligned item to this row. The row takes the ownership
* of the ContentItem pointer. Please note that the first right aligned item
* will start at the right edge, the second right aligned item will come after it etc...
*/
void addRightItem(ContentItem *item);
/**
* Adds a right aligned item at the specified position in this row. The row takes the ownership
* of the ContentItem pointer. Remember that right item positions go from right to left.
*/
void insertRightItem(int idx, ContentItem *item);
/**
* Removes the specified right aligned content item from this row.
* The item is NOT deleted.
*/
void removeRightItem(ContentItem *item);
/**
* Returns true if this row contains text items.
* This is useful if you want to know if the column should just get
* its minimum allowable space or it should get more.
*/
bool containsTextItems() const;
/**
* Handles row saving (used by Theme::Column::save())
*/
void save(QDataStream &stream) const;
/**
* Handles row loading (used by Theme::Column::load())
*/
bool load(QDataStream &stream, int themeVersion);
};
/**
* The Column class defines a view column available inside this theme.
* Each Column has a list of Row items that define the visible rows.
*/
class Column
{
public:
/**
* A set of shared runtime data. This is used to store a set of "override" settings
* at runtime. For instance, the width of the visible columns of a skin are stored here.
*/
class SharedRuntimeData
{
private:
int mReferences; ///< The number of external references to this shared data object
int mCurrentlyVisible; ///< Is this column currently visible ? always valid (eventually set from default)
double mCurrentWidth; ///< The current width of this column, -1 if not valid (never set)
public:
/**
* Create a shared runtime data object
*/
explicit SharedRuntimeData(bool currentlyVisible, double currentWidth);
/**
* Destroy a shared runtime data object
*/
~SharedRuntimeData();
public:
/**
* Increments the reference count for this shared runtime data object.
*/
void addReference();
/**
* Decrements the reference count for this shared runtime data object.
* Returns true if there are other references and false otherwise (so the data can be safely deleted)
*/
bool deleteReference();
/**
* Returns the current number of reference counts, that is, the number of
* Theme::Column objects that use this SharedRuntimeData instance.
*/
int referenceCount() const;
/**
* Returns the current visibility state
*/
bool currentlyVisible() const;
/**
* Sets the current visibility state
*/
void setCurrentlyVisible(bool visible);
/**
* Returns the current width or -1 if the width is unspecified/invalid
*/
double currentWidth() const;
/**
* Sets the current width of the column
*/
void setCurrentWidth(double currentWidth);
/**
* Saves this runtime data to the specified stream
*/
void save(QDataStream &stream) const;
/**
* Loads the shared runtime data from the specified stream
* assuming that it uses the specified theme version.
* Returns true on success and false if the data can't be loaded.
*/
bool load(QDataStream &stream, int themeVersion);
};
public:
/**
* Create an empty column with default settings
*/
explicit Column();
/**
* Create an exact copy of the column src.
* The shared runtime data is not copied (only a reference is added).
* If you need to create an independent clone then please use detach()
* after the construction.
*/
explicit Column(const Column &src);
/**
* Kill a column object
*/
~Column();
private:
QString mLabel; ///< The label visible in the column header
QString mPixmapName; ///< The icon's name visible in the column header if it was set
bool mVisibleByDefault; ///< Is this column visible by default ?
bool mIsSenderOrReceiver; ///< If this column displays the sender/receiver field then we will update its label on the fly
SortOrder::MessageSorting mMessageSorting; ///< The message sort order we switch to when clicking on this column
QList< Row * > mGroupHeaderRows; ///< The list of rows we display in this column for a GroupHeaderItem
QList< Row * > mMessageRows; ///< The list of rows we display in this column for a MessageItem
SharedRuntimeData *mSharedRuntimeData = nullptr; ///< A pointer to the shared runtime data: shared between all instances of a theme with the same id
public:
/**
* Returns the label set for this column
*/
const QString &label() const;
/**
* Sets the label for this column
*/
void setLabel(const QString &label);
/**
* Returns the icon's name (used in SmallIcon) set for this column
*/
const QString &pixmapName() const;
/**
* Sets the icon's name (used in SmallIcon) for this column
*/
void setPixmapName(const QString &pixmapName);
/**
* Returns true if this column is marked as "sender/receiver" and we should
* update its label on-the-fly.
*/
bool isSenderOrReceiver() const;
/**
* Marks this column as containing the "sender/receiver" field.
* Such columns will have the label automatically updated.
*/
void setIsSenderOrReceiver(bool sor);
/**
* Returns true if this column has to be shown by default
*/
bool visibleByDefault() const;
/**
* Sets the "visible by default" tag for this column.
*/
void setVisibleByDefault(bool vbd);
/**
* Detaches the shared runtime data object and makes this object
* totally independent. The shared runtime data is initialized to default values.
*/
void detach();
/**
* Returns the sort order for messages that we should switch to
* when clicking on this column's header (if visible at all).
*/
SortOrder::MessageSorting messageSorting() const;
/**
* Sets the sort order for messages that we should switch to
* when clicking on this column's header (if visible at all).
*/
void setMessageSorting(SortOrder::MessageSorting ms);
/**
* Returns the current shared visibility state for this column.
* This state is shared between all the instances of this theme.
*/
bool currentlyVisible() const;
/**
* Sets the current shared visibility state for this column.
* This state is shared between all the instances of this theme.
*/
void setCurrentlyVisible(bool currentlyVisible);
/**
* Returns the current shared width setting for this column
* or -1 if the width is not specified and should be auto-determined.
* This state is shared between all the instances of this theme.
*/
double currentWidth() const;
/**
* Sets the current shared width setting for this column.
* This state is shared between all the instances of this theme.
*/
void setCurrentWidth(double currentWidth);
/**
* Returns the list of rows visible in this column for a MessageItem
*/
const QList< Row * > &messageRows() const;
/**
* Removes all the message rows from this column.
*/
void removeAllMessageRows();
/**
* Appends a message row to this theme column. The Theme takes
* the ownership of the Row pointer.
*/
void addMessageRow(Row *row);
/**
* Inserts a message row to this theme column in the specified position. The Theme takes
* the ownership of the Row pointer.
*/
void insertMessageRow(int idx, Row *row);
/**
* Removes the specified message row. The row is NOT deleted.
*/
void removeMessageRow(Row *row);
/**
* Returns the list of rows visible in this column for a GroupHeaderItem
*/
const QList< Row * > &groupHeaderRows() const;
/**
* Removes all the group header rows from this column.
*/
void removeAllGroupHeaderRows();
/**
* Appends a group header row to this theme. The Theme takes
* the ownership of the Row pointer.
*/
void addGroupHeaderRow(Row *row);
/**
* Inserts a group header row to this theme column in the specified position. The Theme takes
* the ownership of the Row pointer.
*/
void insertGroupHeaderRow(int idx, Row *row);
/**
* Removes the specified group header row. The row is NOT deleted.
*/
void removeGroupHeaderRow(Row *row);
/**
* Returns true if this column contains text items.
* This is useful if you want to know if the column should just get
* its minimum allowable space or it should get more.
*/
bool containsTextItems() const;
/**
* Handles column saving (used by Theme::save())
*/
void save(QDataStream &stream) const;
/**
* Handles column loading (used by Theme::load())
*/
bool load(QDataStream &stream, int themeVersion);
};
public:
/**
* Creates a totally uninitialized theme object.
*/
explicit Theme();
/**
* Creates a theme object with the specified name and description.
*/
explicit Theme(const QString &name, const QString &description, bool readOnly = false);
/**
* Creates an exact copy of the theme sharing the same runtime data.
* If you need an exact clone please use detach() and generateUniqueId() just
* after creation.
*/
explicit Theme(const Theme &src);
/**
* Destroys this theme object.
*/
~Theme();
static bool compareName(Theme *theme1, Theme *theme2)
{
return theme1->name() < theme2->name();
}
public:
/**
* Which color do we use to paint group header background ?
*/
enum GroupHeaderBackgroundMode {
Transparent, ///< No background at all: use style default
AutoColor, ///< Automatically determine the color (somewhere in the middle between background and text)
CustomColor ///< Use a custom color
};
/**
* How do we paint group header background ?
*/
enum GroupHeaderBackgroundStyle {
PlainRect, ///< One plain rect per column
PlainJoinedRect, ///< One big plain rect for all the columns
RoundedRect, ///< One rounded rect per column
RoundedJoinedRect, ///< One big rounded rect for all the columns
GradientRect, ///< One rounded gradient filled rect per column
GradientJoinedRect, ///< One big rounded gradient rect for all the columns
StyledRect, ///< One styled rect per column
StyledJoinedRect ///< One big styled rect per column
};
/**
- * How do we manage the QHeaderView attacched to our View ?
+ * How do we manage the QHeaderView attached to our View ?
*/
enum ViewHeaderPolicy {
ShowHeaderAlways,
NeverShowHeader
//ShowWhenMoreThanOneColumn, ///< This doesn't work at the moment (since without header we don't have means for showing columns back)
};
enum ThemeIcon {
IconNew,
IconUnread,
IconRead,
IconDeleted,
IconReplied,
IconRepliedAndForwarded,
IconQueued,
IconActionItem,
IconSent,
IconForwarded,
IconImportant,
IconWatched,
IconIgnored,
IconSpam,
IconHam,
IconFullySigned,
IconPartiallySigned,
IconUndefinedSigned,
IconNotSigned,
IconFullyEncrypted,
IconPartiallyEncrypted,
IconUndefinedEncrypted,
IconNotEncrypted,
IconAttachment,
IconAnnotation,
IconInvitation,
IconShowMore,
IconShowLess,
IconVerticalLine,
IconHorizontalSpacer,
_IconCount
};
private:
QList< Column * > mColumns; ///< The list of columns available in this theme
// pixmaps cache. Mutable, so it can be lazily populated from const methods
mutable QVector<QPixmap *> mPixmaps;
GroupHeaderBackgroundMode mGroupHeaderBackgroundMode; ///< How do we paint group header background ?
QColor mGroupHeaderBackgroundColor; ///< The background color of the message group, used only if CustomColor
GroupHeaderBackgroundStyle mGroupHeaderBackgroundStyle; ///< How do we paint group header background ?
ViewHeaderPolicy mViewHeaderPolicy; ///< Do we show the header or not ?
int mIconSize; ///< The icon size for this theme, 16 is the default
public:
/**
* Detaches this object from the shared runtime data for columns.
*/
void detach();
/**
* Resets the column state (visibility and width) to their default values (the "visible by default" ones).
*/
void resetColumnState();
/**
* Resets the column sizes to "default" (subset of resetColumnState() above).
*/
void resetColumnSizes();
/**
* Returns the list of columns available in this theme
*/
const QList< Column * > &columns() const;
/**
* Returns a pointer to the column at the specified index or 0 if there is no such column
*/
Column *column(int idx) const;
void moveColumn(int idx, int newPosition);
/**
* Removes all columns from this theme
*/
void removeAllColumns();
/**
* Appends a column to this theme
*/
void addColumn(Column *column);
/**
* Inserts a column to this theme at the specified position.
*/
void insertColumn(int idx, Column *column);
/**
* Removes the specified message row. The row is NOT deleted.
*/
void removeColumn(Column *col);
/**
* Returns the group header background mode for this theme.
*/
GroupHeaderBackgroundMode groupHeaderBackgroundMode() const;
/**
* Sets the group header background mode for this theme.
* If you set it to CustomColor then please also setGroupHeaderBackgroundColor()
*/
void setGroupHeaderBackgroundMode(GroupHeaderBackgroundMode bm);
/**
* Returns the group header background color for this theme.
* This color is used only if groupHeaderBackgroundMode() is set to CustomColor.
*/
const QColor &groupHeaderBackgroundColor() const;
/**
* Sets the group header background color for this theme.
* This color is used only if groupHeaderBackgroundMode() is set to CustomColor.
*/
void setGroupHeaderBackgroundColor(const QColor &clr);
/**
* Returns the group header background style for this theme.
* The group header background style makes sense only if groupHeaderBackgroundMode() is
* set to something different than Transparent.
*/
GroupHeaderBackgroundStyle groupHeaderBackgroundStyle() const;
/**
* Sets the group header background style for this theme.
* The group header background style makes sense only if groupHeaderBackgroundMode() is
* set to something different than Transparent.
*/
void setGroupHeaderBackgroundStyle(GroupHeaderBackgroundStyle groupHeaderBackgroundStyle);
/**
* Enumerates the available group header background styles.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateGroupHeaderBackgroundStyles();
/**
* Returns the currently set ViewHeaderPolicy
*/
ViewHeaderPolicy viewHeaderPolicy() const;
/**
* Sets the ViewHeaderPolicy for this theme
*/
void setViewHeaderPolicy(ViewHeaderPolicy vhp);
/**
* Returns the currently set icon size
*/
int iconSize() const;
/**
* Sets the icon size for this theme.
* Please note that the function will not let you set insane values.
* The allowable range is [8,64]
*/
void setIconSize(int iconSize);
/**
* Enumerates the available view header policy options.
* The returned descriptors are pairs in that the first item is the localized description
* of the option value and the second item is the integer option value itself.
*/
static QList< QPair< QString, int > > enumerateViewHeaderPolicyOptions();
inline const QPixmap *pixmap(ThemeIcon icon) const
{
if (Q_UNLIKELY(mPixmaps.isEmpty())) {
populatePixmapCache();
}
return mPixmaps[icon];
}
protected:
/**
* Pure virtual reimplemented from OptionSet.
*/
void save(QDataStream &stream) const override;
/**
* Pure virtual reimplemented from OptionSet.
*/
bool load(QDataStream &stream) override;
void clearPixmapCache() const;
void populatePixmapCache() const;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_SKIN_H
diff --git a/messagelist/src/core/themedelegate.cpp b/messagelist/src/core/themedelegate.cpp
index 17f9a8aa..145c4762 100644
--- a/messagelist/src/core/themedelegate.cpp
+++ b/messagelist/src/core/themedelegate.cpp
@@ -1,1727 +1,1720 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* This program is free softhisare; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Softhisare 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 Softhisare
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*******************************************************************************/
#include "core/themedelegate.h"
#include "core/messageitem.h"
#include "core/groupheaderitem.h"
#include "core/manager.h"
#include "messagelistsettings.h"
#include "MessageCore/StringUtil"
#include "MessageCore/MessageCoreSettings"
#include <QStyle>
#include <QPainter>
#include <QFont>
#include <QFontMetrics>
#include <QAbstractItemView>
#include <QPixmap>
#include <QLinearGradient>
#include <KColorScheme>
#include <QFontDatabase>
using namespace MessageList::Core;
static const int gGroupHeaderOuterVerticalMargin = 1;
static const int gGroupHeaderOuterHorizontalMargin = 1;
static const int gGroupHeaderInnerVerticalMargin = 1;
static const int gGroupHeaderInnerHorizontalMargin = 1;
static const int gMessageVerticalMargin = 2;
static const int gMessageHorizontalMargin = 2;
static const int gHorizontalItemSpacing = 2;
ThemeDelegate::ThemeDelegate(QAbstractItemView *parent)
: QStyledItemDelegate(parent)
, mTheme(nullptr)
{
mItemView = parent;
}
ThemeDelegate::~ThemeDelegate()
{
}
void ThemeDelegate::setTheme(const Theme *theme)
{
mTheme = theme;
if (!mTheme) {
return; // hum
}
// Rebuild the group header background color cache
switch (mTheme->groupHeaderBackgroundMode()) {
case Theme::Transparent:
mGroupHeaderBackgroundColor = QColor(); // invalid
break;
case Theme::CustomColor:
mGroupHeaderBackgroundColor = mTheme->groupHeaderBackgroundColor();
break;
case Theme::AutoColor:
{
QPalette pal = mItemView->palette();
QColor txt = pal.color(QPalette::Normal, QPalette::Text);
QColor bck = pal.color(QPalette::Normal, QPalette::Base);
mGroupHeaderBackgroundColor = QColor(
(txt.red() + (bck.red() * 3)) / 4,
(txt.green() + (bck.green() * 3)) / 4,
(txt.blue() + (bck.blue() * 3)) / 4
);
break;
}
}
generalFontChanged();
mItemView->reset();
}
enum FontType {
Normal,
Bold,
Italic,
BoldItalic,
FontTypesCount
};
static QFont sFontCache[FontTypesCount];
static QFontMetrics sFontMetricsCache[FontTypesCount] = { QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont()), QFontMetrics(QFont()) };
static int sFontHeightCache = 0;
static inline const QFontMetrics &cachedFontMetrics(const Theme::ContentItem *ci)
{
return (!ci->isBold() && !ci->isItalic()) ? sFontMetricsCache[Normal]
: (ci->isBold() && !ci->isItalic()) ? sFontMetricsCache[Bold]
: (!ci->isBold() && ci->isItalic()) ? sFontMetricsCache[Italic]
: sFontMetricsCache[BoldItalic];
}
static inline const QFont &cachedFont(const Theme::ContentItem *ci)
{
return (!ci->isBold() && !ci->isItalic()) ? sFontCache[Normal]
: (ci->isBold() && !ci->isItalic()) ? sFontCache[Bold]
: (!ci->isBold() && ci->isItalic()) ? sFontCache[Italic]
: sFontCache[BoldItalic];
}
static inline const QFont &cachedFont(const Theme::ContentItem *ci, const Item *i)
{
if (i->type() != Item::Message) {
return cachedFont(ci);
}
const MessageItem *mi = static_cast<const MessageItem *>(i);
const bool bold = ci->isBold() || mi->isBold();
const bool italic = ci->isItalic() || mi->isItalic();
return (!bold && !italic) ? sFontCache[Normal]
: (bold && !italic) ? sFontCache[Bold]
: (!bold && italic) ? sFontCache[Italic]
: sFontCache[BoldItalic];
}
static inline void paint_right_aligned_elided_text(const QString &text, Theme::ContentItem *ci, QPainter *painter, int &left, int top, int &right, Qt::LayoutDirection layoutDir, const QFont &font)
{
painter->setFont(font);
const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
const int w = right - left;
const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
const QRect rct(left, top, w, sFontHeightCache);
QRect outRct;
if (ci->softenByBlending()) {
qreal oldOpacity = painter->opacity();
painter->setOpacity(0.6);
painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
painter->setOpacity(oldOpacity);
} else {
painter->drawText(rct, Qt::AlignTop | Qt::AlignRight | Qt::TextSingleLine, elidedText, &outRct);
}
if (layoutDir == Qt::LeftToRight) {
right -= outRct.width() + gHorizontalItemSpacing;
} else {
left += outRct.width() + gHorizontalItemSpacing;
}
}
-static inline void compute_bounding_rect_for_right_aligned_elided_text(const QString &text, Theme::ContentItem *ci, int &left, int top, int &right, QRect &outRect, Qt::LayoutDirection layoutDir,
- const QFont &font)
+static inline void compute_bounding_rect_for_right_aligned_elided_text(const QString &text, Theme::ContentItem *ci, int &left, int top, int &right, QRect &outRect, Qt::LayoutDirection layoutDir, const QFont &font)
{
Q_UNUSED(font);
const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
const int w = right - left;
const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideLeft : Qt::ElideRight, w);
const QRect rct(left, top, w, sFontHeightCache);
const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignRight : Qt::AlignLeft;
outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
if (layoutDir == Qt::LeftToRight) {
right -= outRect.width() + gHorizontalItemSpacing;
} else {
left += outRect.width() + gHorizontalItemSpacing;
}
}
static inline void paint_left_aligned_elided_text(const QString &text, Theme::ContentItem *ci, QPainter *painter, int &left, int top, int &right, Qt::LayoutDirection layoutDir, const QFont &font)
{
painter->setFont(font);
const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
const int w = right - left;
const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
const QRect rct(left, top, w, sFontHeightCache);
QRect outRct;
if (ci->softenByBlending()) {
qreal oldOpacity = painter->opacity();
painter->setOpacity(0.6);
painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
painter->setOpacity(oldOpacity);
} else {
painter->drawText(rct, Qt::AlignTop | Qt::AlignLeft | Qt::TextSingleLine, elidedText, &outRct);
}
if (layoutDir == Qt::LeftToRight) {
left += outRct.width() + gHorizontalItemSpacing;
} else {
right -= outRct.width() + gHorizontalItemSpacing;
}
}
-static inline void compute_bounding_rect_for_left_aligned_elided_text(const QString &text, Theme::ContentItem *ci, int &left, int top, int &right, QRect &outRect, Qt::LayoutDirection layoutDir,
- const QFont &font)
+static inline void compute_bounding_rect_for_left_aligned_elided_text(const QString &text, Theme::ContentItem *ci, int &left, int top, int &right, QRect &outRect, Qt::LayoutDirection layoutDir, const QFont &font)
{
Q_UNUSED(font);
const QFontMetrics &fontMetrics = cachedFontMetrics(ci);
const int w = right - left;
const QString elidedText = fontMetrics.elidedText(text, layoutDir == Qt::LeftToRight ? Qt::ElideRight : Qt::ElideLeft, w);
const QRect rct(left, top, w, sFontHeightCache);
const Qt::AlignmentFlag af = layoutDir == Qt::LeftToRight ? Qt::AlignLeft : Qt::AlignRight;
outRect = fontMetrics.boundingRect(rct, Qt::AlignTop | af | Qt::TextSingleLine, elidedText);
if (layoutDir == Qt::LeftToRight) {
left += outRect.width() + gHorizontalItemSpacing;
} else {
right -= outRect.width() + gHorizontalItemSpacing;
}
}
static inline const QPixmap *get_read_state_icon(const Theme *theme, Item *item)
{
if (item->status().isQueued()) {
return theme->pixmap(Theme::IconQueued);
} else if (item->status().isSent()) {
return theme->pixmap(Theme::IconSent);
} else if (item->status().isRead()) {
return theme->pixmap(Theme::IconRead);
} else if (!item->status().isRead()) {
return theme->pixmap(Theme::IconUnread);
} else if (item->status().isDeleted()) {
return theme->pixmap(Theme::IconDeleted);
}
// Uhm... should never happen.. but fallback to "read"...
return theme->pixmap(Theme::IconRead);
}
static inline const QPixmap *get_combined_read_replied_state_icon(const Theme *theme, MessageItem *messageItem)
{
if (messageItem->status().isReplied()) {
if (messageItem->status().isForwarded()) {
return theme->pixmap(Theme::IconRepliedAndForwarded);
}
return theme->pixmap(Theme::IconReplied);
}
if (messageItem->status().isForwarded()) {
return theme->pixmap(Theme::IconForwarded);
}
return get_read_state_icon(theme, messageItem);
}
static inline const QPixmap *get_encryption_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
{
switch (messageItem->encryptionState()) {
case MessageItem::FullyEncrypted:
*treatAsEnabled = true;
return theme->pixmap(Theme::IconFullyEncrypted);
- break;
case MessageItem::PartiallyEncrypted:
*treatAsEnabled = true;
return theme->pixmap(Theme::IconPartiallyEncrypted);
- break;
case MessageItem::EncryptionStateUnknown:
*treatAsEnabled = false;
return theme->pixmap(Theme::IconUndefinedEncrypted);
- break;
case MessageItem::NotEncrypted:
*treatAsEnabled = false;
return theme->pixmap(Theme::IconNotEncrypted);
- break;
default:
// should never happen
Q_ASSERT(false);
break;
}
*treatAsEnabled = false;
return theme->pixmap(Theme::IconUndefinedEncrypted);
}
static inline const QPixmap *get_signature_state_icon(const Theme *theme, MessageItem *messageItem, bool *treatAsEnabled)
{
switch (messageItem->signatureState()) {
case MessageItem::FullySigned:
*treatAsEnabled = true;
return theme->pixmap(Theme::IconFullySigned);
- break;
case MessageItem::PartiallySigned:
*treatAsEnabled = true;
return theme->pixmap(Theme::IconPartiallySigned);
- break;
case MessageItem::SignatureStateUnknown:
*treatAsEnabled = false;
return theme->pixmap(Theme::IconUndefinedSigned);
- break;
case MessageItem::NotSigned:
*treatAsEnabled = false;
return theme->pixmap(Theme::IconNotSigned);
- break;
default:
// should never happen
Q_ASSERT(false);
break;
}
*treatAsEnabled = false;
return theme->pixmap(Theme::IconUndefinedSigned);
}
static inline const QPixmap *get_replied_state_icon(const Theme *theme, MessageItem *messageItem)
{
if (messageItem->status().isReplied()) {
if (messageItem->status().isForwarded()) {
return theme->pixmap(Theme::IconRepliedAndForwarded);
}
return theme->pixmap(Theme::IconReplied);
}
if (messageItem->status().isForwarded()) {
return theme->pixmap(Theme::IconForwarded);
}
return nullptr;
}
static inline const QPixmap *get_spam_ham_state_icon(const Theme *theme, MessageItem *messageItem)
{
if (messageItem->status().isSpam()) {
return theme->pixmap(Theme::IconSpam);
} else if (messageItem->status().isHam()) {
return theme->pixmap(Theme::IconHam);
}
return nullptr;
}
static inline const QPixmap *get_watched_ignored_state_icon(const Theme *theme, MessageItem *messageItem)
{
if (messageItem->status().isIgnored()) {
return theme->pixmap(Theme::IconIgnored);
} else if (messageItem->status().isWatched()) {
return theme->pixmap(Theme::IconWatched);
}
return nullptr;
}
static inline void paint_vertical_line(QPainter *painter, int &left, int top, int &right, int bottom, bool alignOnRight)
{
if (alignOnRight) {
right -= 1;
if (right < 0) {
return;
}
painter->drawLine(right, top, right, bottom);
right -= 2;
right -= gHorizontalItemSpacing;
} else {
left += 1;
if (left > right) {
return;
}
painter->drawLine(left, top, left, bottom);
left += 2 + gHorizontalItemSpacing;
}
}
static inline void compute_bounding_rect_for_vertical_line(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
{
if (alignOnRight) {
right -= 3;
outRect = QRect(right, top, 3, bottom - top);
right -= gHorizontalItemSpacing;
} else {
outRect = QRect(left, top, 3, bottom - top);
left += 3 + gHorizontalItemSpacing;
}
}
static inline void paint_horizontal_spacer(int &left, int, int &right, int, bool alignOnRight)
{
if (alignOnRight) {
right -= 3 + gHorizontalItemSpacing;
} else {
left += 3 + gHorizontalItemSpacing;
}
}
static inline void compute_bounding_rect_for_horizontal_spacer(int &left, int top, int &right, int bottom, QRect &outRect, bool alignOnRight)
{
if (alignOnRight) {
right -= 3;
outRect = QRect(right, top, 3, bottom - top);
right -= gHorizontalItemSpacing;
} else {
outRect = QRect(left, top, 3, bottom - top);
left += 3 + gHorizontalItemSpacing;
}
}
static inline void paint_permanent_icon(const QPixmap *pix, Theme::ContentItem *, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
{
if (alignOnRight) {
right -= iconSize; // this icon is always present
if (right < 0) {
return;
}
painter->drawPixmap(right, top, iconSize, iconSize, *pix);
right -= gHorizontalItemSpacing;
} else {
if (left > (right - iconSize)) {
return;
}
painter->drawPixmap(left, top, iconSize, iconSize, *pix);
left += iconSize + gHorizontalItemSpacing;
}
}
static inline void compute_bounding_rect_for_permanent_icon(Theme::ContentItem *, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
{
if (alignOnRight) {
right -= iconSize; // this icon is always present
outRect = QRect(right, top, iconSize, iconSize);
right -= gHorizontalItemSpacing;
} else {
outRect = QRect(left, top, iconSize, iconSize);
left += iconSize + gHorizontalItemSpacing;
}
}
static inline void paint_boolean_state_icon(bool enabled, const QPixmap *pix, Theme::ContentItem *ci, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
{
if (enabled) {
paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
return;
}
// off -> icon disabled
if (ci->hideWhenDisabled()) {
return; // doesn't even take space
}
if (ci->softenByBlendingWhenDisabled()) {
// still paint, but very soft
qreal oldOpacity = painter->opacity();
painter->setOpacity(0.1);
paint_permanent_icon(pix, ci, painter, left, top, right, alignOnRight, iconSize);
painter->setOpacity(oldOpacity);
return;
}
// just takes space
if (alignOnRight) {
right -= iconSize + gHorizontalItemSpacing;
} else {
left += iconSize + gHorizontalItemSpacing;
}
}
static inline void compute_bounding_rect_for_boolean_state_icon(bool enabled, Theme::ContentItem *ci, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
{
if ((!enabled) && ci->hideWhenDisabled()) {
outRect = QRect();
return; // doesn't even take space
}
compute_bounding_rect_for_permanent_icon(ci, left, top, right, outRect, alignOnRight, iconSize);
}
static inline void paint_tag_list(const QList< MessageItem::Tag * > &tagList, QPainter *painter, int &left, int top, int &right, bool alignOnRight, int iconSize)
{
if (alignOnRight) {
for (const MessageItem::Tag *tag : tagList) {
right -= iconSize; // this icon is always present
if (right < 0) {
return;
}
painter->drawPixmap(right, top, iconSize, iconSize, tag->pixmap());
right -= gHorizontalItemSpacing;
}
} else {
for (const MessageItem::Tag *tag : tagList) {
if (left > right - iconSize) {
return;
}
painter->drawPixmap(left, top, iconSize, iconSize, tag->pixmap());
left += iconSize + gHorizontalItemSpacing;
}
}
}
static inline void compute_bounding_rect_for_tag_list(const QList< MessageItem::Tag * > &tagList, int &left, int top, int &right, QRect &outRect, bool alignOnRight, int iconSize)
{
int width = tagList.count() * (iconSize + gHorizontalItemSpacing);
if (alignOnRight) {
right -= width;
outRect = QRect(right, top, width, iconSize);
} else {
outRect = QRect(left, top, width, iconSize);
left += width;
}
}
static inline void compute_size_hint_for_item(Theme::ContentItem *ci, int &maxh, int &totalw, int iconSize, const Item *item)
{
Q_UNUSED(item);
if (ci->displaysText()) {
if (sFontHeightCache > maxh) {
maxh = sFontHeightCache;
}
totalw += ci->displaysLongText() ? 128 : 64;
return;
}
if (ci->isIcon()) {
totalw += iconSize + gHorizontalItemSpacing;
if (maxh < iconSize) {
maxh = iconSize;
}
return;
}
if (ci->isSpacer()) {
if (18 > maxh) {
maxh = 18;
}
totalw += 3 + gHorizontalItemSpacing;
return;
}
// should never be reached
if (18 > maxh) {
maxh = 18;
}
totalw += gHorizontalItemSpacing;
}
static inline QSize compute_size_hint_for_row(const Theme::Row *r, int iconSize, const Item *item)
{
int maxh = 8; // at least 8 pixels for a pixmap
int totalw = 0;
// right aligned stuff first
auto items = r->rightItems();
for (const auto it : qAsConst(items)) {
compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
}
// then left aligned stuff
items = r->leftItems();
for (const auto it : qAsConst(items)) {
compute_size_hint_for_item(const_cast<Theme::ContentItem *>(it), maxh, totalw, iconSize, item);
}
return QSize(totalw, maxh);
}
void ThemeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (!index.isValid()) {
return; // bleah
}
Item *item = itemFromIndex(index);
if (!item) {
return; // hm...
}
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text.clear(); // draw no text for me, please.. I'll do it in a while
// Set background color of control if necessary
if (item->type() == Item::Message) {
MessageItem *msgItem = static_cast< MessageItem * >(item);
if (msgItem->backgroundColor().isValid()) {
opt.backgroundBrush = QBrush(msgItem->backgroundColor());
}
}
QStyle *style = mItemView->style();
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
if (!mTheme) {
return; // hm hm...
}
const Theme::Column *skcolumn = mTheme->column(index.column());
if (!skcolumn) {
return; // bleah
}
const QList<MessageList::Core::Theme::Row *> *rows;
MessageItem *messageItem = nullptr;
GroupHeaderItem *groupHeaderItem = nullptr;
int top = opt.rect.top();
int right = opt.rect.left() + opt.rect.width(); // don't use opt.rect.right() since it's screwed
int left = opt.rect.left();
// Storing the changed members one by one is faster than saving the painter state
QFont oldFont = painter->font();
QPen oldPen = painter->pen();
qreal oldOpacity = painter->opacity();
QPen defaultPen;
bool usingNonDefaultTextColor = false;
switch (item->type()) {
case Item::Message:
rows = &(skcolumn->messageRows());
messageItem = static_cast< MessageItem * >(item);
if (
(!(opt.state & QStyle::State_Enabled))
|| messageItem->aboutToBeRemoved()
|| (!messageItem->isValid())
) {
painter->setOpacity(0.5);
defaultPen = QPen(opt.palette.brush(QPalette::Disabled, QPalette::Text), 0);
} else {
QPalette::ColorGroup cg;
if (opt.state & QStyle::State_Active) {
cg = QPalette::Normal;
} else {
cg = QPalette::Inactive;
}
if (opt.state & QStyle::State_Selected) {
defaultPen = QPen(opt.palette.brush(cg, QPalette::HighlightedText), 0);
} else {
if (messageItem->textColor().isValid()) {
usingNonDefaultTextColor = true;
defaultPen = QPen(messageItem->textColor(), 0);
} else {
defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
}
}
}
top += gMessageVerticalMargin;
right -= gMessageHorizontalMargin;
left += gMessageHorizontalMargin;
break;
case Item::GroupHeader:
{
rows = &(skcolumn->groupHeaderRows());
groupHeaderItem = static_cast< GroupHeaderItem * >(item);
QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
cg = QPalette::Inactive;
}
QPalette::ColorRole cr;
top += gGroupHeaderOuterVerticalMargin;
right -= gGroupHeaderOuterHorizontalMargin;
left += gGroupHeaderOuterHorizontalMargin;
switch (mTheme->groupHeaderBackgroundMode()) {
case Theme::Transparent:
cr = (opt.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text;
defaultPen = QPen(opt.palette.brush(cg, cr), 0);
break;
case Theme::AutoColor:
case Theme::CustomColor:
switch (mTheme->groupHeaderBackgroundStyle()) {
case Theme::PlainRect:
painter->fillRect(
QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(mGroupHeaderBackgroundColor)
);
break;
case Theme::PlainJoinedRect:
{
int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne) ? left : opt.rect.left();
int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne) ? right : opt.rect.left() + opt.rect.width();
painter->fillRect(
QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(mGroupHeaderBackgroundColor)
);
break;
}
case Theme::RoundedJoinedRect:
if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
painter->fillRect(
QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(mGroupHeaderBackgroundColor)
);
break; // don't fall through
}
if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
painter->fillRect(
QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(mGroupHeaderBackgroundColor)
);
} else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
painter->fillRect(
QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(mGroupHeaderBackgroundColor)
);
}
// fall through anyway
Q_FALLTHROUGH();
case Theme::RoundedRect:
{
painter->setPen(Qt::NoPen);
bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
if (!hadAntialiasing) {
painter->setRenderHint(QPainter::Antialiasing, true);
}
painter->setBrush(QBrush(mGroupHeaderBackgroundColor));
painter->setBackgroundMode(Qt::OpaqueMode);
int w = right - left;
if (w > 0) {
painter->drawRoundedRect(
QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
4.0, 4.0
);
}
if (!hadAntialiasing) {
painter->setRenderHint(QPainter::Antialiasing, false);
}
painter->setBackgroundMode(Qt::TransparentMode);
break;
}
case Theme::GradientJoinedRect:
{
// FIXME: Could cache this brush
QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
if (opt.viewItemPosition == QStyleOptionViewItem::Middle) {
painter->fillRect(
QRect(opt.rect.left(), top, opt.rect.width(), opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(gradient)
);
break; // don't fall through
}
if (opt.viewItemPosition == QStyleOptionViewItem::Beginning) {
painter->fillRect(
QRect(opt.rect.left() + opt.rect.width() - 10, top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(gradient)
);
} else if (opt.viewItemPosition == QStyleOptionViewItem::End) {
painter->fillRect(
QRect(opt.rect.left(), top, 10, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
QBrush(gradient)
);
}
// fall through anyway
Q_FALLTHROUGH();
}
case Theme::GradientRect:
{
// FIXME: Could cache this brush
QLinearGradient gradient(0, top, 0, top + opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
gradient.setColorAt(0.0, KColorScheme::shade(mGroupHeaderBackgroundColor, KColorScheme::LightShade, 0.3));
gradient.setColorAt(1.0, mGroupHeaderBackgroundColor);
painter->setPen(Qt::NoPen);
bool hadAntialiasing = painter->renderHints() & QPainter::Antialiasing;
if (!hadAntialiasing) {
painter->setRenderHint(QPainter::Antialiasing, true);
}
painter->setBrush(QBrush(gradient));
painter->setBackgroundMode(Qt::OpaqueMode);
int w = right - left;
if (w > 0) {
painter->drawRoundedRect(
QRect(left, top, w, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2)),
4.0, 4.0
);
}
if (!hadAntialiasing) {
painter->setRenderHint(QPainter::Antialiasing, false);
}
painter->setBackgroundMode(Qt::TransparentMode);
break;
}
case Theme::StyledRect:
// oxygen, for instance, has a nice graphics for selected items
opt.rect = QRect(left, top, right - left, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
opt.state |= QStyle::State_Selected;
opt.viewItemPosition = QStyleOptionViewItem::OnlyOne;
opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
break;
case Theme::StyledJoinedRect:
{
int rleft = (opt.viewItemPosition == QStyleOptionViewItem::Beginning) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne) ? left : opt.rect.left();
int rright = (opt.viewItemPosition == QStyleOptionViewItem::End) || (opt.viewItemPosition == QStyleOptionViewItem::OnlyOne) ? right : opt.rect.left() + opt.rect.width();
opt.rect = QRect(rleft, top, rright - rleft, opt.rect.height() - (gGroupHeaderInnerVerticalMargin * 2));
opt.state |= QStyle::State_Selected;
opt.palette.setColor(cg, QPalette::Highlight, mGroupHeaderBackgroundColor);
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, mItemView);
break;
}
}
defaultPen = QPen(opt.palette.brush(cg, QPalette::Text), 0);
break;
}
top += gGroupHeaderInnerVerticalMargin;
right -= gGroupHeaderInnerHorizontalMargin;
left += gGroupHeaderInnerHorizontalMargin;
break;
}
default:
Q_ASSERT(false);
return; // bug
- break;
}
Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
for (const auto row : qAsConst(*rows)) {
QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), item);
int bottom = top + rowSizeHint.height();
// paint right aligned stuff first
int r = right;
int l = left;
- for (const auto itemit : qAsConst(row->rightItems())) {
+ const auto rightItems = row->rightItems();
+ for (const auto itemit : rightItems) {
auto ci = const_cast<Theme::ContentItem *>(itemit);
if (ci->canUseCustomColor()) {
if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
if (usingNonDefaultTextColor) {
// merge the colors
QColor nonDefault = defaultPen.color();
QColor custom = ci->customColor();
QColor merged(
(nonDefault.red() + custom.red()) >> 1,
(nonDefault.green() + custom.green()) >> 1,
(nonDefault.blue() + custom.blue()) >> 1
);
painter->setPen(QPen(merged));
} else {
painter->setPen(QPen(ci->customColor()));
}
} else {
painter->setPen(defaultPen);
}
} // otherwise setting a pen is useless at this time
const QFont &font = cachedFont(ci, item);
switch (ci->type()) {
case Theme::ContentItem::Subject:
paint_right_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::SenderOrReceiver:
paint_right_aligned_elided_text(item->displaySenderOrReceiver(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Receiver:
paint_right_aligned_elided_text(item->displayReceiver(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Sender:
paint_right_aligned_elided_text(item->displaySender(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Date:
paint_right_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::MostRecentDate:
paint_right_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Size:
paint_right_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::GroupHeaderLabel:
if (groupHeaderItem) {
paint_right_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
}
break;
case Theme::ContentItem::ReadStateIcon:
paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r,
layoutDir == Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::CombinedReadRepliedStateIcon:
if (messageItem) {
paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem), ci, painter,
l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ExpandedStateIcon:
{
const QPixmap *pix = item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconShowMore),
ci, painter, l, top, r, layoutDir == Qt::LeftToRight,
mTheme->iconSize());
break;
}
case Theme::ContentItem::RepliedStateIcon:
if (messageItem) {
const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconReplied),
ci, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::EncryptionStateIcon:
if (messageItem) {
bool enabled;
const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r,
layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SignatureStateIcon:
if (messageItem) {
bool enabled;
const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r,
layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SpamHamStateIcon:
if (messageItem) {
const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconSpam),
ci, painter, l, top, r, layoutDir == Qt::LeftToRight,
mTheme->iconSize());
}
break;
case Theme::ContentItem::WatchedIgnoredStateIcon:
if (messageItem) {
const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconWatched),
ci, painter, l, top, r, layoutDir == Qt::LeftToRight,
mTheme->iconSize());
}
break;
case Theme::ContentItem::AttachmentStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().hasAttachment(),
mTheme->pixmap(Theme::IconAttachment), ci, painter,
l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AnnotationIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->hasAnnotation(),
mTheme->pixmap(Theme::IconAnnotation), ci, painter,
l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::InvitationIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().hasInvitation(),
mTheme->pixmap(Theme::IconInvitation), ci, painter,
l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ActionItemStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().isToAct(),
mTheme->pixmap(Theme::IconActionItem), ci, painter,
l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ImportantStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().isImportant(),
mTheme->pixmap(Theme::IconImportant), ci, painter, l,
top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::VerticalLine:
paint_vertical_line(painter, l, top, r, bottom, layoutDir == Qt::LeftToRight);
break;
case Theme::ContentItem::HorizontalSpacer:
paint_horizontal_spacer(l, top, r, bottom, layoutDir == Qt::LeftToRight);
break;
case Theme::ContentItem::TagList:
if (messageItem) {
const QList< MessageItem::Tag * > tagList = messageItem->tagList();
paint_tag_list(tagList, painter, l, top, r, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
}
}
// then paint left aligned stuff
- for (const auto itemit : qAsConst(row->leftItems())) {
+ const auto leftItems = row->leftItems();
+ for (const auto itemit : leftItems) {
auto ci = const_cast<Theme::ContentItem *>(itemit);
if (ci->canUseCustomColor()) {
if (ci->useCustomColor() && (!(opt.state & QStyle::State_Selected))) {
if (usingNonDefaultTextColor) {
// merge the colors
QColor nonDefault = defaultPen.color();
QColor custom = ci->customColor();
QColor merged(
(nonDefault.red() + custom.red()) >> 1,
(nonDefault.green() + custom.green()) >> 1,
(nonDefault.blue() + custom.blue()) >> 1
);
painter->setPen(QPen(merged));
} else {
painter->setPen(QPen(ci->customColor()));
}
} else {
painter->setPen(defaultPen);
}
} // otherwise setting a pen is useless at this time
const QFont &font = cachedFont(ci, item);
switch (ci->type()) {
case Theme::ContentItem::Subject:
paint_left_aligned_elided_text(item->subject(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::SenderOrReceiver:
paint_left_aligned_elided_text(item->displaySenderOrReceiver(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Receiver:
paint_left_aligned_elided_text(item->displayReceiver(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Sender:
paint_left_aligned_elided_text(item->displaySender(),
ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Date:
paint_left_aligned_elided_text(item->formattedDate(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::MostRecentDate:
paint_left_aligned_elided_text(item->formattedMaxDate(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::Size:
paint_left_aligned_elided_text(item->formattedSize(), ci, painter, l, top, r, layoutDir, font);
break;
case Theme::ContentItem::GroupHeaderLabel:
if (groupHeaderItem) {
paint_left_aligned_elided_text(groupHeaderItem->label(), ci, painter, l, top, r, layoutDir, font);
}
break;
case Theme::ContentItem::ReadStateIcon:
paint_permanent_icon(get_read_state_icon(mTheme, item), ci, painter, l, top, r,
layoutDir != Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::CombinedReadRepliedStateIcon:
if (messageItem) {
paint_permanent_icon(get_combined_read_replied_state_icon(mTheme, messageItem), ci, painter,
l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ExpandedStateIcon:
{
const QPixmap *pix = item->childItemCount() > 0 ? mTheme->pixmap((option.state & QStyle::State_Open) ? Theme::IconShowLess : Theme::IconShowMore) : nullptr;
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconShowMore),
ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
break;
}
case Theme::ContentItem::RepliedStateIcon:
if (messageItem) {
const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconReplied),
ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::EncryptionStateIcon:
if (messageItem) {
bool enabled;
const QPixmap *pix = get_encryption_state_icon(mTheme, messageItem, &enabled);
paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r,
layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SignatureStateIcon:
if (messageItem) {
bool enabled;
const QPixmap *pix = get_signature_state_icon(mTheme, messageItem, &enabled);
paint_boolean_state_icon(enabled, pix, ci, painter, l, top, r,
layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SpamHamStateIcon:
if (messageItem) {
const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconSpam),
ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::WatchedIgnoredStateIcon:
if (messageItem) {
const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
paint_boolean_state_icon(pix != nullptr, pix ? pix : mTheme->pixmap(Theme::IconWatched),
ci, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AttachmentStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().hasAttachment(),
mTheme->pixmap(Theme::IconAttachment), ci, painter,
l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AnnotationIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->hasAnnotation(),
mTheme->pixmap(Theme::IconAnnotation), ci, painter,
l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::InvitationIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().hasInvitation(),
mTheme->pixmap(Theme::IconInvitation), ci, painter,
l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ActionItemStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().isToAct(),
mTheme->pixmap(Theme::IconActionItem), ci, painter,
l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ImportantStateIcon:
if (messageItem) {
paint_boolean_state_icon(messageItem->status().isImportant(),
mTheme->pixmap(Theme::IconImportant), ci, painter, l,
top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::VerticalLine:
paint_vertical_line(painter, l, top, r, bottom, layoutDir != Qt::LeftToRight);
break;
case Theme::ContentItem::HorizontalSpacer:
paint_horizontal_spacer(l, top, r, bottom, layoutDir != Qt::LeftToRight);
break;
case Theme::ContentItem::TagList:
if (messageItem) {
const QList< MessageItem::Tag * > tagList = messageItem->tagList();
paint_tag_list(tagList, painter, l, top, r, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
}
}
top = bottom;
}
painter->setFont(oldFont);
painter->setPen(oldPen);
painter->setOpacity(oldOpacity);
}
bool ThemeDelegate::hitTest(const QPoint &viewportPoint, bool exact)
{
mHitItem = nullptr;
mHitColumn = nullptr;
mHitRow = nullptr;
mHitContentItem = nullptr;
if (!mTheme) {
return false; // hm hm...
}
mHitIndex = mItemView->indexAt(viewportPoint);
if (!mHitIndex.isValid()) {
return false; // bleah
}
mHitItem = itemFromIndex(mHitIndex);
if (!mHitItem) {
return false; // hm...
}
mHitItemRect = mItemView->visualRect(mHitIndex);
mHitColumn = mTheme->column(mHitIndex.column());
if (!mHitColumn) {
return false; // bleah
}
const QList< Theme::Row * > *rows; // I'd like to have it as reference, but gcc complains...
MessageItem *messageItem = nullptr;
GroupHeaderItem *groupHeaderItem = nullptr;
int top = mHitItemRect.top();
int right = mHitItemRect.right();
int left = mHitItemRect.left();
mHitRow = nullptr;
mHitRowIndex = -1;
mHitContentItem = nullptr;
switch (mHitItem->type()) {
case Item::Message:
mHitRowIsMessageRow = true;
rows = &(mHitColumn->messageRows());
messageItem = static_cast< MessageItem * >(mHitItem);
// FIXME: paint eventual background here
top += gMessageVerticalMargin;
right -= gMessageHorizontalMargin;
left += gMessageHorizontalMargin;
break;
case Item::GroupHeader:
mHitRowIsMessageRow = false;
rows = &(mHitColumn->groupHeaderRows());
groupHeaderItem = static_cast< GroupHeaderItem * >(mHitItem);
top += gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin;
right -= gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
left += gGroupHeaderOuterHorizontalMargin + gGroupHeaderInnerHorizontalMargin;
break;
default:
return false; // bug
break;
}
int rowIdx = 0;
int bestInexactDistance = 0xffffff;
bool bestInexactItemRight = false;
QRect bestInexactRect;
const Theme::ContentItem *bestInexactContentItem = nullptr;
Qt::LayoutDirection layoutDir = mItemView->layoutDirection();
for (const auto row : qAsConst(*rows)) {
QSize rowSizeHint = compute_size_hint_for_row(row, mTheme->iconSize(), mHitItem);
if ((viewportPoint.y() < top) && (rowIdx > 0)) {
break; // not this row (tough we should have already found it... probably clicked upper margin)
}
int bottom = top + rowSizeHint.height();
if (viewportPoint.y() > bottom) {
top += rowSizeHint.height();
rowIdx++;
continue; // not this row
}
bestInexactItemRight = false;
bestInexactDistance = 0xffffff;
bestInexactContentItem = nullptr;
// this row!
mHitRow = row;
mHitRowIndex = rowIdx;
mHitRowRect = QRect(left, top, right - left, bottom - top);
// check right aligned stuff first
mHitContentItemRight = true;
int r = right;
int l = left;
- for (const auto itemit : qAsConst(mHitRow->rightItems())) {
+ const auto rightItems = mHitRow->rightItems();
+ for (const auto itemit : rightItems) {
auto ci = const_cast<Theme::ContentItem *>(itemit);
mHitContentItemRect = QRect();
const QFont &font = cachedFont(ci, mHitItem);
switch (ci->type()) {
case Theme::ContentItem::Subject:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::SenderOrReceiver:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySenderOrReceiver(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Receiver:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displayReceiver(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Sender:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->displaySender(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Date:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::MostRecentDate:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Size:
compute_bounding_rect_for_right_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::GroupHeaderLabel:
if (groupHeaderItem) {
compute_bounding_rect_for_right_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
}
break;
case Theme::ContentItem::ReadStateIcon:
compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::CombinedReadRepliedStateIcon:
compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::ExpandedStateIcon:
compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::RepliedStateIcon:
if (messageItem) {
const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::EncryptionStateIcon:
if (messageItem) {
bool enabled;
get_encryption_state_icon(mTheme, messageItem, &enabled);
compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SignatureStateIcon:
if (messageItem) {
bool enabled;
get_signature_state_icon(mTheme, messageItem, &enabled);
compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SpamHamStateIcon:
if (messageItem) {
const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::WatchedIgnoredStateIcon:
if (messageItem) {
const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AttachmentStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(), ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AnnotationIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(), ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::InvitationIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(), ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ActionItemStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(), ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ImportantStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(), ci, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::VerticalLine:
compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
break;
case Theme::ContentItem::HorizontalSpacer:
compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir == Qt::LeftToRight);
break;
case Theme::ContentItem::TagList:
if (messageItem) {
const QList< MessageItem::Tag * > tagList = messageItem->tagList();
compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir == Qt::LeftToRight, mTheme->iconSize());
}
break;
}
if (mHitContentItemRect.isValid()) {
if (mHitContentItemRect.contains(viewportPoint)) {
// caught!
mHitContentItem = ci;
return true;
}
if (!exact) {
QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
if (inexactRect.contains(viewportPoint)) {
mHitContentItem = ci;
return true;
}
int inexactDistance = viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
if (inexactDistance < bestInexactDistance) {
bestInexactDistance = inexactDistance;
bestInexactRect = mHitContentItemRect;
bestInexactItemRight = true;
bestInexactContentItem = ci;
}
}
}
}
// then check left aligned stuff
mHitContentItemRight = false;
- for (const auto itemit : qAsConst(mHitRow->leftItems())) {
+ const auto leftItems = mHitRow->leftItems();
+ for (const auto itemit : leftItems) {
auto ci = const_cast<Theme::ContentItem *>(itemit);
mHitContentItemRect = QRect();
const QFont &font = cachedFont(ci, mHitItem);
switch (ci->type()) {
case Theme::ContentItem::Subject:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->subject(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::SenderOrReceiver:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySenderOrReceiver(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Receiver:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displayReceiver(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Sender:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->displaySender(),
ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Date:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::MostRecentDate:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedMaxDate(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::Size:
compute_bounding_rect_for_left_aligned_elided_text(mHitItem->formattedSize(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
break;
case Theme::ContentItem::GroupHeaderLabel:
if (groupHeaderItem) {
compute_bounding_rect_for_left_aligned_elided_text(groupHeaderItem->label(), ci, l, top, r, mHitContentItemRect, layoutDir, font);
}
break;
case Theme::ContentItem::ReadStateIcon:
compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::CombinedReadRepliedStateIcon:
compute_bounding_rect_for_permanent_icon(ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::ExpandedStateIcon:
compute_bounding_rect_for_boolean_state_icon(mHitItem->childItemCount() > 0, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
break;
case Theme::ContentItem::RepliedStateIcon:
if (messageItem) {
const QPixmap *pix = get_replied_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::EncryptionStateIcon:
if (messageItem) {
bool enabled;
get_encryption_state_icon(mTheme, messageItem, &enabled);
compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SignatureStateIcon:
if (messageItem) {
bool enabled;
get_signature_state_icon(mTheme, messageItem, &enabled);
compute_bounding_rect_for_boolean_state_icon(enabled, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::SpamHamStateIcon:
if (messageItem) {
const QPixmap *pix = get_spam_ham_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::WatchedIgnoredStateIcon:
if (messageItem) {
const QPixmap *pix = get_watched_ignored_state_icon(mTheme, messageItem);
compute_bounding_rect_for_boolean_state_icon(pix != nullptr, ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AttachmentStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasAttachment(), ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::AnnotationIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->hasAnnotation(), ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::InvitationIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().hasInvitation(), ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ActionItemStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().isToAct(), ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::ImportantStateIcon:
if (messageItem) {
compute_bounding_rect_for_boolean_state_icon(messageItem->status().isImportant(), ci, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
case Theme::ContentItem::VerticalLine:
compute_bounding_rect_for_vertical_line(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
break;
case Theme::ContentItem::HorizontalSpacer:
compute_bounding_rect_for_horizontal_spacer(l, top, r, bottom, mHitContentItemRect, layoutDir != Qt::LeftToRight);
break;
case Theme::ContentItem::TagList:
if (messageItem) {
const QList< MessageItem::Tag * > tagList = messageItem->tagList();
compute_bounding_rect_for_tag_list(tagList, l, top, r, mHitContentItemRect, layoutDir != Qt::LeftToRight, mTheme->iconSize());
}
break;
}
if (mHitContentItemRect.isValid()) {
if (mHitContentItemRect.contains(viewportPoint)) {
// caught!
mHitContentItem = ci;
return true;
}
if (!exact) {
QRect inexactRect(mHitContentItemRect.left(), mHitRowRect.top(), mHitContentItemRect.width(), mHitRowRect.height());
if (inexactRect.contains(viewportPoint)) {
mHitContentItem = ci;
return true;
}
int inexactDistance = viewportPoint.x() > inexactRect.right() ? viewportPoint.x() - inexactRect.right() : inexactRect.left() - viewportPoint.x();
if (inexactDistance < bestInexactDistance) {
bestInexactDistance = inexactDistance;
bestInexactRect = mHitContentItemRect;
bestInexactItemRight = false;
bestInexactContentItem = ci;
}
}
}
}
top += rowSizeHint.height();
rowIdx++;
}
mHitContentItem = bestInexactContentItem;
mHitContentItemRight = bestInexactItemRight;
mHitContentItemRect = bestInexactRect;
return true;
}
const QModelIndex &ThemeDelegate::hitIndex() const
{
return mHitIndex;
}
Item *ThemeDelegate::hitItem() const
{
return mHitItem;
}
QRect ThemeDelegate::hitItemRect() const
{
return mHitItemRect;
}
const Theme::Column *ThemeDelegate::hitColumn() const
{
return mHitColumn;
}
int ThemeDelegate::hitColumnIndex() const
{
return mHitIndex.column();
}
const Theme::Row *ThemeDelegate::hitRow() const
{
return mHitRow;
}
int ThemeDelegate::hitRowIndex() const
{
return mHitRowIndex;
}
QRect ThemeDelegate::hitRowRect() const
{
return mHitRowRect;
}
bool ThemeDelegate::hitRowIsMessageRow() const
{
return mHitRowIsMessageRow;
}
const Theme::ContentItem *ThemeDelegate::hitContentItem() const
{
return mHitContentItem;
}
bool ThemeDelegate::hitContentItemRight() const
{
return mHitContentItemRight;
}
QRect ThemeDelegate::hitContentItemRect() const
{
return mHitContentItemRect;
}
QSize ThemeDelegate::sizeHintForItemTypeAndColumn(Item::Type type, int column, const Item *item) const
{
if (!mTheme) {
return QSize(16, 16); // bleah
}
const Theme::Column *skcolumn = mTheme->column(column);
if (!skcolumn) {
return QSize(16, 16); // bleah
}
const QList< Theme::Row * > *rows; // I'd like to have it as reference, but gcc complains...
// The sizeHint() is layout direction independent.
int marginw;
int marginh;
switch (type) {
case Item::Message:
rows = &(skcolumn->messageRows());
marginh = gMessageVerticalMargin << 1;
marginw = gMessageHorizontalMargin << 1;
break;
case Item::GroupHeader:
rows = &(skcolumn->groupHeaderRows());
marginh = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
marginw = (gGroupHeaderOuterVerticalMargin + gGroupHeaderInnerVerticalMargin) << 1;
break;
default:
return QSize(16, 16); // bug
break;
}
int totalh = 0;
int maxw = 0;
for (QList< Theme::Row * >::ConstIterator rowit = rows->constBegin(), endRowIt = rows->constEnd(); rowit != endRowIt; ++rowit) {
const QSize sh = compute_size_hint_for_row((*rowit), mTheme->iconSize(), item);
totalh += sh.height();
if (sh.width() > maxw) {
maxw = sh.width();
}
}
return QSize(maxw + marginw, totalh + marginh);
}
QSize ThemeDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &index) const
{
if (!mTheme || !index.isValid()) {
return QSize(16, 16); // hm hm...
}
Item *item = itemFromIndex(index);
if (!item) {
return QSize(16, 16); // hm...
}
const Item::Type type = item->type();
if (type == Item::Message) {
if (!mCachedMessageItemSizeHint.isValid()) {
mCachedMessageItemSizeHint = sizeHintForItemTypeAndColumn(Item::Message, index.column(), item);
}
return mCachedMessageItemSizeHint;
} else if (type == Item::GroupHeader) {
if (!mCachedGroupHeaderItemSizeHint.isValid()) {
mCachedGroupHeaderItemSizeHint = sizeHintForItemTypeAndColumn(Item::GroupHeader, index.column(), item);
}
return mCachedGroupHeaderItemSizeHint;
} else {
Q_ASSERT(false);
return QSize();
}
}
// Store the new fonts when the generalFont changes and flush sizeHint cache
void ThemeDelegate::generalFontChanged()
{
mCachedMessageItemSizeHint = QSize();
mCachedGroupHeaderItemSizeHint = QSize();
QFont font;
if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
} else {
font = MessageListSettings::self()->messageListFont();
}
sFontCache[Normal] = font;
sFontMetricsCache[Normal] = QFontMetrics(font);
font.setBold(true);
sFontCache[Bold] = font;
sFontMetricsCache[Bold] = QFontMetrics(font);
font.setBold(false);
font.setItalic(true);
sFontCache[Italic] = font;
sFontMetricsCache[Italic] = QFontMetrics(font);
font.setBold(true);
font.setItalic(true);
sFontCache[BoldItalic] = font;
sFontMetricsCache[BoldItalic] = QFontMetrics(font);
sFontHeightCache = sFontMetricsCache[Normal].height();
}
const Theme *ThemeDelegate::theme() const
{
return mTheme;
}
diff --git a/messagelist/src/core/themedelegate.h b/messagelist/src/core/themedelegate.h
index 4a65f985..c66029cf 100644
--- a/messagelist/src/core/themedelegate.h
+++ b/messagelist/src/core/themedelegate.h
@@ -1,206 +1,206 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_THEMEDELEGATE_H
#define MESSAGELIST_CORE_THEMEDELEGATE_H
#include <QStyledItemDelegate>
#include <QRect>
#include <QColor>
#include "core/theme.h"
#include "core/item.h"
class QAbstractItemView;
namespace MessageList {
namespace Core {
class Item;
/**
* The ThemeDelegate paints the message list view message and group items by
* using the supplied Theme.
*/
class ThemeDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ThemeDelegate(QAbstractItemView *parent);
~ThemeDelegate() override;
/**
* Called when the global fonts change (from systemsettings)
*/
void generalFontChanged();
private:
const Theme *mTheme; ///< Shallow pointer to the current theme
QAbstractItemView *mItemView;
QColor mGroupHeaderBackgroundColor; // cache
// hitTest results
QModelIndex mHitIndex;
Item *mHitItem;
QRect mHitItemRect;
const Theme::Column *mHitColumn;
const Theme::Row *mHitRow;
int mHitRowIndex;
bool mHitRowIsMessageRow;
QRect mHitRowRect;
bool mHitContentItemRight;
const Theme::ContentItem *mHitContentItem;
QRect mHitContentItemRect;
mutable QSize mCachedMessageItemSizeHint;
mutable QSize mCachedGroupHeaderItemSizeHint;
public:
const Theme *theme() const;
void setTheme(const Theme *theme);
/**
* Returns a heuristic sizeHint() for the specified item type and column.
* The hint is based on the contents of the theme (and not of any message or group header).
*/
QSize sizeHintForItemTypeAndColumn(Item::Type type, int column, const Item *item = nullptr) const;
/**
* Performs a hit test on the specified viewport point.
* Returns true if the point hit something and false otherwise.
- * When the hit test is succesfull then the hitIndex(), hitItem(), hitColumn(), hitRow(), and hitContentItem()
+ * When the hit test is successful then the hitIndex(), hitItem(), hitColumn(), hitRow(), and hitContentItem()
* function will return information about the item that was effectively hit.
* If exact is set to true then hitTest() will return true only if the viewportPoint
* is exactly over an item. If exact is set to false then the hitTest() function
* will do its best to find the closest object to be actually "hit": this is useful,
* for example, in drag and drop operations.
*/
bool hitTest(const QPoint &viewportPoint, bool exact = true);
/**
* Returns the model index that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function.
*/
const QModelIndex &hitIndex() const;
/**
* Returns the Item that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function.
*/
Item *hitItem() const;
/**
* Returns the visual rectangle of the item that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function. Please note that this rectangle refers
* to a specific item column (and not all of the columns).
*/
QRect hitItemRect() const;
/**
* Returns the theme column that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function.
*/
const Theme::Column *hitColumn() const;
/**
* Returns the index of the theme column that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function.
* This is the same as hitIndex().column().
*/
int hitColumnIndex() const;
/**
* Returns the theme row that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function. This function may also return a null row
* when hitTest() returned true. This means that the item was globally hit
* but no row was exactly hit (the user probably hit the margin instead).
*/
const Theme::Row *hitRow() const;
/**
* Returns the index of the theme row that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitRow() returns a non null value.
*/
int hitRowIndex() const;
/**
* Returns the rectangle of the row that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function. The result of this function is also invalid
* if hitRow() returns 0.
*/
QRect hitRowRect() const;
/**
* Returns true if the hitRow() is a message row, false otherwise.
* The result of this function has a meaning only if hitRow() returns a non zero result.
*/
bool hitRowIsMessageRow() const;
/**
* Returns the theme content item that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function. This function may also return a null content item
* when hitTest() returned true. This means that the item was globally hit
* but no content item was exactly hit (the user might have clicked inside a blank unused space instead).
*/
const Theme::ContentItem *hitContentItem() const;
/**
* Returns true if the hit theme content item was a right item and false otherwise.
* The result of this function is valid only if hitContentItem() returns true.
*/
bool hitContentItemRight() const;
/**
* Returns the bounding rect of the content item that was reported as hit by the previous call to hitTest().
* The result of this function is valid only if hitTest() returned true and only
* within the same calling function. The result of this function is to be considered
* invalid also when hitContentItem() returns 0.
*/
QRect hitContentItemRect() const;
protected:
/**
* Returns the Item for the specified model index. Pure virtual: must be reimplemented
* by derived classes.
*/
virtual Item *itemFromIndex(const QModelIndex &index) const = 0;
/**
* Reimplemented from QStyledItemDelegate
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
/**
* Reimplemented from QStyledItemDelegate
*/
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_SKINDELEGATE_H
diff --git a/messagelist/src/core/view.cpp b/messagelist/src/core/view.cpp
index 11515d66..745e22d8 100644
--- a/messagelist/src/core/view.cpp
+++ b/messagelist/src/core/view.cpp
@@ -1,2722 +1,2762 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/view.h"
#include "core/aggregation.h"
#include "core/delegate.h"
#include "core/groupheaderitem.h"
#include "core/item.h"
#include "core/manager.h"
#include "core/messageitem.h"
#include "core/model.h"
#include "core/theme.h"
#include "messagelistsettings.h"
#include "core/storagemodelbase.h"
#include "core/widgetbase.h"
#include "messagelistutil.h"
#include "messagelistutil_p.h"
#include "MessageCore/StringUtil"
#include <kmime/kmime_dateformatter.h> // kdepimlibs
#include <Item>
#include <QHelpEvent>
#include <QToolTip>
#include <QHeaderView>
#include <QTimer>
#include <QApplication>
#include <QScrollBar>
#include <QLineEdit>
#include <QMenu>
+#include <QPainter>
+
#include <KLocalizedString>
#include "messagelist_debug.h"
using namespace MessageList::Core;
class Q_DECL_HIDDEN View::Private
{
public:
Private(View *owner, Widget *parent)
: q(owner)
, mWidget(parent)
, mModel(nullptr)
, mDelegate(new Delegate(owner))
, mAggregation(nullptr)
, mTheme(nullptr)
, mNeedToApplyThemeColumns(false)
, mLastCurrentItem(nullptr)
, mSaveThemeColumnStateOnSectionResize(true)
, mSaveThemeColumnStateTimer(nullptr)
, mApplyThemeColumnsTimer(nullptr)
, mLastViewportWidth(-1)
, mIgnoreUpdateGeometries(false)
{
}
void expandFullThread(const QModelIndex &index);
+ void generalPaletteChanged();
+ QColor mTextColor;
View *const q;
Widget *mWidget = nullptr;
Model *mModel = nullptr;
Delegate *mDelegate = nullptr;
const Aggregation *mAggregation = nullptr; ///< The Aggregation we're using now, shallow pointer
Theme *mTheme = nullptr; ///< The Theme we're using now, shallow pointer
bool mNeedToApplyThemeColumns; ///< Flag signaling a pending application of theme columns
Item *mLastCurrentItem = nullptr;
QPoint mMousePressPosition;
bool mSaveThemeColumnStateOnSectionResize; ///< This is used to filter out programmatic column resizes in slotSectionResized().
QTimer *mSaveThemeColumnStateTimer = nullptr; ///< Used to trigger a delayed "save theme state"
QTimer *mApplyThemeColumnsTimer = nullptr; ///< Used to trigger a delayed "apply theme columns"
int mLastViewportWidth;
bool mIgnoreUpdateGeometries; ///< Shall we ignore the "update geometries" calls ?
};
View::View(Widget *pParent)
: QTreeView(pParent)
, d(new Private(this, pParent))
{
d->mSaveThemeColumnStateTimer = new QTimer();
connect(d->mSaveThemeColumnStateTimer, &QTimer::timeout, this, &View::saveThemeColumnState);
d->mApplyThemeColumnsTimer = new QTimer();
connect(d->mApplyThemeColumnsTimer, &QTimer::timeout, this, &View::applyThemeColumns);
setItemDelegate(d->mDelegate);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setAlternatingRowColors(true);
setAllColumnsShowFocus(true);
setSelectionMode(QAbstractItemView::ExtendedSelection);
viewport()->setAcceptDrops(true);
header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(header(), &QWidget::customContextMenuRequested,
this, &View::slotHeaderContextMenuRequested);
connect(header(), &QHeaderView::sectionResized,
this, &View::slotHeaderSectionResized);
header()->setSectionsClickable(true);
header()->setSectionResizeMode(QHeaderView::Interactive);
header()->setMinimumSectionSize(2); // QTreeView overrides our sections sizes if we set them smaller than this value
header()->setDefaultSectionSize(2); // QTreeView overrides our sections sizes if we set them smaller than this value
d->mModel = new Model(this);
setModel(d->mModel);
connect(d->mModel, &Model::statusMessage,
pParent, &Widget::statusMessage);
connect(selectionModel(), &QItemSelectionModel::selectionChanged,
this, &View::slotSelectionChanged,
Qt::UniqueConnection);
// as in KDE3, when a root-item of a message thread is expanded, expand all children
- connect(this, &View::expanded, this, [this](const QModelIndex &index) { d->expandFullThread(index);} );
+ connect(this, &View::expanded, this, [this](const QModelIndex &index) {
+ d->expandFullThread(index);
+ });
}
View::~View()
{
if (d->mSaveThemeColumnStateTimer->isActive()) {
d->mSaveThemeColumnStateTimer->stop();
}
delete d->mSaveThemeColumnStateTimer;
if (d->mApplyThemeColumnsTimer->isActive()) {
d->mApplyThemeColumnsTimer->stop();
}
delete d->mApplyThemeColumnsTimer;
// Zero out the theme, aggregation and ApplyThemeColumnsTimer so Model will not cause accesses to them in its destruction process
d->mApplyThemeColumnsTimer = nullptr;
d->mTheme = nullptr;
d->mAggregation = nullptr;
delete d;
d = nullptr;
}
Model *View::model() const
{
return d->mModel;
}
Delegate *View::delegate() const
{
return d->mDelegate;
}
void View::ignoreCurrentChanges(bool ignore)
{
if (ignore) {
disconnect(selectionModel(), &QItemSelectionModel::selectionChanged,
this, &View::slotSelectionChanged);
viewport()->setUpdatesEnabled(false);
} else {
connect(selectionModel(), &QItemSelectionModel::selectionChanged,
this, &View::slotSelectionChanged,
Qt::UniqueConnection);
viewport()->setUpdatesEnabled(true);
}
}
void View::ignoreUpdateGeometries(bool ignore)
{
d->mIgnoreUpdateGeometries = ignore;
}
bool View::isScrollingLocked() const
{
// There is another popular requisite: people want the view to automatically
// scroll in order to show new arriving mail. This actually makes sense
// only when the view is sorted by date and the new mail is (usually) either
// appended at the bottom or inserted at the top. It would be also confusing
// when the user is browsing some other thread in the meantime.
//
// So here we make a simple guess: if the view is scrolled somewhere in the
// middle then we assume that the user is browsing other threads and we
// try to keep the currently selected item steady on the screen.
// When the view is "locked" to the top (scrollbar value 0) or to the
// bottom (scrollbar value == maximum) then we assume that the user
// isn't browsing and we should attempt to show the incoming messages
// by keeping the view "locked".
//
// The "locking" also doesn't make sense in the first big fill view job.
// [Well this concept is pre-akonadi. Now the loading is all async anyway...
// So all this code is actually triggered during the initial loading, too.]
const int scrollBarPosition = verticalScrollBar()->value();
const int scrollBarMaximum = verticalScrollBar()->maximum();
const SortOrder *sortOrder = d->mModel->sortOrder();
const bool lockView = (
// not the first loading job
!d->mModel->isLoading()
) && (
// messages sorted by date
(sortOrder->messageSorting() == SortOrder::SortMessagesByDateTime)
|| (sortOrder->messageSorting() == SortOrder::SortMessagesByDateTimeOfMostRecent)
) && (
// scrollbar at top (Descending order) or bottom (Ascending order)
(scrollBarPosition == 0 && sortOrder->messageSortDirection() == SortOrder::Descending)
|| (scrollBarPosition == scrollBarMaximum && sortOrder->messageSortDirection() == SortOrder::Ascending)
);
return lockView;
}
void View::updateGeometries()
{
if (d->mIgnoreUpdateGeometries || !d->mModel) {
return;
}
const int scrollBarPositionBefore = verticalScrollBar()->value();
const bool lockView = isScrollingLocked();
QTreeView::updateGeometries();
if (lockView) {
// we prefer to keep the view locked to the top or bottom
if (scrollBarPositionBefore != 0) {
// we wanted the view to be locked to the bottom
if (verticalScrollBar()->value() != verticalScrollBar()->maximum()) {
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
} // else we wanted the view to be locked to top and we shouldn't need to do anything
}
}
StorageModel *View::storageModel() const
{
return d->mModel->storageModel();
}
void View::setAggregation(const Aggregation *aggregation)
{
d->mAggregation = aggregation;
d->mModel->setAggregation(aggregation);
// use uniform row heights to speed up, but only if there are no group headers used
setUniformRowHeights(d->mAggregation->grouping() == Aggregation::NoGrouping);
}
void View::setTheme(Theme *theme)
{
d->mNeedToApplyThemeColumns = true;
d->mTheme = theme;
d->mDelegate->setTheme(theme);
d->mModel->setTheme(theme);
}
void View::setSortOrder(const SortOrder *sortOrder)
{
d->mModel->setSortOrder(sortOrder);
}
void View::reload()
{
setStorageModel(storageModel());
}
void View::setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode)
{
// This will cause the model to be reset.
d->mSaveThemeColumnStateOnSectionResize = false;
d->mModel->setStorageModel(storageModel, preSelectionMode);
d->mSaveThemeColumnStateOnSectionResize = true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Theme column state machinery
//
// This is yet another beast to beat. The QHeaderView behaviour, at the time of writing,
// is quite unpredictable. This is due to the complex interaction with the model, with the QTreeView
// and due to its attempts to delay the layout jobs. The delayed layouts, especially, may
// cause the widths of the columns to quickly change in an unexpected manner in a place
// where previously they have been always settled to the values you set...
//
// So here we have the tools to:
//
// - Apply the saved state of the theme columns (applyThemeColumns()).
// This function computes the "best fit" state of the visible columns and tries
// to apply it to QHeaderView. It also saves the new computed state to the Theme object.
//
// - Explicitly save the column state, used when the user changes the widths or visibility manually.
// This is called through a delayed timer after a column has been resized or used directly
// when the visibility state of a column has been changed by toggling a popup menu entry.
//
// - Display the column state context popup menu and handle its actions
//
// - Apply the theme columns when the theme changes, when the model changes or when
// the widget is resized.
//
// - Avoid saving a corrupted column state in that QHeaderView can be found *very* frequently.
//
void View::applyThemeColumns()
{
if (!d->mApplyThemeColumnsTimer) {
return;
}
if (d->mApplyThemeColumnsTimer->isActive()) {
d->mApplyThemeColumnsTimer->stop();
}
if (!d->mTheme) {
return;
}
//qCDebug(MESSAGELIST_LOG) << "Apply theme columns";
const QList< Theme::Column * > &columns = d->mTheme->columns();
if (columns.isEmpty()) {
return; // bad theme
}
if (!viewport()->isVisible()) {
return; // invisible
}
if (viewport()->width() < 1) {
return; // insane width
}
const int viewportWidth = viewport()->width();
d->mLastViewportWidth = viewportWidth;
// Now we want to distribute the available width on all the visible columns.
//
// The rules:
// - The visible columns will span the width of the view, if possible.
// - The columns with a saved width should take that width.
// - The columns on the left should take more space, if possible.
// - The columns with no text take just slightly more than their size hint.
// while the columns with text take possibly a lot more.
//
// Note that the first column is always shown (it can't be hidden at all)
// The algorithm below is a sort of compromise between:
// - Saving the user preferences for widths
// - Using exactly the available view space
//
// It "tends to work" in all cases:
// - When there are no user preferences saved and the column widths must be
// automatically computed to make best use of available space
// - When there are user preferences for only some of the columns
// and that should be somewhat preserved while still using all the
// available space.
// - When all the columns have well defined saved widths
int idx = 0;
// Gather total size "hint" for visible sections: if the widths of the columns wers
// all saved then the total hint is equal to the total saved width.
int totalVisibleWidthHint = 0;
QList< int > lColumnSizeHints;
for (const auto col : qAsConst(columns)) {
if (col->currentlyVisible() || (idx == 0)) {
//qCDebug(MESSAGELIST_LOG) << "Column " << idx << " will be visible";
// Column visible
const int savedWidth = col->currentWidth();
const int hintWidth = d->mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
totalVisibleWidthHint += savedWidth > 0 ? savedWidth : hintWidth;
lColumnSizeHints.append(hintWidth);
//qCDebug(MESSAGELIST_LOG) << "Column " << idx << " size hint is " << hintWidth;
} else {
//qCDebug(MESSAGELIST_LOG) << "Column " << idx << " will be not visible";
// The column is not visible
lColumnSizeHints.append(-1); // dummy
}
idx++;
}
if (totalVisibleWidthHint < 16) {
totalVisibleWidthHint = 16; // be reasonable
}
// Now compute somewhat "proportional" widths.
idx = 0;
QList< double > lColumnWidths;
lColumnWidths.reserve(columns.count());
int totalVisibleWidth = 0;
for (const auto col : qAsConst(columns)) {
double savedWidth = col->currentWidth();
double hintWidth = savedWidth > 0 ? savedWidth : lColumnSizeHints.at(idx);
double realWidth;
if (col->currentlyVisible() || (idx == 0)) {
if (col->containsTextItems()) {
// the column contains text items, it should get more space (if possible)
realWidth = ((hintWidth * viewportWidth) / totalVisibleWidthHint);
} else {
// the column contains no text items, it should get exactly its hint/saved width.
realWidth = hintWidth;
}
if (realWidth < 2) {
realWidth = 2; // don't allow very insane values
}
totalVisibleWidth += realWidth;
} else {
// Column not visible
realWidth = -1;
}
lColumnWidths.append(realWidth);
idx++;
}
// Now the algorithm above may be wrong for several reasons...
// - We're using fixed widths for certain columns and proportional
// for others...
// - The user might have changed the width of the view from the
// time in that the widths have been saved
// - There are some (not well identified) issues with the QTreeView
// scrollbar that make our view appear larger or shorter by 2-3 pixels
// sometimes.
// - ...
// So we correct the previous estimates by trying to use exactly
// the available space.
idx = 0;
if (totalVisibleWidth != viewportWidth) {
// The estimated widths were not using exactly the available space.
if (totalVisibleWidth < viewportWidth) {
// We were using less space than available.
// Give the additional space to the text columns
// also give more space to the first ones and less space to the last ones
qreal available = viewportWidth - totalVisibleWidth;
for (int idx = 0; idx < columns.count(); ++idx) {
Theme::Column *column = columns.at(idx);
if ((column->currentlyVisible() || (idx == 0)) && column->containsTextItems()) {
// give more space to this column
available /= 2; // eat half of the available space
lColumnWidths[ idx ] += available; // and give it to this column
if (available < 1) {
break; // no more space to give away
}
}
}
// if any space is still available, give it to the first column
if (available >= 1) {
lColumnWidths[ 0 ] += available;
}
} else {
// We were using more space than available
// If the columns span more than the view then
// try to squeeze them in order to make them fit
double missing = totalVisibleWidth - viewportWidth;
if (missing > 0) {
const int count = lColumnWidths.count();
idx = count - 1;
while (idx >= 0) {
if (columns.at(idx)->currentlyVisible() || (idx == 0)) {
double chop = lColumnWidths.at(idx) - lColumnSizeHints.at(idx);
if (chop > 0) {
if (chop > missing) {
chop = missing;
}
lColumnWidths[ idx ] -= chop;
missing -= chop;
if (missing < 1) {
break; // no more space to recover
}
}
} // else it's invisible
idx--;
}
}
}
}
// We're ready to assign widths.
bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
d->mSaveThemeColumnStateOnSectionResize = false;
// A huge problem here is that QHeaderView goes quite nuts if we show or hide sections
// while resizing them. This is because it has several machineries aimed to delay
// the layout to the last possible moment. So if we show a column, it will tend to
// screw up the layout of other ones.
// We first loop showing/hiding columns then.
idx = 0;
for (const auto col : qAsConst(columns)) {
bool visible = (idx == 0) || col->currentlyVisible();
//qCDebug(MESSAGELIST_LOG) << "Column " << idx << " visible " << visible;
col->setCurrentlyVisible(visible);
header()->setSectionHidden(idx, !visible);
idx++;
}
// Then we loop assigning widths. This is still complicated since QHeaderView tries
// very badly to stretch the last section and thus will resize it in the meantime.
// But seems to work most of the times...
idx = 0;
for (const auto col : qAsConst(columns)) {
if (col->currentlyVisible()) {
const double columnWidth(lColumnWidths.at(idx));
col->setCurrentWidth(columnWidth);
//Laurent Bug 358855 - message list column widths lost when program closed
// I need to investigate if this code is still necessary (all method)
header()->resizeSection(idx, static_cast<int>(columnWidth));
} else {
col->setCurrentWidth(-1);
}
idx++;
}
idx = 0;
bool bTriggeredQtBug = false;
for (const auto col : qAsConst(columns)) {
if (!header()->isSectionHidden(idx)) {
if (!col->currentlyVisible()) {
bTriggeredQtBug = true;
}
}
idx++;
}
setHeaderHidden(d->mTheme->viewHeaderPolicy() == Theme::NeverShowHeader);
d->mSaveThemeColumnStateOnSectionResize = oldSave;
d->mNeedToApplyThemeColumns = false;
static bool bAllowRecursion = true;
if (bTriggeredQtBug && bAllowRecursion) {
bAllowRecursion = false;
//qCDebug(MESSAGELIST_LOG) << "I've triggered the QHeaderView bug: trying to fix by calling myself again";
applyThemeColumns();
bAllowRecursion = true;
}
}
void View::triggerDelayedApplyThemeColumns()
{
if (d->mApplyThemeColumnsTimer->isActive()) {
d->mApplyThemeColumnsTimer->stop();
}
d->mApplyThemeColumnsTimer->setSingleShot(true);
d->mApplyThemeColumnsTimer->start(100);
}
void View::saveThemeColumnState()
{
if (d->mSaveThemeColumnStateTimer->isActive()) {
d->mSaveThemeColumnStateTimer->stop();
}
if (!d->mTheme) {
return;
}
if (d->mNeedToApplyThemeColumns) {
return; // don't save the state if it hasn't been applied at all
}
//qCDebug(MESSAGELIST_LOG) << "Save theme column state";
const auto columns = d->mTheme->columns();
if (columns.isEmpty()) {
return; // bad theme
}
int idx = 0;
for (const auto col : qAsConst(columns)) {
if (header()->isSectionHidden(idx)) {
//qCDebug(MESSAGELIST_LOG) << "Section " << idx << " is hidden";
col->setCurrentlyVisible(false);
col->setCurrentWidth(-1); // reset (hmmm... we could use the "don't touch" policy here too...)
} else {
//qCDebug(MESSAGELIST_LOG) << "Section " << idx << " is visible and has size " << header()->sectionSize( idx );
col->setCurrentlyVisible(true);
col->setCurrentWidth(header()->sectionSize(idx));
}
idx++;
}
}
void View::triggerDelayedSaveThemeColumnState()
{
if (d->mSaveThemeColumnStateTimer->isActive()) {
d->mSaveThemeColumnStateTimer->stop();
}
d->mSaveThemeColumnStateTimer->setSingleShot(true);
d->mSaveThemeColumnStateTimer->start(200);
}
void View::resizeEvent(QResizeEvent *e)
{
qCDebug(MESSAGELIST_LOG) << "Resize event enter (viewport width is " << viewport()->width() << ")";
QTreeView::resizeEvent(e);
if (!isVisible()) {
return; // don't play with
}
if (d->mLastViewportWidth != viewport()->width()) {
triggerDelayedApplyThemeColumns();
}
if (header()->isVisible()) {
return;
}
// header invisible
bool oldSave = d->mSaveThemeColumnStateOnSectionResize;
d->mSaveThemeColumnStateOnSectionResize = false;
const int count = header()->count();
if ((count - header()->hiddenSectionCount()) < 2) {
// a single column visible: resize it
int visibleIndex;
for (visibleIndex = 0; visibleIndex < count; visibleIndex++) {
if (!header()->isSectionHidden(visibleIndex)) {
break;
}
}
if (visibleIndex < count) {
header()->resizeSection(visibleIndex, viewport()->width() - 4);
}
}
d->mSaveThemeColumnStateOnSectionResize = oldSave;
triggerDelayedSaveThemeColumnState();
}
+void View::paintEvent(QPaintEvent *event)
+{
+#if 0
+ if (/*mFirstResult &&*/ (!model() || model()->rowCount() == 0)) {
+ QPainter p(viewport());
+
+ QFont font = p.font();
+ font.setItalic(true);
+ p.setFont(font);
+
+ if (!d->mTextColor.isValid()) {
+ d->generalPaletteChanged();
+ }
+ p.setPen(d->mTextColor);
+
+ p.drawText(QRect(0, 0, width(), height()), Qt::AlignCenter, i18n("No result found"));
+ } else {
+ QTreeView::paintEvent(event);
+ }
+#else
+ QTreeView::paintEvent(event);
+#endif
+}
+
void View::modelAboutToEmitLayoutChanged()
{
// QHeaderView goes totally NUTS with a layoutChanged() call
d->mSaveThemeColumnStateOnSectionResize = false;
}
void View::modelEmittedLayoutChanged()
{
// This is after a first chunk of work has been done by the model: do apply column states
d->mSaveThemeColumnStateOnSectionResize = true;
applyThemeColumns();
}
void View::slotHeaderSectionResized(int logicalIndex, int oldWidth, int newWidth)
{
Q_UNUSED(logicalIndex);
Q_UNUSED(oldWidth);
Q_UNUSED(newWidth);
if (d->mSaveThemeColumnStateOnSectionResize) {
triggerDelayedSaveThemeColumnState();
}
}
int View::sizeHintForColumn(int logicalColumnIndex) const
{
// QTreeView: please don't touch my column widths...
int w = header()->sectionSize(logicalColumnIndex);
if (w > 0) {
return w;
}
if (!d->mDelegate) {
return 32; // dummy
}
w = d->mDelegate->sizeHintForItemTypeAndColumn(Item::Message, logicalColumnIndex).width();
return w;
}
void View::showEvent(QShowEvent *e)
{
QTreeView::showEvent(e);
}
void View::slotHeaderContextMenuRequested(const QPoint &pnt)
{
if (!d->mTheme) {
return;
}
const auto columns = d->mTheme->columns();
if (columns.isEmpty()) {
return; // bad theme
}
// the menu for the columns
QMenu menu;
int idx = 0;
for (const auto col : qAsConst(columns)) {
QAction *act = menu.addAction(col->label());
act->setCheckable(true);
act->setChecked(!header()->isSectionHidden(idx));
if (idx == 0) {
act->setEnabled(false);
}
- QObject::connect(act, &QAction::triggered, this, [this, idx] {slotShowHideColumn(idx);});
+ QObject::connect(act, &QAction::triggered, this, [this, idx] {
+ slotShowHideColumn(idx);
+ });
idx++;
}
menu.addSeparator();
{
QAction *act = menu.addAction(i18n("Adjust Column Sizes"));
QObject::connect(act, &QAction::triggered, this, &View::slotAdjustColumnSizes);
}
{
QAction *act = menu.addAction(i18n("Show Default Columns"));
QObject::connect(act, &QAction::triggered, this, &View::slotShowDefaultColumns);
}
menu.addSeparator();
{
QAction *act = menu.addAction(i18n("Display Tooltips"));
act->setCheckable(true);
act->setChecked(MessageListSettings::self()->messageToolTipEnabled());
QObject::connect(act, &QAction::triggered, this, &View::slotDisplayTooltips);
}
menu.addSeparator();
MessageList::Util::fillViewMenu(&menu, d->mWidget);
menu.exec(header()->mapToGlobal(pnt));
}
void View::slotAdjustColumnSizes()
{
if (!d->mTheme) {
return;
}
d->mTheme->resetColumnSizes();
applyThemeColumns();
}
void View::slotShowDefaultColumns()
{
if (!d->mTheme) {
return;
}
d->mTheme->resetColumnState();
applyThemeColumns();
}
void View::slotDisplayTooltips(bool showTooltips)
{
MessageListSettings::self()->setMessageToolTipEnabled(showTooltips);
}
void View::slotShowHideColumn(int columnIdx)
{
if (!d->mTheme) {
return; // oops
}
if (columnIdx == 0) {
return; // can never be hidden
}
if (columnIdx >= d->mTheme->columns().count()) {
return;
}
const bool showIt = header()->isSectionHidden(columnIdx);
Theme::Column *column = d->mTheme->columns().at(columnIdx);
Q_ASSERT(column);
// first save column state (as it is, with the column still in previous state)
saveThemeColumnState();
// If a section has just been shown, invalidate its width in the skin
// since QTreeView assigned it a (possibly insane) default width.
// If a section has been hidden, then invalidate its width anyway...
// so finally invalidate width always, here.
column->setCurrentlyVisible(showIt);
column->setCurrentWidth(-1);
// then apply theme columns to re-compute proportional widths (so we hopefully stay in the view)
applyThemeColumns();
}
Item *View::currentItem() const
{
QModelIndex idx = currentIndex();
if (!idx.isValid()) {
return nullptr;
}
Item *it = static_cast< Item * >(idx.internalPointer());
Q_ASSERT(it);
return it;
}
MessageItem *View::currentMessageItem(bool selectIfNeeded) const
{
Item *it = currentItem();
if (!it || (it->type() != Item::Message)) {
return nullptr;
}
if (selectIfNeeded) {
// Keep things coherent, if the user didn't select it, but acted on it via
// a shortcut, do select it now.
if (!selectionModel()->isSelected(currentIndex())) {
selectionModel()->select(currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows);
}
}
return static_cast< MessageItem * >(it);
}
void View::setCurrentMessageItem(MessageItem *it, bool center)
{
if (it) {
qCDebug(MESSAGELIST_LOG) << "Setting current message to" << it->subject();
const QModelIndex index = d->mModel->index(it, 0);
selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select
|QItemSelectionModel::Current | QItemSelectionModel::Rows);
if (center) {
scrollTo(index, QAbstractItemView::PositionAtCenter);
}
} else {
selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Current
|QItemSelectionModel::Clear);
}
}
bool View::selectionEmpty() const
{
return selectionModel()->selectedRows().isEmpty();
}
QList< MessageItem * > View::selectionAsMessageItemList(bool includeCollapsedChildren) const
{
QList< MessageItem * > selectedMessages;
QModelIndexList lSelected = selectionModel()->selectedRows();
if (lSelected.isEmpty()) {
return selectedMessages;
}
- for (const auto idx : qAsConst(lSelected)) {
+ for (const auto &idx : qAsConst(lSelected)) {
// The asserts below are theoretically valid but at the time
// of writing they fail because of a bug in QItemSelectionModel::selectedRows()
// which returns also non-selectable items.
//Q_ASSERT( selectedItem->type() == Item::Message );
//Q_ASSERT( ( *it ).isValid() );
if (!idx.isValid()) {
continue;
}
Item *selectedItem = static_cast<Item *>(idx.internalPointer());
Q_ASSERT(selectedItem);
if (selectedItem->type() != Item::Message) {
continue;
}
if (!static_cast<MessageItem *>(selectedItem)->isValid()) {
continue;
}
Q_ASSERT(!selectedMessages.contains(static_cast<MessageItem *>(selectedItem)));
if (includeCollapsedChildren && (selectedItem->childItemCount() > 0) && (!isExpanded(idx))) {
static_cast<MessageItem *>(selectedItem)->subTreeToList(selectedMessages);
} else {
selectedMessages.append(static_cast<MessageItem *>(selectedItem));
}
}
return selectedMessages;
}
QList<MessageItem *> View::currentThreadAsMessageItemList() const
{
QList<MessageItem *> currentThread;
MessageItem *msg = currentMessageItem();
if (!msg) {
return currentThread;
}
while (msg->parent()) {
if (msg->parent()->type() != Item::Message) {
break;
}
msg = static_cast< MessageItem * >(msg->parent());
}
msg->subTreeToList(currentThread);
return currentThread;
}
void View::setChildrenExpanded(const Item *root, bool expand)
{
Q_ASSERT(root);
auto childList = root->childItems();
if (!childList) {
return;
}
for (const auto child : qAsConst(*childList)) {
QModelIndex idx = d->mModel->index(child, 0);
Q_ASSERT(idx.isValid());
Q_ASSERT(static_cast<Item *>(idx.internalPointer()) == child);
if (expand) {
setExpanded(idx, true);
if (child->childItemCount() > 0) {
setChildrenExpanded(child, true);
}
} else {
if (child->childItemCount() > 0) {
setChildrenExpanded(child, false);
}
setExpanded(idx, false);
}
}
}
+void View::Private::generalPaletteChanged()
+{
+ const QPalette palette = q->viewport()->palette();
+ QColor color = palette.text().color();
+ color.setAlpha(128);
+ mTextColor = color;
+}
+
void View::Private::expandFullThread(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
Item *item = static_cast< Item * >(index.internalPointer());
if (item->type() != Item::Message) {
return;
}
if (!static_cast< MessageItem * >(item)->parent()
|| (static_cast< MessageItem * >(item)->parent()->type() != Item::Message)) {
q->setChildrenExpanded(item, true);
}
}
void View::setCurrentThreadExpanded(bool expand)
{
Item *it = currentItem();
if (!it) {
return;
}
if (it->type() == Item::GroupHeader) {
setExpanded(currentIndex(), expand);
} else if (it->type() == Item::Message) {
MessageItem *message = static_cast< MessageItem *>(it);
while (message->parent()) {
if (message->parent()->type() != Item::Message) {
break;
}
message = static_cast< MessageItem * >(message->parent());
}
if (expand) {
setExpanded(d->mModel->index(message, 0), true);
setChildrenExpanded(message, true);
} else {
setChildrenExpanded(message, false);
setExpanded(d->mModel->index(message, 0), false);
}
}
}
void View::setAllThreadsExpanded(bool expand)
{
scheduleDelayedItemsLayout();
if (d->mAggregation->grouping() == Aggregation::NoGrouping) {
// we have no groups so threads start under the root item: just expand/unexpand all
setChildrenExpanded(d->mModel->rootItem(), expand);
return;
}
// grouping is in effect: must expand/unexpand one level lower
auto childList = d->mModel->rootItem()->childItems();
if (!childList) {
return;
}
for (const auto item : qAsConst(*childList)) {
setChildrenExpanded(item, expand);
}
}
void View::setAllGroupsExpanded(bool expand)
{
if (d->mAggregation->grouping() == Aggregation::NoGrouping) {
return; // no grouping in effect
}
Item *item = d->mModel->rootItem();
auto childList = item->childItems();
if (!childList) {
return;
}
scheduleDelayedItemsLayout();
for (const auto item : qAsConst(*childList)) {
Q_ASSERT(item->type() == Item::GroupHeader);
QModelIndex idx = d->mModel->index(item, 0);
Q_ASSERT(idx.isValid());
Q_ASSERT(static_cast< Item * >(idx.internalPointer()) == item);
if (expand) {
if (!isExpanded(idx)) {
setExpanded(idx, true);
}
} else {
if (isExpanded(idx)) {
setExpanded(idx, false);
}
}
}
}
void View::selectMessageItems(const QList< MessageItem * > &list)
{
QItemSelection selection;
for (const auto mi : qAsConst(list)) {
Q_ASSERT(mi);
QModelIndex idx = d->mModel->index(mi, 0);
Q_ASSERT(idx.isValid());
Q_ASSERT(static_cast<MessageItem *>(idx.internalPointer()) == mi);
if (!selectionModel()->isSelected(idx)) {
selection.append(QItemSelectionRange(idx));
}
ensureDisplayedWithParentsExpanded(mi);
}
if (!selection.isEmpty()) {
selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
}
static inline bool message_type_matches(Item *item, MessageTypeFilter messageTypeFilter)
{
switch (messageTypeFilter) {
case MessageTypeAny:
return true;
break;
case MessageTypeUnreadOnly:
return !item->status().isRead();
break;
default:
// nothing here
break;
}
// never reached
Q_ASSERT(false);
return false;
}
Item *View::messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
{
if (!storageModel()) {
return nullptr; // no folder
}
// find the item to start with
Item *below;
if (referenceItem) {
// there was a current item: we start just below it
if (
(referenceItem->childItemCount() > 0)
&&
(
(messageTypeFilter != MessageTypeAny)
||
isExpanded(d->mModel->index(referenceItem, 0))
)
) {
// the current item had children: either expanded or we want unread/new messages (and so we'll expand it if it isn't)
below = referenceItem->itemBelow();
} else {
// the current item had no children: ask the parent to find the item below
Q_ASSERT(referenceItem->parent());
below = referenceItem->parent()->itemBelowChild(referenceItem);
}
if (!below) {
// reached the end
if (loop) {
// try re-starting from top
below = d->mModel->rootItem()->itemBelow();
Q_ASSERT(below); // must exist (we had a current item)
if (below == referenceItem) {
return nullptr; // only one item in folder: loop complete
}
} else {
// looping not requested
return nullptr;
}
}
} else {
// there was no current item, start from beginning
below = d->mModel->rootItem()->itemBelow();
if (!below) {
return nullptr; // folder empty
}
}
// ok.. now below points to the next message.
// While it doesn't satisfy our requirements, go further down
QModelIndex parentIndex = d->mModel->index(below->parent(), 0);
QModelIndex belowIndex = d->mModel->index(below, 0);
Q_ASSERT(belowIndex.isValid());
while (
// is not a message (we want messages, don't we ?)
(below->type() != Item::Message)
||// message filter doesn't match
(!message_type_matches(below, messageTypeFilter))
||// is hidden (and we don't want hidden items as they arent "officially" in the view)
isRowHidden(belowIndex.row(), parentIndex)
||// is not enabled or not selectable
((d->mModel->flags(belowIndex) & (Qt::ItemIsSelectable | Qt::ItemIsEnabled)) != (Qt::ItemIsSelectable | Qt::ItemIsEnabled))
) {
// find the next one
if ((below->childItemCount() > 0) && ((messageTypeFilter != MessageTypeAny) || isExpanded(belowIndex))) {
// the current item had children: either expanded or we want unread messages (and so we'll expand it if it isn't)
below = below->itemBelow();
} else {
// the current item had no children: ask the parent to find the item below
Q_ASSERT(below->parent());
below = below->parent()->itemBelowChild(below);
}
if (!below) {
// we reached the end of the folder
if (loop) {
// looping requested
if (referenceItem) { // <-- this means "we have started from something that is not the top: looping makes sense"
below = d->mModel->rootItem()->itemBelow();
}
// else mi == 0 and below == 0: we have started from the beginning and reached the end (it will fail the test below and exit)
} else {
// looping not requested: nothing more to do
return nullptr;
}
}
if (below == referenceItem) {
Q_ASSERT(loop);
return nullptr; // looped and returned back to the first message
}
parentIndex = d->mModel->index(below->parent(), 0);
belowIndex = d->mModel->index(below, 0);
Q_ASSERT(belowIndex.isValid());
}
return below;
}
Item *View::firstMessageItem(MessageTypeFilter messageTypeFilter)
{
return messageItemAfter(nullptr, messageTypeFilter, false);
}
Item *View::nextMessageItem(MessageTypeFilter messageTypeFilter, bool loop)
{
return messageItemAfter(currentMessageItem(false), messageTypeFilter, loop);
}
Item *View::deepestExpandedChild(Item *referenceItem) const
{
const int children = referenceItem->childItemCount();
if (children > 0
&& isExpanded(d->mModel->index(referenceItem, 0))) {
return deepestExpandedChild(referenceItem->childItem(children - 1));
} else {
return referenceItem;
}
}
Item *View::messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop)
{
if (!storageModel()) {
return nullptr; // no folder
}
// find the item to start with
Item *above;
if (referenceItem) {
Item *parent = referenceItem->parent();
Item *siblingAbove = parent
? parent->itemAboveChild(referenceItem) : nullptr;
// there was a current item: we start just above it
if ((siblingAbove && siblingAbove != referenceItem && siblingAbove != parent)
&& (siblingAbove->childItemCount() > 0)
&& (
(messageTypeFilter != MessageTypeAny)
|| (isExpanded(d->mModel->index(siblingAbove, 0)))
)
) {
// the current item had children: either expanded or we want unread/new messages (and so we'll expand it if it isn't)
above = deepestExpandedChild(siblingAbove);
} else {
// the current item had no children: ask the parent to find the item above
Q_ASSERT(referenceItem->parent());
above = referenceItem->parent()->itemAboveChild(referenceItem);
}
if ((!above) || (above == d->mModel->rootItem())) {
// reached the beginning
if (loop) {
// try re-starting from bottom
above = d->mModel->rootItem()->deepestItem();
Q_ASSERT(above); // must exist (we had a current item)
Q_ASSERT(above != d->mModel->rootItem());
if (above == referenceItem) {
return nullptr; // only one item in folder: loop complete
}
} else {
// looping not requested
return nullptr;
}
}
} else {
// there was no current item, start from end
above = d->mModel->rootItem()->deepestItem();
if (!above || !above->parent() || (above == d->mModel->rootItem())) {
return nullptr; // folder empty
}
}
// ok.. now below points to the previous message.
// While it doesn't satisfy our requirements, go further up
QModelIndex parentIndex = d->mModel->index(above->parent(), 0);
QModelIndex aboveIndex = d->mModel->index(above, 0);
Q_ASSERT(aboveIndex.isValid());
while (
// is not a message (we want messages, don't we ?)
(above->type() != Item::Message)
||// message filter doesn't match
(!message_type_matches(above, messageTypeFilter))
||// we don't expand items but the item has parents unexpanded (so should be skipped)
(
// !expand items
(messageTypeFilter == MessageTypeAny)
&&// has unexpanded parents or is itself hidden
(!isDisplayedWithParentsExpanded(above))
)
||// is hidden
isRowHidden(aboveIndex.row(), parentIndex)
||// is not enabled or not selectable
((d->mModel->flags(aboveIndex) & (Qt::ItemIsSelectable | Qt::ItemIsEnabled)) != (Qt::ItemIsSelectable | Qt::ItemIsEnabled))
) {
above = above->itemAbove();
if ((!above) || (above == d->mModel->rootItem())) {
// reached the beginning
if (loop) {
// looping requested
if (referenceItem) { // <-- this means "we have started from something that is not the beginning: looping makes sense"
above = d->mModel->rootItem()->deepestItem();
}
// else mi == 0 and above == 0: we have started from the end and reached the beginning (it will fail the test below and exit)
} else {
// looping not requested: nothing more to do
return nullptr;
}
}
if (above == referenceItem) {
Q_ASSERT(loop);
return nullptr; // looped and returned back to the first message
}
if (!above->parent()) {
return nullptr;
}
parentIndex = d->mModel->index(above->parent(), 0);
aboveIndex = d->mModel->index(above, 0);
Q_ASSERT(aboveIndex.isValid());
}
return above;
}
Item *View::lastMessageItem(MessageTypeFilter messageTypeFilter)
{
return messageItemBefore(nullptr, messageTypeFilter, false);
}
Item *View::previousMessageItem(MessageTypeFilter messageTypeFilter, bool loop)
{
return messageItemBefore(currentMessageItem(false), messageTypeFilter, loop);
}
void View::growOrShrinkExistingSelection(const QModelIndex &newSelectedIndex, bool movingUp)
{
// Qt: why visualIndex() is private? ...I'd really need it here...
int selectedVisualCoordinate = visualRect(newSelectedIndex).top();
int topVisualCoordinate = 0xfffffff; // huuuuuge number
int bottomVisualCoordinate = -(0xfffffff);
int candidate;
QModelIndex bottomIndex;
QModelIndex topIndex;
// find out the actual selection range
const QItemSelection selection = selectionModel()->selection();
for (const QItemSelectionRange &range : selection) {
// We're asking the model for the index as range.topLeft() and range.bottomRight()
// can return indexes in invisible columns which have a null visualRect().
// Column 0, instead, is always visible.
QModelIndex top = d->mModel->index(range.top(), 0, range.parent());
QModelIndex bottom = d->mModel->index(range.bottom(), 0, range.parent());
if (top.isValid()) {
if (!bottom.isValid()) {
bottom = top;
}
} else {
if (!top.isValid()) {
top = bottom;
}
}
candidate = visualRect(bottom).bottom();
if (candidate > bottomVisualCoordinate) {
bottomVisualCoordinate = candidate;
bottomIndex = range.bottomRight();
}
candidate = visualRect(top).top();
if (candidate < topVisualCoordinate) {
topVisualCoordinate = candidate;
topIndex = range.topLeft();
}
}
if (topIndex.isValid() && bottomIndex.isValid()) {
if (movingUp) {
if (selectedVisualCoordinate < topVisualCoordinate) {
// selecting something above the top: grow selection
selectionModel()->select(newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
} else {
// selecting something below the top: shrink selection
const QModelIndexList selectedIndexes = selection.indexes();
for (const QModelIndex &idx : selectedIndexes) {
if ((idx.column() == 0) && (visualRect(idx).top() > selectedVisualCoordinate)) {
selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::Deselect);
}
}
}
} else {
if (selectedVisualCoordinate > bottomVisualCoordinate) {
// selecting something below bottom: grow selection
selectionModel()->select(newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
} else {
// selecting something above bottom: shrink selection
const QModelIndexList selectedIndexes = selection.indexes();
for (const QModelIndex &idx : selectedIndexes) {
if ((idx.column() == 0) && (visualRect(idx).top() < selectedVisualCoordinate)) {
selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::Deselect);
}
}
}
}
} else {
// no existing selection, just grow
selectionModel()->select(newSelectedIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
}
}
bool View::selectNextMessageItem(
MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
Item *it = nextMessageItem(messageTypeFilter, loop);
if (!it) {
return false;
}
if (it->parent() != d->mModel->rootItem()) {
ensureDisplayedWithParentsExpanded(it);
}
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
switch (existingSelectionBehaviour) {
case ExpandExistingSelection:
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::Select);
break;
case GrowOrShrinkExistingSelection:
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
growOrShrinkExistingSelection(idx, false);
break;
default:
//case ClearExistingSelection:
setCurrentIndex(idx);
break;
}
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
bool View::selectPreviousMessageItem(
MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
Item *it = previousMessageItem(messageTypeFilter, loop);
if (!it) {
return false;
}
if (it->parent() != d->mModel->rootItem()) {
ensureDisplayedWithParentsExpanded(it);
}
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
switch (existingSelectionBehaviour) {
case ExpandExistingSelection:
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::Select);
break;
case GrowOrShrinkExistingSelection:
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
growOrShrinkExistingSelection(idx, true);
break;
default:
//case ClearExistingSelection:
setCurrentIndex(idx);
break;
}
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
bool View::focusNextMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
Item *it = nextMessageItem(messageTypeFilter, loop);
if (!it) {
return false;
}
if (it->parent() != d->mModel->rootItem()) {
ensureDisplayedWithParentsExpanded(it);
}
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
bool View::focusPreviousMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
Item *it = previousMessageItem(messageTypeFilter, loop);
if (!it) {
return false;
}
if (it->parent() != d->mModel->rootItem()) {
ensureDisplayedWithParentsExpanded(it);
}
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
selectionModel()->setCurrentIndex(idx, QItemSelectionModel::NoUpdate);
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
void View::selectFocusedMessageItem(bool centerItem)
{
QModelIndex idx = currentIndex();
if (!idx.isValid()) {
return;
}
if (selectionModel()->isSelected(idx)) {
return;
}
selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows);
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
}
bool View::selectFirstMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem)
{
if (!storageModel()) {
return false; // nothing to do
}
Item *it = firstMessageItem(messageTypeFilter);
if (!it) {
return false;
}
Q_ASSERT(it != d->mModel->rootItem()); // must never happen (obviously)
ensureDisplayedWithParentsExpanded(it);
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
setCurrentIndex(idx);
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
bool View::selectLastMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem)
{
if (!storageModel()) {
return false;
}
Item *it = lastMessageItem(messageTypeFilter);
if (!it) {
return false;
}
Q_ASSERT(it != d->mModel->rootItem());
ensureDisplayedWithParentsExpanded(it);
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
setCurrentIndex(idx);
if (centerItem) {
scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
return true;
}
void View::modelFinishedLoading()
{
Q_ASSERT(storageModel());
Q_ASSERT(!d->mModel->isLoading());
// nothing here for now :)
}
MessageItemSetReference View::createPersistentSet(const QList< MessageItem * > &items)
{
return d->mModel->createPersistentSet(items);
}
QList< MessageItem * > View::persistentSetCurrentMessageItemList(MessageItemSetReference ref)
{
return d->mModel->persistentSetCurrentMessageItemList(ref);
}
void View::deletePersistentSet(MessageItemSetReference ref)
{
d->mModel->deletePersistentSet(ref);
}
void View::markMessageItemsAsAboutToBeRemoved(QList<MessageItem *> &items, bool bMark)
{
if (!bMark) {
for (const auto mi : qAsConst(items)) {
if (mi->isValid()) { // hasn't been removed in the meantime
mi->setAboutToBeRemoved(false);
}
}
viewport()->update();
return;
}
// ok.. we're going to mark the messages as "about to be deleted".
// This means that we're going to make them non selectable.
// What happens to the selection is generally an untrackable big mess.
// Several components and entities are involved.
// Qutie tries to apply some kind of internal logic in order to keep
// "something" selected and "something" (else) to be current.
// The results sometimes appear to depend on the current moon phase.
// The Model will do crazy things in order to preserve the current
// selection (and possibly the current item). If it's impossible then
// it will make its own guesses about what should be selected next.
// A problem is that the Model will do it one message at a time.
// When item reparenting/reordering is involved then the guesses
// can produce non-intuitive results.
// Add the fact that selection and current item are distinct concepts,
// their relative interaction depends on the settings and is often quite
// unclear.
// Add the fact that (at the time of writing) several styles don't show
// the current item (only Yoda knows why) and this causes some confusion to the user.
// Add the fact that the operations are asynchronous: deletion will start
// a job, do some event loop processing and then complete the work at a later time.
// The Qutie views also tend to accumulate the changes and perform them
// all at once at the latest possible stage.
// A radical approach is needed: we FIRST deal with the selection
// by tring to move it away from the messages about to be deleted
// and THEN mark the (hopefully no longer selected) messages as "about to be deleted".
// First of all, find out if we're going to clear the entire selection (very likely).
bool clearingEntireSelection = true;
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
if (selectedIndexes.count() > items.count()) {
// the selection is bigger: we can't clear it completely
clearingEntireSelection = false;
} else {
// the selection has same size or is smaller: we can clear it completely with our removal
for (const QModelIndex &selectedIndex : selectedIndexes) {
Q_ASSERT(selectedIndex.isValid());
Q_ASSERT(selectedIndex.column() == 0);
Item *selectedItem = static_cast< Item * >(selectedIndex.internalPointer());
Q_ASSERT(selectedItem);
if (selectedItem->type() != Item::Message) {
continue;
}
if (!items.contains(static_cast< MessageItem * >(selectedItem))) {
// the selection contains something that we aren't going to remove:
// we will not clear the selection completely
clearingEntireSelection = false;
break;
}
}
}
if (clearingEntireSelection) {
// Try to clear the current selection and select something sensible instead,
// so after the deletion we will not end up with a random selection.
// Pick up a message in the set (which is very likely to be contiguous), walk the tree
// and select the next message that is NOT in the set.
MessageItem *aMessage = items.last();
Q_ASSERT(aMessage);
// Avoid infinite loops by carrying only a limited number of attempts.
- // If there is any message that is not in the set then items.count() attemps should find it.
+ // If there is any message that is not in the set then items.count() attempts should find it.
int maxAttempts = items.count();
while (items.contains(aMessage) && (maxAttempts > 0)) {
Item *next = messageItemAfter(aMessage, MessageTypeAny, false);
if (!next) {
// no way
aMessage = nullptr;
break;
}
Q_ASSERT(next->type() == Item::Message);
aMessage = static_cast< MessageItem * >(next);
maxAttempts--;
}
if (!aMessage) {
// try backwards
aMessage = items.first();
Q_ASSERT(aMessage);
maxAttempts = items.count();
while (items.contains(aMessage) && (maxAttempts > 0)) {
Item *prev = messageItemBefore(aMessage, MessageTypeAny, false);
if (!prev) {
// no way
aMessage = nullptr;
break;
}
Q_ASSERT(prev->type() == Item::Message);
aMessage = static_cast< MessageItem * >(prev);
maxAttempts--;
}
}
if (aMessage) {
QModelIndex aMessageIndex = d->mModel->index(aMessage, 0);
Q_ASSERT(aMessageIndex.isValid());
Q_ASSERT(static_cast< MessageItem * >(aMessageIndex.internalPointer()) == aMessage);
Q_ASSERT(!selectionModel()->isSelected(aMessageIndex));
setCurrentIndex(aMessageIndex);
selectionModel()->select(aMessageIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
} // else we aren't clearing the entire selection so something should just stay selected.
// Now mark messages as about to be removed.
for (const auto mi : qAsConst(items)) {
mi->setAboutToBeRemoved(true);
QModelIndex idx = d->mModel->index(mi, 0);
Q_ASSERT(idx.isValid());
Q_ASSERT(static_cast< MessageItem * >(idx.internalPointer()) == mi);
if (selectionModel()->isSelected(idx)) {
selectionModel()->select(idx, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
}
}
viewport()->update();
}
void View::ensureDisplayedWithParentsExpanded(Item *it)
{
Q_ASSERT(it);
Q_ASSERT(it->parent());
Q_ASSERT(it->isViewable()); // must be attached to the viewable root
if (isRowHidden(it->parent()->indexOfChildItem(it), d->mModel->index(it->parent(), 0))) {
setRowHidden(it->parent()->indexOfChildItem(it), d->mModel->index(it->parent(), 0), false);
}
it = it->parent();
while (it->parent()) {
if (isRowHidden(it->parent()->indexOfChildItem(it), d->mModel->index(it->parent(), 0))) {
setRowHidden(it->parent()->indexOfChildItem(it), d->mModel->index(it->parent(), 0), false);
}
QModelIndex idx = d->mModel->index(it, 0);
Q_ASSERT(idx.isValid());
Q_ASSERT(static_cast< Item * >(idx.internalPointer()) == it);
if (!isExpanded(idx)) {
setExpanded(idx, true);
}
it = it->parent();
}
}
bool View::isDisplayedWithParentsExpanded(Item *it) const
{
// An item is currently viewable iff
// - it is marked as viewable in the item structure (that is, qt knows about its existence)
// (and this means that all of its parents are marked as viewable)
// - it is not explicitly hidden
// - all of its parents are expanded
if (!it) {
return false; // be nice and allow the caller not to care
}
if (!it->isViewable()) {
return false; // item not viewable (not attached to the viewable root or qt not yet aware of it)
}
// the item and all the parents are marked as viewable.
if (isRowHidden(it->parent()->indexOfChildItem(it), d->mModel->index(it->parent(), 0))) {
return false; // item qt representation explicitly hidden
}
// the item (and theoretically all the parents) are not explicitly hidden
// check the parent chain
it = it->parent();
while (it) {
if (it == d->mModel->rootItem()) {
return true; // parent is root item: ok
}
// parent is not root item
if (!isExpanded(d->mModel->index(it, 0))) {
return false; // parent is not expanded (so child not actually visible)
}
it = it->parent(); // climb up
}
// parent hierarchy interrupted somewhere
return false;
}
bool View::isThreaded() const
{
if (!d->mAggregation) {
return false;
}
return d->mAggregation->threading() != Aggregation::NoThreading;
}
void View::slotSelectionChanged(const QItemSelection &, const QItemSelection &)
{
// We assume that when selection changes, current item also changes.
QModelIndex current = currentIndex();
if (!current.isValid()) {
d->mLastCurrentItem = nullptr;
d->mWidget->viewMessageSelected(nullptr);
d->mWidget->viewSelectionChanged();
return;
}
if (!selectionModel()->isSelected(current)) {
if (selectedIndexes().count() < 1) {
// It may happen after row removals: Model calls this slot on currentIndex()
// that actually might have changed "silently", without being selected.
QItemSelection selection;
selection.append(QItemSelectionRange(current));
selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows);
return; // the above recurses
} else {
// something is still selected anyway
// This is probably a result of CTRL+Click which unselected current: leave it as it is.
return;
}
}
Item *it = static_cast< Item * >(current.internalPointer());
Q_ASSERT(it);
switch (it->type()) {
case Item::Message:
if (d->mLastCurrentItem != it) {
qCDebug(MESSAGELIST_LOG) << "View message selected [" << static_cast< MessageItem * >(it)->subject() << "]";
d->mWidget->viewMessageSelected(static_cast< MessageItem * >(it));
d->mLastCurrentItem = it;
}
break;
case Item::GroupHeader:
if (d->mLastCurrentItem) {
d->mWidget->viewMessageSelected(nullptr);
d->mLastCurrentItem = nullptr;
}
break;
default:
// should never happen
Q_ASSERT(false);
break;
}
d->mWidget->viewSelectionChanged();
}
void View::mouseDoubleClickEvent(QMouseEvent *e)
{
// Perform a hit test
if (!d->mDelegate->hitTest(e->pos(), true)) {
return;
}
// Something was hit :)
Item *it = static_cast< Item * >(d->mDelegate->hitItem());
if (!it) {
return; // should never happen
}
switch (it->type()) {
case Item::Message:
// Let QTreeView handle the expansion
QTreeView::mousePressEvent(e);
switch (e->button()) {
case Qt::LeftButton:
if (d->mDelegate->hitContentItem()) {
- // Double clikcking on clickable icons does NOT activate the message
+ // Double clicking on clickable icons does NOT activate the message
if (d->mDelegate->hitContentItem()->isIcon() && d->mDelegate->hitContentItem()->isClickable()) {
return;
}
}
d->mWidget->viewMessageActivated(static_cast< MessageItem * >(it));
break;
default:
// make gcc happy
break;
}
break;
case Item::GroupHeader:
- // Don't let QTreeView handle the selection (as it deselects the curent messages)
+ // Don't let QTreeView handle the selection (as it deselects the current messages)
switch (e->button()) {
case Qt::LeftButton:
if (it->childItemCount() > 0) {
// toggle expanded state
setExpanded(d->mDelegate->hitIndex(), !isExpanded(d->mDelegate->hitIndex()));
}
break;
default:
// make gcc happy
break;
}
break;
default:
// should never happen
Q_ASSERT(false);
break;
}
}
void View::changeMessageStatusRead(MessageItem *it, bool read)
{
Akonadi::MessageStatus set = it->status();
Akonadi::MessageStatus unset = it->status();
if (read) {
set.setRead(true);
unset.setRead(false);
} else {
set.setRead(false);
unset.setRead(true);
}
viewport()->update();
// This will actually request the widget to perform a status change on the storage.
// The request will be then processed by the Model and the message will be updated again.
d->mWidget->viewMessageStatusChangeRequest(it, set, unset);
}
void View::changeMessageStatus(MessageItem *it, Akonadi::MessageStatus set, Akonadi::MessageStatus unset)
{
// We first change the status of MessageItem itself. This will make the change
// visible to the user even if the Model is actually in the middle of a long job (maybe it's loading)
// and can't process the status change request immediately.
// Here we actually desynchronize the cache and trust that the later call to
// d->mWidget->viewMessageStatusChangeRequest() will really perform the status change on the storage.
// Well... in KMail it will unless something is really screwed. Anyway, if it will not, at the next
// load the status will be just unchanged: no animals will be harmed.
qint32 stat = it->status().toQInt32();
stat |= set.toQInt32();
stat &= ~(unset.toQInt32());
Akonadi::MessageStatus status;
status.fromQInt32(stat);
it->setStatus(status);
// Trigger an update so the immediate change will be shown to the user
viewport()->update();
// This will actually request the widget to perform a status change on the storage.
// The request will be then processed by the Model and the message will be updated again.
d->mWidget->viewMessageStatusChangeRequest(it, set, unset);
}
void View::mousePressEvent(QMouseEvent *e)
{
d->mMousePressPosition = QPoint();
// Perform a hit test
if (!d->mDelegate->hitTest(e->pos(), true)) {
return;
}
// Something was hit :)
Item *it = static_cast< Item * >(d->mDelegate->hitItem());
if (!it) {
return; // should never happen
}
// Abort any pending message pre-selection as the user is probably
// already navigating the view (so pre-selection would make his view jump
// to an unexpected place).
d->mModel->setPreSelectionMode(PreSelectNone);
switch (it->type()) {
case Item::Message:
d->mMousePressPosition = e->pos();
switch (e->button()) {
case Qt::LeftButton:
// if we have multi selection then the meaning of hitting
// the content item is quite unclear.
if (d->mDelegate->hitContentItem() && (selectedIndexes().count() > 1)) {
qCDebug(MESSAGELIST_LOG) << "Left hit with selectedIndexes().count() == " << selectedIndexes().count();
switch (d->mDelegate->hitContentItem()->type()) {
case Theme::ContentItem::AnnotationIcon:
static_cast< MessageItem * >(it)->editAnnotation(this);
return; // don't select the item
break;
case Theme::ContentItem::ActionItemStateIcon:
changeMessageStatus(
static_cast< MessageItem * >(it),
it->status().isToAct() ? Akonadi::MessageStatus() : Akonadi::MessageStatus::statusToAct(),
it->status().isToAct() ? Akonadi::MessageStatus::statusToAct() : Akonadi::MessageStatus()
);
return; // don't select the item
break;
case Theme::ContentItem::ImportantStateIcon:
changeMessageStatus(
static_cast< MessageItem * >(it),
it->status().isImportant() ? Akonadi::MessageStatus() : Akonadi::MessageStatus::statusImportant(),
it->status().isImportant() ? Akonadi::MessageStatus::statusImportant() : Akonadi::MessageStatus()
);
return; // don't select the item
case Theme::ContentItem::ReadStateIcon:
changeMessageStatusRead(static_cast< MessageItem * >(it), it->status().isRead() ? false : true);
return;
break;
case Theme::ContentItem::SpamHamStateIcon:
changeMessageStatus(
static_cast< MessageItem * >(it),
it->status().isSpam() ? Akonadi::MessageStatus() : (it->status().isHam() ? Akonadi::MessageStatus::statusSpam() : Akonadi::MessageStatus::statusHam()),
it->status().isSpam() ? Akonadi::MessageStatus::statusSpam() : (it->status().isHam() ? Akonadi::MessageStatus::statusHam() : Akonadi::MessageStatus())
);
return; // don't select the item
break;
case Theme::ContentItem::WatchedIgnoredStateIcon:
changeMessageStatus(
static_cast< MessageItem * >(it),
it->status().isIgnored() ? Akonadi::MessageStatus() : (it->status().isWatched() ? Akonadi::MessageStatus::statusIgnored() : Akonadi::MessageStatus::statusWatched()),
it->status().isIgnored() ? Akonadi::MessageStatus::statusIgnored() : (it->status().isWatched() ? Akonadi::MessageStatus::statusWatched() : Akonadi::MessageStatus())
);
return; // don't select the item
break;
default:
// make gcc happy
break;
}
}
// Let QTreeView handle the selection and Q_EMIT the appropriate signals (slotSelectionChanged() may be called)
QTreeView::mousePressEvent(e);
break;
case Qt::RightButton:
// Let QTreeView handle the selection and Q_EMIT the appropriate signals (slotSelectionChanged() may be called)
QTreeView::mousePressEvent(e);
e->accept();
d->mWidget->viewMessageListContextPopupRequest(selectionAsMessageItemList(), viewport()->mapToGlobal(e->pos()));
break;
default:
// make gcc happy
break;
}
break;
case Item::GroupHeader:
{
- // Don't let QTreeView handle the selection (as it deselects the curent messages)
+ // Don't let QTreeView handle the selection (as it deselects the current messages)
GroupHeaderItem *groupHeaderItem = static_cast< GroupHeaderItem * >(it);
switch (e->button()) {
case Qt::LeftButton:
{
QModelIndex index = d->mModel->index(groupHeaderItem, 0);
if (index.isValid()) {
setCurrentIndex(index);
}
if (!d->mDelegate->hitContentItem()) {
return;
}
if (d->mDelegate->hitContentItem()->type() == Theme::ContentItem::ExpandedStateIcon) {
if (groupHeaderItem->childItemCount() > 0) {
// toggle expanded state
setExpanded(d->mDelegate->hitIndex(), !isExpanded(d->mDelegate->hitIndex()));
}
}
break;
}
case Qt::RightButton:
d->mWidget->viewGroupHeaderContextPopupRequest(groupHeaderItem, viewport()->mapToGlobal(e->pos()));
break;
default:
// make gcc happy
break;
}
break;
}
default:
// should never happen
Q_ASSERT(false);
break;
}
}
void View::mouseMoveEvent(QMouseEvent *e)
{
if (!(e->buttons() & Qt::LeftButton)) {
QTreeView::mouseMoveEvent(e);
return;
}
if (d->mMousePressPosition.isNull()) {
return;
}
if ((e->pos() - d->mMousePressPosition).manhattanLength() <= QApplication::startDragDistance()) {
return;
}
d->mWidget->viewStartDragRequest();
}
#if 0
void View::contextMenuEvent(QContextMenuEvent *e)
{
Q_UNUSED(e);
QModelIndex index = currentIndex();
if (index.isValid()) {
QRect indexRect = this->visualRect(index);
QPoint pos;
if ((indexRect.isValid()) && (indexRect.bottom() > 0)) {
if (indexRect.bottom() > viewport()->height()) {
if (indexRect.top() <= viewport()->height()) {
pos = indexRect.topLeft();
}
} else {
pos = indexRect.bottomLeft();
}
}
Item *item = static_cast< Item * >(index.internalPointer());
if (item) {
if (item->type() == Item::GroupHeader) {
d->mWidget->viewGroupHeaderContextPopupRequest(static_cast< GroupHeaderItem * >(item), viewport()->mapToGlobal(pos));
} else if (!selectionEmpty()) {
d->mWidget->viewMessageListContextPopupRequest(selectionAsMessageItemList(), viewport()->mapToGlobal(pos));
e->accept();
}
}
}
}
#endif
void View::dragEnterEvent(QDragEnterEvent *e)
{
d->mWidget->viewDragEnterEvent(e);
}
void View::dragMoveEvent(QDragMoveEvent *e)
{
d->mWidget->viewDragMoveEvent(e);
}
void View::dropEvent(QDropEvent *e)
{
d->mWidget->viewDropEvent(e);
}
void View::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::FontChange:
d->mDelegate->generalFontChanged();
Q_FALLTHROUGH();
case QEvent::PaletteChange:
case QEvent::StyleChange:
case QEvent::LayoutDirectionChange:
case QEvent::LocaleChange:
case QEvent::LanguageChange:
// All of these affect the theme's internal cache.
setTheme(d->mTheme);
// A layoutChanged() event will screw up the view state a bit.
// Since this is a rare event we just reload the view.
reload();
break;
default:
// make gcc happy by default
break;
}
QTreeView::changeEvent(e);
}
bool View::event(QEvent *e)
{
// We catch ToolTip events and pass everything else
if (e->type() != QEvent::ToolTip) {
return QTreeView::event(e);
}
if (!MessageListSettings::self()->messageToolTipEnabled()) {
return true; // don't display tooltips
}
QHelpEvent *he = dynamic_cast< QHelpEvent * >(e);
if (!he) {
return true; // eh ?
}
QPoint pnt = viewport()->mapFromGlobal(mapToGlobal(he->pos()));
if (pnt.y() < 0) {
return true; // don't display the tooltip for items hidden under the header
}
QModelIndex idx = indexAt(pnt);
if (!idx.isValid()) {
return true; // may be
}
Item *it = static_cast< Item * >(idx.internalPointer());
if (!it) {
return true; // hum
}
Q_ASSERT(storageModel());
QColor bckColor = palette().color(QPalette::ToolTipBase);
QColor txtColor = palette().color(QPalette::ToolTipText);
QColor darkerColor(
((bckColor.red() * 8) + (txtColor.red() * 2)) / 10,
((bckColor.green() * 8) + (txtColor.green() * 2)) / 10,
((bckColor.blue() * 8) + (txtColor.blue() * 2)) / 10
);
QString bckColorName = bckColor.name();
QString txtColorName = txtColor.name();
QString darkerColorName = darkerColor.name();
const bool textIsLeftToRight = (QApplication::layoutDirection() == Qt::LeftToRight);
const QString textDirection = textIsLeftToRight ? QStringLiteral("left") : QStringLiteral("right");
QString tip = QStringLiteral(
"<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">"
);
switch (it->type()) {
case Item::Message:
{
MessageItem *mi = static_cast< MessageItem * >(it);
tip += QStringLiteral(
"<tr>" \
"<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">" \
"<div style=\"color: %2; font-weight: bold;\">" \
"%3" \
"</div>" \
"</td>" \
"</tr>"
).arg(txtColorName, bckColorName, mi->subject().toHtmlEscaped(), textDirection);
tip += QLatin1String(
"<tr>" \
"<td align=\"center\" valign=\"middle\">" \
"<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">"
);
const QString htmlCodeForStandardRow = QStringLiteral(
"<tr>" \
"<td align=\"right\" valign=\"top\" width=\"45\">" \
"<div style=\"font-weight: bold;\"><nobr>" \
"%1:" \
"</nobr></div>" \
"</td>" \
"<td align=\"left\" valign=\"top\">" \
"%2" \
"</td>" \
"</tr>");
if (textIsLeftToRight) {
tip += htmlCodeForStandardRow.arg(i18n("From"), mi->displaySender().toHtmlEscaped());
tip += htmlCodeForStandardRow.arg(i18nc("Receiver of the email", "To"), mi->displayReceiver().toHtmlEscaped());
tip += htmlCodeForStandardRow.arg(i18n("Date"), mi->formattedDate());
} else {
tip += htmlCodeForStandardRow.arg(mi->displaySender().toHtmlEscaped(), i18n("From"));
tip += htmlCodeForStandardRow.arg(mi->displayReceiver().toHtmlEscaped(), i18nc("Receiver of the email", "To"));
tip += htmlCodeForStandardRow.arg(mi->formattedDate(), i18n("Date"));
}
QString status = mi->statusDescription();
const QString tags = mi->tagListDescription();
if (!tags.isEmpty()) {
if (!status.isEmpty()) {
status += QLatin1String(", ");
}
status += tags;
}
if (textIsLeftToRight) {
tip += htmlCodeForStandardRow.arg(i18n("Status")).arg(status);
tip += htmlCodeForStandardRow.arg(i18n("Size")).arg(mi->formattedSize());
} else {
tip += htmlCodeForStandardRow.arg(status).arg(i18n("Status"));
tip += htmlCodeForStandardRow.arg(mi->formattedSize()).arg(i18n("Size"));
}
if (mi->hasAnnotation()) {
if (textIsLeftToRight) {
tip += htmlCodeForStandardRow.arg(i18n("Note"), mi->annotation().replace(QLatin1Char('\n'), QStringLiteral("<br>")));
} else {
tip += htmlCodeForStandardRow.arg(mi->annotation().replace(QLatin1Char('\n'), QStringLiteral("<br>"))).arg(i18n("Note"));
}
}
QString content = MessageList::Util::contentSummary(mi->akonadiItem());
if (!content.trimmed().isEmpty()) {
if (textIsLeftToRight) {
tip += htmlCodeForStandardRow.arg(i18n("Preview"), content.replace(QLatin1Char('\n'), QStringLiteral("<br>")));
} else {
tip += htmlCodeForStandardRow.arg(content.replace(QLatin1Char('\n'), QStringLiteral("<br>"))).arg(i18n("Preview"));
}
}
tip += QLatin1String(
"</table>" \
"</td>" \
"</tr>"
);
// FIXME: Find a way to show also CC and other header fields ?
if (mi->hasChildren()) {
Item::ChildItemStats stats;
mi->childItemStats(stats);
QString statsText;
statsText = i18np("<b>%1</b> reply", "<b>%1</b> replies", mi->childItemCount());
statsText += QLatin1String(", ");
statsText += i18np(
"<b>%1</b> message in subtree (<b>%2</b> unread)",
"<b>%1</b> messages in subtree (<b>%2</b> unread)",
stats.mTotalChildCount,
stats.mUnreadChildCount
);
tip += QStringLiteral(
"<tr>" \
"<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">" \
"<nobr>%2</nobr>" \
"</td>" \
"</tr>"
).arg(darkerColorName).arg(statsText).arg(textDirection);
}
break;
}
case Item::GroupHeader:
{
GroupHeaderItem *ghi = static_cast< GroupHeaderItem * >(it);
tip += QStringLiteral(
"<tr>" \
"<td bgcolor=\"%1\" align=\"%4\" valign=\"middle\">" \
"<div style=\"color: %2; font-weight: bold;\">" \
"%3" \
"</div>" \
"</td>" \
"</tr>"
).arg(txtColorName).arg(bckColorName).arg(ghi->label()).arg(textDirection);
QString description;
switch (d->mAggregation->grouping()) {
case Aggregation::GroupByDate:
if (d->mAggregation->threading() != Aggregation::NoThreading) {
switch (d->mAggregation->threadLeader()) {
case Aggregation::TopmostMessage:
if (ghi->label().contains(QRegularExpression(QStringLiteral("[0-9]")))) {
description = i18nc(
"@info:tooltip Formats to something like 'Threads started on 2008-12-21'",
"Threads started on %1",
ghi->label()
);
} else {
description = i18nc(
"@info:tooltip Formats to something like 'Threads started Yesterday'",
"Threads started %1",
ghi->label()
);
}
break;
case Aggregation::MostRecentMessage:
description = i18n("Threads with messages dated %1", ghi->label());
break;
default:
// nuthin, make gcc happy
break;
}
} else {
if (ghi->label().contains(QRegularExpression(QStringLiteral("[0-9]")))) {
if (storageModel()->containsOutboundMessages()) {
description = i18nc(
"@info:tooltip Formats to something like 'Messages sent on 2008-12-21'",
"Messages sent on %1",
ghi->label()
);
} else {
description = i18nc(
"@info:tooltip Formats to something like 'Messages received on 2008-12-21'",
"Messages received on %1",
ghi->label()
);
}
} else {
if (storageModel()->containsOutboundMessages()) {
description = i18nc(
"@info:tooltip Formats to something like 'Messages sent Yesterday'",
"Messages sent %1",
ghi->label()
);
} else {
description = i18nc(
"@info:tooltip Formats to something like 'Messages received Yesterday'",
"Messages received %1",
ghi->label()
);
}
}
}
break;
case Aggregation::GroupByDateRange:
if (d->mAggregation->threading() != Aggregation::NoThreading) {
switch (d->mAggregation->threadLeader()) {
case Aggregation::TopmostMessage:
description = i18n("Threads started within %1", ghi->label());
break;
case Aggregation::MostRecentMessage:
description = i18n("Threads containing messages with dates within %1", ghi->label());
break;
default:
// nuthin, make gcc happy
break;
}
} else {
if (storageModel()->containsOutboundMessages()) {
description = i18n("Messages sent within %1", ghi->label());
} else {
description = i18n("Messages received within %1", ghi->label());
}
}
break;
case Aggregation::GroupBySenderOrReceiver:
case Aggregation::GroupBySender:
if (d->mAggregation->threading() != Aggregation::NoThreading) {
switch (d->mAggregation->threadLeader()) {
case Aggregation::TopmostMessage:
description = i18n("Threads started by %1", ghi->label());
break;
case Aggregation::MostRecentMessage:
description = i18n("Threads with most recent message by %1", ghi->label());
break;
default:
// nuthin, make gcc happy
break;
}
} else {
if (storageModel()->containsOutboundMessages()) {
if (d->mAggregation->grouping() == Aggregation::GroupBySenderOrReceiver) {
description = i18n("Messages sent to %1", ghi->label());
} else {
description = i18n("Messages sent by %1", ghi->label());
}
} else {
description = i18n("Messages received from %1", ghi->label());
}
}
break;
case Aggregation::GroupByReceiver:
if (d->mAggregation->threading() != Aggregation::NoThreading) {
switch (d->mAggregation->threadLeader()) {
case Aggregation::TopmostMessage:
description = i18n("Threads directed to %1", ghi->label());
break;
case Aggregation::MostRecentMessage:
description = i18n("Threads with most recent message directed to %1", ghi->label());
break;
default:
// nuthin, make gcc happy
break;
}
} else {
if (storageModel()->containsOutboundMessages()) {
description = i18n("Messages sent to %1", ghi->label());
} else {
description = i18n("Messages received by %1", ghi->label());
}
}
break;
default:
// nuthin, make gcc happy
break;
}
if (!description.isEmpty()) {
tip += QStringLiteral(
"<tr>" \
"<td align=\"%2\" valign=\"middle\">" \
"%1" \
"</td>" \
"</tr>"
).arg(description).arg(textDirection);
}
if (ghi->hasChildren()) {
Item::ChildItemStats stats;
ghi->childItemStats(stats);
QString statsText;
if (d->mAggregation->threading() != Aggregation::NoThreading) {
statsText = i18np("<b>%1</b> thread", "<b>%1</b> threads", ghi->childItemCount());
statsText += QLatin1String(", ");
}
statsText += i18np(
"<b>%1</b> message (<b>%2</b> unread)",
"<b>%1</b> messages (<b>%2</b> unread)",
stats.mTotalChildCount,
stats.mUnreadChildCount
);
tip += QStringLiteral(
"<tr>" \
"<td bgcolor=\"%1\" align=\"%3\" valign=\"middle\">" \
"<nobr>%2</nobr>" \
"</td>" \
"</tr>"
).arg(darkerColorName).arg(statsText).arg(textDirection);
}
break;
}
default:
// nuthin (just make gcc happy for now)
break;
}
tip += QLatin1String(
"</table>"
);
QToolTip::showText(he->globalPos(), tip, viewport(), visualRect(idx));
return true;
}
void View::slotCollapseAllGroups()
{
setAllGroupsExpanded(false);
}
void View::slotExpandAllGroups()
{
setAllGroupsExpanded(true);
}
void View::slotCollapseCurrentItem()
{
setCurrentThreadExpanded(false);
}
void View::slotExpandCurrentItem()
{
setCurrentThreadExpanded(true);
}
void View::focusQuickSearch(const QString &selectedText)
{
d->mWidget->focusQuickSearch(selectedText);
}
QList<Akonadi::MessageStatus> View::currentFilterStatus() const
{
return d->mWidget->currentFilterStatus();
}
MessageList::Core::QuickSearchLine::SearchOptions View::currentOptions() const
{
return d->mWidget->currentOptions();
}
QString View::currentFilterSearchString() const
{
return d->mWidget->currentFilterSearchString();
}
void View::setRowHidden(int row, const QModelIndex &parent, bool hide)
{
const QModelIndex rowModelIndex = model()->index(row, 0, parent);
const Item *const rowItem = static_cast< Item * >(rowModelIndex.internalPointer());
if (rowItem) {
const bool currentlyHidden = isRowHidden(row, parent);
if (currentlyHidden != hide) {
if (currentMessageItem() == rowItem) {
selectionModel()->clear();
selectionModel()->clearSelection();
}
}
}
QTreeView::setRowHidden(row, parent, hide);
}
void View::sortOrderMenuAboutToShow(QMenu *menu)
{
d->mWidget->sortOrderMenuAboutToShow(menu);
}
void View::aggregationMenuAboutToShow(QMenu *menu)
{
d->mWidget->aggregationMenuAboutToShow(menu);
}
void View::themeMenuAboutToShow(QMenu *menu)
{
d->mWidget->themeMenuAboutToShow(menu);
}
void View::setCollapseItem(const QModelIndex &index)
{
if (index.isValid()) {
setExpanded(index, false);
}
}
void View::setExpandItem(const QModelIndex &index)
{
if (index.isValid()) {
setExpanded(index, true);
}
}
void View::setQuickSearchClickMessage(const QString &msg)
{
d->mWidget->quickSearch()->setPlaceholderText(msg);
}
#include "moc_view.cpp"
diff --git a/messagelist/src/core/view.h b/messagelist/src/core/view.h
index 5aa8b385..b0ad38c3 100644
--- a/messagelist/src/core/view.h
+++ b/messagelist/src/core/view.h
@@ -1,649 +1,650 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_CORE_VIEW_H
#define MESSAGELIST_CORE_VIEW_H
#include <QTreeView>
#include <QList>
#include <QPoint>
#include <messagelist/enums.h>
#include <messagelist/quicksearchline.h>
class QMenu;
namespace Akonadi {
class MessageStatus;
}
namespace MessageList {
namespace Core {
typedef long int MessageItemSetReference;
class Aggregation;
class Delegate;
class Item;
class MessageItem;
class Model;
class Theme;
class SortOrder;
class StorageModel;
class Widget;
/**
* The MessageList::View is the real display of the message list. It is
* based on QTreeView, has a Model that manipulates the underlying message storage
* and a Delegate that is responsable of painting the items.
*/
class View : public QTreeView
{
friend class Model;
friend class ModelPrivate;
Q_OBJECT
public:
explicit View(Widget *parent);
~View() override;
/**
- * Returns the Model attacched to this View. You probably never need to manipulate
+ * Returns the Model attached to this View. You probably never need to manipulate
* it directly.
*/
Model *model() const;
/**
- * Returns the Delegate attacched to this View. You probably never need to manipulate
+ * Returns the Delegate attached to this View. You probably never need to manipulate
* it directly. Model uses it to obtain size hints.
*/
Delegate *delegate() const;
/**
* Sets the StorageModel to be displayed in this view. The StorageModel may be 0 (so no content is displayed).
* Setting the StorageModel will obviously trigger a view reload.
* Be sure to set the Aggregation and the Theme BEFORE calling this function.
*
* Pre-selection is the action of automatically selecting a message just after the folder
* has finished loading. See Model::setStorageModel() for more information.
*/
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode = PreSelectLastSelected);
/**
* Returns the currently displayed StorageModel. May be 0.
*/
StorageModel *storageModel() const;
/**
* Sets the aggregation for this view.
* Does not trigger a reload of the view: you *MUST* trigger it manually.
*/
void setAggregation(const Aggregation *aggregation);
/**
* Sets the specified theme for this view.
* Does not trigger a reload of the view: you *MUST* trigger it manually.
*/
void setTheme(Theme *theme);
/**
* Sets the specified sort order.
* Does not trigger a reload of the view: you *MUST* trigger it manually.
*/
void setSortOrder(const SortOrder *sortOrder);
/**
* Triggers a reload of the view in order to re-display the current folder.
* Call this function after changing the Aggregation or the Theme.
*/
void reload();
/**
* Returns the current MessageItem (that is bound to current StorageModel).
* May return 0 if there is no current message or no current StorageModel.
* If the current message item isn't currently selected (so is only focused)
* then it's selected when this function is called, unless selectIfNeeded is false.
*/
MessageItem *currentMessageItem(bool selectIfNeeded = true) const;
/**
* Returns the current Item (that is bound to current StorageModel).
* May return 0 if there is no current item or no current StorageModel.
* If the current item isn't currently selected (so is only focused)
* then it's selected when this function is called.
*/
Item *currentItem() const;
/**
* Sets the current message item.
*/
void setCurrentMessageItem(MessageItem *it, bool center = false);
/**
* Returns true if the specified item is currently displayed in the tree
* and has all the parents expanded. This means that the user can
* see the message (by eventually scrolling the view).
*/
bool isDisplayedWithParentsExpanded(Item *it) const;
/**
* Makes sure that the specified is currently viewable by the user.
* This means that the user can see the message (by eventually scrolling the view).
*/
void ensureDisplayedWithParentsExpanded(Item *it);
/**
* Returns the currently selected MessageItems (bound to current StorageModel).
* The list may be empty if there are no selected messages or no StorageModel.
*
* If includeCollapsedChildren is true then the children of the selected but
* collapsed items are also added to the list.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
QList< MessageItem * > selectionAsMessageItemList(bool includeCollapsedChildren = true) const;
/**
* Returns the MessageItems bound to the current StorageModel that
* are part of the current thread. The current thread is the thread
* that contains currentMessageItem().
* The list may be empty if there is no currentMessageItem() or no StorageModel.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
QList< MessageItem * > currentThreadAsMessageItemList() const;
/**
* Fast function that determines if the selection is empty
*/
bool selectionEmpty() const;
/**
* Selects the specified MessageItems. The current selection is NOT removed.
* Use clearSelection() for that purpose.
*/
void selectMessageItems(const QList< MessageItem * > &list);
/**
* Creates a persistent set for the specified MessageItems and
* returns its reference. Later you can use this reference
* to retrieve the list of MessageItems that are still valid.
* See persistentSetCurrentMessageList() for that.
*
* Persistent sets consume resources (both memory and CPU time
* while manipulating the view) so be sure to call deletePersistentSet()
* when you no longer need it.
*/
MessageItemSetReference createPersistentSet(const QList< MessageItem * > &items);
/**
* Returns the list of MessageItems that are still existing in the
* set pointed by the specified reference. This list will contain
* at most the messages that you have passed to createPersistentSet()
* but may contain less (even 0) if these MessageItem object were removed
* from the view for some reason.
*/
QList< MessageItem * > persistentSetCurrentMessageItemList(MessageItemSetReference ref);
/**
* Deletes the persistent set pointed by the specified reference.
* If the set does not exist anymore, nothing happens.
*/
void deletePersistentSet(MessageItemSetReference ref);
/**
* If bMark is true this function marks the messages as "about to be removed"
* so they appear dimmer and aren't selectable in the view.
* If bMark is false then this function clears the "about to be removed" state
* for the specified MessageItems.
*/
void markMessageItemsAsAboutToBeRemoved(QList< MessageItem * > &items, bool bMark);
/**
* Returns true if the current Aggregation is threaded, false otherwise
* (or if there is no current Aggregation).
*/
bool isThreaded() const;
/**
* If expand is true then it expands the current thread, otherwise
* collapses it.
*/
void setCurrentThreadExpanded(bool expand);
/**
* If expand is true then it expands all the threads, otherwise
* collapses them.
*/
void setAllThreadsExpanded(bool expand);
/**
* If expand is true then it expands all the groups (only the toplevel
* group item: inner threads are NOT expanded). If expand is false
* then it collapses all the groups. If no grouping is in effect
* then this function does nothing.
*/
void setAllGroupsExpanded(bool expand);
/**
* Selects the next message item in the view.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
*
* existingSelectionBehaviour specifies how the existing selection
* is manipulated. It may be cleared, expanded or grown/shrinked.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "next" algorithm will restart from the beginning
* of the list if the end is reached, otherwise it will just stop returning false.
*
* \sa MessageList::Core::MessageTypeFilter
* \sa MessageList::Core::ExistingSelectionBehaviour
*/
bool selectNextMessageItem(
MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
/**
* Selects the previous message item in the view.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
*
* existingSelectionBehaviour specifies how the existing selection
* is manipulated. It may be cleared, expanded or grown/shrinked.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "previous" algorithm will restart from the end
* of the list if the beginning is reached, otherwise it will just stop returning false.
*
* \sa MessageList::Core::MessageTypeFilter
* \sa MessageList::Core::ExistingSelectionBehaviour
*/
bool selectPreviousMessageItem(
MessageTypeFilter messageTypeFilter, ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
/**
* Focuses the next message item in the view without actually selecting it.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "next" algorithm will restart from the beginning
* of the list if the end is reached, otherwise it will just stop returning false.
*/
bool focusNextMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
/**
* Focuses the previous message item in the view without actually selecting it.
* If unread is true then focuses the previous unread message item.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "previous" algorithm will restart from the end
* of the list if the beginning is reached, otherwise it will just stop returning false.
*/
bool focusPreviousMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
/**
* Selects the currently focused message item. If the currently focused
* message is already selected (which is very likely) nothing happens.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*/
void selectFocusedMessageItem(bool centerItem);
/**
* Selects the first message item in the view that matches messageTypeFilter.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*/
bool selectFirstMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem);
/**
* Selects the last message item in the view that matches messageTypeFilter.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*/
bool selectLastMessageItem(MessageTypeFilter messageTypeFilter, bool centerItem);
/**
* Sets the focus on the quick search line of the currently active tab.
*/
void focusQuickSearch(const QString &selectedText);
/**
* Returns the Akonadi::MessageStatus in the current quicksearch field.
*/
QList<Akonadi::MessageStatus> currentFilterStatus() const;
/**
* Returns the search term in the current quicksearch field.
*/
QString currentFilterSearchString() const;
/**
* Called to hide or show the specified row from the view.
* @reimp
*/
virtual void setRowHidden(int row, const QModelIndex &parent, bool hide);
void sortOrderMenuAboutToShow(QMenu *menu);
void aggregationMenuAboutToShow(QMenu *menu);
void themeMenuAboutToShow(QMenu *menu);
void setCollapseItem(const QModelIndex &index);
void setExpandItem(const QModelIndex &index);
void setQuickSearchClickMessage(const QString &msg);
MessageList::Core::QuickSearchLine::SearchOptions currentOptions() const;
protected:
/**
* Reimplemented in order to catch QHelpEvent
*/
bool event(QEvent *e) override;
/**
* Reimplemented in order to catch palette, font and style changes
*/
void changeEvent(QEvent *e) override;
/**
* Reimplemented in order to apply theme column widths on the first show
*/
void showEvent(QShowEvent *e) override;
/**
* Reimplemented in order to handle clicks with sub-item precision.
*/
void mousePressEvent(QMouseEvent *e) override;
/**
* Reimplemented in order to handle double clicks with sub-item precision.
*/
void mouseDoubleClickEvent(QMouseEvent *e) override;
/**
* Reimplemented in order to handle DnD
*/
void mouseMoveEvent(QMouseEvent *e) override;
/**
* Reimplemented in order to handle message DnD
*/
void dragEnterEvent(QDragEnterEvent *e) override;
/**
* Reimplemented in order to handle message DnD
*/
void dragMoveEvent(QDragMoveEvent *e) override;
/**
* Reimplemented in order to handle message DnD
*/
void dropEvent(QDropEvent *e) override;
/**
* Reimplemented in order to resize columns when header is not visible
*/
void resizeEvent(QResizeEvent *e) override;
+ void paintEvent(QPaintEvent *event) override;
/**
* Reimplemented in order to kill the QTreeView column auto-resizing
*/
int sizeHintForColumn(int logicalColumnIndex) const override;
/**
* Reimplemented in order to disable update of the geometries
* while a job step is running (as it takes a very long time and it's called for every item insertion...)
* TODO: not true anymore, it's called after a delay.
*/
void updateGeometries() override;
/**
* Returns true if the vertical scrollbar should keep to the top or bottom
* while inserting items.
*/
bool isScrollingLocked() const;
/**
* Used to enable/disable the ignoring of updateGeometries() calls.
*/
void ignoreUpdateGeometries(bool ignore);
void modelAboutToEmitLayoutChanged();
void modelEmittedLayoutChanged();
/**
* Recursive helper for currentThreadAsMessageItemList()
*/
void appendMessageItemChildren(MessageItem *par, QList< MessageItem * > &list);
/**
* This is called by the model to insulate us from certain QTreeView signals
* This is because they may be spurious (caused by Model item rearrangements).
*/
void ignoreCurrentChanges(bool ignore);
/**
* Expands or collapses the children of the specified item, recursively.
*/
void setChildrenExpanded(const Item *parent, bool expand);
/**
* Finds the next message item with respect to the current item.
* If there is no current item then the search starts from the beginning.
* Returns 0 if no next message could be found.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
* If loop is true then restarts from the beginning if end is
* reached, otherwise it just returns 0 in this case.
*/
Item *nextMessageItem(MessageTypeFilter messageTypeFilter, bool loop);
/**
* Finds message item that comes "after" the reference item.
* If reference item is 0 then the search starts from the beginning.
* Returns 0 if no next message could be found.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
* If loop is true then restarts from the beginning if end is
* reached, otherwise it just returns 0 in this case.
*/
Item *messageItemAfter(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop);
/**
* Finds the first message item in the view.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
*
* Returns 0 if the view is empty.
*/
Item *firstMessageItem(MessageTypeFilter messageTypeFilter);
/**
* Finds the previous message item with respect to the current item.
* If there is no current item then the search starts from the end.
* Returns 0 if no previous message could be found.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
* If loop is true then restarts from the end if beginning is
* reached, otherwise it just return 0 in this case.
*/
Item *previousMessageItem(MessageTypeFilter messageTypeFilter, bool loop);
/**
* Returns the deepest child that is visible (i.e. not in a collapsed tree) of
* the specified reference item.
*/
Item *deepestExpandedChild(Item *referenceItem) const;
/**
* Finds message item that comes "before" the reference item.
* If reference item is 0 then the search starts from the end.
* Returns 0 if no next message could be found.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
* If loop is true then restarts from the beginning if end is
* reached, otherwise it just returns 0 in this case.
*/
Item *messageItemBefore(Item *referenceItem, MessageTypeFilter messageTypeFilter, bool loop);
/**
* Finds the last message item in the view.
*
* messageTypeFilter can be used to limit the selection to
* a certain category of messages.
*
* Returns nullptr if the view is empty.
*/
Item *lastMessageItem(MessageTypeFilter messageTypeFilter);
/**
* This is called by Model to signal that the initial loading stage of a newly
* attached StorageModel is terminated.
*/
void modelFinishedLoading();
/**
* Performs a change in the specified MessageItem status.
* It first applies the change to the cached state in MessageItem and
* then requests our parent widget to act on the storage.
*/
void changeMessageStatus(MessageItem *it, Akonadi::MessageStatus set, Akonadi::MessageStatus unset);
void changeMessageStatusRead(MessageItem *it, bool read);
/**
* Starts a short-delay timer connected to saveThemeColumnState().
* Used to accumulate consecutive changes and break out of the call stack
* up to the main event loop (since in the call stack the column state might be left undefined).
*/
void triggerDelayedSaveThemeColumnState();
/**
* Starts a short-delay timer connected to applyThemeColumns().
* Used to accumulate consecutive changes and break out of the call stack
* up to the main event loop (since multiple resize events tend to be sent by Qt at startup).
*/
void triggerDelayedApplyThemeColumns();
/**
* This is used by the selection functions to grow/shrink the existing selection
* according to the newly selected item passed as parameter.
* If movingUp is true then: if the newly selected item is above the current selection top
* then the selection is expanded, otherwise it's shrunk. If movingUp is false then: if the
* newly selected item is below the current selection bottom then the selection is expanded
* otherwise it's shrunk.
*/
void growOrShrinkExistingSelection(const QModelIndex &newSelectedIndex, bool movingUp);
public Q_SLOTS:
/**
* Collapses all the group headers (if present in the current Aggregation)
*/
void slotCollapseAllGroups();
/**
* Expands all the group headers (if present in the current Aggregation)
*/
void slotExpandAllGroups();
/**
- * Expands the currect item.
+ * Expands the current item.
* If it's a Message, it expands its thread, if its a group header it expands the group
*/
void slotExpandCurrentItem();
/**
- * Collapses the currect item.
+ * Collapses the current item.
* If it's a Message, it collapses its thread, if its a group header it collapses the group
*/
void slotCollapseCurrentItem();
protected Q_SLOTS:
/**
* Handles context menu requests for the header.
*/
void slotHeaderContextMenuRequested(const QPoint &pnt);
/**
* Handles the actions of the header context menu for showing/hiding a column.
*/
void slotShowHideColumn(int columnIndex);
/**
* Handles the Adjust Column Sizes action of the header context menu.
*/
void slotAdjustColumnSizes();
/**
* Handles the Show Default Columns action of the header context menu.
*/
void slotShowDefaultColumns();
/**
* Handles the Display Tooltips action of the header context menu.
*/
void slotDisplayTooltips(bool showTooltips);
/**
* Handles section resizes in order to save the column widths
*/
void slotHeaderSectionResized(int logicalIndex, int oldWidth, int newWidth);
/**
* Handles selection item management
*/
void slotSelectionChanged(const QItemSelection &current, const QItemSelection &);
/**
- * Saves the state of the columns (width and visility) to the currently selected theme object.
+ * Saves the state of the columns (width and visibility) to the currently selected theme object.
*/
void saveThemeColumnState();
/**
* Applies the theme columns to this view.
* Columns visible by default are shown, the other are hidden.
* Visible columns are assigned space inside the view by using the size hints and some heuristics.
*/
void applyThemeColumns();
private:
class Private;
Private *d;
}; // class View
} // namespace Core
} // namespace MessageList
#endif //!__MESSAGELIST_CORE_VIEW_H
diff --git a/messagelist/src/core/widgetbase.cpp b/messagelist/src/core/widgetbase.cpp
index f9e037ed..4240cb84 100644
--- a/messagelist/src/core/widgetbase.cpp
+++ b/messagelist/src/core/widgetbase.cpp
@@ -1,1101 +1,1101 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "core/widgetbase.h"
#include "core/widgets/quicksearchwarning.h"
#include "core/widgets/searchcollectionindexingwarning.h"
#include "core/aggregation.h"
#include "core/theme.h"
#include "core/filter.h"
#include "core/manager.h"
#include "core/optionset.h"
#include "core/view.h"
#include "core/model.h"
#include "core/messageitem.h"
#include "core/storagemodelbase.h"
#include "widgets/searchlinestatus.h"
#include "messagelistsettings.h"
#include "utils/configureaggregationsdialog.h"
#include "utils/configurethemesdialog.h"
#include <QVBoxLayout>
#include <QActionGroup>
#include <QHeaderView>
#include <QTimer>
#include <QVariant>
#include <QAction>
#include <KComboBox>
#include "messagelist_debug.h"
#include <KLocalizedString>
#include <QMenu>
#include <QUrl>
#include <collection.h>
#include <Akonadi/KMime/MessageStatus>
using namespace MessageList::Core;
class Widget::Private
{
public:
Private(Widget *owner)
: q(owner)
, quickSearchWarning(nullptr)
, searchCollectionIndexingWarning(nullptr)
, quickSearchLine(nullptr)
, mView(nullptr)
, mSearchTimer(nullptr)
, mStorageModel(nullptr)
, mAggregation(nullptr)
, mTheme(nullptr)
, mFilter(nullptr)
, mStorageUsesPrivateTheme(false)
, mStorageUsesPrivateAggregation(false)
, mStorageUsesPrivateSortOrder(false)
, mStatusFilterComboPopulationInProgress(false)
{
}
/**
* Small helper for switching SortOrder::MessageSorting and SortOrder::SortDirection
* on the fly.
* After doing this, the sort indicator in the header is updated.
*/
void switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex);
/**
* Check if our sort order can still be used with this aggregation.
* This can happen if the global aggregation changed, for example we can now
* have "most recent in subtree" sorting with an aggregation without threading.
* If this happens, reset to the default sort order and don't use the global sort
* order.
*/
void checkSortOrder(const StorageModel *storageModel);
void setDefaultAggregationForStorageModel(const StorageModel *storageModel);
void setDefaultThemeForStorageModel(const StorageModel *storageModel);
void setDefaultSortOrderForStorageModel(const StorageModel *storageModel);
void applyFilter();
Widget *const q;
QuickSearchWarning *quickSearchWarning;
SearchCollectionIndexingWarning *searchCollectionIndexingWarning;
QuickSearchLine *quickSearchLine;
View *mView;
QString mLastAggregationId;
QString mLastThemeId;
QTimer *mSearchTimer;
StorageModel *mStorageModel; ///< The currently displayed storage. The storage itself
/// is owned by MessageList::Widget.
Aggregation *mAggregation; ///< The currently set aggregation mode, a deep copy
Theme *mTheme; ///< The currently set theme, a deep copy
SortOrder mSortOrder; ///< The currently set sort order
Filter *mFilter; ///< The currently applied filter, owned by us.
bool mStorageUsesPrivateTheme; ///< true if the current folder does not use the global theme
bool mStorageUsesPrivateAggregation; ///< true if the current folder does not use the global aggregation
bool mStorageUsesPrivateSortOrder; ///< true if the current folder does not use the global sort order
QUrl mCurrentFolderUrl; ///< The Akonadi URL of the current folder
Akonadi::Collection mCurrentFolder; ///< The current folder
int mCurrentStatusFilterIndex = 0;
bool mStatusFilterComboPopulationInProgress;
};
Widget::Widget(QWidget *pParent)
: QWidget(pParent)
, d(new Private(this))
{
Manager::registerWidget(this);
connect(Manager::instance(), &Manager::aggregationsChanged,
this, &Widget::aggregationsChanged);
connect(Manager::instance(), &Manager::themesChanged,
this, &Widget::themesChanged);
setAutoFillBackground(true);
setObjectName(QStringLiteral("messagelistwidget"));
QVBoxLayout *g = new QVBoxLayout(this);
- g->setMargin(0);
+ g->setContentsMargins(0, 0, 0, 0);
g->setSpacing(0);
d->quickSearchLine = new QuickSearchLine;
d->quickSearchLine->setObjectName(QStringLiteral("quicksearchline"));
connect(d->quickSearchLine, &QuickSearchLine::clearButtonClicked, this, &Widget::searchEditClearButtonClicked);
connect(d->quickSearchLine, &QuickSearchLine::searchEditTextEdited, this, &Widget::searchEditTextEdited);
connect(d->quickSearchLine, &QuickSearchLine::searchOptionChanged, this, &Widget::searchEditTextEdited);
connect(d->quickSearchLine, &QuickSearchLine::statusButtonsClicked, this, &Widget::slotStatusButtonsClicked);
connect(d->quickSearchLine, &QuickSearchLine::forceLostFocus, this, &Widget::forceLostFocus);
g->addWidget(d->quickSearchLine, 0);
d->quickSearchWarning = new QuickSearchWarning(this);
g->addWidget(d->quickSearchWarning, 0);
d->searchCollectionIndexingWarning = new SearchCollectionIndexingWarning(this);
g->addWidget(d->searchCollectionIndexingWarning, 0);
d->mView = new View(this);
d->mView->setFrameStyle(QFrame::NoFrame);
d->mView->setSortOrder(&d->mSortOrder);
d->mView->setObjectName(QStringLiteral("messagealistview"));
g->addWidget(d->mView, 1);
connect(d->mView->header(), &QHeaderView::sectionClicked,
this, &Widget::slotViewHeaderSectionClicked);
d->mSearchTimer = nullptr;
}
Widget::~Widget()
{
d->mView->setStorageModel(nullptr);
Manager::unregisterWidget(this);
delete d->mSearchTimer;
delete d->mTheme;
delete d->mAggregation;
delete d->mFilter;
delete d->mStorageModel;
delete d;
}
void Widget::changeQuicksearchVisibility(bool show)
{
QLineEdit *const lineEdit = d->quickSearchLine->searchEdit();
if (!show) {
//if we hide it we do not want to apply the filter,
//otherwise someone is maybe stuck with x new emails
//and cannot read it because of filter
lineEdit->clear();
//we focus the message list if we hide the searchbar
d->mView->setFocus(Qt::OtherFocusReason);
} else {
// on show: we focus the lineedit for fast filtering
lineEdit->setFocus(Qt::OtherFocusReason);
if (d->mFilter) {
resetFilter();
}
}
d->quickSearchLine->changeQuicksearchVisibility(show);
MessageListSettings::self()->setShowQuickSearch(show);
}
void Widget::populateStatusFilterCombo()
{
if (d->mStatusFilterComboPopulationInProgress) {
return;
}
d->mStatusFilterComboPopulationInProgress = true;
KComboBox *tagFilterComboBox = d->quickSearchLine->tagFilterComboBox();
d->mCurrentStatusFilterIndex = (tagFilterComboBox->currentIndex() != -1) ? tagFilterComboBox->currentIndex() : 0;
- disconnect(tagFilterComboBox, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &Widget::statusSelected);
+ disconnect(tagFilterComboBox, qOverload<int>(&KComboBox::currentIndexChanged), this, &Widget::statusSelected);
tagFilterComboBox->clear();
fillMessageTagCombo();
}
void Widget::addMessageTagItem(const QPixmap &icon, const QString &text, const QVariant &data)
{
d->quickSearchLine->tagFilterComboBox()->addItem(icon, text, data);
}
void Widget::setCurrentStatusFilterItem()
{
d->quickSearchLine->updateComboboxVisibility();
- connect(d->quickSearchLine->tagFilterComboBox(), QOverload<int>::of(&KComboBox::currentIndexChanged),
+ connect(d->quickSearchLine->tagFilterComboBox(), qOverload<int>(&KComboBox::currentIndexChanged),
this, &Widget::statusSelected);
d->quickSearchLine->tagFilterComboBox()->setCurrentIndex(d->mCurrentStatusFilterIndex >= d->quickSearchLine->tagFilterComboBox()->count() ? 0 : d->mCurrentStatusFilterIndex);
d->mStatusFilterComboPopulationInProgress = false;
}
MessageItem *Widget::currentMessageItem() const
{
return view()->currentMessageItem();
}
MessageList::Core::QuickSearchLine::SearchOptions Widget::currentOptions() const
{
return d->quickSearchLine->searchOptions();
}
QList<Akonadi::MessageStatus> Widget::currentFilterStatus() const
{
if (d->mFilter) {
return d->mFilter->status();
}
return QList<Akonadi::MessageStatus>();
}
QString Widget::currentFilterSearchString() const
{
if (d->mFilter) {
return d->mFilter->searchString();
}
return QString();
}
QString Widget::currentFilterTagId() const
{
if (d->mFilter) {
return d->mFilter->tagId();
}
return QString();
}
void Widget::Private::setDefaultAggregationForStorageModel(const StorageModel *storageModel)
{
const Aggregation *opt = Manager::instance()->aggregationForStorageModel(storageModel, &mStorageUsesPrivateAggregation);
Q_ASSERT(opt);
delete mAggregation;
mAggregation = new Aggregation(*opt);
mView->setAggregation(mAggregation);
mLastAggregationId = opt->id();
}
void Widget::Private::setDefaultThemeForStorageModel(const StorageModel *storageModel)
{
const Theme *opt = Manager::instance()->themeForStorageModel(storageModel, &mStorageUsesPrivateTheme);
Q_ASSERT(opt);
delete mTheme;
mTheme = new Theme(*opt);
mView->setTheme(mTheme);
mLastThemeId = opt->id();
}
void Widget::Private::checkSortOrder(const StorageModel *storageModel)
{
if (storageModel && mAggregation && !mSortOrder.validForAggregation(mAggregation)) {
qCDebug(MESSAGELIST_LOG) << "Could not restore sort order for folder" << storageModel->id();
mSortOrder = SortOrder::defaultForAggregation(mAggregation, mSortOrder);
// Change the global sort order if the sort order didn't fit the global aggregation.
// Otherwise, if it is a per-folder aggregation, make the sort order per-folder too.
if (mStorageUsesPrivateAggregation) {
mStorageUsesPrivateSortOrder = true;
}
if (mStorageModel) {
Manager::instance()->saveSortOrderForStorageModel(storageModel, mSortOrder,
mStorageUsesPrivateSortOrder);
}
switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
}
}
void Widget::Private::setDefaultSortOrderForStorageModel(const StorageModel *storageModel)
{
// Load the sort order from config and update column headers
mSortOrder = Manager::instance()->sortOrderForStorageModel(storageModel, &mStorageUsesPrivateSortOrder);
switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
checkSortOrder(storageModel);
}
void Widget::saveCurrentSelection()
{
if (d->mStorageModel) {
// Save the current selection
MessageItem *lastSelectedMessageItem = d->mView->currentMessageItem(false);
if (lastSelectedMessageItem) {
d->mStorageModel->savePreSelectedMessage(lastSelectedMessageItem->uniqueId());
}
}
}
void Widget::setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode)
{
if (storageModel == d->mStorageModel) {
return; // nuthin to do here
}
d->setDefaultAggregationForStorageModel(storageModel);
d->setDefaultThemeForStorageModel(storageModel);
d->setDefaultSortOrderForStorageModel(storageModel);
if (!d->quickSearchLine->searchEdit()->locked()) {
if (d->mSearchTimer) {
d->mSearchTimer->stop();
delete d->mSearchTimer;
d->mSearchTimer = nullptr;
}
d->quickSearchLine->searchEdit()->clear();
if (d->mFilter) {
resetFilter();
}
}
StorageModel *oldModel = d->mStorageModel;
d->mStorageModel = storageModel;
d->mView->setStorageModel(d->mStorageModel, preSelectionMode);
delete oldModel;
d->quickSearchLine->tagFilterComboBox()->setEnabled(d->mStorageModel);
d->quickSearchLine->searchEdit()->setEnabled(d->mStorageModel);
d->quickSearchLine->setContainsOutboundMessages(d->mStorageModel->containsOutboundMessages());
}
StorageModel *Widget::storageModel() const
{
return d->mStorageModel;
}
QLineEdit *Widget::quickSearch() const
{
return d->quickSearchLine->searchEdit();
}
View *Widget::view() const
{
return d->mView;
}
void Widget::themeMenuAboutToShow()
{
if (!d->mStorageModel) {
return;
}
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
themeMenuAboutToShow(menu);
}
void Widget::themeMenuAboutToShow(QMenu *menu)
{
menu->clear();
menu->addSection(i18n("Theme"));
QActionGroup *grp = new QActionGroup(menu);
QList< Theme * > sortedThemes = Manager::instance()->themes().values();
QAction *act;
std::sort(sortedThemes.begin(), sortedThemes.end(), MessageList::Core::Theme::compareName);
for (const auto theme : qAsConst(sortedThemes)) {
act = menu->addAction(theme->name());
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mLastThemeId == theme->id());
act->setData(QVariant(theme->id()));
connect(act, &QAction::triggered,
this, &Widget::themeSelected);
}
menu->addSeparator();
act = menu->addAction(i18n("Configure..."));
connect(act, &QAction::triggered,
this, &Widget::configureThemes);
}
void Widget::setPrivateSortOrderForStorage()
{
if (!d->mStorageModel) {
return;
}
d->mStorageUsesPrivateSortOrder = !d->mStorageUsesPrivateSortOrder;
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
}
void Widget::configureThemes()
{
Utils::ConfigureThemesDialog *dialog = new Utils::ConfigureThemesDialog(window());
dialog->selectTheme(d->mLastThemeId);
dialog->show();
}
void Widget::themeSelected(bool)
{
if (!d->mStorageModel) {
return; // nuthin to do
}
QAction *act = qobject_cast< QAction * >(sender());
if (!act) {
return;
}
QVariant v = act->data();
const QString id = v.toString();
if (id.isEmpty()) {
return;
}
const Theme *opt = Manager::instance()->theme(id);
delete d->mTheme;
d->mTheme = new Theme(*opt);
d->mView->setTheme(d->mTheme);
d->mLastThemeId = opt->id();
//mStorageUsesPrivateTheme = false;
Manager::instance()->saveThemeForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateTheme);
d->mView->reload();
}
void Widget::aggregationMenuAboutToShow()
{
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
aggregationMenuAboutToShow(menu);
}
void Widget::aggregationMenuAboutToShow(QMenu *menu)
{
menu->clear();
menu->addSection(i18n("Aggregation"));
QActionGroup *grp = new QActionGroup(menu);
QList< Aggregation * > sortedAggregations = Manager::instance()->aggregations().values();
QAction *act;
std::sort(sortedAggregations.begin(), sortedAggregations.end(), MessageList::Core::Aggregation::compareName);
for (const auto agg : qAsConst(sortedAggregations)) {
act = menu->addAction(agg->name());
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mLastAggregationId == agg->id());
act->setData(QVariant(agg->id()));
connect(act, &QAction::triggered,
this, &Widget::aggregationSelected);
}
menu->addSeparator();
act = menu->addAction(i18n("Configure..."));
act->setData(QVariant(QString()));
connect(act, &QAction::triggered,
this, &Widget::aggregationSelected);
}
void Widget::aggregationSelected(bool)
{
QAction *act = qobject_cast< QAction * >(sender());
if (!act) {
return;
}
QVariant v = act->data();
QString id = v.toString();
if (id.isEmpty()) {
Utils::ConfigureAggregationsDialog *dialog = new Utils::ConfigureAggregationsDialog(window());
dialog->selectAggregation(d->mLastAggregationId);
dialog->show();
return;
}
if (!d->mStorageModel) {
return; // nuthin to do
}
const Aggregation *opt = Manager::instance()->aggregation(id);
delete d->mAggregation;
d->mAggregation = new Aggregation(*opt);
d->mView->setAggregation(d->mAggregation);
d->mLastAggregationId = opt->id();
//mStorageUsesPrivateAggregation = false;
Manager::instance()->saveAggregationForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateAggregation);
// The sort order might not be valid anymore for this aggregation
d->checkSortOrder(d->mStorageModel);
d->mView->reload();
}
void Widget::sortOrderMenuAboutToShow()
{
if (!d->mAggregation) {
return;
}
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
sortOrderMenuAboutToShow(menu);
}
void Widget::sortOrderMenuAboutToShow(QMenu *menu)
{
menu->clear();
menu->addSection(i18n("Message Sort Order"));
QActionGroup *grp;
QAction *act;
QList< QPair< QString, int > > options;
grp = new QActionGroup(menu);
options = SortOrder::enumerateMessageSortingOptions(d->mAggregation->threading());
for (const auto &opt : qAsConst(options)) {
act = menu->addAction(opt.first);
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mSortOrder.messageSorting() == opt.second);
act->setData(QVariant(opt.second));
}
connect(grp, &QActionGroup::triggered,
this, &Widget::messageSortingSelected);
options = SortOrder::enumerateMessageSortDirectionOptions(d->mSortOrder.messageSorting());
if (options.size() >= 2) {
menu->addSection(i18n("Message Sort Direction"));
grp = new QActionGroup(menu);
for (const auto &opt : qAsConst(options)) {
act = menu->addAction(opt.first);
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mSortOrder.messageSortDirection() == opt.second);
act->setData(QVariant(opt.second));
}
connect(grp, &QActionGroup::triggered,
this, &Widget::messageSortDirectionSelected);
}
options = SortOrder::enumerateGroupSortingOptions(d->mAggregation->grouping());
if (options.size() >= 2) {
menu->addSection(i18n("Group Sort Order"));
grp = new QActionGroup(menu);
for (const auto &opt : qAsConst(options)) {
act = menu->addAction(opt.first);
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mSortOrder.groupSorting() == opt.second);
act->setData(QVariant(opt.second));
}
connect(grp, &QActionGroup::triggered,
this, &Widget::groupSortingSelected);
}
options = SortOrder::enumerateGroupSortDirectionOptions(d->mAggregation->grouping(),
d->mSortOrder.groupSorting());
if (options.size() >= 2) {
menu->addSection(i18n("Group Sort Direction"));
grp = new QActionGroup(menu);
for (const auto &opt : qAsConst(options)) {
act = menu->addAction(opt.first);
act->setCheckable(true);
grp->addAction(act);
act->setChecked(d->mSortOrder.groupSortDirection() == opt.second);
act->setData(QVariant(opt.second));
}
connect(grp, &QActionGroup::triggered,
this, &Widget::groupSortDirectionSelected);
}
menu->addSeparator();
act = menu->addAction(i18n("Folder Always Uses This Sort Order"));
act->setCheckable(true);
act->setChecked(d->mStorageUsesPrivateSortOrder);
connect(act, &QAction::triggered,
this, &Widget::setPrivateSortOrderForStorage);
}
void Widget::Private::switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex)
{
mSortOrder.setMessageSorting(messageSorting);
mSortOrder.setMessageSortDirection(sortDirection);
// If the logicalHeaderColumnIndex was specified then we already know which
// column we should set the sort indicator to. If it wasn't specified (it's -1)
// then we need to find it out in the theme.
if (logicalHeaderColumnIndex == -1) {
// try to find the specified message sorting in the theme columns
const auto columns = mTheme->columns();
int idx = 0;
// First try with a well defined message sorting.
for (const auto column : qAsConst(columns)) {
if (!mView->header()->isSectionHidden(idx)) {
if (column->messageSorting() == messageSorting) {
// found a visible column with this message sorting
logicalHeaderColumnIndex = idx;
break;
}
}
++idx;
}
// if still not found, try again with a wider range
if (logicalHeaderColumnIndex == -1) {
idx = 0;
for (const auto column : qAsConst(columns)) {
if (!mView->header()->isSectionHidden(idx)) {
if (
(
(column->messageSorting() == SortOrder::SortMessagesBySenderOrReceiver)
|| (column->messageSorting() == SortOrder::SortMessagesByReceiver)
|| (column->messageSorting() == SortOrder::SortMessagesBySender)
)
&& (
(messageSorting == SortOrder::SortMessagesBySenderOrReceiver)
|| (messageSorting == SortOrder::SortMessagesByReceiver)
|| (messageSorting == SortOrder::SortMessagesBySender)
)
) {
// found a visible column with this message sorting
logicalHeaderColumnIndex = idx;
break;
}
}
++idx;
}
}
}
if (logicalHeaderColumnIndex == -1) {
// not found: either not a column-based sorting or the related column is hidden
mView->header()->setSortIndicatorShown(false);
return;
}
mView->header()->setSortIndicatorShown(true);
if (sortDirection == SortOrder::Ascending) {
mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::AscendingOrder);
} else {
mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::DescendingOrder);
}
}
void Widget::messageSortingSelected(QAction *action)
{
if (!d->mAggregation) {
return;
}
if (!action) {
return;
}
if (!d->mStorageModel) {
return;
}
bool ok;
auto ord = static_cast< SortOrder::MessageSorting >(action->data().toInt(&ok));
if (!ok) {
return;
}
d->switchMessageSorting(ord, d->mSortOrder.messageSortDirection(), -1);
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
d->mView->reload();
}
void Widget::messageSortDirectionSelected(QAction *action)
{
if (!d->mAggregation) {
return;
}
if (!action) {
return;
}
if (!d->mStorageModel) {
return;
}
bool ok;
auto ord = static_cast< SortOrder::SortDirection >(action->data().toInt(&ok));
if (!ok) {
return;
}
d->switchMessageSorting(d->mSortOrder.messageSorting(), ord, -1);
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
d->mView->reload();
}
void Widget::groupSortingSelected(QAction *action)
{
if (!d->mAggregation) {
return;
}
if (!action) {
return;
}
if (!d->mStorageModel) {
return;
}
bool ok;
auto ord = static_cast< SortOrder::GroupSorting >(action->data().toInt(&ok));
if (!ok) {
return;
}
d->mSortOrder.setGroupSorting(ord);
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
d->mView->reload();
}
void Widget::groupSortDirectionSelected(QAction *action)
{
if (!d->mAggregation) {
return;
}
if (!action) {
return;
}
if (!d->mStorageModel) {
return;
}
bool ok;
auto ord = static_cast< SortOrder::SortDirection >(action->data().toInt(&ok));
if (!ok) {
return;
}
d->mSortOrder.setGroupSortDirection(ord);
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
d->mView->reload();
}
void Widget::resetFilter()
{
delete d->mFilter;
d->mFilter = nullptr;
d->mView->model()->setFilter(nullptr);
d->quickSearchLine->resetFilter();
d->quickSearchWarning->animatedHide();
}
void Widget::slotViewHeaderSectionClicked(int logicalIndex)
{
if (!d->mTheme) {
return;
}
if (!d->mAggregation) {
return;
}
if (logicalIndex >= d->mTheme->columns().count()) {
return;
}
if (!d->mStorageModel) {
return;
}
auto column = d->mTheme->column(logicalIndex);
if (!column) {
return; // should never happen...
}
if (column->messageSorting() == SortOrder::NoMessageSorting) {
return; // this is a null op.
}
if (d->mSortOrder.messageSorting() == column->messageSorting()) {
// switch sort direction
if (d->mSortOrder.messageSortDirection() == SortOrder::Ascending) {
d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Descending, logicalIndex);
} else {
d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Ascending, logicalIndex);
}
} else {
// keep sort direction but switch sort order
d->switchMessageSorting(column->messageSorting(), d->mSortOrder.messageSortDirection(), logicalIndex);
}
Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder,
d->mStorageUsesPrivateSortOrder);
d->mView->reload();
}
void Widget::themesChanged()
{
d->setDefaultThemeForStorageModel(d->mStorageModel);
d->mView->reload();
}
void Widget::aggregationsChanged()
{
d->setDefaultAggregationForStorageModel(d->mStorageModel);
d->checkSortOrder(d->mStorageModel);
d->mView->reload();
}
void Widget::fillMessageTagCombo()
{
// nothing here: must be overridden in derived classes
setCurrentStatusFilterItem();
}
void Widget::tagIdSelected(const QVariant &data)
{
QString tagId = data.toString();
if (tagId.isEmpty()) {
if (d->mFilter) {
if (d->mFilter->isEmpty()) {
resetFilter();
return;
}
}
} else {
if (!d->mFilter) {
d->mFilter = new Filter();
}
d->mFilter->setTagId(tagId);
}
d->mView->model()->setFilter(d->mFilter);
}
void Widget::statusSelected(int index)
{
if (index == 0) {
resetFilter();
return;
}
tagIdSelected(d->quickSearchLine->tagFilterComboBox()->itemData(index));
d->mView->model()->setFilter(d->mFilter);
}
void Widget::searchEditTextEdited()
{
// This slot is called whenever the user edits the search QLineEdit.
// Since the user is likely to type more than one character
// so we start the real search after a short delay in order to catch
// multiple textEdited() signals.
if (!d->mSearchTimer) {
d->mSearchTimer = new QTimer(this);
connect(d->mSearchTimer, &QTimer::timeout,
this, &Widget::searchTimerFired);
} else {
d->mSearchTimer->stop(); // eventually
}
d->mSearchTimer->setSingleShot(true);
d->mSearchTimer->start(1000);
}
void Widget::slotStatusButtonsClicked()
{
- // We also arbitrairly set tagId to an empty string, though we *could* allow filtering
+ // We also arbitrarily set tagId to an empty string, though we *could* allow filtering
// by status AND tag...
if (d->mFilter) {
d->mFilter->setTagId(QString());
}
auto lst = d->quickSearchLine->status();
if (lst.isEmpty()) {
if (d->mFilter) {
d->mFilter->setStatus(lst);
if (d->mFilter->isEmpty()) {
qCDebug(MESSAGELIST_LOG) << " RESET FILTER";
resetFilter();
return;
}
}
} else {
// don't have this status bit
if (!d->mFilter) {
d->mFilter = new Filter();
}
d->mFilter->setStatus(lst);
}
d->mView->model()->setFilter(d->mFilter);
}
void Widget::searchTimerFired()
{
// A search is pending.
if (d->mSearchTimer) {
d->mSearchTimer->stop();
}
if (!d->mFilter) {
d->mFilter = new Filter();
}
const QString text = d->quickSearchLine->searchEdit()->text();
if (!text.isEmpty()) {
d->quickSearchLine->addCompletionItem(text);
}
d->mFilter->setCurrentFolder(d->mCurrentFolder);
d->mFilter->setSearchString(text, d->quickSearchLine->searchOptions());
d->quickSearchWarning->setSearchText(text);
if (d->mFilter->isEmpty()) {
resetFilter();
return;
}
d->mView->model()->setFilter(d->mFilter);
}
void Widget::searchEditClearButtonClicked()
{
if (!d->mFilter) {
return;
}
resetFilter();
d->mView->scrollTo(d->mView->currentIndex(), QAbstractItemView::PositionAtCenter);
}
void Widget::viewMessageSelected(MessageItem *)
{
}
void Widget::viewMessageActivated(MessageItem *)
{
}
void Widget::viewSelectionChanged()
{
}
void Widget::viewMessageListContextPopupRequest(const QList< MessageItem * > &, const QPoint &)
{
}
void Widget::viewGroupHeaderContextPopupRequest(GroupHeaderItem *, const QPoint &)
{
}
void Widget::viewDragEnterEvent(QDragEnterEvent *)
{
}
void Widget::viewDragMoveEvent(QDragMoveEvent *)
{
}
void Widget::viewDropEvent(QDropEvent *)
{
}
void Widget::viewStartDragRequest()
{
}
void Widget::viewMessageStatusChangeRequest(MessageItem *msg, Akonadi::MessageStatus set, Akonadi::MessageStatus clear)
{
Q_UNUSED(msg);
Q_UNUSED(set);
Q_UNUSED(clear);
}
void Widget::focusQuickSearch(const QString &selectedText)
{
d->quickSearchLine->focusQuickSearch(selectedText);
}
bool Widget::isThreaded() const
{
return d->mView->isThreaded();
}
bool Widget::selectionEmpty() const
{
return d->mView->selectionEmpty();
}
Akonadi::Collection Widget::currentFolder() const
{
return d->mCurrentFolder;
}
void Widget::setCurrentFolder(const Akonadi::Collection &collection)
{
d->mCurrentFolder = collection;
d->searchCollectionIndexingWarning->setCollection(collection);
}
bool Widget::searchEditHasFocus() const
{
return d->quickSearchLine->searchEdit()->hasFocus();
}
diff --git a/messagelist/src/core/widgets/quicksearchline.cpp b/messagelist/src/core/widgets/quicksearchline.cpp
index 23bb2c67..8c6f1937 100644
--- a/messagelist/src/core/widgets/quicksearchline.cpp
+++ b/messagelist/src/core/widgets/quicksearchline.cpp
@@ -1,184 +1,184 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#include "quicksearchline.h"
#include "messagelistsettings.h"
#include <KStringHandler>
#include <Akonadi/KMime/MessageStatus>
#include "searchlinestatus.h"
#include <KLocalizedString>
#include <KComboBox>
#include <QPushButton>
#include <QHBoxLayout>
#include <QStandardPaths>
using namespace MessageList::Core;
QuickSearchLine::QuickSearchLine(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
- vbox->setMargin(0);
+ vbox->setContentsMargins(0, 0, 0, 0);
vbox->setSpacing(0);
QWidget *w = new QWidget;
QHBoxLayout *hbox = new QHBoxLayout;
- hbox->setMargin(0);
+ hbox->setContentsMargins(0, 0, 0, 0);
hbox->setSpacing(0);
w->setLayout(hbox);
vbox->addWidget(w);
mSearchEdit = new SearchLineStatus(this);
connect(mSearchEdit, &SearchLineStatus::filterActionChanged, this, &QuickSearchLine::slotFilterActionChanged);
connect(mSearchEdit, &SearchLineStatus::searchOptionChanged, this, &QuickSearchLine::searchOptionChanged);
connect(mSearchEdit, &SearchLineStatus::forceLostFocus, this, &QuickSearchLine::forceLostFocus);
mSearchEdit->setPlaceholderText(i18nc("Search for messages.", "Search"));
mSearchEdit->setObjectName(QStringLiteral("quicksearch"));
mSearchEdit->setClearButtonEnabled(true);
connect(mSearchEdit, &QLineEdit::textChanged, this, &QuickSearchLine::slotSearchEditTextEdited);
connect(mSearchEdit, &SearchLineStatus::clearButtonClicked, this, &QuickSearchLine::slotClearButtonClicked);
hbox->addWidget(mSearchEdit);
// The status filter button. Will be populated later, as populateStatusFilterCombo() is virtual
mTagFilterCombo = new KComboBox(this);
mTagFilterCombo->setMaximumWidth(300);
mTagFilterCombo->setMaximumWidth(200);
mTagFilterCombo->hide();
hbox->addWidget(mTagFilterCombo);
//Be disable until we have a storageModel => logical that it's disable.
mSearchEdit->setEnabled(false);
mTagFilterCombo->setEnabled(false);
installEventFilter(this);
mTagFilterCombo->installEventFilter(this);
changeQuicksearchVisibility(MessageListSettings::self()->showQuickSearch());
}
QuickSearchLine::~QuickSearchLine()
{
}
void QuickSearchLine::slotSearchEditTextEdited(const QString &text)
{
int minimumStringLength = 3;
if (text.startsWith(QLatin1Char('"')) && text.endsWith(QLatin1Char('"'))) {
minimumStringLength = 5;
}
if (!text.trimmed().isEmpty()) {
if (KStringHandler::logicalLength(text) >= minimumStringLength) {
Q_EMIT searchEditTextEdited(text);
}
} else {
slotClearButtonClicked();
}
}
void QuickSearchLine::slotClearButtonClicked()
{
if (mTagFilterCombo->isVisible()) {
mTagFilterCombo->setCurrentIndex(0);
}
mSearchEdit->clearFilterButtonClicked();
Q_EMIT clearButtonClicked();
}
QuickSearchLine::SearchOptions QuickSearchLine::searchOptions() const
{
return mSearchEdit->searchOptions();
}
void QuickSearchLine::focusQuickSearch(const QString &selectedText)
{
if (!selectedText.isEmpty()) {
mSearchEdit->setText(selectedText);
}
mSearchEdit->setFocus();
}
KComboBox *QuickSearchLine::tagFilterComboBox() const
{
return mTagFilterCombo;
}
SearchLineStatus *QuickSearchLine::searchEdit() const
{
return mSearchEdit;
}
void QuickSearchLine::resetFilter()
{
if (mTagFilterCombo->isVisible()) {
mTagFilterCombo->setCurrentIndex(0);
}
mSearchEdit->clearFilterButtonClicked();
mSearchEdit->setLocked(false);
}
void QuickSearchLine::slotFilterActionChanged(const QList<Akonadi::MessageStatus> &lst)
{
mLstStatus = lst;
Q_EMIT statusButtonsClicked();
}
QList<Akonadi::MessageStatus> QuickSearchLine::status() const
{
return mLstStatus;
}
bool QuickSearchLine::containsOutboundMessages() const
{
return mSearchEdit->containsOutboundMessages();
}
void QuickSearchLine::setContainsOutboundMessages(bool containsOutboundMessages)
{
mSearchEdit->setContainsOutboundMessages(containsOutboundMessages);
}
void QuickSearchLine::updateComboboxVisibility()
{
mTagFilterCombo->setVisible(!mSearchEdit->isHidden() && mTagFilterCombo->count());
}
bool QuickSearchLine::eventFilter(QObject *object, QEvent *e)
{
const bool shortCutOverride = (e->type() == QEvent::ShortcutOverride);
if (shortCutOverride) {
e->accept();
return true;
}
return QWidget::eventFilter(object, e);
}
void QuickSearchLine::changeQuicksearchVisibility(bool show)
{
mSearchEdit->setVisible(show);
mTagFilterCombo->setVisible(show && mTagFilterCombo->count());
}
void QuickSearchLine::addCompletionItem(const QString &str)
{
mSearchEdit->addCompletionItem(str);
}
diff --git a/messagelist/src/core/widgets/quicksearchline.h b/messagelist/src/core/widgets/quicksearchline.h
index 95e80c45..eee3810e 100644
--- a/messagelist/src/core/widgets/quicksearchline.h
+++ b/messagelist/src/core/widgets/quicksearchline.h
@@ -1,90 +1,90 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#ifndef QUICKSEARCHLINE_H
#define QUICKSEARCHLINE_H
#include <QWidget>
#include "messagelist_export.h"
#include <Akonadi/KMime/MessageStatus>
class KComboBox;
class QToolButton;
namespace MessageList {
namespace Core {
class SearchLineStatus;
class MESSAGELIST_EXPORT QuickSearchLine : public QWidget
{
Q_OBJECT
public:
explicit QuickSearchLine(QWidget *parent = nullptr);
~QuickSearchLine() override;
enum SearchOption {
SearchEveryWhere = 1,
SearchAgainstBody = 2,
SearchAgainstSubject = 4,
SearchAgainstFrom = 8,
SearchAgainstBcc = 16,
SearchAgainstTo = 32
};
Q_ENUM(SearchOption)
Q_DECLARE_FLAGS(SearchOptions, SearchOption)
Q_REQUIRED_RESULT SearchOptions searchOptions() const;
void focusQuickSearch(const QString &selectedText);
Q_REQUIRED_RESULT KComboBox *tagFilterComboBox() const;
Q_REQUIRED_RESULT SearchLineStatus *searchEdit() const;
Q_REQUIRED_RESULT QToolButton *openFullSearchButton() const;
void resetFilter();
Q_REQUIRED_RESULT QList<Akonadi::MessageStatus> status() const;
void updateComboboxVisibility();
Q_REQUIRED_RESULT bool containsOutboundMessages() const;
void setContainsOutboundMessages(bool containsOutboundMessages);
void changeQuicksearchVisibility(bool show);
void addCompletionItem(const QString &str);
Q_SIGNALS:
void clearButtonClicked();
void searchEditTextEdited(const QString &);
void searchOptionChanged();
void statusButtonsClicked();
void forceLostFocus();
protected:
bool eventFilter(QObject *object, QEvent *e) override;
private Q_SLOTS:
void slotSearchEditTextEdited(const QString &text);
void slotClearButtonClicked();
void slotFilterActionChanged(const QList<Akonadi::MessageStatus> &lst);
private:
SearchLineStatus *mSearchEdit = nullptr;
KComboBox *mTagFilterCombo = nullptr;
QList<Akonadi::MessageStatus> mLstStatus;
};
}
}
#endif // QUICKSEARCHLINE_H
diff --git a/messagelist/src/core/widgets/quicksearchwarning.cpp b/messagelist/src/core/widgets/quicksearchwarning.cpp
index aa9dc219..fff69cf9 100644
--- a/messagelist/src/core/widgets/quicksearchwarning.cpp
+++ b/messagelist/src/core/widgets/quicksearchwarning.cpp
@@ -1,65 +1,66 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "quicksearchwarning.h"
#include "messagelistsettings.h"
#include <KLocalizedString>
#include <QAction>
using namespace MessageList::Core;
QuickSearchWarning::QuickSearchWarning(QWidget *parent)
: KMessageWidget(parent)
{
setVisible(false);
setCloseButtonVisible(true);
setMessageType(Warning);
setWordWrap(true);
setText(i18n("The words less than 3 letters are ignored."));
QAction *action = new QAction(i18n("Do not show again"), this);
action->setObjectName(QStringLiteral("donotshowagain"));
connect(action, &QAction::triggered, this, &QuickSearchWarning::slotDoNotRememberIt);
addAction(action);
}
QuickSearchWarning::~QuickSearchWarning()
{
}
void QuickSearchWarning::setSearchText(const QString &text)
{
if (!MessageList::MessageListSettings::quickSearchWarningDoNotShowAgain()) {
const QStringList lstText = text.split(QLatin1Char(' '), QString::SkipEmptyParts);
bool foundLessThanThreeCharacters = false;
for (const QString &text : lstText) {
if (text.trimmed().size() < 3) {
foundLessThanThreeCharacters = true;
break;
}
}
if (foundLessThanThreeCharacters) {
animatedShow();
} else {
animatedHide();
}
}
}
void QuickSearchWarning::slotDoNotRememberIt()
{
MessageList::MessageListSettings::setQuickSearchWarningDoNotShowAgain(true);
animatedHide();
}
diff --git a/messagelist/src/core/widgets/quicksearchwarning.h b/messagelist/src/core/widgets/quicksearchwarning.h
index 97c15491..97fc6348 100644
--- a/messagelist/src/core/widgets/quicksearchwarning.h
+++ b/messagelist/src/core/widgets/quicksearchwarning.h
@@ -1,38 +1,39 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef QUICKSEARCHWARNING_H
#define QUICKSEARCHWARNING_H
#include <KMessageWidget>
#include "messagelist_private_export.h"
namespace MessageList {
namespace Core {
class MESSAGELIST_TESTS_EXPORT QuickSearchWarning : public KMessageWidget
{
Q_OBJECT
public:
explicit QuickSearchWarning(QWidget *parent = nullptr);
~QuickSearchWarning();
void setSearchText(const QString &text);
private Q_SLOTS:
void slotDoNotRememberIt();
};
}
}
#endif // QUICKSEARCHWARNING_H
diff --git a/messagelist/src/core/widgets/searchcollectionindexingwarning.cpp b/messagelist/src/core/widgets/searchcollectionindexingwarning.cpp
index d9f2e08d..1edf4e6b 100644
--- a/messagelist/src/core/widgets/searchcollectionindexingwarning.cpp
+++ b/messagelist/src/core/widgets/searchcollectionindexingwarning.cpp
@@ -1,147 +1,149 @@
/*
* Copyright (c) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
*
* 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.
*
*/
#include "searchcollectionindexingwarning.h"
#include "messagelist_debug.h"
#include <AkonadiCore/persistentsearchattribute.h>
#include <AkonadiCore/collectionfetchjob.h>
#include <AkonadiCore/collectionfetchscope.h>
#include <AkonadiCore/collectionstatistics.h>
#include <AkonadiCore/entityhiddenattribute.h>
#include <AkonadiCore/cachepolicy.h>
#include <PimCommon/PimUtil>
#include <KLocalizedString>
#include <AkonadiSearch/PIM/indexeditems.h>
using namespace MessageList::Core;
SearchCollectionIndexingWarning::SearchCollectionIndexingWarning(QWidget *parent)
: KMessageWidget(parent)
, mIndexedItems(new Akonadi::Search::PIM::IndexedItems(this))
{
setVisible(false);
setWordWrap(true);
setText(i18n("Some of the search folders in this query are still being indexed "
"or are excluded from indexing completely. The results below may be incomplete."));
setCloseButtonVisible(true);
setMessageType(Information);
}
SearchCollectionIndexingWarning::~SearchCollectionIndexingWarning()
{
}
Akonadi::CollectionFetchJob *SearchCollectionIndexingWarning::fetchCollections(const Akonadi::Collection::List &cols, bool recursive)
{
const Akonadi::CollectionFetchJob::Type type = recursive ? Akonadi::CollectionFetchJob::Recursive : Akonadi::CollectionFetchJob::Base;
Akonadi::CollectionFetchJob *fetch = new Akonadi::CollectionFetchJob(cols, type, this);
fetch->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None);
fetch->fetchScope().setContentMimeTypes(QStringList() << Akonadi::Collection::mimeType()
<< QStringLiteral("message/rfc822"));
fetch->fetchScope().setIncludeStatistics(true);
return fetch;
}
void SearchCollectionIndexingWarning::setCollection(const Akonadi::Collection &collection)
{
if (collection == mCollection) {
return;
}
animatedHide();
mCollection = collection;
mCollections.clear();
// Not a search collection?
if (!collection.hasAttribute<Akonadi::PersistentSearchAttribute>()) {
return;
}
- Akonadi::PersistentSearchAttribute *attr = collection.attribute<Akonadi::PersistentSearchAttribute>();
+ const Akonadi::PersistentSearchAttribute *attr = collection.attribute<Akonadi::PersistentSearchAttribute>();
Akonadi::Collection::List cols;
- Q_FOREACH (qint64 col, attr->queryCollections()) {
+ const QVector<qint64> queryCols = attr->queryCollections();
+ cols.reserve(queryCols.count());
+ for (qint64 col : queryCols) {
cols.push_back(Akonadi::Collection(col));
}
if (cols.isEmpty()) {
return;
}
// First retrieve the top-level collections
Akonadi::CollectionFetchJob *fetch = fetchCollections(cols, false);
fetch->setProperty("recursiveQuery", attr->isRecursive());
connect(fetch, &Akonadi::CollectionFetchJob::finished, this, &SearchCollectionIndexingWarning::queryRootCollectionFetchFinished);
}
void SearchCollectionIndexingWarning::queryRootCollectionFetchFinished(KJob *job)
{
if (job->error()) {
qCWarning(MESSAGELIST_LOG) << job->errorString();
return;
}
// Store the root collections
mCollections = qobject_cast<Akonadi::CollectionFetchJob *>(job)->collections();
if (job->property("recursiveQuery").toBool()) {
// Fetch all descendants, if necessary
Akonadi::CollectionFetchJob *fetch = fetchCollections(mCollections, true);
connect(fetch, &Akonadi::CollectionFetchJob::finished, this, &SearchCollectionIndexingWarning::queryCollectionFetchFinished);
} else {
queryIndexerStatus();
}
}
void SearchCollectionIndexingWarning::queryCollectionFetchFinished(KJob *job)
{
if (job->error()) {
qCWarning(MESSAGELIST_LOG) << job->errorString();
return;
}
mCollections += qobject_cast<Akonadi::CollectionFetchJob *>(job)->collections();
queryIndexerStatus();
}
void SearchCollectionIndexingWarning::queryIndexerStatus()
{
bool allFullyIndexed = true;
for (const Akonadi::Collection &col : qAsConst(mCollections)) {
if (col.hasAttribute<Akonadi::EntityHiddenAttribute>()) {
continue;
}
if (PimCommon::Util::isImapResource(col.resource()) && !col.cachePolicy().localParts().contains(QLatin1String("RFC822"))) {
continue;
}
const qlonglong result = mIndexedItems->indexedItems(col.id());
qCDebug(MESSAGELIST_LOG) << "Collection:" << col.displayName() << "(" << col.id() << "), count:" << col.statistics().count() << ", index:" << result;
if (col.statistics().count() != result) {
allFullyIndexed = false;
break;
}
}
if (!allFullyIndexed) {
animatedShow();
}
}
diff --git a/messagelist/src/core/widgets/searchlinestatus.cpp b/messagelist/src/core/widgets/searchlinestatus.cpp
index cffa8f11..0eea11c5 100644
--- a/messagelist/src/core/widgets/searchlinestatus.cpp
+++ b/messagelist/src/core/widgets/searchlinestatus.cpp
@@ -1,370 +1,369 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "searchlinestatus.h"
#include "messagelist_debug.h"
#include <KLocalizedString>
#include <QAction>
#include <QStandardPaths>
#include <QMenu>
#include <KIconEngine>
#include <KIconLoader>
#include <QWidgetAction>
#include <QPushButton>
#include <QCompleter>
#include <QContextMenuEvent>
#include <QStringListModel>
#include <QAbstractItemView>
#include <KColorScheme>
static const char qLineEditclearButtonActionNameC[] = "_q_qlineeditclearaction";
#define MAX_COMPLETION_ITEMS 20
using namespace MessageList::Core;
SearchLineStatus::SearchLineStatus(QWidget *parent)
: QLineEdit(parent)
, mLocked(false)
, mHasFilter(false)
, mLockAction(nullptr)
, mFiltersAction(nullptr)
, mFilterMenu(nullptr)
, mContainsOutboundMessages(false)
{
mCompleter = new QCompleter(this);
mCompleterListModel = new QStringListModel(this);
mCompleter->setModel(mCompleterListModel);
setCompleter(mCompleter);
setClearButtonEnabled(true);
initializeActions();
createMenuSearch();
QAction *act = findChild<QAction *>(QLatin1String(qLineEditclearButtonActionNameC));
if (act) {
connect(act, &QAction::triggered, this, &SearchLineStatus::slotClear);
} else {
qCWarning(MESSAGELIST_LOG) << "Clear button name was changed ! Please verify qt code";
}
}
SearchLineStatus::~SearchLineStatus()
{
}
void SearchLineStatus::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Escape) {
- qDebug() << " void SearchLineStatus::keyPressEvent(QKeyEvent *e)" << mCompleter->widget();
if (mCompleter->popup()->isVisible()) {
QLineEdit::keyPressEvent(e);
} else {
Q_EMIT forceLostFocus();
- qDebug() << " void SearchLineStatus::keyPressEvent(QKeyEvent *e)ESCAPre";
}
} else {
QLineEdit::keyPressEvent(e);
}
}
void SearchLineStatus::slotClear()
{
Q_EMIT clearButtonClicked();
}
void SearchLineStatus::updateLockAction()
{
if (mLocked) {
mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
mLockAction->setToolTip(i18nc("@info:tooltip",
"Prevent the quick search field from being cleared when changing folders"));
} else {
mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
mLockAction->setToolTip(i18nc("@info:tooltip", "Clear the quick search field when changing folders"));
}
}
void SearchLineStatus::setLocked(bool b)
{
if (mLocked != b) {
slotToggledLockAction();
}
}
bool SearchLineStatus::locked() const
{
return mLocked;
}
void SearchLineStatus::initializeActions()
{
mLockAction = addAction(QIcon::fromTheme(QStringLiteral("object-locked")), QLineEdit::TrailingPosition);
mLockAction->setWhatsThis(
i18nc("@info:whatsthis",
"Toggle this button if you want to keep your quick search "
"locked when moving to other folders or when narrowing the search "
"by message status."));
connect(mLockAction, &QAction::triggered, this, &SearchLineStatus::slotToggledLockAction);
updateLockAction();
const QStringList overlays = QStringList() << QStringLiteral("list-add");
mWithFilter = QIcon(new KIconEngine(QStringLiteral("view-filter"), KIconLoader::global(), overlays));
mWithoutFilter = QIcon::fromTheme(QStringLiteral("view-filter"));
mFiltersAction = addAction(mWithoutFilter, QLineEdit::LeadingPosition);
mFiltersAction->setToolTip(i18n("Filter Mails by Status"));
connect(mFiltersAction, &QAction::triggered, this, &SearchLineStatus::showMenu);
}
void SearchLineStatus::slotToggledLockAction()
{
mLocked = !mLocked;
updateLockAction();
}
void SearchLineStatus::updateFilters()
{
QList<Akonadi::MessageStatus> lstStatus;
for (QAction *act : qAsConst(mFilterListActions)) {
if (act->isChecked()) {
Akonadi::MessageStatus status;
status.fromQInt32(static_cast< qint32 >(act->data().toInt()));
lstStatus.append(status);
}
}
mHasFilter = !lstStatus.isEmpty();
Q_EMIT filterActionChanged(lstStatus);
updateFilterActionIcon();
}
void SearchLineStatus::showMenu()
{
if (mFilterMenu->exec(mapToGlobal(QPoint(0, height())))) {
updateFilters();
}
}
void SearchLineStatus::clearFilterAction()
{
for (QAction *act : qAsConst(mFilterListActions)) {
act->setChecked(false);
}
mHasFilter = false;
updateFilterActionIcon();
}
void SearchLineStatus::createFilterAction(const QIcon &icon, const QString &text, int value)
{
QAction *act = new QAction(icon, text, this);
act->setCheckable(true);
act->setData(value);
mFilterMenu->addAction(act);
mFilterListActions.append(act);
}
void SearchLineStatus::updateFilterActionIcon()
{
mFiltersAction->setIcon(mHasFilter ? mWithFilter : mWithoutFilter);
if (mColorName.isEmpty()) {
const KColorScheme::BackgroundRole bgColorScheme(KColorScheme::PositiveBackground);
KStatefulBrush bgBrush(KColorScheme::View, bgColorScheme);
mColorName = bgBrush.brush(this).color().name();
}
setStyleSheet(mHasFilter ? QStringLiteral("QLineEdit{ background-color:%1 }").arg(mColorName) : QString());
}
void SearchLineStatus::clearFilterButtonClicked()
{
clearFilterAction();
clearFilterByAction();
updateFilters();
}
void SearchLineStatus::createMenuSearch()
{
mFilterMenu = new QMenu(this);
mFilterMenu->setObjectName(QStringLiteral("filtermenu"));
QWidgetAction *clearWidgetAction = new QWidgetAction(mFilterMenu);
QPushButton *clearFilterButton = new QPushButton(i18n("Clear Filter"), mFilterMenu);
connect(clearFilterButton, &QPushButton::clicked, this, &SearchLineStatus::clearFilterButtonClicked);
clearWidgetAction->setDefaultWidget(clearFilterButton);
mFilterMenu->addAction(clearWidgetAction);
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-unread")), i18nc("@action:inmenu Status of a message", "Unread"),
Akonadi::MessageStatus::statusUnread().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-replied")),
i18nc("@action:inmenu Status of a message", "Replied"),
Akonadi::MessageStatus::statusReplied().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-forwarded")),
i18nc("@action:inmenu Status of a message", "Forwarded"),
Akonadi::MessageStatus::statusForwarded().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("emblem-important")),
i18nc("@action:inmenu Status of a message", "Important"),
Akonadi::MessageStatus::statusImportant().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-task")),
i18nc("@action:inmenu Status of a message", "Action Item"),
Akonadi::MessageStatus::statusToAct().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch.png")),
i18nc("@action:inmenu Status of a message", "Watched"),
Akonadi::MessageStatus::statusWatched().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored.png")),
i18nc("@action:inmenu Status of a message", "Ignored"),
Akonadi::MessageStatus::statusIgnored().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-attachment")),
i18nc("@action:inmenu Status of a message", "Has Attachment"),
Akonadi::MessageStatus::statusHasAttachment().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-invitation")),
i18nc("@action:inmenu Status of a message", "Has Invitation"),
Akonadi::MessageStatus::statusHasInvitation().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-junk")),
i18nc("@action:inmenu Status of a message", "Spam"),
Akonadi::MessageStatus::statusSpam().toQInt32());
createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")),
i18nc("@action:inmenu Status of a message", "Ham"),
Akonadi::MessageStatus::statusHam().toQInt32());
createFilterByAction();
}
void SearchLineStatus::createFilterByAction()
{
mFilterMenu->addSeparator();
QActionGroup *grp = new QActionGroup(mFilterMenu);
mSearchEveryWhereAction = new QAction(i18n("Full Message"), mFilterMenu);
mSearchEveryWhereAction->setCheckable(true);
mSearchEveryWhereAction->setChecked(true);
mFilterMenu->addAction(mSearchEveryWhereAction);
grp->addAction(mSearchEveryWhereAction);
mSearchAgainstBodyAction = new QAction(i18n("Body"), mFilterMenu);
grp->addAction(mSearchAgainstBodyAction);
mFilterMenu->addAction(mSearchAgainstBodyAction);
mSearchAgainstBodyAction->setCheckable(true);
mSearchAgainstSubjectAction = new QAction(i18n("Subject"), mFilterMenu);
grp->addAction(mSearchAgainstSubjectAction);
mFilterMenu->addAction(mSearchAgainstSubjectAction);
mSearchAgainstSubjectAction->setCheckable(true);
mSearchAgainstFromOrToAction = new QAction(mFilterMenu);
changeSearchAgainstFromOrToText();
grp->addAction(mSearchAgainstFromOrToAction);
mFilterMenu->addAction(mSearchAgainstFromOrToAction);
mSearchAgainstFromOrToAction->setCheckable(true);
mSearchAgainstBccAction = new QAction(i18n("BCC"), mFilterMenu);
grp->addAction(mSearchAgainstBccAction);
mFilterMenu->addAction(mSearchAgainstBccAction);
mSearchAgainstBccAction->setCheckable(true);
connect(grp, &QActionGroup::triggered, this, &SearchLineStatus::slotFilterActionClicked);
}
void SearchLineStatus::clearFilterByAction()
{
mSearchEveryWhereAction->setChecked(true);
}
bool SearchLineStatus::containsOutboundMessages() const
{
return mContainsOutboundMessages;
}
void SearchLineStatus::setContainsOutboundMessages(bool containsOutboundMessages)
{
if (mContainsOutboundMessages != containsOutboundMessages) {
mContainsOutboundMessages = containsOutboundMessages;
changeSearchAgainstFromOrToText();
}
}
void SearchLineStatus::changeSearchAgainstFromOrToText()
{
if (mContainsOutboundMessages) {
mSearchAgainstFromOrToAction->setText(i18n("To"));
} else {
mSearchAgainstFromOrToAction->setText(i18n("From"));
}
}
QuickSearchLine::SearchOptions SearchLineStatus::searchOptions() const
{
QuickSearchLine::SearchOptions searchOptions;
if (mSearchEveryWhereAction->isChecked()) {
searchOptions |= QuickSearchLine::SearchEveryWhere;
}
if (mSearchAgainstBodyAction->isChecked()) {
searchOptions |= QuickSearchLine::SearchAgainstBody;
}
if (mSearchAgainstSubjectAction->isChecked()) {
searchOptions |= QuickSearchLine::SearchAgainstSubject;
}
if (mSearchAgainstFromOrToAction->isChecked()) {
if (mContainsOutboundMessages) {
searchOptions |= QuickSearchLine::SearchAgainstTo;
} else {
searchOptions |= QuickSearchLine::SearchAgainstFrom;
}
}
if (mSearchAgainstBccAction->isChecked()) {
searchOptions |= QuickSearchLine::SearchAgainstBcc;
}
return searchOptions;
}
void SearchLineStatus::slotFilterActionClicked(QAction *act)
{
Q_UNUSED(act);
Q_EMIT searchOptionChanged();
}
void SearchLineStatus::addCompletionItem(const QString &str)
{
mListCompetion.removeAll(str);
mListCompetion.prepend(str);
while (mListCompetion.size() > MAX_COMPLETION_ITEMS) {
mListCompetion.removeLast();
}
mCompleterListModel->setStringList(mListCompetion);
}
void SearchLineStatus::contextMenuEvent(QContextMenuEvent *e)
{
QMenu *popup = QLineEdit::createStandardContextMenu();
if (popup) {
popup->addSeparator();
popup->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")), i18n("Clear History"), this, &SearchLineStatus::slotClearHistory);
popup->exec(e->globalPos());
delete popup;
}
}
void SearchLineStatus::slotClearHistory()
{
mListCompetion.clear();
mCompleterListModel->setStringList(mListCompetion);
}
diff --git a/messagelist/src/core/widgets/searchlinestatus.h b/messagelist/src/core/widgets/searchlinestatus.h
index c66343f4..932b1010 100644
--- a/messagelist/src/core/widgets/searchlinestatus.h
+++ b/messagelist/src/core/widgets/searchlinestatus.h
@@ -1,95 +1,96 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef SEARCHLINESTATUS_H
#define SEARCHLINESTATUS_H
#include <QLineEdit>
#include <Akonadi/KMime/MessageStatus>
#include "messagelist_private_export.h"
#include "quicksearchline.h"
#include <QIcon>
class QStringListModel;
class QAction;
namespace MessageList {
namespace Core {
class MESSAGELIST_TESTS_EXPORT SearchLineStatus : public QLineEdit
{
Q_OBJECT
public:
explicit SearchLineStatus(QWidget *parent = nullptr);
~SearchLineStatus() override;
void setLocked(bool b);
Q_REQUIRED_RESULT bool locked() const;
void setContainsOutboundMessages(bool containsOutboundMessages);
Q_REQUIRED_RESULT bool containsOutboundMessages() const;
Q_REQUIRED_RESULT QuickSearchLine::SearchOptions searchOptions() const;
void addCompletionItem(const QString &str);
void slotClearHistory();
void clearFilterButtonClicked();
Q_SIGNALS:
void filterActionChanged(const QList<Akonadi::MessageStatus> &lst);
void searchOptionChanged();
void clearButtonClicked();
void forceLostFocus();
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private Q_SLOTS:
void slotToggledLockAction();
void showMenu();
void slotFilterActionClicked(QAction *act);
private:
void clearFilterAction();
void createFilterAction(const QIcon &icon, const QString &text, int value);
void createMenuSearch();
void updateLockAction();
void initializeActions();
void updateFilterActionIcon();
void updateFilters();
void createFilterByAction();
void clearFilterByAction();
void changeSearchAgainstFromOrToText();
void slotClear();
bool mLocked;
bool mHasFilter;
QIcon mWithoutFilter;
QIcon mWithFilter;
QAction *mLockAction = nullptr;
QAction *mFiltersAction = nullptr;
QMenu *mFilterMenu = nullptr;
QCompleter *mCompleter = nullptr;
QList<QAction *> mFilterListActions;
QAction *mSearchEveryWhereAction;
QAction *mSearchAgainstBodyAction;
QAction *mSearchAgainstSubjectAction;
QAction *mSearchAgainstFromOrToAction;
QAction *mSearchAgainstBccAction;
QStringListModel *mCompleterListModel;
QStringList mListCompetion;
QString mColorName;
bool mContainsOutboundMessages;
};
}
}
#endif // SEARCHLINESTATUS_H
diff --git a/messagelist/src/messagelist_private_export.h b/messagelist/src/messagelist_private_export.h
index 41b9438d..b88d27c6 100644
--- a/messagelist/src/messagelist_private_export.h
+++ b/messagelist/src/messagelist_private_export.h
@@ -1,34 +1,34 @@
/* This file is part of the KDE project
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MESSAGELISTPRIVATE_EXPORT_H
#define MESSAGELISTPRIVATE_EXPORT_H
#include "messagelist_export.h"
/* Classes which are exported only for unit tests */
#ifdef BUILD_TESTING
# ifndef MESSAGELIST_TESTS_EXPORT
# define MESSAGELIST_TESTS_EXPORT MESSAGELIST_EXPORT
# endif
#else /* not compiling tests */
# define MESSAGELIST_TESTS_EXPORT
#endif
#endif
diff --git a/messagelist/src/messagelistutil.cpp b/messagelist/src/messagelistutil.cpp
index a46ef7dd..b3f9c461 100644
--- a/messagelist/src/messagelistutil.cpp
+++ b/messagelist/src/messagelistutil.cpp
@@ -1,176 +1,176 @@
/*
This file is part of KMail, the KDE mail client.
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
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.
+ 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.
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
*/
#include "messagelistutil.h"
#include "messagelistutil_p.h"
#include "messagelistsettings.h"
#include <KConfigGroup>
#include <QMenu>
#include <QIcon>
#include <KLocalizedString>
#include <KMime/Message>
#include <Item>
#include <KColorScheme>
QString MessageList::Util::messageSortingConfigName()
{
return QStringLiteral("MessageSorting");
}
QString MessageList::Util::messageSortDirectionConfigName()
{
return QStringLiteral("MessageSortDirection");
}
QString MessageList::Util::groupSortingConfigName()
{
return QStringLiteral("GroupSorting");
}
QString MessageList::Util::groupSortDirectionConfigName()
{
return QStringLiteral("GroupSortDirection");
}
QString MessageList::Util::messageUniqueIdConfigName()
{
return QStringLiteral("MessageUniqueIdForStorageModel%1");
}
QString MessageList::Util::storageModelSortOrderGroup()
{
return QStringLiteral("MessageListView::StorageModelSortOrder");
}
QString MessageList::Util::storageModelThemesGroup()
{
return QStringLiteral("MessageListView::StorageModelThemes");
}
QString MessageList::Util::storageModelAggregationsGroup()
{
return QStringLiteral("MessageListView::StorageModelAggregations");
}
QString MessageList::Util::setForStorageModelConfigName()
{
return QStringLiteral("SetForStorageModel%1");
}
QString MessageList::Util::storageModelSelectedMessageGroup()
{
return QStringLiteral("MessageListView::StorageModelSelectedMessages");
}
void MessageList::Util::deleteConfig(const QString &collectionId)
{
KConfigGroup confselectedMessage(MessageListSettings::self()->config(),
MessageList::Util::storageModelSelectedMessageGroup());
confselectedMessage.deleteEntry(MessageList::Util::messageUniqueIdConfigName().arg(collectionId));
KConfigGroup storageModelOrder(MessageListSettings::self()->config(),
MessageList::Util::storageModelSortOrderGroup());
storageModelOrder.deleteEntry(collectionId + groupSortDirectionConfigName());
storageModelOrder.deleteEntry(collectionId + groupSortingConfigName());
storageModelOrder.deleteEntry(collectionId + messageSortDirectionConfigName());
storageModelOrder.deleteEntry(collectionId + messageSortingConfigName());
KConfigGroup storageModelTheme(MessageListSettings::self()->config(),
MessageList::Util::storageModelThemesGroup());
storageModelTheme.deleteEntry(collectionId + setForStorageModelConfigName());
KConfigGroup storageModelAggregation(MessageListSettings::self()->config(),
MessageList::Util::storageModelAggregationsGroup());
storageModelAggregation.deleteEntry(collectionId + setForStorageModelConfigName());
}
QColor MessageList::Util::unreadDefaultMessageColor()
{
return KColorScheme(QPalette::Active, KColorScheme::Complementary).decoration(KColorScheme::FocusColor).color();
}
QColor MessageList::Util::importantDefaultMessageColor()
{
return KColorScheme(QPalette::Active).foreground(KColorScheme::NegativeText).color();
}
QColor MessageList::Util::todoDefaultMessageColor()
{
return KColorScheme(QPalette::Active).foreground(KColorScheme::PositiveText).color();
}
void MessageList::Util::fillViewMenu(QMenu *menu, QObject *receiver)
{
QMenu *sortingMenu = new QMenu(i18n("Sorting"), menu);
sortingMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-sort-ascending")));
menu->addMenu(sortingMenu);
QObject::connect(sortingMenu, SIGNAL(aboutToShow()), receiver, SLOT(sortOrderMenuAboutToShow()));
QMenu *aggregationMenu = new QMenu(i18n("Aggregation"), menu);
aggregationMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-process-tree")));
menu->addMenu(aggregationMenu);
QObject::connect(aggregationMenu, SIGNAL(aboutToShow()), receiver, SLOT(aggregationMenuAboutToShow()));
QMenu *themeMenu = new QMenu(i18n("Theme"), menu);
themeMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")));
menu->addMenu(themeMenu);
QObject::connect(themeMenu, SIGNAL(aboutToShow()),
receiver, SLOT(themeMenuAboutToShow()));
}
QString MessageList::Util::contentSummary(const Akonadi::Item &item)
{
if (!item.hasPayload<KMime::Message::Ptr>()) {
return QString();
}
KMime::Message::Ptr message = item.payload<KMime::Message::Ptr>();
KMime::Content *textContent = message->textContent();
if (!textContent) {
return QString();
}
const QString content = textContent->decodedText(true, true);
if (content.isEmpty()) {
return QString();
}
// Extract the first 5 non-empty, non-quoted lines from the content and return it
int numLines = 0;
const int maxLines = 5;
const QStringList lines = content.split(QLatin1Char('\n'));
if (lines.isEmpty()) {
return QString();
}
if (lines.count() == 1 && content.length() > 100) {
return content.left(100);
-
}
QString ret;
for (const QString &line : lines) {
const QString lineTrimmed = line.trimmed();
const bool isQuoted = lineTrimmed.startsWith(QLatin1Char('>')) || lineTrimmed.startsWith(QLatin1Char('|'));
if (!isQuoted && !lineTrimmed.isEmpty()) {
ret += line + QLatin1Char('\n');
numLines++;
if (numLines >= maxLines) {
break;
}
}
}
return ret.toHtmlEscaped();
}
diff --git a/messagelist/src/messagelistutil.h b/messagelist/src/messagelistutil.h
index 106da16d..166668aa 100644
--- a/messagelist/src/messagelistutil.h
+++ b/messagelist/src/messagelistutil.h
@@ -1,40 +1,41 @@
/*
This file is part of KMail, the KDE mail client.
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
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.
+ 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.
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
*/
#ifndef MESSAGELISTUTIL_H
#define MESSAGELISTUTIL_H
#include <messagelist_export.h>
#include <QString>
#include <QColor>
namespace Akonadi {
class Item;
}
namespace MessageList {
namespace Util {
MESSAGELIST_EXPORT void deleteConfig(const QString &collectionId);
MESSAGELIST_EXPORT QColor unreadDefaultMessageColor();
MESSAGELIST_EXPORT QColor importantDefaultMessageColor();
MESSAGELIST_EXPORT QColor todoDefaultMessageColor();
/// Returns the first few lines of the actual email text if available.
MESSAGELIST_EXPORT QString contentSummary(const Akonadi::Item &item);
}
}
#endif /* MESSAGELISTUTIL_H */
diff --git a/messagelist/src/messagelistutil_p.h b/messagelist/src/messagelistutil_p.h
index b0aa4597..d4ef5c14 100644
--- a/messagelist/src/messagelistutil_p.h
+++ b/messagelist/src/messagelistutil_p.h
@@ -1,43 +1,44 @@
/*
This file is part of KMail, the KDE mail client.
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
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.
+ 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.
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
*/
#ifndef MESSAGELISTUTIL_P_H
#define MESSAGELISTUTIL_P_H
#include <QObject>
#include <QString>
class QMenu;
namespace MessageList {
namespace Util {
QString messageSortingConfigName();
QString messageSortDirectionConfigName();
QString groupSortingConfigName();
QString groupSortDirectionConfigName();
QString messageUniqueIdConfigName();
QString storageModelSortOrderGroup();
QString storageModelThemesGroup();
QString storageModelAggregationsGroup();
QString setForStorageModelConfigName();
QString storageModelSelectedMessageGroup();
void fillViewMenu(QMenu *menu, QObject *receiver);
}
}
#endif /* MESSAGELISTUTIL_H */
diff --git a/messagelist/src/pane.cpp b/messagelist/src/pane.cpp
index fe551d8f..f9b9ac06 100644
--- a/messagelist/src/pane.cpp
+++ b/messagelist/src/pane.cpp
@@ -1,1187 +1,1215 @@
/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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.
*/
#include "pane.h"
#include <KActionCollection>
#include <KActionMenu>
#include <QIcon>
#include <KLocalizedString>
#include <QMenu>
#include <KXMLGUIClient>
#include <QAction>
#include <KToggleAction>
#include <QAbstractItemModel>
#include <QAbstractProxyModel>
#include <QItemSelectionModel>
#include <QTabBar>
#include <QToolButton>
#include <QMouseEvent>
#include <QHeaderView>
#include <QRegularExpression>
#include "storagemodel.h"
#include "core/widgets/quicksearchline.h"
#include "widget.h"
#include "messagelistsettings.h"
#include "core/manager.h"
#include <Akonadi/KMime/MessageStatus>
#include "core/model.h"
#include "messagelistutil_p.h"
namespace MessageList {
class Q_DECL_HIDDEN Pane::Private
{
public:
Private(Pane *owner)
: q(owner)
, mXmlGuiClient(nullptr)
, mActionMenu(nullptr)
, mModel(nullptr)
, mSelectionModel(nullptr)
, mPreSelectionMode(Core::PreSelectLastSelected)
, mNewTabButton(nullptr)
, mCloseTabButton(nullptr)
, mCloseTabAction(nullptr)
, mActivateNextTabAction(nullptr)
, mActivatePreviousTabAction(nullptr)
, mMoveTabLeftAction(nullptr)
, mMoveTabRightAction(nullptr)
, mPreferEmptyTab(false)
, mMaxTabCreated(0)
{
}
void setCurrentFolder(const QModelIndex &etmIndex);
void onNewTabClicked();
void onCloseTabClicked();
void activateTab();
void closeTab(QWidget *);
void onCurrentTabChanged();
void onTabContextMenuRequest(const QPoint &pos);
void activateNextTab();
void activatePreviousTab();
void moveTabLeft();
void moveTabRight();
void moveTabBackward();
void moveTabForward();
void changeQuicksearchVisibility(bool);
void addActivateTabAction(int i);
void slotTabCloseRequested(int index);
QItemSelection mapSelectionFromSource(const QItemSelection &selection) const;
void updateTabControls();
Pane *const q;
KXMLGUIClient *mXmlGuiClient = nullptr;
KActionMenu *mActionMenu = nullptr;
QAbstractItemModel *mModel = nullptr;
QItemSelectionModel *mSelectionModel = nullptr;
Core::PreSelectionMode mPreSelectionMode;
QHash<Widget *, QItemSelectionModel *> mWidgetSelectionHash;
QVector<const QAbstractProxyModel *> mProxyStack;
QToolButton *mNewTabButton = nullptr;
QToolButton *mCloseTabButton = nullptr;
QAction *mCloseTabAction = nullptr;
QAction *mActivateNextTabAction = nullptr;
QAction *mActivatePreviousTabAction = nullptr;
QAction *mMoveTabLeftAction = nullptr;
QAction *mMoveTabRightAction = nullptr;
bool mPreferEmptyTab;
int mMaxTabCreated;
};
} // namespace MessageList
using namespace Akonadi;
using namespace MessageList;
Pane::Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent)
: QTabWidget(parent)
, d(new Private(this))
{
setDocumentMode(true);
d->mModel = model;
d->mSelectionModel = selectionModel;
// Build the proxy stack
const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel *>(d->mSelectionModel->model());
while (proxyModel) {
if (static_cast<const QAbstractItemModel *>(proxyModel) == d->mModel) {
break;
}
d->mProxyStack << proxyModel;
const QAbstractProxyModel *nextProxyModel = qobject_cast<const QAbstractProxyModel *>(proxyModel->sourceModel());
if (!nextProxyModel) {
// It's the final model in the chain, so it is necessarily the sourceModel.
Q_ASSERT(qobject_cast<const QAbstractItemModel *>(proxyModel->sourceModel()) == d->mModel);
break;
}
proxyModel = nextProxyModel;
} // Proxy stack done
d->mNewTabButton = new QToolButton(this);
d->mNewTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
d->mNewTabButton->adjustSize();
d->mNewTabButton->setToolTip(i18nc("@info:tooltip", "Open a new tab"));
#ifndef QT_NO_ACCESSIBILITY
d->mNewTabButton->setAccessibleName(i18n("New tab"));
#endif
setCornerWidget(d->mNewTabButton, Qt::TopLeftCorner);
- connect(d->mNewTabButton, &QToolButton::clicked, this, [this]() { d->onNewTabClicked(); });
+ connect(d->mNewTabButton, &QToolButton::clicked, this, [this]() {
+ d->onNewTabClicked();
+ });
d->mCloseTabButton = new QToolButton(this);
d->mCloseTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
d->mCloseTabButton->adjustSize();
d->mCloseTabButton->setToolTip(i18nc("@info:tooltip", "Close the current tab"));
#ifndef QT_NO_ACCESSIBILITY
d->mCloseTabButton->setAccessibleName(i18n("Close tab"));
#endif
setCornerWidget(d->mCloseTabButton, Qt::TopRightCorner);
connect(d->mCloseTabButton, &QToolButton::clicked,
- this, [this]() {d->onCloseTabClicked(); });
+ this, [this]() {
+ d->onCloseTabClicked();
+ });
setTabsClosable(true);
- connect(this, &Pane::tabCloseRequested, this, [this](int index) { d->slotTabCloseRequested(index);});
+ connect(this, &Pane::tabCloseRequested, this, [this](int index) {
+ d->slotTabCloseRequested(index);
+ });
readConfig(restoreSession);
setMovable(true);
connect(this, &Pane::currentChanged,
- this, [this]() { d->onCurrentTabChanged(); });
+ this, [this]() {
+ d->onCurrentTabChanged();
+ });
setContextMenuPolicy(Qt::CustomContextMenu);
- connect(this, &Pane::customContextMenuRequested, this, [this](const QPoint &point) { d->onTabContextMenuRequest(point);});
+ connect(this, &Pane::customContextMenuRequested, this, [this](const QPoint &point) {
+ d->onTabContextMenuRequest(point);
+ });
connect(MessageListSettings::self(), &MessageListSettings::configChanged,
- this, [this]() { d->updateTabControls(); });
+ this, [this]() {
+ d->updateTabControls();
+ });
connect(this, &QTabWidget::tabBarDoubleClicked,
this, &Pane::createNewTab);
tabBar()->installEventFilter(this);
}
Pane::~Pane()
{
saveCurrentSelection();
writeConfig(true);
delete d;
}
void Pane::Private::addActivateTabAction(int i)
{
QString actionname;
actionname.sprintf("activate_tab_%02d", i);
QAction *action = new QAction(i18n("Activate Tab %1", i), q);
mXmlGuiClient->actionCollection()->addAction(actionname, action);
mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+%1").arg(i)));
- connect(action, &QAction::triggered, q, [this]() { activateTab(); });
+ connect(action, &QAction::triggered, q, [this]() {
+ activateTab();
+ });
}
void Pane::Private::slotTabCloseRequested(int index)
{
QWidget *w = q->widget(index);
if (w) {
closeTab(w);
}
}
void Pane::setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
{
d->mXmlGuiClient = xmlGuiClient;
KToggleAction *const showHideQuicksearch = new KToggleAction(i18n("Show Quick Search Bar"), this);
d->mXmlGuiClient->actionCollection()->setDefaultShortcut(showHideQuicksearch, Qt::CTRL + Qt::Key_H);
showHideQuicksearch->setChecked(MessageListSettings::showQuickSearch());
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("show_quick_search"), showHideQuicksearch);
- connect(showHideQuicksearch, &KToggleAction::triggered, this, [this](bool state) { d->changeQuicksearchVisibility(state); });
+ connect(showHideQuicksearch, &KToggleAction::triggered, this, [this](bool state) {
+ d->changeQuicksearchVisibility(state);
+ });
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w) {
w->setXmlGuiClient(d->mXmlGuiClient);
}
}
// Setup "View->Message List" actions.
if (xmlGuiClient) {
if (d->mActionMenu) {
d->mXmlGuiClient->actionCollection()->removeAction(d->mActionMenu);
}
d->mActionMenu = new KActionMenu(QIcon(), i18n("Message List"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("view_message_list"), d->mActionMenu);
MessageList::Util::fillViewMenu(d->mActionMenu->menu(), this);
d->mActionMenu->addSeparator();
QAction *action = new QAction(i18n("Create New Tab"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("create_new_tab"), action);
d->mXmlGuiClient->actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O));
- connect(action, &QAction::triggered, this, [this]() { d->onNewTabClicked(); });
+ connect(action, &QAction::triggered, this, [this]() {
+ d->onNewTabClicked();
+ });
d->mActionMenu->addAction(action);
d->mMaxTabCreated = count();
for (int i = 1; i < 10 && i <= count(); ++i) {
d->addActivateTabAction(i);
}
d->mCloseTabAction = new QAction(i18n("Close Tab"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("close_current_tab"), d->mCloseTabAction);
d->mXmlGuiClient->actionCollection()->setDefaultShortcuts(d->mCloseTabAction, QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W) << QKeySequence(Qt::CTRL + Qt::Key_W));
- connect(d->mCloseTabAction, &QAction::triggered, this, [this]() { d->onCloseTabClicked(); });
+ connect(d->mCloseTabAction, &QAction::triggered, this, [this]() {
+ d->onCloseTabClicked();
+ });
d->mActionMenu->addAction(d->mCloseTabAction);
d->mCloseTabAction->setEnabled(false);
d->mActivateNextTabAction = new QAction(i18n("Activate Next Tab"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_next_tab"), d->mActivateNextTabAction);
d->mActivateNextTabAction->setEnabled(false);
- connect(d->mActivateNextTabAction, &QAction::triggered, [this]() { d->activateNextTab(); });
+ connect(d->mActivateNextTabAction, &QAction::triggered, [this]() {
+ d->activateNextTab();
+ });
d->mActivatePreviousTabAction = new QAction(i18n("Activate Previous Tab"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("activate_previous_tab"), d->mActivatePreviousTabAction);
d->mActivatePreviousTabAction->setEnabled(false);
- connect(d->mActivatePreviousTabAction, &QAction::triggered, this, [this]() { d->activatePreviousTab(); });
+ connect(d->mActivatePreviousTabAction, &QAction::triggered, this, [this]() {
+ d->activatePreviousTab();
+ });
d->mMoveTabLeftAction = new QAction(i18n("Move Tab Left"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_left"), d->mMoveTabLeftAction);
d->mMoveTabLeftAction->setEnabled(false);
- connect(d->mMoveTabLeftAction, &QAction::triggered, this, [this]() { d->moveTabLeft(); });
+ connect(d->mMoveTabLeftAction, &QAction::triggered, this, [this]() {
+ d->moveTabLeft();
+ });
d->mMoveTabRightAction = new QAction(i18n("Move Tab Right"), this);
d->mXmlGuiClient->actionCollection()->addAction(QStringLiteral("move_tab_right"), d->mMoveTabRightAction);
d->mMoveTabRightAction->setEnabled(false);
- connect(d->mMoveTabRightAction, &QAction::triggered, this, [this]() { d->moveTabRight(); });
+ connect(d->mMoveTabRightAction, &QAction::triggered, this, [this]() {
+ d->moveTabRight();
+ });
}
}
bool Pane::selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->selectNextMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
} else {
return false;
}
}
bool Pane::selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->selectPreviousMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
} else {
return false;
}
}
bool Pane::focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->focusNextMessageItem(messageTypeFilter, centerItem, loop);
} else {
return false;
}
}
bool Pane::focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->focusPreviousMessageItem(messageTypeFilter, centerItem, loop);
} else {
return false;
}
}
void Pane::selectFocusedMessageItem(bool centerItem)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return;
}
w->selectFocusedMessageItem(centerItem);
}
}
bool Pane::selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->selectFirstMessageItem(messageTypeFilter, centerItem);
} else {
return false;
}
}
bool Pane::selectLastMessageItem(Core::MessageTypeFilter messageTypeFilter, bool centerItem)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return true;
}
return w->selectLastMessageItem(messageTypeFilter, centerItem);
} else {
return false;
}
}
void Pane::selectAll()
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return;
}
w->selectAll();
}
}
void Pane::setCurrentThreadExpanded(bool expand)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return;
}
w->setCurrentThreadExpanded(expand);
}
}
void Pane::setAllThreadsExpanded(bool expand)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return;
}
w->setAllThreadsExpanded(expand);
}
}
void Pane::setAllGroupsExpanded(bool expand)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
if (w->view()->model()->isLoading()) {
return;
}
w->setAllGroupsExpanded(expand);
}
}
void Pane::focusQuickSearch(const QString &selectedText)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
w->focusQuickSearch(selectedText);
}
}
void Pane::setQuickSearchClickMessage(const QString &msg)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
w->setQuickSearchClickMessage(msg);
}
}
void Pane::Private::setCurrentFolder(const QModelIndex &etmIndex)
{
if (mPreferEmptyTab) {
q->createNewTab();
}
Widget *w = static_cast<Widget *>(q->currentWidget());
QItemSelectionModel *s = mWidgetSelectionHash[w];
w->saveCurrentSelection();
// Deselect old before we select new - so that the messagelist can clear first.
s->clear();
if (s->selection().isEmpty()) {
w->view()->model()->setPreSelectionMode(mPreSelectionMode);
}
Q_ASSERT(s->model() == etmIndex.model());
s->select(etmIndex, QItemSelectionModel::Select);
QString label;
QIcon icon;
QString toolTip;
for (const QModelIndex &index : s->selectedRows()) {
label += index.data(Qt::DisplayRole).toString() + QLatin1String(", ");
}
label.chop(2);
if (label.isEmpty()) {
label = i18nc("@title:tab Empty messagelist", "Empty");
icon = QIcon();
} else if (s->selectedRows().size() == 1) {
icon = s->selectedRows().first().data(Qt::DecorationRole).value<QIcon>();
QModelIndex idx = s->selectedRows().first().parent();
toolTip = label;
while (idx != QModelIndex()) {
toolTip = idx.data().toString() + QLatin1Char('/') + toolTip;
idx = idx.parent();
}
} else {
icon = QIcon::fromTheme(QStringLiteral("folder"));
}
const int index = q->indexOf(w);
q->setTabText(index, label);
q->setTabIcon(index, icon);
q->setTabToolTip(index, toolTip);
if (mPreferEmptyTab) {
mSelectionModel->select(mapSelectionFromSource(s->selection()),
QItemSelectionModel::ClearAndSelect);
}
mPreferEmptyTab = false;
}
void Pane::Private::activateTab()
{
q->tabBar()->setCurrentIndex(q->sender()->objectName().rightRef(2).toInt() - 1);
}
void Pane::Private::moveTabRight()
{
const int numberOfTab = q->tabBar()->count();
if (numberOfTab == 1) {
return;
}
if (QApplication::isRightToLeft()) {
moveTabForward();
} else {
moveTabBackward();
}
}
void Pane::Private::moveTabLeft()
{
const int numberOfTab = q->tabBar()->count();
if (numberOfTab == 1) {
return;
}
if (QApplication::isRightToLeft()) {
moveTabBackward();
} else {
moveTabForward();
}
}
void Pane::Private::moveTabForward()
{
const int currentIndex = q->tabBar()->currentIndex();
if (currentIndex == q->tabBar()->count() - 1) {
return;
}
q->tabBar()->moveTab(currentIndex, currentIndex + 1);
}
void Pane::Private::moveTabBackward()
{
const int currentIndex = q->tabBar()->currentIndex();
if (currentIndex == 0) {
return;
}
q->tabBar()->moveTab(currentIndex, currentIndex - 1);
}
void Pane::Private::activateNextTab()
{
const int numberOfTab = q->tabBar()->count();
if (numberOfTab == 1) {
return;
}
int indexTab = (q->tabBar()->currentIndex() + 1);
if (indexTab == numberOfTab) {
indexTab = 0;
}
q->tabBar()->setCurrentIndex(indexTab);
}
void Pane::Private::activatePreviousTab()
{
const int numberOfTab = q->tabBar()->count();
if (numberOfTab == 1) {
return;
}
int indexTab = (q->tabBar()->currentIndex() - 1);
if (indexTab == -1) {
indexTab = numberOfTab - 1;
}
q->tabBar()->setCurrentIndex(indexTab);
}
void Pane::Private::onNewTabClicked()
{
q->createNewTab();
}
void Pane::Private::onCloseTabClicked()
{
closeTab(q->currentWidget());
}
void Pane::Private::closeTab(QWidget *w)
{
if (!w || (q->count() < 2)) {
return;
}
Widget *wWidget = qobject_cast<Widget *>(w);
if (wWidget) {
wWidget->saveCurrentSelection();
}
delete w;
updateTabControls();
}
void Pane::Private::changeQuicksearchVisibility(bool show)
{
for (int i = 0; i < q->count(); ++i) {
Widget *w = qobject_cast<Widget *>(q->widget(i));
if (w) {
w->changeQuicksearchVisibility(show);
}
}
}
bool Pane::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::MidButton) {
return true;
}
}
return QTabWidget::eventFilter(object, event);
}
void Pane::Private::onCurrentTabChanged()
{
Q_EMIT q->currentTabChanged();
Widget *w = static_cast<Widget *>(q->currentWidget());
QItemSelectionModel *s = mWidgetSelectionHash[w];
mSelectionModel->select(mapSelectionFromSource(s->selection()),
QItemSelectionModel::ClearAndSelect);
}
void Pane::Private::onTabContextMenuRequest(const QPoint &pos)
{
QTabBar *bar = q->tabBar();
if (q->count() <= 1) {
return;
}
const int indexBar = bar->tabAt(bar->mapFrom(q, pos));
if (indexBar == -1) {
return;
}
Widget *w = qobject_cast<Widget *>(q->widget(indexBar));
if (!w) {
return;
}
QMenu menu(q);
QAction *closeTabAction = menu.addAction(i18nc("@action:inmenu", "Close Tab"));
closeTabAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
QAction *allOther = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs"));
allOther->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other")));
QAction *action = menu.exec(q->mapToGlobal(pos));
if (action == allOther) { // Close all other tabs
QList<Widget *> widgets;
const int index = q->indexOf(w);
for (int i = 0; i < q->count(); ++i) {
if (i == index) {
continue; // Skip the current one
}
Widget *other = qobject_cast<Widget *>(q->widget(i));
if (other) {
widgets << other;
}
}
foreach (Widget *other, widgets) {
other->saveCurrentSelection();
delete other;
}
updateTabControls();
} else if (action == closeTabAction) {
closeTab(q->widget(indexBar));
}
}
MessageList::StorageModel *Pane::createStorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent)
{
return new MessageList::StorageModel(model, selectionModel, parent);
}
Akonadi::Collection Pane::currentFolder() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->currentCollection();
}
return {};
}
void Pane::setCurrentFolder(const Akonadi::Collection &collection, const QModelIndex &etmIndex, bool, Core::PreSelectionMode preSelectionMode, const QString &overrideLabel)
{
d->setCurrentFolder(etmIndex);
d->mPreSelectionMode = preSelectionMode;
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
w->setCurrentFolder(collection);
QItemSelectionModel *s = d->mWidgetSelectionHash[w];
MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
w->setStorageModel(m, preSelectionMode);
if (!overrideLabel.isEmpty()) {
int index = indexOf(w);
setTabText(index, overrideLabel);
}
}
}
void Pane::updateTabIconText(const Akonadi::Collection &collection, const QString &label, const QIcon &icon)
{
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w && (w->currentCollection() == collection)) {
const int index = indexOf(w);
setTabText(index, label);
setTabIcon(index, icon);
}
}
}
QItemSelectionModel *Pane::createNewTab()
{
Widget *w = new Widget(this);
w->setXmlGuiClient(d->mXmlGuiClient);
addTab(w, i18nc("@title:tab Empty messagelist", "Empty"));
if (d->mXmlGuiClient && count() < 10) {
if (d->mMaxTabCreated < count()) {
d->mMaxTabCreated = count();
d->addActivateTabAction(d->mMaxTabCreated);
}
}
QItemSelectionModel *s = new QItemSelectionModel(d->mModel, w);
MessageList::StorageModel *m = createStorageModel(d->mModel, s, w);
w->setStorageModel(m);
d->mWidgetSelectionHash[w] = s;
connect(w, &Widget::messageSelected,
this, &Pane::messageSelected);
connect(w, &Widget::messageActivated,
this, &Pane::messageActivated);
connect(w, &Widget::selectionChanged,
this, &Pane::selectionChanged);
connect(w, &Widget::messageStatusChangeRequest,
this, &Pane::messageStatusChangeRequest);
connect(w, &Core::Widget::statusMessage,
this, &Pane::statusMessage);
connect(w, &Core::Widget::forceLostFocus,
this, &Pane::forceLostFocus);
d->updateTabControls();
setCurrentWidget(w);
return s;
}
QItemSelection Pane::Private::mapSelectionFromSource(const QItemSelection &selection) const
{
QItemSelection result = selection;
typedef QVector<const QAbstractProxyModel *>::ConstIterator Iterator;
for (Iterator it = mProxyStack.end() - 1; it != mProxyStack.begin(); --it) {
result = (*it)->mapSelectionFromSource(result);
}
result = mProxyStack.first()->mapSelectionFromSource(result);
return result;
}
void Pane::Private::updateTabControls()
{
const bool enableAction = (q->count() > 1);
if (enableAction) {
q->setCornerWidget(mCloseTabButton, Qt::TopRightCorner);
mCloseTabButton->setVisible(true);
} else {
q->setCornerWidget(nullptr, Qt::TopRightCorner);
}
if (mCloseTabAction) {
mCloseTabAction->setEnabled(enableAction);
}
if (mActivatePreviousTabAction) {
mActivatePreviousTabAction->setEnabled(enableAction);
}
if (mActivateNextTabAction) {
mActivateNextTabAction->setEnabled(enableAction);
}
if (mMoveTabRightAction) {
mMoveTabRightAction->setEnabled(enableAction);
}
if (mMoveTabLeftAction) {
mMoveTabLeftAction->setEnabled(enableAction);
}
q->tabBar()->setVisible(enableAction);
if (enableAction) {
q->setCornerWidget(mNewTabButton, Qt::TopLeftCorner);
mNewTabButton->setVisible(true);
} else {
q->setCornerWidget(nullptr, Qt::TopLeftCorner);
}
q->setTabsClosable(true);
const int numberOfTab(q->count());
if (numberOfTab == 1) {
q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(false);
} else if (numberOfTab > 1) {
q->tabBar()->tabButton(0, QTabBar::RightSide)->setEnabled(true);
}
}
Item Pane::currentItem() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return Item();
}
return w->currentItem();
}
KMime::Message::Ptr Pane::currentMessage() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return KMime::Message::Ptr();
}
return w->currentMessage();
}
QList<KMime::Message::Ptr > Pane::selectionAsMessageList(bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return QList<KMime::Message::Ptr>();
}
return w->selectionAsMessageList(includeCollapsedChildren);
}
Akonadi::Item::List Pane::selectionAsMessageItemList(bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return Akonadi::Item::List();
}
return w->selectionAsMessageItemList(includeCollapsedChildren);
}
QList<Akonadi::Item::Id> Pane::selectionAsListMessageId(bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return QList<Akonadi::Item::Id>();
}
return w->selectionAsListMessageId(includeCollapsedChildren);
}
QVector<qlonglong> Pane::selectionAsMessageItemListId(bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return QVector<qlonglong>();
}
return w->selectionAsMessageItemListId(includeCollapsedChildren);
}
Akonadi::Item::List Pane::currentThreadAsMessageList() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return Akonadi::Item::List();
}
return w->currentThreadAsMessageList();
}
Akonadi::Item::List Pane::itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->itemListFromPersistentSet(ref);
}
return Akonadi::Item::List();
}
void Pane::deletePersistentSet(MessageList::Core::MessageItemSetReference ref)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
w->deletePersistentSet(ref);
}
}
void Pane::markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark)
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
w->markMessageItemsAsAboutToBeRemoved(ref, bMark);
}
}
QList<Akonadi::MessageStatus> Pane::currentFilterStatus() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return QList<Akonadi::MessageStatus>();
}
return w->currentFilterStatus();
}
Core::QuickSearchLine::SearchOptions Pane::currentOptions() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return Core::QuickSearchLine::SearchEveryWhere;
}
return w->currentOptions();
}
QString Pane::currentFilterSearchString() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->currentFilterSearchString();
}
return QString();
}
bool Pane::isThreaded() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->isThreaded();
}
return false;
}
bool Pane::selectionEmpty() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->selectionEmpty();
}
return false;
}
bool Pane::getSelectionStats(Akonadi::Item::List &selectedItems, Akonadi::Item::List &selectedVisibleItems, bool *allSelectedBelongToSameThread, bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w == nullptr) {
return false;
}
return w->getSelectionStats(
selectedItems, selectedVisibleItems,
allSelectedBelongToSameThread, includeCollapsedChildren
);
}
MessageList::Core::MessageItemSetReference Pane::selectionAsPersistentSet(bool includeCollapsedChildren) const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->selectionAsPersistentSet(includeCollapsedChildren);
}
return -1;
}
MessageList::Core::MessageItemSetReference Pane::currentThreadAsPersistentSet() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->currentThreadAsPersistentSet();
}
return -1;
}
void Pane::focusView()
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
QWidget *view = w->view();
if (view) {
view->setFocus();
}
}
}
void Pane::reloadGlobalConfiguration()
{
d->updateTabControls();
}
QItemSelectionModel *Pane::currentItemSelectionModel()
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->view()->selectionModel();
}
return nullptr;
}
void Pane::resetModelStorage()
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
MessageList::StorageModel *m = static_cast<MessageList::StorageModel *>(w->storageModel());
if (m) {
m->resetModelStorage();
}
}
}
void Pane::setPreferEmptyTab(bool emptyTab)
{
d->mPreferEmptyTab = emptyTab;
}
void Pane::saveCurrentSelection()
{
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w) {
w->saveCurrentSelection();
}
}
}
void Pane::updateTagComboBox()
{
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w) {
w->populateStatusFilterCombo();
}
}
}
void Pane::writeConfig(bool restoreSession)
{
KConfigGroup conf(MessageList::MessageListSettings::self()->config(), "MessageListPane");
// Delete list before
const QStringList list = MessageList::MessageListSettings::self()->config()->groupList().filter(QRegularExpression(QStringLiteral("MessageListTab\\d+")));
for (const QString &group : list) {
MessageList::MessageListSettings::self()->config()->deleteGroup(group);
}
if (restoreSession) {
conf.writeEntry(QStringLiteral("currentIndex"), currentIndex());
conf.writeEntry(QStringLiteral("tabNumber"), count());
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w) {
KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(i));
grp.writeEntry(QStringLiteral("collectionId"), w->currentCollection().id());
grp.writeEntry(QStringLiteral("HeaderState"), w->view()->header()->saveState());
}
}
}
conf.sync();
}
void Pane::readConfig(bool restoreSession)
{
if (MessageList::MessageListSettings::self()->config()->hasGroup(QStringLiteral("MessageListPane"))) {
KConfigGroup conf(MessageList::MessageListSettings::self()->config(), "MessageListPane");
const int numberOfTab = conf.readEntry(QStringLiteral("tabNumber"), 0);
if (numberOfTab == 0) {
createNewTab();
} else {
for (int i = 0; i < numberOfTab; ++i) {
createNewTab();
restoreHeaderSettings(i);
if (restoreSession) {
#if 0 //TODO fix me
Akonadi::Collection::Id id = grp.readEntry(QStringLiteral("collectionId"), -1);
ETMViewStateSaver *saver = new ETMViewStateSaver;
saver->setSelectionModel(selectionModel);
if (id != -1) {
ETMViewStateSaver *saver = new ETMViewStateSaver;
saver->setSelectionModel(selectionModel);
saver->restoreState(grp);
saver->selectCollections(Akonadi::Collection::List() << Akonadi::Collection(id));
saver->restoreCurrentItem(QString::fromLatin1("c%1").arg(id));
}
#endif
}
}
setCurrentIndex(conf.readEntry(QStringLiteral("currentIndex"), 0));
}
} else {
createNewTab();
restoreHeaderSettings(0);
}
}
void Pane::restoreHeaderSettings(int index)
{
KConfigGroup grp(MessageList::MessageListSettings::self()->config(), QStringLiteral("MessageListTab%1").arg(index));
if (grp.exists()) {
Widget *w = qobject_cast<Widget *>(widget(index));
if (w) {
w->view()->header()->restoreState(grp.readEntry(QStringLiteral("HeaderState"), QByteArray()));
}
}
}
bool Pane::searchEditHasFocus() const
{
Widget *w = static_cast<Widget *>(currentWidget());
if (w) {
return w->searchEditHasFocus();
}
return false;
}
void Pane::sortOrderMenuAboutToShow()
{
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
const Widget *const w = static_cast<Widget *>(currentWidget());
w->view()->sortOrderMenuAboutToShow(menu);
}
void Pane::aggregationMenuAboutToShow()
{
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
const Widget *const w = static_cast<Widget *>(currentWidget());
w->view()->aggregationMenuAboutToShow(menu);
}
void Pane::themeMenuAboutToShow()
{
QMenu *menu = qobject_cast< QMenu * >(sender());
if (!menu) {
return;
}
const Widget *const w = static_cast<Widget *>(currentWidget());
w->view()->themeMenuAboutToShow(menu);
}
void Pane::populateStatusFilterCombo()
{
for (int i = 0; i < count(); ++i) {
Widget *w = qobject_cast<Widget *>(widget(i));
if (w) {
w->populateStatusFilterCombo();
}
}
}
#include "moc_pane.cpp"
diff --git a/messagelist/src/pane.h b/messagelist/src/pane.h
index 07faf269..3fd730fa 100644
--- a/messagelist/src/pane.h
+++ b/messagelist/src/pane.h
@@ -1,448 +1,445 @@
/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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.
*/
#ifndef MESSAGELIST_PANE_H
#define MESSAGELIST_PANE_H
#include <messagelist/enums.h>
#include <messagelist/view.h>
#include <QHash>
#include <QTabWidget>
#include <kmime/kmime_message.h>
#include <collection.h>
#include <messagelist_export.h>
#include <AkonadiCore/Item>
class KXMLGUIClient;
class QAbstractItemModel;
class QItemSelectionModel;
class QItemSelection;
namespace KPIM {
class MessageStatus;
}
namespace MessageList {
class Widget;
class StorageModel;
/**
* This is the main MessageList panel for Akonadi applications.
* It contains multiple MessageList::Widget tabs
* so it can actually display multiple folder sets at once.
*
* When a KXmlGuiWindow is passed to setXmlGuiClient, the XMLGUI
* defined context menu @c akonadi_messagelist_contextmenu is
* used if available.
*
*/
class MESSAGELIST_EXPORT Pane : public QTabWidget
{
Q_OBJECT
public:
/**
* Create a Pane wrapping the specified model and selection.
*/
explicit Pane(bool restoreSession, QAbstractItemModel *model, QItemSelectionModel *selectionModel, QWidget *parent = nullptr);
~Pane() override;
virtual MessageList::StorageModel *createStorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent);
virtual void writeConfig(bool restoreSession);
/**
* Sets the XML GUI client which the pane is used in.
*
* This is needed if you want to use the built-in context menu.
* Passing 0 is ok and will disable the builtin context menu.
*
* @param xmlGuiClient The KXMLGUIClient the view is used in.
*/
void setXmlGuiClient(KXMLGUIClient *xmlGuiClient);
/**
* Returns the current message for the list as Akonadi::Item.
* May return an invalid Item if there is no current message or no current folder.
*/
Akonadi::Item currentItem() const;
/**
* Returns the current message for the list as KMime::Message::Ptr.
* May return 0 if there is no current message or no current folder.
*/
KMime::Message::Ptr currentMessage() const;
/**
* Returns the currently selected KMime::Message::Ptr (bound to current StorageModel).
* The list may be empty if there are no selected messages or no StorageModel.
*
* If includeCollapsedChildren is true then the children of the selected but
* collapsed items are also added to the list.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
QList<KMime::Message::Ptr > selectionAsMessageList(bool includeCollapsedChildren = true) const;
/**
* Returns the currently selected Items (bound to current StorageModel).
* The list may be empty if there are no selected messages or no StorageModel.
*
* If includeCollapsedChildren is true then the children of the selected but
* collapsed items are also added to the list.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
Akonadi::Item::List selectionAsMessageItemList(bool includeCollapsedChildren = true) const;
/**
* Returns the currently selected Items id(bound to current StorageModel).
* The list may be empty if there are no selected messages or no StorageModel.
*
* If includeCollapsedChildren is true then the children of the selected but
* collapsed items are also added to the list.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
QVector<qlonglong> selectionAsMessageItemListId(bool includeCollapsedChildren = true) const;
QList<Akonadi::Item::Id> selectionAsListMessageId(bool includeCollapsedChildren = true) const;
/**
* Returns the Akonadi::Item bound to the current StorageModel that
* are part of the current thread. The current thread is the thread
* that contains currentMessageItem().
* The list may be empty if there is no currentMessageItem() or no StorageModel.
*
* The returned list is guaranteed to be valid only until you return control
* to the main even loop. Don't store it for any longer. If you need to reference
* this set of messages at a later stage then take a look at createPersistentSet().
*/
Akonadi::Item::List currentThreadAsMessageList() const;
/**
* Selects the next message item in the view.
*
* messageTypeFilter can be used to restrict the selection to only certain message types.
*
* existingSelectionBehaviour specifies how the existing selection
* is manipulated. It may be cleared, expanded or grown/shrinked.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "next" algorithm will restart from the beginning
* of the list if the end is reached, otherwise it will just stop returning false.
*/
bool selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
/**
* Selects the previous message item in the view.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*
* messageTypeFilter can be used to restrict the selection to only certain message types.
*
* existingSelectionBehaviour specifies how the existing selection
* is manipulated. It may be cleared, expanded or grown/shrinked.
*
* If loop is true then the "previous" algorithm will restart from the end
* of the list if the beginning is reached, otherwise it will just stop returning false.
*/
bool selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop);
/**
* Focuses the next message item in the view without actually selecting it.
*
* messageTypeFilter can be used to restrict the selection to only certain message types.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "next" algorithm will restart from the beginning
* of the list if the end is reached, otherwise it will just stop returning false.
*/
bool focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
/**
* Focuses the previous message item in the view without actually selecting it.
*
* messageTypeFilter can be used to restrict the selection to only certain message types.
*
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
* If loop is true then the "previous" algorithm will restart from the end
* of the list if the beginning is reached, otherwise it will just stop returning false.
*/
bool focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop);
/**
* Selects the currently focused message item. May do nothing if the
* focused message item is already selected (which is very likely).
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*/
void selectFocusedMessageItem(bool centerItem);
/**
* Selects the first message item in the view that matches the specified Core::MessageTypeFilter.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*
* If the current view is already loaded then the request will
* be satisfied immediately (well... if an unread message exists at all).
* If the current view is still loading then the selection of the first
* message will be scheduled to be executed when loading terminates.
*
* So this function doesn't actually guarantee that an unread or new message
* was selected when the call returns. Take care :)
*
* The function returns true if a message was selected and false otherwise.
*/
bool selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem);
/**
* Selects the last message item in the view that matches the specified Core::MessageTypeFilter.
* If centerItem is true then the specified item will be positioned
* at the center of the view, if possible.
*
* The function returns true if a message was selected and false otherwise.
*/
bool selectLastMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem);
/**
* If expand is true then it expands the current thread, otherwise
* collapses it.
*/
void setCurrentThreadExpanded(bool expand);
/**
* If expand is true then it expands all the threads, otherwise
* collapses them.
*/
void setAllThreadsExpanded(bool expand);
/**
* If expand is true then it expands all the groups (only the toplevel
* group item: inner threads are NOT expanded). If expand is false
* then it collapses all the groups. If no grouping is in effect
* then this function does nothing.
*/
void setAllGroupsExpanded(bool expand);
/**
* Sets the focus on the quick search line of the currently active tab.
*/
void focusQuickSearch(const QString &selectedText = QString());
/**
* Returns the Akonadi::MessageStatus in the current quicksearch field.
*/
QList<Akonadi::MessageStatus> currentFilterStatus() const;
/**
* Returns the search term in the current quicksearch field.
*/
QString currentFilterSearchString() const;
/**
* Returns true if the current Aggregation is threaded, false otherwise
* (or if there is no current Aggregation).
*/
bool isThreaded() const;
/**
* Fast function that determines if the selection is empty
*/
bool selectionEmpty() const;
/**
* Fills the lists of the selected message serial numbers and of the selected+visible ones.
* Returns true if the returned stats are valid (there is a current folder after all)
* and false otherwise. This is called by KMMainWidget in a single place so we optimize by
* making it a single sweep on the selection.
*
* If includeCollapsedChildren is true then the children of the selected but
* collapsed items are also included in the stats
*/
bool getSelectionStats(Akonadi::Item::List &selectedItems, Akonadi::Item::List &selectedVisibleItems, bool *allSelectedBelongToSameThread, bool includeCollapsedChildren = true) const;
/**
* Deletes the persistent set pointed by the specified reference.
* If the set does not exist anymore, nothing happens.
*/
void deletePersistentSet(MessageList::Core::MessageItemSetReference ref);
/**
* If bMark is true this function marks the messages as "about to be removed"
* so they appear dimmer and aren't selectable in the view.
* If bMark is false then this function clears the "about to be removed" state
* for the specified MessageItems.
*/
void markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark);
/**
* Return Akonadi::Item from messageItemReference
*/
Akonadi::Item::List itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref);
/**
* Return a persistent set from current selection
*/
MessageList::Core::MessageItemSetReference selectionAsPersistentSet(bool includeCollapsedChildren = true) const;
/**
* Return a persistent set from current thread
*/
MessageList::Core::MessageItemSetReference currentThreadAsPersistentSet() const;
/**
* Sets the focus on the view of the currently active tab.
*/
void focusView();
/**
* Reloads global configuration and eventually reloads all the views.
*/
void reloadGlobalConfiguration();
/**
* Returns the QItemSelectionModel for the currently displayed tab.
*/
QItemSelectionModel *currentItemSelectionModel();
/**
* Sets the current folder to be displayed by this Pane.
* If the specified folder is already open in one of the tabs
* then that tab is made current (and no reloading happens).
* If the specified folder is not open yet then behaviour
* depends on the preferEmptyTab value as follows.
*
* @param etmIndex the index for the collection in the EntityTreeModel (source model)
*
* If preferEmptyTab is set to false then the (new) folder is loaded
* in the current tab. If preferEmptyTab is set to true then the (new) folder is
* loaded in the first empty tab (or a new one if there are no empty ones).
*
* Pre-selection is the action of automatically selecting a message just after the folder
* has finished loading. See Model::setStorageModel() for more information.
*
* If overrideLabel is not empty then it's used as the tab text for the
* specified folder. This is useful to signal a particular folder state
* like "loading..."
*/
void setCurrentFolder(
- const Akonadi::Collection &fld,
- const QModelIndex &etmIndex,
- bool preferEmptyTab = false, MessageList::Core::PreSelectionMode preSelectionMode = MessageList::Core::PreSelectLastSelected,
- const QString &overrideLabel = QString());
+ const Akonadi::Collection &fld, const QModelIndex &etmIndex, bool preferEmptyTab = false, MessageList::Core::PreSelectionMode preSelectionMode = MessageList::Core::PreSelectLastSelected, const QString &overrideLabel = QString());
void resetModelStorage();
void setPreferEmptyTab(bool emptyTab);
void updateTabIconText(const Akonadi::Collection &collection, const QString &label, const QIcon &icon);
void saveCurrentSelection();
void updateTagComboBox();
bool searchEditHasFocus() const;
void setQuickSearchClickMessage(const QString &msg);
void populateStatusFilterCombo();
Core::QuickSearchLine::SearchOptions currentOptions() const;
Akonadi::Collection currentFolder() const;
public Q_SLOTS:
/**
* Selects all the items in the current folder.
*/
void selectAll();
/**
* Add a new tab to the Pane and select it.
*/
QItemSelectionModel *createNewTab();
void sortOrderMenuAboutToShow();
void aggregationMenuAboutToShow();
void themeMenuAboutToShow();
Q_SIGNALS:
/**
* Emitted when a message is selected (that is, single clicked and thus made current in the view)
* Note that this message CAN be 0 (when the current item is cleared, for example).
*
* This signal is emitted when a SINGLE message is selected in the view, probably
* by clicking on it or by simple keyboard navigation. When multiple items
* are selected at once (by shift+clicking, for example) then you will get
* this signal only for the last clicked message (or at all, if the last shift+clicked
* thing is a group header...). You should handle selection changed in this case.
*/
void messageSelected(const Akonadi::Item &item);
/**
* Emitted when a message is doubleclicked or activated by other input means
*/
void messageActivated(const Akonadi::Item &item);
/**
* Emitted when the selection in the view changes.
*/
void selectionChanged();
/**
* Emitted when a message wants its status to be changed
*/
void messageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear);
/**
* Notify the outside when updating the status bar with a message
* could be useful
*/
void statusMessage(const QString &message);
/**
* Emitted when the current tab has changed. Clients using the
* selection model from currentItemSelectionModel() should
* ask for it again, as it may be different now.
*/
void currentTabChanged();
void forceLostFocus();
private:
void restoreHeaderSettings(int index);
void readConfig(bool restoreSession);
bool eventFilter(QObject *obj, QEvent *event) override;
class Private;
Private *const d;
};
} // namespace MessageList
#endif //!__MESSAGELIST_PANE_H
diff --git a/messagelist/src/storagemodel.cpp b/messagelist/src/storagemodel.cpp
index 32e72e07..baa4c0cf 100644
--- a/messagelist/src/storagemodel.cpp
+++ b/messagelist/src/storagemodel.cpp
@@ -1,491 +1,497 @@
/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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.
*/
#include "storagemodel.h"
#include <MessageCore/StringUtil>
#include <MessageCore/MessageCoreSettings>
#include <MessageCore/NodeHelper>
#include <attributefactory.h>
#include <collection.h>
#include <collectionstatistics.h>
#include <entitymimetypefiltermodel.h>
#include <entitytreemodel.h>
#include <item.h>
#include <itemmodifyjob.h>
#include <Akonadi/KMime/MessageFolderAttribute>
#include <selectionproxymodel.h>
#include "messagelist_debug.h"
#include <KLocalizedString>
#include "core/messageitem.h"
#include "messagelistsettings.h"
#include "messagelistutil.h"
#include <QUrl>
#include <QAbstractItemModel>
#include <QAtomicInt>
#include <QItemSelectionModel>
#include <QMimeData>
#include <QCryptographicHash>
#include <QFontDatabase>
namespace MessageList {
class Q_DECL_HIDDEN StorageModel::Private
{
public:
void onSourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void onSelectionChanged();
void loadSettings();
StorageModel *const q;
QAbstractItemModel *mModel = nullptr;
QAbstractItemModel *mChildrenFilterModel = nullptr;
QItemSelectionModel *mSelectionModel = nullptr;
Private(StorageModel *owner)
: q(owner)
{
}
};
} // namespace MessageList
using namespace Akonadi;
using namespace MessageList;
namespace {
KMime::Message::Ptr messageForItem(const Akonadi::Item &item)
{
if (!item.hasPayload<KMime::Message::Ptr>()) {
qCWarning(MESSAGELIST_LOG) << "Not a message" << item.id() << item.remoteId() << item.mimeType();
return KMime::Message::Ptr();
}
return item.payload<KMime::Message::Ptr>();
}
}
static QAtomicInt _k_attributeInitialized;
StorageModel::StorageModel(QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent)
: Core::StorageModel(parent)
, d(new Private(this))
{
d->mSelectionModel = selectionModel;
if (_k_attributeInitialized.testAndSetAcquire(0, 1)) {
AttributeFactory::registerAttribute<MessageFolderAttribute>();
}
Akonadi::SelectionProxyModel *childrenFilter = new Akonadi::SelectionProxyModel(d->mSelectionModel, this);
childrenFilter->setSourceModel(model);
childrenFilter->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection);
d->mChildrenFilterModel = childrenFilter;
EntityMimeTypeFilterModel *itemFilter = new EntityMimeTypeFilterModel(this);
itemFilter->setSourceModel(childrenFilter);
itemFilter->addMimeTypeExclusionFilter(Collection::mimeType());
itemFilter->addMimeTypeInclusionFilter(QStringLiteral("message/rfc822"));
itemFilter->setHeaderGroup(EntityTreeModel::ItemListHeaders);
d->mModel = itemFilter;
qCDebug(MESSAGELIST_LOG) << "Using model:" << model->metaObject()->className();
connect(d->mModel, &QAbstractItemModel::dataChanged,
- this, [this](const QModelIndex &id1, const QModelIndex &id2) { d->onSourceDataChanged(id1, id2);});
+ this, [this](const QModelIndex &id1, const QModelIndex &id2) {
+ d->onSourceDataChanged(id1, id2);
+ });
connect(d->mModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &StorageModel::layoutAboutToBeChanged);
connect(d->mModel, &QAbstractItemModel::layoutChanged, this, &StorageModel::layoutChanged);
connect(d->mModel, &QAbstractItemModel::modelAboutToBeReset, this, &StorageModel::modelAboutToBeReset);
connect(d->mModel, &QAbstractItemModel::modelReset, this, &StorageModel::modelReset);
//Here we assume we'll always get QModelIndex() in the parameters
connect(d->mModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &StorageModel::rowsAboutToBeInserted);
connect(d->mModel, &QAbstractItemModel::rowsInserted, this, &StorageModel::rowsInserted);
connect(d->mModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &StorageModel::rowsAboutToBeRemoved);
connect(d->mModel, &QAbstractItemModel::rowsRemoved, this, &StorageModel::rowsRemoved);
connect(d->mSelectionModel, &QItemSelectionModel::selectionChanged,
- this, [this]() { d->onSelectionChanged(); });
+ this, [this]() {
+ d->onSelectionChanged();
+ });
d->loadSettings();
connect(MessageListSettings::self(), &MessageListSettings::configChanged,
- this, [this]() {d->loadSettings();});
+ this, [this]() {
+ d->loadSettings();
+ });
}
StorageModel::~StorageModel()
{
delete d;
}
Collection::List StorageModel::displayedCollections() const
{
Collection::List collections;
const QModelIndexList indexes = d->mSelectionModel->selectedRows();
for (const QModelIndex &index : indexes) {
Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
if (c.isValid()) {
collections << c;
}
}
return collections;
}
QString StorageModel::id() const
{
QStringList ids;
const QModelIndexList indexes = d->mSelectionModel->selectedRows();
for (const QModelIndex &index : indexes) {
Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
if (c.isValid()) {
ids << QString::number(c.id());
}
}
ids.sort();
return ids.join(QLatin1Char(':'));
}
bool StorageModel::isOutBoundFolder(const Akonadi::Collection &c) const
{
if (c.hasAttribute<MessageFolderAttribute>()
&& c.attribute<MessageFolderAttribute>()->isOutboundFolder()) {
return true;
}
return false;
}
bool StorageModel::containsOutboundMessages() const
{
const QModelIndexList indexes = d->mSelectionModel->selectedRows();
for (const QModelIndex &index : indexes) {
Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
if (c.isValid()) {
return isOutBoundFolder(c);
}
}
return false;
}
int StorageModel::initialUnreadRowCountGuess() const
{
const QModelIndexList indexes = d->mSelectionModel->selectedRows();
int unreadCount = 0;
for (const QModelIndex &index : indexes) {
Collection c = index.data(EntityTreeModel::CollectionRole).value<Collection>();
if (c.isValid()) {
unreadCount += c.statistics().unreadCount();
}
}
return unreadCount;
}
bool StorageModel::initializeMessageItem(MessageList::Core::MessageItem *mi, int row, bool bUseReceiver) const
{
const Item item = itemForRow(row);
const KMime::Message::Ptr mail = messageForItem(item);
if (!mail) {
return false;
}
const Collection parentCol = parentCollectionForRow(row);
QString sender;
if (mail->from()) {
sender = mail->from()->asUnicodeString();
}
QString receiver;
if (mail->to()) {
receiver = mail->to()->asUnicodeString();
}
// Static for speed reasons
static const QString noSubject = i18nc("displayed as subject when the subject of a mail is empty", "No Subject");
static const QString unknown(i18nc("displayed when a mail has unknown sender, receiver or date", "Unknown"));
if (sender.isEmpty()) {
sender = unknown;
}
if (receiver.isEmpty()) {
receiver = unknown;
}
mi->initialSetup(mail->date()->dateTime().toTime_t(),
item.size(),
sender, receiver,
bUseReceiver);
mi->setItemId(item.id());
mi->setParentCollectionId(parentCol.id());
QString subject = mail->subject()->asUnicodeString();
if (subject.isEmpty()) {
subject = QLatin1Char('(') + noSubject + QLatin1Char(')');
}
mi->setSubject(subject);
updateMessageItemData(mi, row);
return true;
}
static QByteArray md5Encode(const QByteArray &str)
{
auto trimmed = str.trimmed();
if (trimmed.isEmpty()) {
return QByteArray();
}
QCryptographicHash c(QCryptographicHash::Md5);
c.addData(trimmed);
return c.result();
}
static QByteArray md5Encode(const QString &str)
{
auto trimmed = str.trimmed();
if (trimmed.isEmpty()) {
return QByteArray();
}
QCryptographicHash c(QCryptographicHash::Md5);
c.addData(reinterpret_cast<const char *>(trimmed.unicode()), sizeof(QChar) * trimmed.length());
return c.result();
}
void StorageModel::fillMessageItemThreadingData(MessageList::Core::MessageItem *mi, int row, ThreadingDataSubset subset) const
{
const KMime::Message::Ptr mail = messageForRow(row);
Q_ASSERT(mail); // We ASSUME that initializeMessageItem has been called successfully...
switch (subset) {
case PerfectThreadingReferencesAndSubject:
{
const QString subject = mail->subject()->asUnicodeString();
const QString strippedSubject = MessageCore::StringUtil::stripOffPrefixes(subject);
mi->setStrippedSubjectMD5(md5Encode(strippedSubject));
mi->setSubjectIsPrefixed(subject != strippedSubject);
// fall through
}
Q_FALLTHROUGH();
case PerfectThreadingPlusReferences:
{
const auto refs = mail->references()->identifiers();
if (!refs.isEmpty()) {
mi->setReferencesIdMD5(md5Encode(refs.last()));
}
}
Q_FALLTHROUGH();
// fall through
case PerfectThreadingOnly:
{
mi->setMessageIdMD5(md5Encode(mail->messageID()->identifier()));
const auto inReplyTos = mail->inReplyTo()->identifiers();
if (!inReplyTos.isEmpty()) {
mi->setInReplyToIdMD5(md5Encode(inReplyTos.first()));
}
break;
}
default:
Q_ASSERT(false); // should never happen
break;
}
}
void StorageModel::updateMessageItemData(MessageList::Core::MessageItem *mi, int row) const
{
const Item item = itemForRow(row);
Akonadi::MessageStatus stat;
stat.setStatusFromFlags(item.flags());
mi->setAkonadiItem(item);
mi->setStatus(stat);
if (stat.isEncrypted()) {
mi->setEncryptionState(Core::MessageItem::FullyEncrypted);
} else {
mi->setEncryptionState(Core::MessageItem::EncryptionStateUnknown);
}
if (stat.isSigned()) {
mi->setSignatureState(Core::MessageItem::FullySigned);
} else {
mi->setSignatureState(Core::MessageItem::SignatureStateUnknown);
}
mi->invalidateTagCache();
mi->invalidateAnnotationCache();
}
void StorageModel::setMessageItemStatus(MessageList::Core::MessageItem *mi, int row, Akonadi::MessageStatus status)
{
Q_UNUSED(mi);
Item item = itemForRow(row);
item.setFlags(status.statusFlags());
ItemModifyJob *job = new ItemModifyJob(item, this);
job->disableRevisionCheck();
job->setIgnorePayload(true);
}
QVariant StorageModel::data(const QModelIndex &index, int role) const
{
// We don't provide an implementation for data() in No-Akonadi-KMail.
// This is because StorageModel must be a wrapper anyway (because columns
// must be re-mapped and functions not available in a QAbstractItemModel
// are strictly needed. So when porting to Akonadi this class will
// either wrap or subclass the MessageModel and implement initializeMessageItem()
// with appropriate calls to data(). And for No-Akonadi-KMail we still have
// a somewhat efficient implementation.
Q_UNUSED(index);
Q_UNUSED(role);
return QVariant();
}
int StorageModel::columnCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return 1;
}
return 0; // this model is flat.
}
QModelIndex StorageModel::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid()) {
return createIndex(row, column, (void *)nullptr);
}
return QModelIndex(); // this model is flat.
}
QModelIndex StorageModel::parent(const QModelIndex &index) const
{
Q_UNUSED(index);
return QModelIndex(); // this model is flat.
}
int StorageModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
return d->mModel->rowCount();
}
return 0; // this model is flat.
}
QMimeData *StorageModel::mimeData(const QList< MessageList::Core::MessageItem * > &items) const
{
QMimeData *data = new QMimeData();
QList<QUrl> urls;
urls.reserve(items.count());
for (MessageList::Core::MessageItem *mi : items) {
Akonadi::Item item = itemForRow(mi->currentModelIndexRow());
urls << item.url(Item::UrlWithMimeType);
}
data->setUrls(urls);
return data;
}
void StorageModel::Private::onSourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
Q_EMIT q->dataChanged(q->index(topLeft.row(), 0),
q->index(bottomRight.row(), 0));
}
void StorageModel::Private::onSelectionChanged()
{
Q_EMIT q->headerDataChanged(Qt::Horizontal, 0, q->columnCount() - 1);
}
void StorageModel::Private::loadSettings()
{
// Custom/System colors
MessageListSettings *settings = MessageListSettings::self();
if (MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
Core::MessageItem::setUnreadMessageColor(MessageList::Util::unreadDefaultMessageColor());
Core::MessageItem::setImportantMessageColor(MessageList::Util::importantDefaultMessageColor());
Core::MessageItem::setToDoMessageColor(MessageList::Util::todoDefaultMessageColor());
} else {
Core::MessageItem::setUnreadMessageColor(settings->unreadMessageColor());
Core::MessageItem::setImportantMessageColor(settings->importantMessageColor());
Core::MessageItem::setToDoMessageColor(settings->todoMessageColor());
}
if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
Core::MessageItem::setGeneralFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
Core::MessageItem::setUnreadMessageFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
Core::MessageItem::setImportantMessageFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
Core::MessageItem::setToDoMessageFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
} else {
Core::MessageItem::setGeneralFont(settings->messageListFont());
Core::MessageItem::setUnreadMessageFont(settings->unreadMessageFont());
Core::MessageItem::setImportantMessageFont(settings->importantMessageFont());
Core::MessageItem::setToDoMessageFont(settings->todoMessageFont());
}
}
Item StorageModel::itemForRow(int row) const
{
return d->mModel->data(d->mModel->index(row, 0), EntityTreeModel::ItemRole).value<Item>();
}
KMime::Message::Ptr StorageModel::messageForRow(int row) const
{
return messageForItem(itemForRow(row));
}
Collection StorageModel::parentCollectionForRow(int row) const
{
QAbstractProxyModel *mimeProxy = static_cast<QAbstractProxyModel *>(d->mModel);
// This is index mapped to Akonadi::SelectionProxyModel
const QModelIndex childrenFilterIndex = mimeProxy->mapToSource(d->mModel->index(row, 0));
Q_ASSERT(childrenFilterIndex.isValid());
QAbstractProxyModel *childrenProxy = static_cast<QAbstractProxyModel *>(d->mChildrenFilterModel);
// This is index mapped to ETM
const QModelIndex etmIndex = childrenProxy->mapToSource(childrenFilterIndex);
Q_ASSERT(etmIndex.isValid());
// We cannot possibly refer to top-level collection
Q_ASSERT(etmIndex.parent().isValid());
const Collection col = etmIndex.parent().data(EntityTreeModel::CollectionRole).value<Collection>();
Q_ASSERT(col.isValid());
return col;
}
void StorageModel::resetModelStorage()
{
beginResetModel();
endResetModel();
}
#include "moc_storagemodel.cpp"
diff --git a/messagelist/src/utils/aggregationcombobox.cpp b/messagelist/src/utils/aggregationcombobox.cpp
index 49dc2ffa..5577229c 100644
--- a/messagelist/src/utils/aggregationcombobox.cpp
+++ b/messagelist/src/utils/aggregationcombobox.cpp
@@ -1,141 +1,141 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "aggregationcombobox.h"
#include "aggregationcombobox_p.h"
#include "core/aggregation.h"
#include "core/manager.h"
#include "messagelistsettings.h"
#include "storagemodel.h"
using namespace MessageList::Core;
using namespace MessageList::Utils;
AggregationComboBox::AggregationComboBox(QWidget *parent)
- : KComboBox(parent)
+ : QComboBox(parent)
, d(new AggregationComboBoxPrivate(this))
{
if (Manager::instance()) {
d->slotLoadAggregations();
} else {
setEnabled(false);
}
}
AggregationComboBox::~AggregationComboBox()
{
delete d;
}
QString AggregationComboBox::currentAggregation() const
{
return itemData(currentIndex()).toString();
}
void AggregationComboBox::writeDefaultConfig() const
{
KConfigGroup group(MessageListSettings::self()->config(), "MessageListView::StorageModelAggregations");
const QString aggregationID = currentAggregation();
group.writeEntry(QStringLiteral("DefaultSet"), aggregationID);
if (Manager::instance()) {
Manager::instance()->aggregationsConfigurationCompleted();
}
}
void AggregationComboBox::writeStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool isPrivateSetting) const
{
writeStorageModelConfig(storageModel->id(), isPrivateSetting);
}
void AggregationComboBox::writeStorageModelConfig(const QString &id, bool isPrivateSetting) const
{
if (Manager::instance()) {
// message list aggregation
QString aggregationID;
if (isPrivateSetting) {
aggregationID = currentAggregation();
} else { // explicitly use default aggregation id when using default aggregation.
aggregationID = Manager::instance()->defaultAggregation()->id();
}
Manager::instance()->saveAggregationForStorageModel(id, aggregationID, isPrivateSetting);
Manager::instance()->aggregationsConfigurationCompleted();
}
}
void AggregationComboBox::readStorageModelConfig(const QString &id, bool &isPrivateSetting)
{
if (Manager::instance()) {
const Aggregation *aggregation = Manager::instance()->aggregationForStorageModel(id, &isPrivateSetting);
d->setCurrentAggregation(aggregation);
}
}
void AggregationComboBox::readStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool &isPrivateSetting)
{
readStorageModelConfig(storageModel->id(), isPrivateSetting);
}
void AggregationComboBox::readStorageModelConfig(const Akonadi::Collection &col, bool &isPrivateSetting)
{
if (col.isValid()) {
readStorageModelConfig(QString::number(col.id()), isPrivateSetting);
}
}
void AggregationComboBox::selectDefault()
{
if (Manager::instance()) {
const Aggregation *defaultAggregation = Manager::instance()->defaultAggregation();
d->setCurrentAggregation(defaultAggregation);
}
}
void AggregationComboBox::slotLoadAggregations()
{
d->slotLoadAggregations();
}
void AggregationComboBoxPrivate::slotLoadAggregations()
{
if (!Manager::instance()) {
return;
}
q->clear();
// Get all message list aggregations and sort them into alphabetical order.
QList< Aggregation * > aggregations = Manager::instance()->aggregations().values();
std::sort(aggregations.begin(), aggregations.end(), MessageList::Core::Aggregation::compareName);
for (const Aggregation *aggregation : qAsConst(aggregations)) {
q->addItem(aggregation->name(), QVariant(aggregation->id()));
}
}
void AggregationComboBoxPrivate::setCurrentAggregation(const Aggregation *aggregation)
{
Q_ASSERT(aggregation != nullptr);
const QString aggregationID = aggregation->id();
const int aggregationIndex = q->findData(QVariant(aggregationID));
q->setCurrentIndex(aggregationIndex);
}
#include "moc_aggregationcombobox.cpp"
diff --git a/messagelist/src/utils/aggregationcombobox.h b/messagelist/src/utils/aggregationcombobox.h
index 36d1f1dd..0748122b 100644
--- a/messagelist/src/utils/aggregationcombobox.h
+++ b/messagelist/src/utils/aggregationcombobox.h
@@ -1,65 +1,65 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_H
#define MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_H
#include <messagelist_export.h>
-#include <KComboBox>
+#include <QComboBox>
#include <collection.h>
namespace MessageList {
namespace Core {
class Aggregation;
class StorageModel;
}
namespace Utils {
class AggregationComboBoxPrivate;
/**
- * A specialized KComboBox that lists all message list aggregations.
+ * A specialized QComboBox that lists all message list aggregations.
*/
-class MESSAGELIST_EXPORT AggregationComboBox : public KComboBox
+class MESSAGELIST_EXPORT AggregationComboBox : public QComboBox
{
Q_OBJECT
public:
explicit AggregationComboBox(QWidget *parent);
~AggregationComboBox();
QString currentAggregation() const;
void writeDefaultConfig() const;
void writeStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool isPrivateSetting) const;
void writeStorageModelConfig(const QString &id, bool isPrivateSetting) const;
void readStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool &isPrivateSetting);
void readStorageModelConfig(const Akonadi::Collection &col, bool &isPrivateSetting);
void readStorageModelConfig(const QString &id, bool &isPrivateSetting);
public Q_SLOTS:
void selectDefault();
void slotLoadAggregations();
private:
AggregationComboBoxPrivate *const d;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_H
diff --git a/messagelist/src/utils/aggregationcombobox_p.h b/messagelist/src/utils/aggregationcombobox_p.h
index dcd367a6..4980b6c9 100644
--- a/messagelist/src/utils/aggregationcombobox_p.h
+++ b/messagelist/src/utils/aggregationcombobox_p.h
@@ -1,50 +1,50 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_P_H
#define MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_P_H
namespace MessageList {
namespace Core {
class Aggregation;
} // namespace Core
namespace Utils {
class AggregationComboBox;
class AggregationComboBoxPrivate
{
public:
explicit AggregationComboBoxPrivate(AggregationComboBox *owner)
: q(owner)
{
}
AggregationComboBox *const q;
/**
* Refresh list of aggregations in the combobox.
*/
void slotLoadAggregations();
void setCurrentAggregation(const Core::Aggregation *aggregation);
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_AGGREGATIONCOMBOBOX_P_H
diff --git a/messagelist/src/utils/aggregationconfigbutton.cpp b/messagelist/src/utils/aggregationconfigbutton.cpp
index f17ae68b..dca85843 100644
--- a/messagelist/src/utils/aggregationconfigbutton.cpp
+++ b/messagelist/src/utils/aggregationconfigbutton.cpp
@@ -1,82 +1,84 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "utils/aggregationconfigbutton.h"
#include "utils/aggregationcombobox.h"
#include "utils/aggregationcombobox_p.h"
#include "utils/configureaggregationsdialog.h"
#include "core/manager.h"
#include <KLocalizedString>
using namespace MessageList::Core;
using namespace MessageList::Utils;
class MessageList::Utils::AggregationConfigButtonPrivate
{
public:
AggregationConfigButtonPrivate(AggregationConfigButton *owner)
: q(owner)
, mAggregationComboBox(nullptr)
{
}
AggregationConfigButton *const q;
const AggregationComboBox *mAggregationComboBox = nullptr;
void slotConfigureAggregations();
};
AggregationConfigButton::AggregationConfigButton(QWidget *parent, const AggregationComboBox *aggregationComboBox)
: QPushButton(i18n("Configure..."), parent)
, d(new AggregationConfigButtonPrivate(this))
{
d->mAggregationComboBox = aggregationComboBox;
- connect(this, &AggregationConfigButton::pressed, this, [this]() { d->slotConfigureAggregations(); });
+ connect(this, &AggregationConfigButton::pressed, this, [this]() {
+ d->slotConfigureAggregations();
+ });
// Keep aggregation combo up-to-date with any changes made in the configure dialog.
if (d->mAggregationComboBox != nullptr) {
connect(this, &AggregationConfigButton::configureDialogCompleted,
d->mAggregationComboBox, &AggregationComboBox::slotLoadAggregations);
}
setEnabled(Manager::instance());
}
AggregationConfigButton::~AggregationConfigButton()
{
delete d;
}
void AggregationConfigButtonPrivate::slotConfigureAggregations()
{
QString currentAggregationID;
if (mAggregationComboBox) {
currentAggregationID = mAggregationComboBox->currentAggregation();
}
ConfigureAggregationsDialog *dialog = new ConfigureAggregationsDialog(q->window());
dialog->selectAggregation(currentAggregationID);
QObject::connect(dialog, &ConfigureAggregationsDialog::okClicked, q, &AggregationConfigButton::configureDialogCompleted);
dialog->show();
}
#include "moc_aggregationconfigbutton.cpp"
diff --git a/messagelist/src/utils/aggregationconfigbutton.h b/messagelist/src/utils/aggregationconfigbutton.h
index 55582be7..a545f83c 100644
--- a/messagelist/src/utils/aggregationconfigbutton.h
+++ b/messagelist/src/utils/aggregationconfigbutton.h
@@ -1,59 +1,59 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_AGGREGATIONCONFIGBUTTON_H
#define MESSAGELIST_UTILS_AGGREGATIONCONFIGBUTTON_H
#include <messagelist_export.h>
#include <QPushButton>
namespace MessageList {
namespace Utils {
class AggregationComboBox;
class AggregationConfigButtonPrivate;
/**
* A specialized QPushButton that displays the aggregation
* configure dialog when pressed.
*/
class MESSAGELIST_EXPORT AggregationConfigButton : public QPushButton
{
Q_OBJECT
public:
/** Constructor.
* @param parent The parent widget for the button.
* @param aggregationComboBox Optional AggregationComboBox to be kept in sync
* with changes made by the configure dialog.
*/
explicit AggregationConfigButton(QWidget *parent, const AggregationComboBox *aggregationComboBox = nullptr);
~AggregationConfigButton();
Q_SIGNALS:
/**
* A signal emitted when configure dialog has been successfully completed.
*/
void configureDialogCompleted();
private:
AggregationConfigButtonPrivate *const d;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_AGGREGATIONCONFIGBUTTON_H
diff --git a/messagelist/src/utils/aggregationeditor.cpp b/messagelist/src/utils/aggregationeditor.cpp
index 8a4e8196..e881d119 100644
--- a/messagelist/src/utils/aggregationeditor.cpp
+++ b/messagelist/src/utils/aggregationeditor.cpp
@@ -1,255 +1,255 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* This program is free softhisare; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Softhisare 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 Softhisare
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*******************************************************************************/
#include "utils/aggregationeditor.h"
#include "core/aggregation.h"
#include "utils/comboboxutils.h"
#include <KLineEdit>
#include <KTextEdit>
#include <QLabel>
#include <QGridLayout>
#include <QPushButton>
#include <QCheckBox>
-#include <KComboBox>
+#include <QComboBox>
#include <KLocalizedString>
using namespace MessageList::Utils;
using namespace MessageList::Core;
AggregationEditor::AggregationEditor(QWidget *parent)
: OptionSetEditor(parent)
{
mCurrentAggregation = nullptr;
// Grouping and Threading tab
QWidget *tab = new QWidget(this);
addTab(tab, i18n("Groups && Threading"));
QGridLayout *tabg = new QGridLayout(tab);
tabg->addWidget(new QLabel(i18n("Grouping:"), tab), 0, 0);
- mGroupingCombo = new KComboBox(tab);
+ mGroupingCombo = new QComboBox(tab);
tabg->addWidget(mGroupingCombo, 0, 1);
- connect(mGroupingCombo, QOverload<int>::of(&KComboBox::activated), this, &AggregationEditor::groupingComboActivated);
+ connect(mGroupingCombo, qOverload<int>(&QComboBox::activated), this, &AggregationEditor::groupingComboActivated);
tabg->addWidget(new QLabel(i18n("Group expand policy:"), tab), 3, 0);
- mGroupExpandPolicyCombo = new KComboBox(tab);
+ mGroupExpandPolicyCombo = new QComboBox(tab);
tabg->addWidget(mGroupExpandPolicyCombo, 3, 1);
tabg->addWidget(new QLabel(i18n("Threading:"), tab), 4, 0);
- mThreadingCombo = new KComboBox(tab);
+ mThreadingCombo = new QComboBox(tab);
tabg->addWidget(mThreadingCombo, 4, 1);
- connect(mThreadingCombo, QOverload<int>::of(&KComboBox::activated), this, &AggregationEditor::threadingComboActivated);
+ connect(mThreadingCombo, qOverload<int>(&QComboBox::activated), this, &AggregationEditor::threadingComboActivated);
tabg->addWidget(new QLabel(i18n("Thread leader:"), tab), 5, 0);
- mThreadLeaderCombo = new KComboBox(tab);
+ mThreadLeaderCombo = new QComboBox(tab);
tabg->addWidget(mThreadLeaderCombo, 5, 1);
tabg->addWidget(new QLabel(i18n("Thread expand policy:"), tab), 6, 0);
- mThreadExpandPolicyCombo = new KComboBox(tab);
+ mThreadExpandPolicyCombo = new QComboBox(tab);
tabg->addWidget(mThreadExpandPolicyCombo, 6, 1);
tabg->setColumnStretch(1, 1);
tabg->setRowStretch(9, 1);
// Advanced tab
tab = new QWidget(this);
addTab(tab, i18nc("@title:tab Advanced settings tab for aggregation mode", "Advanced"));
tabg = new QGridLayout(tab);
tabg->addWidget(new QLabel(i18n("Fill view strategy:"), tab), 0, 0);
- mFillViewStrategyCombo = new KComboBox(tab);
+ mFillViewStrategyCombo = new QComboBox(tab);
tabg->addWidget(mFillViewStrategyCombo, 0, 1);
tabg->setColumnStretch(1, 1);
tabg->setRowStretch(1, 1);
fillGroupingCombo();
fillThreadingCombo();
fillFillViewStrategyCombo();
fillThreadLeaderCombo();
fillThreadExpandPolicyCombo();
fillGroupExpandPolicyCombo();
}
AggregationEditor::~AggregationEditor()
{
}
void AggregationEditor::editAggregation(Aggregation *set)
{
mCurrentAggregation = set;
if (!mCurrentAggregation) {
setEnabled(false);
return;
}
setEnabled(true);
nameEdit()->setText(set->name());
descriptionEdit()->setText(set->description());
ComboBoxUtils::setIntegerOptionComboValue(mGroupingCombo, (int)mCurrentAggregation->grouping());
ComboBoxUtils::setIntegerOptionComboValue(mThreadingCombo,
(int)mCurrentAggregation->threading());
ComboBoxUtils::setIntegerOptionComboValue(mFillViewStrategyCombo,
(int)mCurrentAggregation->fillViewStrategy());
//Necessary to fill after apply mGroupingCombo/mThreadingCombo/mFillViewStrategyCombo otherwise other combo are not filled.
fillThreadLeaderCombo();
fillThreadExpandPolicyCombo();
fillGroupExpandPolicyCombo();
ComboBoxUtils::setIntegerOptionComboValue(mThreadLeaderCombo,
(int)mCurrentAggregation->threadLeader());
ComboBoxUtils::setIntegerOptionComboValue(mThreadExpandPolicyCombo,
(int)mCurrentAggregation->threadExpandPolicy());
ComboBoxUtils::setIntegerOptionComboValue(mGroupExpandPolicyCombo,
(int)mCurrentAggregation->groupExpandPolicy());
fillThreadLeaderCombo();
fillThreadExpandPolicyCombo();
fillGroupExpandPolicyCombo();
setReadOnly(mCurrentAggregation->readOnly());
}
void AggregationEditor::setReadOnly(bool readOnly)
{
mGroupingCombo->setEnabled(!readOnly);
mGroupExpandPolicyCombo->setEnabled(!readOnly);
mThreadingCombo->setEnabled(!readOnly);
mThreadLeaderCombo->setEnabled(!readOnly);
mThreadExpandPolicyCombo->setEnabled(!readOnly);
mFillViewStrategyCombo->setEnabled(!readOnly);
OptionSetEditor::setReadOnly(readOnly);
}
void AggregationEditor::commit()
{
mCurrentAggregation->setName(nameEdit()->text());
mCurrentAggregation->setDescription(descriptionEdit()->toPlainText());
mCurrentAggregation->setGrouping(
static_cast<Aggregation::Grouping>(ComboBoxUtils::getIntegerOptionComboValue(mGroupingCombo, 0))
);
mCurrentAggregation->setGroupExpandPolicy(
static_cast<Aggregation::GroupExpandPolicy>(ComboBoxUtils::getIntegerOptionComboValue(mGroupExpandPolicyCombo, 0))
);
mCurrentAggregation->setThreading(
static_cast<Aggregation::Threading>(ComboBoxUtils::getIntegerOptionComboValue(mThreadingCombo, 0))
);
mCurrentAggregation->setThreadLeader(
static_cast<Aggregation::ThreadLeader>(ComboBoxUtils::getIntegerOptionComboValue(mThreadLeaderCombo, 0))
);
mCurrentAggregation->setThreadExpandPolicy(
static_cast<Aggregation::ThreadExpandPolicy>(ComboBoxUtils::getIntegerOptionComboValue(mThreadExpandPolicyCombo, 0))
);
mCurrentAggregation->setFillViewStrategy(
static_cast<Aggregation::FillViewStrategy>(ComboBoxUtils::getIntegerOptionComboValue(mFillViewStrategyCombo, 0))
);
}
void AggregationEditor::slotNameEditTextEdited(const QString &newName)
{
if (!mCurrentAggregation) {
return;
}
mCurrentAggregation->setName(newName);
Q_EMIT aggregationNameChanged();
}
void AggregationEditor::fillGroupingCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mGroupingCombo,
Aggregation::enumerateGroupingOptions()
);
}
void AggregationEditor::groupingComboActivated(int)
{
fillGroupExpandPolicyCombo();
fillThreadLeaderCombo();
}
void AggregationEditor::fillGroupExpandPolicyCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mGroupExpandPolicyCombo,
Aggregation::enumerateGroupExpandPolicyOptions(
(Aggregation::Grouping)ComboBoxUtils::getIntegerOptionComboValue(mGroupingCombo, Aggregation::NoGrouping)
)
);
}
void AggregationEditor::fillThreadingCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mThreadingCombo,
Aggregation::enumerateThreadingOptions()
);
}
void AggregationEditor::threadingComboActivated(int)
{
fillThreadLeaderCombo();
fillThreadExpandPolicyCombo();
}
void AggregationEditor::fillThreadLeaderCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mThreadLeaderCombo,
Aggregation::enumerateThreadLeaderOptions(
(Aggregation::Grouping)ComboBoxUtils::getIntegerOptionComboValue(mGroupingCombo, Aggregation::NoGrouping),
(Aggregation::Threading)ComboBoxUtils::getIntegerOptionComboValue(mThreadingCombo, Aggregation::NoThreading)
)
);
}
void AggregationEditor::fillThreadExpandPolicyCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mThreadExpandPolicyCombo,
Aggregation::enumerateThreadExpandPolicyOptions(
(Aggregation::Threading)ComboBoxUtils::getIntegerOptionComboValue(mThreadingCombo, Aggregation::NoThreading)
)
);
}
void AggregationEditor::fillFillViewStrategyCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mFillViewStrategyCombo,
Aggregation::enumerateFillViewStrategyOptions()
);
}
diff --git a/messagelist/src/utils/aggregationeditor.h b/messagelist/src/utils/aggregationeditor.h
index 33a283c3..336d0427 100644
--- a/messagelist/src/utils/aggregationeditor.h
+++ b/messagelist/src/utils/aggregationeditor.h
@@ -1,111 +1,111 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_UTILS_AGGREGATIONEDITOR_H
#define MESSAGELIST_UTILS_AGGREGATIONEDITOR_H
#include <utils/optionseteditor.h>
-class KComboBox;
+class QComboBox;
namespace MessageList {
namespace Core {
class Aggregation;
} // namespace Core
namespace Utils {
/**
* A widget that allows editing a single MessageList::Aggregation.
*
* Used by ConfigureAggregationsDialog.
*/
class AggregationEditor : public OptionSetEditor
{
Q_OBJECT
public:
explicit AggregationEditor(QWidget *parent);
~AggregationEditor() override;
private:
Core::Aggregation *mCurrentAggregation = nullptr; // shallow, may be null!
// Grouping, Threading and Sorting tab
- KComboBox *mGroupingCombo = nullptr;
- KComboBox *mGroupExpandPolicyCombo = nullptr;
- KComboBox *mThreadingCombo = nullptr;
- KComboBox *mThreadLeaderCombo = nullptr;
- KComboBox *mThreadExpandPolicyCombo = nullptr;
+ QComboBox *mGroupingCombo = nullptr;
+ QComboBox *mGroupExpandPolicyCombo = nullptr;
+ QComboBox *mThreadingCombo = nullptr;
+ QComboBox *mThreadLeaderCombo = nullptr;
+ QComboBox *mThreadExpandPolicyCombo = nullptr;
// Advanced tab
- KComboBox *mFillViewStrategyCombo = nullptr;
+ QComboBox *mFillViewStrategyCombo = nullptr;
public:
/**
* Sets the Aggregation to be edited.
* Saves and forgets any previously Aggregation that was being edited.
* The set parameter may be 0: in this case the editor is simply disabled.
*/
void editAggregation(Core::Aggregation *set);
/**
* Returns the Aggregation currently edited by this AggregationEditor.
* May be 0.
*/
Core::Aggregation *editedAggregation() const
{
return mCurrentAggregation;
}
/**
* Explicitly commits the changes in the editor to the edited Aggregation, if any.
*/
void commit();
Q_SIGNALS:
/**
* This is triggered when the aggregation name changes in the editor text field.
* It's connected to the Aggregation configuration dialog which updates
* the list of aggregations with the new name.
*/
void aggregationNameChanged();
private:
// Helpers for filling the various editing elements
void fillGroupingCombo();
void fillGroupExpandPolicyCombo();
void fillThreadingCombo();
void fillThreadLeaderCombo();
void fillThreadExpandPolicyCombo();
void fillFillViewStrategyCombo();
void setReadOnly(bool readOnly);
private Q_SLOTS:
// Internal handlers for editing element interaction
void groupingComboActivated(int idx);
void threadingComboActivated(int idx);
void slotNameEditTextEdited(const QString &newName) override;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_AGGREGATIONEDITOR_H
diff --git a/messagelist/src/utils/comboboxutils.cpp b/messagelist/src/utils/comboboxutils.cpp
index df1a0b6d..b948ad63 100644
--- a/messagelist/src/utils/comboboxutils.cpp
+++ b/messagelist/src/utils/comboboxutils.cpp
@@ -1,88 +1,88 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "utils/comboboxutils.h"
#include <QVariant>
-#include <KComboBox>
+#include <QComboBox>
using namespace MessageList::Utils;
-void ComboBoxUtils::fillIntegerOptionCombo(KComboBox *combo, const QList< QPair< QString, int > > &optionDescriptors)
+void ComboBoxUtils::fillIntegerOptionCombo(QComboBox *combo, const QList< QPair< QString, int > > &optionDescriptors)
{
int val = getIntegerOptionComboValue(combo, -1);
combo->clear();
int valIdx = -1;
int idx = 0;
QList< QPair< QString, int > >::ConstIterator end(optionDescriptors.end());
for (QList< QPair< QString, int > >::ConstIterator it = optionDescriptors.constBegin(); it != end; ++it) {
if (val == (*it).second) {
valIdx = idx;
}
combo->addItem((*it).first, QVariant((*it).second));
++idx;
}
if (idx == 0) {
combo->addItem(QStringLiteral("-"), QVariant((int)0)); // always default to 0
combo->setEnabled(false);
} else {
if (!combo->isEnabled()) {
combo->setEnabled(true);
}
if (valIdx >= 0) {
combo->setCurrentIndex(valIdx);
}
if (combo->count() == 1) {
combo->setEnabled(false); // disable when there is no choice
}
}
}
-void ComboBoxUtils::setIntegerOptionComboValue(KComboBox *combo, int value)
+void ComboBoxUtils::setIntegerOptionComboValue(QComboBox *combo, int value)
{
if (combo->itemData(combo->currentIndex()).toInt() == value) {
return;
}
int index = combo->findData(value);
if (index != -1) {
combo->setCurrentIndex(index);
} else {
combo->setCurrentIndex(0); // default
}
}
-int ComboBoxUtils::getIntegerOptionComboValue(KComboBox *combo, int defaultValue)
+int ComboBoxUtils::getIntegerOptionComboValue(QComboBox *combo, int defaultValue)
{
const int idx = combo->currentIndex();
if (idx < 0) {
return defaultValue;
}
QVariant data = combo->itemData(idx);
bool ok;
const int val = data.toInt(&ok);
if (!ok) {
return defaultValue;
}
return val;
}
diff --git a/messagelist/src/utils/comboboxutils.h b/messagelist/src/utils/comboboxutils.h
index efa24ec1..829c9a01 100644
--- a/messagelist/src/utils/comboboxutils.h
+++ b/messagelist/src/utils/comboboxutils.h
@@ -1,57 +1,57 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_UTILS_COMBOBOXUTILS_H
#define MESSAGELIST_UTILS_COMBOBOXUTILS_H
#include <QList>
#include <QPair>
-class KComboBox;
+class QComboBox;
namespace MessageList {
namespace Utils {
/**
- * Namespace containing some helper functions for KComboBox widgets.
+ * Namespace containing some helper functions for QComboBox widgets.
*/
namespace ComboBoxUtils {
/**
- * Fills the specified KComboBox with the options available in optionDescriptors.
+ * Fills the specified QComboBox with the options available in optionDescriptors.
* Each option descriptor contains a description string and a distinct integer (possibly enum)
* identifier value.
*/
-void fillIntegerOptionCombo(KComboBox *combo, const QList< QPair< QString, int > > &optionDescriptors);
+void fillIntegerOptionCombo(QComboBox *combo, const QList< QPair< QString, int > > &optionDescriptors);
/**
* Returns the identifier of the currently selected option in the specified combo.
* If the combo has no current selection or something goes wrong then the defaultValue
* is returned instead.
*/
-int getIntegerOptionComboValue(KComboBox *combo, int defaultValue);
+int getIntegerOptionComboValue(QComboBox *combo, int defaultValue);
/**
* Sets the currently selected option in the specified combo.
*/
-void setIntegerOptionComboValue(KComboBox *combo, int value);
+void setIntegerOptionComboValue(QComboBox *combo, int value);
} // namespace ComboBoxUtils
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_COMBOBOXUTILS_H
diff --git a/messagelist/src/utils/configureaggregationsdialog.cpp b/messagelist/src/utils/configureaggregationsdialog.cpp
index 2bf48e0c..b36d35f9 100644
--- a/messagelist/src/utils/configureaggregationsdialog.cpp
+++ b/messagelist/src/utils/configureaggregationsdialog.cpp
@@ -1,460 +1,475 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "utils/configureaggregationsdialog.h"
#include "utils/configureaggregationsdialog_p.h"
#include "utils/aggregationeditor.h"
#include "core/aggregation.h"
#include "core/manager.h"
#include <QGridLayout>
#include <QPushButton>
#include <QFrame>
#include <QMap>
#include <KLocalizedString>
#include <KIconLoader>
#include <QIcon>
#include <KConfig>
#include <QFileDialog>
#include <KConfigGroup>
#include <QDialogButtonBox>
#include <QVBoxLayout>
namespace MessageList {
namespace Utils {
class AggregationListWidgetItem : public QListWidgetItem
{
private:
Core::Aggregation *mAggregation = nullptr;
public:
AggregationListWidgetItem(QListWidget *par, const Core::Aggregation &set)
: QListWidgetItem(set.name(), par)
{
mAggregation = new Core::Aggregation(set);
}
~AggregationListWidgetItem()
{
delete mAggregation;
}
public:
Core::Aggregation *aggregation() const
{
return mAggregation;
}
void forgetAggregation()
{
mAggregation = nullptr;
}
};
/**
* The widget that lists the available Aggregations.
*
* At the moment of writing, derived from QListWidget only to override sizeHint().
*/
class AggregationListWidget : public QListWidget
{
public:
AggregationListWidget(QWidget *parent)
: QListWidget(parent)
{
}
public:
// need a larger but shorter QListWidget
QSize sizeHint() const override
{
return QSize(450, 128);
}
};
} // namespace Utils
} // namespace MessageList
using namespace MessageList::Core;
using namespace MessageList::Utils;
ConfigureAggregationsDialog::ConfigureAggregationsDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setAttribute(Qt::WA_DeleteOnClose);
- setWindowModality(Qt::ApplicationModal); // FIXME: Sure ?
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureAggregationsDialog::reject);
setWindowTitle(i18n("Customize Message Aggregation Modes"));
QWidget *base = new QWidget(this);
mainLayout->addWidget(base);
mainLayout->addWidget(buttonBox);
QGridLayout *g = new QGridLayout(base);
- g->setMargin(0);
+ g->setContentsMargins(0, 0, 0, 0);
d->mAggregationList = new AggregationListWidget(base);
d->mAggregationList->setSelectionMode(QAbstractItemView::ExtendedSelection);
d->mAggregationList->setSortingEnabled(true);
g->addWidget(d->mAggregationList, 0, 0, 7, 1);
- connect(d->mAggregationList, &AggregationListWidget::itemClicked, this, [this](QListWidgetItem *item) { d->aggregationListItemClicked(item); });
+ connect(d->mAggregationList, &AggregationListWidget::itemClicked, this, [this](QListWidgetItem *item) {
+ d->aggregationListItemClicked(item);
+ });
d->mNewAggregationButton = new QPushButton(i18n("New Aggregation"), base);
d->mNewAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
d->mNewAggregationButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mNewAggregationButton, 0, 1);
- connect(d->mNewAggregationButton, &QPushButton::clicked, this, [this]() { d->newAggregationButtonClicked(); });
+ connect(d->mNewAggregationButton, &QPushButton::clicked, this, [this]() {
+ d->newAggregationButtonClicked();
+ });
d->mCloneAggregationButton = new QPushButton(i18n("Clone Aggregation"), base);
d->mCloneAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
d->mCloneAggregationButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mCloneAggregationButton, 1, 1);
- connect(d->mCloneAggregationButton,&QPushButton::clicked, this, [this]() { d->cloneAggregationButtonClicked(); });
+ connect(d->mCloneAggregationButton, &QPushButton::clicked, this, [this]() {
+ d->cloneAggregationButtonClicked();
+ });
QFrame *f = new QFrame(base);
f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
f->setMinimumHeight(24);
g->addWidget(f, 2, 1, Qt::AlignVCenter);
d->mExportAggregationButton = new QPushButton(i18n("Export Aggregation..."), base);
g->addWidget(d->mExportAggregationButton, 3, 1);
- connect(d->mExportAggregationButton, &QPushButton::clicked, this, [this]() { d->exportAggregationButtonClicked(); });
+ connect(d->mExportAggregationButton, &QPushButton::clicked, this, [this]() {
+ d->exportAggregationButtonClicked();
+ });
d->mImportAggregationButton = new QPushButton(i18n("Import Aggregation..."), base);
g->addWidget(d->mImportAggregationButton, 4, 1);
- connect(d->mImportAggregationButton, &QPushButton::clicked, this, [this]() { d->importAggregationButtonClicked(); });
+ connect(d->mImportAggregationButton, &QPushButton::clicked, this, [this]() {
+ d->importAggregationButtonClicked();
+ });
f = new QFrame(base);
f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
f->setMinimumHeight(24);
g->addWidget(f, 5, 1, Qt::AlignVCenter);
d->mDeleteAggregationButton = new QPushButton(i18n("Delete Aggregation"), base);
d->mDeleteAggregationButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
d->mDeleteAggregationButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mDeleteAggregationButton, 6, 1);
- connect(d->mDeleteAggregationButton, &QPushButton::clicked, this, [this]() { d->deleteAggregationButtonClicked(); });
+ connect(d->mDeleteAggregationButton, &QPushButton::clicked, this, [this]() {
+ d->deleteAggregationButtonClicked();
+ });
d->mEditor = new AggregationEditor(base);
g->addWidget(d->mEditor, 8, 0, 1, 2);
- connect(d->mEditor, &AggregationEditor::aggregationNameChanged, this, [this]() { d->editedAggregationNameChanged(); });
+ connect(d->mEditor, &AggregationEditor::aggregationNameChanged, this, [this]() {
+ d->editedAggregationNameChanged();
+ });
g->setColumnStretch(0, 1);
g->setRowStretch(7, 1);
- connect(okButton, &QPushButton::clicked, this, [this]() { d->okButtonClicked(); });
+ connect(okButton, &QPushButton::clicked, this, [this]() {
+ d->okButtonClicked();
+ });
d->fillAggregationList();
}
ConfigureAggregationsDialog::~ConfigureAggregationsDialog()
{
delete d;
}
void ConfigureAggregationsDialog::selectAggregation(const QString &aggregationId)
{
AggregationListWidgetItem *item = d->findAggregationItemById(aggregationId);
if (item) {
d->mAggregationList->setCurrentItem(item);
d->aggregationListItemClicked(item);
}
}
void ConfigureAggregationsDialog::Private::okButtonClicked()
{
if (Manager::instance()) {
commitEditor();
Manager::instance()->removeAllAggregations();
const int c = mAggregationList->count();
int i = 0;
while (i < c) {
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->item(i));
if (item) {
Manager::instance()->addAggregation(item->aggregation());
item->forgetAggregation();
}
++i;
}
Manager::instance()->aggregationsConfigurationCompleted();
}
Q_EMIT q->okClicked();
q->close(); // this will delete too
}
void ConfigureAggregationsDialog::Private::commitEditor()
{
Aggregation *editedAggregation = mEditor->editedAggregation();
if (!editedAggregation) {
return;
}
mEditor->commit();
AggregationListWidgetItem *editedItem = findAggregationItemByAggregation(editedAggregation);
if (!editedItem) {
return;
}
const QString goodName = uniqueNameForAggregation(editedAggregation->name(), editedAggregation);
editedAggregation->setName(goodName);
editedItem->setText(goodName);
}
void ConfigureAggregationsDialog::Private::editedAggregationNameChanged()
{
Aggregation *set = mEditor->editedAggregation();
if (!set) {
return;
}
AggregationListWidgetItem *it = findAggregationItemByAggregation(set);
if (!it) {
return;
}
const QString goodName = uniqueNameForAggregation(set->name(), set);
it->setText(goodName);
}
void ConfigureAggregationsDialog::Private::fillAggregationList()
{
if (!Manager::instance()) {
return;
}
const QMap< QString, Aggregation * > &sets = Manager::instance()->aggregations();
QMap< QString, Aggregation * >::ConstIterator end(sets.constEnd());
for (QMap< QString, Aggregation * >::ConstIterator it = sets.constBegin(); it != end; ++it) {
(void)new AggregationListWidgetItem(mAggregationList, *(*it));
}
}
void ConfigureAggregationsDialog::Private::aggregationListItemClicked(QListWidgetItem *cur)
{
commitEditor();
updateButton(cur);
}
void ConfigureAggregationsDialog::Private::updateButton(QListWidgetItem *cur)
{
const int numberOfSelectedItem(mAggregationList->selectedItems().count());
AggregationListWidgetItem *item = cur ? dynamic_cast< AggregationListWidgetItem * >(cur) : nullptr;
mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly() && (mAggregationList->count() > 1));
mCloneAggregationButton->setEnabled(numberOfSelectedItem == 1);
mExportAggregationButton->setEnabled(numberOfSelectedItem > 0);
mEditor->editAggregation(item ? item->aggregation() : nullptr);
if (item && !item->isSelected()) {
item->setSelected(true); // make sure it's true
}
}
AggregationListWidgetItem *ConfigureAggregationsDialog::Private::findAggregationItemByName(const QString &name, Aggregation *skipAggregation)
{
const int c = mAggregationList->count();
int i = 0;
while (i < c) {
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->item(i));
if (item) {
if (item->aggregation() != skipAggregation) {
if (item->aggregation()->name() == name) {
return item;
}
}
}
++i;
}
return nullptr;
}
AggregationListWidgetItem *ConfigureAggregationsDialog::Private::findAggregationItemById(const QString &aggregationId)
{
const int c = mAggregationList->count();
int i = 0;
while (i < c) {
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->item(i));
if (item) {
if (item->aggregation()->id() == aggregationId) {
return item;
}
}
++i;
}
return nullptr;
}
AggregationListWidgetItem *ConfigureAggregationsDialog::Private::findAggregationItemByAggregation(Aggregation *set)
{
const int c = mAggregationList->count();
int i = 0;
while (i < c) {
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->item(i));
if (item) {
if (item->aggregation() == set) {
return item;
}
}
++i;
}
return nullptr;
}
QString ConfigureAggregationsDialog::Private::uniqueNameForAggregation(const QString &baseName, Aggregation *skipAggregation)
{
QString ret = baseName;
if (ret.isEmpty()) {
ret = i18n("Unnamed Aggregation");
}
int idx = 1;
AggregationListWidgetItem *item = findAggregationItemByName(ret, skipAggregation);
while (item) {
idx++;
ret = QStringLiteral("%1 %2").arg(baseName).arg(idx);
item = findAggregationItemByName(ret, skipAggregation);
}
return ret;
}
void ConfigureAggregationsDialog::Private::newAggregationButtonClicked()
{
Aggregation emptyAggregation;
emptyAggregation.setName(uniqueNameForAggregation(i18n("New Aggregation")));
AggregationListWidgetItem *item = new AggregationListWidgetItem(mAggregationList, emptyAggregation);
mAggregationList->setCurrentItem(item);
mDeleteAggregationButton->setEnabled(item && !item->aggregation()->readOnly());
}
void ConfigureAggregationsDialog::Private::cloneAggregationButtonClicked()
{
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->currentItem());
if (!item) {
return;
}
commitEditor();
item->setSelected(false);
Aggregation copyAggregation(*(item->aggregation()));
copyAggregation.setReadOnly(false);
copyAggregation.generateUniqueId(); // regenerate id so it becomes different
copyAggregation.setName(uniqueNameForAggregation(item->aggregation()->name()));
item = new AggregationListWidgetItem(mAggregationList, copyAggregation);
mAggregationList->setCurrentItem(item);
aggregationListItemClicked(item);
}
void ConfigureAggregationsDialog::Private::deleteAggregationButtonClicked()
{
const QList<QListWidgetItem *> list = mAggregationList->selectedItems();
if (list.isEmpty()) {
return;
}
mEditor->editAggregation(nullptr); // forget it
for (QListWidgetItem *it : list) {
AggregationListWidgetItem *item = dynamic_cast< AggregationListWidgetItem * >(it);
if (!item) {
return;
}
if (!item->aggregation()->readOnly()) {
delete item; // this will trigger aggregationListCurrentItemChanged()
}
if (mAggregationList->count() < 2) {
break; // no way: desperately try to keep at least one option set alive :)
}
}
AggregationListWidgetItem *newItem = dynamic_cast< AggregationListWidgetItem * >(mAggregationList->currentItem());
updateButton(newItem);
}
void ConfigureAggregationsDialog::Private::importAggregationButtonClicked()
{
const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Aggregation"));
if (!filename.isEmpty()) {
KConfig config(filename);
if (config.hasGroup(QStringLiteral("MessageListView::Aggregations"))) {
KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations"));
const int cnt = grp.readEntry("Count", 0);
int idx = 0;
while (idx < cnt) {
const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString());
if (!data.isEmpty()) {
Aggregation *set = new Aggregation();
if (set->loadFromString(data)) {
set->setReadOnly(false);
set->generateUniqueId(); // regenerate id so it becomes different
set->setName(uniqueNameForAggregation(set->name()));
(void)new AggregationListWidgetItem(mAggregationList, *set);
} else {
delete set; // b0rken
}
}
++idx;
}
}
}
}
void ConfigureAggregationsDialog::Private::exportAggregationButtonClicked()
{
const QList<QListWidgetItem *> list = mAggregationList->selectedItems();
if (list.isEmpty()) {
return;
}
const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Aggregation"), QString(), i18n("All Files (*)"));
if (!filename.isEmpty()) {
KConfig config(filename);
KConfigGroup grp(&config, QStringLiteral("MessageListView::Aggregations"));
grp.writeEntry("Count", list.count());
int idx = 0;
for (QListWidgetItem *item : list) {
AggregationListWidgetItem *themeItem = static_cast< AggregationListWidgetItem * >(item);
grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->aggregation()->saveToString());
++idx;
}
}
}
#include "moc_configureaggregationsdialog.cpp"
diff --git a/messagelist/src/utils/configurethemesdialog.cpp b/messagelist/src/utils/configurethemesdialog.cpp
index b2228769..ada0ee53 100644
--- a/messagelist/src/utils/configurethemesdialog.cpp
+++ b/messagelist/src/utils/configurethemesdialog.cpp
@@ -1,482 +1,497 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#include "utils/configurethemesdialog.h"
#include "utils/configurethemesdialog_p.h"
#include "utils/themeeditor.h"
#include "core/theme.h"
#include "core/manager.h"
#include <QGridLayout>
#include <QPushButton>
#include <QFrame>
#include <QMap>
#include <KMessageBox>
#include <KLocalizedString>
#include <KIconLoader>
#include <KConfigGroup>
#include <QIcon>
#include <KConfig>
#include <QFileDialog>
#include <QDialogButtonBox>
#include <QVBoxLayout>
namespace MessageList {
namespace Utils {
class ThemeListWidgetItem : public QListWidgetItem
{
public:
ThemeListWidgetItem(QListWidget *par, const Core::Theme &set)
: QListWidgetItem(set.name(), par)
{
mTheme = new Core::Theme(set);
}
~ThemeListWidgetItem()
{
delete mTheme;
}
Core::Theme *theme() const
{
return mTheme;
}
void forgetTheme()
{
mTheme = nullptr;
}
private:
Core::Theme *mTheme = nullptr;
};
class ThemeListWidget : public QListWidget
{
public:
ThemeListWidget(QWidget *parent)
: QListWidget(parent)
{
}
public:
// need a larger but shorter QListWidget
QSize sizeHint() const override
{
return QSize(450, 128);
}
};
} // namespace Utils
} // namespace MessageList
using namespace MessageList::Core;
using namespace MessageList::Utils;
ConfigureThemesDialog::ConfigureThemesDialog(QWidget *parent)
: QDialog(parent)
, d(new Private(this))
{
setAttribute(Qt::WA_DeleteOnClose);
- setWindowModality(Qt::ApplicationModal); // FIXME: Sure ?
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ConfigureThemesDialog::reject);
setWindowTitle(i18n("Customize Themes"));
QWidget *base = new QWidget(this);
mainLayout->addWidget(base);
mainLayout->addWidget(buttonBox);
QGridLayout *g = new QGridLayout(base);
g->setContentsMargins(0, 0, 0, 0);
d->mThemeList = new ThemeListWidget(base);
d->mThemeList->setSelectionMode(QAbstractItemView::ExtendedSelection);
d->mThemeList->setSortingEnabled(true);
g->addWidget(d->mThemeList, 0, 0, 7, 1);
connect(d->mThemeList, &ThemeListWidget::itemClicked,
- this, [this](QListWidgetItem *item) { d->themeListItemClicked(item); });
+ this, [this](QListWidgetItem *item) {
+ d->themeListItemClicked(item);
+ });
d->mNewThemeButton = new QPushButton(i18n("New Theme"), base);
d->mNewThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
d->mNewThemeButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mNewThemeButton, 0, 1);
- connect(d->mNewThemeButton, &QPushButton::clicked, this, [this]() {d->newThemeButtonClicked(); });
+ connect(d->mNewThemeButton, &QPushButton::clicked, this, [this]() {
+ d->newThemeButtonClicked();
+ });
d->mCloneThemeButton = new QPushButton(i18n("Clone Theme"), base);
d->mCloneThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
d->mCloneThemeButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mCloneThemeButton, 1, 1);
- connect(d->mCloneThemeButton, &QPushButton::clicked, this, [this]() { d->cloneThemeButtonClicked(); });
+ connect(d->mCloneThemeButton, &QPushButton::clicked, this, [this]() {
+ d->cloneThemeButtonClicked();
+ });
QFrame *f = new QFrame(base);
f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
f->setMinimumHeight(24);
g->addWidget(f, 2, 1, Qt::AlignVCenter);
d->mExportThemeButton = new QPushButton(i18n("Export Theme..."), base);
g->addWidget(d->mExportThemeButton, 3, 1);
- connect(d->mExportThemeButton, &QPushButton::clicked, this, [this]() { d->exportThemeButtonClicked(); });
+ connect(d->mExportThemeButton, &QPushButton::clicked, this, [this]() {
+ d->exportThemeButtonClicked();
+ });
d->mImportThemeButton = new QPushButton(i18n("Import Theme..."), base);
g->addWidget(d->mImportThemeButton, 4, 1);
- connect(d->mImportThemeButton, &QPushButton::clicked, this, [this]() { d->importThemeButtonClicked(); });
+ connect(d->mImportThemeButton, &QPushButton::clicked, this, [this]() {
+ d->importThemeButtonClicked();
+ });
f = new QFrame(base);
f->setFrameStyle(QFrame::Sunken | QFrame::HLine);
f->setMinimumHeight(24);
g->addWidget(f, 5, 1, Qt::AlignVCenter);
d->mDeleteThemeButton = new QPushButton(i18n("Delete Theme"), base);
d->mDeleteThemeButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
d->mDeleteThemeButton->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall));
g->addWidget(d->mDeleteThemeButton, 6, 1);
- connect(d->mDeleteThemeButton, &QPushButton::clicked, this, [this]() { d->deleteThemeButtonClicked(); });
+ connect(d->mDeleteThemeButton, &QPushButton::clicked, this, [this]() {
+ d->deleteThemeButtonClicked();
+ });
d->mEditor = new ThemeEditor(base);
g->addWidget(d->mEditor, 8, 0, 1, 2);
- connect(d->mEditor, &ThemeEditor::themeNameChanged, this, [this]() { d->editedThemeNameChanged(); });
+ connect(d->mEditor, &ThemeEditor::themeNameChanged, this, [this]() {
+ d->editedThemeNameChanged();
+ });
g->setColumnStretch(0, 1);
g->setRowStretch(4, 1);
- connect(okButton, &QPushButton::clicked, this, [this]() { d->okButtonClicked(); } );
+ connect(okButton, &QPushButton::clicked, this, [this]() {
+ d->okButtonClicked();
+ });
d->fillThemeList();
}
ConfigureThemesDialog::~ConfigureThemesDialog()
{
delete d;
}
void ConfigureThemesDialog::selectTheme(const QString &themeId)
{
ThemeListWidgetItem *item = d->findThemeItemById(themeId);
if (item) {
d->mThemeList->setCurrentItem(item);
d->themeListItemClicked(item);
}
}
void ConfigureThemesDialog::Private::okButtonClicked()
{
commitEditor();
Manager::instance()->removeAllThemes();
const int c = mThemeList->count();
int i = 0;
while (i < c) {
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(mThemeList->item(i));
if (item) {
Manager::instance()->addTheme(item->theme());
item->forgetTheme();
}
++i;
}
Manager::instance()->themesConfigurationCompleted();
Q_EMIT q->okClicked();
q->close(); // this will delete too
}
void ConfigureThemesDialog::Private::commitEditor()
{
Theme *editedTheme = mEditor->editedTheme();
if (!editedTheme) {
return;
}
mEditor->commit();
ThemeListWidgetItem *editedItem = findThemeItemByTheme(editedTheme);
if (!editedItem) {
return;
}
// We must reset the runtime column state as the columns might have
// totally changed in the editor
editedTheme->resetColumnState();
QString goodName = uniqueNameForTheme(editedTheme->name(), editedTheme);
editedTheme->setName(goodName);
editedItem->setText(goodName);
}
void ConfigureThemesDialog::Private::editedThemeNameChanged()
{
Theme *set = mEditor->editedTheme();
if (!set) {
return;
}
ThemeListWidgetItem *it = findThemeItemByTheme(set);
if (!it) {
return;
}
QString goodName = uniqueNameForTheme(set->name(), set);
it->setText(goodName);
}
void ConfigureThemesDialog::Private::fillThemeList()
{
const QMap< QString, Theme * > &sets = Manager::instance()->themes();
QMap< QString, Theme * >::ConstIterator end(sets.constEnd());
for (QMap< QString, Theme * >::ConstIterator it = sets.constBegin(); it != end; ++it) {
(void)new ThemeListWidgetItem(mThemeList, *(*it));
}
}
void ConfigureThemesDialog::Private::themeListItemClicked(QListWidgetItem *cur)
{
commitEditor();
const int numberOfSelectedItem(mThemeList->selectedItems().count());
ThemeListWidgetItem *item = cur ? dynamic_cast< ThemeListWidgetItem * >(cur) : nullptr;
mDeleteThemeButton->setEnabled(item && !item->theme()->readOnly());
mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
mEditor->editTheme(item ? item->theme() : nullptr);
mExportThemeButton->setEnabled(numberOfSelectedItem > 0);
if (item && !item->isSelected()) {
item->setSelected(true); // make sure it's true
}
}
ThemeListWidgetItem *ConfigureThemesDialog::Private::findThemeItemById(const QString &themeId)
{
const int c = mThemeList->count();
int i = 0;
while (i < c) {
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(mThemeList->item(i));
if (item) {
if (item->theme()->id() == themeId) {
return item;
}
}
++i;
}
return nullptr;
}
ThemeListWidgetItem *ConfigureThemesDialog::Private::findThemeItemByName(const QString &name, Theme *skipTheme)
{
const int c = mThemeList->count();
int i = 0;
while (i < c) {
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(mThemeList->item(i));
if (item) {
if (item->theme() != skipTheme) {
if (item->theme()->name() == name) {
return item;
}
}
}
++i;
}
return nullptr;
}
ThemeListWidgetItem *ConfigureThemesDialog::Private::findThemeItemByTheme(Theme *set)
{
const int c = mThemeList->count();
int i = 0;
while (i < c) {
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(mThemeList->item(i));
if (item) {
if (item->theme() == set) {
return item;
}
}
++i;
}
return nullptr;
}
QString ConfigureThemesDialog::Private::uniqueNameForTheme(const QString &baseName, Theme *skipTheme)
{
QString ret = baseName;
if (ret.isEmpty()) {
ret = i18n("Unnamed Theme");
}
int idx = 1;
ThemeListWidgetItem *item = findThemeItemByName(ret, skipTheme);
while (item) {
idx++;
ret = QStringLiteral("%1 %2").arg(baseName, QString::number(idx));
item = findThemeItemByName(ret, skipTheme);
}
return ret;
}
void ConfigureThemesDialog::Private::newThemeButtonClicked()
{
const int numberOfSelectedItem(mThemeList->selectedItems().count());
Theme emptyTheme;
emptyTheme.setName(uniqueNameForTheme(i18n("New Theme")));
Theme::Column *col = new Theme::Column();
col->setLabel(i18n("New Column"));
col->setVisibleByDefault(true);
col->addMessageRow(new Theme::Row());
col->addGroupHeaderRow(new Theme::Row());
emptyTheme.addColumn(col);
ThemeListWidgetItem *item = new ThemeListWidgetItem(mThemeList, emptyTheme);
mThemeList->setCurrentItem(item);
Core::Theme *theme = item->theme();
if (theme) {
mEditor->editTheme(theme);
mDeleteThemeButton->setEnabled(!theme->readOnly());
mExportThemeButton->setEnabled(item);
mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
} else {
mDeleteThemeButton->setEnabled(false);
mExportThemeButton->setEnabled(false);
mCloneThemeButton->setEnabled(false);
}
}
void ConfigureThemesDialog::Private::cloneThemeButtonClicked()
{
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(mThemeList->currentItem());
if (!item) {
return;
}
commitEditor();
item->setSelected(false);
Theme copyTheme(*(item->theme()));
copyTheme.setReadOnly(false);
copyTheme.detach(); // detach shared data
copyTheme.generateUniqueId(); // regenerate id so it becomes different
copyTheme.setName(uniqueNameForTheme(item->theme()->name()));
item = new ThemeListWidgetItem(mThemeList, copyTheme);
mThemeList->setCurrentItem(item);
mEditor->editTheme(item->theme());
const int numberOfSelectedItem(mThemeList->selectedItems().count());
mDeleteThemeButton->setEnabled(!item->theme()->readOnly());
mExportThemeButton->setEnabled(true);
mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
}
void ConfigureThemesDialog::Private::deleteThemeButtonClicked()
{
const QList<QListWidgetItem *> list = mThemeList->selectedItems();
if (list.isEmpty()) {
return;
}
if (KMessageBox::Yes == KMessageBox::questionYesNo(q, list.count() > 1 ? i18n("Do you want to delete selected themes?")
: i18n("Do you want to delete \"%1\"?", list.first()->text()), i18nc("@title:window", "Delete Theme"))) {
mEditor->editTheme(nullptr); // forget it
for (QListWidgetItem *it : list) {
ThemeListWidgetItem *item = dynamic_cast< ThemeListWidgetItem * >(it);
if (!item) {
return;
}
if (!item->theme()->readOnly()) {
delete item;// this will trigger themeListCurrentItemChanged()
}
if (mThemeList->count() < 2) {
break; // no way: desperately try to keep at least one option set alive :)
}
}
ThemeListWidgetItem *newItem = dynamic_cast< ThemeListWidgetItem * >(mThemeList->currentItem());
mDeleteThemeButton->setEnabled(newItem && !newItem->theme()->readOnly());
mExportThemeButton->setEnabled(newItem);
const int numberOfSelectedItem(mThemeList->selectedItems().count());
mCloneThemeButton->setEnabled(numberOfSelectedItem == 1);
}
}
void ConfigureThemesDialog::Private::importThemeButtonClicked()
{
const QString filename = QFileDialog::getOpenFileName(q, i18n("Import Theme"));
if (!filename.isEmpty()) {
KConfig config(filename);
if (config.hasGroup(QStringLiteral("MessageListView::Themes"))) {
KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes"));
const int cnt = grp.readEntry("Count", 0);
int idx = 0;
while (idx < cnt) {
const QString data = grp.readEntry(QStringLiteral("Set%1").arg(idx), QString());
if (!data.isEmpty()) {
Theme *set = new Theme();
if (set->loadFromString(data)) {
set->setReadOnly(false);
set->detach(); // detach shared data
set->generateUniqueId(); // regenerate id so it becomes different
set->setName(uniqueNameForTheme(set->name()));
(void)new ThemeListWidgetItem(mThemeList, *set);
} else {
delete set;
}
}
++idx;
}
}
}
}
void ConfigureThemesDialog::Private::exportThemeButtonClicked()
{
const QList<QListWidgetItem *> list = mThemeList->selectedItems();
if (list.isEmpty()) {
return;
}
const QString filename = QFileDialog::getSaveFileName(q, i18n("Export Theme"), QString(), i18n("All Files (*)"));
if (!filename.isEmpty()) {
KConfig config(filename);
KConfigGroup grp(&config, QStringLiteral("MessageListView::Themes"));
grp.writeEntry("Count", list.count());
int idx = 0;
for (QListWidgetItem *item : list) {
ThemeListWidgetItem *themeItem = static_cast< ThemeListWidgetItem * >(item);
grp.writeEntry(QStringLiteral("Set%1").arg(idx), themeItem->theme()->saveToString());
++idx;
}
}
}
#include "moc_configurethemesdialog.cpp"
diff --git a/messagelist/src/utils/themecombobox.cpp b/messagelist/src/utils/themecombobox.cpp
index f81a8883..61be2e10 100644
--- a/messagelist/src/utils/themecombobox.cpp
+++ b/messagelist/src/utils/themecombobox.cpp
@@ -1,137 +1,137 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "themecombobox.h"
#include "utils/themecombobox.h"
#include "utils/themecombobox_p.h"
#include "storagemodel.h"
#include "core/manager.h"
#include "core/theme.h"
#include "messagelistsettings.h"
using namespace MessageList::Core;
using namespace MessageList::Utils;
ThemeComboBox::ThemeComboBox(QWidget *parent)
- : KComboBox(parent)
+ : QComboBox(parent)
, d(new ThemeComboBoxPrivate(this))
{
if (Manager::instance()) {
d->slotLoadThemes();
} else {
setEnabled(false);
}
}
ThemeComboBox::~ThemeComboBox()
{
delete d;
}
QString ThemeComboBox::currentTheme() const
{
return itemData(currentIndex()).toString();
}
void ThemeComboBox::writeDefaultConfig() const
{
KConfigGroup group(MessageListSettings::self()->config(), "MessageListView::StorageModelThemes");
const QString themeID = currentTheme();
group.writeEntry(QStringLiteral("DefaultSet"), themeID);
if (Manager::instance()) {
Manager::instance()->themesConfigurationCompleted();
}
}
void ThemeComboBox::writeStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool isPrivateSetting) const
{
writeStorageModelConfig(storageModel->id(), isPrivateSetting);
}
void ThemeComboBox::writeStorageModelConfig(const QString &id, bool isPrivateSetting) const
{
if (Manager::instance()) {
QString themeID;
if (isPrivateSetting) {
themeID = currentTheme();
} else { // explicitly use default theme id when using default theme.
themeID = Manager::instance()->defaultTheme()->id();
}
Manager::instance()->saveThemeForStorageModel(id, themeID, isPrivateSetting);
Manager::instance()->themesConfigurationCompleted();
}
}
void ThemeComboBox::readStorageModelConfig(const Akonadi::Collection &col, bool &isPrivateSetting)
{
if (Manager::instance()) {
const Theme *theme = Manager::instance()->themeForStorageModel(col, &isPrivateSetting);
d->setCurrentTheme(theme);
}
}
void ThemeComboBox::readStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool &isPrivateSetting)
{
if (Manager::instance()) {
const Theme *theme = Manager::instance()->themeForStorageModel(storageModel, &isPrivateSetting);
d->setCurrentTheme(theme);
}
}
void ThemeComboBox::selectDefault()
{
if (Manager::instance()) {
const Theme *defaultTheme = Manager::instance()->defaultTheme();
d->setCurrentTheme(defaultTheme);
}
}
void ThemeComboBox::slotLoadThemes()
{
d->slotLoadThemes();
}
void ThemeComboBoxPrivate::slotLoadThemes()
{
if (!Manager::instance()) {
return;
}
q->clear();
// Get all message list themes and sort them into alphabetical order.
QList< Theme * > themes = Manager::instance()->themes().values();
std::sort(themes.begin(), themes.end(), MessageList::Core::Theme::compareName);
for (const Theme *theme : qAsConst(themes)) {
q->addItem(theme->name(), QVariant(theme->id()));
}
}
void ThemeComboBoxPrivate::setCurrentTheme(const Theme *theme)
{
Q_ASSERT(theme != nullptr);
const QString themeID = theme->id();
const int themeIndex = q->findData(QVariant(themeID));
q->setCurrentIndex(themeIndex);
}
#include "moc_themecombobox.cpp"
diff --git a/messagelist/src/utils/themecombobox.h b/messagelist/src/utils/themecombobox.h
index e20ae74e..040cd8da 100644
--- a/messagelist/src/utils/themecombobox.h
+++ b/messagelist/src/utils/themecombobox.h
@@ -1,65 +1,65 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_THEMECOMBOBOX_H
#define MESSAGELIST_UTILS_THEMECOMBOBOX_H
#include <messagelist_export.h>
-#include <KComboBox>
+#include <QComboBox>
#include <collection.h>
namespace MessageList {
namespace Core {
class StorageModel;
class Theme;
} // namespace Core
namespace Utils {
class ThemeComboBoxPrivate;
/**
- * A specialized KComboBox that lists all message list themes.
+ * A specialized QComboBox that lists all message list themes.
*/
-class MESSAGELIST_EXPORT ThemeComboBox : public KComboBox
+class MESSAGELIST_EXPORT ThemeComboBox : public QComboBox
{
Q_OBJECT
public:
explicit ThemeComboBox(QWidget *parent);
~ThemeComboBox();
QString currentTheme() const;
void writeDefaultConfig() const;
void writeStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool isPrivateSetting) const;
void writeStorageModelConfig(const QString &id, bool isPrivateSetting) const;
void readStorageModelConfig(const Akonadi::Collection &col, bool &isPrivateSetting);
void readStorageModelConfig(MessageList::Core::StorageModel *storageModel, bool &isPrivateSetting);
public Q_SLOTS:
void slotLoadThemes();
void selectDefault();
private:
ThemeComboBoxPrivate *const d;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_THEMECOMBOBOX_H
diff --git a/messagelist/src/utils/themecombobox_p.h b/messagelist/src/utils/themecombobox_p.h
index 45487df8..81eb7958 100644
--- a/messagelist/src/utils/themecombobox_p.h
+++ b/messagelist/src/utils/themecombobox_p.h
@@ -1,50 +1,50 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_THEMECOMBOBOX_P_H
#define MESSAGELIST_UTILS_THEMECOMBOBOX_P_H
namespace MessageList {
namespace Core {
class Theme;
} // namespace Core
namespace Utils {
class ThemeComboBox;
class ThemeComboBoxPrivate
{
public:
explicit ThemeComboBoxPrivate(ThemeComboBox *owner)
: q(owner)
{
}
ThemeComboBox *const q;
/**
* Refresh list of themes in the combobox.
*/
void slotLoadThemes();
void setCurrentTheme(const Core::Theme *theme);
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_THEMECOMBOBOX_P_H
diff --git a/messagelist/src/utils/themeconfigbutton.cpp b/messagelist/src/utils/themeconfigbutton.cpp
index 9d6a502c..3bb1805a 100644
--- a/messagelist/src/utils/themeconfigbutton.cpp
+++ b/messagelist/src/utils/themeconfigbutton.cpp
@@ -1,83 +1,85 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "utils/themeconfigbutton.h"
#include "core/theme.h"
#include "utils/themecombobox.h"
#include "utils/themecombobox_p.h"
#include "utils/configurethemesdialog.h"
#include "core/manager.h"
#include <KLocalizedString>
using namespace MessageList::Core;
using namespace MessageList::Utils;
class MessageList::Utils::ThemeConfigButtonPrivate
{
public:
ThemeConfigButtonPrivate(ThemeConfigButton *owner)
: q(owner)
, mThemeComboBox(nullptr)
{
}
ThemeConfigButton *const q;
const ThemeComboBox *mThemeComboBox = nullptr;
void slotConfigureThemes();
};
void ThemeConfigButtonPrivate::slotConfigureThemes()
{
QString currentThemeID;
if (mThemeComboBox != nullptr) {
currentThemeID = mThemeComboBox->currentTheme();
}
ConfigureThemesDialog *dialog = new ConfigureThemesDialog(q->window());
dialog->selectTheme(currentThemeID);
QObject::connect(dialog, &ConfigureThemesDialog::okClicked, q, &ThemeConfigButton::configureDialogCompleted);
dialog->show();
}
ThemeConfigButton::ThemeConfigButton(QWidget *parent, const ThemeComboBox *themeComboBox)
: QPushButton(i18n("Configure..."), parent)
, d(new ThemeConfigButtonPrivate(this))
{
d->mThemeComboBox = themeComboBox;
- connect(this, &ThemeConfigButton::pressed, this, [this]() { d->slotConfigureThemes(); });
+ connect(this, &ThemeConfigButton::pressed, this, [this]() {
+ d->slotConfigureThemes();
+ });
//Keep theme combo up-to-date with any changes made in the configure dialog.
if (d->mThemeComboBox != nullptr) {
connect(this, &ThemeConfigButton::configureDialogCompleted,
d->mThemeComboBox, &ThemeComboBox::slotLoadThemes);
}
setEnabled(Manager::instance());
}
ThemeConfigButton::~ThemeConfigButton()
{
delete d;
}
#include "moc_themeconfigbutton.cpp"
diff --git a/messagelist/src/utils/themeconfigbutton.h b/messagelist/src/utils/themeconfigbutton.h
index a5f153c3..31228d3d 100644
--- a/messagelist/src/utils/themeconfigbutton.h
+++ b/messagelist/src/utils/themeconfigbutton.h
@@ -1,58 +1,58 @@
/* Copyright 2009 James Bendig <james@imptalk.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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGELIST_UTILS_THEMECONFIGBUTTON_H
#define MESSAGELIST_UTILS_THEMECONFIGBUTTON_H
#include <messagelist_export.h>
#include <QPushButton>
namespace MessageList {
namespace Utils {
class ThemeComboBox;
class ThemeConfigButtonPrivate;
/**
* A specialized QPushButton that displays the theme
* configure dialog when pressed.
*/
class MESSAGELIST_EXPORT ThemeConfigButton : public QPushButton
{
Q_OBJECT
public:
/** Constructor.
* @param parent The parent widget for the button.
* @param themeComboBox Optional ThemeComboBox to be kept in sync
* with changes made by the configure dialog.
*/
explicit ThemeConfigButton(QWidget *parent, const ThemeComboBox *themeComboBox = nullptr);
~ThemeConfigButton();
Q_SIGNALS:
/**
* A signal emitted when configure dialog has been successfully completed.
*/
void configureDialogCompleted();
private:
ThemeConfigButtonPrivate *const d;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_THEMECONFIGBUTTON_H
diff --git a/messagelist/src/utils/themeeditor.cpp b/messagelist/src/utils/themeeditor.cpp
index 403522bd..b79666fd 100644
--- a/messagelist/src/utils/themeeditor.cpp
+++ b/messagelist/src/utils/themeeditor.cpp
@@ -1,1561 +1,1560 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* This program is free softhisare; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Softhisare 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 Softhisare
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*******************************************************************************/
#include "utils/themeeditor.h"
#include "core/theme.h"
#include "core/groupheaderitem.h"
#include "core/messageitem.h"
#include "core/modelinvariantrowmapper.h"
#include "core/manager.h"
#include "utils/comboboxutils.h"
#include <Akonadi/KMime/MessageStatus>
#include <KTextEdit>
#include <QActionGroup>
#include <QCheckBox>
#include <QCursor>
#include <QDrag>
#include <QGridLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QLabel>
#include <QMouseEvent>
#include <QTreeWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QPixmap>
#include <QPushButton>
#include <QStringList>
#include <QMimeData>
#include <QColorDialog>
-#include <KComboBox>
+#include <QComboBox>
#include <KLineEdit>
#include <KLocalizedString>
#include <QMenu>
#include <KIconLoader>
#include <KPluralHandlingSpinBox>
#include <time.h> // for time_t
#include <QDialogButtonBox>
#include <QVBoxLayout>
using namespace MessageList::Utils;
using namespace MessageList::Core;
static const char gThemeContentItemTypeDndMimeDataFormat[] = "application/x-kmail-messagelistview-theme-contentitem-type";
ThemeColumnPropertiesDialog::ThemeColumnPropertiesDialog(QWidget *parent, Theme::Column *column, const QString &title)
: QDialog(parent)
, mColumn(column)
{
- setWindowModality(Qt::ApplicationModal); // FIXME: Sure ?
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ThemeColumnPropertiesDialog::reject);
setWindowTitle(title);
QWidget *base = new QWidget(this);
mainLayout->addWidget(base);
mainLayout->addWidget(buttonBox);
QGridLayout *g = new QGridLayout(base);
QLabel *l = new QLabel(i18nc("@label:textbox Property name", "Name:"), base);
g->addWidget(l, 0, 0);
mNameEdit = new KLineEdit(base);
mNameEdit->setToolTip(i18n("The label that will be displayed in the column header."));
g->addWidget(mNameEdit, 0, 1);
l = new QLabel(i18n("Header click sorts messages:"), base);
g->addWidget(l, 1, 0);
- mMessageSortingCombo = new KComboBox(base);
+ mMessageSortingCombo = new QComboBox(base);
mMessageSortingCombo->setToolTip(i18n("The sorting order that clicking on this column header will switch to."));
g->addWidget(mMessageSortingCombo, 1, 1);
mVisibleByDefaultCheck = new QCheckBox(i18n("Visible by default"), base);
mVisibleByDefaultCheck->setToolTip(i18n("Check this if this column should be visible when the theme is selected."));
g->addWidget(mVisibleByDefaultCheck, 2, 1);
mIsSenderOrReceiverCheck = new QCheckBox(i18n("Contains \"Sender or Receiver\" field"), base);
mIsSenderOrReceiverCheck->setToolTip(i18n("Check this if this column label should be updated depending on the folder \"inbound\"/\"outbound\" type."));
g->addWidget(mIsSenderOrReceiverCheck, 3, 1);
g->setColumnStretch(1, 1);
g->setRowStretch(10, 1);
connect(okButton, &QPushButton::clicked, this, &ThemeColumnPropertiesDialog::slotOkButtonClicked);
// Display the current settings
mNameEdit->setText(mColumn->label());
mVisibleByDefaultCheck->setChecked(mColumn->visibleByDefault());
mIsSenderOrReceiverCheck->setChecked(mColumn->isSenderOrReceiver());
ComboBoxUtils::fillIntegerOptionCombo(mMessageSortingCombo, SortOrder::enumerateMessageSortingOptions(Aggregation::PerfectReferencesAndSubject));
ComboBoxUtils::setIntegerOptionComboValue(mMessageSortingCombo, mColumn->messageSorting());
}
void ThemeColumnPropertiesDialog::slotOkButtonClicked()
{
QString text = mNameEdit->text();
if (text.isEmpty()) {
text = i18n("Unnamed Column");
}
mColumn->setLabel(text);
mColumn->setVisibleByDefault(mVisibleByDefaultCheck->isChecked());
mColumn->setIsSenderOrReceiver(mIsSenderOrReceiverCheck->isChecked());
mColumn->setMessageSorting(
static_cast< SortOrder::MessageSorting >(
ComboBoxUtils::getIntegerOptionComboValue(mMessageSortingCombo, SortOrder::NoMessageSorting)
)
);
accept();
}
ThemeContentItemSourceLabel::ThemeContentItemSourceLabel(QWidget *parent, Theme::ContentItem::Type type)
: QLabel(parent)
, mType(type)
{
setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
}
ThemeContentItemSourceLabel::~ThemeContentItemSourceLabel()
{
}
MessageList::Core::Theme::ContentItem::Type ThemeContentItemSourceLabel::type() const
{
return mType;
}
void ThemeContentItemSourceLabel::mousePressEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) {
mMousePressPoint = e->pos();
}
}
void ThemeContentItemSourceLabel::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton) {
const QPoint diff = mMousePressPoint - e->pos();
if (diff.manhattanLength() > 4) {
startDrag();
}
}
}
void ThemeContentItemSourceLabel::startDrag()
{
//QPixmap pix = QPixmap::grabWidget( this );
//QPixmap alpha( pix.width(), pix.height() );
//alpha.fill(0x0f0f0f0f);
//pix.setAlphaChannel( alpha ); // <-- this crashes... no alpha for dragged pixmap :(
QMimeData *data = new QMimeData();
QByteArray arry;
arry.resize(sizeof(Theme::ContentItem::Type));
*((Theme::ContentItem::Type *)arry.data()) = mType;
data->setData(QLatin1String(gThemeContentItemTypeDndMimeDataFormat), arry);
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
//drag->setPixmap( pix );
//drag->setHotSpot( mapFromGlobal( QCursor::pos() ) );
drag->exec(Qt::CopyAction, Qt::CopyAction);
}
ThemePreviewDelegate::ThemePreviewDelegate(QAbstractItemView *parent)
: ThemeDelegate(parent)
{
mRowMapper = new ModelInvariantRowMapper();
mSampleGroupHeaderItem = new GroupHeaderItem(i18n("Message Group"));
mSampleGroupHeaderItem->setDate(time(nullptr));
mSampleGroupHeaderItem->setMaxDate(time(nullptr) + 31337);
mSampleGroupHeaderItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long"));
mSampleMessageItem = new FakeItem();
mSampleMessageItem->setDate(time(nullptr));
mSampleMessageItem->setSize(0x31337);
mSampleMessageItem->setMaxDate(time(nullptr) + 31337);
mSampleMessageItem->setSender(i18n("Sender"));
mSampleMessageItem->setReceiver(i18n("Receiver"));
mSampleMessageItem->setSubject(i18n("Very long subject very long subject very long subject very long subject very long subject very long"));
mSampleMessageItem->setSignatureState(MessageItem::FullySigned);
mSampleMessageItem->setEncryptionState(MessageItem::FullyEncrypted);
QList< MessageItem::Tag * > list;
list.append(new MessageItem::Tag(SmallIcon(QStringLiteral("feed-subscribe")), i18n("Sample Tag 1"), QString()));
list.append(new MessageItem::Tag(SmallIcon(QStringLiteral("feed-subscribe")), i18n("Sample Tag 2"), QString()));
list.append(new MessageItem::Tag(SmallIcon(QStringLiteral("feed-subscribe")), i18n("Sample Tag 3"), QString()));
mSampleMessageItem->setFakeTags(list);
mRowMapper->createModelInvariantIndex(0, mSampleMessageItem);
mSampleGroupHeaderItem->rawAppendChildItem(mSampleMessageItem);
mSampleMessageItem->setParent(mSampleGroupHeaderItem);
Akonadi::MessageStatus stat;
stat.fromQInt32(0x7fffffff);
stat.setQueued(false);
stat.setSent(false);
stat.setSpam(true);
stat.setWatched(true);
stat.setHasInvitation();
//stat.setHasAttachment( false );
mSampleMessageItem->setStatus(stat);
}
ThemePreviewDelegate::~ThemePreviewDelegate()
{
delete mSampleGroupHeaderItem;
//delete mSampleMessageItem; (deleted by the parent)
delete mRowMapper;
}
Item *ThemePreviewDelegate::itemFromIndex(const QModelIndex &index) const
{
if (index.parent().isValid()) {
return mSampleMessageItem;
}
return mSampleGroupHeaderItem;
}
ThemePreviewWidget::ThemePreviewWidget(QWidget *parent)
: QTreeWidget(parent)
, mTheme(nullptr)
{
mSelectedThemeContentItem = nullptr;
mSelectedThemeColumn = nullptr;
mFirstShow = true;
mReadOnly = false;
mDelegate = new ThemePreviewDelegate(this);
setItemDelegate(mDelegate);
setRootIsDecorated(false);
viewport()->setAcceptDrops(true);
header()->setContextMenuPolicy(Qt::CustomContextMenu); // make sure it's true
connect(header(), &QWidget::customContextMenuRequested,
this, &ThemePreviewWidget::slotHeaderContextMenuRequested);
mGroupHeaderSampleItem = new QTreeWidgetItem(this);
mGroupHeaderSampleItem->setText(0, QString());
mGroupHeaderSampleItem->setFlags(Qt::ItemIsEnabled);
QTreeWidgetItem *m = new QTreeWidgetItem(mGroupHeaderSampleItem);
m->setText(0, QString());
mGroupHeaderSampleItem->setExpanded(true);
header()->setSectionsMovable(false);
}
void ThemePreviewWidget::changeEvent(QEvent *event)
{
if (event->type() == QEvent::FontChange) {
mDelegate->generalFontChanged();
}
QTreeWidget::changeEvent(event);
}
ThemePreviewWidget::~ThemePreviewWidget()
{
}
QSize ThemePreviewWidget::sizeHint() const
{
return QSize(350, 180);
}
void ThemePreviewWidget::setReadOnly(bool readOnly)
{
mReadOnly = readOnly;
}
void ThemePreviewWidget::applyThemeColumnWidths()
{
if (!mTheme) {
return;
}
const QList< Theme::Column * > &columns = mTheme->columns();
if (columns.isEmpty()) {
viewport()->update(); // trigger a repaint
return;
}
// Now we want to distribute the available width on all the columns.
// The algorithm used here is very similar to the one used in View::applyThemeColumns().
// It just takes care of ALL the columns instead of the visible ones.
QList< Theme::Column * >::ConstIterator it;
// Gather size hints for all sections.
int idx = 0;
int totalVisibleWidthHint = 0;
QList< Theme::Column * >::ConstIterator end(columns.constEnd());
for (it = columns.constBegin(); it != end; ++it) {
totalVisibleWidthHint += mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
idx++;
}
if (totalVisibleWidthHint < 16) {
totalVisibleWidthHint = 16; // be reasonable
}
// Now we can compute proportional widths.
idx = 0;
QList< int > realWidths;
realWidths.reserve(columns.count());
int totalVisibleWidth = 0;
end = columns.constEnd();
for (it = columns.constBegin(); it != end; ++it) {
int hintWidth = mDelegate->sizeHintForItemTypeAndColumn(Item::Message, idx).width();
int realWidth;
if ((*it)->containsTextItems()) {
// the column contains text items, it should get more space
realWidth = ((hintWidth * viewport()->width()) / totalVisibleWidthHint) - 2; // -2 is heuristic
if (realWidth < (hintWidth + 2)) {
realWidth = hintWidth + 2; // can't be less
}
} else {
// the column contains no text items, it should get just a little bit more than its sizeHint().
realWidth = hintWidth + 2;
}
realWidths.append(realWidth);
totalVisibleWidth += realWidth;
idx++;
}
idx = 0;
totalVisibleWidth += 4; // account for some view's border
if (totalVisibleWidth < viewport()->width()) {
// give the additional space to the text columns
// also give more space to the first ones and less space to the last ones
int available = viewport()->width() - totalVisibleWidth;
for (it = columns.begin(); it != columns.end(); ++it) {
if (((*it)->visibleByDefault() || (idx == 0)) && (*it)->containsTextItems()) {
// give more space to this column
available >>= 1; // eat half of the available space
realWidths[ idx ] += available; // and give it to this column
}
idx++;
}
// if any space is still available, give it to the first column
if (available) {
realWidths[ 0 ] += available;
}
}
idx = 0;
// We're ready.
// Assign widths. Hide the sections that are not visible by default, show the other ones.
for (it = columns.begin(); it != columns.end(); ++it) {
header()->resizeSection(idx, realWidths[ idx ]);
idx++;
}
#if 0
if (mTheme->viewHeaderPolicy() == Theme::NeverShowHeader) {
header()->hide();
} else {
header()->show();
}
#endif
}
void ThemePreviewWidget::setTheme(Theme *theme)
{
bool themeChanged = theme != mTheme;
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = QPoint();
mDropIndicatorPoint2 = QPoint();
mTheme = theme;
mDelegate->setTheme(theme);
if (!mTheme) {
return;
}
mGroupHeaderSampleItem->setExpanded(true);
const QList< Theme::Column * > &columns = mTheme->columns();
setColumnCount(columns.count());
QStringList headerLabels;
headerLabels.reserve(columns.count());
QList< Theme::Column * >::ConstIterator end(columns.constEnd());
for (QList< Theme::Column * >::ConstIterator it = columns.constBegin(); it != end; ++it) {
QString label = (*it)->label();
if ((*it)->visibleByDefault()) {
label += QStringLiteral(" (%1)").arg(i18nc("Indicates whether or not a header label is visible", "Visible"));
}
headerLabels.append(label);
}
setHeaderLabels(headerLabels);
if (themeChanged) {
applyThemeColumnWidths();
}
viewport()->update(); // trigger a repaint
}
void ThemePreviewWidget::internalHandleDragEnterEvent(QDragEnterEvent *e)
{
e->ignore();
if (!e->mimeData()) {
return;
}
if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
return;
}
e->accept();
}
void ThemePreviewWidget::showEvent(QShowEvent *e)
{
QTreeWidget::showEvent(e);
if (mFirstShow) {
// Make sure we re-apply column widths the first time we're shown.
// The first "apply" call was made while the widget was still hidden and
// almost surely had wrong sizes.
applyThemeColumnWidths();
mFirstShow = false;
}
}
void ThemePreviewWidget::dragEnterEvent(QDragEnterEvent *e)
{
internalHandleDragEnterEvent(e);
mThemeSelectedContentItemRect = QRect();
viewport()->update(); // trigger a repaint
}
void ThemePreviewWidget::internalHandleDragMoveEvent(QDragMoveEvent *e)
{
e->ignore();
if (mReadOnly) {
return;
}
if (!e->mimeData()) {
return;
}
if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
return;
}
QByteArray arry = e->mimeData()->data(QLatin1String(gThemeContentItemTypeDndMimeDataFormat));
if (arry.size() != sizeof(Theme::ContentItem::Type)) {
return; // ugh
}
Theme::ContentItem::Type type = *((Theme::ContentItem::Type *)arry.data());
if (!computeContentItemInsertPosition(e->pos(), type)) {
return;
}
e->accept();
}
void ThemePreviewWidget::dragMoveEvent(QDragMoveEvent *e)
{
if (mReadOnly) {
return;
}
internalHandleDragMoveEvent(e);
mThemeSelectedContentItemRect = QRect();
viewport()->update(); // trigger a repaint
}
void ThemePreviewWidget::dropEvent(QDropEvent *e)
{
mDropIndicatorPoint1 = mDropIndicatorPoint2;
e->ignore();
if (mReadOnly) {
return;
}
if (!e->mimeData()) {
return;
}
if (!e->mimeData()->hasFormat(QLatin1String(gThemeContentItemTypeDndMimeDataFormat))) {
return;
}
QByteArray arry = e->mimeData()->data(QLatin1String(gThemeContentItemTypeDndMimeDataFormat));
if (arry.size() != sizeof(Theme::ContentItem::Type)) {
return; // ugh
}
Theme::ContentItem::Type type = *((Theme::ContentItem::Type *)arry.data());
if (!computeContentItemInsertPosition(e->pos(), type)) {
viewport()->update();
return;
}
Theme::Row *row = nullptr;
switch (mRowInsertPosition) {
case AboveRow:
row = new Theme::Row();
if (mDelegate->hitItem()->type() == Item::Message) {
const_cast< Theme::Column * >(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex(), row);
} else {
const_cast< Theme::Column * >(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex(), row);
}
break;
case InsideRow:
row = const_cast< Theme::Row * >(mDelegate->hitRow());
break;
case BelowRow:
row = new Theme::Row();
if (mDelegate->hitItem()->type() == Item::Message) {
const_cast< Theme::Column * >(mDelegate->hitColumn())->insertMessageRow(mDelegate->hitRowIndex() + 1, row);
} else {
const_cast< Theme::Column * >(mDelegate->hitColumn())->insertGroupHeaderRow(mDelegate->hitRowIndex() + 1, row);
}
break;
}
if (!row) {
return;
}
Theme::ContentItem *ci = new Theme::ContentItem(type);
if (ci->canBeDisabled()) {
if (ci->isClickable()) {
ci->setSoftenByBlendingWhenDisabled(true); // default to softened
} else {
ci->setHideWhenDisabled(true); // default to hidden
}
}
int idx;
switch (mItemInsertPosition) {
case OnLeftOfItem:
if (!mDelegate->hitContentItem()) {
// bleah
delete ci;
return;
}
idx = mDelegate->hitContentItemRight() \
? row->rightItems().indexOf(const_cast< Theme::ContentItem * >(mDelegate->hitContentItem())) \
: row->leftItems().indexOf(const_cast< Theme::ContentItem * >(mDelegate->hitContentItem()));
if (idx < 0) {
// bleah
delete ci;
return;
}
if (mDelegate->hitContentItemRight()) {
row->insertRightItem(idx + 1, ci);
} else {
row->insertLeftItem(idx, ci);
}
break;
case OnRightOfItem:
if (!mDelegate->hitContentItem()) {
// bleah
delete ci;
return;
}
idx = mDelegate->hitContentItemRight() \
? row->rightItems().indexOf(const_cast< Theme::ContentItem * >(mDelegate->hitContentItem())) \
: row->leftItems().indexOf(const_cast< Theme::ContentItem * >(mDelegate->hitContentItem()));
if (idx < 0) {
// bleah
delete ci;
return;
}
if (mDelegate->hitContentItemRight()) {
row->insertRightItem(idx, ci);
} else {
row->insertLeftItem(idx + 1, ci);
}
break;
case AsLastLeftItem:
row->addLeftItem(ci);
break;
case AsLastRightItem:
row->addRightItem(ci);
break;
case AsFirstLeftItem:
row->insertLeftItem(0, ci);
break;
case AsFirstRightItem:
row->insertRightItem(0, ci);
break;
default: // should never happen
row->addRightItem(ci);
break;
}
e->acceptProposedAction();
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = mDropIndicatorPoint2;
mSelectedThemeContentItem = nullptr;
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
bool ThemePreviewWidget::computeContentItemInsertPosition(const QPoint &pos, Theme::ContentItem::Type type)
{
mDropIndicatorPoint1 = mDropIndicatorPoint2; // this marks the position as invalid
if (!mDelegate->hitTest(pos, false)) {
return false;
}
if (!mDelegate->hitRow()) {
return false;
}
if (mDelegate->hitRowIsMessageRow()) {
if (!Theme::ContentItem::applicableToMessageItems(type)) {
return false;
}
} else {
if (!Theme::ContentItem::applicableToGroupHeaderItems(type)) {
return false;
}
}
QRect rowRect = mDelegate->hitRowRect();
if (pos.y() < rowRect.top() + 3) {
// above a row
mRowInsertPosition = AboveRow;
if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
mDropIndicatorPoint1 = rowRect.topLeft();
mItemInsertPosition = AsLastLeftItem;
} else {
mDropIndicatorPoint1 = rowRect.topRight();
mItemInsertPosition = AsLastRightItem;
}
mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.top());
return true;
}
if (pos.y() > rowRect.bottom() - 3) {
// below a row
mRowInsertPosition = BelowRow;
if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
mDropIndicatorPoint1 = rowRect.bottomLeft();
mItemInsertPosition = AsLastLeftItem;
} else {
mDropIndicatorPoint1 = rowRect.bottomRight();
mItemInsertPosition = AsLastRightItem;
}
mDropIndicatorPoint2 = QPoint(rowRect.left() + (rowRect.width() / 2), rowRect.bottom());
return true;
}
mRowInsertPosition = InsideRow;
if (!mDelegate->hitContentItem()) {
// didn't hit anything... probably no items in the row
if (pos.x() < (rowRect.left() + (rowRect.width() / 2))) {
mItemInsertPosition = AsLastLeftItem;
mDropIndicatorPoint1 = QPoint(rowRect.left(), rowRect.top());
mDropIndicatorPoint2 = QPoint(rowRect.left(), rowRect.bottom());
} else {
mItemInsertPosition = AsLastRightItem;
mDropIndicatorPoint1 = QPoint(rowRect.right(), rowRect.top());
mDropIndicatorPoint2 = QPoint(rowRect.right(), rowRect.bottom());
}
return true;
}
// hit something, maybe inexactly
QRect itemRect = mDelegate->hitContentItemRect();
if (!itemRect.contains(pos)) {
// inexact hit: outside an item
if (pos.x() > itemRect.right()) {
// right side of an item
if (mDelegate->hitRow()->rightItems().count() < 1) {
// between the last left item and the right side
if (pos.x() > (itemRect.right() + ((rowRect.right() - itemRect.right()) / 2))) {
// first/last right item
mItemInsertPosition = AsFirstRightItem;
mDropIndicatorPoint1 = rowRect.topRight();
mDropIndicatorPoint2 = rowRect.bottomRight();
}
return true;
}
// either there were some right items (so the theme delegate knows that the reported item is the closest)
// or there were no right items but the position is closest to the left item than the right row end
mItemInsertPosition = OnRightOfItem;
mDropIndicatorPoint1 = itemRect.topRight();
mDropIndicatorPoint2 = itemRect.bottomRight();
return true;
}
// left side of an item
if (mDelegate->hitRow()->leftItems().count() < 1) {
// between the left side and the leftmost right item
if (pos.x() < (itemRect.left() - ((itemRect.left() - rowRect.left()) / 2))) {
mItemInsertPosition = AsFirstLeftItem;
mDropIndicatorPoint1 = rowRect.topLeft();
mDropIndicatorPoint2 = rowRect.bottomLeft();
return true;
}
}
mItemInsertPosition = OnLeftOfItem;
mDropIndicatorPoint1 = itemRect.topLeft();
mDropIndicatorPoint2 = itemRect.bottomLeft();
return true;
}
// exact hit
if (pos.x() < (itemRect.left() + (itemRect.width() / 2))) {
// left side
mItemInsertPosition = OnLeftOfItem;
mDropIndicatorPoint1 = itemRect.topLeft();
mDropIndicatorPoint2 = itemRect.bottomLeft();
return true;
}
// right side
mItemInsertPosition = OnRightOfItem;
mDropIndicatorPoint1 = itemRect.topRight();
mDropIndicatorPoint2 = itemRect.bottomRight();
return true;
}
void ThemePreviewWidget::mouseMoveEvent(QMouseEvent *e)
{
if (!(mSelectedThemeContentItem && (e->buttons() & Qt::LeftButton)) || mReadOnly) {
QTreeWidget::mouseMoveEvent(e);
return;
}
if (mSelectedThemeContentItem != mDelegate->hitContentItem()) {
QTreeWidget::mouseMoveEvent(e);
return; // ugh.. something weird happened
}
// starting a drag ?
const QPoint diff = e->pos() - mMouseDownPoint;
if (diff.manhattanLength() <= 4) {
QTreeWidget::mouseMoveEvent(e);
return; // ugh.. something weird happened
}
// starting a drag
QMimeData *data = new QMimeData();
QByteArray arry;
arry.resize(sizeof(Theme::ContentItem::Type));
*((Theme::ContentItem::Type *)arry.data()) = mSelectedThemeContentItem->type();
data->setData(QLatin1String(gThemeContentItemTypeDndMimeDataFormat), arry);
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
// remove the Theme::ContentItem from the Theme
if (mDelegate->hitContentItemRight()) {
const_cast< Theme::Row * >(mDelegate->hitRow())->removeRightItem(mSelectedThemeContentItem);
} else {
const_cast< Theme::Row * >(mDelegate->hitRow())->removeLeftItem(mSelectedThemeContentItem);
}
delete mSelectedThemeContentItem;
if (mDelegate->hitRow()->rightItems().isEmpty() && mDelegate->hitRow()->leftItems().isEmpty()) {
if (mDelegate->hitItem()->type() == Item::Message) {
if (mDelegate->hitColumn()->messageRows().count() > 1) {
const_cast< Theme::Column * >(mDelegate->hitColumn())->removeMessageRow(const_cast< Theme::Row * >(mDelegate->hitRow()));
delete mDelegate->hitRow();
}
} else {
if (mDelegate->hitColumn()->groupHeaderRows().count() > 1) {
const_cast< Theme::Column * >(mDelegate->hitColumn())->removeGroupHeaderRow(const_cast< Theme::Row * >(mDelegate->hitRow()));
delete mDelegate->hitRow();
}
}
}
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = mDropIndicatorPoint2;
setTheme(mTheme); // this will reset theme cache and trigger a global update
// and do drag
drag->exec(Qt::CopyAction, Qt::CopyAction);
}
void ThemePreviewWidget::mousePressEvent(QMouseEvent *e)
{
if (mReadOnly) {
QTreeWidget::mousePressEvent(e);
return;
}
mMouseDownPoint = e->pos();
if (mDelegate->hitTest(mMouseDownPoint)) {
mSelectedThemeContentItem = const_cast< Theme::ContentItem * >(mDelegate->hitContentItem());
mThemeSelectedContentItemRect = mSelectedThemeContentItem ? mDelegate->hitContentItemRect() : QRect();
} else {
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
}
QTreeWidget::mousePressEvent(e);
viewport()->update();
if (e->button() == Qt::RightButton) {
QMenu menu;
if (mSelectedThemeContentItem) {
menu.addSection(Theme::ContentItem::description(mSelectedThemeContentItem->type()));
if (mSelectedThemeContentItem->displaysText()) {
QAction *act = menu.addAction(i18nc("@action:inmenu soften the text color", "Soften"));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->softenByBlending());
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotSoftenActionTriggered);
QMenu *childmenu = new QMenu(&menu);
act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Bold"));
act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsBold)));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->isBold());
act = childmenu->addAction(i18nc("@action:inmenu Font setting", "Italic"));
act->setData(QVariant(static_cast<int>(Theme::ContentItem::IsItalic)));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->isItalic());
connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotFontMenuTriggered);
menu.addMenu(childmenu)->setText(i18n("Font"));
}
if (mSelectedThemeContentItem->canUseCustomColor()) {
QMenu *childmenu = new QMenu(&menu);
QActionGroup *grp = new QActionGroup(childmenu);
QAction *act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Default"));
act->setData(QVariant(static_cast< int >(0)));
act->setCheckable(true);
act->setChecked(!mSelectedThemeContentItem->useCustomColor());
grp->addAction(act);
act = childmenu->addAction(i18nc("@action:inmenu Foreground color setting", "Custom..."));
act->setData(QVariant(static_cast< int >(Theme::ContentItem::UseCustomColor)));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->useCustomColor());
grp->addAction(act);
connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotForegroundColorMenuTriggered);
menu.addMenu(childmenu)->setText(i18n("Foreground Color"));
}
if (mSelectedThemeContentItem->canBeDisabled()) {
QMenu *childmenu = new QMenu(&menu);
QActionGroup *grp = new QActionGroup(childmenu);
QAction *act = childmenu->addAction(i18nc("Hide a mark if the mail does not have the attribute, e.g. Important mark on a non important mail", "Hide"));
act->setData(QVariant(static_cast< int >(Theme::ContentItem::HideWhenDisabled)));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->hideWhenDisabled());
grp->addAction(act);
act = childmenu->addAction(i18nc("Keep a empty space in the list if the mail does not have the attribute, e.g. Important mark on a non important mail", "Keep Empty Space"));
act->setData(QVariant(static_cast< int >(0)));
act->setCheckable(true);
act->setChecked(!(mSelectedThemeContentItem->softenByBlendingWhenDisabled() || mSelectedThemeContentItem->hideWhenDisabled()));
grp->addAction(act);
act = childmenu->addAction(i18nc("Show the icon softened in the list if the mail does not have the attribute, e.g. Important mark on a non important mail", "Keep Softened Icon"));
act->setData(QVariant(static_cast< int >(Theme::ContentItem::SoftenByBlendingWhenDisabled)));
act->setCheckable(true);
act->setChecked(mSelectedThemeContentItem->softenByBlendingWhenDisabled());
grp->addAction(act);
connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotDisabledFlagsMenuTriggered);
menu.addMenu(childmenu)->setText(i18n("When Disabled"));
}
}
if (mDelegate->hitItem()) {
if (mDelegate->hitItem()->type() == Item::GroupHeader) {
menu.addSection(i18n("Group Header"));
// Background color (mode) submenu
QMenu *childmenu = new QMenu(&menu);
QActionGroup *grp = new QActionGroup(childmenu);
QAction *act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "None"));
act->setData(QVariant(static_cast< int >(Theme::Transparent)));
act->setCheckable(true);
act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::Transparent);
grp->addAction(act);
act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Automatic"));
act->setData(QVariant(static_cast< int >(Theme::AutoColor)));
act->setCheckable(true);
act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::AutoColor);
grp->addAction(act);
act = childmenu->addAction(i18nc("@action:inmenu Group header background color setting", "Custom..."));
act->setData(QVariant(static_cast< int >(Theme::CustomColor)));
act->setCheckable(true);
act->setChecked(mTheme->groupHeaderBackgroundMode() == Theme::CustomColor);
grp->addAction(act);
connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered);
menu.addMenu(childmenu)->setText(i18n("Background Color"));
// Background style submenu
childmenu = new QMenu(&menu);
grp = new QActionGroup(childmenu);
QList< QPair< QString, int > > styles = Theme::enumerateGroupHeaderBackgroundStyles();
QList< QPair< QString, int > >::ConstIterator end(styles.constEnd());
for (QList< QPair< QString, int > >::ConstIterator it = styles.constBegin(); it != end; ++it) {
act = childmenu->addAction((*it).first);
act->setData(QVariant((*it).second));
act->setCheckable(true);
act->setChecked(mTheme->groupHeaderBackgroundStyle() == static_cast< Theme::GroupHeaderBackgroundStyle >((*it).second));
grp->addAction(act);
}
connect(childmenu, &QMenu::triggered, this, &ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered);
act = menu.addMenu(childmenu);
act->setText(i18n("Background Style"));
if (mTheme->groupHeaderBackgroundMode() == Theme::Transparent) {
act->setEnabled(false);
}
}
}
if (menu.isEmpty()) {
return;
}
menu.exec(viewport()->mapToGlobal(e->pos()));
}
}
void ThemePreviewWidget::slotDisabledFlagsMenuTriggered(QAction *act)
{
if (!mSelectedThemeContentItem) {
return;
}
bool ok;
const int flags = act->data().toInt(&ok);
if (!ok) {
return;
}
mSelectedThemeContentItem->setHideWhenDisabled(flags == Theme::ContentItem::HideWhenDisabled);
mSelectedThemeContentItem->setSoftenByBlendingWhenDisabled(flags == Theme::ContentItem::SoftenByBlendingWhenDisabled);
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotForegroundColorMenuTriggered(QAction *act)
{
if (!mSelectedThemeContentItem) {
return;
}
bool ok;
const int flag = act->data().toInt(&ok);
if (!ok) {
return;
}
if (flag == 0) {
mSelectedThemeContentItem->setUseCustomColor(false);
setTheme(mTheme); // this will reset theme cache and trigger a global update
return;
}
QColor clr;
clr = QColorDialog::getColor(mSelectedThemeContentItem->customColor(), this);
if (!clr.isValid()) {
return;
}
mSelectedThemeContentItem->setCustomColor(clr);
mSelectedThemeContentItem->setUseCustomColor(true);
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotSoftenActionTriggered(bool)
{
if (!mSelectedThemeContentItem) {
return;
}
mSelectedThemeContentItem->setSoftenByBlending(!mSelectedThemeContentItem->softenByBlending());
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotFontMenuTriggered(QAction *act)
{
if (!mSelectedThemeContentItem) {
return;
}
bool ok;
const int flag = act->data().toInt(&ok);
if (!ok) {
return;
}
if (flag == Theme::ContentItem::IsBold && mSelectedThemeContentItem->isBold() != act->isChecked()) {
mSelectedThemeContentItem->setBold(act->isChecked());
setTheme(mTheme);
} else if (flag == Theme::ContentItem::IsItalic && mSelectedThemeContentItem->isItalic() != act->isChecked()) {
mSelectedThemeContentItem->setItalic(act->isChecked());
setTheme(mTheme);
}
}
void ThemePreviewWidget::slotGroupHeaderBackgroundModeMenuTriggered(QAction *act)
{
bool ok;
Theme::GroupHeaderBackgroundMode mode = static_cast< Theme::GroupHeaderBackgroundMode >(act->data().toInt(&ok));
if (!ok) {
return;
}
switch (mode) {
case Theme::Transparent:
mTheme->setGroupHeaderBackgroundMode(Theme::Transparent);
break;
case Theme::AutoColor:
mTheme->setGroupHeaderBackgroundMode(Theme::AutoColor);
break;
case Theme::CustomColor:
{
QColor clr;
clr = QColorDialog::getColor(mTheme->groupHeaderBackgroundColor(), this);
if (!clr.isValid()) {
return;
}
mTheme->setGroupHeaderBackgroundMode(Theme::CustomColor);
mTheme->setGroupHeaderBackgroundColor(clr);
break;
}
}
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotGroupHeaderBackgroundStyleMenuTriggered(QAction *act)
{
bool ok;
Theme::GroupHeaderBackgroundStyle mode = static_cast< Theme::GroupHeaderBackgroundStyle >(act->data().toInt(&ok));
if (!ok) {
return;
}
mTheme->setGroupHeaderBackgroundStyle(mode);
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::paintEvent(QPaintEvent *e)
{
QTreeWidget::paintEvent(e);
if (
mThemeSelectedContentItemRect.isValid()
|| (mDropIndicatorPoint1 != mDropIndicatorPoint2)
) {
QPainter painter(viewport());
if (mThemeSelectedContentItemRect.isValid()) {
painter.setPen(QPen(Qt::black));
painter.drawRect(mThemeSelectedContentItemRect);
}
if (mDropIndicatorPoint1 != mDropIndicatorPoint2) {
painter.setPen(QPen(Qt::black, 3));
painter.drawLine(mDropIndicatorPoint1, mDropIndicatorPoint2);
}
}
}
void ThemePreviewWidget::slotHeaderContextMenuRequested(const QPoint &pos)
{
if (mReadOnly) {
return;
}
QTreeWidgetItem *hitem = headerItem();
if (!hitem) {
return; // ooops
}
int col = header()->logicalIndexAt(pos);
if (col < 0) {
return;
}
if (col >= mTheme->columns().count()) {
return;
}
mSelectedThemeColumn = mTheme->column(col);
if (!mSelectedThemeColumn) {
return;
}
QMenu menu;
menu.setTitle(mSelectedThemeColumn->label());
QAction *act = menu.addAction(i18n("Column Properties..."));
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotColumnProperties);
act = menu.addAction(i18n("Add Column..."));
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotAddColumn);
act = menu.addAction(i18n("Delete Column"));
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotDeleteColumn);
act->setEnabled(col > 0);
menu.addSeparator();
act = menu.addAction(i18n("Move Column to Left"));
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToLeft);
act->setEnabled(col > 0);
act = menu.addAction(i18n("Move Column to Right"));
connect(act, &QAction::triggered, this, &ThemePreviewWidget::slotMoveColumnToRight);
act->setEnabled(col < mTheme->columns().count() - 1);
menu.exec(header()->mapToGlobal(pos));
}
void ThemePreviewWidget::slotMoveColumnToLeft()
{
if (!mSelectedThemeColumn) {
return;
}
const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
mTheme->moveColumn(columnIndex, columnIndex - 1);
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotMoveColumnToRight()
{
if (!mSelectedThemeColumn) {
return;
}
const int columnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
mTheme->moveColumn(columnIndex, columnIndex + 1);
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
void ThemePreviewWidget::slotAddColumn()
{
int newColumnIndex = mTheme->columns().count();
if (mSelectedThemeColumn) {
newColumnIndex = mTheme->columns().indexOf(mSelectedThemeColumn);
if (newColumnIndex < 0) {
newColumnIndex = mTheme->columns().count();
} else {
newColumnIndex++;
}
}
mSelectedThemeColumn = new Theme::Column();
mSelectedThemeColumn->setLabel(i18n("New Column"));
mSelectedThemeColumn->setVisibleByDefault(true);
mSelectedThemeColumn->addMessageRow(new Theme::Row());
mSelectedThemeColumn->addGroupHeaderRow(new Theme::Row());
ThemeColumnPropertiesDialog *dlg
= new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Add New Column"));
if (dlg->exec() == QDialog::Accepted) {
mTheme->insertColumn(newColumnIndex, mSelectedThemeColumn);
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = mDropIndicatorPoint2;
setTheme(mTheme); // this will reset theme cache and trigger a global update
} else {
delete mSelectedThemeColumn;
mSelectedThemeColumn = nullptr;
}
delete dlg;
}
void ThemePreviewWidget::slotColumnProperties()
{
if (!mSelectedThemeColumn) {
return;
}
ThemeColumnPropertiesDialog *dlg
= new ThemeColumnPropertiesDialog(this, mSelectedThemeColumn, i18n("Column Properties"));
if (dlg->exec() == QDialog::Accepted) {
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = mDropIndicatorPoint2;
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
delete dlg;
}
void ThemePreviewWidget::slotDeleteColumn()
{
if (!mSelectedThemeColumn) {
return;
}
const int idx = mTheme->columns().indexOf(mSelectedThemeColumn);
if (idx < 1) { // first column can't be deleted
return;
}
mTheme->removeColumn(mSelectedThemeColumn);
delete mSelectedThemeColumn;
mSelectedThemeColumn = nullptr;
mSelectedThemeContentItem = nullptr;
mThemeSelectedContentItemRect = QRect();
mDropIndicatorPoint1 = mDropIndicatorPoint2;
setTheme(mTheme); // this will reset theme cache and trigger a global update
}
ThemeEditor::ThemeEditor(QWidget *parent)
: OptionSetEditor(parent)
{
mCurrentTheme = nullptr;
// Appearance tab
QWidget *tab = new QWidget(this);
addTab(tab, i18n("Appearance"));
QGridLayout *tabg = new QGridLayout(tab);
QGroupBox *gb = new QGroupBox(i18n("Content Items"), tab);
tabg->addWidget(gb, 0, 0);
QGridLayout *gblayout = new QGridLayout(gb);
Theme dummyTheme;
ThemeContentItemSourceLabel *cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Subject);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 0);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Date);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 0);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Size);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 0);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Sender);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 1);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::Receiver);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 1);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SenderOrReceiver);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 1);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::MostRecentDate);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 2);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::TagList);
cil->setText(Theme::ContentItem::description(cil->type()));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 2);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::CombinedReadRepliedStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconRepliedAndForwarded));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 3);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ReadStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconNew));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 3);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::RepliedStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconReplied));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 3);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AttachmentStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconAttachment));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 4);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::EncryptionStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullyEncrypted));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 4);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SignatureStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconFullySigned));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 4);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ActionItemStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconActionItem));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 5);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::AnnotationIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconAnnotation));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 5);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::InvitationIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconInvitation));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 5);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ImportantStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconImportant));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 6);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::SpamHamStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconSpam));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 6);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::WatchedIgnoredStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconWatched));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 6);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::ExpandedStateIcon);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconShowMore));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 0, 7);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::VerticalLine);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconVerticalLine));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 1, 7);
cil = new ThemeContentItemSourceLabel(gb, Theme::ContentItem::HorizontalSpacer);
cil->setPixmap(*dummyTheme.pixmap(Theme::IconHorizontalSpacer));
cil->setToolTip(Theme::ContentItem::description(cil->type()));
gblayout->addWidget(cil, 2, 7);
mPreviewWidget = new ThemePreviewWidget(tab);
tabg->addWidget(mPreviewWidget, 1, 0);
QLabel *l = new QLabel(tab);
l->setText(i18n(
"Right click on the header to add or modify columns. Drag the content items and drop them on the columns in order to compose your theme. Right click on the items inside the view for more options."));
l->setWordWrap(true);
l->setAlignment(Qt::AlignCenter);
tabg->addWidget(l, 2, 0);
tabg->setRowStretch(1, 1);
// Advanced tab
tab = new QWidget(this);
addTab(tab, i18nc("@title:tab Advanced theme settings", "Advanced"));
tabg = new QGridLayout(tab);
l = new QLabel(i18n("Header:"), tab);
tabg->addWidget(l, 0, 0);
- mViewHeaderPolicyCombo = new KComboBox(tab);
+ mViewHeaderPolicyCombo = new QComboBox(tab);
tabg->addWidget(mViewHeaderPolicyCombo, 0, 1);
l = new QLabel(i18n("Icon size:"), tab);
tabg->addWidget(l, 1, 0);
mIconSizeSpinBox = new KPluralHandlingSpinBox(tab);
mIconSizeSpinBox->setMinimum(8);
mIconSizeSpinBox->setMaximum(64);
mIconSizeSpinBox->setSuffix(ki18ncp("suffix in a spinbox", " pixel", " pixels"));
- QObject::connect(mIconSizeSpinBox, QOverload<int>::of(&KPluralHandlingSpinBox::valueChanged), this, &ThemeEditor::slotIconSizeSpinBoxValueChanged);
+ QObject::connect(mIconSizeSpinBox, qOverload<int>(&KPluralHandlingSpinBox::valueChanged), this, &ThemeEditor::slotIconSizeSpinBoxValueChanged);
tabg->addWidget(mIconSizeSpinBox, 1, 1);
tabg->setColumnStretch(1, 1);
tabg->setRowStretch(2, 1);
fillViewHeaderPolicyCombo();
}
ThemeEditor::~ThemeEditor()
{
}
void ThemeEditor::editTheme(Theme *set)
{
mCurrentTheme = set;
mPreviewWidget->setTheme(mCurrentTheme);
if (!mCurrentTheme) {
setEnabled(false);
return;
}
setEnabled(true);
nameEdit()->setText(set->name());
descriptionEdit()->setPlainText(set->description());
ComboBoxUtils::setIntegerOptionComboValue(mViewHeaderPolicyCombo, (int)mCurrentTheme->viewHeaderPolicy());
mIconSizeSpinBox->setValue(set->iconSize());
setReadOnly(mCurrentTheme->readOnly());
}
void ThemeEditor::setReadOnly(bool readOnly)
{
mPreviewWidget->setReadOnly(readOnly);
mViewHeaderPolicyCombo->setEnabled(!readOnly);
mIconSizeSpinBox->setEnabled(!readOnly);
OptionSetEditor::setReadOnly(readOnly);
}
void ThemeEditor::commit()
{
if (!mCurrentTheme || mCurrentTheme->readOnly()) {
return;
}
mCurrentTheme->setName(nameEdit()->text());
mCurrentTheme->setDescription(descriptionEdit()->toPlainText());
mCurrentTheme->setViewHeaderPolicy(
(Theme::ViewHeaderPolicy)ComboBoxUtils::getIntegerOptionComboValue(mViewHeaderPolicyCombo, 0)
);
mCurrentTheme->setIconSize(mIconSizeSpinBox->value());
// other settings are already committed to this theme
}
void ThemeEditor::fillViewHeaderPolicyCombo()
{
ComboBoxUtils::fillIntegerOptionCombo(
mViewHeaderPolicyCombo,
Theme::enumerateViewHeaderPolicyOptions()
);
}
void ThemeEditor::slotNameEditTextEdited(const QString &newName)
{
if (!mCurrentTheme) {
return;
}
mCurrentTheme->setName(newName);
Q_EMIT themeNameChanged();
}
void ThemeEditor::slotIconSizeSpinBoxValueChanged(int val)
{
if (!mCurrentTheme) {
return;
}
mCurrentTheme->setIconSize(val);
mPreviewWidget->setTheme(mCurrentTheme); // will trigger a cache reset and a view update
}
MessageList::Core::Theme *ThemeEditor::editedTheme() const
{
return mCurrentTheme;
}
diff --git a/messagelist/src/utils/themeeditor.h b/messagelist/src/utils/themeeditor.h
index 66dc0e96..db078fc9 100644
--- a/messagelist/src/utils/themeeditor.h
+++ b/messagelist/src/utils/themeeditor.h
@@ -1,234 +1,234 @@
/******************************************************************************
*
* Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
*
* 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.
*
*******************************************************************************/
#ifndef MESSAGELIST_UTILS_THEMEEDITOR_H
#define MESSAGELIST_UTILS_THEMEEDITOR_H
#include "utils/optionseteditor.h"
#include "core/themedelegate.h"
#include "core/theme.h"
#include <QTreeWidget>
#include <QLabel>
#include <QRect>
#include <QDialog>
class QCheckBox;
-class KComboBox;
+class QComboBox;
class KPluralHandlingSpinBox;
class KLineEdit;
namespace MessageList {
namespace Core {
class Item;
class GroupHeaderItem;
class MessageItem;
class FakeItem;
class ModelInvariantRowMapper;
} // namespace Core
namespace Utils {
class ThemeColumnPropertiesDialog : public QDialog
{
Q_OBJECT
public:
explicit ThemeColumnPropertiesDialog(QWidget *parent, Core::Theme::Column *column, const QString &title);
protected:
Core::Theme::Column *mColumn = nullptr;
KLineEdit *mNameEdit = nullptr;
QCheckBox *mVisibleByDefaultCheck = nullptr;
QCheckBox *mIsSenderOrReceiverCheck = nullptr;
- KComboBox *mMessageSortingCombo = nullptr;
+ QComboBox *mMessageSortingCombo = nullptr;
protected Q_SLOTS:
void slotOkButtonClicked();
};
class ThemePreviewDelegate : public Core::ThemeDelegate
{
Q_OBJECT
public:
explicit ThemePreviewDelegate(QAbstractItemView *parent);
~ThemePreviewDelegate();
private:
Core::GroupHeaderItem *mSampleGroupHeaderItem = nullptr;
Core::FakeItem *mSampleMessageItem = nullptr;
Core::ModelInvariantRowMapper *mRowMapper = nullptr; // needed for the MessageItem above to be valid
public:
Core::Item *itemFromIndex(const QModelIndex &index) const override;
};
class ThemePreviewWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit ThemePreviewWidget(QWidget *parent);
~ThemePreviewWidget();
void setReadOnly(bool readOnly);
private:
// DnD insert position stuff
/**
* The row we'll be inserting the dragged item into
*/
enum RowInsertPosition {
AboveRow, ///< We'll insert above the currently hit row in mDelegate
InsideRow, ///< We'll insert inside the currently hit row in mDelegate
BelowRow ///< We'll insert below the currently hit row in mDelegate
};
/**
* The position in row that we'll be inserting the dragged item
*/
enum ItemInsertPosition {
OnLeftOfItem, ///< We'll insert on the left of the selected item
OnRightOfItem, ///< We'll insert on the right of the selected item
AsLastLeftItem, ///< We'll insert as last left item of the row (rightmost left item)
AsLastRightItem, ///< We'll insert as last right item of the row (leftmost right item)
AsFirstLeftItem, ///< We'll insert as first left item of the row (leftmost)
AsFirstRightItem ///< We'll insert as first right item of the row (rightmost)
};
private:
ThemePreviewDelegate *mDelegate = nullptr;
QTreeWidgetItem *mGroupHeaderSampleItem = nullptr;
QRect mThemeSelectedContentItemRect;
Core::Theme::ContentItem *mSelectedThemeContentItem = nullptr;
Core::Theme::Column *mSelectedThemeColumn = nullptr;
QPoint mMouseDownPoint;
Core::Theme *mTheme;
RowInsertPosition mRowInsertPosition;
ItemInsertPosition mItemInsertPosition;
QPoint mDropIndicatorPoint1;
QPoint mDropIndicatorPoint2;
bool mFirstShow;
bool mReadOnly;
public:
QSize sizeHint() const override;
void setTheme(Core::Theme *theme);
protected:
void dragMoveEvent(QDragMoveEvent *e) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void showEvent(QShowEvent *e) override;
void changeEvent(QEvent *event) override;
private:
void internalHandleDragMoveEvent(QDragMoveEvent *e);
void internalHandleDragEnterEvent(QDragEnterEvent *e);
/**
* Computes the drop insert position for the dragged item at position pos.
* Returns true if the dragged item can be inserted somewhere and
* false otherwise. Sets mRowInsertPosition, mItemInsertPosition,
* mDropIndicatorPoint1 ,mDropIndicatorPoint2.
*/
bool computeContentItemInsertPosition(const QPoint &pos, Core::Theme::ContentItem::Type type);
void applyThemeColumnWidths();
protected Q_SLOTS:
void slotHeaderContextMenuRequested(const QPoint &pos);
void slotAddColumn();
void slotColumnProperties();
void slotDeleteColumn();
void slotDisabledFlagsMenuTriggered(QAction *act);
void slotForegroundColorMenuTriggered(QAction *act);
void slotFontMenuTriggered(QAction *act);
void slotSoftenActionTriggered(bool);
void slotGroupHeaderBackgroundModeMenuTriggered(QAction *act);
void slotGroupHeaderBackgroundStyleMenuTriggered(QAction *act);
void slotMoveColumnToLeft();
void slotMoveColumnToRight();
};
class ThemeContentItemSourceLabel : public QLabel
{
Q_OBJECT
public:
ThemeContentItemSourceLabel(QWidget *parent, Core::Theme::ContentItem::Type type);
~ThemeContentItemSourceLabel();
public:
Core::Theme::ContentItem::Type type() const;
void startDrag();
protected:
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
private:
QPoint mMousePressPoint;
Core::Theme::ContentItem::Type mType;
};
class ThemeEditor : public OptionSetEditor
{
Q_OBJECT
public:
explicit ThemeEditor(QWidget *parent);
~ThemeEditor();
public:
/**
* Sets the option set to be edited.
* Saves and forgets any previously option set that was being edited.
* The set parameter may be 0: in this case the editor is simply disabled.
*/
void editTheme(Core::Theme *set);
Core::Theme *editedTheme() const;
void commit();
Q_SIGNALS:
void themeNameChanged();
private:
void fillViewHeaderPolicyCombo();
protected Q_SLOTS:
void slotNameEditTextEdited(const QString &newName) override;
void slotIconSizeSpinBoxValueChanged(int val);
private:
void setReadOnly(bool readOnly);
Core::Theme *mCurrentTheme = nullptr; // shallow, may be null!
// Appearance tab
ThemePreviewWidget *mPreviewWidget = nullptr;
// Advanced tab
- KComboBox *mViewHeaderPolicyCombo = nullptr;
+ QComboBox *mViewHeaderPolicyCombo = nullptr;
KPluralHandlingSpinBox *mIconSizeSpinBox = nullptr;
};
} // namespace Utils
} // namespace MessageList
#endif //!__MESSAGELIST_UTILS_SKINEDITOR_H
diff --git a/messagelist/src/widget.cpp b/messagelist/src/widget.cpp
index b22db292..0e5cfbf6 100644
--- a/messagelist/src/widget.cpp
+++ b/messagelist/src/widget.cpp
@@ -1,762 +1,762 @@
/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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.
*/
#include "widget.h"
#include <collection.h>
#include <item.h>
#include <itemcopyjob.h>
#include <itemmovejob.h>
#include "storagemodel.h"
#include "core/messageitem.h"
#include "core/view.h"
#include <messagelistsettings.h>
#include <QAction>
#include <QApplication>
#include <QDrag>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QUrlQuery>
#include "messagelist_debug.h"
#include <QIcon>
#include <KIconLoader>
#include <KLocalizedString>
#include <QMenu>
#include <KXMLGUIClient>
#include <KXMLGUIFactory>
#include <QUrl>
#include "core/groupheaderitem.h"
#include <Monitor>
#include <Tag>
#include <TagFetchJob>
#include <TagFetchScope>
#include <TagAttribute>
namespace MessageList {
class Q_DECL_HIDDEN Widget::Private
{
public:
Private(Widget *owner)
: q(owner)
, mLastSelectedMessage(-1)
, mXmlGuiClient(nullptr)
, mMonitor(nullptr)
{
}
Akonadi::Item::List selectionAsItems() const;
Akonadi::Item itemForRow(int row) const;
KMime::Message::Ptr messageForRow(int row) const;
Widget *const q;
int mLastSelectedMessage;
KXMLGUIClient *mXmlGuiClient = nullptr;
QModelIndex mGroupHeaderItemIndex;
Akonadi::Monitor *mMonitor = nullptr;
};
} // namespace MessageList
using namespace MessageList;
using namespace Akonadi;
Widget::Widget(QWidget *parent)
: Core::Widget(parent)
, d(new Private(this))
{
populateStatusFilterCombo();
d->mMonitor = new Akonadi::Monitor(this);
d->mMonitor->setObjectName(QStringLiteral("MessageListTagMonitor"));
d->mMonitor->setTypeMonitored(Akonadi::Monitor::Tags);
connect(d->mMonitor, &Akonadi::Monitor::tagAdded, this, &Widget::populateStatusFilterCombo);
connect(d->mMonitor, &Akonadi::Monitor::tagRemoved, this, &Widget::populateStatusFilterCombo);
connect(d->mMonitor, &Akonadi::Monitor::tagChanged, this, &Widget::populateStatusFilterCombo);
}
Widget::~Widget()
{
delete d;
}
void Widget::setXmlGuiClient(KXMLGUIClient *xmlGuiClient)
{
d->mXmlGuiClient = xmlGuiClient;
}
bool Widget::canAcceptDrag(const QDropEvent *e)
{
if (e->source() == view()->viewport()) {
return false;
}
Collection::List collections = static_cast<const StorageModel *>(storageModel())->displayedCollections();
if (collections.size() != 1) {
return false; // no folder here or too many (in case we can't decide where the drop will end)
}
const Collection target = collections.first();
if ((target.rights() & Collection::CanCreateItem) == 0) {
return false; // no way to drag into
}
const QList<QUrl> urls = e->mimeData()->urls();
for (const QUrl &url : urls) {
const Collection collection = Collection::fromUrl(url);
if (collection.isValid()) { // You're not supposed to drop collections here
return false;
} else { // Yay, this is an item!
QUrlQuery query(url);
const QString type = query.queryItemValue(QStringLiteral("type"));
if (!target.contentMimeTypes().contains(type)) {
return false;
}
}
}
return true;
}
bool Widget::selectNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
return view()->selectNextMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
}
bool Widget::selectPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop)
{
return view()->selectPreviousMessageItem(messageTypeFilter, existingSelectionBehaviour, centerItem, loop);
}
bool Widget::focusNextMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
return view()->focusNextMessageItem(messageTypeFilter, centerItem, loop);
}
bool Widget::focusPreviousMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop)
{
return view()->focusPreviousMessageItem(messageTypeFilter, centerItem, loop);
}
void Widget::selectFocusedMessageItem(bool centerItem)
{
view()->selectFocusedMessageItem(centerItem);
}
bool Widget::selectFirstMessageItem(MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem)
{
return view()->selectFirstMessageItem(messageTypeFilter, centerItem);
}
bool Widget::selectLastMessageItem(Core::MessageTypeFilter messageTypeFilter, bool centerItem)
{
return view()->selectLastMessageItem(messageTypeFilter, centerItem);
}
void Widget::selectAll()
{
view()->setAllGroupsExpanded(true);
view()->selectAll();
}
void Widget::setCurrentThreadExpanded(bool expand)
{
view()->setCurrentThreadExpanded(expand);
}
void Widget::setAllThreadsExpanded(bool expand)
{
view()->setAllThreadsExpanded(expand);
}
void Widget::setAllGroupsExpanded(bool expand)
{
view()->setAllGroupsExpanded(expand);
}
void Widget::focusQuickSearch(const QString &selectedText)
{
view()->focusQuickSearch(selectedText);
}
void Widget::setQuickSearchClickMessage(const QString &msg)
{
view()->setQuickSearchClickMessage(msg);
}
void Widget::fillMessageTagCombo()
{
Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this);
fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
connect(fetchJob, &Akonadi::TagFetchJob::result, this, &Widget::slotTagsFetched);
}
void Widget::slotTagsFetched(KJob *job)
{
if (job->error()) {
qCWarning(MESSAGELIST_LOG) << "Failed to load tags " << job->errorString();
return;
}
Akonadi::TagFetchJob *fetchJob = static_cast<Akonadi::TagFetchJob *>(job);
KConfigGroup conf(MessageList::MessageListSettings::self()->config(), "MessageListView");
const QString tagSelected = conf.readEntry(QStringLiteral("TagSelected"));
if (tagSelected.isEmpty()) {
setCurrentStatusFilterItem();
return;
}
const QStringList tagSelectedLst = tagSelected.split(QLatin1Char(','));
addMessageTagItem(QIcon::fromTheme(QStringLiteral("mail-flag")).pixmap(16, 16), i18nc("Item in list of Akonadi tags, to show all e-mails", "All"), QString());
QStringList tagFound;
foreach (const Akonadi::Tag &akonadiTag, fetchJob->tags()) {
if (tagSelectedLst.contains(akonadiTag.url().url())) {
tagFound.append(akonadiTag.url().url());
QString iconName = QStringLiteral("mail-tagged");
const QString label = akonadiTag.name();
const QString id = akonadiTag.url().url();
- Akonadi::TagAttribute *attr = akonadiTag.attribute<Akonadi::TagAttribute>();
+ const Akonadi::TagAttribute *attr = akonadiTag.attribute<Akonadi::TagAttribute>();
if (attr) {
iconName = attr->iconName();
}
addMessageTagItem(QIcon::fromTheme(iconName).pixmap(16, 16), label, QVariant(id));
}
}
conf.writeEntry(QStringLiteral("TagSelected"), tagFound);
conf.sync();
setCurrentStatusFilterItem();
}
void Widget::viewMessageSelected(MessageList::Core::MessageItem *msg)
{
int row = -1;
if (msg) {
row = msg->currentModelIndexRow();
}
if (!msg || !msg->isValid() || !storageModel()) {
d->mLastSelectedMessage = -1;
Q_EMIT messageSelected(Item());
return;
}
Q_ASSERT(row >= 0);
d->mLastSelectedMessage = row;
Q_EMIT messageSelected(d->itemForRow(row)); // this MAY be null
}
void Widget::viewMessageActivated(MessageList::Core::MessageItem *msg)
{
Q_ASSERT(msg); // must not be null
Q_ASSERT(storageModel());
if (!msg->isValid()) {
return;
}
int row = msg->currentModelIndexRow();
Q_ASSERT(row >= 0);
// The assert below may fail when quickly opening and closing a non-selected thread.
// This will actually activate the item without selecting it...
//Q_ASSERT( d->mLastSelectedMessage == row );
if (d->mLastSelectedMessage != row) {
// Very ugly. We are activating a non selected message.
// This is very likely a double click on the plus sign near a thread leader.
// Dealing with mLastSelectedMessage here would be expensive: it would involve releasing the last selected,
// emitting signals, handling recursion... ugly.
// We choose a very simple solution: double clicking on the plus sign near a thread leader does
// NOT activate the message (i.e open it in a toplevel window) if it isn't previously selected.
return;
}
Q_EMIT messageActivated(d->itemForRow(row)); // this MAY be null
}
void Widget::viewSelectionChanged()
{
Q_EMIT selectionChanged();
if (!currentMessageItem()) {
Q_EMIT messageSelected(Item());
}
}
void Widget::viewMessageListContextPopupRequest(const QList< MessageList::Core::MessageItem * > &selectedItems, const QPoint &globalPos)
{
Q_UNUSED(selectedItems);
if (!d->mXmlGuiClient) {
return;
}
QMenu *popup = static_cast<QMenu *>(d->mXmlGuiClient->factory()->container(
QStringLiteral("akonadi_messagelist_contextmenu"),
d->mXmlGuiClient));
if (popup) {
popup->exec(globalPos);
}
}
void Widget::viewMessageStatusChangeRequest(MessageList::Core::MessageItem *msg, Akonadi::MessageStatus set, Akonadi::MessageStatus clear)
{
Q_ASSERT(msg); // must not be null
Q_ASSERT(storageModel());
if (!msg->isValid()) {
return;
}
int row = msg->currentModelIndexRow();
Q_ASSERT(row >= 0);
Item item = d->itemForRow(row);
Q_ASSERT(item.isValid());
Q_EMIT messageStatusChangeRequest(item, set, clear);
}
void Widget::viewGroupHeaderContextPopupRequest(MessageList::Core::GroupHeaderItem *ghi, const QPoint &globalPos)
{
Q_UNUSED(ghi);
QMenu menu(this);
QAction *act = nullptr;
QModelIndex index = view()->model()->index(ghi, 0);
d->mGroupHeaderItemIndex = index;
if (view()->isExpanded(index)) {
act = menu.addAction(i18n("Collapse Group"));
connect(act, &QAction::triggered, this, &Widget::slotCollapseItem);
} else {
act = menu.addAction(i18n("Expand Group"));
connect(act, &QAction::triggered, this, &Widget::slotExpandItem);
}
menu.addSeparator();
act = menu.addAction(i18n("Expand All Groups"));
connect(act, &QAction::triggered,
view(), &Core::View::slotExpandAllGroups);
act = menu.addAction(i18n("Collapse All Groups"));
connect(act, &QAction::triggered,
view(), &Core::View::slotCollapseAllGroups);
menu.exec(globalPos);
}
void Widget::viewDragEnterEvent(QDragEnterEvent *e)
{
if (!canAcceptDrag(e)) {
e->ignore();
return;
}
e->accept();
}
void Widget::viewDragMoveEvent(QDragMoveEvent *e)
{
if (!canAcceptDrag(e)) {
e->ignore();
return;
}
e->accept();
}
enum DragMode {
DragCopy,
DragMove,
DragCancel
};
void Widget::viewDropEvent(QDropEvent *e)
{
if (!canAcceptDrag(e)) {
e->ignore();
return;
}
const QList<QUrl> urls = e->mimeData()->urls();
if (urls.isEmpty()) {
qCWarning(MESSAGELIST_LOG) << "Could not decode drag data!";
e->ignore();
return;
}
e->accept();
int action;
if ((e->possibleActions() & Qt::MoveAction) == 0) { // We can't move anyway
action = DragCopy;
} else {
action = DragCancel;
const auto keybstate = QApplication::keyboardModifiers();
if (keybstate & Qt::CTRL) {
action = DragCopy;
} else if (keybstate & Qt::SHIFT) {
action = DragMove;
} else {
QMenu menu;
QAction *moveAction = menu.addAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("&Move Here"));
QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy Here"));
menu.addSeparator();
menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("C&ancel"));
QAction *menuChoice = menu.exec(QCursor::pos());
if (menuChoice == moveAction) {
action = DragMove;
} else if (menuChoice == copyAction) {
action = DragCopy;
} else {
action = DragCancel;
}
}
}
if (action == DragCancel) {
return;
}
Collection::List collections = static_cast<const StorageModel *>(storageModel())->displayedCollections();
Collection target = collections.at(0);
Item::List items;
items.reserve(urls.count());
for (const QUrl &url : qAsConst(urls)) {
items << Item::fromUrl(url);
}
if (action == DragCopy) {
new ItemCopyJob(items, target, this);
} else if (action == DragMove) {
new ItemMoveJob(items, target, this);
}
}
void Widget::viewStartDragRequest()
{
Collection::List collections = static_cast<const StorageModel *>(storageModel())->displayedCollections();
if (collections.isEmpty()) {
return; // no folder here
}
const QList<Core::MessageItem *> selection = view()->selectionAsMessageItemList();
if (selection.isEmpty()) {
return;
}
bool readOnly = false;
for (const Collection &c : qAsConst(collections)) {
// We won't be able to remove items from this collection
if ((c.rights() & Collection::CanDeleteItem) == 0) {
// So the drag will be read-only
readOnly = true;
break;
}
}
QList<QUrl> urls;
urls.reserve(selection.count());
for (Core::MessageItem *mi : selection) {
const Item i = d->itemForRow(mi->currentModelIndexRow());
QUrl url = i.url(Item::Item::Item::UrlWithMimeType);
QUrlQuery query(url);
query.addQueryItem(QStringLiteral("parent"), QString::number(mi->parentCollectionId()));
url.setQuery(query);
urls << url;
}
QMimeData *mimeData = new QMimeData;
mimeData->setUrls(urls);
QDrag *drag = new QDrag(view()->viewport());
drag->setMimeData(mimeData);
// Set pixmap
QPixmap pixmap;
if (selection.size() == 1) {
pixmap = QPixmap(DesktopIcon(QStringLiteral("mail-message"), KIconLoader::SizeSmall));
} else {
pixmap = QPixmap(DesktopIcon(QStringLiteral("document-multiple"), KIconLoader::SizeSmall));
}
// Calculate hotspot (as in Konqueror)
if (!pixmap.isNull()) {
drag->setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2));
drag->setPixmap(pixmap);
}
if (readOnly) {
drag->exec(Qt::CopyAction);
} else {
drag->exec(Qt::CopyAction | Qt::MoveAction);
}
}
Item::List Widget::Private::selectionAsItems() const
{
Item::List res;
const QList<Core::MessageItem *> selection = q->view()->selectionAsMessageItemList();
res.reserve(selection.count());
for (Core::MessageItem *mi : qAsConst(selection)) {
Item i = itemForRow(mi->currentModelIndexRow());
Q_ASSERT(i.isValid());
res << i;
}
return res;
}
Item Widget::Private::itemForRow(int row) const
{
return static_cast<const StorageModel *>(q->storageModel())->itemForRow(row);
}
KMime::Message::Ptr Widget::Private::messageForRow(int row) const
{
return static_cast<const StorageModel *>(q->storageModel())->messageForRow(row);
}
Item Widget::currentItem() const
{
Core::MessageItem *mi = view()->currentMessageItem();
if (mi == nullptr) {
return Item();
}
return d->itemForRow(mi->currentModelIndexRow());
}
KMime::Message::Ptr Widget::currentMessage() const
{
Core::MessageItem *mi = view()->currentMessageItem();
if (mi == nullptr) {
return KMime::Message::Ptr();
}
return d->messageForRow(mi->currentModelIndexRow());
}
QList<KMime::Message::Ptr > Widget::selectionAsMessageList(bool includeCollapsedChildren) const
{
QList<KMime::Message::Ptr> lstMiPtr;
const QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList(includeCollapsedChildren);
if (lstMi.isEmpty()) {
return lstMiPtr;
}
lstMiPtr.reserve(lstMi.count());
for (Core::MessageItem *it : qAsConst(lstMi)) {
lstMiPtr.append(d->messageForRow(it->currentModelIndexRow()));
}
return lstMiPtr;
}
Akonadi::Item::List Widget::selectionAsMessageItemList(bool includeCollapsedChildren) const
{
Akonadi::Item::List lstMiPtr;
const QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList(includeCollapsedChildren);
if (lstMi.isEmpty()) {
return lstMiPtr;
}
lstMiPtr.reserve(lstMi.count());
for (Core::MessageItem *it : qAsConst(lstMi)) {
lstMiPtr.append(d->itemForRow(it->currentModelIndexRow()));
}
return lstMiPtr;
}
QVector<qlonglong> Widget::selectionAsMessageItemListId(bool includeCollapsedChildren) const
{
QVector<qlonglong> lstMiPtr;
const QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList(includeCollapsedChildren);
if (lstMi.isEmpty()) {
return lstMiPtr;
}
lstMiPtr.reserve(lstMi.count());
for (Core::MessageItem *it : qAsConst(lstMi)) {
lstMiPtr.append(d->itemForRow(it->currentModelIndexRow()).id());
}
return lstMiPtr;
}
QList<Akonadi::Item::Id> Widget::selectionAsListMessageId(bool includeCollapsedChildren) const
{
QList<qlonglong> lstMiPtr;
const QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList(includeCollapsedChildren);
if (lstMi.isEmpty()) {
return lstMiPtr;
}
lstMiPtr.reserve(lstMi.count());
for (Core::MessageItem *it : qAsConst(lstMi)) {
lstMiPtr.append(d->itemForRow(it->currentModelIndexRow()).id());
}
return lstMiPtr;
}
Akonadi::Item::List Widget::currentThreadAsMessageList() const
{
Akonadi::Item::List lstMiPtr;
const QList<Core::MessageItem *> lstMi = view()->currentThreadAsMessageItemList();
if (lstMi.isEmpty()) {
return lstMiPtr;
}
lstMiPtr.reserve(lstMi.count());
for (Core::MessageItem *it : qAsConst(lstMi)) {
lstMiPtr.append(d->itemForRow(it->currentModelIndexRow()));
}
return lstMiPtr;
}
MessageList::Core::QuickSearchLine::SearchOptions Widget::currentOptions() const
{
return view()->currentOptions();
}
QList<Akonadi::MessageStatus> Widget::currentFilterStatus() const
{
return view()->currentFilterStatus();
}
QString Widget::currentFilterSearchString() const
{
return view()->currentFilterSearchString();
}
bool Widget::isThreaded() const
{
return view()->isThreaded();
}
bool Widget::selectionEmpty() const
{
return view()->selectionEmpty();
}
bool Widget::getSelectionStats(
Akonadi::Item::List &selectedItems, Akonadi::Item::List &selectedVisibleItems, bool *allSelectedBelongToSameThread, bool includeCollapsedChildren) const
{
if (!storageModel()) {
return false;
}
selectedItems.clear();
selectedVisibleItems.clear();
const QList< Core::MessageItem * > selected = view()->selectionAsMessageItemList(includeCollapsedChildren);
Core::MessageItem *topmost = nullptr;
*allSelectedBelongToSameThread = true;
for (Core::MessageItem *it : qAsConst(selected)) {
const Item item = d->itemForRow(it->currentModelIndexRow());
selectedItems.append(item);
if (view()->isDisplayedWithParentsExpanded(it)) {
selectedVisibleItems.append(item);
}
if (topmost == nullptr) {
topmost = (*it).topmostMessage();
} else {
if (topmost != (*it).topmostMessage()) {
*allSelectedBelongToSameThread = false;
}
}
}
return true;
}
void Widget::deletePersistentSet(MessageList::Core::MessageItemSetReference ref)
{
view()->deletePersistentSet(ref);
}
void Widget::markMessageItemsAsAboutToBeRemoved(MessageList::Core::MessageItemSetReference ref, bool bMark)
{
QList< Core::MessageItem * > lstPersistent = view()->persistentSetCurrentMessageItemList(ref);
if (!lstPersistent.isEmpty()) {
view()->markMessageItemsAsAboutToBeRemoved(lstPersistent, bMark);
}
}
Akonadi::Item::List Widget::itemListFromPersistentSet(MessageList::Core::MessageItemSetReference ref)
{
Akonadi::Item::List lstItem;
const QList< Core::MessageItem * > refList = view()->persistentSetCurrentMessageItemList(ref);
if (!refList.isEmpty()) {
lstItem.reserve(refList.count());
for (Core::MessageItem *it : qAsConst(refList)) {
lstItem.append(d->itemForRow(it->currentModelIndexRow()));
}
}
return lstItem;
}
MessageList::Core::MessageItemSetReference Widget::selectionAsPersistentSet(bool includeCollapsedChildren) const
{
QList<Core::MessageItem *> lstMi = view()->selectionAsMessageItemList(includeCollapsedChildren);
if (lstMi.isEmpty()) {
return -1;
}
return view()->createPersistentSet(lstMi);
}
MessageList::Core::MessageItemSetReference Widget::currentThreadAsPersistentSet() const
{
QList<Core::MessageItem *> lstMi = view()->currentThreadAsMessageItemList();
if (lstMi.isEmpty()) {
return -1;
}
return view()->createPersistentSet(lstMi);
}
Akonadi::Collection Widget::currentCollection() const
{
Collection::List collections = static_cast<const StorageModel *>(storageModel())->displayedCollections();
if (collections.size() != 1) {
return Akonadi::Collection(); // no folder here or too many (in case we can't decide where the drop will end)
}
return collections.first();
}
void Widget::slotCollapseItem()
{
view()->setCollapseItem(d->mGroupHeaderItemIndex);
}
void Widget::slotExpandItem()
{
view()->setExpandItem(d->mGroupHeaderItemIndex);
}
diff --git a/messageviewer/autotests/CMakeLists.txt b/messageviewer/autotests/CMakeLists.txt
index 533eba94..8f312ff4 100644
--- a/messageviewer/autotests/CMakeLists.txt
+++ b/messageviewer/autotests/CMakeLists.txt
@@ -1,48 +1,49 @@
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
add_definitions( -DMAIL_DATA_DIR="${CMAKE_SOURCE_DIR}/mimetreeparser/autotests/data" )
add_definitions( -DGRANTLEETHEME_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
set(test_libmessageviewer_SRCS)
ecm_qt_declare_logging_category(test_libmessageviewer_SRCS HEADER messageviewer_debug.h IDENTIFIER MESSAGEVIEWER_LOG CATEGORY_NAME org.kde.pim.messageviewer)
# convenience macro to add qtest unit tests
macro(add_messageviewer_unittest _source)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source} setupenv.cpp util.cpp
TEST_NAME ${_name}
NAME_PREFIX "messageviewer-"
LINK_LIBRARIES KF5::MessageViewer KF5::Libkleo QGpgme Qt5::Test KF5::KIOCore KF5::Mime KF5::AkonadiCore KF5::CalendarCore KF5::WebEngineViewer
)
endmacro ()
macro(add_messageviewer_class_unittest _source _additionalSource)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source} ${_additionalSource}
TEST_NAME ${_name}
NAME_PREFIX "messageviewer-"
LINK_LIBRARIES KF5::MessageViewer Qt5::Test KF5::IconThemes KF5::XmlGui KF5::I18n
)
endmacro ()
add_messageviewer_unittest( messagedisplayformatattributetest.cpp )
# convenience macro to add qtest unit tests
macro(add_messageviewer_mailsourceviewbrowserwidget_unittest _source)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source} ../src/widgets/mailsourceviewtextbrowserwidget.cpp ../src/findbar/findbarsourceview.cpp ${test_libmessageviewer_SRCS}
TEST_NAME ${_name}
NAME_PREFIX "messageviewer-"
LINK_LIBRARIES Qt5::Test Qt5::Gui Qt5::Widgets KF5::KIOCore KF5::Mime KF5::AkonadiCore KF5::CalendarCore KF5::PimTextEdit KF5::PimCommon KF5::MessageViewer KF5::WebEngineViewer KF5::SyntaxHighlighting KF5::I18n KF5::IconThemes
)
endmacro ()
add_messageviewer_mailsourceviewbrowserwidget_unittest( mailsourceviewtextbrowserwidgettest.cpp )
########### viewertest_gui ###############
-
-set(KDEPIMLIBS_RUN_ISOLATED_TESTS TRUE)
-set(KDEPIMLIBS_RUN_SQLITE_ISOLATED_TESTS TRUE)
-
-add_akonadi_isolated_test_advanced(viewertest.cpp "" "KF5::MessageViewer;KF5::XmlGui")
-add_akonadi_isolated_test_advanced(viewergrantleethemesupporttest.cpp "" "KF5::MessageViewer;KF5::XmlGui")
-add_akonadi_isolated_test_advanced(urlhandlermanagertest.cpp "util.cpp" "KF5::MessageViewer;KF5::Libkleo;QGpgme;KF5::KIOCore;KF5::Mime;KF5::AkonadiCore;KF5::CalendarCore;KF5::WebEngineViewer")
+if (KDEPIM_RUN_AKONADI_TEST)
+ set(KDEPIMLIBS_RUN_ISOLATED_TESTS TRUE)
+ set(KDEPIMLIBS_RUN_SQLITE_ISOLATED_TESTS TRUE)
+
+ add_akonadi_isolated_test_advanced(viewertest.cpp "" "KF5::MessageViewer;KF5::XmlGui")
+ add_akonadi_isolated_test_advanced(viewergrantleethemesupporttest.cpp "" "KF5::MessageViewer;KF5::XmlGui")
+ add_akonadi_isolated_test_advanced(urlhandlermanagertest.cpp "util.cpp" "KF5::MessageViewer;KF5::Libkleo;QGpgme;KF5::KIOCore;KF5::Mime;KF5::AkonadiCore;KF5::CalendarCore;KF5::WebEngineViewer")
+endif()
diff --git a/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.cpp b/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.cpp
index f59fe0a5..7c021411 100644
--- a/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.cpp
+++ b/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.cpp
@@ -1,52 +1,52 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "mailsourceviewtextbrowserwidgettest.h"
#include "../src/widgets/mailsourceviewtextbrowserwidget.h"
#include "../src/findbar/findbarsourceview.h"
#include "kpimtextedit/texttospeechwidget.h"
-#include <qtest.h>
+#include <QTest>
MailSourceViewTextBrowserWidgetTest::MailSourceViewTextBrowserWidgetTest(QObject *parent)
: QObject(parent)
{
}
MailSourceViewTextBrowserWidgetTest::~MailSourceViewTextBrowserWidgetTest()
{
}
void MailSourceViewTextBrowserWidgetTest::shouldHaveDefaultValue()
{
MessageViewer::MailSourceViewTextBrowserWidget widget(QStringLiteral("Email"));
MessageViewer::MailSourceViewTextBrowser *textbrowser
= widget.findChild<MessageViewer::MailSourceViewTextBrowser *>(QStringLiteral("textbrowser"));
QVERIFY(textbrowser);
QVERIFY(!textbrowser->isHidden());
MessageViewer::FindBarSourceView *findbar
= widget.findChild<MessageViewer::FindBarSourceView *>(QStringLiteral("findbar"));
QVERIFY(findbar);
QVERIFY(findbar->isHidden());
KPIMTextEdit::TextToSpeechWidget *texttospeechwidget
= widget.findChild<KPIMTextEdit::TextToSpeechWidget *>(QStringLiteral("texttospeech"));
QVERIFY(texttospeechwidget);
QVERIFY(texttospeechwidget->isHidden());
}
QTEST_MAIN(MailSourceViewTextBrowserWidgetTest)
diff --git a/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.h b/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.h
index d584f2d9..17acdf7d 100644
--- a/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.h
+++ b/messageviewer/autotests/mailsourceviewtextbrowserwidgettest.h
@@ -1,33 +1,33 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef MAILSOURCEVIEWTEXTBROWSERWIDGETTEST_H
#define MAILSOURCEVIEWTEXTBROWSERWIDGETTEST_H
#include <QObject>
class MailSourceViewTextBrowserWidgetTest : public QObject
{
Q_OBJECT
public:
explicit MailSourceViewTextBrowserWidgetTest(QObject *parent = nullptr);
~MailSourceViewTextBrowserWidgetTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // MAILSOURCEVIEWTEXTBROWSERWIDGETTEST_H
diff --git a/messageviewer/autotests/messagedisplayformatattributetest.cpp b/messageviewer/autotests/messagedisplayformatattributetest.cpp
index 178cbb40..f56f3a4b 100644
--- a/messageviewer/autotests/messagedisplayformatattributetest.cpp
+++ b/messageviewer/autotests/messagedisplayformatattributetest.cpp
@@ -1,93 +1,93 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#include "messagedisplayformatattributetest.h"
#include "../src/viewer/messagedisplayformatattribute.h"
-#include <qtest.h>
+#include <QTest>
MessageDisplayFormatAttributeTest::MessageDisplayFormatAttributeTest(QObject *parent)
: QObject(parent)
{
}
MessageDisplayFormatAttributeTest::~MessageDisplayFormatAttributeTest()
{
}
void MessageDisplayFormatAttributeTest::shouldHaveDefaultValue()
{
MessageViewer::MessageDisplayFormatAttribute attr;
QVERIFY(!attr.remoteContent());
QCOMPARE(attr.messageFormat(), MessageViewer::Viewer::UseGlobalSetting);
}
void MessageDisplayFormatAttributeTest::shouldChangeRemoteValue()
{
MessageViewer::MessageDisplayFormatAttribute attr;
attr.setRemoteContent(true);
QVERIFY(attr.remoteContent());
}
void MessageDisplayFormatAttributeTest::shouldChangeMessageFormat()
{
MessageViewer::Viewer::DisplayFormatMessage format = MessageViewer::Viewer::Html;
MessageViewer::MessageDisplayFormatAttribute attr;
attr.setMessageFormat(format);
QCOMPARE(attr.messageFormat(), format);
format = MessageViewer::Viewer::Text;
attr.setMessageFormat(format);
QCOMPARE(attr.messageFormat(), format);
format = MessageViewer::Viewer::UseGlobalSetting;
attr.setMessageFormat(format);
QCOMPARE(attr.messageFormat(), format);
}
void MessageDisplayFormatAttributeTest::shouldDeserializeValue()
{
MessageViewer::Viewer::DisplayFormatMessage format = MessageViewer::Viewer::Html;
MessageViewer::MessageDisplayFormatAttribute attr;
attr.setMessageFormat(format);
attr.setRemoteContent(true);
const QByteArray ba = attr.serialized();
MessageViewer::MessageDisplayFormatAttribute result;
result.deserialize(ba);
QVERIFY(attr == result);
}
void MessageDisplayFormatAttributeTest::shouldCloneAttribute()
{
MessageViewer::Viewer::DisplayFormatMessage format = MessageViewer::Viewer::Html;
MessageViewer::MessageDisplayFormatAttribute attr;
attr.setMessageFormat(format);
attr.setRemoteContent(true);
MessageViewer::MessageDisplayFormatAttribute *result = attr.clone();
QVERIFY(attr == *result);
delete result;
}
void MessageDisplayFormatAttributeTest::shouldDefineType()
{
MessageViewer::MessageDisplayFormatAttribute attr;
QCOMPARE(attr.type(), QByteArrayLiteral("MessageDisplayFormatAttribute"));
}
QTEST_MAIN(MessageDisplayFormatAttributeTest)
diff --git a/messageviewer/autotests/messagedisplayformatattributetest.h b/messageviewer/autotests/messagedisplayformatattributetest.h
index 57e77467..56edbaa2 100644
--- a/messageviewer/autotests/messagedisplayformatattributetest.h
+++ b/messageviewer/autotests/messagedisplayformatattributetest.h
@@ -1,41 +1,41 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#ifndef MESSAGEDISPLAYFORMATATTRIBUTETEST_H
#define MESSAGEDISPLAYFORMATATTRIBUTETEST_H
#include <QObject>
class MessageDisplayFormatAttributeTest : public QObject
{
Q_OBJECT
public:
explicit MessageDisplayFormatAttributeTest(QObject *parent = nullptr);
~MessageDisplayFormatAttributeTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldChangeRemoteValue();
void shouldChangeMessageFormat();
void shouldDeserializeValue();
void shouldCloneAttribute();
void shouldDefineType();
};
#endif // MESSAGEDISPLAYFORMATATTRIBUTETEST_H
diff --git a/messageviewer/autotests/setupenv.cpp b/messageviewer/autotests/setupenv.cpp
index 1e1fb96b..6d64e37d 100644
--- a/messageviewer/autotests/setupenv.cpp
+++ b/messageviewer/autotests/setupenv.cpp
@@ -1,34 +1,34 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#include "setupenv.h"
#include <QStandardPaths>
#include <QFile>
#include <QDir>
void MessageViewer::Test::setupEnv()
{
qputenv("LC_ALL", "C");
- qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1(
+ qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String(
"/.qttest")).constData());
QStandardPaths::setTestModeEnabled(true);
}
diff --git a/messageviewer/autotests/urlhandlermanagertest.cpp b/messageviewer/autotests/urlhandlermanagertest.cpp
index 408f089a..28c5f653 100644
--- a/messageviewer/autotests/urlhandlermanagertest.cpp
+++ b/messageviewer/autotests/urlhandlermanagertest.cpp
@@ -1,127 +1,127 @@
/*
Copyright (c) 2017 Sandro Knauß <sknauss@kde.org>
This program 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.
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
*/
#include "urlhandlermanagertest.h"
#include "util.h"
#include "viewer/viewer.h"
#include "viewer/viewer_p.h"
#include "viewer/urlhandlermanager_p.h"
#include <MimeTreeParser/BodyPart>
#include <MimeTreeParser/Enums>
#include <MessageViewer/BodyPartURLHandler>
#include <QTest>
using namespace MessageViewer;
class TestBodyPartURLHandler : public Interface::BodyPartURLHandler
{
public:
- void testPath(QString path) const
+ void testPath(const QString &path) const
{
QCOMPARE(path, mPath);
}
void testViewer(MessageViewer::Viewer *viewerInstance) const
{
QCOMPARE(viewerInstance, mViewerInstance);
}
void testContent(KMime::Content *content) const
{
QCOMPARE(content, mContent);
}
bool handleClick(MessageViewer::Viewer *viewerInstance, MimeTreeParser::Interface::BodyPart *part, const QString &path) const override
{
testContent(part->content());
testViewer(viewerInstance);
testPath(path);
return mHandleClick;
}
bool handleContextMenuRequest(MimeTreeParser::Interface::BodyPart *part, const QString &path, const QPoint &p) const override
{
Q_UNUSED(p);
testContent(part->content());
testPath(path);
return mHandleContextMenuRequest;
}
QString statusBarMessage(MimeTreeParser::Interface::BodyPart *part, const QString &path) const override
{
testContent(part->content());
testPath(path);
return mStatusBarMessageRet;
}
QString mPath;
QString mStatusBarMessageRet;
bool mHandleClick;
bool mHandleContextMenuRequest;
MessageViewer::Viewer *mViewerInstance;
KMime::Content *mContent;
};
BodyPartUrlHandlerManagerTest::BodyPartUrlHandlerManagerTest(QObject *parent)
: QObject(parent)
{
}
void BodyPartUrlHandlerManagerTest::testHandleClick_data()
{
QTest::addColumn<QString>("url");
QTest::addColumn<QString>("path");
QTest::addColumn<KMime::ContentIndex>("index");
QTest::addColumn<bool>("ret");
- QTest::newRow("completly_empty") << "" << QStringLiteral("") << KMime::ContentIndex() << false;
- QTest::newRow("empty") << "x-kmail:" << QStringLiteral("") << KMime::ContentIndex() << false;
- QTest::newRow("pgpkey") << "x-kmail:/bodypart/1234/2/pgpkey" << QStringLiteral("pgpkey") << KMime::ContentIndex(QStringLiteral("2")) << true;
- QTest::newRow("test") << "x-kmail:/bodypart/1234/1/test" << QStringLiteral("test") << KMime::ContentIndex(QStringLiteral("1")) << true;
- QTest::newRow("test_with_arguments") << "x-kmail:/bodypart/1234/1/test?foo=qua" << QStringLiteral("test?foo=qua") << KMime::ContentIndex(QStringLiteral("1")) << true;
+ QTest::newRow("completely_empty") << QString() << QString() << KMime::ContentIndex() << false;
+ QTest::newRow("empty") << QStringLiteral("x-kmail:") << QString() << KMime::ContentIndex() << false;
+ QTest::newRow("pgpkey") << QStringLiteral("x-kmail:/bodypart/1234/2/pgpkey") << QStringLiteral("pgpkey") << KMime::ContentIndex(QStringLiteral("2")) << true;
+ QTest::newRow("test") << QStringLiteral("x-kmail:/bodypart/1234/1/test") << QStringLiteral("test") << KMime::ContentIndex(QStringLiteral("1")) << true;
+ QTest::newRow("test_with_arguments") << QStringLiteral("x-kmail:/bodypart/1234/1/test?foo=qua") << QStringLiteral("test?foo=qua") << KMime::ContentIndex(QStringLiteral("1")) << true;
}
void BodyPartUrlHandlerManagerTest::testHandleClick()
{
QFETCH(QString, url);
QFETCH(QString, path);
QFETCH(KMime::ContentIndex, index);
QFETCH(bool, ret);
BodyPartURLHandlerManager manager;
TestBodyPartURLHandler handler;
- manager.registerHandler(&handler, QStringLiteral(""));
+ manager.registerHandler(&handler, QLatin1String(""));
Viewer v(nullptr);
ViewerPrivate vp(&v, nullptr, nullptr);
const KMime::Message::Ptr msg(Test::readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox")));
vp.setMessage(msg, MimeTreeParser::Delayed);
handler.mViewerInstance = &v;
handler.mPath = path;
handler.mHandleClick = true;
handler.mContent = msg->content(index);
QCOMPARE(manager.handleClick(QUrl(url), &vp), ret);
manager.unregisterHandler(&handler);
}
QTEST_MAIN(BodyPartUrlHandlerManagerTest)
diff --git a/messageviewer/autotests/viewergrantleethemesupporttest.cpp b/messageviewer/autotests/viewergrantleethemesupporttest.cpp
index 20f2e52b..14fc0649 100644
--- a/messageviewer/autotests/viewergrantleethemesupporttest.cpp
+++ b/messageviewer/autotests/viewergrantleethemesupporttest.cpp
@@ -1,54 +1,54 @@
/*
- Copyright (C) 2017-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2017-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "viewergrantleethemesupporttest.h"
#include "messageviewer/viewer.h"
#include <KActionCollection>
#include <QStandardPaths>
#include <KConfigGroup>
#include <KSharedConfig>
#include <QTest>
ViewerGrantleeThemeSupportTest::ViewerGrantleeThemeSupportTest(QObject *parent)
: QObject(parent)
{
}
ViewerGrantleeThemeSupportTest::~ViewerGrantleeThemeSupportTest()
{
}
void ViewerGrantleeThemeSupportTest::initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
// Point the test to our dummy icon theme
KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
cg.writeEntry("Theme", "dummyTheme");
qputenv("XDG_DATA_DIRS", GRANTLEETHEME_DATA_DIR);
}
void ViewerGrantleeThemeSupportTest::shouldUpdateThemeMenu()
{
MessageViewer::Viewer *viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(
this));
viewer->show();
QVERIFY(QTest::qWaitForWindowExposed(viewer));
}
QTEST_MAIN(ViewerGrantleeThemeSupportTest)
diff --git a/messageviewer/autotests/viewergrantleethemesupporttest.h b/messageviewer/autotests/viewergrantleethemesupporttest.h
index c003a200..322d7c39 100644
--- a/messageviewer/autotests/viewergrantleethemesupporttest.h
+++ b/messageviewer/autotests/viewergrantleethemesupporttest.h
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2017-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2017-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef VIEWERGRANTLEETHEMESUPPORTTEST_H
#define VIEWERGRANTLEETHEMESUPPORTTEST_H
#include <QObject>
class ViewerGrantleeThemeSupportTest : public QObject
{
Q_OBJECT
public:
explicit ViewerGrantleeThemeSupportTest(QObject *parent = nullptr);
~ViewerGrantleeThemeSupportTest();
private Q_SLOTS:
void initTestCase();
void shouldUpdateThemeMenu();
};
#endif // VIEWERGRANTLEETHEMESUPPORTTEST_H
diff --git a/messageviewer/autotests/viewertest.cpp b/messageviewer/autotests/viewertest.cpp
index ba6673ea..d9928b35 100644
--- a/messageviewer/autotests/viewertest.cpp
+++ b/messageviewer/autotests/viewertest.cpp
@@ -1,124 +1,124 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "viewertest.h"
-#include <qtest.h>
+#include <QTest>
#include "messageviewer/viewer.h"
#include <qtestmouse.h>
#include <KActionCollection>
ViewerTest::ViewerTest()
{
}
void ViewerTest::shouldHaveDefaultValuesOnCreation()
{
MessageViewer::Viewer *viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(
this));
viewer->show();
QVERIFY(QTest::qWaitForWindowExposed(viewer));
QWidget *mViewer = viewer->findChild<QWidget *>(QStringLiteral("mViewer"));
QVERIFY(mViewer);
QCOMPARE(mViewer->isVisible(), true);
QWidget *sliderContainer = viewer->findChild<QWidget *>(QStringLiteral("slidercontainer"));
QVERIFY(sliderContainer);
QCOMPARE(sliderContainer->isVisible(), false);
QWidget *colorBar = viewer->findChild<QWidget *>(QStringLiteral("mColorBar"));
QVERIFY(colorBar);
QWidget *scandetectionWidget
= viewer->findChild<QWidget *>(QStringLiteral("scandetectionwarning"));
QVERIFY(scandetectionWidget);
QCOMPARE(scandetectionWidget->isVisible(), false);
QWidget *openattachementfolderwidget
= viewer->findChild<QWidget *>(QStringLiteral("openattachementfolderwidget"));
QVERIFY(openattachementfolderwidget);
QCOMPARE(openattachementfolderwidget->isVisible(), false);
QVERIFY(viewer->toggleFixFontAction());
QVERIFY(viewer->toggleMimePartTreeAction());
QVERIFY(viewer->selectAllAction());
QVERIFY(viewer->copyURLAction());
QVERIFY(viewer->copyAction());
QVERIFY(viewer->urlOpenAction());
QVERIFY(viewer->speakTextAction());
QVERIFY(viewer->copyImageLocation());
QVERIFY(viewer->viewSourceAction());
QVERIFY(viewer->findInMessageAction());
QVERIFY(viewer->saveAsAction());
QVERIFY(viewer->saveMessageDisplayFormatAction());
QVERIFY(viewer->resetMessageDisplayFormatAction());
QVERIFY(viewer->urlClicked().isEmpty());
QVERIFY(viewer->imageUrlClicked().isEmpty());
QCOMPARE(viewer->isFixedFont(), false);
QVERIFY(viewer->shareServiceUrlMenu());
delete viewer;
}
static const char s_mail1[]
= "From: Konqui <konqui@kde.org>\n"
"To: Friends <friends@kde.org>\n"
"Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n"
"Subject: Sample message\n"
"MIME-Version: 1.0\n"
"Content-type: text/plain; charset=us-ascii\n"
"\n"
"\n"
"This is a test message.\n"
"\n";
static const char s_mail2[]
= "From: David Faure <dfaure@example.com>\n"
"To: Friends <friends@example.com>\n"
"Date: Sun, 31 Aug 2016 23:56:48 +0200 (CEST)\n"
"Subject: Second mail\n"
"MIME-Version: 1.0\n"
"Content-type: text/plain; charset=\"us-ascii\"\n"
"\n"
"\n"
"This is the second message.\n"
"\n";
KMime::Message::Ptr createMsg(const char *data)
{
KMime::Message::Ptr msgPtr(new KMime::Message());
msgPtr->setContent(QByteArray(data));
msgPtr->parse();
return msgPtr;
}
void ViewerTest::shouldDisplayMessage()
{
MessageViewer::Viewer viewer(nullptr, nullptr, new KActionCollection(this));
viewer.setMessage(createMsg(s_mail1), MimeTreeParser::Force);
// not sure what to check, but at least we check it neither crashes nor hangs
// TODO: retrieve message text and check it
}
void ViewerTest::shouldSwitchToAnotherMessage()
{
MessageViewer::Viewer viewer(nullptr, nullptr, new KActionCollection(this));
viewer.setMessage(createMsg(s_mail1), MimeTreeParser::Force);
viewer.setMessage(createMsg(s_mail2), MimeTreeParser::Force);
}
QTEST_MAIN(ViewerTest)
diff --git a/messageviewer/autotests/viewertest.h b/messageviewer/autotests/viewertest.h
index bf55e297..be596bb7 100644
--- a/messageviewer/autotests/viewertest.h
+++ b/messageviewer/autotests/viewertest.h
@@ -1,33 +1,33 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef VIEWERTEST_H
#define VIEWERTEST_H
#include <QObject>
class ViewerTest : public QObject
{
Q_OBJECT
public:
ViewerTest();
private Q_SLOTS:
void shouldHaveDefaultValuesOnCreation();
void shouldDisplayMessage();
void shouldSwitchToAnotherMessage();
};
#endif // VIEWERTEST_H
diff --git a/messageviewer/autotests/zoomactionmenutest.cpp b/messageviewer/autotests/zoomactionmenutest.cpp
index 43baa629..be360f63 100644
--- a/messageviewer/autotests/zoomactionmenutest.cpp
+++ b/messageviewer/autotests/zoomactionmenutest.cpp
@@ -1,53 +1,53 @@
/*
- Copyright (c) 2015-2016 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Laurent Montel <montel@kde.org>
This program 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.
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
*/
#include "zoomactionmenutest.h"
#include "../src/widgets/zoomactionmenu.h"
#include <QTest>
#include <KActionCollection>
ZoomActionMenuTest::ZoomActionMenuTest(QObject *parent)
: QObject(parent)
{
}
ZoomActionMenuTest::~ZoomActionMenuTest()
{
}
void ZoomActionMenuTest::shouldHaveDefaultValue()
{
MessageViewer::ZoomActionMenu menu(this);
menu.setActionCollection(new KActionCollection(this));
menu.createZoomActions();
QVERIFY(menu.zoomInAction());
QVERIFY(menu.zoomOutAction());
QVERIFY(menu.zoomResetAction());
}
void ZoomActionMenuTest::shouldAssignZoomFactor()
{
MessageViewer::ZoomActionMenu menu(this);
menu.setActionCollection(new KActionCollection(this));
menu.createZoomActions();
qreal initialValue = 50;
menu.setZoomFactor(initialValue);
QCOMPARE(menu.zoomFactor(), initialValue);
}
QTEST_MAIN(ZoomActionMenuTest)
diff --git a/messageviewer/autotests/zoomactionmenutest.h b/messageviewer/autotests/zoomactionmenutest.h
index 164b413c..431f4fc7 100644
--- a/messageviewer/autotests/zoomactionmenutest.h
+++ b/messageviewer/autotests/zoomactionmenutest.h
@@ -1,34 +1,34 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef ZOOMACTIONMENUTEST_H
#define ZOOMACTIONMENUTEST_H
#include <QObject>
class ZoomActionMenuTest : public QObject
{
Q_OBJECT
public:
explicit ZoomActionMenuTest(QObject *parent = nullptr);
~ZoomActionMenuTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAssignZoomFactor();
};
#endif // ZOOMACTIONMENUTEST_H
diff --git a/messageviewer/metainfo.yaml b/messageviewer/metainfo.yaml
index 1cadfff0..49a20fd2 100644
--- a/messageviewer/metainfo.yaml
+++ b/messageviewer/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer: mlaurent
description: MessageViewer Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: MessageViewer
cmake: "KF5::MessageViewer"
- cmakename: KF5MessageViewer
+cmakename: KF5MessageViewer
diff --git a/messageviewer/src/CMakeLists.txt b/messageviewer/src/CMakeLists.txt
index 2b7df6f7..fd19af49 100644
--- a/messageviewer/src/CMakeLists.txt
+++ b/messageviewer/src/CMakeLists.txt
@@ -1,454 +1,503 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libmessageviewer\")
add_subdirectory(messagepartthemes/grantlee)
# KCFG files:
# The main messageviewer.kcfg is configured by CMake and put in the build directory.
if(KDEPIM_ENTERPRISE_BUILD)
set(LEGACY_MANGLE_FROM_TO_HEADERS true)
set(LEGACY_BODY_INVITES true)
set(EXCHANGE_COMPATIBLE_INVITATIONS true)
else()
set(LEGACY_MANGLE_FROM_TO_HEADERS false)
set(LEGACY_BODY_INVITES false)
set(EXCHANGE_COMPATIBLE_INVITATIONS false)
endif()
configure_file(settings/messageviewer.kcfg.cmake ${CMAKE_CURRENT_BINARY_DIR}/messageviewer.kcfg)
include(CheckIncludeFiles)
find_package(Inotify)
set_package_properties(Inotify PROPERTIES
PURPOSE "Filesystem alteration notifications using inotify")
if(Inotify_FOUND)
set(HAVE_SYS_INOTIFY_H 1)
else()
set(HAVE_SYS_INOTIFY_H 0)
endif()
configure_file(config-messageviewer.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-messageviewer.h)
# target_include_directories does not handle empty include paths
include_directories(${GPGME_INCLUDES})
if(BUILD_TESTING)
add_subdirectory(header/autotests)
add_subdirectory(scamdetection/autotests)
add_subdirectory(scamdetection/tests)
add_subdirectory(viewerplugins/tests/)
add_subdirectory(htmlwriter/autotests)
add_subdirectory(viewer/webengine/tests)
add_subdirectory(messagepartthemes/default/autotests)
add_subdirectory(widgets/autotests/)
+ add_subdirectory(utils/autotests)
endif()
add_subdirectory(pics)
add_subdirectory(kconf_update)
add_subdirectory(about)
add_subdirectory(messageviewerheaderplugins)
+
+if (DKIM_CHECKER_BUILD)
+ add_subdirectory(dkim-verify)
+ set(dkim_verify_SRCS
+ dkim-verify/dkiminfo.cpp
+ dkim-verify/dkimmanagerkey.cpp
+ dkim-verify/dkimmanagerkeywidget.cpp
+ dkim-verify/dkimmanagerkeydialog.cpp
+ dkim-verify/dkimdownloadkeyjob.cpp
+ dkim-verify/dkimchecksignaturejob.cpp
+ dkim-verify/dkimcheckauthenticationstatusjob.cpp
+ dkim-verify/dkimauthenticationstatusinfo.cpp
+ dkim-verify/dkimutil.cpp
+ )
+endif()
+
if(DEBUG_SIGNATURE)
add_definitions(-DDEBUG_SIGNATURE)
endif()
set(libmessageviewer_mailviewer_SRCS
viewer/webengine/mailwebengineview.cpp
viewer/webengine/mailwebenginepage.cpp
viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp
viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp
viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp
viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.cpp
)
set(libmessageviewer_viewer_SRCS
viewer/attachmentstrategy.cpp
viewer/csshelper.cpp
viewer/csshelperbase.cpp
viewer/editorwatcher.cpp
viewer/objecttreeemptysource.cpp
viewer/objecttreeviewersource.cpp
viewer/viewer.cpp
viewer/viewer_p.cpp
viewer/messagedisplayformatattribute.cpp
viewer/urlhandlermanager.cpp
viewer/mimeparttree/mimeparttreeview.cpp
viewer/mimeparttree/mimetreemodel.cpp
)
set(libmessageviewer_widgets_SRCS
widgets/attachmentdialog.cpp
widgets/configurewidget.cpp
widgets/printingsettings.cpp
widgets/htmlstatusbar.cpp
widgets/vcardviewer.cpp
widgets/invitationsettings.cpp
widgets/openattachmentfolderwidget.cpp
widgets/mailsourceviewtextbrowserwidget.cpp
widgets/submittedformwarningwidget.cpp
widgets/mailtrackingwarningwidget.cpp
widgets/mailtrackingdetailsdialog.cpp
+ widgets/shownextmessagewidget.cpp
)
set(libmessageviewer_widgets_webengine_SRCS
widgets/mailsourcewebengineviewer.cpp
)
set(libmessageviewer_header_SRCS
header/contactdisplaymessagememento.cpp
header/headerstrategy.cpp
header/richheaderstrategy.cpp
header/headerstyle.cpp
header/grantleeheaderstyle.cpp
header/plainheaderstyle.cpp
header/headerstyle_util.cpp
header/grantleeheaderformatter.cpp
header/grantleeheaderteststyle.cpp
header/kxface.cpp
header/headerstyleplugin.cpp
header/headerstylepluginmanager.cpp
header/headerstyleinterface.cpp
header/headerstylemenumanager.cpp
)
set(libmessageviewer_scamdetection_SRCS
scamdetection/scamdetectionwarningwidget.cpp
scamdetection/scamdetectiondetailsdialog.cpp
scamdetection/scamattribute.cpp
scamdetection/scamcheckshorturl.cpp
scamdetection/scamexpandurljob.cpp
scamdetection/scamcheckshorturlmanager.cpp
)
set(libmessageviewer_scamdetection_webengine_SRCS
scamdetection/scamdetectionwebengine.cpp
)
set(libmessageviewer_findbar_SRCS
findbar/findbarsourceview.cpp
)
set(libmessageviewer_utils_SRCS
utils/iconnamecache.cpp
utils/markmessagereadhandler.cpp
utils/messageviewerutil.cpp
utils/mimetype.cpp
)
set(libmessageviewer_htmlwriter_webengine_SRCS
htmlwriter/webengineparthtmlwriter.cpp
htmlwriter/webengineembedpart.cpp
)
set(libmessageviewer_htmlwriter_SRCS
${libmessageviewer_htmlwriter_webengine_SRCS}
htmlwriter/bufferedhtmlwriter.cpp
htmlwriter/filehtmlwriter.cpp
)
set(libmessageviewer_antispam_SRCS
antispam/spamheaderanalyzer.cpp
antispam/antispamconfig.cpp
)
set(libmessageviewer_job_SRCS
job/modifymessagedisplayformatjob.cpp
)
set(libmessageviewer_viewerplugins_SRCS
viewerplugins/viewerpluginmanager.cpp
viewerplugins/viewerplugin.cpp
viewerplugins/viewerplugininterface.cpp
viewerplugins/viewerplugintoolmanager.cpp
)
+set(libmessageviewer_configureplugins_SRCS
+ messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.cpp
+ messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.cpp
+ messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.cpp
+ )
+
set(libmessageviewer_messagepartthemes_default_SRCS
messagepartthemes/default/converthtmltoplaintext.cpp
messagepartthemes/default/defaultrenderer.cpp
messagepartthemes/default/htmlblock.cpp
messagepartthemes/default/messagepartrenderermanager.cpp
messagepartthemes/default/plugins/attachmentmessagepartrenderer.cpp
messagepartthemes/default/plugins/messagepartrenderer.cpp
messagepartthemes/default/plugins/textmessagepartrenderer.cpp
messagepartthemes/default/plugins/quotehtml.cpp
messagepartthemes/default/messagepartrenderbase.cpp
messagepartthemes/default/messagepartrenderplugin.cpp
messagepartthemes/default/messagepartrendererfactory.cpp
)
set(libmessageviewer_interfaces_SRCS
interfaces/htmlwriter.cpp
)
set(libmessageviewer_SRCS
+ ${dkim_verify_SRCS}
${libmessageviewer_messagepartthemes_default_SRCS}
${libmessageviewer_htmlwriter_SRCS}
${libmessageviewer_messagepartthemes_SRCS}
${libmessageviewer_scamdetection_webengine_SRCS}
${libmessageviewer_widgets_webengine_SRCS}
${libmessageviewer_viewer_SRCS}
${libmessageviewer_widgets_SRCS}
${libmessageviewer_header_SRCS}
${libmessageviewer_scamdetection_SRCS}
${libmessageviewer_findbar_SRCS}
${libmessageviewer_utils_SRCS}
${libmessageviewer_antispam_SRCS}
${libmessageviewer_job_SRCS}
${libmessageviewer_viewerplugins_SRCS}
settings/messageviewersettings.cpp
${libmessageviewer_mailviewer_SRCS}
${libmessageviewer_interfaces_SRCS}
+ ${libmessageviewer_configureplugins_SRCS}
)
qt5_add_resources(libmessageviewer_SRCS messagepartthemes.qrc)
ecm_qt_declare_logging_category(libmessageviewer_SRCS HEADER messageviewer_debug.h IDENTIFIER MESSAGEVIEWER_LOG CATEGORY_NAME org.kde.pim.messageviewer)
kconfig_add_kcfg_files(libmessageviewer_SRCS
settings/globalsettings_messageviewer.kcfgc
)
ki18n_wrap_ui(libmessageviewer_SRCS
ui/settings.ui
ui/invitationsettings.ui
ui/printingsettings.ui
)
add_library(KF5MessageViewer ${libmessageviewer_SRCS})
generate_export_header(KF5MessageViewer BASE_NAME messageviewer)
add_library(KF5::MessageViewer ALIAS KF5MessageViewer)
target_include_directories(KF5MessageViewer INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/MessageViewer/;${KDE_INSTALL_INCLUDEDIR_KF5}/messageviewer>;${Inotify_INCLUDE_DIRS}")
target_link_libraries(KF5MessageViewer
PUBLIC
KF5::MessageCore
KF5::PimCommon
KF5::AkonadiCore
KF5::AkonadiMime
KF5::Contacts
KF5::Libkleo
KF5::MimeTreeParser
PRIVATE
KF5::SyntaxHighlighting
KF5::ItemViews
Qt5::Network
KF5::WebEngineViewer
KF5::LibkdepimAkonadi
KF5::GrantleeTheme
KF5::KaddressbookGrantlee
KF5::MailTransportAkonadi
KF5::Mime
KF5::Mbox
KF5::PimTextEdit
KF5::Gravatar
KF5::IconThemes
KF5::I18n
KF5::KIOFileWidgets
KF5::KIOWidgets
KF5::WindowSystem
KF5::XmlGui
Grantlee5::TextDocument
Grantlee5::Templates
Qt5::PrintSupport
QGpgme
${Inotify_LIBRARIES}
)
set_target_properties(KF5MessageViewer PROPERTIES
VERSION ${MESSAGEVIEWER_VERSION_STRING}
SOVERSION ${MESSAGEVIEWER_SOVERSION}
EXPORT_NAME MessageViewer
)
install(TARGETS
KF5MessageViewer
EXPORT KF5MessageViewerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK}
)
+ecm_generate_headers(MessageViewer_Camelblockmailtrackingurlinterceptor_HEADERS
+ HEADER_NAMES
+ BlockMailTrackingUrlInterceptor
+
+ REQUIRED_HEADERS MessageViewer_blockmailtrackingurlinterceptor_HEADERS
+ PREFIX MessageViewer
+ RELATIVE viewer/webengine/blockmailtrackingurlinterceptor
+ )
+
ecm_generate_headers(MessageViewer_Camelcasewebengine_HEADERS
HEADER_NAMES
MailWebEnginePage
MailWebEngineView
REQUIRED_HEADERS MessageViewer_webengine_HEADERS
PREFIX MessageViewer
RELATIVE viewer/webengine
)
ecm_generate_headers(MessageViewer_Camelcasescam_HEADERS
HEADER_NAMES
ScamExpandUrlJob
ScamCheckShortUrlManager
ScamCheckShortUrl
REQUIRED_HEADERS MessageViewer_scam_HEADERS
PREFIX MessageViewer
RELATIVE scamdetection
)
ecm_generate_headers(MessageViewer_Camelcaseviewer_HEADERS
HEADER_NAMES
AttachmentStrategy
Viewer
CSSHelperBase
CSSHelper
ObjectTreeEmptySource
EditorWatcher
Stl_Util
REQUIRED_HEADERS MessageViewer_viewer_HEADERS
PREFIX MessageViewer
RELATIVE viewer
)
ecm_generate_headers(MessageViewer_Camelcasewidgets_HEADERS
HEADER_NAMES
InvitationSettings
PrintingSettings
ConfigureWidget
REQUIRED_HEADERS MessageViewer_widgets_HEADERS
PREFIX MessageViewer
RELATIVE widgets
)
ecm_generate_headers(MessageViewer_Camelcaseutils_HEADERS
HEADER_NAMES
IconNameCache
MarkMessageReadHandler
MessageViewerUtil
MimeType
REQUIRED_HEADERS MessageViewer_utils_HEADERS
PREFIX MessageViewer
RELATIVE utils
)
ecm_generate_headers(MessageViewer_Camelcaseantispam_HEADERS
HEADER_NAMES
SpamHeaderAnalyzer
REQUIRED_HEADERS MessageViewer_antispam_HEADERS
PREFIX MessageViewer
RELATIVE antispam
)
ecm_generate_headers(MessageViewer_Camelcaseinterfaces_HEADERS
HEADER_NAMES
HtmlWriter
BodyPartURLHandler
URLHandler
REQUIRED_HEADERS MessageViewer_interfaces_HEADERS
PREFIX MessageViewer
RELATIVE interfaces
)
ecm_generate_headers(MessageViewer_Camelcasehtmlwriter_HEADERS
HEADER_NAMES
BufferedHtmlWriter
FileHtmlWriter
REQUIRED_HEADERS MessageViewer_htmlwriter_HEADERS
PREFIX MessageViewer
RELATIVE htmlwriter
)
ecm_generate_headers(MessageViewer_Camelcasesettings_HEADERS
HEADER_NAMES
MessageViewerSettings
REQUIRED_HEADERS MessageViewer_settings_HEADERS
PREFIX MessageViewer
RELATIVE settings
)
+ecm_generate_headers(MessageViewer_CamelcaseConfigurePlugins_HEADERS
+ HEADER_NAMES
+ MessageViewerConfigureSettingsPluginManager
+ MessageViewerConfigureSettingsPlugin
+ MessageViewerConfigureSettingsPluginWidget
+ REQUIRED_HEADERS MessageViewer_ConfigurePlugins_HEADERS
+ PREFIX MessageViewer
+ RELATIVE messageviewerconfigureplugins
+ )
+
ecm_generate_headers(MessageViewer_Camelcaseheader_HEADERS
HEADER_NAMES
HeaderStrategy
GrantleeHeaderTestStyle
GrantleeHeaderStyle
HeaderStyle
KXFace
HeaderStyle_Util
HeaderStylePlugin
HeaderStyleInterface
PlainHeaderStyle
RichHeaderStrategy
HeaderStylePluginManager
HeaderStyleMenuManager
REQUIRED_HEADERS MessageViewer_header_HEADERS
PREFIX MessageViewer
RELATIVE header
)
ecm_generate_headers(MessageViewer_Camelcaseviewerplugin_HEADERS
HEADER_NAMES
ViewerPluginManager
ViewerPlugin
ViewerPluginInterface
ViewerPluginToolManager
REQUIRED_HEADERS MessageViewer_viewerplugin_HEADERS
PREFIX MessageViewer
RELATIVE viewerplugins
)
ecm_generate_headers(MessageViewer_Camelcaserenderer_HEADERS
HEADER_NAMES
HtmlBlock
MessagePartRendererBase
MessagePartRendererManager
MessagePartRenderPlugin
REQUIRED_HEADERS MessageViewer_renderer_HEADERS
PREFIX MessageViewer
RELATIVE messagepartthemes/default
)
ecm_generate_pri_file(BASE_NAME MessageViewer
LIB_NAME KF5MessageViewer
DEPS "PimCommon MessageCore AkonadiCore AkonadiMime Contacts Libkleo MimeTreeParser" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageViewer
)
install(FILES
+ ${MessageViewer_CamelcaseConfigurePlugins_HEADERS}
${MessageViewer_Camelcasewebengine_HEADERS}
${MessageViewer_Camelcaseheader_HEADERS}
${MessageViewer_Camelcaseviewerplugin_HEADERS}
${MessageViewer_Camelcasesettings_HEADERS}
${MessageViewer_Camelcaseutils_HEADERS}
${MessageViewer_Camelcaseinterfaces_HEADERS}
${MessageViewer_Camelcasehtmlwriter_HEADERS}
${MessageViewer_Camelcaseviewer_HEADERS}
${MessageViewer_Camelcasewidgets_HEADERS}
${MessageViewer_Camelcaseantispam_HEADERS}
${MessageViewer_Camelfindbar_HEADERS}
${MessageViewer_Camelcasescam_HEADERS}
${MessageViewer_Camelcaserenderer_HEADERS}
+ ${MessageViewer_Camelblockmailtrackingurlinterceptor_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MessageViewer
COMPONENT Devel
)
install(FILES
+ ${MessageViewer_ConfigurePlugins_HEADERS}
${MessageViewer_webengine_HEADERS}
${MessageViewer_scam_HEADERS}
${MessageViewer_viewerplugin_HEADERS}
${MessageViewer_settings_HEADERS}
${MessageViewer_header_HEADERS}
${MessageViewer_utils_HEADERS}
${MessageViewer_interfaces_HEADERS}
${MessageViewer_htmlwriter_HEADERS}
${MessageViewer_HEADERS}
${MessageViewer_viewer_HEADERS}
${MessageViewer_widgets_HEADERS}
${MessageViewer_antispam_HEADERS}
${MessageViewer_findbar_HEADERS}
${MessageViewer_renderer_HEADERS}
+ ${MessageViewer_blockmailtrackingurlinterceptor_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/messageviewer_export.h
${CMAKE_CURRENT_BINARY_DIR}/globalsettings_messageviewer.h
${CMAKE_CURRENT_BINARY_DIR}/messageviewer_debug.h
${CMAKE_CURRENT_BINARY_DIR}/config-messageviewer.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/messageviewer
COMPONENT Devel
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(FILES header/data/messageviewer_header_themes.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} )
install(FILES notify/messageviewer.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} )
install(FILES scamdetection/data/longurlServices.json DESTINATION ${KDE_INSTALL_DATADIR}/messageviewer )
diff --git a/messageviewer/src/antispam/spamheaderanalyzer.cpp b/messageviewer/src/antispam/spamheaderanalyzer.cpp
index 0400eaf3..50cc99d8 100644
--- a/messageviewer/src/antispam/spamheaderanalyzer.cpp
+++ b/messageviewer/src/antispam/spamheaderanalyzer.cpp
@@ -1,192 +1,192 @@
/*
spamheaderanalyzer.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
KMail 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.
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.
*/
#include "spamheaderanalyzer.h"
#include "messageviewer_debug.h"
#include "antispamconfig.h"
#include <kmime/kmime_message.h>
#include <kmime/kmime_headers.h>
using namespace MessageViewer;
// static
SpamScores SpamHeaderAnalyzer::getSpamScores(KMime::Message *message)
{
SpamScores scores;
const SpamAgents agents = AntiSpamConfig::instance()->uniqueAgents();
SpamAgents::const_iterator end(agents.constEnd());
for (SpamAgents::const_iterator it = agents.constBegin(); it != end; ++it) {
float score = -2.0;
SpamError spamError = noError;
// Skip bogus agents
if ((*it).scoreType() == SpamAgentNone) {
continue;
}
// Do we have the needed score field for this agent?
KMime::Headers::Base *header = message->headerByType((*it).header().constData());
if (!header) {
continue;
}
const QString mField = header->asUnicodeString();
if (mField.isEmpty()) {
continue;
}
QString scoreString;
bool scoreValid = false;
if ((*it).scoreType() != SpamAgentBool) {
// Can we extract the score?
QRegExp scorePattern = (*it).scorePattern();
if (scorePattern.indexIn(mField) != -1) {
scoreString = scorePattern.cap(1);
scoreValid = true;
}
} else {
scoreValid = true;
}
if (!scoreValid) {
spamError = couldNotFindTheScoreField;
qCDebug(MESSAGEVIEWER_LOG) << "Score could not be extracted from header '"
<< mField << "'";
} else {
bool floatValid = false;
switch ((*it).scoreType()) {
case SpamAgentNone:
spamError = errorExtractingAgentString;
break;
case SpamAgentBool:
if ((*it).scorePattern().indexIn(mField) == -1) {
score = 0.0;
} else {
score = 100.0;
}
break;
case SpamAgentFloat:
score = scoreString.toFloat(&floatValid);
if (!floatValid) {
spamError = couldNotConverScoreToFloat;
qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
} else {
score *= 100.0;
}
break;
case SpamAgentFloatLarge:
score = scoreString.toFloat(&floatValid);
if (!floatValid) {
spamError = couldNotConverScoreToFloat;
qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
}
break;
case SpamAgentAdjustedFloat:
score = scoreString.toFloat(&floatValid);
if (!floatValid) {
spamError = couldNotConverScoreToFloat;
qCDebug(MESSAGEVIEWER_LOG) << "Score (" << scoreString << ") is no number";
break;
}
// Find the threshold value.
QString thresholdString;
const QRegExp thresholdPattern = (*it).thresholdPattern();
if (thresholdPattern.indexIn(mField) != -1) {
thresholdString = thresholdPattern.cap(1);
} else {
spamError = couldNotFindTheThresholdField;
qCDebug(MESSAGEVIEWER_LOG) << "Threshold could not be extracted from header '"
<< mField << "'";
break;
}
const float threshold = thresholdString.toFloat(&floatValid);
if (!floatValid || (threshold <= 0.0)) {
spamError = couldNotConvertThresholdToFloatOrThresholdIsNegative;
qCDebug(MESSAGEVIEWER_LOG) << "Threshold (" << thresholdString << ") is no"
<< "number or is negative";
break;
}
// Normalize the score. Anything below 0 means 0%, anything above
// threshold mean 100%. Values between 0 and threshold are mapped
- // linearily to 0% - 100%.
+ // linearly to 0% - 100%.
if (score < 0.0) {
score = 0.0;
} else if (score > threshold) {
score = 100.0;
} else {
score = score / threshold * 100.0;
}
break;
}
}
//Find the confidence
float confidence = -2.0;
QString confidenceString = QStringLiteral("-2.0");
bool confidenceValid = false;
// Do we have the needed confidence field for this agent?
const QByteArray confidenceHeaderName = (*it).confidenceHeader();
QString mCField;
if (!confidenceHeaderName.isEmpty()) {
KMime::Headers::Base *cHeader = message->headerByType(confidenceHeaderName.constData());
if (cHeader) {
mCField = cHeader->asUnicodeString();
if (!mCField.isEmpty()) {
// Can we extract the confidence?
QRegExp cScorePattern = (*it).confidencePattern();
if (cScorePattern.indexIn(mCField) != -1) {
confidenceString = cScorePattern.cap(1);
}
confidence = confidenceString.toFloat(&confidenceValid);
if (!confidenceValid) {
spamError = couldNotConvertConfidenceToFloat;
qCDebug(MESSAGEVIEWER_LOG) << "Unable to convert confidence to float:"
<< confidenceString;
}
}
}
}
scores.append(SpamScore((*it).name(), spamError, score, confidence * 100, mField, mCField));
}
return scores;
}
diff --git a/messageviewer/src/antispam/spamheaderanalyzer.h b/messageviewer/src/antispam/spamheaderanalyzer.h
index 40b97591..e358238a 100644
--- a/messageviewer/src/antispam/spamheaderanalyzer.h
+++ b/messageviewer/src/antispam/spamheaderanalyzer.h
@@ -1,145 +1,145 @@
/*
spamheaderanalyzer.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
KMail 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.
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.
*/
#ifndef MESSAGEVIEWER_SPAMHEADERANALYZER_H
#define MESSAGEVIEWER_SPAMHEADERANALYZER_H
#include <KMime/Message>
#include <QString>
#include <QVector>
namespace MessageViewer {
enum SpamError {
noError,
uninitializedStructUsed,
errorExtractingAgentString,
couldNotConverScoreToFloat,
couldNotConvertThresholdToFloatOrThresholdIsNegative,
couldNotFindTheScoreField,
couldNotFindTheThresholdField,
couldNotConvertConfidenceToFloat
};
/**
- @short A simple tupel of error, agent, score, confidence and header.
+ @short A simple tuple of error, agent, score, confidence and header.
The score returned is positive if no error has occurred.
error values indicate the following errors:
noError Spam Headers successfully parsed
uninitializedStructUsed Unintialized struct used
- errorExtractingAgentString Error extracing agent string
+ errorExtractingAgentString Error extracting agent string
couldNotConverScoreToFloat Couldn't convert score to float
couldNotConvertThresholdToFloatOrThresholdIsNegative Couldn't convert threshold to float or threshold is negative
couldNotFindTheScoreField Couldn't find the score field
couldNotFindTheThresholdField Couldn't find the threshold field
couldNotConvertConfidenceToFloat Couldn't convert confidence to float
*/
class SpamScore
{
public:
SpamScore()
: mError(noError)
, mScore(-2.0)
, mConfidence(-2.0)
{
}
SpamScore(const QString &agent, SpamError error, float score, float confidence, const QString &header, const QString &cheader)
: mAgent(agent)
, mError(error)
, mScore(score)
, mConfidence(confidence)
, mHeader(header)
, mConfidenceHeader(cheader)
{
}
Q_REQUIRED_RESULT QString agent() const
{
return mAgent;
}
Q_REQUIRED_RESULT float score() const
{
return mScore;
}
Q_REQUIRED_RESULT float confidence() const
{
return mConfidence;
}
Q_REQUIRED_RESULT SpamError error() const
{
return mError;
}
Q_REQUIRED_RESULT QString spamHeader() const
{
return mHeader;
}
Q_REQUIRED_RESULT QString confidenceHeader() const
{
return mConfidenceHeader;
}
private:
QString mAgent;
SpamError mError;
float mScore;
float mConfidence;
QString mHeader;
QString mConfidenceHeader;
};
typedef QVector<SpamScore> SpamScores;
/**
@short Flyweight for analysing spam headers.
@author Patrick Audley <paudley@blackcat.ca>
*/
class SpamHeaderAnalyzer
{
public:
/**
@short Extract scores from known anti-spam headers
@param message A KMime::Message to examine
@return A list of detected scores. See SpamScore
*/
static SpamScores getSpamScores(KMime::Message *message);
};
}
#endif // SPAMHEADERANALYZER_H
diff --git a/messageviewer/src/dkim-verify/CMakeLists.txt b/messageviewer/src/dkim-verify/CMakeLists.txt
new file mode 100644
index 00000000..8da42ea0
--- /dev/null
+++ b/messageviewer/src/dkim-verify/CMakeLists.txt
@@ -0,0 +1,3 @@
+if (BUILD_TESTING)
+ add_subdirectory(autotests)
+endif()
diff --git a/messageviewer/src/dkim-verify/autotests/CMakeLists.txt b/messageviewer/src/dkim-verify/autotests/CMakeLists.txt
new file mode 100644
index 00000000..951bc19e
--- /dev/null
+++ b/messageviewer/src/dkim-verify/autotests/CMakeLists.txt
@@ -0,0 +1,17 @@
+macro(add_messageviewer_dkim_verify_unittest _source)
+ get_filename_component(_name ${_source} NAME_WE)
+ ecm_add_test(${_source}
+ TEST_NAME ${_name}
+ NAME_PREFIX "messageviewer-"
+ LINK_LIBRARIES KF5::MessageViewer KF5::WebEngineViewer KF5::Libkleo QGpgme Qt5::Test
+ )
+endmacro ()
+
+add_messageviewer_dkim_verify_unittest(dkiminfotest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimmanagerkeytest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimmanagerkeywidgettest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimmanagerkeydialogtest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimdownloadkeyjobtest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimchecksignaturejobtest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimcheckauthenticationstatusjobtest.cpp)
+add_messageviewer_dkim_verify_unittest(dkimauthenticationstatusinfotest.cpp)
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.cpp
similarity index 64%
copy from webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.cpp
index 3a383811..d38bf379 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.cpp
@@ -1,34 +1,35 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "downloadlocaldatabasethreadtest.h"
-#include "../downloadlocaldatabasethread.h"
-
+#include "dkimauthenticationstatusinfotest.h"
+#include "dkim-verify/dkimauthenticationstatusinfo.h"
#include <QTest>
-DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
+QTEST_GUILESS_MAIN(DKIMAuthenticationStatusInfoTest)
+
+DKIMAuthenticationStatusInfoTest::DKIMAuthenticationStatusInfoTest(QObject *parent)
: QObject(parent)
{
}
-DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
+void DKIMAuthenticationStatusInfoTest::shouldHaveDefaultValue()
{
+ MessageViewer::DKIMAuthenticationStatusInfo info;
+ QVERIFY(info.authservId().isEmpty());
}
-
-QTEST_MAIN(DownloadLocalDatabaseThreadTest)
diff --git a/templateparser/autotests/templatesinsertcommandactiontest.h b/messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.h
similarity index 71%
copy from templateparser/autotests/templatesinsertcommandactiontest.h
copy to messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.h
index b4927e28..caeaca8d 100644
--- a/templateparser/autotests/templatesinsertcommandactiontest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimauthenticationstatusinfotest.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TEMPLATESINSERTCOMMANDACTIONTEST_H
-#define TEMPLATESINSERTCOMMANDACTIONTEST_H
+#ifndef DKIMAUTHENTICATIONSTATUSINFOTEST_H
+#define DKIMAUTHENTICATIONSTATUSINFOTEST_H
#include <QObject>
-class TemplatesInsertCommandActionTest : public QObject
+class DKIMAuthenticationStatusInfoTest : public QObject
{
Q_OBJECT
public:
- explicit TemplatesInsertCommandActionTest(QObject *parent = nullptr);
- ~TemplatesInsertCommandActionTest() = default;
+ explicit DKIMAuthenticationStatusInfoTest(QObject *parent = nullptr);
+ ~DKIMAuthenticationStatusInfoTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
};
-#endif // TEMPLATESINSERTCOMMANDACTIONTEST_H
+#endif // DKIMAUTHENTICATIONSTATUSINFOTEST_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.cpp
similarity index 72%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.cpp
index 106dabf7..accebe6f 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.cpp
@@ -1,29 +1,28 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templateparserextracthtmlinforesult.h"
+#include "dkimcheckauthenticationstatusjobtest.h"
+#include <QTest>
-void TemplateParserExtractHtmlInfoResult::clear()
+QTEST_MAIN(DKIMCheckAuthenticationStatusJobTest)
+
+DKIMCheckAuthenticationStatusJobTest::DKIMCheckAuthenticationStatusJobTest(QObject *parent)
+ : QObject(parent)
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
}
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.h
similarity index 67%
copy from webengineviewer/src/autotests/zoomactionmenutest.h
copy to messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.h
index 000e7ca0..ccd2dafb 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimcheckauthenticationstatusjobtest.h
@@ -1,36 +1,33 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef DKIMCHECKAUTHENTICATIONSTATUSJOBTEST_H
+#define DKIMCHECKAUTHENTICATIONSTATUSJOBTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class DKIMCheckAuthenticationStatusJobTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
-private Q_SLOTS:
- void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
+ explicit DKIMCheckAuthenticationStatusJobTest(QObject *parent = nullptr);
+ ~DKIMCheckAuthenticationStatusJobTest() = default;
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // DKIMCHECKAUTHENTICATIONSTATUSJOBTEST_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.cpp
similarity index 73%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.cpp
index 106dabf7..0078a84c 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.cpp
@@ -1,29 +1,27 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templateparserextracthtmlinforesult.h"
+#include "dkimchecksignaturejobtest.h"
+#include <QTest>
+QTEST_MAIN(DKIMCheckSignatureJobTest)
-void TemplateParserExtractHtmlInfoResult::clear()
+DKIMCheckSignatureJobTest::DKIMCheckSignatureJobTest(QObject *parent)
+ : QObject(parent)
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
}
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h b/messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.h
similarity index 71%
copy from webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
copy to messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.h
index 0bf0ff2b..d670e620 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimchecksignaturejobtest.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef LOCALDATABASEMANAGERTEST_H
-#define LOCALDATABASEMANAGERTEST_H
+#ifndef DKIMCHECKSIGNATUREJOBTEST_H
+#define DKIMCHECKSIGNATUREJOBTEST_H
#include <QObject>
-class LocalDataBaseManagerTest : public QObject
+class DKIMCheckSignatureJobTest : public QObject
{
Q_OBJECT
public:
- explicit LocalDataBaseManagerTest(QObject *parent = nullptr);
- ~LocalDataBaseManagerTest();
+ explicit DKIMCheckSignatureJobTest(QObject *parent = nullptr);
+ ~DKIMCheckSignatureJobTest() = default;
};
-#endif // LOCALDATABASEMANAGERTEST_H
+#endif // DKIMCHECKSIGNATUREJOBTEST_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.cpp
similarity index 73%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.cpp
index 106dabf7..9072ba65 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.cpp
@@ -1,29 +1,27 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templateparserextracthtmlinforesult.h"
+#include "dkimdownloadkeyjobtest.h"
+#include <QTest>
+QTEST_MAIN(DKIMDownloadKeyJobTest)
-void TemplateParserExtractHtmlInfoResult::clear()
+DKIMDownloadKeyJobTest::DKIMDownloadKeyJobTest(QObject *parent)
+ : QObject(parent)
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
}
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h b/messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.h
similarity index 72%
copy from webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
copy to messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.h
index 0bf0ff2b..5529c582 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimdownloadkeyjobtest.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef LOCALDATABASEMANAGERTEST_H
-#define LOCALDATABASEMANAGERTEST_H
+#ifndef DKIMDOWNLOADKEYJOBTEST_H
+#define DKIMDOWNLOADKEYJOBTEST_H
#include <QObject>
-class LocalDataBaseManagerTest : public QObject
+class DKIMDownloadKeyJobTest : public QObject
{
Q_OBJECT
public:
- explicit LocalDataBaseManagerTest(QObject *parent = nullptr);
- ~LocalDataBaseManagerTest();
+ explicit DKIMDownloadKeyJobTest(QObject *parent = nullptr);
+ ~DKIMDownloadKeyJobTest() = default;
};
-#endif // LOCALDATABASEMANAGERTEST_H
+#endif // DKIMDOWNLOADKEYJOBTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp
similarity index 63%
copy from webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
copy to messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp
index 3a383811..e71c7e0f 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp
@@ -1,34 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "downloadlocaldatabasethreadtest.h"
-#include "../downloadlocaldatabasethread.h"
-
+#include "dkiminfotest.h"
+#include "dkim-verify/dkiminfo.h"
#include <QTest>
-DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
+QTEST_GUILESS_MAIN(DKIMInfoTest)
+
+DKIMInfoTest::DKIMInfoTest(QObject *parent)
: QObject(parent)
{
}
-DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
+void DKIMInfoTest::shouldHaveDefaultValue()
{
-}
+ MessageViewer::DKIMInfo info;
+ QVERIFY(info.version().isEmpty());
-QTEST_MAIN(DownloadLocalDatabaseThreadTest)
+ QVERIFY(info.hashingAlgorithm().isEmpty());
+ QVERIFY(info.domain().isEmpty());
+ QVERIFY(info.selector().isEmpty());
+ QVERIFY(info.bodyHash().isEmpty());
+}
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/messageviewer/src/dkim-verify/autotests/dkiminfotest.h
similarity index 73%
copy from webengineviewer/src/autotests/zoomactionmenutest.h
copy to messageviewer/src/dkim-verify/autotests/dkiminfotest.h
index 000e7ca0..c833fec1 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkiminfotest.h
@@ -1,36 +1,35 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef DKIMINFOTEST_H
+#define DKIMINFOTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class DKIMInfoTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
+ explicit DKIMInfoTest(QObject *parent = nullptr);
+ ~DKIMInfoTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // DKIMINFOTEST_H
diff --git a/messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.cpp b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.cpp
new file mode 100644
index 00000000..b0b76a7b
--- /dev/null
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.cpp
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
+*/
+
+#include "dkimmanagerkeydialogtest.h"
+#include "dkim-verify/dkimmanagerkeydialog.h"
+#include "dkim-verify/dkimmanagerkeywidget.h"
+#include <QVBoxLayout>
+#include <QDialogButtonBox>
+#include <QTest>
+#include <QStandardPaths>
+
+QTEST_MAIN(DKIMManagerKeyDialogTest)
+
+DKIMManagerKeyDialogTest::DKIMManagerKeyDialogTest(QObject *parent)
+ : QObject(parent)
+{
+ QStandardPaths::setTestModeEnabled(true);
+}
+
+DKIMManagerKeyDialogTest::~DKIMManagerKeyDialogTest()
+{
+}
+
+void DKIMManagerKeyDialogTest::shouldHaveDefaultValue()
+{
+ MessageViewer::DKIMManagerKeyDialog dlg;
+
+ QVBoxLayout *mainLayout = dlg.findChild<QVBoxLayout *>(QStringLiteral("mainlayout"));
+ QVERIFY(mainLayout);
+
+ MessageViewer::DKIMManagerKeyWidget *w = dlg.findChild<MessageViewer::DKIMManagerKeyWidget *>(QStringLiteral("managerWidget"));
+ QVERIFY(w);
+
+ QDialogButtonBox *buttonBox = dlg.findChild<QDialogButtonBox *>(QStringLiteral("buttonbox"));
+ QVERIFY(buttonBox);
+ QCOMPARE(buttonBox->standardButtons(), {QDialogButtonBox::Close});
+}
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.h
similarity index 73%
copy from webengineviewer/src/autotests/zoomactionmenutest.h
copy to messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.h
index 000e7ca0..4a18f10c 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeydialogtest.h
@@ -1,36 +1,35 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef DKIMMANAGERKEYDIALOGTEST_H
+#define DKIMMANAGERKEYDIALOGTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class DKIMManagerKeyDialogTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
+ explicit DKIMManagerKeyDialogTest(QObject *parent = nullptr);
+ ~DKIMManagerKeyDialogTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // DKIMMANAGERKEYDIALOGTEST_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.cpp
similarity index 73%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.cpp
index 106dabf7..cdac1303 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.cpp
@@ -1,29 +1,27 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
+#include "dkimmanagerkeytest.h"
+#include <QTest>
-#include "templateparserextracthtmlinforesult.h"
+QTEST_MAIN(DKIMManagerKeyTest)
-void TemplateParserExtractHtmlInfoResult::clear()
+DKIMManagerKeyTest::DKIMManagerKeyTest(QObject *parent)
+ : QObject(parent)
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
}
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.h
similarity index 72%
copy from webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
copy to messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.h
index 0bf0ff2b..359f41a3 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeytest.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef LOCALDATABASEMANAGERTEST_H
-#define LOCALDATABASEMANAGERTEST_H
+#ifndef DKIMMANAGERKEYTEST_H
+#define DKIMMANAGERKEYTEST_H
#include <QObject>
-class LocalDataBaseManagerTest : public QObject
+class DKIMManagerKeyTest : public QObject
{
Q_OBJECT
public:
- explicit LocalDataBaseManagerTest(QObject *parent = nullptr);
- ~LocalDataBaseManagerTest();
+ explicit DKIMManagerKeyTest(QObject *parent = nullptr);
+ ~DKIMManagerKeyTest() = default;
};
-#endif // LOCALDATABASEMANAGERTEST_H
+#endif // DKIMMANAGERKEYTEST_H
diff --git a/templateparser/autotests/templatesinsertcommandactiontest.cpp b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.cpp
similarity index 54%
copy from templateparser/autotests/templatesinsertcommandactiontest.cpp
copy to messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.cpp
index bc0a477a..43524cc8 100644
--- a/templateparser/autotests/templatesinsertcommandactiontest.cpp
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.cpp
@@ -1,40 +1,42 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templatesinsertcommandactiontest.h"
-#include "templatesinsertcommandaction.h"
-#include "templatescommandmenu.h"
-#include <QMenu>
+#include "dkimmanagerkeywidgettest.h"
+#include "dkim-verify/dkimmanagerkeywidget.h"
#include <QTest>
-QTEST_MAIN(TemplatesInsertCommandActionTest)
-
-TemplatesInsertCommandActionTest::TemplatesInsertCommandActionTest(QObject *parent)
+#include <QTreeWidget>
+#include <QVBoxLayout>
+QTEST_MAIN(DKIMManagerKeyWidgetTest)
+DKIMManagerKeyWidgetTest::DKIMManagerKeyWidgetTest(QObject *parent)
: QObject(parent)
{
-
}
-void TemplatesInsertCommandActionTest::shouldHaveDefaultValue()
+void DKIMManagerKeyWidgetTest::shouldHaveDefaultValue()
{
- TemplateParser::TemplatesInsertCommandAction act;
- QVERIFY(act.menu());
- QVERIFY(!act.menu()->isEmpty());
- TemplateParser::TemplatesCommandMenu *menu = act.findChild<TemplateParser::TemplatesCommandMenu *>(QStringLiteral("templatescommandmenu"));
- QVERIFY(menu);
+ MessageViewer::DKIMManagerKeyWidget w;
+
+ QVBoxLayout *mainLayout = w.findChild<QVBoxLayout *>(QStringLiteral("mainlayout"));
+ QVERIFY(mainLayout);
+ QCOMPARE(mainLayout->margin(), 0);
+
+ QTreeWidget *mTreeWidget = w.findChild<QTreeWidget *>(QStringLiteral("treewidget"));
+ QVERIFY(mTreeWidget);
+ //TODO verify that it's empty
}
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.h
similarity index 73%
copy from webengineviewer/src/autotests/zoomactionmenutest.h
copy to messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.h
index 000e7ca0..6da8168c 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/dkim-verify/autotests/dkimmanagerkeywidgettest.h
@@ -1,36 +1,35 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef DKIMMANAGERKEYWIDGETTEST_H
+#define DKIMMANAGERKEYWIDGETTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class DKIMManagerKeyWidgetTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
+ explicit DKIMManagerKeyWidgetTest(QObject *parent = nullptr);
+ ~DKIMManagerKeyWidgetTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // DKIMMANAGERKEYWIDGETTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.cpp
similarity index 67%
copy from webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
copy to messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.cpp
index 3a383811..cbe0f334 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.cpp
@@ -1,34 +1,35 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "downloadlocaldatabasethreadtest.h"
-#include "../downloadlocaldatabasethread.h"
+#include "dkimauthenticationstatusinfo.h"
-#include <QTest>
-
-DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
- : QObject(parent)
+using namespace MessageViewer;
+DKIMAuthenticationStatusInfo::DKIMAuthenticationStatusInfo()
{
}
-DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
+QString DKIMAuthenticationStatusInfo::authservId() const
{
+ return mAuthservId;
}
-QTEST_MAIN(DownloadLocalDatabaseThreadTest)
+void DKIMAuthenticationStatusInfo::setAuthservId(const QString &authservId)
+{
+ mAuthservId = authservId;
+}
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h b/messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.h
similarity index 63%
copy from messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
copy to messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.h
index f860aeed..1427bc25 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
+++ b/messageviewer/src/dkim-verify/dkimauthenticationstatusinfo.h
@@ -1,39 +1,40 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TEST_VIEWERPLUGIN_GUI_H
-#define TEST_VIEWERPLUGIN_GUI_H
+#ifndef DKIMAUTHENTICATIONSTATUSINFO_H
+#define DKIMAUTHENTICATIONSTATUSINFO_H
-#include <QWidget>
-namespace MessageViewer {
-class ViewerPluginInterface;
-}
+#include "messageviewer_private_export.h"
+#include <QString>
-class ViewerPluginTest : public QWidget
+namespace MessageViewer {
+class MESSAGEVIEWER_TESTS_EXPORT DKIMAuthenticationStatusInfo
{
- Q_OBJECT
public:
- explicit ViewerPluginTest(QWidget *parent = nullptr);
- ~ViewerPluginTest();
+ DKIMAuthenticationStatusInfo();
-private Q_SLOTS:
- void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
+ Q_REQUIRED_RESULT QString authservId() const;
+ void setAuthservId(const QString &authservId);
+
+private:
+ QString mAuthservId;
};
+}
-#endif
+#endif // DKIMAUTHENTICATIONSTATUSINFO_H
diff --git a/messageviewer/src/viewerplugins/viewerplugin.cpp b/messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.cpp
similarity index 59%
copy from messageviewer/src/viewerplugins/viewerplugin.cpp
copy to messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.cpp
index 36fb75eb..dd680ca7 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.cpp
+++ b/messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.cpp
@@ -1,63 +1,47 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "viewerplugin.h"
-
+#include "dkimcheckauthenticationstatusjob.h"
+#include "messageviewer_debug.h"
using namespace MessageViewer;
-class MessageViewer::ViewerPluginPrivate
-{
-public:
- ViewerPluginPrivate()
- {
- }
-
- bool mEnabled = false;
-};
-
-ViewerPlugin::ViewerPlugin(QObject *parent)
+DKIMCheckAuthenticationStatusJob::DKIMCheckAuthenticationStatusJob(QObject *parent)
: QObject(parent)
- , d(new MessageViewer::ViewerPluginPrivate)
{
}
-ViewerPlugin::~ViewerPlugin()
+DKIMCheckAuthenticationStatusJob::~DKIMCheckAuthenticationStatusJob()
{
- delete d;
}
-void ViewerPlugin::showConfigureDialog(QWidget *parent)
+void DKIMCheckAuthenticationStatusJob::start()
{
- Q_UNUSED(parent);
+ if (!canStart()) {
+ qCWarning(MESSAGEVIEWER_LOG) << "Impossible to start job";
+ deleteLater();
+ return;
+ }
+ //TODO
}
-bool ViewerPlugin::hasConfigureDialog() const
+bool DKIMCheckAuthenticationStatusJob::canStart() const
{
+ //TODO
return false;
}
-
-void ViewerPlugin::setIsEnabled(bool enabled)
-{
- d->mEnabled = enabled;
-}
-
-bool ViewerPlugin::isEnabled() const
-{
- return d->mEnabled;
-}
diff --git a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h b/messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.h
similarity index 63%
copy from messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
copy to messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.h
index c11ebb3a..e66a8d40 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
+++ b/messageviewer/src/dkim-verify/dkimcheckauthenticationstatusjob.h
@@ -1,39 +1,39 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TESTWEBENGINESCROLLADDATTACHMENT_H
-#define TESTWEBENGINESCROLLADDATTACHMENT_H
+#ifndef DKIMCHECKAUTHENTICATIONSTATUSJOB_H
+#define DKIMCHECKAUTHENTICATIONSTATUSJOB_H
-#include <QWidget>
+#include <QObject>
+#include "messageviewer_private_export.h"
namespace MessageViewer {
-class MailWebEngineView;
-}
-class TestWebEngineScrollAddAttachment : public QWidget
+class MESSAGEVIEWER_TESTS_EXPORT DKIMCheckAuthenticationStatusJob : public QObject
{
Q_OBJECT
public:
- explicit TestWebEngineScrollAddAttachment(QWidget *parent = nullptr);
+ explicit DKIMCheckAuthenticationStatusJob(QObject *parent = nullptr);
+ ~DKIMCheckAuthenticationStatusJob();
+
+ void start();
-private Q_SLOTS:
- void slotScrollToAttachment();
-private:
- MessageViewer::MailWebEngineView *mTestWebEngine;
+ bool canStart() const;
};
+}
-#endif // TESTWEBENGINESCROLLADDATTACHMENT_H
+#endif // DKIMCHECKAUTHENTICATIONSTATUSJOB_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp
similarity index 59%
copy from webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
copy to messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp
index 3a383811..038d7514 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp
@@ -1,34 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "downloadlocaldatabasethreadtest.h"
-#include "../downloadlocaldatabasethread.h"
-
-#include <QTest>
-
-DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
+#include "dkimchecksignaturejob.h"
+#include "messageviewer_debug.h"
+using namespace MessageViewer;
+DKIMCheckSignatureJob::DKIMCheckSignatureJob(QObject *parent)
: QObject(parent)
{
}
-DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
+DKIMCheckSignatureJob::~DKIMCheckSignatureJob()
{
}
-QTEST_MAIN(DownloadLocalDatabaseThreadTest)
+void DKIMCheckSignatureJob::start()
+{
+ if (!canStart()) {
+ qCWarning(MESSAGEVIEWER_LOG) << "Impossible to start job";
+ deleteLater();
+ return;
+ }
+ //TODO
+ deleteLater();
+}
+
+bool DKIMCheckSignatureJob::canStart() const
+{
+ //TODO
+ return false;
+}
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h b/messageviewer/src/dkim-verify/dkimchecksignaturejob.h
similarity index 66%
copy from messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
copy to messageviewer/src/dkim-verify/dkimchecksignaturejob.h
index f860aeed..b88ae6e3 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
+++ b/messageviewer/src/dkim-verify/dkimchecksignaturejob.h
@@ -1,39 +1,38 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TEST_VIEWERPLUGIN_GUI_H
-#define TEST_VIEWERPLUGIN_GUI_H
+#ifndef DKIMCHECKSIGNATUREJOB_H
+#define DKIMCHECKSIGNATUREJOB_H
-#include <QWidget>
+#include <QObject>
+#include "messageviewer_private_export.h"
namespace MessageViewer {
-class ViewerPluginInterface;
-}
-
-class ViewerPluginTest : public QWidget
+class MESSAGEVIEWER_TESTS_EXPORT DKIMCheckSignatureJob : public QObject
{
Q_OBJECT
public:
- explicit ViewerPluginTest(QWidget *parent = nullptr);
- ~ViewerPluginTest();
+ explicit DKIMCheckSignatureJob(QObject *parent = nullptr);
+ ~DKIMCheckSignatureJob();
+ void start();
-private Q_SLOTS:
- void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
+ bool canStart() const;
};
+}
-#endif
+#endif // DKIMCHECKSIGNATUREJOB_H
diff --git a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp b/messageviewer/src/dkim-verify/dkimdownloadkeyjob.cpp
similarity index 60%
copy from messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp
copy to messageviewer/src/dkim-verify/dkimdownloadkeyjob.cpp
index 9d49b0d0..16427ad6 100644
--- a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp
+++ b/messageviewer/src/dkim-verify/dkimdownloadkeyjob.cpp
@@ -1,36 +1,46 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "mailtrackingdetailsdialogtest.h"
-#include "widgets/mailtrackingdetailsdialog.h"
-#include <QTest>
-#include <QStandardPaths>
+#include "dkimdownloadkeyjob.h"
+#include "messageviewer_debug.h"
+using namespace MessageViewer;
+DKIMDownloadKeyJob::DKIMDownloadKeyJob(QObject *parent)
+ : QObject(parent)
+{
+}
-QTEST_MAIN(MailTrackingDetailsDialogTest)
+DKIMDownloadKeyJob::~DKIMDownloadKeyJob()
+{
+}
-MailTrackingDetailsDialogTest::MailTrackingDetailsDialogTest(QObject *parent)
- : QObject(parent)
+void DKIMDownloadKeyJob::start()
{
- QStandardPaths::setTestModeEnabled(true);
+ if (!canStart()) {
+ qCWarning(MESSAGEVIEWER_LOG) << "Impossible to start download public keys";
+ deleteLater();
+ return;
+ }
+ //TODO
}
-void MailTrackingDetailsDialogTest::shouldHaveDefaultValue()
+bool DKIMDownloadKeyJob::canStart() const
{
- //TOOD
+ //TODO
+ return false;
}
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h b/messageviewer/src/dkim-verify/dkimdownloadkeyjob.h
similarity index 67%
copy from messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
copy to messageviewer/src/dkim-verify/dkimdownloadkeyjob.h
index f860aeed..7d4da9c8 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
+++ b/messageviewer/src/dkim-verify/dkimdownloadkeyjob.h
@@ -1,39 +1,42 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TEST_VIEWERPLUGIN_GUI_H
-#define TEST_VIEWERPLUGIN_GUI_H
+#ifndef DKIMDOWNLOADKEYJOB_H
+#define DKIMDOWNLOADKEYJOB_H
-#include <QWidget>
+#include <QObject>
namespace MessageViewer {
-class ViewerPluginInterface;
-}
-
-class ViewerPluginTest : public QWidget
+class DKIMDownloadKeyJob : public QObject
{
Q_OBJECT
public:
- explicit ViewerPluginTest(QWidget *parent = nullptr);
- ~ViewerPluginTest();
+ explicit DKIMDownloadKeyJob(QObject *parent = nullptr);
+ ~DKIMDownloadKeyJob();
+
+ void start();
-private Q_SLOTS:
- void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
+ bool canStart() const;
+
+Q_SIGNALS:
+ void success();
+ void error(const QString &err);
};
+}
-#endif
+#endif // DKIMDOWNLOADKEYJOB_H
diff --git a/messageviewer/src/dkim-verify/dkiminfo.cpp b/messageviewer/src/dkim-verify/dkiminfo.cpp
new file mode 100644
index 00000000..84b008aa
--- /dev/null
+++ b/messageviewer/src/dkim-verify/dkiminfo.cpp
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
+*/
+
+#include "dkiminfo.h"
+#include "messageviewer_debug.h"
+
+#include <QStringList>
+#include "messageviewer_debug.h"
+using namespace MessageViewer;
+
+DKIMInfo::DKIMInfo()
+{
+}
+
+void DKIMInfo::parseDKIM(const QString &header)
+{
+ if (header.isEmpty()) {
+ qCWarning(MESSAGEVIEWER_LOG) << "Error: trying to parse empty header";
+ //TODO error ?
+ return;
+ }
+ const QStringList items = header.split(QLatin1Char(';'));
+ for (int i = 0; i < items.count(); ++i) {
+ const QString elem = items.at(i).trimmed();
+ if (elem.startsWith(QStringLiteral("v="))) {
+ mVersion = elem.right(elem.length() - 2);
+ } else if (elem.startsWith(QStringLiteral("a="))) {
+ } else if (elem.startsWith(QStringLiteral("c="))) {
+ } else if (elem.startsWith(QStringLiteral("i="))) {
+ } else if (elem.startsWith(QStringLiteral("q="))) {
+ } else if (elem.startsWith(QStringLiteral("d="))) {
+ } else if (elem.startsWith(QStringLiteral("s="))) {
+ } else if (elem.startsWith(QStringLiteral("b="))) {
+ } else if (elem.startsWith(QStringLiteral("h="))) {
+ } else if (elem.startsWith(QStringLiteral("hb="))) {
+ } else {
+ qCWarning(MESSAGEVIEWER_LOG) << " Unknown element type" << elem;
+ }
+ }
+ //TODO
+}
+
+QString DKIMInfo::version() const
+{
+ return mVersion;
+}
+
+void DKIMInfo::setVersion(const QString &version)
+{
+ mVersion = version;
+}
+
+QString DKIMInfo::hashingAlgorithm() const
+{
+ return mHashingAlgorithm;
+}
+
+void DKIMInfo::setHashingAlgorithm(const QString &hashingAlgorithm)
+{
+ mHashingAlgorithm = hashingAlgorithm;
+}
+
+QString DKIMInfo::domain() const
+{
+ return mDomain;
+}
+
+void DKIMInfo::setDomain(const QString &domain)
+{
+ mDomain = domain;
+}
+
+QString DKIMInfo::selector() const
+{
+ return mSelector;
+}
+
+void DKIMInfo::setSelector(const QString &selector)
+{
+ mSelector = selector;
+}
+
+QString DKIMInfo::bodyHash() const
+{
+ return mBodyHash;
+}
+
+void DKIMInfo::setBodyHash(const QString &bodyHash)
+{
+ mBodyHash = bodyHash;
+}
+
+bool DKIMInfo::isValid() const
+{
+ //TODO verify
+
+ return !mSelector.isEmpty() && !mDomain.isEmpty() && !mBodyHash.isEmpty() && !mHashingAlgorithm.isEmpty();
+}
diff --git a/messageviewer/src/dkim-verify/dkiminfo.h b/messageviewer/src/dkim-verify/dkiminfo.h
new file mode 100644
index 00000000..a26b2dec
--- /dev/null
+++ b/messageviewer/src/dkim-verify/dkiminfo.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
+*/
+
+#ifndef DKIMINFO_H
+#define DKIMINFO_H
+
+#include "messageviewer_private_export.h"
+#include <QString>
+namespace MessageViewer {
+class MESSAGEVIEWER_TESTS_EXPORT DKIMInfo
+{
+public:
+ DKIMInfo();
+
+ void parseDKIM(const QString &header);
+ Q_REQUIRED_RESULT QString version() const;
+ void setVersion(const QString &version);
+
+ Q_REQUIRED_RESULT QString hashingAlgorithm() const;
+ void setHashingAlgorithm(const QString &hashingAlgorithm);
+
+ Q_REQUIRED_RESULT QString domain() const;
+ void setDomain(const QString &domain);
+
+ Q_REQUIRED_RESULT QString selector() const;
+ void setSelector(const QString &selector);
+
+ Q_REQUIRED_RESULT QString bodyHash() const;
+ void setBodyHash(const QString &bodyHash);
+
+ Q_REQUIRED_RESULT bool isValid() const;
+
+private:
+ QString mVersion;
+ QString mHashingAlgorithm;
+ QString mDomain;
+ QString mSelector;
+ QString mBodyHash;
+};
+}
+
+#endif // DKIMINFO_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/messageviewer/src/dkim-verify/dkimmanagerkey.cpp
similarity index 67%
copy from webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
copy to messageviewer/src/dkim-verify/dkimmanagerkey.cpp
index 3a383811..b4fd066d 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/messageviewer/src/dkim-verify/dkimmanagerkey.cpp
@@ -1,34 +1,46 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "downloadlocaldatabasethreadtest.h"
-#include "../downloadlocaldatabasethread.h"
+#include "dkimmanagerkey.h"
-#include <QTest>
-
-DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
+using namespace MessageViewer;
+DKIMManagerKey::DKIMManagerKey(QObject *parent)
: QObject(parent)
{
}
-DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
+DKIMManagerKey::~DKIMManagerKey()
{
}
-QTEST_MAIN(DownloadLocalDatabaseThreadTest)
+DKIMManagerKey *DKIMManagerKey::self()
+{
+ static DKIMManagerKey s_self;
+ return &s_self;
+}
+
+void DKIMManagerKey::loadKeys()
+{
+ //TODO
+}
+
+void DKIMManagerKey::saveKeys()
+{
+ //TODO
+}
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h b/messageviewer/src/dkim-verify/dkimmanagerkey.h
similarity index 66%
copy from messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
copy to messageviewer/src/dkim-verify/dkimmanagerkey.h
index f860aeed..0d96137b 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
+++ b/messageviewer/src/dkim-verify/dkimmanagerkey.h
@@ -1,39 +1,40 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef TEST_VIEWERPLUGIN_GUI_H
-#define TEST_VIEWERPLUGIN_GUI_H
+#ifndef DKIMMANAGERKEY_H
+#define DKIMMANAGERKEY_H
-#include <QWidget>
+#include <QObject>
+#include "messageviewer_private_export.h"
namespace MessageViewer {
-class ViewerPluginInterface;
-}
-
-class ViewerPluginTest : public QWidget
+class MESSAGEVIEWER_TESTS_EXPORT DKIMManagerKey : public QObject
{
Q_OBJECT
public:
- explicit ViewerPluginTest(QWidget *parent = nullptr);
- ~ViewerPluginTest();
+ explicit DKIMManagerKey(QObject *parent = nullptr);
+ ~DKIMManagerKey() override;
-private Q_SLOTS:
- void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
+ static DKIMManagerKey *self();
+
+ void loadKeys();
+ void saveKeys();
};
+}
-#endif
+#endif // DKIMMANAGERKEY_H
diff --git a/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp b/messageviewer/src/dkim-verify/dkimmanagerkeydialog.cpp
similarity index 53%
copy from messageviewer/src/widgets/mailtrackingdetailsdialog.cpp
copy to messageviewer/src/dkim-verify/dkimmanagerkeydialog.cpp
index b2a2b806..030f279b 100644
--- a/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp
+++ b/messageviewer/src/dkim-verify/dkimmanagerkeydialog.cpp
@@ -1,79 +1,77 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
+#include "dkimmanagerkeydialog.h"
+#include "dkimmanagerkeywidget.h"
-#include "mailtrackingdetailsdialog.h"
-#include <QVBoxLayout>
#include <KLocalizedString>
+
+#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <KConfigGroup>
-#include <QPushButton>
#include <KSharedConfig>
-#include <KPIMTextEdit/RichTextEditorWidget>
using namespace MessageViewer;
-MailTrackingDetailsDialog::MailTrackingDetailsDialog(QWidget *parent)
+DKIMManagerKeyDialog::DKIMManagerKeyDialog(QWidget *parent)
: QDialog(parent)
{
- setWindowTitle(i18n("Details"));
- setAttribute(Qt::WA_DeleteOnClose);
- setModal(false);
-
QVBoxLayout *mainLayout = new QVBoxLayout(this);
- mainLayout->setObjectName(QStringLiteral("mainLayout"));
+ mainLayout->setObjectName(QStringLiteral("mainlayout"));
+
+ mManagerWidget = new DKIMManagerKeyWidget(this);
+ mManagerWidget->setObjectName(QStringLiteral("managerWidget"));
+ mainLayout->addWidget(mManagerWidget);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
buttonBox->setObjectName(QStringLiteral("buttonbox"));
- connect(buttonBox, &QDialogButtonBox::rejected, this, &MailTrackingDetailsDialog::reject);
- connect(buttonBox->button(
- QDialogButtonBox::Close), &QPushButton::clicked, this,
- &MailTrackingDetailsDialog::close);
-
- mDetails = new KPIMTextEdit::RichTextEditorWidget(this);
- mDetails->setObjectName(QStringLiteral("detail"));
- mainLayout->addWidget(mDetails);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &DKIMManagerKeyDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &DKIMManagerKeyDialog::reject);
mainLayout->addWidget(buttonBox);
- mDetails->setReadOnly(true);
readConfig();
}
-MailTrackingDetailsDialog::~MailTrackingDetailsDialog()
+DKIMManagerKeyDialog::~DKIMManagerKeyDialog()
{
writeConfig();
}
-void MailTrackingDetailsDialog::readConfig()
+void DKIMManagerKeyDialog::readConfig()
{
- KConfigGroup group(KSharedConfig::openConfig(), "MailTrackingDetailsDialog");
+ KConfigGroup group(KSharedConfig::openConfig(), "DKIMManagerKeyDialog");
const QSize size = group.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
-void MailTrackingDetailsDialog::writeConfig()
+void DKIMManagerKeyDialog::writeConfig()
{
- KConfigGroup group(KSharedConfig::openConfig(), "MailTrackingDetailsDialog");
+ KConfigGroup group(KSharedConfig::openConfig(), "DKIMManagerKeyDialog");
group.writeEntry("Size", size());
group.sync();
}
-void MailTrackingDetailsDialog::setDetails(const QString &details)
+void DKIMManagerKeyDialog::loadKeys()
+{
+ mManagerWidget->loadKeys();
+}
+
+void DKIMManagerKeyDialog::saveKeys()
{
- mDetails->setHtml(details);
+ mManagerWidget->saveKeys();
}
diff --git a/messageviewer/src/widgets/mailtrackingdetailsdialog.h b/messageviewer/src/dkim-verify/dkimmanagerkeydialog.h
similarity index 65%
copy from messageviewer/src/widgets/mailtrackingdetailsdialog.h
copy to messageviewer/src/dkim-verify/dkimmanagerkeydialog.h
index 571712b4..9f47d8ed 100644
--- a/messageviewer/src/widgets/mailtrackingdetailsdialog.h
+++ b/messageviewer/src/dkim-verify/dkimmanagerkeydialog.h
@@ -1,47 +1,43 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef MAILTRACKINGDETAILSDIALOG_H
-#define MAILTRACKINGDETAILSDIALOG_H
+#ifndef DKIMMANAGERKEYDIALOG_H
+#define DKIMMANAGERKEYDIALOG_H
#include <QDialog>
#include "messageviewer_private_export.h"
-namespace KPIMTextEdit {
-class RichTextEditorWidget;
-}
-
namespace MessageViewer {
-class MESSAGEVIEWER_TESTS_EXPORT MailTrackingDetailsDialog : public QDialog
+class DKIMManagerKeyWidget;
+class MESSAGEVIEWER_TESTS_EXPORT DKIMManagerKeyDialog : public QDialog
{
Q_OBJECT
public:
- explicit MailTrackingDetailsDialog(QWidget *parent = nullptr);
- ~MailTrackingDetailsDialog();
-
- void setDetails(const QString &details);
+ explicit DKIMManagerKeyDialog(QWidget *parent = nullptr);
+ ~DKIMManagerKeyDialog();
private:
- void writeConfig();
void readConfig();
-
- KPIMTextEdit::RichTextEditorWidget *mDetails = nullptr;
+ void writeConfig();
+ void loadKeys();
+ void saveKeys();
+ DKIMManagerKeyWidget *mManagerWidget = nullptr;
};
}
-#endif // MAILTRACKINGDETAILSDIALOG_H
+#endif // DKIMMANAGERKEYDIALOG_H
diff --git a/messageviewer/src/viewerplugins/viewerplugin.cpp b/messageviewer/src/dkim-verify/dkimmanagerkeywidget.cpp
similarity index 53%
copy from messageviewer/src/viewerplugins/viewerplugin.cpp
copy to messageviewer/src/dkim-verify/dkimmanagerkeywidget.cpp
index 36fb75eb..1e997f98 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.cpp
+++ b/messageviewer/src/dkim-verify/dkimmanagerkeywidget.cpp
@@ -1,63 +1,52 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "viewerplugin.h"
+#include "dkimmanagerkeywidget.h"
+#include "dkimmanagerkey.h"
-using namespace MessageViewer;
-
-class MessageViewer::ViewerPluginPrivate
-{
-public:
- ViewerPluginPrivate()
- {
- }
-
- bool mEnabled = false;
-};
-
-ViewerPlugin::ViewerPlugin(QObject *parent)
- : QObject(parent)
- , d(new MessageViewer::ViewerPluginPrivate)
-{
-}
+#include <KLocalizedString>
+#include <QTreeWidget>
+#include <QVBoxLayout>
-ViewerPlugin::~ViewerPlugin()
+using namespace MessageViewer;
+DKIMManagerKeyWidget::DKIMManagerKeyWidget(QWidget *parent)
+ : QWidget(parent)
{
- delete d;
-}
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+ mainLayout->setObjectName(QStringLiteral("mainlayout"));
+ mainLayout->setContentsMargins(0, 0, 0, 0);
-void ViewerPlugin::showConfigureDialog(QWidget *parent)
-{
- Q_UNUSED(parent);
+ mTreeWidget = new QTreeWidget(this);
+ mTreeWidget->setObjectName(QStringLiteral("treewidget"));
+ mainLayout->addWidget(mTreeWidget);
}
-bool ViewerPlugin::hasConfigureDialog() const
+DKIMManagerKeyWidget::~DKIMManagerKeyWidget()
{
- return false;
}
-void ViewerPlugin::setIsEnabled(bool enabled)
+void DKIMManagerKeyWidget::loadKeys()
{
- d->mEnabled = enabled;
+ //TODO
}
-bool ViewerPlugin::isEnabled() const
+void DKIMManagerKeyWidget::saveKeys()
{
- return d->mEnabled;
+ //TODO
}
diff --git a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h b/messageviewer/src/dkim-verify/dkimmanagerkeywidget.h
similarity index 63%
copy from messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
copy to messageviewer/src/dkim-verify/dkimmanagerkeywidget.h
index c11ebb3a..1f185346 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
+++ b/messageviewer/src/dkim-verify/dkimmanagerkeywidget.h
@@ -1,39 +1,41 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-
-#ifndef TESTWEBENGINESCROLLADDATTACHMENT_H
-#define TESTWEBENGINESCROLLADDATTACHMENT_H
+#ifndef DKIMMANAGERKEYWIDGET_H
+#define DKIMMANAGERKEYWIDGET_H
#include <QWidget>
+#include "messageviewer_private_export.h"
+class QTreeWidget;
namespace MessageViewer {
-class MailWebEngineView;
-}
-class TestWebEngineScrollAddAttachment : public QWidget
+class MESSAGEVIEWER_TESTS_EXPORT DKIMManagerKeyWidget : public QWidget
{
Q_OBJECT
public:
- explicit TestWebEngineScrollAddAttachment(QWidget *parent = nullptr);
+ explicit DKIMManagerKeyWidget(QWidget *parent = nullptr);
+ ~DKIMManagerKeyWidget();
-private Q_SLOTS:
- void slotScrollToAttachment();
+ //TODO make it private ?
+ void loadKeys();
+ void saveKeys();
private:
- MessageViewer::MailWebEngineView *mTestWebEngine;
+ QTreeWidget *mTreeWidget = nullptr;
};
+}
-#endif // TESTWEBENGINESCROLLADDATTACHMENT_H
+#endif // DKIMMANAGERKEYWIDGET_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/dkimutil.cpp
similarity index 73%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/dkimutil.cpp
index 106dabf7..548a0376 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/dkimutil.cpp
@@ -1,29 +1,25 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templateparserextracthtmlinforesult.h"
+#include "dkimutil.h"
-void TemplateParserExtractHtmlInfoResult::clear()
+using namespace MessageViewer;
+DKIMUtil::DKIMUtil()
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
}
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/messageviewer/src/dkim-verify/dkimutil.h
similarity index 73%
copy from templateparser/src/templateparserextracthtmlinforesult.cpp
copy to messageviewer/src/dkim-verify/dkimutil.h
index 106dabf7..29f02c98 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/messageviewer/src/dkim-verify/dkimutil.h
@@ -1,29 +1,31 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "templateparserextracthtmlinforesult.h"
+#ifndef DKIMUTIL_H
+#define DKIMUTIL_H
-void TemplateParserExtractHtmlInfoResult::clear()
+namespace MessageViewer {
+class DKIMUtil
{
- mPlainText.clear();
- mBodyElement.clear();
- mHeaderElement.clear();
- mHtmlElement.clear();
- mTemplate.clear();
+public:
+ DKIMUtil();
+};
}
+
+#endif // DKIMUTIL_H
diff --git a/messageviewer/src/findbar/findbarsourceview.cpp b/messageviewer/src/findbar/findbarsourceview.cpp
index f7274df6..974549c8 100644
--- a/messageviewer/src/findbar/findbarsourceview.cpp
+++ b/messageviewer/src/findbar/findbarsourceview.cpp
@@ -1,83 +1,83 @@
-/* Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2011-2019 Laurent Montel <montel@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.
*/
#include "findbarsourceview.h"
#include <PimCommon/LineEditWithCompleterNg>
#include <QPlainTextEdit>
#include <QAction>
using namespace MessageViewer;
FindBarSourceView::FindBarSourceView(QPlainTextEdit *view, QWidget *parent)
: WebEngineViewer::FindBarBase(parent)
, mView(view)
{
}
FindBarSourceView::~FindBarSourceView()
{
}
void FindBarSourceView::searchText(bool backward, bool isAutoSearch)
{
QTextDocument::FindFlags searchOptions = nullptr;
if (backward) {
searchOptions |= QTextDocument::FindBackward;
}
if (mCaseSensitiveAct->isChecked()) {
searchOptions |= QTextDocument::FindCaseSensitively;
}
if (isAutoSearch) {
QTextCursor cursor = mView->textCursor();
cursor.setPosition(cursor.selectionStart());
mView->setTextCursor(cursor);
} else if (!mLastSearchStr.contains(mSearch->text(), Qt::CaseSensitive)) {
clearSelections();
}
mLastSearchStr = mSearch->text();
const bool found = mView->find(mLastSearchStr, searchOptions);
setFoundMatch(found);
}
void FindBarSourceView::clearSelections()
{
QTextCursor textCursor = mView->textCursor();
textCursor.clearSelection();
textCursor.setPosition(0);
mView->setTextCursor(textCursor);
WebEngineViewer::FindBarBase::clearSelections();
}
void FindBarSourceView::updateHighLight(bool)
{
clearSelections();
}
void FindBarSourceView::updateSensitivity(bool)
{
QTextDocument::FindFlags searchOptions = nullptr;
if (mCaseSensitiveAct->isChecked()) {
searchOptions |= QTextDocument::FindCaseSensitively;
}
mLastSearchStr = mSearch->text();
const bool found = mView->find(mLastSearchStr, searchOptions);
setFoundMatch(found);
}
diff --git a/messageviewer/src/findbar/findbarsourceview.h b/messageviewer/src/findbar/findbarsourceview.h
index 20d7900a..e2bd6583 100644
--- a/messageviewer/src/findbar/findbarsourceview.h
+++ b/messageviewer/src/findbar/findbarsourceview.h
@@ -1,51 +1,51 @@
-/* Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2011-2019 Laurent Montel <montel@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.
*/
#ifndef FINDBARSOURCEVIEW_H
#define FINDBARSOURCEVIEW_H
#include <WebEngineViewer/FindBarBase>
class QPlainTextEdit;
namespace MessageViewer {
class FindBarSourceView : public WebEngineViewer::FindBarBase
{
Q_OBJECT
public:
explicit FindBarSourceView(QPlainTextEdit *view, QWidget *parent = nullptr);
~FindBarSourceView() override;
private:
explicit FindBarSourceView(QWidget *parent)
{
Q_UNUSED(parent);
}
void clearSelections() override;
void searchText(bool backward, bool isAutoSearch) override;
void updateHighLight(bool) override;
void updateSensitivity(bool) override;
private:
QPlainTextEdit *mView = nullptr;
};
}
#endif /* FINDBARSOURCEVIEW_H */
diff --git a/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp b/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp
index c316e685..c6c3a0a9 100644
--- a/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp
+++ b/messageviewer/src/header/autotests/grantleeheaderformattertest.cpp
@@ -1,161 +1,161 @@
/*
Copyright 2018 Sandro Knauß <sknauss@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "grantleeheaderformattertest.h"
#include <MessageViewer/GrantleeHeaderStyle>
#include "../grantleeheaderformatter.h"
#include <QFile>
#include <QProcess>
#include <QRegExp>
#include <QStringList>
#include <QStandardPaths>
#include <QTest>
using namespace MessageViewer;
QTEST_MAIN(GrantleeHeaderFormatterTest)
void testHeaderFile(const QString &data, const QString &absolutePath, const QString &name)
{
QString header = QStringLiteral("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
"<body>\n");
header += data + QStringLiteral("</div>\n</div>");
header += QStringLiteral("\n</body>\n</html>\n");
header.replace(QStringLiteral("file://") + absolutePath, QStringLiteral("file://PATHTOSTYLE"));
header.replace(QRegExp(QStringLiteral("[\t ]+")), QStringLiteral(" "));
header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*")), QStringLiteral("\n"));
header.replace(QRegExp(QStringLiteral("([\n\t ])\\1+")), QStringLiteral("\\1"));
header.replace(QRegExp(QStringLiteral(">\n+[\t ]*")), QStringLiteral(">"));
header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*<")), QStringLiteral("<"));
header.replace(QLatin1String("&nbsp;"), QLatin1String("NBSP_ENTITY_PLACEHOLDER")); // xmlling chokes on &nbsp;
QString outName = name + QStringLiteral(".out.html");
QString fName = name + QStringLiteral(".html");
QVERIFY(QFile(QStringLiteral(HEADER_DATA_DIR "/") + fName).exists());
{
QFile f(outName);
f.open(QIODevice::WriteOnly);
f.write(header.toUtf8());
f.close();
}
// TODO add proper cmake check for xmllint and diff
{
const QStringList args = QStringList()
<< QStringLiteral("--format")
<< QStringLiteral("--encode") << QStringLiteral("UTF8")
<< QStringLiteral("--output") << fName
<< outName;
QCOMPARE(QProcess::execute(QStringLiteral("xmllint"), args), 0);
}
{
// compare to reference file
const QStringList args = QStringList()
<< QStringLiteral("-u")
<< fName
<< QStringLiteral(HEADER_DATA_DIR "/") + fName;
QProcess proc;
proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.start(QStringLiteral("diff"), args);
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 0);
}
}
KMime::Message::Ptr readAndParseMail(const QString &mailFile)
{
QFile file(QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + mailFile);
bool openFile = file.open(QIODevice::ReadOnly);
Q_ASSERT(openFile);
const QByteArray data = KMime::CRLFtoLF(file.readAll());
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
void GrantleeHeaderFormatterTest::testInvalid()
{
auto style = GrantleeHeaderStyle();
auto formatter = GrantleeHeaderFormatter();
auto aMsg = readAndParseMail(QStringLiteral("allheaders.mbox"));
QString filename = QStringLiteral("invalid");
QString absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + filename;
QString data = formatter.toHtml(QStringList(), absolutePath, filename, &style, aMsg.data(), false);
QCOMPARE(data, QStringLiteral("Template not found, invalid"));
}
void GrantleeHeaderFormatterTest::testPrint()
{
QString tmplName = QStringLiteral("printtest.tmpl");
auto style = GrantleeHeaderStyle();
auto formatter = GrantleeHeaderFormatter();
KMime::Message::Ptr aMsg(new KMime::Message);
const QString &absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + tmplName;
{
const QString &data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), false);
testHeaderFile(QStringLiteral("<div><div>")+data, absolutePath, QStringLiteral("printtest.off"));
}
{
const QString &data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), true);
testHeaderFile(QStringLiteral("<div><div>")+data, absolutePath, QStringLiteral("printtest.on"));
}
}
void GrantleeHeaderFormatterTest::testBlock_data()
{
QTest::addColumn<QString>("tmplName");
QDir dir(QStringLiteral(HEADER_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.tmpl")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html"))) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << file;
}
}
void GrantleeHeaderFormatterTest::testBlock()
{
QFETCH(QString, tmplName);
auto style = GrantleeHeaderStyle();
auto formatter = GrantleeHeaderFormatter();
auto aMsg = readAndParseMail(QStringLiteral("headertest.mbox"));
QString absolutePath = QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + tmplName;
QString data = formatter.toHtml(QStringList(), QStringLiteral(HEADER_DATA_DIR), tmplName, &style, aMsg.data(), false);
testHeaderFile(QStringLiteral("<div><div>")+data, absolutePath, tmplName);
}
diff --git a/messageviewer/src/header/autotests/grantleeheaderformattertest.h b/messageviewer/src/header/autotests/grantleeheaderformattertest.h
index 1e547128..4dcaf08d 100644
--- a/messageviewer/src/header/autotests/grantleeheaderformattertest.h
+++ b/messageviewer/src/header/autotests/grantleeheaderformattertest.h
@@ -1,36 +1,36 @@
/*
Copyright 2018 Sandro Knauß <sknauss@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GRANTLEEHEADERFORMATTERTEST_H
#define GRANTLEEHEADERFORMATTERTEST_H
#include <QObject>
class GrantleeHeaderFormatterTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testInvalid();
void testPrint();
void testBlock_data();
void testBlock();
};
#endif
diff --git a/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp b/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp
index f3de33cb..e3361bfa 100644
--- a/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp
+++ b/messageviewer/src/header/autotests/grantleeheaderstyletest.cpp
@@ -1,174 +1,174 @@
/*
Copyright 2018 Sandro Knauß <sknauss@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "grantleeheaderstyletest.h"
#include <MessageViewer/GrantleeHeaderStyle>
#include <GrantleeTheme/GrantleeThemeManager>
#include <QFile>
#include <QProcess>
#include <QRegExp>
#include <QStringList>
#include <QStandardPaths>
#include <QTest>
using namespace MessageViewer;
QTEST_MAIN(GrantleeHeaderStyleTest)
void testHeaderFile(const HeaderStyle &style, KMime::Message *msg, const QString &name)
{
QString header = QStringLiteral("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
"<body>\n");
header += style.format(msg) + QStringLiteral("</div>\n</div>");
header += QStringLiteral("\n</body>\n</html>\n");
header.replace(QStringLiteral("file://") + style.theme().absolutePath(), QStringLiteral("file://PATHTOSTYLE"));
header.replace(QRegExp(QStringLiteral("[\t ]+")), QStringLiteral(" "));
header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*")), QStringLiteral("\n"));
header.replace(QRegExp(QStringLiteral("([\n\t ])\\1+")), QStringLiteral("\\1"));
header.replace(QRegExp(QStringLiteral(">\n+[\t ]*")), QStringLiteral(">"));
header.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*<")), QStringLiteral("<"));
header.replace(QLatin1String("&nbsp;"), QLatin1String("NBSP_ENTITY_PLACEHOLDER")); // xmlling chokes on &nbsp;
QString outName = name + QStringLiteral(".out.html");
QString fName = name + QStringLiteral(".html");
QVERIFY(QFile(QStringLiteral(HEADER_DATA_DIR "/") + fName).exists());
{
QFile f(outName);
f.open(QIODevice::WriteOnly);
f.write(header.toUtf8());
f.close();
}
// TODO add proper cmake check for xmllint and diff
{
const QStringList args = QStringList()
<< QStringLiteral("--format")
<< QStringLiteral("--encode") << QStringLiteral("UTF8")
<< QStringLiteral("--output") << fName
<< outName;
QCOMPARE(QProcess::execute(QStringLiteral("xmllint"), args), 0);
}
{
// compare to reference file
const QStringList args = QStringList()
<< QStringLiteral("-u")
<< fName
<< QStringLiteral(HEADER_DATA_DIR "/") + fName;
QProcess proc;
proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.start(QStringLiteral("diff"), args);
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 0);
}
}
KMime::Message::Ptr readAndParseMail(const QString &mailFile)
{
QFile file(QStringLiteral(HEADER_DATA_DIR) + QLatin1Char('/') + mailFile);
bool openFile = file.open(QIODevice::ReadOnly);
Q_ASSERT(openFile);
const QByteArray data = KMime::CRLFtoLF(file.readAll());
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
const GrantleeTheme::Theme defaultTheme(const QString &name = QStringLiteral("5.2"))
{
const QStringList defaultThemePath = QStandardPaths::locateAll(
QStandardPaths::GenericDataLocation,
QStringLiteral("messageviewer/defaultthemes/"),
QStandardPaths::LocateDirectory);
return GrantleeTheme::ThemeManager::loadTheme(defaultThemePath.at(0) + QStringLiteral("/")+name,
name,
QStringLiteral("kmail_default.desktop"));
}
void GrantleeHeaderStyleTest::testName()
{
auto style = GrantleeHeaderStyle();
QCOMPARE(style.name(), "grantlee");
}
void GrantleeHeaderStyleTest::testRenderHeaderNoMessage()
{
auto style = GrantleeHeaderStyle();
QCOMPARE(style.format(nullptr), QString());
}
void GrantleeHeaderStyleTest::testRenderHeaderInvalidTheme()
{
auto style = GrantleeHeaderStyle();
auto aMsg = new KMime::Message();
QCOMPARE(style.format(aMsg), QStringLiteral("Grantlee theme \"\" is not valid."));
}
void GrantleeHeaderStyleTest::testRenderHeaderEmpty()
{
auto style = GrantleeHeaderStyle();
auto aMsg = new KMime::Message();
style.setTheme(defaultTheme());
testHeaderFile(style, aMsg, QStringLiteral("empty"));
}
void GrantleeHeaderStyleTest::testRenderHeaderVCard()
{
auto style = GrantleeHeaderStyle();
auto aMsg = new KMime::Message();
style.setTheme(defaultTheme());
style.setVCardName(QStringLiteral("nofile.vcd"));
testHeaderFile(style, aMsg, QStringLiteral("vcard"));
}
void GrantleeHeaderStyleTest::testRenderHeader_data()
{
QTest::addColumn<QString>("mailFileName");
QDir dir(QStringLiteral(HEADER_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html"))) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << file;
}
}
void GrantleeHeaderStyleTest::testRenderHeader()
{
QFETCH(QString, mailFileName);
auto style = GrantleeHeaderStyle();
auto aMsg = readAndParseMail(mailFileName);
style.setTheme(defaultTheme());
testHeaderFile(style, aMsg.data(), mailFileName);
}
diff --git a/messageviewer/src/header/autotests/grantleeheaderstyletest.h b/messageviewer/src/header/autotests/grantleeheaderstyletest.h
index da27c92b..ca85f26a 100644
--- a/messageviewer/src/header/autotests/grantleeheaderstyletest.h
+++ b/messageviewer/src/header/autotests/grantleeheaderstyletest.h
@@ -1,39 +1,39 @@
/*
Copyright 2018 Sandro Knauß <sknauss@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GRANTLEEHEADERSTYLETEST_H
#define GRANTLEEHEADERSTYLETEST_H
#include <QObject>
class GrantleeHeaderStyleTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testName();
void testRenderHeaderNoMessage();
void testRenderHeaderInvalidTheme();
void testRenderHeaderEmpty();
void testRenderHeaderVCard();
void testRenderHeader_data();
void testRenderHeader();
};
#endif
diff --git a/messageviewer/src/header/contactdisplaymessagememento.cpp b/messageviewer/src/header/contactdisplaymessagememento.cpp
index 10190fa3..34ea5724 100644
--- a/messageviewer/src/header/contactdisplaymessagememento.cpp
+++ b/messageviewer/src/header/contactdisplaymessagememento.cpp
@@ -1,206 +1,203 @@
/*
- * Copyright (C) 2012-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2012-2019 Laurent Montel <montel@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.
*/
#include "contactdisplaymessagememento.h"
#include "messageviewer_debug.h"
#include "Gravatar/GravatarResolvUrlJob"
#include "gravatarsettings.h"
#include "PimCommon/NetworkUtil"
#include "settings/messageviewersettings.h"
#include <Akonadi/Contact/ContactSearchJob>
#include <kio/transferjob.h>
using namespace MessageViewer;
ContactDisplayMessageMemento::ContactDisplayMessageMemento(const QString &emailAddress)
: QObject(nullptr)
- , mForceDisplayTo(Viewer::UseGlobalSetting)
, mEmailAddress(emailAddress)
{
if (!emailAddress.isEmpty()) {
mSearchJob = new Akonadi::ContactSearchJob();
mSearchJob->setQuery(Akonadi::ContactSearchJob::Email,
emailAddress.toLower(), Akonadi::ContactSearchJob::ExactMatch);
connect(
mSearchJob.data(), &Akonadi::ContactSearchJob::result, this,
&ContactDisplayMessageMemento::slotSearchJobFinished);
} else {
mFinished = true;
}
}
ContactDisplayMessageMemento::~ContactDisplayMessageMemento()
{
if (mSearchJob) {
disconnect(
mSearchJob.data(), &Akonadi::ContactSearchJob::result, this,
&ContactDisplayMessageMemento::slotSearchJobFinished);
mSearchJob->kill();
}
}
void ContactDisplayMessageMemento::slotSearchJobFinished(KJob *job)
{
mFinished = true;
Akonadi::ContactSearchJob *searchJob = static_cast<Akonadi::ContactSearchJob *>(job);
if (searchJob->error()) {
qCWarning(MESSAGEVIEWER_LOG) << "Unable to fetch contact:" << searchJob->errorText();
Q_EMIT update(MimeTreeParser::Delayed);
return;
}
const int contactSize(searchJob->contacts().size());
if (contactSize >= 1) {
if (contactSize > 1) {
- qCDebug(MESSAGEVIEWER_LOG) << " more than 1 contact was found we return first contact";
+ qCWarning(MESSAGEVIEWER_LOG) << " more than 1 contact was found we return first contact";
}
const KContacts::Addressee addressee = searchJob->contacts().at(0);
processAddress(addressee);
searchPhoto(searchJob->contacts());
if (!mPhoto.isEmpty()) {
//We have a data raw => we can update message
if (mPhoto.isIntern()) {
Q_EMIT update(MimeTreeParser::Delayed);
} else {
QUrl url = QUrl::fromUserInput(mPhoto.url(), QString(), QUrl::AssumeLocalFile);
QImage image;
bool ok = false;
if (url.isLocalFile()) {
if (image.load(url.toLocalFile())) {
ok = true;
}
} else {
QByteArray imageData;
KIO::TransferJob *job = KIO::get(url, KIO::NoReload);
QObject::connect(job, &KIO::TransferJob::data,
[&imageData](KIO::Job *, const QByteArray &data) {
imageData.append(data);
});
if (job->exec()) {
if (image.loadFromData(imageData)) {
ok = true;
}
}
}
if (ok) {
mImageFromUrl = image;
Q_EMIT update(MimeTreeParser::Delayed);
}
}
}
}
if (!PimCommon::NetworkUtil::self()->lowBandwidth()) {
if (mPhoto.isEmpty() && mPhoto.url().isEmpty()) {
// No url, no photo => search gravatar
if (Gravatar::GravatarSettings::self()->gravatarSupportEnabled()) {
Gravatar::GravatarResolvUrlJob *job = new Gravatar::GravatarResolvUrlJob(this);
job->setEmail(mEmailAddress);
job->setUseDefaultPixmap(
Gravatar::GravatarSettings::self()->gravatarUseDefaultImage());
if (job->canStart()) {
connect(job, &Gravatar::GravatarResolvUrlJob::finished, this,
&ContactDisplayMessageMemento::slotGravatarResolvUrlFinished);
job->start();
} else {
job->deleteLater();
}
}
}
}
}
bool ContactDisplayMessageMemento::finished() const
{
return mFinished;
}
void ContactDisplayMessageMemento::detach()
{
disconnect(this, SIGNAL(update(MimeTreeParser::UpdateMode)), nullptr, nullptr);
disconnect(this, SIGNAL(changeDisplayMail(Viewer::DisplayFormatMessage,bool)), nullptr,
nullptr);
}
-bool ContactDisplayMessageMemento::allowToRemoteContent() const
-{
- return mMailAllowToRemoteContent;
-}
-
bool ContactDisplayMessageMemento::searchPhoto(const KContacts::AddresseeList &list)
{
bool foundPhoto = false;
for (const KContacts::Addressee &addressee : list) {
- if (!addressee.photo().isEmpty()) {
- mPhoto = addressee.photo();
+ const KContacts::Picture photo = addressee.photo();
+ if (!photo.isEmpty()) {
+ mPhoto = photo;
foundPhoto = true;
break;
}
}
return foundPhoto;
}
QImage ContactDisplayMessageMemento::imageFromUrl() const
{
return mImageFromUrl;
}
QPixmap ContactDisplayMessageMemento::gravatarPixmap() const
{
return mGravatarPixmap;
}
void ContactDisplayMessageMemento::processAddress(const KContacts::Addressee &addressee)
{
+ Viewer::DisplayFormatMessage forceDisplayTo = Viewer::UseGlobalSetting;
+ bool mailAllowToRemoteContent = false;
const QStringList customs = addressee.customs();
for (const QString &custom : customs) {
if (custom.contains(QLatin1String("MailPreferedFormatting"))) {
const QString value
= addressee.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral(
"MailPreferedFormatting"));
if (value == QLatin1String("TEXT")) {
- mForceDisplayTo = Viewer::Text;
+ forceDisplayTo = Viewer::Text;
} else if (value == QLatin1String("HTML")) {
- mForceDisplayTo = Viewer::Html;
+ forceDisplayTo = Viewer::Html;
} else {
- mForceDisplayTo = Viewer::UseGlobalSetting;
+ forceDisplayTo = Viewer::UseGlobalSetting;
}
} else if (custom.contains(QLatin1String("MailAllowToRemoteContent"))) {
const QString value
= addressee.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral(
"MailAllowToRemoteContent"));
- mMailAllowToRemoteContent = (value == QLatin1String("TRUE"));
+ mailAllowToRemoteContent = (value == QLatin1String("TRUE"));
}
}
- Q_EMIT changeDisplayMail(mForceDisplayTo, mMailAllowToRemoteContent);
+ Q_EMIT changeDisplayMail(forceDisplayTo, mailAllowToRemoteContent);
}
KContacts::Picture ContactDisplayMessageMemento::photo() const
{
return mPhoto;
}
void ContactDisplayMessageMemento::slotGravatarResolvUrlFinished(Gravatar::GravatarResolvUrlJob *job)
{
if (job && job->hasGravatar()) {
mGravatarPixmap = job->pixmap();
Q_EMIT update(MimeTreeParser::Delayed);
}
}
diff --git a/messageviewer/src/header/contactdisplaymessagememento.h b/messageviewer/src/header/contactdisplaymessagememento.h
index 2dc2a5ab..a38faa14 100644
--- a/messageviewer/src/header/contactdisplaymessagememento.h
+++ b/messageviewer/src/header/contactdisplaymessagememento.h
@@ -1,80 +1,77 @@
-/* Copyright (C) 2012-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2012-2019 Laurent Montel <montel@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.
*/
#ifndef CONTACTDISPLAYMESSAGEMEMENTO_H
#define CONTACTDISPLAYMESSAGEMEMENTO_H
#include "interfaces/bodypart.h"
#include "messageviewer/viewer.h"
#include <KContacts/Picture>
#include <KContacts/Addressee>
#include <QObject>
#include <QPointer>
class KJob;
namespace Gravatar {
class GravatarResolvUrlJob;
}
namespace Akonadi {
class ContactSearchJob;
}
namespace MessageViewer {
class ContactDisplayMessageMemento : public QObject, public MimeTreeParser::Interface::BodyPartMemento
{
Q_OBJECT
public:
explicit ContactDisplayMessageMemento(const QString &emailAddress);
~ContactDisplayMessageMemento() override;
void processAddress(const KContacts::Addressee &addressee);
- bool allowToRemoteContent() const;
Q_REQUIRED_RESULT KContacts::Picture photo() const;
Q_REQUIRED_RESULT bool finished() const;
void detach() override;
Q_REQUIRED_RESULT QPixmap gravatarPixmap() const;
Q_REQUIRED_RESULT QImage imageFromUrl() const;
Q_SIGNALS:
// TODO: Factor our update and detach into base class
void update(MimeTreeParser::UpdateMode);
void changeDisplayMail(Viewer::DisplayFormatMessage displayAsHtml, bool remoteContent);
private Q_SLOTS:
void slotSearchJobFinished(KJob *job);
void slotGravatarResolvUrlFinished(Gravatar::GravatarResolvUrlJob *);
private:
bool searchPhoto(const KContacts::AddresseeList &list);
- Viewer::DisplayFormatMessage mForceDisplayTo;
KContacts::Picture mPhoto;
QPixmap mGravatarPixmap;
QImage mImageFromUrl;
QString mEmailAddress;
bool mFinished = false;
- bool mMailAllowToRemoteContent = false;
QPointer<Akonadi::ContactSearchJob> mSearchJob;
};
}
#endif /* CONTACTDISPLAYMESSAGE_H */
diff --git a/messageviewer/src/header/data/messageviewer_header_themes.knsrc b/messageviewer/src/header/data/messageviewer_header_themes.knsrc
index d0cb25e9..53b9da06 100644
--- a/messageviewer/src/header/data/messageviewer_header_themes.knsrc
+++ b/messageviewer/src/header/data/messageviewer_header_themes.knsrc
@@ -1,5 +1,26 @@
[KNewStuff3]
+Name=KMail Header Theme
+Name[ca]=Tema de capçalera pel KMail
+Name[ca@valencia]=Tema de capçalera pel KMail
+Name[de]=KMail-Vorspanndesign
+Name[en_GB]=KMail Header Theme
+Name[es]=Temas de encabezado de KMail
+Name[fi]=KMail-otsaketeema
+Name[fr]=Thème d'en-tête KMail
+Name[gl]=Tema da cabeceira de KMail
+Name[it]=Tema della intestazioni di KMail
+Name[ko]=KMail 헤더 테마
+Name[nl]=Header-thema van KMail
+Name[pl]=Wygląd nagłówka KMail
+Name[pt]=Tema de Cabeçalhos do KMail
+Name[pt_BR]=Tema de cabeçalho do KMail
+Name[ru]=Оформление заголовков KMail
+Name[sv]=Kmail huvudtema
+Name[uk]=Тема заголовків KMail
+Name[x-test]=xxKMail Header Themexx
+Name[zh_CN]=KMail主题标题
+Name[zh_TW]=KMail 開頭主題
ProvidersUrl=http://download.kde.org/ocs/providers.xml
Categories=KMail Header Theme
TargetDir=messageviewer/themes
Uncompress=always
diff --git a/messageviewer/src/header/grantleeheaderformatter.cpp b/messageviewer/src/header/grantleeheaderformatter.cpp
index e9dfb6fd..a39f841d 100644
--- a/messageviewer/src/header/grantleeheaderformatter.cpp
+++ b/messageviewer/src/header/grantleeheaderformatter.cpp
@@ -1,415 +1,412 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#include "grantleeheaderformatter.h"
#include "headerstyle_util.h"
#include "grantleetheme/grantleetheme.h"
#include "settings/messageviewersettings.h"
#include "utils/iconnamecache.h"
#include "config-messageviewer.h"
#include <MessageCore/StringUtil>
#include <MimeTreeParser/NodeHelper>
#include <kmime/kmime_message.h>
#include <kmime/kmime_dateformatter.h>
#include <KLocalizedString>
#include <KIconLoader>
#include <KColorScheme>
#include <grantlee/engine.h>
#include <grantlee/metatype.h>
#include <grantlee/templateloader.h>
using namespace MessageCore;
using namespace MessageViewer;
Q_DECLARE_METATYPE(const KMime::Headers::Generics::AddressList *)
Q_DECLARE_METATYPE(const KMime::Headers::Generics::MailboxList *)
Q_DECLARE_METATYPE(QSharedPointer<KMime::Headers::Generics::MailboxList>)
Q_DECLARE_METATYPE(const KMime::Headers::Date *)
// Read-only introspection of KMime::Headers::Generics::AddressList object.
namespace Grantlee {
template<>
inline QVariant TypeAccessor<const KMime::Headers::Generics::AddressList *>::lookUp(const KMime::Headers::Generics::AddressList *const object, const QString &property)
{
if (property == QStringLiteral("nameOnly")) {
return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly);
} else if (property == QStringLiteral("isSet")) {
return !object->asUnicodeString().isEmpty();
} else if (property == QStringLiteral("fullAddress")) {
return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress);
} else if (property == QStringLiteral("str")) {
return object->asUnicodeString();
} else if (property.startsWith(QStringLiteral("expandable"))) {
const auto &name = property.mid(10);
const QString val = MessageCore::StringUtil::emailAddrAsAnchor(
object, MessageCore::StringUtil::DisplayFullAddress,
QString(), MessageCore::StringUtil::ShowLink,
MessageCore::StringUtil::ExpandableAddresses,
QStringLiteral("Full") + name + QStringLiteral("AddressList"));
return val;
}
return QVariant();
}
}
// Read-only introspection of KMime::Headers::Generics::MailboxList object.
namespace Grantlee {
template<>
inline QVariant TypeAccessor<const KMime::Headers::Generics::MailboxList *>::lookUp(const KMime::Headers::Generics::MailboxList *const object, const QString &property)
{
if (property == QStringLiteral("nameOnly")) {
return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly);
} else if (property == QStringLiteral("isSet")) {
return !object->asUnicodeString().isEmpty();
} else if (property == QStringLiteral("fullAddress")) {
return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress);
} else if (property == QStringLiteral("str")) {
return object->asUnicodeString();
} else if (property.startsWith(QStringLiteral("expandable"))) {
const auto &name = property.mid(10);
const QString val = MessageCore::StringUtil::emailAddrAsAnchor(
object, MessageCore::StringUtil::DisplayFullAddress,
QString(), MessageCore::StringUtil::ShowLink,
MessageCore::StringUtil::ExpandableAddresses,
QStringLiteral("Full") + name + QStringLiteral("AddressList"));
return val;
}
return QVariant();
}
}
GRANTLEE_BEGIN_LOOKUP(QSharedPointer<KMime::Headers::Generics::MailboxList>)
- if (property == QStringLiteral("nameOnly")) {
- return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayNameOnly);
- } else if (property == QStringLiteral("isSet")) {
- return !object->asUnicodeString().isEmpty();
- } else if (property == QStringLiteral("fullAddress")) {
- return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayFullAddress);
- } else if (property == QStringLiteral("str")) {
- return object->asUnicodeString();
- } else if (property.startsWith(QStringLiteral("expandable"))) {
- const auto &name = property.mid(10);
- const QString val = MessageCore::StringUtil::emailAddrAsAnchor(
- object.data(), MessageCore::StringUtil::DisplayFullAddress,
- QString(), MessageCore::StringUtil::ShowLink,
- MessageCore::StringUtil::ExpandableAddresses,
- QStringLiteral("Full") + name + QStringLiteral("AddressList"));
- return val;
- }
+if (property == QStringLiteral("nameOnly")) {
+ return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayNameOnly);
+} else if (property == QStringLiteral("isSet")) {
+ return !object->asUnicodeString().isEmpty();
+} else if (property == QStringLiteral("fullAddress")) {
+ return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayFullAddress);
+} else if (property == QStringLiteral("str")) {
+ return object->asUnicodeString();
+} else if (property.startsWith(QStringLiteral("expandable"))) {
+ const auto &name = property.mid(10);
+ const QString val = MessageCore::StringUtil::emailAddrAsAnchor(
+ object.data(), MessageCore::StringUtil::DisplayFullAddress,
+ QString(), MessageCore::StringUtil::ShowLink,
+ MessageCore::StringUtil::ExpandableAddresses,
+ QStringLiteral("Full") + name + QStringLiteral("AddressList"));
+ return val;
+}
GRANTLEE_END_LOOKUP
-
namespace Grantlee {
template<>
inline QVariant TypeAccessor<const KMime::Headers::Date *>::lookUp(const KMime::Headers::Date *const object, const QString &property)
{
MessageViewer::HeaderStyleUtil::HeaderStyleUtilDateFormat dateFormat;
if (property == QStringLiteral("str")) {
return HeaderStyleUtil::dateStr(object->dateTime());
} else if (property == QStringLiteral("short")) {
dateFormat = MessageViewer::HeaderStyleUtil::ShortDate;
} else if (property == QStringLiteral("long")) {
dateFormat = MessageViewer::HeaderStyleUtil::CustomDate;
} else if (property == QStringLiteral("fancylong")) {
dateFormat = MessageViewer::HeaderStyleUtil::FancyLongDate;
} else if (property == QStringLiteral("fancyshort")) {
dateFormat = MessageViewer::HeaderStyleUtil::FancyShortDate;
- } else if(property == QStringLiteral("localelong")){
+ } else if (property == QStringLiteral("localelong")) {
dateFormat = MessageViewer::HeaderStyleUtil::LongDate;
} else {
return QVariant();
}
return HeaderStyleUtil::strToHtml(HeaderStyleUtil::dateString(object, dateFormat));
}
}
-
class Q_DECL_HIDDEN MessageViewer::GrantleeHeaderFormatter::Private
{
public:
Private()
{
Grantlee::registerMetaType<const KMime::Headers::Generics::AddressList *>();
Grantlee::registerMetaType<const KMime::Headers::Generics::MailboxList *>();
- Grantlee::registerMetaType<QSharedPointer<KMime::Headers::Generics::MailboxList>>();
+ Grantlee::registerMetaType<QSharedPointer<KMime::Headers::Generics::MailboxList> >();
Grantlee::registerMetaType<const KMime::Headers::Date *>();
iconSize = KIconLoader::global()->currentSize(KIconLoader::Toolbar);
engine = new Grantlee::Engine;
templateLoader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(
new Grantlee::FileSystemTemplateLoader);
engine->addTemplateLoader(templateLoader);
}
~Private()
{
delete engine;
}
QSharedPointer<Grantlee::FileSystemTemplateLoader> templateLoader;
Grantlee::Engine *engine = nullptr;
MessageViewer::HeaderStyleUtil headerStyleUtil;
int iconSize;
};
GrantleeHeaderFormatter::GrantleeHeaderFormatter()
: d(new GrantleeHeaderFormatter::Private)
{
}
GrantleeHeaderFormatter::~GrantleeHeaderFormatter()
{
delete d;
}
QString GrantleeHeaderFormatter::toHtml(
const GrantleeHeaderFormatter::GrantleeHeaderFormatterSettings &settings) const
{
QString errorMessage;
if (!settings.theme.isValid()) {
errorMessage = i18n("Grantlee theme \"%1\" is not valid.", settings.theme.name());
return errorMessage;
}
d->templateLoader->setTemplateDirs(QStringList() << settings.theme.absolutePath());
Grantlee::Template headerTemplate = d->engine->loadByName(settings.theme.themeFilename());
if (headerTemplate->error()) {
errorMessage = headerTemplate->errorString();
return errorMessage;
}
return format(
settings.theme.absolutePath(), headerTemplate,
settings.theme.displayExtraVariables(), settings.isPrinting, settings.style, settings.message,
settings.showEmoticons);
}
-QString GrantleeHeaderFormatter::toHtml(const QStringList &displayExtraHeaders, const QString &absolutPath, const QString &filename, const MessageViewer::HeaderStyle *style, KMime::Message *message,
- bool isPrinting) const
+QString GrantleeHeaderFormatter::toHtml(const QStringList &displayExtraHeaders, const QString &absolutPath, const QString &filename, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool isPrinting) const
{
d->templateLoader->setTemplateDirs(QStringList() << absolutPath);
Grantlee::Template headerTemplate = d->engine->loadByName(filename);
if (headerTemplate->error()) {
return headerTemplate->errorString();
}
return format(absolutPath, headerTemplate, displayExtraHeaders, isPrinting, style, message);
}
-QString GrantleeHeaderFormatter::format(const QString &absolutePath, const Grantlee::Template &headerTemplate, const QStringList &displayExtraHeaders, bool isPrinting,
- const MessageViewer::HeaderStyle *style, KMime::Message *message, bool showEmoticons) const
+QString GrantleeHeaderFormatter::format(const QString &absolutePath, const Grantlee::Template &headerTemplate, const QStringList &displayExtraHeaders, bool isPrinting, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool showEmoticons) const
{
QVariantHash headerObject;
const auto nodeHelper = style->nodeHelper();
// However, the direction of the message subject within the header is
// determined according to the contents of the subject itself. Since
// the "Re:" and "Fwd:" prefixes would always cause the subject to be
// considered left-to-right, they are ignored when determining its
// direction.
const QString absoluteThemePath = QUrl::fromLocalFile(absolutePath + QLatin1Char('/')).url();
headerObject.insert(QStringLiteral("absoluteThemePath"), absoluteThemePath);
headerObject.insert(QStringLiteral("applicationDir"),
QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral(
"ltr"));
headerObject.insert(QStringLiteral("subjectDir"),
d->headerStyleUtil.subjectDirectionString(message));
headerObject.insert(QStringLiteral("subjecti18n"), i18n("Subject:"));
KTextToHTML::Options flags = KTextToHTML::PreserveSpaces;
if (showEmoticons) {
flags |= KTextToHTML::ReplaceSmileys;
}
headerObject.insert(QStringLiteral("subject"),
d->headerStyleUtil.subjectString(message, flags));
if (nodeHelper->hasMailHeader("to", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("to",message);
headerObject.insert(QStringLiteral("toi18n"), i18n("To:"));
headerObject.insert(QStringLiteral("to"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
if (nodeHelper->hasMailHeader("replyTo", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("replyTo",message);
headerObject.insert(QStringLiteral("replyToi18n"), i18n("Reply to:"));
headerObject.insert(QStringLiteral("replyTo"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
if (nodeHelper->hasMailHeader("cc", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("cc",message);
headerObject.insert(QStringLiteral("cci18n"), i18n("CC:"));
headerObject.insert(QStringLiteral("cc"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
if (nodeHelper->hasMailHeader("bcc", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("bcc",message);
headerObject.insert(QStringLiteral("bcci18n"), i18n("BCC:"));
headerObject.insert(QStringLiteral("bcc"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
{
const auto value = nodeHelper->mailHeaderAsAddressList("from",message);
headerObject.insert(QStringLiteral("fromi18n"), i18n("From:"));
headerObject.insert(QStringLiteral("from"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
//Sender
headerObject.insert(QStringLiteral("senderi18n"), i18n("Sender:"));
headerObject.insert(QStringLiteral("sender"),
HeaderStyleUtil::strToHtml(nodeHelper->mailHeaderAsBase("sender", message)->asUnicodeString()));
headerObject.insert(QStringLiteral("listidi18n"), i18n("List-Id:"));
if (nodeHelper->hasMailHeader("List-Id", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("List-Id", message);
headerObject.insert(QStringLiteral("listid"), value->asUnicodeString());
}
const QString spamHtml = d->headerStyleUtil.spamStatus(message);
if (!spamHtml.isEmpty()) {
headerObject.insert(QStringLiteral("spamstatusi18n"), i18n("Spam Status:"));
headerObject.insert(QStringLiteral("spamHTML"), spamHtml);
}
{
const auto value = nodeHelper->dateHeader(message);
headerObject.insert(QStringLiteral("datei18n"), i18n("Date:"));
// TODO: rewrite that QDateTime is expected in GRANTLEE
headerObject.insert(QStringLiteral("date"), QVariant::fromValue(static_cast<const KMime::Headers::Date *>(message->date())));
}
if (nodeHelper->hasMailHeader("Resent-From", message)) {
const auto value = nodeHelper->mailHeaderAsAddressList("Resent-From", message);
headerObject.insert(QStringLiteral("resentfromi18n"), i18n("resent from"));
headerObject.insert(QStringLiteral("resentfrom"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(value)));
}
if (nodeHelper->hasMailHeader("Resent-To", message)) {
const auto resentTo = nodeHelper->mailHeaderAsAddressList("Resent-From", message);
headerObject.insert(QStringLiteral("resenttoi18n"),
i18np("receiver was", "receivers were", resentTo->mailboxes().count()));
headerObject.insert(QStringLiteral("resentto"), QVariant::fromValue(static_cast<const KMime::Headers::Generics::AddressList *>(resentTo)));
}
if (auto organization = message->organization(false)) {
headerObject.insert(QStringLiteral("organization"),
HeaderStyleUtil::strToHtml(organization->asUnicodeString()));
}
if (!style->vCardName().isEmpty()) {
headerObject.insert(QStringLiteral("vcardname"), style->vCardName());
}
if (!style->collectionName().isEmpty()) {
headerObject.insert(QStringLiteral("collectionname"), style->collectionName());
}
if (isPrinting) {
//provide a bit more left padding when printing
//kolab/issue3254 (printed mail cut at the left side)
//Use it just for testing if we are in printing mode
headerObject.insert(QStringLiteral("isprinting"), i18n("Printing mode"));
headerObject.insert(QStringLiteral("printmode"), QStringLiteral("printmode"));
} else {
headerObject.insert(QStringLiteral("screenmode"), QStringLiteral("screenmode"));
}
// colors depend on if it is encapsulated or not
QColor fontColor(Qt::white);
QString linkColor = QStringLiteral("white");
const QColor activeColor
= KColorScheme(QPalette::Active, KColorScheme::Selection).background().color();
QColor activeColorDark = activeColor.darker(130);
// reverse colors for encapsulated
if (!style->isTopLevel()) {
activeColorDark = activeColor.darker(50);
fontColor = QColor(Qt::black);
linkColor = QStringLiteral("black");
}
// 3D borders
headerObject.insert(QStringLiteral("activecolordark"), activeColorDark.name());
headerObject.insert(QStringLiteral("fontcolor"), fontColor.name());
headerObject.insert(QStringLiteral("linkcolor"), linkColor);
MessageViewer::HeaderStyleUtil::xfaceSettings xface = d->headerStyleUtil.xface(style, message);
if (!xface.photoURL.isEmpty()) {
headerObject.insert(QStringLiteral("photowidth"), xface.photoWidth);
headerObject.insert(QStringLiteral("photoheight"), xface.photoHeight);
headerObject.insert(QStringLiteral("photourl"), xface.photoURL);
}
for (QString header : qAsConst(displayExtraHeaders)) {
const QByteArray baHeader = header.toLocal8Bit();
if (auto hrd = message->headerByType(baHeader.constData())) {
//Grantlee doesn't support '-' in variable name => remove it.
header = header.remove(QLatin1Char('-'));
headerObject.insert(header, hrd->asUnicodeString());
}
}
headerObject.insert(QStringLiteral("vcardi18n"), i18n("[vcard]"));
headerObject.insert(QStringLiteral("readOnlyMessage"), style->readOnlyMessage());
- const bool messageHasAttachment = KMime::hasAttachment(message);
+ const QString attachmentHtml = style->attachmentHtml();
+ const bool messageHasAttachment = KMime::hasAttachment(message) && !attachmentHtml.isEmpty();
headerObject.insert(QStringLiteral("hasAttachment"), messageHasAttachment);
-
- headerObject.insert(QStringLiteral("attachmentHtml"), style->attachmentHtml());
+ headerObject.insert(QStringLiteral("attachmentHtml"), attachmentHtml);
+ headerObject.insert(QStringLiteral("attachmentI18n"), i18n("Attachments:"));
if (messageHasAttachment) {
const QString iconPath
= MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral(
"mail-attachment"),
KIconLoader::Toolbar);
const QString html = QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
headerObject.insert(QStringLiteral("attachmentIcon"), html);
}
const bool messageIsSigned = KMime::isSigned(message);
headerObject.insert(QStringLiteral("messageIsSigned"), messageIsSigned);
if (messageIsSigned) {
const QString iconPath
= MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral(
"mail-signed"),
KIconLoader::Toolbar);
const QString html = QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
headerObject.insert(QStringLiteral("signedIcon"), html);
}
const bool messageIsEncrypted = KMime::isEncrypted(message);
headerObject.insert(QStringLiteral("messageIsEncrypted"), messageIsEncrypted);
if (messageIsEncrypted) {
const QString iconPath
= MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral(
"mail-encrypted"),
KIconLoader::Toolbar);
const QString html = QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
headerObject.insert(QStringLiteral("encryptedIcon"), html);
}
const bool messageHasSecurityInfo = messageIsEncrypted || messageIsSigned;
headerObject.insert(QStringLiteral("messageHasSecurityInfo"), messageHasSecurityInfo);
headerObject.insert(QStringLiteral("messageHasSecurityInfoI18n"), i18n("Security:"));
QVariantHash mapping;
mapping.insert(QStringLiteral("header"), headerObject);
Grantlee::Context context(mapping);
return headerTemplate->render(&context);
}
diff --git a/messageviewer/src/header/grantleeheaderformatter.h b/messageviewer/src/header/grantleeheaderformatter.h
index ddcfe71f..554b72ca 100644
--- a/messageviewer/src/header/grantleeheaderformatter.h
+++ b/messageviewer/src/header/grantleeheaderformatter.h
@@ -1,63 +1,61 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#ifndef GRANTLEEHEADERFORMATTER_H
#define GRANTLEEHEADERFORMATTER_H
#include <QString>
#include <grantlee/templateloader.h>
#include <GrantleeTheme/GrantleeTheme>
#include "messageviewer_export.h"
namespace KMime {
class Message;
}
namespace MessageViewer {
class HeaderStyle;
class MESSAGEVIEWER_EXPORT GrantleeHeaderFormatter
{
public:
struct GrantleeHeaderFormatterSettings {
GrantleeHeaderFormatterSettings()
{
}
GrantleeTheme::Theme theme;
bool isPrinting = false;
mutable const MessageViewer::HeaderStyle *style = nullptr;
KMime::Message *message = nullptr;
bool showEmoticons = true;
};
explicit GrantleeHeaderFormatter();
~GrantleeHeaderFormatter();
Q_REQUIRED_RESULT QString toHtml(const GrantleeHeaderFormatterSettings &settings) const;
- Q_REQUIRED_RESULT QString toHtml(const QStringList &displayExtraHeaders, const QString &absolutPath, const QString &filename, const MessageViewer::HeaderStyle *style, KMime::Message *message,
- bool isPrinting) const;
+ Q_REQUIRED_RESULT QString toHtml(const QStringList &displayExtraHeaders, const QString &absolutPath, const QString &filename, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool isPrinting) const;
private:
- QString format(const QString &absolutePath, const Grantlee::Template &headerTemplate, const QStringList &displayExtraHeaders, bool isPrinting, const MessageViewer::HeaderStyle *style,
- KMime::Message *message, bool showEmoticons = true) const;
+ QString format(const QString &absolutePath, const Grantlee::Template &headerTemplate, const QStringList &displayExtraHeaders, bool isPrinting, const MessageViewer::HeaderStyle *style, KMime::Message *message, bool showEmoticons = true) const;
class Private;
Private *const d;
};
}
#endif // GRANTLEEHEADERFORMATTER_H
diff --git a/messageviewer/src/header/grantleeheaderstyle.cpp b/messageviewer/src/header/grantleeheaderstyle.cpp
index cc9bfed5..ea9861f7 100644
--- a/messageviewer/src/header/grantleeheaderstyle.cpp
+++ b/messageviewer/src/header/grantleeheaderstyle.cpp
@@ -1,73 +1,73 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#include "grantleeheaderstyle.h"
#include "header/grantleeheaderformatter.h"
#include "header/headerstrategy.h"
#include "grantleetheme/grantleetheme.h"
#include <kmime/kmime_message.h>
using namespace MessageViewer;
class MessageViewer::GrantleeHeaderStylePrivate
{
public:
GrantleeHeaderStylePrivate()
{
mGrantleeFormatter = new GrantleeHeaderFormatter;
}
~GrantleeHeaderStylePrivate()
{
delete mGrantleeFormatter;
}
GrantleeHeaderFormatter *mGrantleeFormatter = nullptr;
};
GrantleeHeaderStyle::GrantleeHeaderStyle()
: HeaderStyle()
, d(new MessageViewer::GrantleeHeaderStylePrivate)
{
}
GrantleeHeaderStyle::~GrantleeHeaderStyle()
{
delete d;
}
const char *GrantleeHeaderStyle::name() const
{
return "grantlee";
}
QString GrantleeHeaderStyle::format(KMime::Message *message) const
{
if (!message) {
return QString();
}
GrantleeHeaderFormatter::GrantleeHeaderFormatterSettings settings;
settings.isPrinting = isPrinting();
settings.theme = theme();
settings.style = this;
settings.message = message;
settings.showEmoticons = showEmoticons();
return d->mGrantleeFormatter->toHtml(settings);
}
diff --git a/messageviewer/src/header/grantleeheaderstyle.h b/messageviewer/src/header/grantleeheaderstyle.h
index c9cbb204..850c7c8e 100644
--- a/messageviewer/src/header/grantleeheaderstyle.h
+++ b/messageviewer/src/header/grantleeheaderstyle.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#ifndef GRANTLEEHEADERSTYLE_H
#define GRANTLEEHEADERSTYLE_H
#include "messageviewer/headerstyle.h"
namespace MessageViewer {
class GrantleeHeaderStylePrivate;
class MESSAGEVIEWER_EXPORT GrantleeHeaderStyle : public HeaderStyle
{
public:
GrantleeHeaderStyle();
~GrantleeHeaderStyle() override;
public:
const char *name() const override;
Q_REQUIRED_RESULT QString format(KMime::Message *message) const override;
private:
GrantleeHeaderStylePrivate *const d;
};
}
#endif // GRANTLEEHEADERSTYLE_H
diff --git a/messageviewer/src/header/grantleeheaderteststyle.cpp b/messageviewer/src/header/grantleeheaderteststyle.cpp
index 33aa577e..f4f78722 100644
--- a/messageviewer/src/header/grantleeheaderteststyle.cpp
+++ b/messageviewer/src/header/grantleeheaderteststyle.cpp
@@ -1,86 +1,86 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#include "grantleeheaderteststyle.h"
#include "header/grantleeheaderformatter.h"
#include "header/headerstrategy.h"
#include <kmime/kmime_message.h>
using namespace MessageViewer;
class MessageViewer::GrantleeHeaderTestStylePrivate
{
public:
GrantleeHeaderTestStylePrivate()
: mGrantleeFormatter(new GrantleeHeaderFormatter)
{
}
~GrantleeHeaderTestStylePrivate()
{
delete mGrantleeFormatter;
}
QStringList mExtraDisplay;
QString mAbsolutePath;
QString mMainFilename;
GrantleeHeaderFormatter *mGrantleeFormatter = nullptr;
};
GrantleeHeaderTestStyle::GrantleeHeaderTestStyle()
: HeaderStyle()
, d(new MessageViewer::GrantleeHeaderTestStylePrivate)
{
}
GrantleeHeaderTestStyle::~GrantleeHeaderTestStyle()
{
delete d;
}
const char *GrantleeHeaderTestStyle::name() const
{
return "grantleetest";
}
QString GrantleeHeaderTestStyle::format(KMime::Message *message) const
{
if (!message) {
return QString();
}
return d->mGrantleeFormatter->toHtml(d->mExtraDisplay, d->mAbsolutePath, d->mMainFilename, this,
message, isPrinting());
}
void GrantleeHeaderTestStyle::setAbsolutePath(const QString &path)
{
d->mAbsolutePath = path;
}
void GrantleeHeaderTestStyle::setMainFilename(const QString &filename)
{
d->mMainFilename = filename;
}
void GrantleeHeaderTestStyle::setExtraDisplayHeaders(const QStringList &extraDisplay)
{
d->mExtraDisplay = extraDisplay;
}
diff --git a/messageviewer/src/header/grantleeheaderteststyle.h b/messageviewer/src/header/grantleeheaderteststyle.h
index 9079cfa3..b00553f8 100644
--- a/messageviewer/src/header/grantleeheaderteststyle.h
+++ b/messageviewer/src/header/grantleeheaderteststyle.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#ifndef GRANTLEEHEADERTESTSTYLE_H
#define GRANTLEEHEADERTESTSTYLE_H
#include "headerstyle.h"
#include "messageviewer_export.h"
namespace MessageViewer {
class GrantleeHeaderTestStylePrivate;
class MESSAGEVIEWER_EXPORT GrantleeHeaderTestStyle : public HeaderStyle
{
friend class GrantleeHeaderStyle;
public:
GrantleeHeaderTestStyle();
~GrantleeHeaderTestStyle() override;
public:
const char *name() const override;
Q_REQUIRED_RESULT QString format(KMime::Message *message) const override;
void setAbsolutePath(const QString &);
void setMainFilename(const QString &);
void setExtraDisplayHeaders(const QStringList &);
private:
GrantleeHeaderTestStylePrivate *const d;
};
}
#endif // GRANTLEEHEADERTESTSTYLE_H
diff --git a/messageviewer/src/header/headerstrategy.cpp b/messageviewer/src/header/headerstrategy.cpp
index 67fe0dfc..f97fff7e 100644
--- a/messageviewer/src/header/headerstrategy.cpp
+++ b/messageviewer/src/header/headerstrategy.cpp
@@ -1,78 +1,79 @@
/* -*- c++ -*-
headerstrategy.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#include "headerstrategy.h"
//
// HeaderStrategy abstract base:
//
using namespace MessageViewer;
HeaderStrategy::HeaderStrategy()
{
}
HeaderStrategy::~HeaderStrategy()
{
}
QStringList HeaderStrategy::headersToDisplay() const
{
return QStringList();
}
QStringList HeaderStrategy::headersToHide() const
{
return QStringList();
}
bool HeaderStrategy::showHeader(const QString &header) const
{
const QString headerLower(header.toLower());
if (headersToDisplay().contains(headerLower)) {
return true;
}
if (headersToHide().contains(headerLower)) {
return false;
}
return defaultPolicy() == Display;
}
QStringList HeaderStrategy::stringList(const char *const headers[], int numHeaders)
{
QStringList sl;
sl.reserve(numHeaders);
for (int i = 0; i < numHeaders; ++i) {
sl.push_back(QLatin1String(headers[i]));
}
return sl;
}
diff --git a/messageviewer/src/header/headerstrategy.h b/messageviewer/src/header/headerstrategy.h
index b8842628..46252e0f 100644
--- a/messageviewer/src/header/headerstrategy.h
+++ b/messageviewer/src/header/headerstrategy.h
@@ -1,71 +1,72 @@
/* -*- c++ -*-
headerstrategy.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_HEADERSTRATEGY_H
#define MESSAGEVIEWER_HEADERSTRATEGY_H
#include "messageviewer_export.h"
#include <QStringList>
namespace MessageViewer {
//
// Convenience function
//
class MESSAGEVIEWER_EXPORT HeaderStrategy
{
protected:
HeaderStrategy();
public:
virtual ~HeaderStrategy();
//
// Methods for handling the strategies:
//
virtual const char *name() const = 0;
void readConfig();
//
// HeaderStrategy interface:
//
enum DefaultPolicy {
Display, Hide
};
Q_REQUIRED_RESULT virtual QStringList headersToDisplay() const;
Q_REQUIRED_RESULT virtual QStringList headersToHide() const;
Q_REQUIRED_RESULT virtual DefaultPolicy defaultPolicy() const = 0;
Q_REQUIRED_RESULT virtual bool showHeader(const QString &header) const;
static QStringList stringList(const char *const headers[], int numHeaders);
};
}
#endif // MESSAGEVIEWER_HEADERSTRATEGY_H
diff --git a/messageviewer/src/header/headerstyle.cpp b/messageviewer/src/header/headerstyle.cpp
index f1094189..92b74152 100644
--- a/messageviewer/src/header/headerstyle.cpp
+++ b/messageviewer/src/header/headerstyle.cpp
@@ -1,213 +1,214 @@
/* -*- c++ -*-
headerstyle.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#include "headerstyle.h"
#include "messageviewer_debug.h"
#include "grantleetheme/grantleetheme.h"
using namespace MessageViewer;
//
// HeaderStyle abstract base:
//
class MessageViewer::HeaderStylePrivate
{
public:
HeaderStylePrivate()
{
}
GrantleeTheme::Theme mTheme;
QString mMessagePath;
const HeaderStrategy *mStrategy = nullptr;
QString mVCardName;
QString mCollectionName;
QString mAttachmentHtml;
MimeTreeParser::NodeHelper *mNodeHelper = nullptr;
QObject *mSourceObject = nullptr;
Akonadi::MessageStatus mMessageStatus;
bool mPrinting = false;
bool mTopLevel = true;
bool mAllowAsync = false;
bool mReadOnlyMessage = false;
bool mShowEmoticons = true;
};
HeaderStyle::HeaderStyle()
: d(new MessageViewer::HeaderStylePrivate)
{
}
HeaderStyle::~HeaderStyle()
{
delete d;
}
void HeaderStyle::setAttachmentHtml(const QString &html)
{
d->mAttachmentHtml = html;
}
QString HeaderStyle::attachmentHtml() const
{
return d->mAttachmentHtml;
}
void HeaderStyle::setMessagePath(const QString &path)
{
d->mMessagePath = path;
}
QString HeaderStyle::messagePath() const
{
return d->mMessagePath;
}
void HeaderStyle::setHeaderStrategy(const HeaderStrategy *strategy)
{
d->mStrategy = strategy;
}
const HeaderStrategy *HeaderStyle::headerStrategy() const
{
return d->mStrategy;
}
void HeaderStyle::setVCardName(const QString &vCardName)
{
d->mVCardName = vCardName;
}
QString HeaderStyle::vCardName() const
{
return d->mVCardName;
}
void HeaderStyle::setPrinting(bool printing)
{
d->mPrinting = printing;
}
bool HeaderStyle::isPrinting() const
{
return d->mPrinting;
}
void HeaderStyle::setTopLevel(bool topLevel)
{
d->mTopLevel = topLevel;
}
bool HeaderStyle::isTopLevel() const
{
return d->mTopLevel;
}
void HeaderStyle::setNodeHelper(MimeTreeParser::NodeHelper *nodeHelper)
{
d->mNodeHelper = nodeHelper;
}
MimeTreeParser::NodeHelper *HeaderStyle::nodeHelper() const
{
return d->mNodeHelper;
}
void HeaderStyle::setAllowAsync(bool allowAsync)
{
d->mAllowAsync = allowAsync;
}
bool HeaderStyle::allowAsync() const
{
return d->mAllowAsync;
}
void HeaderStyle::setSourceObject(QObject *sourceObject)
{
d->mSourceObject = sourceObject;
}
QObject *HeaderStyle::sourceObject() const
{
return d->mSourceObject;
}
void HeaderStyle::setMessageStatus(Akonadi::MessageStatus status)
{
d->mMessageStatus = status;
}
Akonadi::MessageStatus HeaderStyle::messageStatus() const
{
return d->mMessageStatus;
}
void HeaderStyle::setTheme(const GrantleeTheme::Theme &theme)
{
d->mTheme = theme;
}
GrantleeTheme::Theme HeaderStyle::theme() const
{
return d->mTheme;
}
void HeaderStyle::setCollectionName(const QString &name)
{
d->mCollectionName = name;
}
QString HeaderStyle::collectionName() const
{
return d->mCollectionName;
}
bool HeaderStyle::readOnlyMessage() const
{
return d->mReadOnlyMessage;
}
void HeaderStyle::setReadOnlyMessage(bool readOnlyMessage)
{
d->mReadOnlyMessage = readOnlyMessage;
}
bool HeaderStyle::showEmoticons() const
{
return d->mShowEmoticons;
}
void HeaderStyle::setShowEmoticons(bool b)
{
d->mShowEmoticons = b;
}
diff --git a/messageviewer/src/header/headerstyle.h b/messageviewer/src/header/headerstyle.h
index 7fc5180f..5cbc37ae 100644
--- a/messageviewer/src/header/headerstyle.h
+++ b/messageviewer/src/header/headerstyle.h
@@ -1,129 +1,130 @@
/* -*- c++ -*-
headerstyle.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_HEADERSTYLE_H
#define MESSAGEVIEWER_HEADERSTYLE_H
#include "messageviewer_export.h"
//#include "grantleetheme/grantleetheme.h"
#include <Akonadi/KMime/MessageStatus>
#include <KMime/Message>
class QString;
namespace MimeTreeParser {
class NodeHelper;
}
namespace GrantleeTheme {
class Theme;
}
namespace MessageViewer {
class HeaderStrategy;
/** This class encapsulates the visual appearance of message
headers. Together with HeaderStrategy, which determines
which of the headers present in the message be shown, it is
responsible for the formatting of message headers.
@short Encapsulates visual appearance of message headers.
@author Marc Mutz <mutz@kde.org>
@see HeaderStrategy
**/
class HeaderStylePrivate;
class MESSAGEVIEWER_EXPORT HeaderStyle
{
protected:
HeaderStyle();
public:
virtual ~HeaderStyle();
//
// Methods for handling the styles:
//
virtual const char *name() const = 0;
//
// HeaderStyle interface:
//
virtual QString format(KMime::Message *message) const = 0;
void setMessagePath(const QString &path);
Q_REQUIRED_RESULT QString messagePath() const;
void setHeaderStrategy(const HeaderStrategy *strategy);
Q_REQUIRED_RESULT const HeaderStrategy *headerStrategy() const;
void setVCardName(const QString &vCardName);
Q_REQUIRED_RESULT QString vCardName() const;
void setPrinting(bool printing);
Q_REQUIRED_RESULT bool isPrinting() const;
void setTopLevel(bool topLevel);
Q_REQUIRED_RESULT bool isTopLevel() const;
void setNodeHelper(MimeTreeParser::NodeHelper *nodeHelper);
Q_REQUIRED_RESULT MimeTreeParser::NodeHelper *nodeHelper() const;
void setAllowAsync(bool allowAsync);
Q_REQUIRED_RESULT bool allowAsync() const;
void setSourceObject(QObject *sourceObject);
QObject *sourceObject() const;
void setMessageStatus(Akonadi::MessageStatus status);
Q_REQUIRED_RESULT Akonadi::MessageStatus messageStatus() const;
void setTheme(const GrantleeTheme::Theme &theme);
Q_REQUIRED_RESULT GrantleeTheme::Theme theme() const;
void setCollectionName(const QString &name);
Q_REQUIRED_RESULT QString collectionName() const;
Q_REQUIRED_RESULT bool readOnlyMessage() const;
void setReadOnlyMessage(bool readOnlyMessage);
Q_REQUIRED_RESULT bool showEmoticons() const;
void setShowEmoticons(bool b);
void setAttachmentHtml(const QString &html);
Q_REQUIRED_RESULT QString attachmentHtml() const;
private:
HeaderStylePrivate *const d;
};
}
#endif // MESSAGEVIEWER_HEADERSTYLE_H
diff --git a/messageviewer/src/header/headerstyle_util.cpp b/messageviewer/src/header/headerstyle_util.cpp
index 56009fdd..535e043d 100644
--- a/messageviewer/src/header/headerstyle_util.cpp
+++ b/messageviewer/src/header/headerstyle_util.cpp
@@ -1,400 +1,400 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#include "headerstyle_util.h"
#include "messageviewer_debug.h"
#include "contactdisplaymessagememento.h"
#include "kxface.h"
#include "header/headerstyle.h"
#include "utils/iconnamecache.h"
#include "settings/messageviewersettings.h"
#include <MessageCore/StringUtil>
#include <MimeTreeParser/NodeHelper>
#include <MessageCore/MessageCoreSettings>
#include <KEmailAddress>
#include <KIconLoader>
#include <KLocalizedString>
#include <KTextToHTML>
#include <QBuffer>
#include <QVariantHash>
using namespace MessageCore;
using namespace MessageViewer;
//
// Convenience functions:
//
HeaderStyleUtil::HeaderStyleUtil()
{
}
QString HeaderStyleUtil::directionOf(const QString &str) const
{
return str.isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr");
}
QString HeaderStyleUtil::strToHtml(const QString &str, KTextToHTML::Options flags)
{
return KTextToHTML::convertToHtml(str, flags, 4096, 512);
}
// Prepare the date string
QString HeaderStyleUtil::dateString(KMime::Message *message, HeaderStyleUtilDateFormat dateFormat)
{
return dateString(message->date(), dateFormat);
}
QString HeaderStyleUtil::dateString(const KMime::Headers::Date *date, HeaderStyleUtilDateFormat dateFormat)
{
const QDateTime dateTime = date->dateTime();
if (!dateTime.isValid()) {
qCDebug(MESSAGEVIEWER_LOG) << "Unable to parse date";
return i18nc("Unknown date", "Unknown");
}
const time_t unixTime = dateTime.toTime_t();
switch (dateFormat) {
case ShortDate:
return KMime::DateFormatter::formatDate(KMime::DateFormatter::Localized, unixTime);
case LongDate:
return KMime::DateFormatter::formatDate(KMime::DateFormatter::CTime, unixTime);
case FancyShortDate:
return KMime::DateFormatter::formatDate(KMime::DateFormatter::Fancy, unixTime);
case FancyLongDate:
//Laurent fix me
//TODO return QLocale::system().toString(dateTime, QLocale::LongFormat);
case CustomDate:
default:
return dateStr(dateTime);
}
}
QString HeaderStyleUtil::subjectString(KMime::Message *message, KTextToHTML::Options flags) const
{
QString subjectStr;
const KMime::Headers::Subject *const subject = message->subject(false);
if (subject) {
subjectStr = subject->asUnicodeString();
if (subjectStr.isEmpty()) {
subjectStr = i18n("No Subject");
} else {
subjectStr = strToHtml(subjectStr, flags);
}
} else {
subjectStr = i18n("No Subject");
}
return subjectStr;
}
QString HeaderStyleUtil::subjectDirectionString(KMime::Message *message) const
{
QString subjectDir;
if (message->subject(false)) {
subjectDir = directionOf(MessageCore::StringUtil::cleanSubject(message));
} else {
subjectDir = directionOf(i18n("No Subject"));
}
return subjectDir;
}
QString HeaderStyleUtil::spamStatus(KMime::Message *message) const
{
QString spamHTML;
const SpamScores scores = SpamHeaderAnalyzer::getSpamScores(message);
for (SpamScores::const_iterator it = scores.constBegin(), end = scores.constEnd(); it != end;
++it) {
spamHTML += (*it).agent() + QLatin1Char(' ')
+drawSpamMeter((*it).error(), (*it).score(),
(*it).confidence(), (*it).spamHeader(),
(*it).confidenceHeader());
}
return spamHTML;
}
QString HeaderStyleUtil::drawSpamMeter(SpamError spamError, double percent, double confidence, const QString &filterHeader, const QString &confidenceHeader) const
{
static const int meterWidth = 20;
static const int meterHeight = 5;
QImage meterBar(meterWidth, 1, QImage::Format_Indexed8 /*QImage::Format_RGB32*/);
meterBar.setColorCount(24);
meterBar.setColor(meterWidth + 1, qRgb(255, 255, 255));
meterBar.setColor(meterWidth + 2, qRgb(170, 170, 170));
if (spamError != noError) { // grey is for errors
meterBar.fill(meterWidth + 2);
} else {
static const unsigned short gradient[meterWidth][3] = {
{ 0, 255, 0 },
{ 27, 254, 0 },
{ 54, 252, 0 },
{ 80, 250, 0 },
{ 107, 249, 0 },
{ 135, 247, 0 },
{ 161, 246, 0 },
{ 187, 244, 0 },
{ 214, 242, 0 },
{ 241, 241, 0 },
{ 255, 228, 0 },
{ 255, 202, 0 },
{ 255, 177, 0 },
{ 255, 151, 0 },
{ 255, 126, 0 },
{ 255, 101, 0 },
{ 255, 76, 0 },
{ 255, 51, 0 },
{ 255, 25, 0 },
{ 255, 0, 0 }
};
meterBar.fill(meterWidth + 1);
const int max = qMin(meterWidth, static_cast<int>(percent) / 5);
for (int i = 0; i < max; ++i) {
meterBar.setColor(i + 1, qRgb(gradient[i][0], gradient[i][1],
gradient[i][2]));
meterBar.setPixel(i, 0, i + 1);
}
}
QString titleText;
QString confidenceString;
if (spamError == noError) {
if (confidence >= 0) {
confidenceString = QString::number(confidence) + QLatin1String("% &nbsp;");
titleText = i18n("%1% probability of being spam with confidence %3%.\n\n"
"Full report:\nProbability=%2\nConfidence=%4",
QString::number(percent, 'f',
2), filterHeader, confidence, confidenceHeader);
} else { // do not show negative confidence
confidenceString = QString() + QLatin1String("&nbsp;");
titleText = i18n("%1% probability of being spam.\n\n"
"Full report:\nProbability=%2",
QString::number(percent, 'f', 2), filterHeader);
}
} else {
QString errorMsg;
switch (spamError) {
case errorExtractingAgentString:
errorMsg = i18n("No Spam agent");
break;
case couldNotConverScoreToFloat:
errorMsg = i18n("Spam filter score not a number");
break;
case couldNotConvertThresholdToFloatOrThresholdIsNegative:
errorMsg = i18n("Threshold not a valid number");
break;
case couldNotFindTheScoreField:
errorMsg = i18n("Spam filter score could not be extracted from header");
break;
case couldNotFindTheThresholdField:
errorMsg = i18n("Threshold could not be extracted from header");
break;
default:
errorMsg = i18n("Error evaluating spam score");
break;
}
// report the error in the spam filter
titleText = i18n("%1.\n\n"
"Full report:\n%2",
errorMsg, filterHeader);
}
return QStringLiteral(
"<img src=\"%1\" width=\"%2\" height=\"%3\" style=\"border: 1px solid black;\" title=\"%4\" />")
.arg(imgToDataUrl(meterBar), QString::number(meterWidth),
QString::number(meterHeight), titleText) + confidenceString;
}
QString HeaderStyleUtil::imgToDataUrl(const QImage &image) const
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
return QStringLiteral("data:image/%1;base64,%2").arg(QStringLiteral("PNG"),
QString::fromLatin1(ba.toBase64()));
}
QString HeaderStyleUtil::dateStr(const QDateTime &dateTime)
{
const time_t unixTime = dateTime.toSecsSinceEpoch();
return KMime::DateFormatter::formatDate(
static_cast<KMime::DateFormatter::FormatType>(
MessageCore::MessageCoreSettings::self()->dateFormat()),
unixTime, MessageCore::MessageCoreSettings::self()->customDateFormat());
}
QString HeaderStyleUtil::dateShortStr(const QDateTime &dateTime)
{
KMime::DateFormatter formatter(KMime::DateFormatter::Fancy);
return formatter.dateString(dateTime);
}
QSharedPointer<KMime::Headers::Generics::MailboxList> mailboxesFromHeader(const KMime::Headers::Base *hrd)
{
QSharedPointer<KMime::Headers::Generics::MailboxList> mailboxList(new KMime::Headers::Generics::MailboxList());
const QByteArray &data = hrd->as7BitString(false);
mailboxList->from7BitString(data);
return mailboxList;
}
QSharedPointer<KMime::Headers::Generics::MailboxList> HeaderStyleUtil::resentFromList(KMime::Message *message)
{
if (auto hrd = message->headerByType("Resent-From")) {
return mailboxesFromHeader(hrd);
}
return nullptr;
}
QSharedPointer<KMime::Headers::Generics::MailboxList> HeaderStyleUtil::resentToList(KMime::Message *message)
{
if (auto hrd = message->headerByType("Resent-To")) {
return mailboxesFromHeader(hrd);
}
return nullptr;
}
void HeaderStyleUtil::updateXFaceSettings(QImage photo, xfaceSettings &settings) const
{
if (!photo.isNull()) {
settings.photoWidth = photo.width();
settings.photoHeight = photo.height();
// scale below 60, otherwise it can get way too large
if (settings.photoHeight > 60) {
double ratio = (double)settings.photoHeight / (double)settings.photoWidth;
settings.photoHeight = 60;
settings.photoWidth = (int)(60 / ratio);
photo = photo.scaled(settings.photoWidth, settings.photoHeight, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
}
settings.photoURL = MessageViewer::HeaderStyleUtil::imgToDataUrl(photo);
}
}
HeaderStyleUtil::xfaceSettings HeaderStyleUtil::xface(const MessageViewer::HeaderStyle *style, KMime::Message *message) const
{
xfaceSettings settings;
bool useOtherPhotoSources = false;
if (style->allowAsync()) {
Q_ASSERT(style->nodeHelper());
Q_ASSERT(style->sourceObject());
ContactDisplayMessageMemento *photoMemento
= dynamic_cast<ContactDisplayMessageMemento *>(style->nodeHelper()->bodyPartMemento(
message, "contactphoto"));
if (!photoMemento) {
const QString email
= QString::fromLatin1(KEmailAddress::firstEmailAddress(message->from()->as7BitString(
false)));
photoMemento = new ContactDisplayMessageMemento(email);
style->nodeHelper()->setBodyPartMemento(message, "contactphoto", photoMemento);
QObject::connect(photoMemento, SIGNAL(update(MimeTreeParser::UpdateMode)),
style->sourceObject(), SLOT(update(MimeTreeParser::UpdateMode)));
QObject::connect(photoMemento,
SIGNAL(changeDisplayMail(Viewer::DisplayFormatMessage,bool)),
style->sourceObject(),
SIGNAL(changeDisplayMail(Viewer::DisplayFormatMessage,bool)));
}
if (photoMemento->finished()) {
useOtherPhotoSources = true;
if (photoMemento->photo().isIntern()) {
// get photo data and convert to data: url
const QImage photo = photoMemento->photo().data();
updateXFaceSettings(photo, settings);
} else if (!photoMemento->imageFromUrl().isNull()) {
updateXFaceSettings(photoMemento->imageFromUrl(), settings);
} else if (!photoMemento->photo().url().isEmpty()) {
settings.photoURL = photoMemento->photo().url();
if (settings.photoURL.startsWith(QLatin1Char('/'))) {
settings.photoURL.prepend(QLatin1String("file:"));
}
} else if (!photoMemento->gravatarPixmap().isNull()) {
const QImage photo = photoMemento->gravatarPixmap().toImage();
updateXFaceSettings(photo, settings);
}
} else {
// if the memento is not finished yet, use other photo sources instead
useOtherPhotoSources = true;
}
} else {
useOtherPhotoSources = true;
}
if (settings.photoURL.isEmpty() && useOtherPhotoSources) {
if (auto hrd = message->headerByType("Face")) {
// no photo, look for a Face header
const QString faceheader = hrd->asUnicodeString();
if (!faceheader.isEmpty()) {
qCDebug(MESSAGEVIEWER_LOG) << "Found Face: header";
const QByteArray facestring = faceheader.toUtf8();
// Spec says header should be less than 998 bytes
// Face: is 5 characters
if (facestring.length() < 993) {
const QByteArray facearray = QByteArray::fromBase64(facestring);
QImage faceimage;
if (faceimage.loadFromData(facearray, "png")) {
// Spec says image must be 48x48 pixels
if ((48 == faceimage.width()) && (48 == faceimage.height())) {
settings.photoURL = MessageViewer::HeaderStyleUtil::imgToDataUrl(
faceimage);
settings.photoWidth = 48;
settings.photoHeight = 48;
} else {
qCDebug(MESSAGEVIEWER_LOG) << "Face: header image is"
<< faceimage.width() << "by"
<< faceimage.height() << "not 48x48 Pixels";
}
} else {
qCDebug(MESSAGEVIEWER_LOG)
<< "Failed to load decoded png from Face: header";
}
} else {
qCDebug(MESSAGEVIEWER_LOG) << "Face: header too long at" << facestring.length();
}
}
}
}
if (settings.photoURL.isEmpty() && useOtherPhotoSources) {
if (auto hrd = message->headerByType("X-Face")) {
// no photo, look for a X-Face header
const QString xfhead = hrd->asUnicodeString();
if (!xfhead.isEmpty()) {
MessageViewer::KXFace xf;
settings.photoURL
= MessageViewer::HeaderStyleUtil::imgToDataUrl(xf.toImage(xfhead));
settings.photoWidth = 48;
settings.photoHeight = 48;
}
}
}
return settings;
}
diff --git a/messageviewer/src/header/headerstyle_util.h b/messageviewer/src/header/headerstyle_util.h
index e2b8a566..f81d4432 100644
--- a/messageviewer/src/header/headerstyle_util.h
+++ b/messageviewer/src/header/headerstyle_util.h
@@ -1,90 +1,89 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#ifndef HEADERSTYLE_UTIL_H
#define HEADERSTYLE_UTIL_H
#include <QString>
#include <QVariantHash>
#include <KTextToHTML>
#include <kmime/kmime_message.h>
#include <kmime/kmime_dateformatter.h>
#include "messageviewer/spamheaderanalyzer.h"
#include "messageviewer/headerstyle.h"
#include "messageviewer/viewer.h"
#include "messageviewer_export.h"
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT HeaderStyleUtil
{
public:
HeaderStyleUtil();
struct xfaceSettings {
xfaceSettings()
: photoWidth(60)
, photoHeight(60)
{
}
QString photoURL;
int photoWidth;
int photoHeight;
};
enum HeaderStyleUtilDateFormat {
ShortDate, /**< Locale Short date format, e.g. 08-04-2007 */
LongDate, /**< Locale Long date format, e.g. Sunday 08 April 2007 */
FancyShortDate, /**< Same as ShortDate for dates a week or more ago. For more
recent dates, it is represented as Today, Yesterday, or
the weekday name. */
FancyLongDate, /**< Same as LongDate for dates a week or more ago. For more
recent dates, it is represented as Today, Yesterday, or
the weekday name. */
CustomDate
};
Q_REQUIRED_RESULT QString directionOf(const QString &str) const;
static Q_REQUIRED_RESULT QString strToHtml(const QString &str, KTextToHTML::Options flags = KTextToHTML::PreserveSpaces);
static Q_REQUIRED_RESULT QString dateString(KMime::Message *message, HeaderStyleUtilDateFormat dateFormat);
static Q_REQUIRED_RESULT QString dateString(const KMime::Headers::Date *date, HeaderStyleUtilDateFormat dateFormat);
-
Q_REQUIRED_RESULT QString subjectString(KMime::Message *message, KTextToHTML::Options flags = KTextToHTML::PreserveSpaces) const;
Q_REQUIRED_RESULT QString subjectDirectionString(KMime::Message *message) const;
Q_REQUIRED_RESULT QString spamStatus(KMime::Message *message) const;
static Q_REQUIRED_RESULT QString dateStr(const QDateTime &dateTime);
static Q_REQUIRED_RESULT QString dateShortStr(const QDateTime &dateTime);
static Q_REQUIRED_RESULT QSharedPointer<KMime::Headers::Generics::MailboxList> resentFromList(KMime::Message *message);
static Q_REQUIRED_RESULT QSharedPointer<KMime::Headers::Generics::MailboxList> resentToList(KMime::Message *message);
Q_REQUIRED_RESULT xfaceSettings xface(const HeaderStyle *style, KMime::Message *message) const;
private:
void updateXFaceSettings(QImage photo, xfaceSettings &settings) const;
QString drawSpamMeter(SpamError spamError, double percent, double confidence, const QString &filterHeader, const QString &confidenceHeader) const;
QString imgToDataUrl(const QImage &image) const;
};
}
#endif // HEADERSTYLE_UTIL_H
diff --git a/messageviewer/src/header/headerstyleinterface.cpp b/messageviewer/src/header/headerstyleinterface.cpp
index 290a4aa6..7264e290 100644
--- a/messageviewer/src/header/headerstyleinterface.cpp
+++ b/messageviewer/src/header/headerstyleinterface.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "headerstyle.h"
#include "headerstyleinterface.h"
#include "headerstyleplugin.h"
#include <KActionMenu>
#include <QAction>
#include <KToggleAction>
using namespace MessageViewer;
HeaderStyleInterface::HeaderStyleInterface(MessageViewer::HeaderStylePlugin *headerStylePlugin, QObject *parent)
: QObject(parent)
, mHeaderStylePlugin(headerStylePlugin)
{
}
HeaderStyleInterface::~HeaderStyleInterface()
{
}
QList<KToggleAction *> HeaderStyleInterface::action() const
{
return mAction;
}
void HeaderStyleInterface::addHelpTextAction(QAction *act, const QString &text)
{
act->setStatusTip(text);
act->setToolTip(text);
if (act->whatsThis().isEmpty()) {
act->setWhatsThis(text);
}
}
void HeaderStyleInterface::addActionToMenu(KActionMenu *menu, QActionGroup *actionGroup)
{
for (KToggleAction *taction : qAsConst(mAction)) {
menu->addAction(taction);
actionGroup->addAction(taction);
}
}
HeaderStylePlugin *HeaderStyleInterface::headerStylePlugin() const
{
return mHeaderStylePlugin;
}
-void MessageViewer::HeaderStyleInterface::slotStyleChanged()
+void HeaderStyleInterface::slotStyleChanged()
{
Q_EMIT styleChanged(mHeaderStylePlugin);
}
diff --git a/messageviewer/src/header/headerstyleinterface.h b/messageviewer/src/header/headerstyleinterface.h
index b4b7fe2b..d215fa4a 100644
--- a/messageviewer/src/header/headerstyleinterface.h
+++ b/messageviewer/src/header/headerstyleinterface.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef HEADERSTYLEINTERFACE_H
#define HEADERSTYLEINTERFACE_H
#include <QObject>
#include "messageviewer_export.h"
class KToggleAction;
class KActionCollection;
class QAction;
class QActionGroup;
class KActionMenu;
namespace MessageViewer {
class HeaderStyle;
class HeaderStylePlugin;
class MESSAGEVIEWER_EXPORT HeaderStyleInterface : public QObject
{
Q_OBJECT
public:
explicit HeaderStyleInterface(MessageViewer::HeaderStylePlugin *, QObject *parent = nullptr);
~HeaderStyleInterface() override;
Q_REQUIRED_RESULT QList<KToggleAction * > action() const;
virtual void createAction(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac) = 0;
virtual void activateAction() = 0;
Q_REQUIRED_RESULT HeaderStylePlugin *headerStylePlugin() const;
Q_SIGNALS:
void styleChanged(MessageViewer::HeaderStylePlugin *plugin);
void styleUpdated();
protected Q_SLOTS:
void slotStyleChanged();
protected:
void addHelpTextAction(QAction *act, const QString &text);
void addActionToMenu(KActionMenu *menu, QActionGroup *actionGroup);
QList<KToggleAction *> mAction;
HeaderStylePlugin *mHeaderStylePlugin = nullptr;
};
}
#endif // HEADERSTYLEINTERFACE_H
diff --git a/messageviewer/src/header/headerstylemenumanager.cpp b/messageviewer/src/header/headerstylemenumanager.cpp
index bab80592..5b01359c 100644
--- a/messageviewer/src/header/headerstylemenumanager.cpp
+++ b/messageviewer/src/header/headerstylemenumanager.cpp
@@ -1,190 +1,190 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "headerstyleinterface.h"
#include "headerstylemenumanager.h"
#include "headerstyleplugin.h"
#include "headerstylepluginmanager.h"
#include "header/headerstyle.h"
#include "header/headerstrategy.h"
#include "messageviewer_debug.h"
#include "messageviewer/messageviewersettings.h"
#include <KActionMenu>
#include <KActionCollection>
#include <KLocalizedString>
using namespace MessageViewer;
class MessageViewer::HeaderStyleMenuManagerPrivate
{
public:
HeaderStyleMenuManagerPrivate(HeaderStyleMenuManager *qq)
: q(qq)
{
}
void readSettings();
void writeSettings(const QString &pluginName);
void initialize(KActionCollection *ac);
void addHelpTextAction(QAction *act, const QString &text);
void setPluginName(const QString &pluginName);
QHash<QString, MessageViewer::HeaderStyleInterface *> lstInterface;
QActionGroup *group = nullptr;
KActionMenu *headerMenu = nullptr;
HeaderStyleMenuManager *q = nullptr;
};
void HeaderStyleMenuManagerPrivate::addHelpTextAction(QAction *act, const QString &text)
{
act->setStatusTip(text);
act->setToolTip(text);
if (act->whatsThis().isEmpty()) {
act->setWhatsThis(text);
}
}
void HeaderStyleMenuManagerPrivate::setPluginName(const QString &pluginName)
{
MessageViewer::HeaderStyleInterface *interface = lstInterface.value(pluginName);
if (interface) {
if (!interface->action().isEmpty()) {
interface->activateAction();
}
} else {
if (lstInterface.isEmpty()) {
qCCritical(MESSAGEVIEWER_LOG) << "No Header Style plugin found !";
return;
} else {
interface = lstInterface.cbegin().value();
if (!interface->action().isEmpty()) {
interface->activateAction();
}
}
}
Q_EMIT q->styleChanged(interface->headerStylePlugin());
}
void HeaderStyleMenuManagerPrivate::readSettings()
{
QString headerStyleName = MessageViewer::MessageViewerSettings::self()->headerPluginStyleName();
if (headerStyleName.isEmpty()) {
const QString headerStyle = MessageViewer::MessageViewerSettings::self()->headerStyle();
const QString headerSetDisplayed
= MessageViewer::MessageViewerSettings::self()->headerSetDisplayed();
if ((headerStyle == QLatin1String("custom"))
&& (headerSetDisplayed == QLatin1String("custom"))) { //Custom
headerStyleName = QStringLiteral("custom");
} else if ((headerStyle == QLatin1String("plain"))
&& (headerSetDisplayed == QLatin1String("all"))) { //all
headerStyleName = QStringLiteral("all-headers");
} else if ((headerStyle == QLatin1String("brief"))
&& (headerSetDisplayed == QLatin1String("brief"))) { //brief
headerStyleName = QStringLiteral("brief");
} else if ((headerStyle == QLatin1String("enterprise"))
&& (headerSetDisplayed == QLatin1String("rich"))) { //enterprise
headerStyleName = QStringLiteral("enterprise");
} else if ((headerStyle == QLatin1String("fancy"))
&& (headerSetDisplayed == QLatin1String("rich"))) { //fancy
headerStyleName = QStringLiteral("fancy");
} else if ((headerStyle == QLatin1String("grantlee"))
&& (headerSetDisplayed == QLatin1String("grantlee"))) { //grantlee
headerStyleName = QStringLiteral("grantlee");
} else if ((headerStyle == QLatin1String("plain"))
&& (headerSetDisplayed == QLatin1String("rich"))) { //longheader
headerStyleName = QStringLiteral("long-header");
} else if ((headerStyle == QLatin1String("plain"))
&& (headerSetDisplayed == QLatin1String("standard"))) { //Standard
headerStyleName = QStringLiteral("standards-header");
} else {
qCDebug(MESSAGEVIEWER_LOG) << "unknown style : headerstyle " << headerStyle
<< " headerstrategy :" << headerSetDisplayed;
}
MessageViewer::MessageViewerSettings::self()->setHeaderPluginStyleName(headerStyleName);
}
//Fallback
if (headerStyleName.isEmpty()) {
headerStyleName = QStringLiteral("fancy");
}
setPluginName(headerStyleName);
}
void HeaderStyleMenuManagerPrivate::writeSettings(const QString &pluginName)
{
MessageViewer::MessageViewerSettings::self()->setHeaderPluginStyleName(pluginName);
MessageViewer::MessageViewerSettings::self()->save();
}
void HeaderStyleMenuManagerPrivate::initialize(KActionCollection *ac)
{
headerMenu = new KActionMenu(i18nc("View->", "&Headers"), q);
if (ac) {
ac->addAction(QStringLiteral("view_headers"), headerMenu);
}
addHelpTextAction(headerMenu, i18n("Choose display style of message headers"));
group = new QActionGroup(q);
const QVector<MessageViewer::HeaderStylePlugin *> lstPlugin
= MessageViewer::HeaderStylePluginManager::self()->pluginsList();
for (MessageViewer::HeaderStylePlugin *plugin : lstPlugin) {
if (plugin->isEnabled()) {
MessageViewer::HeaderStyleInterface *interface = plugin->createView(headerMenu, group,
ac, q);
lstInterface.insert(plugin->name(), interface);
q->connect(interface, &HeaderStyleInterface::styleChanged, q,
&HeaderStyleMenuManager::slotStyleChanged);
q->connect(interface, &HeaderStyleInterface::styleUpdated, q,
&HeaderStyleMenuManager::styleUpdated);
}
}
}
HeaderStyleMenuManager::HeaderStyleMenuManager(KActionCollection *ac, QObject *parent)
: QObject(parent)
, d(new MessageViewer::HeaderStyleMenuManagerPrivate(this))
{
d->initialize(ac);
}
HeaderStyleMenuManager::~HeaderStyleMenuManager()
{
delete d;
}
KActionMenu *HeaderStyleMenuManager::menu() const
{
return d->headerMenu;
}
void MessageViewer::HeaderStyleMenuManager::setPluginName(const QString &pluginName)
{
d->setPluginName(pluginName);
}
void HeaderStyleMenuManager::slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin)
{
d->writeSettings(plugin->name());
Q_EMIT styleChanged(plugin);
}
void HeaderStyleMenuManager::readConfig()
{
d->readSettings();
}
diff --git a/messageviewer/src/header/headerstylemenumanager.h b/messageviewer/src/header/headerstylemenumanager.h
index 382ca0dc..1d75deb0 100644
--- a/messageviewer/src/header/headerstylemenumanager.h
+++ b/messageviewer/src/header/headerstylemenumanager.h
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef HEADERSTYLEMENU_H
#define HEADERSTYLEMENU_H
#include "messageviewer_export.h"
#include <QObject>
class KActionMenu;
class KActionCollection;
namespace MessageViewer {
class HeaderStylePlugin;
class HeaderStyleMenuManagerPrivate;
class MESSAGEVIEWER_EXPORT HeaderStyleMenuManager : public QObject
{
Q_OBJECT
public:
explicit HeaderStyleMenuManager(KActionCollection *ac, QObject *parent = nullptr);
~HeaderStyleMenuManager();
Q_REQUIRED_RESULT KActionMenu *menu() const;
void setPluginName(const QString &pluginName);
void readConfig();
public Q_SLOTS:
void slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin);
Q_SIGNALS:
void styleChanged(MessageViewer::HeaderStylePlugin *plugin);
void styleUpdated();
private:
HeaderStyleMenuManagerPrivate *const d;
};
}
#endif // HEADERSTYLEMENU_H
diff --git a/messageviewer/src/header/headerstyleplugin.cpp b/messageviewer/src/header/headerstyleplugin.cpp
index 2502be8c..f294537e 100644
--- a/messageviewer/src/header/headerstyleplugin.cpp
+++ b/messageviewer/src/header/headerstyleplugin.cpp
@@ -1,79 +1,97 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "headerstyleplugin.h"
using namespace MessageViewer;
class MessageViewer::HeaderStylePluginPrivate
{
public:
HeaderStylePluginPrivate()
{
}
bool mIsEnabled = false;
bool mShowEmoticons = true;
};
HeaderStylePlugin::HeaderStylePlugin(QObject *parent)
: QObject(parent)
, d(new MessageViewer::HeaderStylePluginPrivate)
{
}
HeaderStylePlugin::~HeaderStylePlugin()
{
delete d;
}
bool HeaderStylePlugin::hasMargin() const
{
return true;
}
QString HeaderStylePlugin::alignment() const
{
return QStringLiteral("left");
}
int HeaderStylePlugin::elidedTextSize() const
{
return -1;
}
void HeaderStylePlugin::setIsEnabled(bool enabled)
{
d->mIsEnabled = enabled;
}
bool HeaderStylePlugin::isEnabled() const
{
return d->mIsEnabled;
}
bool HeaderStylePlugin::hasConfigureDialog() const
{
return false;
}
void HeaderStylePlugin::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
//Reimplement
}
+
+QString HeaderStylePlugin::extraScreenCss(const QString &headerFont) const
+{
+ Q_UNUSED(headerFont);
+ return {};
+}
+
+QString HeaderStylePlugin::extraPrintCss(const QString &headerFont) const
+{
+ Q_UNUSED(headerFont);
+ return {};
+}
+
+QString HeaderStylePlugin::extraCommonCss(const QString &headerFont) const
+{
+ Q_UNUSED(headerFont);
+ return {};
+}
diff --git a/messageviewer/src/header/headerstyleplugin.h b/messageviewer/src/header/headerstyleplugin.h
index ff2f0a0f..58cbb52f 100644
--- a/messageviewer/src/header/headerstyleplugin.h
+++ b/messageviewer/src/header/headerstyleplugin.h
@@ -1,60 +1,64 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef HEADERSTYLEPLUGIN_H
#define HEADERSTYLEPLUGIN_H
#include <QObject>
#include "messageviewer_export.h"
class KActionCollection;
class QActionGroup;
class KActionMenu;
namespace MessageViewer {
class HeaderStyle;
class HeaderStrategy;
class HeaderStylePluginPrivate;
class HeaderStyleInterface;
class MESSAGEVIEWER_EXPORT HeaderStylePlugin : public QObject
{
Q_OBJECT
public:
explicit HeaderStylePlugin(QObject *parent = nullptr);
~HeaderStylePlugin();
virtual HeaderStyle *headerStyle() const = 0;
virtual HeaderStrategy *headerStrategy() const = 0;
virtual HeaderStyleInterface *createView(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac, QObject *parent = nullptr) = 0;
virtual QString name() const = 0;
virtual bool hasMargin() const;
virtual QString alignment() const;
virtual int elidedTextSize() const;
void setIsEnabled(bool enabled);
bool isEnabled() const;
virtual bool hasConfigureDialog() const;
virtual void showConfigureDialog(QWidget *parent);
+ virtual QString extraScreenCss(const QString &headerFont) const;
+ virtual QString extraPrintCss(const QString &headerFont) const;
+ virtual QString extraCommonCss(const QString &headerFont) const;
+
private:
HeaderStylePluginPrivate *const d;
};
}
#endif // HEADERSTYLEPLUGIN_H
diff --git a/messageviewer/src/header/headerstylepluginmanager.cpp b/messageviewer/src/header/headerstylepluginmanager.cpp
index 75064ea3..d997985d 100644
--- a/messageviewer/src/header/headerstylepluginmanager.cpp
+++ b/messageviewer/src/header/headerstylepluginmanager.cpp
@@ -1,243 +1,243 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "headerstylepluginmanager.h"
#include "headerstyleplugin.h"
#include "messageviewer_debug.h"
#include <KPluginFactory>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <QFileInfo>
#include <QSet>
using namespace MessageViewer;
class HeaderStylePluginInfo
{
public:
HeaderStylePluginInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
MessageViewer::HeaderStylePlugin *plugin = nullptr;
bool isEnabled = false;
};
class MessageViewer::HeaderStylePluginManagerPrivate
{
public:
HeaderStylePluginManagerPrivate(HeaderStylePluginManager *qq)
: q(qq)
{
}
QVector<MessageViewer::HeaderStylePlugin *> pluginsList() const;
QVector<PimCommon::PluginUtilData> pluginDataList() const;
void initializePluginList();
void loadPlugin(HeaderStylePluginInfo *item);
QString configGroupName() const;
QString configPrefixSettingKey() const;
MessageViewer::HeaderStylePlugin *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
QVector<HeaderStylePluginInfo> mPluginList;
HeaderStylePluginManager *q;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
QVector<PimCommon::PluginUtilData> HeaderStylePluginManagerPrivate::pluginDataList() const
{
return mPluginDataList;
}
QString HeaderStylePluginManagerPrivate::configGroupName() const
{
return QStringLiteral("HeaderStylePlugins");
}
QString HeaderStylePluginManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("PluginHeaderStyle");
}
void HeaderStylePluginManagerPrivate::initializePluginList()
{
const QVector<KPluginMetaData> plugins
= KPluginLoader::findPlugins(QStringLiteral("messageviewer"), [](
const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("MessageViewerHeaderStyle/Plugin"));
});
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(
configGroupName(), configPrefixSettingKey());
QSet<QString> unique;
QVector<int> listOrder;
while (i.hasPrevious()) {
HeaderStylePluginInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first,
pair.second,
info.pluginData.mEnableByDefault,
info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
const QString version = data.version();
if (pluginVersion() == version) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
const QVariant p
= data.rawData().value(QStringLiteral("X-KDE-MessageViewer-Header-Order")).toVariant();
int order = -1;
if (p.isValid()) {
order = p.toInt();
}
int pos = 0;
for (; pos < listOrder.count(); ++pos) {
if (listOrder.at(pos) > order) {
pos--;
break;
}
}
pos = qMax(0, pos);
listOrder.insert(pos, order);
info.plugin = nullptr;
mPluginList.insert(pos, info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGEVIEWER_LOG) << "Plugin " << data.name()
<<
" doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<HeaderStylePluginInfo>::iterator end(mPluginList.end());
for (QVector<HeaderStylePluginInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
}
QVector<MessageViewer::HeaderStylePlugin *> HeaderStylePluginManagerPrivate::pluginsList() const
{
QVector<MessageViewer::HeaderStylePlugin *> lst;
QVector<HeaderStylePluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<HeaderStylePluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end;
++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
void HeaderStylePluginManagerPrivate::loadPlugin(HeaderStylePluginInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<MessageViewer::HeaderStylePlugin>(q,
QVariantList()
<< item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = false;
mPluginDataList.append(item->pluginData);
}
}
MessageViewer::HeaderStylePlugin *HeaderStylePluginManagerPrivate::pluginFromIdentifier(
const QString &id)
{
QVector<HeaderStylePluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<HeaderStylePluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end;
++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
HeaderStylePluginManager *HeaderStylePluginManager::self()
{
static HeaderStylePluginManager s_self;
return &s_self;
}
HeaderStylePluginManager::HeaderStylePluginManager(QObject *parent)
: QObject(parent)
, d(new MessageViewer::HeaderStylePluginManagerPrivate(this))
{
d->initializePluginList();
}
HeaderStylePluginManager::~HeaderStylePluginManager()
{
delete d;
}
QVector<MessageViewer::HeaderStylePlugin *> HeaderStylePluginManager::pluginsList() const
{
return d->pluginsList();
}
QStringList HeaderStylePluginManager::pluginListName() const
{
QStringList lst;
lst.reserve(d->pluginsList().count());
for (MessageViewer::HeaderStylePlugin *plugin : d->pluginsList()) {
lst << plugin->name();
}
return lst;
}
QString HeaderStylePluginManager::configGroupName() const
{
return d->configGroupName();
}
QString HeaderStylePluginManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
QVector<PimCommon::PluginUtilData> HeaderStylePluginManager::pluginsDataList() const
{
return d->pluginDataList();
}
MessageViewer::HeaderStylePlugin *HeaderStylePluginManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messageviewer/src/header/headerstylepluginmanager.h b/messageviewer/src/header/headerstylepluginmanager.h
index 985c650b..b932716c 100644
--- a/messageviewer/src/header/headerstylepluginmanager.h
+++ b/messageviewer/src/header/headerstylepluginmanager.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef HEADERSTYLEPLUGINMANAGER_H
#define HEADERSTYLEPLUGINMANAGER_H
#include <QObject>
#include "messageviewer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageViewer {
class HeaderStylePlugin;
class HeaderStylePluginManagerPrivate;
class MESSAGEVIEWER_EXPORT HeaderStylePluginManager : public QObject
{
Q_OBJECT
public:
static HeaderStylePluginManager *self();
explicit HeaderStylePluginManager(QObject *parent = nullptr);
~HeaderStylePluginManager();
Q_REQUIRED_RESULT QVector<MessageViewer::HeaderStylePlugin *> pluginsList() const;
Q_REQUIRED_RESULT QStringList pluginListName() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT MessageViewer::HeaderStylePlugin *pluginFromIdentifier(const QString &id);
private:
HeaderStylePluginManagerPrivate *const d;
};
}
#endif // HEADERSTYLEPLUGINMANAGER_H
diff --git a/messageviewer/src/header/kxface.h b/messageviewer/src/header/kxface.h
index ff52be09..07083ea1 100644
--- a/messageviewer/src/header/kxface.h
+++ b/messageviewer/src/header/kxface.h
@@ -1,580 +1,580 @@
/*
This file is part of libkdepim.
Original compface:
Copyright (c) James Ashton - Sydney University - June 1990. //krazy:exclude=copyright
Additions for KDE:
Copyright (c) 2004 Jakob Schröter <js@camaya.net>
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.
*/
#ifndef MESSAGEVIEWER_KXFACE_H
#define MESSAGEVIEWER_KXFACE_H
#include "messageviewer_export.h"
#include <QObject>
#include <setjmp.h>
// #define WIDTH 48
// #define HEIGHT WIDTH
/* total number of pixels and digits */
// #define PIXELS (WIDTH * HEIGHT)
// #define WORD unsigned char
// #define MAXWORDS ((PIXELS * 2 + BITSPERWORD - 1) / BITSPERWORD)
// #define BITSPERWORD 8
typedef struct guesses {
char g_00[1 << 12];
char g_01[1 << 7];
char g_02[1 << 2];
char g_10[1 << 9];
char g_20[1 << 6];
char g_30[1 << 8];
char g_40[1 << 10];
char g_11[1 << 5];
char g_21[1 << 3];
char g_31[1 << 5];
char g_41[1 << 6];
char g_12[1 << 1];
char g_22[1 << 0];
char g_32[1 << 2];
char g_42[1 << 2];
} Guesses;
static const Guesses G = {
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1,
0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1,
1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1,
1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0,
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0,
1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0,
1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 1, 0, 1,
},
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1,
},
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1,
0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1,
0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 0, 1, 0, 1, 1, 1,
},
{
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1,
0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{
0, 1,
},
{
0,
},
{
0, 0, 0, 1,
},
{
0, 0, 0, 1,
}
};
typedef struct prob {
int p_range;
int p_offset;
} Prob;
static const Prob levels[4][3] = {
{{1, 255}, {251, 0}, {4, 251}}, /* Top of tree almost always grey */
{{1, 255}, {200, 0}, {55, 200}},
{{33, 223}, {159, 0}, {64, 159}},
{{131, 0}, {0, 0}, {125, 131}} /* Grey disallowed at bottom */
};
static const Prob freqs[16] = {
{0, 0}, {38, 0}, {38, 38}, {13, 152},
{38, 76}, {13, 165}, {13, 178}, {6, 230},
{38, 114}, {13, 191}, {13, 204}, {6, 236},
{13, 217}, {6, 242}, {5, 248}, {3, 253}
};
static const char HexDigits[] = "0123456789ABCDEF";
class QImage;
class QString;
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT KXFace : public QObject
{
Q_OBJECT
public:
KXFace();
~KXFace();
/**
* generates the xface string from @p image
*/
QString fromImage(const QImage &image);
/**
* creates a pixmap from @p xface
*/
QImage toImage(const QString &xface);
private:
static const int WIDTH = 48;
static const int HEIGHT = WIDTH;
static const int PIXELS = (WIDTH *HEIGHT);
static const int BITSPERWORD = 8;
static const int MAXWORDS = ((PIXELS * 2 + BITSPERWORD - 1) / BITSPERWORD);
int NumProbs;
int status;
int compface_xbitmap;
char F[PIXELS];
Prob *ProbBuf[PIXELS * 2];
jmp_buf comp_env;
typedef struct bigint {
int b_words;
unsigned char b_word[MAXWORDS];
} BigInt;
BigInt B;
void RevPush(const Prob *p);
void BigPush(Prob *p);
- int BigPop(register const Prob *p);
- void BigDiv(register unsigned char a, register unsigned char *r);
- void BigMul(register unsigned char a);
+ int BigPop(const Prob *p);
+ void BigDiv(unsigned char a, unsigned char *r);
+ void BigMul(unsigned char a);
void BigAdd(unsigned char a);
void BigClear();
QByteArray WriteFace();
void UnCompAll(char *fbuf);
- void UnCompress(register char *f, register int wid, register int hei, register int lev);
- void BigWrite(register char *fbuf);
- void BigRead(register char *fbuf);
+ void UnCompress(char *f, int wid, int hei, int lev);
+ void BigWrite(char *fbuf);
+ void BigRead(char *fbuf);
void ReadFace(char *fbuf);
void GenFace();
void UnGenFace();
- void Gen(register char *f);
+ void Gen(char *f);
void PopGreys(char *f, int wid, int hei);
void CompAll(char *fbuf);
- void Compress(register char *f, register int wid, register int hei, register int lev);
+ void Compress(char *f, int wid, int hei, int lev);
int AllWhite(char *f, int wid, int hei);
int AllBlack(char *f, int wid, int hei);
- int Same(register char *f, register int wid, register int hei);
+ int Same(char *f, int wid, int hei);
void PushGreys(char *f, int wid, int hei);
};
}
#endif
diff --git a/messageviewer/src/header/plainheaderstyle.cpp b/messageviewer/src/header/plainheaderstyle.cpp
index 3b89bb94..66dd886a 100644
--- a/messageviewer/src/header/plainheaderstyle.cpp
+++ b/messageviewer/src/header/plainheaderstyle.cpp
@@ -1,190 +1,190 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#include "plainheaderstyle.h"
#include "messageviewer/messageviewersettings.h"
#include "header/headerstyle_util.h"
#include "header/headerstrategy.h"
#include <MessageCore/StringUtil>
#include <KLocalizedString>
#include <QApplication>
#include <kmime/kmime_message.h>
using namespace MessageCore;
using namespace MessageViewer;
class MessageViewer::PlainHeaderStylePrivate
{
public:
PlainHeaderStylePrivate()
{
}
QString formatAllMessageHeaders(KMime::Message *message) const;
MessageViewer::HeaderStyleUtil mHeaderStyleUtil;
};
QString PlainHeaderStylePrivate::formatAllMessageHeaders(KMime::Message *message) const
{
QByteArray head = message->head();
KMime::Headers::Base *header = KMime::HeaderParsing::extractFirstHeader(head);
QString result;
while (header) {
result += mHeaderStyleUtil.strToHtml(QLatin1String(header->type()) + QLatin1String(
": ") + header->asUnicodeString());
result += QLatin1String("<br />\n");
delete header;
header = KMime::HeaderParsing::extractFirstHeader(head);
}
return result;
}
PlainHeaderStyle::PlainHeaderStyle()
: HeaderStyle()
, d(new MessageViewer::PlainHeaderStylePrivate)
{
}
PlainHeaderStyle::~PlainHeaderStyle()
{
delete d;
}
//
// PlainHeaderStyle:
// show every header field on a line by itself,
// show subject larger
//
QString PlainHeaderStyle::format(KMime::Message *message) const
{
if (!message) {
return QString();
}
const HeaderStrategy *strategy = headerStrategy();
// The direction of the header is determined according to the direction
// of the application layout.
const QString dir
= QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr");
// However, the direction of the message subject within the header is
// determined according to the contents of the subject itself. Since
// the "Re:" and "Fwd:" prefixes would always cause the subject to be
// considered left-to-right, they are ignored when determining its
// direction.
const QString subjectDir = d->mHeaderStyleUtil.subjectDirectionString(message);
QString headerStr;
if (strategy->headersToDisplay().isEmpty()
&& strategy->defaultPolicy() == HeaderStrategy::Display) {
// crude way to emulate "all" headers - Note: no strings have
// i18n(), so direction should always be ltr.
headerStr = QStringLiteral("<div class=\"header\" dir=\"ltr\">");
headerStr += d->formatAllMessageHeaders(message);
return headerStr + QLatin1String("</div>");
}
headerStr = QStringLiteral("<div class=\"header\" dir=\"%1\">").arg(dir);
//case HdrLong:
if (strategy->showHeader(QStringLiteral("subject"))) {
KTextToHTML::Options flags = KTextToHTML::PreserveSpaces;
if (showEmoticons()) {
flags |= KTextToHTML::ReplaceSmileys;
}
headerStr += QStringLiteral("<div dir=\"%1\"><b style=\"font-size:130%\">").arg(subjectDir)
+d->mHeaderStyleUtil.subjectString(message, flags) + QLatin1String(
"</b></div>\n");
}
if (strategy->showHeader(QStringLiteral("date"))) {
- const auto dateFormat = isPrinting()? MessageViewer::HeaderStyleUtil::ShortDate : MessageViewer::HeaderStyleUtil::CustomDate;
+ const auto dateFormat = isPrinting() ? MessageViewer::HeaderStyleUtil::ShortDate : MessageViewer::HeaderStyleUtil::CustomDate;
headerStr.append(i18n("Date: ")
+ HeaderStyleUtil::strToHtml(HeaderStyleUtil::dateString(message,
dateFormat))
+ QLatin1String("<br/>\n"));
}
if (strategy->showHeader(QStringLiteral("from"))) {
headerStr.append(i18n("From: ")
+StringUtil::emailAddrAsAnchor(message->from(),
StringUtil::DisplayFullAddress, QString(),
StringUtil::ShowLink));
if (!vCardName().isEmpty()) {
headerStr.append(QLatin1String("&nbsp;&nbsp;<a href=\"") + vCardName()
+QLatin1String("\">") + i18n("[vCard]") + QLatin1String("</a>"));
}
if (strategy->showHeader(QStringLiteral("organization"))
&& message->organization(false)) {
headerStr.append(QLatin1String("&nbsp;&nbsp;(")
+d->mHeaderStyleUtil.strToHtml(
message->organization()->asUnicodeString()) + QLatin1Char(')'));
}
headerStr.append(QLatin1String("<br/>\n"));
}
if (strategy->showHeader(QStringLiteral("to"))) {
headerStr.append(i18nc("To-field of the mailheader.", "To: ")
+StringUtil::emailAddrAsAnchor(
message->to(),
StringUtil::DisplayFullAddress)
+ QLatin1String("<br/>\n"));
}
if (strategy->showHeader(QStringLiteral("cc")) && message->cc(false)) {
const QString str = StringUtil::emailAddrAsAnchor(
message->cc(), StringUtil::DisplayFullAddress);
if (!str.isEmpty()) {
headerStr.append(i18n("CC: ") + str + QLatin1String("<br/>\n"));
}
}
if (strategy->showHeader(QStringLiteral("bcc")) && message->bcc(false)) {
const QString str = StringUtil::emailAddrAsAnchor(
message->bcc(), StringUtil::DisplayFullAddress);
if (!str.isEmpty()) {
headerStr.append(i18n("BCC: ") + str + QLatin1String("<br/>\n"));
}
}
if (strategy->showHeader(QStringLiteral("reply-to")) && message->replyTo(false)) {
headerStr.append(i18n("Reply to: ")
+StringUtil::emailAddrAsAnchor(
message->replyTo(),
StringUtil::DisplayFullAddress)
+ QLatin1String("<br/>\n"));
}
headerStr += QLatin1String("</div>\n");
return headerStr;
}
const char *MessageViewer::PlainHeaderStyle::name() const
{
return "plain";
}
diff --git a/messageviewer/src/header/plainheaderstyle.h b/messageviewer/src/header/plainheaderstyle.h
index 50d409cd..bd44b816 100644
--- a/messageviewer/src/header/plainheaderstyle.h
+++ b/messageviewer/src/header/plainheaderstyle.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@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.
*/
#ifndef PLAINHEADERSTYLE_H
#define PLAINHEADERSTYLE_H
#include "messageviewer/headerstyle.h"
#include "messageviewer_export.h"
namespace MessageViewer {
class PlainHeaderStylePrivate;
class MESSAGEVIEWER_EXPORT PlainHeaderStyle : public HeaderStyle
{
public:
PlainHeaderStyle();
~PlainHeaderStyle() override;
public:
const char *name() const override;
Q_REQUIRED_RESULT QString format(KMime::Message *message) const override;
private:
PlainHeaderStylePrivate *const d;
};
}
#endif // PLAINHEADERSTYLE_H
diff --git a/messageviewer/src/header/richheaderstrategy.cpp b/messageviewer/src/header/richheaderstrategy.cpp
index 3e697fd4..07662d12 100644
--- a/messageviewer/src/header/richheaderstrategy.cpp
+++ b/messageviewer/src/header/richheaderstrategy.cpp
@@ -1,53 +1,54 @@
/* -*- c++ -*-
header/headerstrategy.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#include "richheaderstrategy.h"
using namespace MessageViewer;
static const char *const richHeaders[] = {
"subject", "date", "from", "cc", "bcc", "to",
"organization", "organisation", "reply-to",
"user-agent", "x-mailer", "x-bugzilla-url", "disposition-notification-to"
};
static const int numRichHeaders = sizeof richHeaders / sizeof *richHeaders;
RichHeaderStrategy::RichHeaderStrategy()
: HeaderStrategy()
, mHeadersToDisplay(stringList(richHeaders, numRichHeaders))
{
}
RichHeaderStrategy::~RichHeaderStrategy()
{
}
diff --git a/messageviewer/src/header/richheaderstrategy.h b/messageviewer/src/header/richheaderstrategy.h
index 869a2435..8c22dcea 100644
--- a/messageviewer/src/header/richheaderstrategy.h
+++ b/messageviewer/src/header/richheaderstrategy.h
@@ -1,72 +1,73 @@
/* -*- c++ -*-
header/headerstrategy.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#ifndef RICHHEADERSTRATEGY_H
#define RICHHEADERSTRATEGY_H
#include "messageviewer/headerstrategy.h"
#include "messageviewer_export.h"
#include <QStringList>
//
namespace MessageViewer {
//
// RichHeaderStrategy:
// Date, Subject, From, To, CC, ### what exactly?
//
class MESSAGEVIEWER_EXPORT RichHeaderStrategy : public HeaderStrategy
{
public:
RichHeaderStrategy();
~RichHeaderStrategy() override;
public:
Q_REQUIRED_RESULT const char *name() const override
{
return "rich";
}
Q_REQUIRED_RESULT QStringList headersToDisplay() const override
{
return mHeadersToDisplay;
}
Q_REQUIRED_RESULT DefaultPolicy defaultPolicy() const override
{
return Hide;
}
private:
const QStringList mHeadersToDisplay;
};
}
#endif
diff --git a/messageviewer/src/htmlwriter/autotests/webengineembedparttest.cpp b/messageviewer/src/htmlwriter/autotests/webengineembedparttest.cpp
index 0d1b46cc..5ea8a80f 100644
--- a/messageviewer/src/htmlwriter/autotests/webengineembedparttest.cpp
+++ b/messageviewer/src/htmlwriter/autotests/webengineembedparttest.cpp
@@ -1,68 +1,68 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "webengineembedparttest.h"
#include "../webengineembedpart.h"
#include <QTest>
WebEngineEmbedPartTest::WebEngineEmbedPartTest(QObject *parent)
: QObject(parent)
{
}
WebEngineEmbedPartTest::~WebEngineEmbedPartTest()
{
}
void WebEngineEmbedPartTest::shouldHaveDefaultValue()
{
MessageViewer::WebEngineEmbedPart part;
QVERIFY(part.isEmpty());
}
void WebEngineEmbedPartTest::shouldClearValue()
{
MessageViewer::WebEngineEmbedPart part;
part.addEmbedPart(QByteArrayLiteral("foo"), QStringLiteral("bla"));
QVERIFY(!part.isEmpty());
QCOMPARE(part.embeddedPartMap().count(), 1);
part.clear();
QVERIFY(part.isEmpty());
}
void WebEngineEmbedPartTest::shouldAddValues()
{
MessageViewer::WebEngineEmbedPart part;
part.addEmbedPart(QByteArrayLiteral("foo"), QStringLiteral("bla"));
QVERIFY(!part.isEmpty());
QCOMPARE(part.embeddedPartMap().count(), 1);
part.addEmbedPart(QByteArrayLiteral("foo1"), QStringLiteral("bla"));
QCOMPARE(part.embeddedPartMap().count(), 2);
}
void WebEngineEmbedPartTest::shouldAddTwoIdenticalValues()
{
MessageViewer::WebEngineEmbedPart part;
part.addEmbedPart(QByteArrayLiteral("foo"), QStringLiteral("bla"));
QVERIFY(!part.isEmpty());
QCOMPARE(part.embeddedPartMap().count(), 1);
part.addEmbedPart(QByteArrayLiteral("foo"), QStringLiteral("bla"));
QCOMPARE(part.embeddedPartMap().count(), 1);
}
QTEST_MAIN(WebEngineEmbedPartTest)
diff --git a/messageviewer/src/htmlwriter/autotests/webengineembedparttest.h b/messageviewer/src/htmlwriter/autotests/webengineembedparttest.h
index eeba8d44..103653fc 100644
--- a/messageviewer/src/htmlwriter/autotests/webengineembedparttest.h
+++ b/messageviewer/src/htmlwriter/autotests/webengineembedparttest.h
@@ -1,36 +1,36 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef WEBENGINEEMBEDPARTTEST_H
#define WEBENGINEEMBEDPARTTEST_H
#include <QObject>
class WebEngineEmbedPartTest : public QObject
{
Q_OBJECT
public:
explicit WebEngineEmbedPartTest(QObject *parent = nullptr);
~WebEngineEmbedPartTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldClearValue();
void shouldAddValues();
void shouldAddTwoIdenticalValues();
};
#endif // WEBENGINEEMBEDPARTTEST_H
diff --git a/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.cpp b/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.cpp
index 853607c0..abd9745e 100644
--- a/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.cpp
+++ b/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.cpp
@@ -1,96 +1,96 @@
/*
- Copyright (c) 2016-2017 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "webengineparthtmlwritertest.h"
#include "../webengineparthtmlwriter.h"
#include <QTest>
WebEnginePartHtmlWriterTest::WebEnginePartHtmlWriterTest(QObject *parent)
: QObject(parent)
{
}
WebEnginePartHtmlWriterTest::~WebEnginePartHtmlWriterTest()
{
}
void WebEnginePartHtmlWriterTest::removeScriptInHtml_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
QTest::newRow("noscript") << QStringLiteral("<a>boo</a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("onescript") << QStringLiteral("<a>boo<script>alert(1)</script></a>")
<< QStringLiteral("<a>boo</a>");
QTest::newRow("onescriptwithattribute") << QStringLiteral(
"<a>boo<script type=\"foo\">alert(1)</script></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("severalscriptwithattribute") << QStringLiteral(
"<p>foo</p><script>a</script><a>boo<script type=\"foo\">alert(1)</script></a>")
<< QStringLiteral("<p>foo</p><a>boo</a>");
QTest::newRow("scriptwithspace") << QStringLiteral(
"<a>boo<script type=\"foo\" >alert(1)</script ></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("scriptwithremoveaccess") << QStringLiteral(
"<a>boo<script src=\"http://foo\"/></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("empty") << QString() << QString();
//MultiLine
QTest::newRow("multiline") << QStringLiteral("<a>boo<script>\nalert(1)</script></a>")
<< QStringLiteral("<a>boo</a>");
QTest::newRow("multiline-scriptwithspace") << QStringLiteral(
"<a>boo<script type=\"foo\" >\nalert(1)\n</script ></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("multiline-severalscriptwithattribute") << QStringLiteral(
"<p>foo</p><script>\na\n</script><a>boo<script type=\"foo\">\nalert(1)</script></a>")
<< QStringLiteral("<p>foo</p><a>boo</a>");
QTest::newRow("multiline-scriptwithspace") << QStringLiteral(
"<a>boo<script type=\"foo\" >\nalert(1)\nbla\nsl</script ></a>") << QStringLiteral(
"<a>boo</a>");
//Insensitive case
QTest::newRow("onescript-insensitive")
<< QStringLiteral("<a>boo<SCRIPT>alert(1)</script></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("onescriptwithattribute-insensitive") << QStringLiteral(
"<a>boo<SCRIPt type=\"foo\">alert(1)</SCRIPT></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("severalscriptwithattribute-insensitive") << QStringLiteral(
"<p>foo</p><script>a</SCRIPT><a>boo<SCRIPT type=\"foo\">alert(1)</script></a>")
<< QStringLiteral("<p>foo</p><a>boo</a>");
QTest::newRow("scriptwithspace-insensitive") << QStringLiteral(
"<a>boo<SCRIPT type=\"foo\" >alert(1)</SCRIPT ></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("scriptwithremoveaccess-insensitive") << QStringLiteral(
"<a>boo<SCRIPT src=\"http://foo\"/></a>") << QStringLiteral("<a>boo</a>");
//MultiLine insensitive
QTest::newRow("multiline-insensitive")
<< QStringLiteral("<a>boo<sCript>\nalert(1)</Script></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("multiline-scriptwithspace-insensitive") << QStringLiteral(
"<a>boo<SCRipT type=\"foo\" >\nalert(1)\n</script ></a>") << QStringLiteral("<a>boo</a>");
QTest::newRow("multiline-severalscriptwithattribute-insensitive") << QStringLiteral(
"<p>foo</p><SCRIPT>\na\n</script><a>boo<script type=\"foo\">\nalert(1)</script></a>")
<< QStringLiteral(
"<p>foo</p><a>boo</a>");
QTest::newRow("multiline-scriptwithspace-insensitive") << QStringLiteral(
"<a>boo<SCRIPT type=\"foo\" >\nalert(1)\nbla\nsl</script ></a>") << QStringLiteral(
"<a>boo</a>");
}
void WebEnginePartHtmlWriterTest::removeScriptInHtml()
{
QFETCH(QString, input);
QFETCH(QString, output);
QCOMPARE(MessageViewer::WebEnginePartHtmlWriter::removeJscripts(input), output);
}
QTEST_MAIN(WebEnginePartHtmlWriterTest)
diff --git a/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.h b/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.h
index 637876a3..93c5f602 100644
--- a/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.h
+++ b/messageviewer/src/htmlwriter/autotests/webengineparthtmlwritertest.h
@@ -1,34 +1,34 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef WEBENGINEPARTHTMLWRITERTEST_H
#define WEBENGINEPARTHTMLWRITERTEST_H
#include <QObject>
class WebEnginePartHtmlWriterTest : public QObject
{
Q_OBJECT
public:
explicit WebEnginePartHtmlWriterTest(QObject *parent = nullptr);
~WebEnginePartHtmlWriterTest();
private Q_SLOTS:
void removeScriptInHtml_data();
void removeScriptInHtml();
};
#endif // WEBENGINEPARTHTMLWRITERTEST_H
diff --git a/messageviewer/src/htmlwriter/filehtmlwriter.cpp b/messageviewer/src/htmlwriter/filehtmlwriter.cpp
index 5efbeaea..4a3e9289 100644
--- a/messageviewer/src/htmlwriter/filehtmlwriter.cpp
+++ b/messageviewer/src/htmlwriter/filehtmlwriter.cpp
@@ -1,89 +1,90 @@
/* -*- c++ -*-
filehtmlwriter.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#include "filehtmlwriter.h"
#include "messageviewer_debug.h"
using namespace MessageViewer;
FileHtmlWriter::FileHtmlWriter(const QString &filename)
: mFile(filename.isEmpty() ? QStringLiteral("filehtmlwriter.out") : filename)
{
}
FileHtmlWriter::~FileHtmlWriter()
{
if (mFile.isOpen()) {
qCWarning(MESSAGEVIEWER_LOG) << "FileHtmlWriter: file still open!";
HtmlWriter::end();
mFile.close();
}
}
void FileHtmlWriter::begin()
{
if (mFile.isOpen()) {
qCWarning(MESSAGEVIEWER_LOG) << "FileHtmlWriter: file still open!";
mFile.close();
}
if (!mFile.open(QIODevice::WriteOnly)) {
qCWarning(MESSAGEVIEWER_LOG) << "FileHtmlWriter: Cannot open file" << mFile.fileName();
}
HtmlWriter::begin();
}
void FileHtmlWriter::end()
{
HtmlWriter::end();
mFile.close();
}
void FileHtmlWriter::reset()
{
HtmlWriter::reset();
if (mFile.isOpen()) {
mFile.close();
}
}
QIODevice *FileHtmlWriter::device() const
{
return const_cast<QFile *>(&mFile);
}
void FileHtmlWriter::embedPart(const QByteArray &contentId, const QString &url)
{
*stream() << "<!-- embedPart(contentID=" << contentId << ", url=" << url << ") -->" << endl;
}
void FileHtmlWriter::extraHead(const QString &)
{
}
diff --git a/messageviewer/src/htmlwriter/filehtmlwriter.h b/messageviewer/src/htmlwriter/filehtmlwriter.h
index f28fc74f..d0634ab5 100644
--- a/messageviewer/src/htmlwriter/filehtmlwriter.h
+++ b/messageviewer/src/htmlwriter/filehtmlwriter.h
@@ -1,59 +1,60 @@
/* -*- c++ -*-
filehtmlwriter.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_FILEHTMLWRITER_H
#define MESSAGEVIEWER_FILEHTMLWRITER_H
#include "messageviewer_export.h"
#include <MessageViewer/HtmlWriter>
#include <QFile>
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT FileHtmlWriter : public HtmlWriter
{
public:
explicit FileHtmlWriter(const QString &filename);
~FileHtmlWriter() override;
void begin() override;
void end() override;
void reset() override;
Q_REQUIRED_RESULT QIODevice *device() const override;
void embedPart(const QByteArray &contentId, const QString &url) override;
void extraHead(const QString &str) override;
private:
QFile mFile;
};
} // namespace MessageViewer
#endif // MESSAGEVIEWER_FILEHTMLWRITER_H
diff --git a/messageviewer/src/htmlwriter/webengineembedpart.cpp b/messageviewer/src/htmlwriter/webengineembedpart.cpp
index ff7a6ce2..b2d84ee8 100644
--- a/messageviewer/src/htmlwriter/webengineembedpart.cpp
+++ b/messageviewer/src/htmlwriter/webengineembedpart.cpp
@@ -1,60 +1,61 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "webengineembedpart.h"
using namespace MessageViewer;
WebEngineEmbedPart::WebEngineEmbedPart(QObject *parent)
: QObject(parent)
{
}
WebEngineEmbedPart::~WebEngineEmbedPart()
{
}
WebEngineEmbedPart *WebEngineEmbedPart::self()
{
static WebEngineEmbedPart s_self;
return &s_self;
}
QString WebEngineEmbedPart::contentUrl(const QString &contentId) const
{
return mEmbeddedPartMap.value(contentId);
}
void WebEngineEmbedPart::addEmbedPart(const QByteArray &contentId, const QString &contentURL)
{
mEmbeddedPartMap[QLatin1String(contentId)] = contentURL;
}
void WebEngineEmbedPart::clear()
{
mEmbeddedPartMap.clear();
}
bool WebEngineEmbedPart::isEmpty() const
{
return mEmbeddedPartMap.isEmpty();
}
QMap<QString, QString> WebEngineEmbedPart::embeddedPartMap() const
{
return mEmbeddedPartMap;
}
diff --git a/messageviewer/src/htmlwriter/webengineembedpart.h b/messageviewer/src/htmlwriter/webengineembedpart.h
index 1dc9a23c..34c8376b 100644
--- a/messageviewer/src/htmlwriter/webengineembedpart.h
+++ b/messageviewer/src/htmlwriter/webengineembedpart.h
@@ -1,45 +1,46 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef WEBENGINEEMBEDPART_H
#define WEBENGINEEMBEDPART_H
#include <QObject>
#include <QMap>
namespace MessageViewer {
class WebEngineEmbedPart : public QObject
{
Q_OBJECT
public:
explicit WebEngineEmbedPart(QObject *parent = nullptr);
~WebEngineEmbedPart();
void clear();
Q_REQUIRED_RESULT bool isEmpty() const;
Q_REQUIRED_RESULT QMap<QString, QString> embeddedPartMap() const;
void addEmbedPart(const QByteArray &contentId, const QString &contentURL);
Q_REQUIRED_RESULT QString contentUrl(const QString &contentId) const;
static WebEngineEmbedPart *self();
private:
// Key is Content-Id, value is URL
QMap<QString, QString> mEmbeddedPartMap;
};
}
#endif // WEBENGINEEMBEDPART_H
diff --git a/messageviewer/src/htmlwriter/webengineparthtmlwriter.cpp b/messageviewer/src/htmlwriter/webengineparthtmlwriter.cpp
index ec1602c3..6bbd7b1d 100644
--- a/messageviewer/src/htmlwriter/webengineparthtmlwriter.cpp
+++ b/messageviewer/src/htmlwriter/webengineparthtmlwriter.cpp
@@ -1,102 +1,103 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "webengineparthtmlwriter.h"
#include "webengineembedpart.h"
#include "messageviewer_debug.h"
#include "viewer/webengine/mailwebengineview.h"
#include <QUrl>
#include <cassert>
#include <QByteArray>
using namespace MessageViewer;
WebEnginePartHtmlWriter::WebEnginePartHtmlWriter(MailWebEngineView *view, QObject *parent)
: QObject(parent)
, mHtmlView(view)
, mState(Ended)
{
assert(view);
}
WebEnginePartHtmlWriter::~WebEnginePartHtmlWriter()
{
}
void WebEnginePartHtmlWriter::begin()
{
if (mState != Ended) {
qCWarning(MESSAGEVIEWER_LOG) << "begin() called on non-ended session!";
reset();
}
BufferedHtmlWriter::begin();
MessageViewer::WebEngineEmbedPart::self()->clear();
mState = Begun;
}
void WebEnginePartHtmlWriter::end()
{
BufferedHtmlWriter::end();
if (mState != Begun) {
qCWarning(MESSAGEVIEWER_LOG) << "Called on non-begun or queued session!";
}
if (!mExtraHead.isEmpty()) {
insertExtraHead();
mExtraHead.clear();
}
// see QWebEnginePage::setHtml()
mHtmlView->setContent(data(), QStringLiteral("text/html;charset=UTF-8"), QUrl(QStringLiteral("file:///")));
mHtmlView->show();
clear();
mHtmlView->setUpdatesEnabled(true);
mHtmlView->update();
mState = Ended;
Q_EMIT finished();
}
void WebEnginePartHtmlWriter::reset()
{
BufferedHtmlWriter::reset();
if (mState != Ended) {
mState = Begun; // don't run into end()'s warning
end();
mState = Ended;
}
}
void WebEnginePartHtmlWriter::embedPart(const QByteArray &contentId, const QString &contentURL)
{
MessageViewer::WebEngineEmbedPart::self()->addEmbedPart(contentId, contentURL);
}
void WebEnginePartHtmlWriter::insertExtraHead()
{
const auto headTag(QByteArrayLiteral("<head>"));
const int index = m_data.indexOf(headTag);
if (index != -1) {
m_data.insert(index + headTag.length(), mExtraHead.toUtf8());
}
}
void WebEnginePartHtmlWriter::extraHead(const QString &str)
{
mExtraHead = str;
}
diff --git a/messageviewer/src/htmlwriter/webengineparthtmlwriter.h b/messageviewer/src/htmlwriter/webengineparthtmlwriter.h
index f665e1dc..d07d43ab 100644
--- a/messageviewer/src/htmlwriter/webengineparthtmlwriter.h
+++ b/messageviewer/src/htmlwriter/webengineparthtmlwriter.h
@@ -1,60 +1,61 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef WEBENGINEPARTHTMLWRITER_H
#define WEBENGINEPARTHTMLWRITER_H
#include "bufferedhtmlwriter.h"
#include <QObject>
#include <QString>
#include <QByteArray>
namespace MessageViewer {
class MailWebEngineView;
}
namespace MessageViewer {
class WebEnginePartHtmlWriter : public QObject, public BufferedHtmlWriter
{
Q_OBJECT
public:
explicit WebEnginePartHtmlWriter(MailWebEngineView *view, QObject *parent = nullptr);
~WebEnginePartHtmlWriter() override;
void begin() override;
void end() override;
void reset() override;
void embedPart(const QByteArray &contentId, const QString &url) override;
void extraHead(const QString &str) override;
Q_SIGNALS:
void finished();
private:
void insertExtraHead();
private:
MailWebEngineView *mHtmlView = nullptr;
QString mExtraHead;
enum State {
Begun,
Queued,
Ended
} mState;
};
}
#endif // WEBENGINEPARTHTMLWRITER_H
diff --git a/messageviewer/src/interfaces/urlhandler.h b/messageviewer/src/interfaces/urlhandler.h
index 4aecda1a..b4c9de71 100644
--- a/messageviewer/src/interfaces/urlhandler.h
+++ b/messageviewer/src/interfaces/urlhandler.h
@@ -1,119 +1,120 @@
/* -*- c++ -*-
interfaces/urlhandler.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef KMAIL_INTERFACES_URLHANDLER_H
#define KMAIL_INTERFACES_URLHANDLER_H
#include <QUrl>
class QString;
class QPoint;
namespace MessasgeViewer {
class ViewerPrivate;
}
namespace MimeTreeParser {
/**
* @short An interface to reader link handlers
* @author Marc Mutz <mutz@kde.org>
*
*/
class URLHandler
{
public:
virtual ~URLHandler()
{
}
/**
* Called when LMB-clicking on a link in the reader. Should start
* processing equivalent to "opening" the link.
*
* @return true if the click was handled by this URLHandler,
* false otherwise.
*/
virtual bool handleClick(const QUrl &url, MessageViewer::ViewerPrivate *w) const = 0;
/**
* Called when RMB-clicking on a link in the reader. Should show
* a context menu at the specified point with the specified
* widget as parent.
*
* @return true if the right-click was handled by this
* URLHandler, false otherwise.
*/
virtual bool handleContextMenuRequest(const QUrl &url, const QPoint &p, MessageViewer::ViewerPrivate *w) const = 0;
/**
* Called when hovering over a link.
*
- * @return a string to be shown in the status bar while hoverin
+ * @return a string to be shown in the status bar while hovering
* over this link.
*/
virtual QString statusBarMessage(const QUrl &url, MessageViewer::ViewerPrivate *w) const = 0;
/**
* Called when shift-clicking the link in the reader.
* @return true if the click was handled by this URLHandler, false otherwise
*/
virtual bool handleShiftClick(const QUrl &url, MessageViewer::ViewerPrivate *window) const
{
Q_UNUSED(url);
Q_UNUSED(window);
return false;
}
/**
* @return should return true if this URLHandler will handle the drag
*/
virtual bool willHandleDrag(const QUrl &url, MessageViewer::ViewerPrivate *window) const
{
Q_UNUSED(url);
Q_UNUSED(window);
return false;
}
/**
* Called when starting a drag with the given URL.
* If the drag is handled, you should create a drag object.
* @return true if the click was handled by this URLHandler, false otherwise
*/
virtual bool handleDrag(const QUrl &url, MessageViewer::ViewerPrivate *window) const
{
Q_UNUSED(url);
Q_UNUSED(window);
return false;
}
};
}
#endif // KMAIL_INTERFACES_URLHANDLER_H
diff --git a/messageviewer/src/job/modifymessagedisplayformatjob.cpp b/messageviewer/src/job/modifymessagedisplayformatjob.cpp
index 2172cdee..a8396144 100644
--- a/messageviewer/src/job/modifymessagedisplayformatjob.cpp
+++ b/messageviewer/src/job/modifymessagedisplayformatjob.cpp
@@ -1,101 +1,101 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "modifymessagedisplayformatjob.h"
#include "messageviewer_debug.h"
#include <AkonadiCore/ItemModifyJob>
#include <AkonadiCore/Session>
#include "viewer/messagedisplayformatattribute.h"
using namespace MessageViewer;
ModifyMessageDisplayFormatJob::ModifyMessageDisplayFormatJob(Akonadi::Session *session, QObject *parent)
: QObject(parent)
, mSession(session)
{
}
ModifyMessageDisplayFormatJob::~ModifyMessageDisplayFormatJob()
{
}
void ModifyMessageDisplayFormatJob::setRemoteContent(bool remote)
{
mRemoteContent = remote;
}
void ModifyMessageDisplayFormatJob::setMessageFormat(Viewer::DisplayFormatMessage format)
{
mMessageFormat = format;
}
void ModifyMessageDisplayFormatJob::setResetFormat(bool resetFormat)
{
mResetFormat = resetFormat;
}
void ModifyMessageDisplayFormatJob::start()
{
if (mMessageItem.isValid()) {
if (mResetFormat) {
resetDisplayFormat();
} else {
modifyDisplayFormat();
}
} else {
qCDebug(MESSAGEVIEWER_LOG) << " messageItem doesn't exist";
deleteLater();
}
}
void ModifyMessageDisplayFormatJob::setMessageItem(const Akonadi::Item &messageItem)
{
mMessageItem = messageItem;
}
void ModifyMessageDisplayFormatJob::resetDisplayFormat()
{
mMessageItem.removeAttribute<MessageViewer::MessageDisplayFormatAttribute>();
Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession);
modify->setIgnorePayload(true);
modify->disableRevisionCheck();
connect(modify, &KJob::result, this, &ModifyMessageDisplayFormatJob::slotModifyItemDone);
}
void ModifyMessageDisplayFormatJob::modifyDisplayFormat()
{
MessageViewer::MessageDisplayFormatAttribute *attr
= mMessageItem.attribute<MessageViewer::MessageDisplayFormatAttribute>(
Akonadi::Item::AddIfMissing);
attr->setRemoteContent(mRemoteContent);
attr->setMessageFormat(mMessageFormat);
Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession);
modify->setIgnorePayload(true);
modify->disableRevisionCheck();
connect(modify, &KJob::result, this, &ModifyMessageDisplayFormatJob::slotModifyItemDone);
}
void ModifyMessageDisplayFormatJob::slotModifyItemDone(KJob *job)
{
if (job && job->error()) {
qCWarning(MESSAGEVIEWER_LOG) << " Error trying to change attribute:" << job->errorText();
}
deleteLater();
}
diff --git a/messageviewer/src/job/modifymessagedisplayformatjob.h b/messageviewer/src/job/modifymessagedisplayformatjob.h
index 45b06791..1a61b89c 100644
--- a/messageviewer/src/job/modifymessagedisplayformatjob.h
+++ b/messageviewer/src/job/modifymessagedisplayformatjob.h
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef MODIFYMESSAGEDISPLAYFORMATJOB_H
#define MODIFYMESSAGEDISPLAYFORMATJOB_H
#include <QObject>
#include "viewer/viewer_p.h"
namespace Akonadi {
class Session;
}
namespace MessageViewer {
class ModifyMessageDisplayFormatJob : public QObject
{
Q_OBJECT
public:
explicit ModifyMessageDisplayFormatJob(Akonadi::Session *session, QObject *parent = nullptr);
~ModifyMessageDisplayFormatJob();
void setRemoteContent(bool remote);
void setMessageFormat(Viewer::DisplayFormatMessage format);
void setResetFormat(bool resetFormat);
void start();
void setMessageItem(const Akonadi::Item &messageItem);
private:
void slotModifyItemDone(KJob *job);
void resetDisplayFormat();
void modifyDisplayFormat();
Akonadi::Session *mSession = nullptr;
Akonadi::Item mMessageItem;
Viewer::DisplayFormatMessage mMessageFormat = Viewer::UseGlobalSetting;
bool mRemoteContent = false;
bool mResetFormat = false;
};
}
#endif // MODIFYMESSAGEDISPLAYFORMATJOB_H
diff --git a/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.cpp b/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.cpp
index ddc20427..8f81172a 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.cpp
@@ -1,87 +1,87 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#include "converthtmltoplaintexttest.h"
#include "../converthtmltoplaintext.h"
#include <QTest>
ConvertHtmlToPlainTextTest::ConvertHtmlToPlainTextTest(QObject *parent)
: QObject(parent)
{
}
ConvertHtmlToPlainTextTest::~ConvertHtmlToPlainTextTest()
{
}
void ConvertHtmlToPlainTextTest::shouldHaveDefaultValue()
{
MimeTreeParser::ConvertHtmlToPlainText convert;
QVERIFY(convert.htmlString().isEmpty());
}
void ConvertHtmlToPlainTextTest::shouldReturnEmptyStringIfInputTextIsEmpty()
{
MimeTreeParser::ConvertHtmlToPlainText convert;
convert.setHtmlString(QString());
QVERIFY(convert.generatePlainText().isEmpty());
}
void ConvertHtmlToPlainTextTest::shouldReturnNotEmptyStringIfInputTextIsNotEmpty()
{
MimeTreeParser::ConvertHtmlToPlainText convert;
const QString str = QStringLiteral("foo bla");
convert.setHtmlString(str);
const QString result = convert.generatePlainText();
QVERIFY(!result.isEmpty());
QCOMPARE(result, QString(str + QLatin1String("\n")));
}
void ConvertHtmlToPlainTextTest::shouldConvertToPlainText_data()
{
QTest::addColumn<QString>("inputText");
QTest::addColumn<QString>("convertedText");
QTest::newRow("plainText") << "foo" << "foo\n";
QTest::newRow("htmlText") << "<html><body>Hi! This is a KDE test</body></html>"
<< "Hi! This is a KDE test\n";
QTest::newRow("htmlTextWithBold")
<< "<html><body><b>Hi!</b> This is a KDE test</body></html>"
<< "*Hi!* This is a KDE test\n";
QTest::newRow("htmlTextWithH1")
<< "<html><body><h1>Hi!</h1> This is a KDE test</body></html>"
<< "*Hi!*\nThis is a KDE test\n";
QTest::newRow("htmlTextWithUnderLine")
<< "<html><body><u>Hi!</u> This is a KDE test</body></html>"
<< "_Hi!_ This is a KDE test\n";
}
void ConvertHtmlToPlainTextTest::shouldConvertToPlainText()
{
QFETCH(QString, inputText);
QFETCH(QString, convertedText);
MimeTreeParser::ConvertHtmlToPlainText convert;
convert.setHtmlString(inputText);
const QString result = convert.generatePlainText();
QVERIFY(!result.isEmpty());
QCOMPARE(result, convertedText);
}
QTEST_APPLESS_MAIN(ConvertHtmlToPlainTextTest)
diff --git a/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.h b/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.h
index 6bd767cc..6d6f4c30 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/converthtmltoplaintexttest.h
@@ -1,41 +1,41 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#ifndef CONVERTHTMLTOPLAINTEXTTEST_H
#define CONVERTHTMLTOPLAINTEXTTEST_H
#include <QObject>
class ConvertHtmlToPlainTextTest : public QObject
{
Q_OBJECT
public:
explicit ConvertHtmlToPlainTextTest(QObject *parent = nullptr);
~ConvertHtmlToPlainTextTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldReturnEmptyStringIfInputTextIsEmpty();
void shouldReturnNotEmptyStringIfInputTextIsNotEmpty();
void shouldConvertToPlainText_data();
void shouldConvertToPlainText();
};
#endif // CONVERTHTMLTOPLAINTEXTTEST_H
diff --git a/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.cpp b/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.cpp
index 7b414e84..4be3b83c 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.cpp
@@ -1,386 +1,385 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "objecttreeparsertest.h"
#include "util.h"
#include "setupenv.h"
#include <MimeTreeParser/ObjectTreeParser>
#include <MessageViewer/BufferedHtmlWriter>
-#include <MessageViewer/CSSHelperBase>
#include <QTest>
using namespace MessageViewer;
using namespace MimeTreeParser;
QTEST_MAIN(ObjectTreeParserTester)
void ObjectTreeParserTester::initTestCase()
{
Test::setupEnv();
}
void ObjectTreeParserTester::test_parsePlainMessage()
{
KMime::Message::Ptr msg(new KMime::Message());
QByteArray content(
"From: Thomas McGuire <dontspamme@gmx.net>\n"
"Subject: Plain Message Test\n"
"Date: Wed, 5 Aug 2009 10:58:27 +0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain;\n"
" charset=\"iso-8859-15\"\n"
"\n"
"This is the message text.\n");
msg->setContent(content);
msg->parse();
QCOMPARE(msg->subject()->as7BitString(false).constData(), "Plain Message Test");
QCOMPARE(msg->contents().size(), 0);
// Parse the message
Test::ObjectTreeSource emptySource(nullptr, nullptr);
ObjectTreeParser otp(&emptySource);
otp.parseObjectTree(msg.data());
// Check that the textual content and the charset have the expected values
QCOMPARE(otp.plainTextContent(), QStringLiteral("This is the message text.\n"));
QVERIFY(otp.htmlContent().isEmpty());
QCOMPARE(otp.plainTextContentCharset().toLower(), QByteArray("iso-8859-15"));
// Check that the message was not modified in any way
QCOMPARE(msg->encodedContent().constData(), content.constData());
// Test that the charset of messages without an explicit charset declaration
// is correct
content
= "From: Thomas McGuire <dontspamme@gmx.net>\n"
"Subject: Plain Message Test\n"
"Date: Wed, 5 Aug 2009 10:58:27 +0200\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain;\n"
"\n"
"This is the message text.\n";
msg->setContent(content);
msg->parse();
ObjectTreeParser otp2(&emptySource);
otp2.parseObjectTree(msg.data());
QCOMPARE(otp2.plainTextContentCharset().constData(), msg->defaultCharset().constData());
}
void ObjectTreeParserTester::test_parseEncapsulatedMessage()
{
KMime::Message::Ptr msg
= Test::readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "Fwd: Test with attachment");
QCOMPARE(msg->contents().size(), 2);
// Parse the message
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
NodeHelper nodeHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource, &nodeHelper);
otp.parseObjectTree(msg.data());
testWriter.end();
// Check that the OTP didn't modify the message in weird ways
QCOMPARE(msg->contents().size(), 2);
QCOMPARE(msg->contents().at(0)->contents().size(), 0);
QCOMPARE(msg->contents().at(1)->contents().size(), 1);
QCOMPARE(msg->contents().at(1)->contents().first()->contents().size(), 2);
QCOMPARE(msg->contents().at(1)->contents().first()->contents().at(0)->contents().size(), 0);
QCOMPARE(msg->contents().at(1)->contents().first()->contents().at(1)->contents().size(), 0);
// Check that the textual content and the charset have the expected values
QCOMPARE(otp.plainTextContent(), QStringLiteral("This is the first encapsulating message.\n"));
QCOMPARE(otp.plainTextContentCharset().toLower(), QByteArray("iso-8859-15"));
QVERIFY(otp.htmlContent().isEmpty());
// Check that the objecttreeparser did process the encapsulated message
KMime::Message::Ptr encapsulated = msg->contents().at(1)->bodyAsMessage();
QVERIFY(encapsulated.data());
QVERIFY(nodeHelper.nodeProcessed(encapsulated.data()));
QVERIFY(nodeHelper.nodeProcessed(encapsulated->contents().at(0)));
QVERIFY(nodeHelper.nodeProcessed(encapsulated->contents().at(1)));
QVERIFY(nodeHelper.partMetaData(msg->contents().at(1)).isEncapsulatedRfc822Message);
}
void ObjectTreeParserTester::test_missingContentTypeHeader()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("no-content-type.mbox"));
QCOMPARE(msg->subject()->as7BitString(
false).constData(), "Simple Mail Without Content-Type Header");
QCOMPARE(msg->contents().size(), 0);
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
NodeHelper nodeHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource, &nodeHelper);
otp.parseObjectTree(msg.data());
testWriter.end();
QCOMPARE(otp.plainTextContent().toLatin1().data(), "asdfasdf");
QVERIFY(otp.htmlContent().isEmpty());
}
void ObjectTreeParserTester::test_inlinePGPDecryption()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("inlinepgpencrypted.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "inlinepgpencrypted");
QCOMPARE(msg->contents().size(), 0);
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
NodeHelper nodeHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource, &nodeHelper);
emptySource.setAllowDecryption(true);
otp.parseObjectTree(msg.data());
testWriter.end();
QCOMPARE(otp.plainTextContent().toLatin1().data(), "some random text");
- // This test is only a workaround, till we can set the memento to the propper node of the mail.
+ // This test is only a workaround, till we can set the memento to the proper node of the mail.
KMime::Content *content = new KMime::Content;
QVERIFY(nodeHelper.bodyPartMemento(content, "decryptverify"));
QVERIFY(otp.htmlContent().isEmpty());
}
void ObjectTreeParserTester::test_inlinePGPSigned()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("openpgp-inline-signed.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "test");
QCOMPARE(msg->contents().size(), 0);
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
NodeHelper nodeHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource, &nodeHelper);
emptySource.setAllowDecryption(true);
otp.parseObjectTree(msg.data());
testWriter.end();
- // This test is only a workaround, till we can set the memento to the propper node of the mail.
+ // This test is only a workaround, till we can set the memento to the proper node of the mail.
QVERIFY(nodeHelper.bodyPartMemento(nullptr, "verification"));
}
void ObjectTreeParserTester::test_HTML()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("html.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "HTML test");
QCOMPARE(msg->contents().size(), 2);
Test::ObjectTreeSource emptySource(nullptr, nullptr);
ObjectTreeParser otp(&emptySource);
otp.parseObjectTree(msg.data());
QCOMPARE(otp.plainTextContent().toLatin1().data(), "Some HTML text");
QVERIFY(otp.htmlContent().contains(QLatin1String(
"Some <span style=\" font-weight:600;\">HTML</span> text")));
QCOMPARE(otp.htmlContentCharset().data(), "windows-1252");
}
void ObjectTreeParserTester::test_HTMLasText()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("html.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "HTML test");
QCOMPARE(msg->contents().size(), 2);
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartPlain);
otp.parseObjectTree(msg.data());
testWriter.end();
QCOMPARE(otp.htmlContent().toLatin1().constData(), "");
QCOMPARE(otp.htmlContentCharset().constData(), "");
QCOMPARE(otp.plainTextContent().toLatin1().constData(), "Some HTML text");
QCOMPARE(otp.plainTextContentCharset().constData(), "windows-1252");
}
void ObjectTreeParserTester::test_HTMLOnly()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("htmlonly.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "HTML test");
QCOMPARE(msg->contents().size(), 0);
Test::ObjectTreeSource emptySource(nullptr, nullptr);
ObjectTreeParser otp(&emptySource);
otp.parseObjectTree(msg.data());
QVERIFY(otp.plainTextContent().isEmpty());
QVERIFY(otp.htmlContent().contains(QLatin1String("<b>SOME</b> HTML text.")));
}
void ObjectTreeParserTester::test_HTMLOnlyText()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("htmlonly.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "HTML test");
QCOMPARE(msg->contents().size(), 0);
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartPlain);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.plainTextContent().isEmpty());
QVERIFY(otp.htmlContent().contains(QLatin1String("<b>SOME</b> HTML text.")));
QVERIFY(testWriter.data().contains("This is an HTML message. For security reasons, only the raw HTML code is shown."));
QVERIFY(testWriter.data().contains("SOME* HTML text. <br>"));
}
void ObjectTreeParserTester::test_HTMLExternal()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("htmlonlyexternal.mbox"));
QCOMPARE(msg->subject()->as7BitString(false).constData(), "HTML test");
QCOMPARE(msg->contents().size(), 0);
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.plainTextContent().isEmpty());
QVERIFY(otp.htmlContent().contains(QLatin1String("<b>SOME</b> HTML text.")));
QVERIFY(testWriter.data().contains("<b>SOME</b> HTML text."));
QVERIFY(testWriter.data().contains("This HTML message may contain external references to images etc. For security/privacy reasons external references are not loaded."));
}
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setHtmlLoadExternal(true);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.htmlContent().contains(QLatin1String("<b>SOME</b> HTML text.")));
QVERIFY(testWriter.data().contains("<b>SOME</b> HTML text."));
QVERIFY(!testWriter.data().contains("This HTML message may contain external references to images etc. For security/privacy reasons external references are not loaded."));
}
}
void ObjectTreeParserTester::test_Alternative()
{
KMime::Message::Ptr msg = Test::readAndParseMail(QStringLiteral("alternative.mbox"));
QCOMPARE(msg->contents().size(), 2);
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartPlain);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.htmlContent().isEmpty());
QVERIFY(otp.plainTextContent().contains(QLatin1String(
"If you can see this text it means that your email client couldn't display our newsletter properly.")));
QVERIFY(testWriter.data().contains("If you can see this text it means that your email client couldn't display our newsletter properly."));
}
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartHtml);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.plainTextContent().contains(QLatin1String(
"If you can see this text it means that your email client couldn't display our newsletter properly.")));
QVERIFY(otp.htmlContent().contains(QLatin1String(
"Some <span style=\" font-weight:600;\">HTML</span> text</p>")));
QVERIFY(testWriter.data().contains("Some <span style=\" font-weight:600;\">HTML</span> text</p>"));
}
msg = Test::readAndParseMail(QStringLiteral("alternative-notext.mbox"));
QCOMPARE(msg->contents().size(), 1);
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartPlain);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.plainTextContent().isEmpty());
QVERIFY(otp.htmlContent().isEmpty());
QVERIFY(testWriter.data().contains("Some *HTML* text"));
}
{
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
ObjectTreeParser otp(&emptySource);
emptySource.setPreferredMode(MimeTreeParser::Util::MultipartHtml);
otp.parseObjectTree(msg.data());
testWriter.end();
QVERIFY(otp.plainTextContent().isEmpty());
QVERIFY(otp.htmlContent().contains(QLatin1String(
"Some <span style=\" font-weight:600;\">HTML</span> text</p>")));
QVERIFY(testWriter.data().contains("Some <span style=\" font-weight:600;\">HTML</span> text</p>"));
}
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.h b/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.h
index 8d32baea..bda2cfa8 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/objecttreeparsertest.h
@@ -1,45 +1,45 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef OBJECTTREEPARSERTEST_H
#define OBJECTTREEPARSERTEST_H
#include <QObject>
class ObjectTreeParserTester : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
private Q_SLOTS:
void test_parseEncapsulatedMessage();
void test_parsePlainMessage();
void test_missingContentTypeHeader();
void test_inlinePGPDecryption();
void test_inlinePGPSigned();
void test_HTML();
void test_HTMLasText();
void test_HTMLOnly();
void test_HTMLOnlyText();
void test_HTMLExternal();
void test_Alternative();
};
#endif
diff --git a/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.cpp b/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.cpp
index 4332cec3..e84c27ce 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.cpp
@@ -1,236 +1,236 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "quotehtmltest.h"
#include "../plugins/quotehtml.h"
#include "util.h"
#include "setupenv.h"
#include <MimeTreeParser/ObjectTreeParser>
#include <MessageViewer/BufferedHtmlWriter>
#include <MessageViewer/MessagePartRendererBase>
#include <MessageViewer/CSSHelperBase>
#include <MimeTreeParser/MessagePart>
#include <MessageViewer/IconNameCache>
#include <QTest>
using namespace MessageViewer;
-QTEST_GUILESS_MAIN(QuoteHtmlTest)
+QTEST_MAIN(QuoteHtmlTest)
class MyRenderContext : public MessageViewer::RenderContext
{
public:
~MyRenderContext() override
{
}
CSSHelperBase *cssHelper() const override
{
return mCssHelper;
}
void renderSubParts(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter) override
{
Q_UNUSED(msgPart);
Q_UNUSED(htmlWriter);
}
bool isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart) override
{
Q_UNUSED(msgPart);
return false;
}
MimeTreeParser::IconType displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart) override
{
Q_UNUSED(msgPart);
return MimeTreeParser::IconType::NoIcon;
}
bool showEmoticons() const override
{
return false;
}
bool isPrinting() const override
{
return false;
}
bool htmlLoadExternal() const override
{
return false;
}
bool showExpandQuotesMark() const override
{
return mShowExpandQuotesMark;
}
bool showOnlyOneMimePart() const override
{
return false;
}
bool showSignatureDetails() const override
{
return false;
}
bool showEncryptionDetails() const override
{
- return false;
+ return false;
}
int levelQuote() const override
{
return mLevelQuote;
}
bool mShowExpandQuotesMark = false;
int mLevelQuote = 1;
CSSHelperBase *mCssHelper = nullptr;
protected:
bool renderWithFactory(const QMetaObject *mo, const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer) override
{
Q_UNUSED(mo);
Q_UNUSED(msgPart);
Q_UNUSED(writer);
return false;
}
};
void QuoteHtmlTest::initTestCase()
{
MessageViewer::Test::setupEnv();
mCollapseIcon
= MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral(
"quotecollapse.png"));
mExpandIcon
= MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral(
"quoteexpand.png"));
}
void QuoteHtmlTest::testQuoteHtml_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<QString>("result");
QTest::addColumn<bool>("showExpandQuotesMark");
QTest::addColumn<int>("quotelevel");
//No Expand Quotes
QTest::newRow("simpletext") << QStringLiteral("http") << QStringLiteral(
"<div class=\"noquote\"><div dir=\"ltr\">http</div></div>") << false << 1;
QTest::newRow("simplequote") << QStringLiteral(">") << QStringLiteral(
"<blockquote><div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarksemptyline\">></span></div></div></blockquote>")
<< false << 1;
QTest::newRow("doublequotewithtext") << QStringLiteral(">> sddf") << QStringLiteral(
"<blockquote><blockquote><div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>> </span><font color=\"#007000\">sddf</font></div></div></blockquote></blockquote>")
<< false << 1;
QTest::newRow("doublequote") << QStringLiteral(">>") << QStringLiteral(
"<blockquote><blockquote><div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarksemptyline\">>></span></div></div></blockquote></blockquote>")
<< false << 1;
QTest::newRow("simplespace") << QStringLiteral(" ") << QStringLiteral(
"<div class=\"noquote\"><div dir=\"ltr\">&nbsp;</div></div>") << false << 1;
QTest::newRow("multispace") << QStringLiteral(" Bug ID: 358324") << QStringLiteral(
"<div class=\"noquote\"><div dir=\"ltr\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Bug ID: 358324</div></div>")
<< false << 1;
QTest::newRow("bug-369072") << QStringLiteral(
"test\n>quote1\n>>quote2\n>>>quote3\n>>new quote2\n>new quote1\nnew text")
<<QStringLiteral("<div class=\"noquote\"><div dir=\"ltr\">test</div>"
"</div><blockquote><div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">quote1</font></div>"
"</div><blockquote><div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">quote2</font></div>"
"</div><blockquote><div class=\"quotelevel3\"><div dir=\"ltr\"><span class=\"quotemarks\">>>></span><font color=\"#006000\">quote3</font></div>"
"</div></blockquote><div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">new quote2</font></div>"
"</div></blockquote><div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">new quote1</font></div>"
"</div></blockquote><div class=\"noquote\"><div dir=\"ltr\">new text</div></div>")
<< false << 1;
//Show Expand Quotes
QTest::newRow("simpletext-expand") << QStringLiteral("http") << QStringLiteral(
"<div class=\"noquote\"><div dir=\"ltr\">http</div></div>") << true << 1;
QString result = QStringLiteral(
"<blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div><div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarksemptyline\">></span></div></div></blockquote>")
.arg(mCollapseIcon);
QTest::newRow("simplequote-expand") << QStringLiteral(">") << result << true << 1;
QTest::newRow("simplespace-expand") << QStringLiteral(" ") << QStringLiteral(
"<div class=\"noquote\"><div dir=\"ltr\">&nbsp;</div></div>") << true << 1;
QTest::newRow("bug-369072-expand-quotelevel3") << QStringLiteral(
"test\n>quote1\n>>quote2\n>>>quote3\n>>new quote2\n>new quote1\nnew text")
<<QStringLiteral("<div class=\"noquote\"><div dir=\"ltr\">test</div>"
"</div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">quote1</font></div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?1 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">quote2</font></div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?2 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel3\"><div dir=\"ltr\"><span class=\"quotemarks\">>>></span><font color=\"#006000\">quote3</font></div></div></blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?1 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">new quote2</font></div></div></blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">new quote1</font></div></div></blockquote><div class=\"noquote\"><div dir=\"ltr\">new text</div></div>")
.arg(mCollapseIcon)
<< true << 3;
QTest::newRow("bug-369072-expand-quotelevel2") << QStringLiteral(
"test\n>quote1\n>>quote2\n>>>quote3\n>>new quote2\n>new quote1\nnew text")
<<QStringLiteral("<div class=\"noquote\"><div dir=\"ltr\">test</div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">quote1</font></div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?1 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">quote2</font></div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?-1 \"><img src=\"%2\"/></a></div><br/></blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?1 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel2\"><div dir=\"ltr\"><span class=\"quotemarks\">>></span><font color=\"#007000\">new quote2</font></div></div></blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">new quote1</font></div></div></blockquote><div class=\"noquote\"><div dir=\"ltr\">new text</div></div>")
.arg(mCollapseIcon).arg(mExpandIcon)
<< true << 2;
QTest::newRow("bug-369072-expand-quotelevel1") << QStringLiteral(
"test\n>quote1\n>>quote2\n>>>quote3\n>>new quote2\n>new quote1\nnew text")
<<QStringLiteral("<div class=\"noquote\"><div dir=\"ltr\">test</div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">quote1</font></div></div><blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?-1 \"><img src=\"%2\"/></a></div><br/>"
"<blockquote></blockquote></blockquote><div class=\"quotelevelmark\" ><a href=\"kmail:levelquote?0 \"><img src=\"%1\"/></a></div>"
"<div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">></span><font color=\"#008000\">new quote1</font></div></div></blockquote><div class=\"noquote\"><div dir=\"ltr\">new text</div></div>")
.arg(mCollapseIcon).arg(mExpandIcon)
<< true << 1;
QTest::newRow("bug-370452") << QStringLiteral("test\n> blo\n>\n>\n>\n> bla\nnew text")
<<QStringLiteral("<div class=\"noquote\">"
"<div dir=\"ltr\">test</div></div>"
"<blockquote><div class=\"quotelevel1\"><div dir=\"ltr\"><span class=\"quotemarks\">> </span><font color=\"#008000\">blo</font></div>"
"<div dir=\"ltr\"><span class=\"quotemarksemptyline\">></span></div>"
"<div dir=\"ltr\"><span class=\"quotemarksemptyline\">></span></div>"
"<div dir=\"ltr\"><span class=\"quotemarksemptyline\">></span></div>"
"<div dir=\"ltr\"><span class=\"quotemarks\">> </span><font color=\"#008000\">bla</font></div></div></blockquote>"
"<div class=\"noquote\"><div dir=\"ltr\">new text</div></div>") << false << 1;
}
void QuoteHtmlTest::testQuoteHtml()
{
QFETCH(QString, data);
QFETCH(QString, result);
QFETCH(bool, showExpandQuotesMark);
QFETCH(int, quotelevel);
BufferedHtmlWriter testWriter;
Test::CSSHelper testCSSHelper;
MyRenderContext context;
context.mCssHelper = &testCSSHelper;
context.mLevelQuote = quotelevel;
context.mShowExpandQuotesMark = showExpandQuotesMark;
testWriter.begin();
quotedHTML(data, &context, &testWriter);
testWriter.end();
QCOMPARE(testWriter.data(), result.toUtf8());
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.h b/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.h
index 984fb209..0274550b 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/quotehtmltest.h
@@ -1,39 +1,39 @@
/* Copyright 2016 Sandro Knauß <sknauss@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef QUOTEHTMLTEST_H
#define QUOTEHTMLTEST_H
#include <QObject>
class QuoteHtmlTest : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
private Q_SLOTS:
void testQuoteHtml();
void testQuoteHtml_data();
private:
QString mCollapseIcon;
QString mExpandIcon;
};
#endif
diff --git a/messageviewer/src/messagepartthemes/default/autotests/rendertest.cpp b/messageviewer/src/messagepartthemes/default/autotests/rendertest.cpp
index 06f3a903..33c8ae2d 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/rendertest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/rendertest.cpp
@@ -1,358 +1,358 @@
/*
Copyright (c) 2010 Volker Krause <vkrause@kde.org>
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.
*/
#include "rendertest.h"
#include "setupenv.h"
#include "testcsshelper.h"
#include "util.h"
#include <MessageViewer/FileHtmlWriter>
#include <MimeTreeParser/ObjectTreeParser>
#include <MimeTreeParser/MessagePart>
#include <KMime/Message>
#include <QDir>
#include <QProcess>
#include <QTest>
using namespace MessageViewer;
void RenderTest::initTestCase()
{
Test::setupEnv();
}
void RenderTest::testRenderSmart_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html"))) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << file << QString(dir.path() + QLatin1Char(
'/') + file
+ QStringLiteral(".html"))
<< QString(file + QStringLiteral(".out"))
<< QStringLiteral("smart") << false << QString();
}
}
void RenderTest::testRenderSmart()
{
testRender();
}
void RenderTest::testRenderSmartAsync_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
if (!QFile::exists(dir.path() + QLatin1Char('/') + file
+ QStringLiteral(".inProgress.html"))) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << file << QString(dir.path() + QLatin1Char(
'/') + file
+ QStringLiteral(".html"))
<< QString(file + QStringLiteral(".out"))
<< QStringLiteral("smart") << false << QString(
dir.path() + QLatin1Char(
'/') + file
+ QStringLiteral(".inProgress.html"));
}
}
void RenderTest::testRenderSmartAsync()
{
testRender();
}
void RenderTest::testRenderSmartDetails_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
QString fname = dir.path() + QStringLiteral("/details/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << file << fname << QString(file + QStringLiteral(
".out"))
<< QStringLiteral("smart") << true << QString();
}
}
void RenderTest::testRenderSmartDetails()
{
testRender();
}
void RenderTest::testRenderInlined_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
QString fname = dir.path() + QStringLiteral("/inlined/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
fname = dir.path() + QStringLiteral("/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
continue;
}
}
QTest::newRow(file.toLatin1().constData()) << file << fname << QString(file + QStringLiteral(
".out"))
<< QStringLiteral("inlined") << false
<< QString();
}
}
void RenderTest::testRenderInlined()
{
testRender();
}
void RenderTest::testRenderIconic_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
QString fname = dir.path() + QStringLiteral("/iconic/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
fname = dir.path() + QStringLiteral("/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
continue;
}
}
QTest::newRow(file.toLatin1().constData()) << file << fname << QString(file + QStringLiteral(
".out"))
<< QStringLiteral("iconic") << false
<< QString();
}
}
void RenderTest::testRenderIconic()
{
testRender();
}
void RenderTest::testRenderHidden_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
QString fname = dir.path() + QStringLiteral("/hidden/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
fname = dir.path() + QStringLiteral("/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
continue;
}
}
QTest::newRow(file.toLatin1().constData()) << file << fname << QString(file + QStringLiteral(
".out"))
<< QStringLiteral("hidden") << false
<< QString();
}
}
void RenderTest::testRenderHidden()
{
testRender();
}
void RenderTest::testRenderHeaderOnly_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QTest::addColumn<QString>("outFileName");
QTest::addColumn<QString>("attachmentStrategy");
QTest::addColumn<bool>("showSignatureDetails");
QTest::addColumn<QString>("asyncFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
- foreach (const QString &file, l) {
+ for (const QString &file : l) {
QString fname = dir.path() + QStringLiteral("/headeronly/") + file
+ QStringLiteral(".html");
if (!QFile::exists(fname)) {
fname = dir.path() + QStringLiteral("/") + file + QStringLiteral(".html");
if (!QFile::exists(fname)) {
continue;
}
}
QTest::newRow(file.toLatin1().constData()) << file << fname << QString(file + QStringLiteral(
".out"))
<< QStringLiteral("headeronly") << false
<< QString();
}
}
void RenderTest::testRenderHeaderOnly()
{
testRender();
}
QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent)
{
const QString line
= QStringLiteral("%1 * %3\n").arg(indent,
QString::fromUtf8(messagePart->metaObject()->className()));
QString ret = line;
indent += QStringLiteral(" ");
foreach (const auto &subPart, messagePart->subParts()) {
ret += renderTreeHelper(subPart, indent);
}
return ret;
}
void RenderTest::testRenderTree(const MimeTreeParser::MessagePart::Ptr &messagePart)
{
QString renderedTree = renderTreeHelper(messagePart, QString());
QFETCH(QString, mailFileName);
QFETCH(QString, outFileName);
const QString treeFileName = QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFileName
+ QStringLiteral(".tree");
const QString outTreeFileName = outFileName + QStringLiteral(".tree");
{
QFile f(outTreeFileName);
f.open(QIODevice::WriteOnly);
f.write(renderedTree.toUtf8());
f.close();
}
// compare to reference file
const QStringList args = QStringList()
<< QStringLiteral("-u")
<< treeFileName
<< outTreeFileName;
QProcess proc;
proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.start(QStringLiteral("diff"), args);
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 0);
}
void RenderTest::testRender()
{
QFETCH(QString, mailFileName);
QFETCH(QString, referenceFileName);
QFETCH(QString, outFileName);
QFETCH(QString, attachmentStrategy);
QFETCH(bool, showSignatureDetails);
QFETCH(QString, asyncFileName);
const QString htmlFileName = outFileName + QStringLiteral(".html");
const bool bAsync = !asyncFileName.isEmpty();
// load input mail
const KMime::Message::Ptr msg(Test::readAndParseMail(mailFileName));
// render the mail
FileHtmlWriter fileWriter(outFileName);
QImage paintDevice;
Test::TestCSSHelper cssHelper(&paintDevice);
MimeTreeParser::NodeHelper nodeHelper;
Test::ObjectTreeSource testSource(&fileWriter, &cssHelper);
testSource.setAllowDecryption(true);
testSource.setAttachmentStrategy(attachmentStrategy);
testSource.setShowSignatureDetails(showSignatureDetails);
QEventLoop loop;
MimeTreeParser::ObjectTreeParser otp(&testSource, &nodeHelper);
connect(&nodeHelper, &MimeTreeParser::NodeHelper::update, &loop, &QEventLoop::quit);
otp.setAllowAsync(bAsync);
fileWriter.begin();
fileWriter.write(cssHelper.htmlHead(false));
otp.parseObjectTree(msg.data());
fileWriter.write(QStringLiteral("</body></html>"));
fileWriter.end();
if (!bAsync) {
testRenderTree(otp.parsedPart());
} else {
Test::compareFile(outFileName, asyncFileName);
loop.exec();
MimeTreeParser::ObjectTreeParser otp(&testSource, &nodeHelper);
otp.setAllowAsync(bAsync);
fileWriter.begin();
fileWriter.write(cssHelper.htmlHead(false));
otp.parseObjectTree(msg.data());
fileWriter.write(QStringLiteral("</body></html>"));
fileWriter.end();
testRenderTree(otp.parsedPart());
}
Test::compareFile(outFileName, referenceFileName);
}
QTEST_MAIN(RenderTest)
diff --git a/messageviewer/src/messagepartthemes/default/autotests/rendertest.h b/messageviewer/src/messagepartthemes/default/autotests/rendertest.h
index e902266f..98b46b5b 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/rendertest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/rendertest.h
@@ -1,60 +1,60 @@
/* Copyright (c) 2010 Volker Krause <vkrause@kde.org>
Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
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
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGEVIEWER_TEST_RENDERTEST_H
#define MESSAGEVIEWER_TEST_RENDERTEST_H
#include <QObject>
#include <MimeTreeParser/MessagePart>
class RenderTest : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
private Q_SLOTS:
void testRenderSmart_data();
void testRenderSmart();
void testRenderSmartAsync_data();
void testRenderSmartAsync();
void testRenderSmartDetails_data();
void testRenderSmartDetails();
void testRenderInlined_data();
void testRenderInlined();
void testRenderIconic_data();
void testRenderIconic();
void testRenderHidden_data();
void testRenderHidden();
void testRenderHeaderOnly_data();
void testRenderHeaderOnly();
private:
void testRender();
void testRenderTree(const MimeTreeParser::MessagePart::Ptr &messagePart);
};
#endif
diff --git a/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp b/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp
index 148d2e0f..f3e74a95 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/setupenv.cpp
@@ -1,42 +1,42 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#include "setupenv.h"
#include "messagepartthemes/default/messagepartrendererfactory.h"
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QIcon>
#include <QLocale>
void MessageViewer::Test::setupEnv()
{
- qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1(
+ qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String(
"/.qttest")).constData());
qputenv("TZ", "UTC");
QStandardPaths::setTestModeEnabled(true);
QIcon::setThemeName(QStringLiteral("breeze"));
QLocale::setDefault(QLocale(QStringLiteral("en_US")));
// disable plugin loading, so kdepim-addons doesn't interfere with our tests
MessagePartRendererFactory::instance()->setPluginPath(QString());
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/setupenv.h b/messageviewer/src/messagepartthemes/default/autotests/setupenv.h
index 79a33a00..916c3903 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/setupenv.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/setupenv.h
@@ -1,198 +1,198 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#ifndef MESSAGEVIEWER_TESTS_SETUPENV_H
#define MESSAGEVIEWER_TESTS_SETUPENV_H
#include <gpgme++/key.h>
#include <MimeTreeParser/BodyPartFormatterFactory>
#include <MimeTreeParser/ObjectTreeSource>
#include <MessageViewer/AttachmentStrategy>
#include <MessageViewer/ObjectTreeEmptySource>
namespace MessageViewer {
namespace Test {
/**
* setup a environment variables for tests:
* * set LC_ALL to C
* * set KDEHOME
*/
void setupEnv();
-// We can't use EmptySource, since we need to control some emelnets of the source for tests to also test
+// We can't use EmptySource, since we need to control some elements of the source for tests to also test
// loadExternal and htmlMail.
class ObjectTreeSource : public MessageViewer::EmptySource
{
public:
ObjectTreeSource(HtmlWriter *writer, MessageViewer::CSSHelperBase *cssHelper)
: mWriter(writer)
, mCSSHelper(cssHelper)
, mAttachmentStrategy(QStringLiteral("smart"))
, mHtmlLoadExternal(false)
, mDecryptMessage(false)
, mShowSignatureDetails(false)
, mShowEncryptionDetails(false)
, mShowExpandQuotesMark(false)
, mPreferredMode(MimeTreeParser::Util::Html)
, mQuoteLevel(1)
{
}
HtmlWriter *htmlWriter() const override
{
return mWriter;
}
CSSHelperBase *cssHelper() const override
{
return mCSSHelper;
}
bool htmlLoadExternal() const override
{
return mHtmlLoadExternal;
}
void setHtmlLoadExternal(bool loadExternal)
{
mHtmlLoadExternal = loadExternal;
}
void setAttachmentStrategy(const QString &strategy)
{
mAttachmentStrategy = strategy;
}
const AttachmentStrategy *attachmentStrategy() const override
{
return AttachmentStrategy::create(mAttachmentStrategy);
}
bool autoImportKeys() const override
{
return true;
}
bool showEmoticons() const override
{
return false;
}
void setShowExpandQuotesMark(bool b)
{
mShowExpandQuotesMark = b;
}
bool showExpandQuotesMark() const override
{
return mShowExpandQuotesMark;
}
const MimeTreeParser::BodyPartFormatterFactory *bodyPartFormatterFactory() override
{
return &mBodyPartFormatterFactory;
}
bool decryptMessage() const override
{
return mDecryptMessage;
}
void setAllowDecryption(bool allowDecryption)
{
mDecryptMessage = allowDecryption;
}
void setShowSignatureDetails(bool showSignatureDetails)
{
mShowSignatureDetails = showSignatureDetails;
}
bool showSignatureDetails() const override
{
return mShowSignatureDetails;
}
void setShowEncryptionDetails(bool showEncryptionDetails)
{
mShowEncryptionDetails = showEncryptionDetails;
}
bool showEncryptionDetails() const override
{
return mShowEncryptionDetails;
}
void setHtmlMode(MimeTreeParser::Util::HtmlMode mode, const QList<MimeTreeParser::Util::HtmlMode> &availableModes) override
{
Q_UNUSED(mode);
Q_UNUSED(availableModes);
}
MimeTreeParser::Util::HtmlMode preferredMode() const override
{
return mPreferredMode;
}
void setPreferredMode(MimeTreeParser::Util::HtmlMode mode)
{
mPreferredMode = mode;
}
int levelQuote() const override
{
return mQuoteLevel;
}
void setLevelQuote(int level)
{
mQuoteLevel = level;
}
const QTextCodec *overrideCodec() override
{
return nullptr;
}
QString createMessageHeader(KMime::Message *message) override
{
Q_UNUSED(message);
return QString(); //do nothing
}
private:
HtmlWriter *mWriter;
MessageViewer::CSSHelperBase *mCSSHelper;
QString mAttachmentStrategy;
MimeTreeParser::BodyPartFormatterFactory mBodyPartFormatterFactory;
bool mHtmlLoadExternal;
bool mDecryptMessage;
bool mShowSignatureDetails;
bool mShowEncryptionDetails;
bool mShowExpandQuotesMark;
MimeTreeParser::Util::HtmlMode mPreferredMode;
int mQuoteLevel;
};
}
}
#endif //__MESSAGEVIEWER_TESTS_SETUPENV_H
diff --git a/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.cpp b/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.cpp
index a9c3f1f3..23190731 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.cpp
@@ -1,137 +1,137 @@
/*
Copyright (c) 2017 Sandro Knauß <sknauss@kde.org>
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
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "showonlymimeparttest.h"
#include "util.h"
#include "setupenv.h"
#include "testcsshelper.h"
#include <MimeTreeParser/ObjectTreeParser>
#include <MimeTreeParser/MessagePart>
#include <MessageViewer/FileHtmlWriter>
#include <QString>
#include <QTest>
using namespace MessageViewer;
void ShowOnlyMimePartTest::initTestCase()
{
Test::setupEnv();
}
void ShowOnlyMimePartTest::testDrawFrame_data()
{
QTest::addColumn<int>("content");
QTest::addColumn<bool>("showOnlyMimePart");
QTest::newRow("only first part") << 0 << true;
QTest::newRow("only first part - render complete") << 0 << false;
QTest::newRow("only second part") << 1 << true;
QTest::newRow("only second part - render complete") << 1 << false;
QTest::newRow("only third part") << 2 << true;
QTest::newRow("only third part - render complete") << 2 << false;
}
void ShowOnlyMimePartTest::testDrawFrame()
{
QFETCH(int, content);
QFETCH(bool, showOnlyMimePart);
QString commonName(QStringLiteral("frametest.mbox.html.content.") + QString::number(content)
+ ((showOnlyMimePart) ? QStringLiteral(".single") : QStringLiteral(".full")));
QString outFileName(commonName);
QString referenceFileName(QStringLiteral(MAIL_DATA_DIR) + QLatin1Char('/') + commonName);
// load input mail
const KMime::Message::Ptr msg(Test::readAndParseMail(QStringLiteral("frametest.mbox")));
// render the mail
FileHtmlWriter fileWriter(outFileName);
QImage paintDevice;
Test::TestCSSHelper cssHelper(&paintDevice);
MimeTreeParser::NodeHelper nodeHelper;
Test::ObjectTreeSource testSource(&fileWriter, &cssHelper);
MimeTreeParser::ObjectTreeParser otp(&testSource, &nodeHelper);
fileWriter.begin();
fileWriter.write(cssHelper.htmlHead(false));
QVERIFY(msg->contents().size() > content);
otp.parseObjectTree(msg->contents().at(content), showOnlyMimePart);
fileWriter.write(QStringLiteral("</body></html>"));
fileWriter.end();
Test::compareFile(outFileName, referenceFileName);
}
void ShowOnlyMimePartTest::testRelated_data()
{
QTest::addColumn<int>("content");
QTest::addColumn<bool>("showOnlyMimePart");
QTest::newRow("only html") << 0 << true;
QTest::newRow("only html - render complete") << 0 << false;
QTest::newRow("only image") << 1 << true;
QTest::newRow("only image - render complete") << 1 << false;
}
void ShowOnlyMimePartTest::testRelated()
{
QFETCH(int, content);
QFETCH(bool, showOnlyMimePart);
QString commonName(QStringLiteral("html-multipart-related.mbox.html.content.") + QString::number(content)
+ ((showOnlyMimePart) ? QStringLiteral(".single") : QStringLiteral(".full")));
QString outFileName(commonName);
QString referenceFileName(QStringLiteral(MAIL_DATA_DIR) + QLatin1Char('/') + commonName);
// load input mail
const KMime::Message::Ptr msg(Test::readAndParseMail(QStringLiteral("html-multipart-related.mbox")));
// render the mail
FileHtmlWriter fileWriter(outFileName);
QImage paintDevice;
Test::TestCSSHelper cssHelper(&paintDevice);
MimeTreeParser::NodeHelper nodeHelper;
Test::ObjectTreeSource testSource(&fileWriter, &cssHelper);
MimeTreeParser::ObjectTreeParser otp(&testSource, &nodeHelper);
fileWriter.begin();
fileWriter.write(cssHelper.htmlHead(false));
QVERIFY(msg->contents().size() > content);
otp.parseObjectTree(msg->contents().at(content), showOnlyMimePart);
fileWriter.write(QStringLiteral("</body></html>"));
fileWriter.end();
Test::compareFile(outFileName, referenceFileName);
}
QTEST_MAIN(ShowOnlyMimePartTest)
diff --git a/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.h b/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.h
index 3d13792c..3957f605 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/showonlymimeparttest.h
@@ -1,41 +1,41 @@
/*
Copyright (c) 2017 Sandro Knauß <sknauss@kde.org>
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
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MESSAGEVIEWER_TEST_SHOWONLYMIMEPARTTEST_H
#define MESSAGEVIEWER_TEST_SHOWONLYMIMEPARTTEST_H
#include <QObject>
class ShowOnlyMimePartTest : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
private Q_SLOTS:
void testDrawFrame_data();
void testDrawFrame();
void testRelated_data();
void testRelated();
};
#endif
diff --git a/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.cpp b/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.cpp
index 2f15fae2..b83f8257 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.cpp
@@ -1,87 +1,87 @@
/*
Copyright (c) 2013 Sandro Knauß <bugs@sandroknauss.de>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "testcsshelper.h"
#include <QColor>
#include <QFont>
#include <QPalette>
#include <QApplication>
using namespace MessageViewer::Test;
TestCSSHelper::TestCSSHelper(const QPaintDevice *pd)
- : MessageViewer::CSSHelperBase(pd)
+ : MessageViewer::CSSHelper(pd)
{
mRecycleQuoteColors = false;
mBackgroundColor = QColor(0xff, 0xff, 0xff);
mForegroundColor = QColor(0x1f, 0x1c, 0x1b);
mLinkColor = QColor(0x00, 0x57, 0xae);
cPgpEncrH = QColor(0x00, 0x80, 0xff);
cPgpOk1H = QColor(0x40, 0xff, 0x40);
cPgpOk0H = QColor(0xff, 0xff, 0x40);
cPgpWarnH = QColor(0xff, 0xff, 0x40);
cPgpErrH = QColor(0xff, 0x00, 0x00);
cPgpEncrHT = QColor(0xff, 0xff, 0xff);
cPgpOk1HT = QColor(0x27, 0xae, 0x60);
cPgpOk0HT = QColor(0xf6, 0x74, 0x00);
cPgpWarnHT = QColor(0xf6, 0x74, 0x00);
cPgpErrHT = QColor(0xda, 0x44, 0x53);
cHtmlWarning = QColor(0xff, 0x40, 0x40);
for (int i = 0; i < 3; ++i) {
mQuoteColor[i] = QColor(0x00, 0x80 - i * 0x10, 0x00);
}
QFont defaultFont = QFont(QStringLiteral("Sans Serif"), 9);
mBodyFont = defaultFont;
mPrintFont = defaultFont;
mFixedFont = defaultFont;
mFixedPrintFont = defaultFont;
defaultFont.setItalic(true);
mQuoteFont = defaultFont;
mShrinkQuotes = false;
QPalette pal;
- pal.setColor(QPalette::Background, QColor(0xd6, 0xd2, 0xd0));
- pal.setColor(QPalette::Foreground, QColor(0x22, 0x1f, 0x1e));
+ pal.setColor(QPalette::Window, QColor(0xd6, 0xd2, 0xd0));
+ pal.setColor(QPalette::WindowText, QColor(0x22, 0x1f, 0x1e));
pal.setColor(QPalette::Highlight, QColor(0x43, 0xac, 0xe8));
pal.setColor(QPalette::HighlightedText, QColor(0xff, 0xff, 0xff));
pal.setColor(QPalette::Mid, QColor(0xb3, 0xab, 0xa7));
QApplication::setPalette(pal);
recalculatePGPColors();
}
TestCSSHelper::~TestCSSHelper()
{
}
QString TestCSSHelper::htmlHead(bool fixed) const
{
Q_UNUSED(fixed);
return
QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html>\n"
"<body>\n");
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.h b/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.h
index 690232b6..ca344104 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/testcsshelper.h
@@ -1,37 +1,37 @@
/*
Copyright (c) 2013 Sandro Knauß <bugs@sandroknauss.de>
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.
*/
#ifndef MESSSAGEVIEWER_TESTCSSHELPER_H
#define MESSSAGEVIEWER_TESTCSSHELPER_H
-#include <MessageViewer/CSSHelperBase>
+#include <MessageViewer/CSSHelper>
namespace MessageViewer {
namespace Test {
-class TestCSSHelper : public MessageViewer::CSSHelperBase
+class TestCSSHelper : public MessageViewer::CSSHelper
{
public:
explicit TestCSSHelper(const QPaintDevice *pd);
~TestCSSHelper() override;
QString htmlHead(bool fixed) const override;
};
}
}
#endif // MESSSAGEVIEWER_TESTCSSHELPER_H
diff --git a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp b/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp
index 00642a67..1c2a9f61 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp
@@ -1,351 +1,98 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
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.
*/
#include "unencryptedmessagetest.h"
#include "util.h"
#include "setupenv.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/ObjectTreeParser>
#include <MessageViewer/BufferedHtmlWriter>
#include <QTest>
using namespace MessageViewer;
QTEST_MAIN(UnencryptedMessageTest)
void UnencryptedMessageTest::initTestCase()
{
Test::setupEnv();
}
-void UnencryptedMessageTest::testMailWithoutEncryption()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
- QVERIFY(!nodeHelper.unencryptedMessage(originalMessage));
-}
-
-void UnencryptedMessageTest::testSignedForwardedOpenPGPSignedEncrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("signed-forward-openpgp-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
- testWriter.end();
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallyEncrypted);
- QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
-
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QVERIFY(!unencryptedMessage); // We must not invalidate the outer signature
-}
-
-void UnencryptedMessageTest::testForwardedOpenPGPSignedEncrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("forward-openpgp-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
- testWriter.end();
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallyEncrypted);
-
- QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallySigned);
-
- // Now, test that the unencrypted message is generated correctly
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QVERIFY(unencryptedMessage.data());
- QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/mixed");
- QCOMPARE(unencryptedMessage->contents().size(), 2);
- QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
- "text/plain");
- QCOMPARE(unencryptedMessage->contents().first()->decodedContent().data(), "bla bla bla");
- QCOMPARE(unencryptedMessage->contents().at(
- 1)->contentType()->mimeType().data(), "message/rfc822");
- KMime::Message::Ptr encapsulated = unencryptedMessage->contents().at(1)->bodyAsMessage();
- QCOMPARE(encapsulated->contentType()->mimeType().data(), "multipart/signed");
- QCOMPARE(encapsulated->contents().size(), 2);
- QCOMPARE(encapsulated->contents().first()->contentType()->mimeType().data(), "text/plain");
- QCOMPARE(encapsulated->contents().at(
- 1)->contentType()->mimeType().data(), "application/pgp-signature");
- QCOMPARE(encapsulated->contents().first()->decodedContent().data(), "encrypted message text");
-
- // TODO: Check that the signature is valid
-}
-
-void UnencryptedMessageTest::testSMIMESignedEncrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("smime-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
-
- QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
-
- // Now, test that the unencrypted message is generated correctly
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed");
- QCOMPARE(unencryptedMessage->contents().size(), 2);
- QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
- "text/plain");
- QCOMPARE(unencryptedMessage->contents().at(
- 1)->contentType()->mimeType().data(), "application/pkcs7-signature");
- QCOMPARE(
- unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text");
-
- // TODO: Check that the signature is valid
-}
-
-void UnencryptedMessageTest::testOpenPGPSignedEncrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
-
- QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
-
- // Now, test that the unencrypted message is generated correctly
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed");
- QCOMPARE(unencryptedMessage->contents().size(), 2);
- QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
- "text/plain");
- QCOMPARE(unencryptedMessage->contents().at(
- 1)->contentType()->mimeType().data(), "application/pgp-signature");
- QCOMPARE(
- unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text");
-
- // TODO: Check that the signature is valid
-}
-
-void UnencryptedMessageTest::testOpenPGPEncryptedAndSigned()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted+signed.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
-
- QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
-
- // Now, test that the unencrypted message is generated correctly
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain");
- QCOMPARE(unencryptedMessage->contents().size(), 0);
- QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text");
-
- // TODO: Check that the signature is valid
-}
-
-void UnencryptedMessageTest::testOpenPGPEncrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
-
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
-
- // Now, test that the unencrypted message is generated correctly
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain");
- QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text");
- QCOMPARE(unencryptedMessage->contents().size(), 0);
-}
-
-void UnencryptedMessageTest::testOpenPGPEncryptedNotDecrypted()
-{
- KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(false);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
-
- QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
-
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QVERIFY(!unencryptedMessage);
-}
-
-void UnencryptedMessageTest::testAsync_data()
-{
- QTest::addColumn<QString>("mailFileName");
- QTest::addColumn<QString>("output");
-
- QTest::newRow("openpgp-encrypt") << QStringLiteral("openpgp-encrypted.mbox") << QStringLiteral(
- "encrypted message text");
- QTest::newRow("smime-opaque-sign") << QStringLiteral("smime-opaque-sign.mbox")
- << QStringLiteral("A simple signed only test.");
- QTest::newRow("smime-encrypt") << QStringLiteral("smime-encrypted.mbox") << QStringLiteral(
- "The quick brown fox jumped over the lazy dog.");
- QTest::newRow("openpgp-inline-encrypt") << QStringLiteral(
- "openpgp-inline-charset-encrypted.mbox") << QStringLiteral(
- "asdasd asd asd asdf sadf sdaf sadf \u00F6\u00E4\u00FC");
-}
-
-void UnencryptedMessageTest::testAsync()
-{
- QFETCH(QString, mailFileName);
- QFETCH(QString, output);
-
- KMime::Message::Ptr originalMessage = Test::readAndParseMail(mailFileName);
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- {
- QEventLoop loop;
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
-
- connect(&nodeHelper, &MimeTreeParser::NodeHelper::update, &loop, &QEventLoop::quit);
- otp.setAllowAsync(true);
- otp.parseObjectTree(originalMessage.data());
- loop.exec();
- }
- // Job ended
- {
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.setAllowAsync(true);
- otp.parseObjectTree(originalMessage.data());
- QCOMPARE(otp.plainTextContent(), output);
- }
-}
-
void UnencryptedMessageTest::testNotDecrypted_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<bool>("decryptMessage");
QTest::newRow("openpgp-inline") << QStringLiteral("inlinepgpencrypted.mbox") << true;
QTest::newRow("openpgp-encrypt") << QStringLiteral("openpgp-encrypted.mbox") << true;
QTest::newRow("smime-encrypt") << QStringLiteral("smime-encrypted.mbox") << true;
QTest::newRow("openpgp-inline-encrypt") << QStringLiteral(
"openpgp-inline-charset-encrypted.mbox") << true;
QTest::newRow("smime-opaque-sign") << QStringLiteral("smime-opaque-sign.mbox") << false;
QTest::newRow("openpgp-inline-signed") << QStringLiteral("openpgp-inline-signed.mbox") << false;
QTest::newRow("openpgp-mime-signed") << QStringLiteral("openpgp-signed-mailinglist.mbox")
<< false;
}
void UnencryptedMessageTest::testNotDecrypted()
{
QFETCH(QString, mailFileName);
QFETCH(bool, decryptMessage);
KMime::Message::Ptr originalMessage = Test::readAndParseMail(mailFileName);
MimeTreeParser::NodeHelper nodeHelper;
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
emptySource.setAllowDecryption(false);
MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
otp.parseObjectTree(originalMessage.data());
testWriter.end();
if (decryptMessage) {
QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
} else {
QVERIFY(otp.plainTextContent().toLatin1().data());
}
QCOMPARE(testWriter.data().contains("<a href=\"kmail:decryptMessage\">"), decryptMessage);
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QCOMPARE((bool)unencryptedMessage, false);
}
void UnencryptedMessageTest::testSMimeAutoCertImport()
{
KMime::Message::Ptr originalMessage = Test::readAndParseMail(QStringLiteral("smime-cert.mbox"));
MimeTreeParser::NodeHelper nodeHelper;
BufferedHtmlWriter testWriter;
testWriter.begin();
Test::CSSHelper testCSSHelper;
Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
otp.parseObjectTree(originalMessage.data());
testWriter.end();
QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
QVERIFY(testWriter.data().contains("Sorry, certificate could not be imported."));
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h b/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h
index 7799fa29..aa896e8f 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h
@@ -1,46 +1,36 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
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.
*/
#ifndef MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
#define MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
#include <QObject>
class UnencryptedMessageTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
- void testMailWithoutEncryption();
- void testSMIMESignedEncrypted();
- void testOpenPGPSignedEncrypted();
- void testOpenPGPEncryptedAndSigned();
- void testForwardedOpenPGPSignedEncrypted();
- void testSignedForwardedOpenPGPSignedEncrypted();
- void testOpenPGPEncrypted();
- void testOpenPGPEncryptedNotDecrypted();
- void testAsync_data();
- void testAsync();
void testNotDecrypted_data();
void testNotDecrypted();
void testSMimeAutoCertImport();
};
#endif // MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
diff --git a/messageviewer/src/messagepartthemes/default/autotests/util.cpp b/messageviewer/src/messagepartthemes/default/autotests/util.cpp
index d062aba1..9ff759e3 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/util.cpp
+++ b/messageviewer/src/messagepartthemes/default/autotests/util.cpp
@@ -1,100 +1,100 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include "util.h"
#include <QFile>
#include <QProcess>
#include <QTest>
KMime::Message::Ptr MessageViewer::Test::readAndParseMail(const QString &mailFile)
{
QFile file(QStringLiteral(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
const bool openFile = file.open(QIODevice::ReadOnly);
Q_ASSERT(openFile);
const QByteArray data = KMime::CRLFtoLF(file.readAll());
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
void MessageViewer::Test::compareFile(const QString &outFile, const QString &referenceFile)
{
QVERIFY(QFile::exists(outFile));
const QString htmlFile = outFile + QStringLiteral(".html");
// remove tailing newlines and spaces and make htmlmore uniform (breaks pre tags)
{
QFile f(outFile);
QVERIFY(f.open(QIODevice::ReadOnly));
QString content = QString::fromUtf8(f.readAll());
f.close();
content.replace(QRegExp(QStringLiteral("[\t ]+")), QStringLiteral(" "));
content.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*")), QStringLiteral("\n"));
content.replace(QRegExp(QStringLiteral("([\n\t ])\\1+")), QStringLiteral("\\1"));
content.replace(QRegExp(QStringLiteral(">\n+[\t ]*")), QStringLiteral(">"));
content.replace(QRegExp(QStringLiteral("[\t ]*\n+[\t ]*<")), QStringLiteral("<"));
content.replace(QLatin1String("&nbsp;"), QLatin1String("NBSP_ENTITY_PLACEHOLDER")); // xmlling chokes on &nbsp;
QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
f.write(content.toUtf8());
f.close();
}
- // validate xml and pretty-print for comparisson
+ // validate xml and pretty-print for comparison
// TODO add proper cmake check for xmllint and diff
QStringList args = QStringList()
<< QStringLiteral("--format")
<< QStringLiteral("--encode")
<< QStringLiteral("UTF8")
<< QStringLiteral("--output")
<< htmlFile
<< outFile;
QCOMPARE(QProcess::execute(QStringLiteral("xmllint"), args), 0);
// get rid of system dependent or random paths
{
QFile f(htmlFile);
QVERIFY(f.open(QIODevice::ReadOnly));
QString content = QString::fromUtf8(f.readAll());
f.close();
content.replace(QRegExp(QStringLiteral(
"\"file:[^\"]*[/(?:%2F)]([^\"/(?:%2F)]*)\"")),
QStringLiteral("\"file:\\1\""));
content.replace(QRegExp(QStringLiteral(
"(file:///tmp/messageviewer)(_[^\"]+)(\\.index\\.[^\"]*)")),
QStringLiteral("\\1\\3"));
content.replace(QLatin1String("NBSP_ENTITY_PLACEHOLDER"), QLatin1String("&nbsp;")); // undo above transformation for xmllint
QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
f.write(content.toUtf8());
f.close();
}
// compare to reference file
args = QStringList()
<< QStringLiteral("-u")
<< referenceFile
<< htmlFile;
QProcess proc;
proc.setProcessChannelMode(QProcess::ForwardedChannels);
proc.start(QStringLiteral("diff"), args);
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 0);
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/util.h b/messageviewer/src/messagepartthemes/default/autotests/util.h
index 08605521..bf8da6a5 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/util.h
+++ b/messageviewer/src/messagepartthemes/default/autotests/util.h
@@ -1,60 +1,61 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
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.
*/
#ifndef MESSAGEVIEWER_TESTS_UTIL_H
#define MESSAGEVIEWER_TESTS_UTIL_H
#include "interfaces/htmlwriter.h"
#include <MessageViewer/CSSHelperBase>
+#include <MessageViewer/CSSHelper>
#include <KMime/Message>
namespace MessageViewer {
namespace Test {
-class CSSHelper : public MessageViewer::CSSHelperBase
+class CSSHelper : public MessageViewer::CSSHelper
{
public:
- CSSHelper() : MessageViewer::CSSHelperBase(nullptr)
+ CSSHelper() : MessageViewer::CSSHelper(nullptr)
{
for (int i = 0; i < 3; ++i) {
mQuoteColor[i] = QColor(0x00, 0x80 - i * 0x10, 0x00);
}
}
virtual ~CSSHelper()
{
}
QString nonQuotedFontTag() const
{
return QStringLiteral("<");
}
QString quoteFontTag(int) const
{
return QStringLiteral("<");
}
};
KMime::Message::Ptr readAndParseMail(const QString &mailFile);
void compareFile(const QString &outFile, const QString &referenceFile);
}
}
#endif //__MESSAGEVIEWER_TESTS_UTIL_H
diff --git a/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.cpp b/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.cpp
index 4140b241..bdf31846 100644
--- a/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.cpp
+++ b/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.cpp
@@ -1,77 +1,77 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#include "converthtmltoplaintext.h"
#include <grantlee/plaintextmarkupbuilder.h>
#include <QTextDocument>
using namespace MimeTreeParser;
ConvertHtmlToPlainText::ConvertHtmlToPlainText()
{
}
ConvertHtmlToPlainText::~ConvertHtmlToPlainText()
{
}
void ConvertHtmlToPlainText::setHtmlString(const QString &htmlString)
{
mHtmlString = htmlString;
}
QString ConvertHtmlToPlainText::generatePlainText()
{
if (mHtmlString.isEmpty()) {
return QString();
}
Grantlee::PlainTextMarkupBuilder *pb = new Grantlee::PlainTextMarkupBuilder();
Grantlee::MarkupDirector *pmd = new Grantlee::MarkupDirector(pb);
QTextDocument *doc = new QTextDocument;
doc->setHtml(mHtmlString);
pmd->processDocument(doc);
QString plainText = pb->getResult();
delete doc;
delete pmd;
delete pb;
toCleanPlainText(plainText);
return plainText;
}
QString ConvertHtmlToPlainText::htmlString() const
{
return mHtmlString;
}
//Duplicate from kpimtextedit/textedit.h
void ConvertHtmlToPlainText::toCleanPlainText(QString &text)
{
// Remove line separators. Normal \n chars are still there, so no linebreaks get lost here
text.remove(QChar::LineSeparator);
// Get rid of embedded images, see QTextImageFormat documentation:
// "Inline images are represented by an object replacement character (0xFFFC in Unicode) "
text.remove(0xFFFC);
// In plaintext mode, each space is non-breaking.
text.replace(QChar::Nbsp, QChar::fromLatin1(' '));
}
diff --git a/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.h b/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.h
index 5a783a6e..2ce4f234 100644
--- a/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.h
+++ b/messageviewer/src/messagepartthemes/default/converthtmltoplaintext.h
@@ -1,43 +1,43 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@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.
*/
#ifndef MIMETREEPARSER_CONVERTHTMLTOPLAINTEXT_H
#define MIMETREEPARSER_CONVERTHTMLTOPLAINTEXT_H
#include <QString>
namespace MimeTreeParser {
class ConvertHtmlToPlainText
{
public:
ConvertHtmlToPlainText();
~ConvertHtmlToPlainText();
Q_REQUIRED_RESULT QString generatePlainText();
Q_REQUIRED_RESULT QString htmlString() const;
void setHtmlString(const QString &htmlString);
private:
void toCleanPlainText(QString &text);
QString mHtmlString;
};
}
#endif // MIMETREEPARSER_CONVERTHTMLTOPLAINTEXT_H
diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp
index eea77b46..e74f7bfa 100644
--- a/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp
+++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.cpp
@@ -1,1156 +1,1174 @@
/*
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.
*/
#include "defaultrenderer.h"
#include "defaultrenderer_p.h"
+#include "utils/messageviewerutil.h"
+
#include "messageviewer_debug.h"
#include "converthtmltoplaintext.h"
#include "messagepartrendererbase.h"
#include "messagepartrendererfactory.h"
#include "htmlblock.h"
-
+#include <QtWebEngineWidgets>
#include "utils/iconnamecache.h"
#include "utils/mimetype.h"
#include "viewer/attachmentstrategy.h"
#include "viewer/csshelperbase.h"
#include "messagepartrenderermanager.h"
#include "htmlwriter/bufferedhtmlwriter.h"
#include <MimeTreeParser/MessagePart>
#include <MimeTreeParser/ObjectTreeParser>
#include <GrantleeTheme/QtResourceTemplateLoader>
#include <QGpgME/Protocol>
#include <MessageCore/StringUtil>
#include <KEmailAddress>
#include <KIconLoader>
#include <KLocalizedString>
#include <QApplication>
#include <QUrl>
#include <grantlee/context.h>
#include <grantlee/engine.h>
#include <grantlee/metatype.h>
#include <grantlee/templateloader.h>
#include <grantlee/template.h>
using namespace MimeTreeParser;
using namespace MessageViewer;
Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient)
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(const QGpgME::Protocol *)
static const int SIG_FRAME_COL_UNDEF = 99;
#define SIG_FRAME_COL_RED -1
#define SIG_FRAME_COL_YELLOW 0
#define SIG_FRAME_COL_GREEN 1
QString sigStatusToString(const QGpgME::Protocol *cryptProto, int status_code, GpgME::Signature::Summary summary, int &frameColor, bool &showKeyInfos)
{
// note: At the moment frameColor and showKeyInfos are
// used for CMS only but not for PGP signatures
// pending(khz): Implement usage of these for PGP sigs as well.
showKeyInfos = true;
QString result;
if (cryptProto) {
if (cryptProto == QGpgME::openpgp()) {
// process enum according to it's definition to be read in
// GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
switch (status_code) {
case 0: // GPGME_SIG_STAT_NONE
result = i18n("Error: Signature not verified");
+ frameColor = SIG_FRAME_COL_YELLOW;
break;
case 1: // GPGME_SIG_STAT_GOOD
result = i18n("Good signature");
+ frameColor = SIG_FRAME_COL_GREEN;
break;
case 2: // GPGME_SIG_STAT_BAD
result = i18n("Bad signature");
+ frameColor = SIG_FRAME_COL_RED;
break;
case 3: // GPGME_SIG_STAT_NOKEY
result = i18n("No public key to verify the signature");
+ frameColor = SIG_FRAME_COL_RED;
break;
case 4: // GPGME_SIG_STAT_NOSIG
result = i18n("No signature found");
+ frameColor = SIG_FRAME_COL_RED;
break;
case 5: // GPGME_SIG_STAT_ERROR
result = i18n("Error verifying the signature");
+ frameColor = SIG_FRAME_COL_RED;
break;
case 6: // GPGME_SIG_STAT_DIFF
result = i18n("Different results for signatures");
+ frameColor = SIG_FRAME_COL_RED;
break;
/* PENDING(khz) Verify exact meaning of the following values:
case 7: // GPGME_SIG_STAT_GOOD_EXP
return i18n("Signature certificate is expired");
break;
case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
return i18n("One of the certificate's keys is expired");
break;
*/
default:
result.clear(); // do *not* return a default text here !
break;
}
} else if (cryptProto == QGpgME::smime()) {
// process status bits according to SigStatus_...
// definitions in kdenetwork/libkdenetwork/cryptplug.h
if (summary == GpgME::Signature::None) {
result = i18n("No status information available.");
frameColor = SIG_FRAME_COL_YELLOW;
showKeyInfos = false;
return result;
}
if (summary & GpgME::Signature::Valid) {
result = i18n("Good signature.");
// Note:
// Here we are work differently than KMail did before!
//
// The GOOD case ( == sig matching and the complete
// certificate chain was verified and is valid today )
// by definition does *not* show any key
// information but just states that things are OK.
// (khz, according to LinuxTag 2002 meeting)
frameColor = SIG_FRAME_COL_GREEN;
showKeyInfos = false;
return result;
}
// we are still there? OK, let's test the different cases:
// we assume green, test for yellow or red (in this order!)
frameColor = SIG_FRAME_COL_GREEN;
QString result2;
if (summary & GpgME::Signature::KeyExpired) {
// still is green!
result2 = i18n("One key has expired.");
}
if (summary & GpgME::Signature::SigExpired) {
// and still is green!
result2 += i18n("The signature has expired.");
}
// test for yellow:
if (summary & GpgME::Signature::KeyMissing) {
result2 += i18n("Unable to verify: key missing.");
// if the signature certificate is missing
// we cannot show information on it
showKeyInfos = false;
frameColor = SIG_FRAME_COL_YELLOW;
}
if (summary & GpgME::Signature::CrlMissing) {
result2 += i18n("CRL not available.");
frameColor = SIG_FRAME_COL_YELLOW;
}
if (summary & GpgME::Signature::CrlTooOld) {
result2 += i18n("Available CRL is too old.");
frameColor = SIG_FRAME_COL_YELLOW;
}
if (summary & GpgME::Signature::BadPolicy) {
result2 += i18n("A policy was not met.");
frameColor = SIG_FRAME_COL_YELLOW;
}
if (summary & GpgME::Signature::SysError) {
result2 += i18n("A system error occurred.");
// if a system error occurred
// we cannot trust any information
// that was given back by the plug-in
showKeyInfos = false;
frameColor = SIG_FRAME_COL_YELLOW;
}
// test for red:
if (summary & GpgME::Signature::KeyRevoked) {
// this is red!
result2 += i18n("One key has been revoked.");
frameColor = SIG_FRAME_COL_RED;
}
if (summary & GpgME::Signature::Red) {
if (result2.isEmpty()) {
// Note:
// Here we are work differently than KMail did before!
//
// The BAD case ( == sig *not* matching )
// by definition does *not* show any key
// information but just states that things are BAD.
//
// The reason for this: In this case ALL information
// might be falsificated, we can NOT trust the data
// in the body NOT the signature - so we don't show
// any key/signature information at all!
// (khz, according to LinuxTag 2002 meeting)
showKeyInfos = false;
}
frameColor = SIG_FRAME_COL_RED;
} else {
result.clear();
}
if (SIG_FRAME_COL_GREEN == frameColor) {
result = i18n("Good signature.");
} else if (SIG_FRAME_COL_RED == frameColor) {
result = i18n("Bad signature.");
} else {
result.clear();
}
if (!result2.isEmpty()) {
if (!result.isEmpty()) {
result.append(QLatin1String("<br />"));
}
result.append(result2);
}
}
/*
// add i18n support for 3rd party plug-ins here:
else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) {
}
*/
}
return result;
}
/** Checks whether @p str contains external references. To be precise,
we only check whether @p str contains 'xxx="http[s]:' where xxx is
not href. Obfuscated external references are ignored on purpose.
*/
bool containsExternalReferences(const QString &str, const QString &extraHead)
{
const bool hasBaseInHeader = extraHead.contains(QLatin1String(
"<base href=\""), Qt::CaseInsensitive);
if (hasBaseInHeader && (str.contains(QLatin1String("href=\"/"), Qt::CaseInsensitive)
|| str.contains(QLatin1String("<img src=\"/"), Qt::CaseInsensitive))) {
return true;
}
int httpPos = str.indexOf(QLatin1String("\"http:"), Qt::CaseInsensitive);
int httpsPos = str.indexOf(QLatin1String("\"https:"), Qt::CaseInsensitive);
while (httpPos >= 0 || httpsPos >= 0) {
// pos = index of next occurrence of "http: or "https: whichever comes first
int pos = (httpPos < httpsPos)
? ((httpPos >= 0) ? httpPos : httpsPos)
: ((httpsPos >= 0) ? httpsPos : httpPos);
// look backwards for "href"
if (pos > 5) {
int hrefPos = str.lastIndexOf(QLatin1String("href"), pos - 5, Qt::CaseInsensitive);
// if no 'href' is found or the distance between 'href' and '"http[s]:'
// is larger than 7 (7 is the distance in 'href = "http[s]:') then
// we assume that we have found an external reference
if ((hrefPos == -1) || (pos - hrefPos > 7)) {
// HTML messages created by KMail itself for now contain the following:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
// Make sure not to show an external references warning for this string
int dtdPos = str.indexOf(QLatin1String(
"http://www.w3.org/TR/html4/loose.dtd"), pos + 1);
if (dtdPos != (pos + 1)) {
return true;
}
}
}
// find next occurrence of "http: or "https:
if (pos == httpPos) {
httpPos = str.indexOf(QLatin1String("\"http:"), httpPos + 6, Qt::CaseInsensitive);
} else {
httpsPos = str.indexOf(QLatin1String("\"https:"), httpsPos + 7, Qt::CaseInsensitive);
}
}
return false;
}
// FIXME this used to go through the full webkit parser to extract the body and head blocks
// until we have that back, at least attempt to fix some of the damage
// yes, "parsing" HTML with regexps is very very wrong, but it's still better than not filtering
// this at all...
QString processHtml(const QString &htmlSource, QString &extraHead)
{
- auto s = htmlSource.trimmed();
- s = s.replace(QRegExp(QStringLiteral("^<!DOCTYPE[^>]*>"), Qt::CaseInsensitive), QString()).trimmed();
- s = s.replace(QRegExp(QStringLiteral("^<html[^>]*>"), Qt::CaseInsensitive), QString()).trimmed();
+ QString s = htmlSource.trimmed();
+ s = s.remove(QRegExp(QStringLiteral("^<!DOCTYPE[^>]*>"), Qt::CaseInsensitive)).trimmed();
+ s = s.remove(QRegExp(QStringLiteral("^<html[^>]*>"), Qt::CaseInsensitive)).trimmed();
// head
s = s.replace(QRegExp(QStringLiteral("^<head/>"), Qt::CaseInsensitive), QString()).trimmed();
- if (s.startsWith(QLatin1String("<head>", Qt::CaseInsensitive))) {
- const auto idx = s.indexOf(QLatin1String("</head>"), Qt::CaseInsensitive);
- if (idx < 0) {
+ const int startIndex = s.indexOf(QLatin1String("<head>"), Qt::CaseInsensitive);
+ if (startIndex >= 0) {
+ const auto endIndex = s.indexOf(QLatin1String("</head>"), Qt::CaseInsensitive);
+
+ if (endIndex < 0) {
return htmlSource;
}
- extraHead = s.mid(6, idx - 6);
- s = s.mid(idx + 7).trimmed();
+ extraHead = s.mid(startIndex + 6, endIndex - startIndex - 6);
+#if QTWEBENGINEWIDGETS_VERSION < QT_VERSION_CHECK(5, 13, 0)
+ //Remove this hack with https://codereview.qt-project.org/#/c/256100/2 is merged
+ //Don't authorize to refresh content.
+ if (MessageViewer::Util::excludeExtraHeader(s)) {
+ extraHead.clear();
+ }
+#endif
+
+
+ s = s.mid(endIndex + 7).trimmed();
}
// body
- s = s.replace(QRegExp(QStringLiteral("<body[^>]*>"), Qt::CaseInsensitive), QString()).trimmed();
- s = s.replace(QRegExp(QStringLiteral("</html>$"), Qt::CaseInsensitive), QString()).trimmed();
- s = s.replace(QRegExp(QStringLiteral("</body>$"), Qt::CaseInsensitive), QString()).trimmed();
+ s = s.remove(QRegExp(QStringLiteral("<body[^>]*>"), Qt::CaseInsensitive)).trimmed();
+ s = s.remove(QRegExp(QStringLiteral("</html>$"), Qt::CaseInsensitive)).trimmed();
+ s = s.remove(QRegExp(QStringLiteral("</body>$"), Qt::CaseInsensitive)).trimmed();
return s;
}
DefaultRendererPrivate::DefaultRendererPrivate(CSSHelperBase *cssHelper, const MessagePartRendererFactory *rendererFactory)
: mCSSHelper(cssHelper)
, mRendererFactory(rendererFactory)
{
}
DefaultRendererPrivate::~DefaultRendererPrivate()
{
}
CSSHelperBase *DefaultRendererPrivate::cssHelper() const
{
return mCSSHelper;
}
Interface::ObjectTreeSource *DefaultRendererPrivate::source() const
{
return mMsgPart->source();
}
void DefaultRendererPrivate::renderSubParts(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
{
- foreach (const auto &m, msgPart->subParts()) {
+ for (const auto &m : msgPart->subParts()) {
renderFactory(m, htmlWriter);
}
}
void DefaultRendererPrivate::render(const MessagePartList::Ptr &mp, HtmlWriter *htmlWriter)
{
HTMLBlock::Ptr rBlock;
HTMLBlock::Ptr aBlock;
if (mp->isRoot()) {
rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
}
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
renderSubParts(mp, htmlWriter);
}
void DefaultRendererPrivate::render(const MimeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
HTMLBlock::Ptr aBlock;
HTMLBlock::Ptr rBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
if (mp->isRoot()) {
rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
}
renderSubParts(mp, htmlWriter);
}
void DefaultRendererPrivate::render(const EncapsulatedRfc822MessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
if (!mp->hasSubParts()) {
return;
}
Grantlee::Template t = MessagePartRendererManager::self()->loadByName(QStringLiteral(":/encapsulatedrfc822messagepart.html"));
Grantlee::Context c = MessagePartRendererManager::self()->createContext();
QObject block;
c.insert(QStringLiteral("block"), &block);
block.setProperty("link",
mp->nodeHelper()->asHREF(mp->message().data(), QStringLiteral("body")));
c.insert(QStringLiteral("msgHeader"), mCreateMessageHeader(mp->message().data()));
c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
renderSubParts(mp, htmlWriter);
}));
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
Grantlee::OutputStream s(htmlWriter->stream());
t->render(&s, &c);
}
void DefaultRendererPrivate::render(const HtmlMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(
":/htmlmessagepart.html"));
Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
QObject block;
c.insert(QStringLiteral("block"), &block);
auto preferredMode = mp->source()->preferredMode();
const bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
block.setProperty("htmlMail", isHtmlPreferred);
block.setProperty("loadExternal", htmlLoadExternal());
block.setProperty("isPrinting", isPrinting());
{
QString extraHead;
//laurent: FIXME port to async method webengine
QString bodyText = processHtml(mp->bodyHtml(), extraHead);
if (isHtmlPreferred) {
mp->nodeHelper()->setNodeDisplayedEmbedded(mp->content(), true);
htmlWriter->extraHead(extraHead);
}
block.setProperty("containsExternalReferences",
containsExternalReferences(bodyText, extraHead));
c.insert(QStringLiteral("content"), bodyText);
}
{
ConvertHtmlToPlainText convert;
convert.setHtmlString(mp->bodyHtml());
QString plaintext = convert.generatePlainText();
plaintext.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
c.insert(QStringLiteral("plaintext"), plaintext);
}
mp->source()->setHtmlMode(MimeTreeParser::Util::Html, QList<MimeTreeParser::Util::HtmlMode>() << MimeTreeParser::Util::Html << MimeTreeParser::Util::Normal);
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
Grantlee::OutputStream s(htmlWriter->stream());
t->render(&s, &c);
}
void DefaultRendererPrivate::renderEncrypted(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
KMime::Content *node = mp->content();
const auto metaData = *mp->partMetaData();
Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(
":/encryptedmessagepart.html"));
Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
QObject block;
if (node || mp->hasSubParts()) {
c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
HTMLBlock::Ptr rBlock;
if (mp->content() && mp->isRoot()) {
rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
}
renderSubParts(mp, htmlWriter);
}));
} else if (!metaData.inProgress) {
c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
}));
}
c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(mp->cryptoProto()));
if (!mp->decryptRecipients().empty()) {
c.insert(QStringLiteral("decryptedRecipients"),
QVariant::fromValue(mp->decryptRecipients()));
}
c.insert(QStringLiteral("block"), &block);
block.setProperty("isPrinting", isPrinting());
block.setProperty("detailHeader", showEncryptionDetails());
block.setProperty("inProgress", metaData.inProgress);
block.setProperty("isDecrypted", mp->decryptMessage());
block.setProperty("isDecryptable", metaData.isDecryptable);
block.setProperty("decryptIcon",
QUrl::fromLocalFile(IconNameCache::instance()->iconPath(QStringLiteral(
"document-decrypt"),
KIconLoader::Small)).url());
block.setProperty("errorText", metaData.errorText);
block.setProperty("noSecKey", mp->isNoSecKey());
Grantlee::OutputStream s(htmlWriter->stream());
t->render(&s, &c);
}
void DefaultRendererPrivate::renderSigned(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
KMime::Content *node = mp->content();
const auto metaData = *mp->partMetaData();
auto cryptoProto = mp->cryptoProto();
const bool isSMIME = cryptoProto && (cryptoProto == QGpgME::smime());
Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(
":/signedmessagepart.html"));
Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
QObject block;
if (node) {
c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
HTMLBlock::Ptr rBlock;
if (mp->isRoot()) {
rBlock = HTMLBlock::Ptr(new RootBlock(htmlWriter));
}
renderSubParts(mp, htmlWriter);
}));
} else if (!metaData.inProgress) {
c.insert(QStringLiteral("content"), QVariant::fromValue<GrantleeCallback>([this, mp, htmlWriter](Grantlee::OutputStream *) {
renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
}));
}
c.insert(QStringLiteral("cryptoProto"), QVariant::fromValue(cryptoProto));
c.insert(QStringLiteral("block"), &block);
block.setProperty("inProgress", metaData.inProgress);
block.setProperty("errorText", metaData.errorText);
block.setProperty("detailHeader", showSignatureDetails());
block.setProperty("isPrinting", isPrinting());
block.setProperty("addr", metaData.signerMailAddresses.join(QLatin1Char(',')));
block.setProperty("technicalProblem", metaData.technicalProblem);
block.setProperty("keyId", metaData.keyId);
if (metaData.creationTime.isValid()) { //should be handled inside grantlee but currently not possible see: https://bugs.kde.org/363475
block.setProperty("creationTime",
QLocale().toString(metaData.creationTime, QLocale::ShortFormat));
}
block.setProperty("isGoodSignature", metaData.isGoodSignature);
block.setProperty("isSMIME", isSMIME);
if (metaData.keyTrust == GpgME::Signature::Unknown) {
block.setProperty("keyTrust", QStringLiteral("unknown"));
} else if (metaData.keyTrust == GpgME::Signature::Marginal) {
block.setProperty("keyTrust", QStringLiteral("marginal"));
} else if (metaData.keyTrust == GpgME::Signature::Full) {
block.setProperty("keyTrust", QStringLiteral("full"));
} else if (metaData.keyTrust == GpgME::Signature::Ultimate) {
block.setProperty("keyTrust", QStringLiteral("ultimate"));
} else {
block.setProperty("keyTrust", QStringLiteral("untrusted"));
}
QString startKeyHREF;
{
QString keyWithWithoutURL;
if (cryptoProto) {
startKeyHREF
= QStringLiteral("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
.arg(cryptoProto->displayName(),
cryptoProto->name(),
QString::fromLatin1(metaData.keyId));
keyWithWithoutURL
= QStringLiteral("%1%2</a>").arg(startKeyHREF,
QString::fromLatin1(QByteArray(QByteArrayLiteral(
"0x")
+ metaData.keyId)));
} else {
keyWithWithoutURL = QStringLiteral("0x") + QString::fromUtf8(metaData.keyId);
}
block.setProperty("keyWithWithoutURL", keyWithWithoutURL);
}
bool onlyShowKeyURL = false;
bool showKeyInfos = false;
bool cannotCheckSignature = true;
QString signer = metaData.signer;
QString statusStr;
QString mClass;
QString greenCaseWarning;
if (metaData.inProgress) {
mClass = QStringLiteral("signInProgress");
} else {
const QStringList &blockAddrs(metaData.signerMailAddresses);
// note: At the moment frameColor and showKeyInfos are
// used for CMS only but not for PGP signatures
// pending(khz): Implement usage of these for PGP sigs as well.
int frameColor = SIG_FRAME_COL_UNDEF;
statusStr = sigStatusToString(cryptoProto,
metaData.status_code,
metaData.sigSummary,
frameColor,
showKeyInfos);
// if needed fallback to english status text
// that was reported by the plugin
if (statusStr.isEmpty()) {
statusStr = metaData.status;
}
if (metaData.technicalProblem) {
frameColor = SIG_FRAME_COL_YELLOW;
}
switch (frameColor) {
case SIG_FRAME_COL_RED:
cannotCheckSignature = false;
break;
case SIG_FRAME_COL_YELLOW:
cannotCheckSignature = true;
break;
case SIG_FRAME_COL_GREEN:
cannotCheckSignature = false;
break;
}
// temporary hack: always show key information!
showKeyInfos = true;
if (isSMIME && (SIG_FRAME_COL_UNDEF != frameColor)) {
switch (frameColor) {
case SIG_FRAME_COL_RED:
mClass = QStringLiteral("signErr");
onlyShowKeyURL = true;
break;
case SIG_FRAME_COL_YELLOW:
if (metaData.technicalProblem) {
mClass = QStringLiteral("signWarn");
} else {
mClass = QStringLiteral("signOkKeyBad");
}
break;
case SIG_FRAME_COL_GREEN:
mClass = QStringLiteral("signOkKeyOk");
// extra hint for green case
// that email addresses in DN do not match fromAddress
QString msgFrom(KEmailAddress::extractEmailAddress(mp->fromAddress()));
QString certificate;
if (metaData.keyId.isEmpty()) {
certificate = i18n("certificate");
} else {
certificate = startKeyHREF + i18n("certificate") + QStringLiteral("</a>");
}
if (!blockAddrs.empty()) {
if (!blockAddrs.contains(msgFrom, Qt::CaseInsensitive)) {
greenCaseWarning
= QStringLiteral("<u>")
+i18nc("Start of warning message.", "Warning:")
+QStringLiteral("</u> ")
+i18n(
"Sender's mail address is not stored in the %1 used for signing.",
certificate)
+QStringLiteral("<br />")
+i18n("sender: ")
+msgFrom
+QStringLiteral("<br />")
+i18n("stored: ");
// We cannot use Qt's join() function here but
// have to join the addresses manually to
// extract the mail addresses (without '<''>')
// before including it into our string:
bool bStart = true;
QStringList::ConstIterator end(blockAddrs.constEnd());
for (QStringList::ConstIterator it = blockAddrs.constBegin();
it != end; ++it) {
if (!bStart) {
greenCaseWarning.append(QLatin1String(", <br />&nbsp; &nbsp;"));
}
bStart = false;
greenCaseWarning.append(KEmailAddress::extractEmailAddress(*it));
}
}
} else {
greenCaseWarning
= QStringLiteral("<u>")
+i18nc("Start of warning message.", "Warning:")
+QStringLiteral("</u> ")
+i18n("No mail address is stored in the %1 used for signing, "
"so we cannot compare it to the sender's address %2.",
certificate,
msgFrom);
}
break;
}
if (showKeyInfos && !cannotCheckSignature) {
if (metaData.signer.isEmpty()) {
signer.clear();
} else {
if (!blockAddrs.empty()) {
const QUrl address = KEmailAddress::encodeMailtoUrl(blockAddrs.first());
signer
= QStringLiteral("<a href=\"mailto:%1\">%2</a>").arg(QLatin1String(QUrl
::
toPercentEncoding(
address
.
path())),
signer);
}
}
}
} else {
if (metaData.signer.isEmpty() || metaData.technicalProblem) {
mClass = QStringLiteral("signWarn");
} else {
// HTMLize the signer's user id and create mailto: link
signer = MessageCore::StringUtil::quoteHtmlChars(signer, true);
signer = QStringLiteral("<a href=\"mailto:%1\">%1</a>").arg(signer);
if (metaData.isGoodSignature) {
if (metaData.keyTrust < GpgME::Signature::Marginal) {
mClass = QStringLiteral("signOkKeyBad");
} else {
mClass = QStringLiteral("signOkKeyOk");
}
} else {
mClass = QStringLiteral("signErr");
}
}
}
}
block.setProperty("onlyShowKeyURL", onlyShowKeyURL);
block.setProperty("showKeyInfos", showKeyInfos);
block.setProperty("cannotCheckSignature", cannotCheckSignature);
block.setProperty("signer", signer);
block.setProperty("statusStr", statusStr);
block.setProperty("signClass", mClass);
block.setProperty("greenCaseWarning", greenCaseWarning);
Grantlee::OutputStream s(htmlWriter->stream());
t->render(&s, &c);
}
void DefaultRendererPrivate::render(const SignedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
const auto metaData = *mp->partMetaData();
if (metaData.isSigned || metaData.inProgress) {
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
renderSigned(mp, htmlWriter);
return;
}
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
if (mp->hasSubParts()) {
renderSubParts(mp, htmlWriter);
} else if (!metaData.inProgress) {
renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
}
}
void DefaultRendererPrivate::render(const EncryptedMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
const auto metaData = *mp->partMetaData();
if (metaData.isEncrypted || metaData.inProgress) {
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
renderEncrypted(mp, htmlWriter);
return;
}
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
if (mp->hasSubParts()) {
renderSubParts(mp, htmlWriter);
} else if (!metaData.inProgress) {
renderWithFactory<MimeTreeParser::MessagePart>(mp, htmlWriter);
}
}
void DefaultRendererPrivate::render(const AlternativeMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
auto mode = mp->preferredMode();
if (mode == MimeTreeParser::Util::MultipartPlain && mp->text().trimmed().isEmpty()) {
- foreach (const auto m, mp->availableModes()) {
+ for (const auto m : mp->availableModes()) {
if (m != MimeTreeParser::Util::MultipartPlain) {
mode = m;
break;
}
}
}
MimeMessagePart::Ptr part(mp->childParts().first());
if (mp->childParts().contains(mode)) {
part = mp->childParts()[mode];
}
render(part, htmlWriter);
}
void DefaultRendererPrivate::render(const CertMessagePart::Ptr &mp, HtmlWriter *htmlWriter)
{
const GpgME::ImportResult &importResult(mp->importResult());
Grantlee::Template t = MessageViewer::MessagePartRendererManager::self()->loadByName(QStringLiteral(
":/certmessagepart.html"));
Grantlee::Context c = MessageViewer::MessagePartRendererManager::self()->createContext();
QObject block;
c.insert(QStringLiteral("block"), &block);
block.setProperty("importError", QString::fromLocal8Bit(importResult.error().asString()));
block.setProperty("nImp", importResult.numImported());
block.setProperty("nUnc", importResult.numUnchanged());
block.setProperty("nSKImp", importResult.numSecretKeysImported());
block.setProperty("nSKUnc", importResult.numSecretKeysUnchanged());
QVariantList keylist;
const auto imports = importResult.imports();
auto end(imports.end());
for (auto it = imports.begin(); it != end; ++it) {
QObject *key(new QObject(mp.data()));
key->setProperty("error", QString::fromLocal8Bit((*it).error().asString()));
key->setProperty("status", (*it).status());
key->setProperty("fingerprint", QLatin1String((*it).fingerprint()));
keylist << QVariant::fromValue(key);
}
HTMLBlock::Ptr aBlock;
if (mp->isAttachment()) {
aBlock = HTMLBlock::Ptr(new AttachmentMarkBlock(htmlWriter, mp->attachmentContent()));
}
Grantlee::OutputStream s(htmlWriter->stream());
t->render(&s, &c);
}
bool DefaultRendererPrivate::renderWithFactory(const QMetaObject *mo, const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
{
if (!mRendererFactory) {
return false;
}
for (auto r : mRendererFactory->renderersForPart(mo, msgPart)) {
if (r->render(msgPart, htmlWriter, this)) {
return true;
}
}
return false;
}
void DefaultRendererPrivate::renderFactory(const MessagePart::Ptr &msgPart, HtmlWriter *htmlWriter)
{
const QString className = QString::fromUtf8(msgPart->metaObject()->className());
if (isHiddenHint(msgPart)) {
QByteArray cid = msgPart->content()->contentID()->identifier();
auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
if (!cid.isEmpty() && mp) {
QString fileName = mp->temporaryFilePath();
QString href = QUrl::fromLocalFile(fileName).url();
htmlWriter->embedPart(cid, href);
}
}
if (renderWithFactory(msgPart, htmlWriter)) {
return;
}
if (className == QStringLiteral("MimeTreeParser::MessagePartList")) {
auto mp = msgPart.dynamicCast<MessagePartList>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::MimeMessagePart")) {
auto mp = msgPart.dynamicCast<MimeMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::EncapsulatedRfc822MessagePart")) {
auto mp = msgPart.dynamicCast<EncapsulatedRfc822MessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::HtmlMessagePart")) {
auto mp = msgPart.dynamicCast<HtmlMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::SignedMessagePart")) {
auto mp = msgPart.dynamicCast<SignedMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::EncryptedMessagePart")) {
auto mp = msgPart.dynamicCast<EncryptedMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::AlternativeMessagePart")) {
auto mp = msgPart.dynamicCast<AlternativeMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else if (className == QStringLiteral("MimeTreeParser::CertMessagePart")) {
auto mp = msgPart.dynamicCast<CertMessagePart>();
if (mp) {
render(mp, htmlWriter);
}
} else {
- qCWarning(MESSAGEVIEWER_LOG) << "We got a unkonwn classname, using default behaviour for "
+ qCWarning(MESSAGEVIEWER_LOG) << "We got a unknown classname, using default behaviour for "
<< className;
}
}
bool DefaultRendererPrivate::isHiddenHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
{
auto mp = msgPart.dynamicCast<MimeTreeParser::MessagePart>();
auto content = msgPart->content();
if (!mp || !content) {
return false;
}
if (mShowOnlyOneMimePart && mMsgPart.data() == msgPart->parentPart()) {
if (mMsgPart->subParts().at(0) == msgPart.data()) {
return false;
}
}
if (msgPart->nodeHelper()->isNodeDisplayedHidden(content)) {
return true;
}
const AttachmentStrategy *const as = mAttachmentStrategy;
const bool defaultHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
auto preferredMode = source()->preferredMode();
bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
QByteArray mediaType("text");
if (content->contentType(false) && !content->contentType()->mediaType().isEmpty()
&& !content->contentType()->subType().isEmpty()) {
mediaType = content->contentType()->mediaType();
}
const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
bool defaultAsIcon = true;
if (!mp->neverDisplayInline()) {
if (as) {
defaultAsIcon = as->defaultDisplay(content) == AttachmentStrategy::AsIcon;
}
}
// neither image nor text -> show as icon
if (!mp->isImage() && !isTextPart) {
defaultAsIcon = true;
}
bool hidden(false);
if (isTextPart) {
hidden = defaultHidden;
} else {
if (mp->isImage() && isHtmlPreferred
&& content->parent() && content->parent()->contentType(false)->subType() == "related") {
hidden = true;
} else {
hidden = defaultHidden && content->parent();
hidden |= defaultAsIcon && defaultHidden;
}
}
msgPart->nodeHelper()->setNodeDisplayedHidden(content, hidden);
return hidden;
}
MimeTreeParser::IconType DefaultRendererPrivate::displayHint(const MimeTreeParser::MessagePart::Ptr &msgPart)
{
auto mp = msgPart.dynamicCast<MimeTreeParser::TextMessagePart>();
auto content = msgPart->content();
if (!content || !mp) {
return MimeTreeParser::IconType::NoIcon;
}
const AttachmentStrategy *const as = mAttachmentStrategy;
const bool defaultDisplayHidden(as && as->defaultDisplay(content) == AttachmentStrategy::None);
const bool defaultDisplayInline(as && as->defaultDisplay(content) == AttachmentStrategy::Inline);
const bool defaultDisplayAsIcon(as && as->defaultDisplay(content) == AttachmentStrategy::AsIcon);
const bool showOnlyOneMimePart(mShowOnlyOneMimePart);
auto preferredMode = source()->preferredMode();
bool isHtmlPreferred = (preferredMode == MimeTreeParser::Util::Html) || (preferredMode == MimeTreeParser::Util::MultipartHtml);
QByteArray mediaType("text");
if (content->contentType(false) && !content->contentType()->mediaType().isEmpty()
&& !content->contentType()->subType().isEmpty()) {
mediaType = content->contentType()->mediaType();
}
const bool isTextPart = (mediaType == QByteArrayLiteral("text"));
bool defaultAsIcon = true;
if (!mp->neverDisplayInline()) {
if (as) {
defaultAsIcon = defaultDisplayAsIcon;
}
}
if (mp->isImage() && showOnlyOneMimePart && !mp->neverDisplayInline()) {
defaultAsIcon = false;
}
// neither image nor text -> show as icon
if (!mp->isImage() && !isTextPart) {
defaultAsIcon = true;
}
if (isTextPart) {
if (as && !defaultDisplayInline) {
return MimeTreeParser::IconExternal;
}
return MimeTreeParser::NoIcon;
} else {
if (mp->isImage() && isHtmlPreferred
&& content->parent() && content->parent()->contentType(false)->subType() == "related") {
return MimeTreeParser::IconInline;
}
if (defaultDisplayHidden && !showOnlyOneMimePart && content->parent()) {
return MimeTreeParser::IconInline;
}
if (defaultAsIcon) {
return MimeTreeParser::IconExternal;
} else if (mp->isImage()) {
return MimeTreeParser::IconInline;
}
}
return MimeTreeParser::NoIcon;
}
bool DefaultRendererPrivate::showEmoticons() const
{
return mShowEmoticons;
}
bool DefaultRendererPrivate::isPrinting() const
{
return mIsPrinting;
}
bool DefaultRendererPrivate::htmlLoadExternal() const
{
return mHtmlLoadExternal;
}
bool DefaultRendererPrivate::showExpandQuotesMark() const
{
return mShowExpandQuotesMark;
}
bool DefaultRendererPrivate::showOnlyOneMimePart() const
{
return mShowOnlyOneMimePart;
}
bool DefaultRendererPrivate::showSignatureDetails() const
{
return mShowSignatureDetails;
}
bool DefaultRendererPrivate::showEncryptionDetails() const
{
return mShowEncryptionDetails;
}
int DefaultRendererPrivate::levelQuote() const
{
return mLevelQuote;
}
DefaultRenderer::DefaultRenderer(CSSHelperBase *cssHelper)
: d(new DefaultRendererPrivate(cssHelper, MessagePartRendererFactory::instance()))
{
}
DefaultRenderer::~DefaultRenderer()
{
delete d;
}
void DefaultRenderer::setShowOnlyOneMimePart(bool onlyOneMimePart)
{
d->mShowOnlyOneMimePart = onlyOneMimePart;
}
void DefaultRenderer::setAttachmentStrategy(const AttachmentStrategy *strategy)
{
d->mAttachmentStrategy = strategy;
}
void DefaultRenderer::setShowEmoticons(bool showEmoticons)
{
d->mShowEmoticons = showEmoticons;
}
void DefaultRenderer::setIsPrinting(bool isPrinting)
{
d->mIsPrinting = isPrinting;
}
void DefaultRenderer::setShowExpandQuotesMark(bool showExpandQuotesMark)
{
d->mShowExpandQuotesMark = showExpandQuotesMark;
}
void DefaultRenderer::setShowEncryptionDetails(bool showEncryptionDetails)
{
d->mShowEncryptionDetails = showEncryptionDetails;
}
void DefaultRenderer::setShowSignatureDetails(bool showSignatureDetails)
{
d->mShowSignatureDetails = showSignatureDetails;
}
void DefaultRenderer::setLevelQuote(int levelQuote)
{
d->mLevelQuote = levelQuote;
}
void DefaultRenderer::setHtmlLoadExternal(bool htmlLoadExternal)
{
d->mHtmlLoadExternal = htmlLoadExternal;
}
-void DefaultRenderer::setCreateMessageHeader(std::function<QString(KMime::Message *)> createMessageHeader)
+void DefaultRenderer::setCreateMessageHeader(const std::function<QString(KMime::Message *)> &createMessageHeader)
{
d->mCreateMessageHeader = createMessageHeader;
}
QString renderTreeHelper(const MimeTreeParser::MessagePart::Ptr &messagePart, QString indent)
{
- const QString line
+ QString ret
= QStringLiteral("%1 * %3\n").arg(indent,
QString::fromUtf8(messagePart->metaObject()->className()));
- QString ret = line;
-
indent += QLatin1Char(' ');
- foreach (const auto &subPart, messagePart->subParts()) {
+ for (const auto &subPart : messagePart->subParts()) {
ret += renderTreeHelper(subPart, indent);
}
return ret;
}
void DefaultRenderer::render(const MimeTreeParser::MessagePart::Ptr &msgPart, HtmlWriter *writer)
{
qCDebug(MESSAGEVIEWER_LOG) << "MimeTreeParser structure:";
qCDebug(MESSAGEVIEWER_LOG) << qPrintable(renderTreeHelper(msgPart, QString()));
d->mMsgPart = msgPart;
d->renderFactory(d->mMsgPart, writer);
}
diff --git a/messageviewer/src/messagepartthemes/default/defaultrenderer.h b/messageviewer/src/messagepartthemes/default/defaultrenderer.h
index ec56ed03..4d68eb5f 100644
--- a/messageviewer/src/messagepartthemes/default/defaultrenderer.h
+++ b/messageviewer/src/messagepartthemes/default/defaultrenderer.h
@@ -1,63 +1,63 @@
/*
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.
*/
#ifndef MESSAGEVIEWER_DEFAULTRENDERER_H
#define MESSAGEVIEWER_DEFAULTRENDERER_H
#include <QSharedPointer>
#include <functional>
namespace KMime {
class Message;
}
namespace MimeTreeParser {
class MessagePart;
typedef QSharedPointer<MessagePart> MessagePartPtr;
}
namespace MessageViewer {
class DefaultRendererPrivate;
class HtmlWriter;
class AttachmentStrategy;
class CSSHelperBase;
class DefaultRenderer
{
public:
explicit DefaultRenderer(CSSHelperBase *cssHelder);
~DefaultRenderer();
void setShowOnlyOneMimePart(bool onlyOneMimePart);
void setAttachmentStrategy(const AttachmentStrategy *strategy);
void setShowEmoticons(bool showEmoticons);
void setIsPrinting(bool isPrinting);
void setShowExpandQuotesMark(bool showExpandQuotesMark);
void setShowSignatureDetails(bool showSignatureDetails);
void setLevelQuote(int levelQuote);
void setHtmlLoadExternal(bool htmlLoadExternal);
- void setCreateMessageHeader(std::function<QString(KMime::Message *)>);
+ void setCreateMessageHeader(const std::function<QString(KMime::Message *)>&);
void render(const MimeTreeParser::MessagePartPtr &msgPart, HtmlWriter *writer);
void setShowEncryptionDetails(bool showEncryptionDetails);
private:
DefaultRendererPrivate *d;
};
}
#endif //__MESSAGEVIEWER_MAILRENDERER_H
diff --git a/messageviewer/src/messagepartthemes/default/htmlblock.cpp b/messageviewer/src/messagepartthemes/default/htmlblock.cpp
index 7589a319..edf15f27 100644
--- a/messageviewer/src/messagepartthemes/default/htmlblock.cpp
+++ b/messageviewer/src/messagepartthemes/default/htmlblock.cpp
@@ -1,134 +1,133 @@
/*
Copyright (c) 2015 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.
*/
#include "htmlblock.h"
-#include "mimetreeparser_debug.h"
#include "interfaces/htmlwriter.h"
#include <KMime/Content>
#include <QApplication>
using namespace MessageViewer;
HTMLBlock::HTMLBlock()
{
}
HTMLBlock::~HTMLBlock()
{
}
QString HTMLBlock::dir() const
{
return QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr");
}
QString HTMLBlock::enter()
{
if (!entered) {
entered = true;
return enterString();
}
return QString();
}
QString HTMLBlock::exit()
{
if (entered) {
entered = false;
return exitString();
}
return QString();
}
AttachmentMarkBlock::AttachmentMarkBlock(HtmlWriter *writer, KMime::Content *node)
: mNode(node)
, mWriter(writer)
{
internalEnter();
}
AttachmentMarkBlock::~AttachmentMarkBlock()
{
internalExit();
}
void AttachmentMarkBlock::internalEnter()
{
if (mWriter) {
mWriter->write(enter());
}
}
void AttachmentMarkBlock::internalExit()
{
if (mWriter) {
mWriter->write(exit());
}
}
QString AttachmentMarkBlock::enterString() const
{
const QString index = mNode->index().toString();
return QStringLiteral("<a name=\"att%1\"></a>").arg(index)
+ QStringLiteral("<div id=\"attachmentDiv%1\">\n").arg(index);
}
QString AttachmentMarkBlock::exitString() const
{
return QStringLiteral("</div>");
}
RootBlock::RootBlock(HtmlWriter *writer)
: HTMLBlock()
, mWriter(writer)
{
internalEnter();
}
RootBlock::~RootBlock()
{
internalExit();
}
void RootBlock::internalEnter()
{
if (mWriter) {
mWriter->write(enter());
}
}
void RootBlock::internalExit()
{
if (mWriter) {
mWriter->write(exit());
}
}
QString RootBlock::enterString() const
{
return QStringLiteral("<div style=\"position: relative; word-wrap: break-word\">\n");
}
QString RootBlock::exitString() const
{
return QStringLiteral("</div>\n");
}
diff --git a/messageviewer/src/messagepartthemes/default/htmlblock.h b/messageviewer/src/messagepartthemes/default/htmlblock.h
index 1b08120c..ffd597d9 100644
--- a/messageviewer/src/messagepartthemes/default/htmlblock.h
+++ b/messageviewer/src/messagepartthemes/default/htmlblock.h
@@ -1,96 +1,96 @@
/*
Copyright (c) 2015 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.
*/
#ifndef MIMETREEPARSER_HTMLBLOCK_H
#define MIMETREEPARSER_HTMLBLOCK_H
#include "messageviewer_export.h"
#include <QString>
#include <QSharedPointer>
namespace KMime {
class Content;
}
namespace MessageViewer {
class HtmlWriter;
class MESSAGEVIEWER_EXPORT HTMLBlock
{
public:
typedef QSharedPointer<HTMLBlock> Ptr;
HTMLBlock();
virtual ~HTMLBlock();
Q_REQUIRED_RESULT QString enter();
Q_REQUIRED_RESULT QString exit();
protected:
QString dir() const;
virtual QString enterString() const = 0;
virtual QString exitString() const = 0;
private:
bool entered = false;
};
-// The attachment mark is a div that is placed around the attchment. It is used for drawing
+// The attachment mark is a div that is placed around the attachment. It is used for drawing
// a yellow border around the attachment when scrolling to it. When scrolling to it, the border
// color of the div is changed, see KMReaderWin::scrollToAttachment().
class MESSAGEVIEWER_EXPORT AttachmentMarkBlock : public HTMLBlock
{
public:
AttachmentMarkBlock(HtmlWriter *writer, KMime::Content *node);
- virtual ~AttachmentMarkBlock();
+ ~AttachmentMarkBlock() override;
protected:
QString enterString() const override;
QString exitString() const override;
private:
void internalEnter();
void internalExit();
KMime::Content *mNode = nullptr;
HtmlWriter *mWriter = nullptr;
};
// Make sure the whole content is relative, so that nothing is painted over the header
// if a malicious message uses absolute positioning.
// Also force word wrapping, which is useful for printing, see https://issues.kolab.org/issue3992.
class RootBlock : public HTMLBlock
{
public:
explicit RootBlock(HtmlWriter *writer);
- virtual ~RootBlock();
+ ~RootBlock() override;
protected:
QString enterString() const override;
QString exitString() const override;
private:
void internalEnter();
void internalExit();
HtmlWriter *mWriter = nullptr;
};
}
#endif //__MIMETREEPARSER_HTMLBLOCK_H
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
index f5e3f045..f27d5ff1 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
+++ b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory.cpp
@@ -1,191 +1,191 @@
/*
This file is part of KMail, the KDE mail client.
Copyright (c) 2017 Sandro Knauß <sknauss@kde.org>
KMail 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.
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.
*/
#include "messagepartrendererfactory.h"
#include "messagepartrendererfactory_p.h"
#include "messagepartrenderplugin.h"
#include "viewer/urlhandlermanager.h"
#include "messagepartrendererbase.h"
#include "messageviewer_debug.h"
#include "plugins/attachmentmessagepartrenderer.h"
#include "plugins/messagepartrenderer.h"
#include "plugins/textmessagepartrenderer.h"
#include <KPluginLoader>
#include <QJsonArray>
#include <QJsonObject>
#include <QMimeDatabase>
#include <QMimeType>
#include <algorithm>
using namespace MessageViewer;
void MessagePartRendererFactoryPrivate::setup()
{
if (m_renderers.isEmpty()) {
- initalize_builtin_renderers();
+ initialize_builtin_renderers();
loadPlugins();
}
Q_ASSERT(!m_renderers.isEmpty());
}
void MessagePartRendererFactoryPrivate::loadPlugins()
{
if (m_pluginSubdir.isEmpty()) {
return;
}
KPluginLoader::forEachPlugin(m_pluginSubdir, [this](const QString &path) {
QPluginLoader loader(path);
const auto pluginData = loader.metaData().value(QLatin1String("MetaData")).toObject().value(QLatin1String("renderer")).toArray();
if (pluginData.isEmpty()) {
qCWarning(MESSAGEVIEWER_LOG) << "Plugin" << path << "has no meta data.";
return;
}
auto plugin = qobject_cast<MessagePartRenderPlugin *>(loader.instance());
if (!plugin) {
qCWarning(MESSAGEVIEWER_LOG) << path << "is not a MessagePartRendererPlugin";
return;
}
MessagePartRendererBase *renderer = nullptr;
for (int i = 0; (renderer = plugin->renderer(i)) && i < pluginData.size(); ++i) {
const auto metaData = pluginData.at(i).toObject();
const auto type = metaData.value(QLatin1String("type")).toString().toUtf8();
if (type.isEmpty()) {
qCWarning(MESSAGEVIEWER_LOG) << path << "returned empty type specification for index" << i;
break;
}
const auto mimetype = metaData.value(QLatin1String("mimetype")).toString().toLower();
// priority should always be higher than the built-in ones, otherwise what's the point?
const auto priority = metaData.value(QLatin1String("priority")).toInt() + 100;
qCDebug(MESSAGEVIEWER_LOG) << "renderer plugin for " << type << mimetype << priority;
insert(type, renderer, mimetype, priority);
}
const Interface::BodyPartURLHandler *handler = nullptr;
for (int i = 0; (handler = plugin->urlHandler(i)); ++i) {
const auto metaData = pluginData.at(i).toObject();
const auto mimeType = metaData.value(QLatin1String("mimetype")).toString().toLower();
URLHandlerManager::instance()->registerHandler(handler, mimeType);
}
});
}
-void MessagePartRendererFactoryPrivate::initalize_builtin_renderers()
+void MessagePartRendererFactoryPrivate::initialize_builtin_renderers()
{
insert("MimeTreeParser::MessagePart", new MessagePartRenderer());
insert("MimeTreeParser::TextMessagePart", new TextMessagePartRenderer());
insert("MimeTreeParser::AttachmentMessagePart", new AttachmentMessagePartRenderer());
}
void MessagePartRendererFactoryPrivate::insert(const QByteArray &type, MessagePartRendererBase *renderer, const QString &mimeType, int priority)
{
if (type.isEmpty() || !renderer) {
return;
}
QMimeDatabase db;
const auto mt = db.mimeTypeForName(mimeType);
RendererInfo info;
- info.renderer = renderer;
+ info.renderer.reset(renderer);
info.mimeType = mt.isValid() ? mt.name() : mimeType;
info.priority = priority;
auto &v = m_renderers[type];
v.push_back(info);
}
MessagePartRendererFactory::MessagePartRendererFactory()
: d(new MessagePartRendererFactoryPrivate)
{
}
MessagePartRendererFactory::~MessagePartRendererFactory() = default;
void MessagePartRendererFactory::setPluginPath(const QString &subdir)
{
d->m_pluginSubdir = subdir;
}
MessagePartRendererFactory *MessagePartRendererFactory::instance()
{
static MessagePartRendererFactory s_instance;
return &s_instance;
}
QVector<MessagePartRendererBase *> MessagePartRendererFactory::renderersForPart(const QMetaObject *mo, const MimeTreeParser::MessagePartPtr &mp) const
{
d->setup();
const auto mtName = mp->content() ? QString::fromUtf8(mp->content()->contentType()->mimeType().toLower()) : QString();
QMimeDatabase db;
const auto mt = db.mimeTypeForName(mtName);
auto ancestors = mt.allAncestors();
if (mt.isValid() || !mtName.isEmpty()) {
ancestors.prepend(mt.isValid() ? mt.name() : mtName);
}
auto candidates = d->m_renderers.value(mo->className());
// remove candidates with a mimetype set that don't match the mimetype of the part
candidates.erase(std::remove_if(candidates.begin(), candidates.end(), [ancestors](const RendererInfo &info) {
if (info.mimeType.isEmpty()) {
return false;
}
return !ancestors.contains(info.mimeType);
}), candidates.end());
// sort most specific mimetpypes first
std::stable_sort(candidates.begin(), candidates.end(), [ancestors](const RendererInfo &lhs, const RendererInfo &rhs) {
if (lhs.mimeType == rhs.mimeType) {
return lhs.priority > rhs.priority;
}
if (lhs.mimeType.isEmpty()) {
return false;
}
if (rhs.mimeType.isEmpty()) {
return true;
}
return ancestors.indexOf(lhs.mimeType) < ancestors.indexOf(rhs.mimeType);
});
QVector<MessagePartRendererBase *> r;
for (const auto &candidate : candidates) {
- r.push_back(candidate.renderer);
+ r.push_back(candidate.renderer.data());
}
return r;
}
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
index 74e4b4b0..75b198c1 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
+++ b/messageviewer/src/messagepartthemes/default/messagepartrendererfactory_p.h
@@ -1,61 +1,63 @@
/*
Copyright (c) 2017 Sandro Knauß <sknauss@kde.org>
KMail 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.
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.
*/
#ifndef MESSAGEVIEWER_MESSAGEPARTRENDERERFACTORY_P_H
#define MESSAGEVIEWER_MESSAGEPARTRENDERERFACTORY_P_H
#include <QByteArray>
#include <QHash>
+#include <QScopedPointer>
#include <QString>
#include <vector>
namespace MessageViewer {
class MessagePartRendererBase;
-struct RendererInfo {
- MessagePartRendererBase *renderer;
+struct RendererInfo
+{
+ QSharedPointer<MessagePartRendererBase> renderer;
QString mimeType;
int priority;
};
class MessagePartRendererFactoryPrivate
{
public:
void setup();
void loadPlugins();
- void initalize_builtin_renderers();
+ void initialize_builtin_renderers();
void insert(const QByteArray &type, MessagePartRendererBase *formatter, const QString &mimeType = QString(), int priority = 0);
QHash<QByteArray, std::vector<RendererInfo> > m_renderers;
QString m_pluginSubdir = QStringLiteral("messageviewer/bodypartformatter");
};
}
#endif
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp
index 48271205..124653af 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp
+++ b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.cpp
@@ -1,167 +1,167 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "messagepartrenderermanager.h"
#include "messageviewer_debug.h"
#include <KIconLoader>
#include <QStandardPaths>
#include <GrantleeTheme/GrantleeKi18nLocalizer>
#include <GrantleeTheme/QtResourceTemplateLoader>
#include <GrantleeTheme/GrantleeThemeEngine>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
#include <QGpgME/Protocol>
#include <grantlee/context.h>
#include <grantlee/engine.h>
#include <grantlee/metatype.h>
#include <grantlee/templateloader.h>
#include <QGuiApplication>
Q_DECLARE_METATYPE(GpgME::DecryptionResult::Recipient)
Q_DECLARE_METATYPE(const QGpgME::Protocol *)
Q_DECLARE_METATYPE(GpgME::Key)
// Read-only introspection of GpgME::DecryptionResult::Recipient object.
GRANTLEE_BEGIN_LOOKUP(GpgME::DecryptionResult::Recipient)
if (property == QStringLiteral("keyID")) {
return QString::fromLatin1(object.keyID());
}
GRANTLEE_END_LOOKUP
// Read-only introspection of QGpgME::Protocol object.
namespace Grantlee {
template<>
inline QVariant TypeAccessor<const QGpgME::Protocol *>::lookUp(const QGpgME::Protocol *const object, const QString &property)
{
if (property == QStringLiteral("name")) {
return object->name();
} else if (property == QStringLiteral("displayName")) {
return object->displayName();
}
return QVariant();
}
}
// Read-only introspection of std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> object.
namespace Grantlee {
template<>
inline QVariant TypeAccessor<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> &>::lookUp(std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> const &object, const QString &property)
{
if (property == QStringLiteral("keyID")) {
return QString::fromLatin1(object.first.keyID());
}
if (property == QStringLiteral("id")) {
return QString::fromUtf8(object.second.userID(0).id());
}
if (property == QStringLiteral("mainID")) {
return QString::fromLatin1(object.second.keyID());
}
return QVariant();
}
}
namespace MessageViewer {
class GlobalContext : public QObject
{
Q_OBJECT
Q_PROPERTY(QString dir READ layoutDirection CONSTANT)
Q_PROPERTY(int iconSize READ iconSize CONSTANT)
public:
explicit GlobalContext(QObject *parent) : QObject(parent)
{
}
QString layoutDirection() const
{
return QGuiApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr");
}
int iconSize() const
{
return KIconLoader::global()->currentSize(KIconLoader::Desktop);
}
};
}
using namespace MessageViewer;
MessagePartRendererManager::MessagePartRendererManager(QObject *parent)
: QObject(parent)
, m_engine(nullptr)
, m_globalContext(new GlobalContext(this))
{
initializeRenderer();
}
MessagePartRendererManager::~MessagePartRendererManager()
{
delete m_engine;
}
MessagePartRendererManager *MessagePartRendererManager::self()
{
static MessagePartRendererManager s_self;
return &s_self;
}
void MessagePartRendererManager::initializeRenderer()
{
Grantlee::registerMetaType<GpgME::DecryptionResult::Recipient>();
Grantlee::registerMetaType<const QGpgME::Protocol *>();
Grantlee::registerMetaType<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> >();
m_engine = new GrantleeTheme::Engine;
- foreach (const auto &p, QCoreApplication::libraryPaths()) {
+ for (const auto &p : QCoreApplication::libraryPaths()) {
m_engine->addPluginPath(p + QStringLiteral("/messageviewer"));
}
m_engine->addDefaultLibrary(QStringLiteral("messageviewer_grantlee_extension"));
m_engine->localizer()->setApplicationDomain(QByteArrayLiteral("libmessageviewer"));
auto loader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(
new GrantleeTheme::QtResourceTemplateLoader());
m_engine->addTemplateLoader(loader);
}
Grantlee::Template MessagePartRendererManager::loadByName(const QString &name)
{
Grantlee::Template t = m_engine->loadByName(name);
if (t->error()) {
qCWarning(MESSAGEVIEWER_LOG) << t->errorString()
<<
". Searched in subdir mimetreeparser/themes/default in these locations"
<< QStandardPaths::standardLocations(
QStandardPaths::GenericDataLocation);
}
return t;
}
Grantlee::Context MessagePartRendererManager::createContext()
{
Grantlee::Context c;
m_engine->localizer()->setApplicationDomain(QByteArrayLiteral("libmessageviewer"));
c.setLocalizer(m_engine->localizer());
c.insert(QStringLiteral("global"), m_globalContext);
return c;
}
#include "messagepartrenderermanager.moc"
diff --git a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.h b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.h
index e78d039d..7e16929c 100644
--- a/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.h
+++ b/messageviewer/src/messagepartthemes/default/messagepartrenderermanager.h
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MESSAGEPARTRENDERERMANAGER_H
#define MESSAGEPARTRENDERERMANAGER_H
#include "messageviewer_export.h"
#include <grantlee/template.h>
#include <QMetaType>
#include <QObject>
#include <functional>
namespace GrantleeTheme {
class Engine;
}
namespace MessageViewer {
class GlobalContext;
typedef std::function<void (Grantlee::OutputStream *stream)> GrantleeCallback;
class MESSAGEVIEWER_EXPORT MessagePartRendererManager : public QObject
{
Q_OBJECT
public:
explicit MessagePartRendererManager(QObject *parent = nullptr);
~MessagePartRendererManager();
static MessagePartRendererManager *self();
Grantlee::Template loadByName(const QString &name);
Grantlee::Context createContext();
private:
void initializeRenderer();
GrantleeTheme::Engine *m_engine = nullptr;
GlobalContext *m_globalContext;
};
}
Q_DECLARE_METATYPE(MessageViewer::GrantleeCallback)
#endif // MESSAGEPARTRENDERERMANAGER_H
diff --git a/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp b/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp
index e577b49a..da6e8329 100644
--- a/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp
+++ b/messageviewer/src/messagepartthemes/default/plugins/quotehtml.cpp
@@ -1,308 +1,310 @@
/*
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.
*/
#include "quotehtml.h"
#include "utils/iconnamecache.h"
#include "viewer/csshelperbase.h"
#include <MessageViewer/HtmlWriter>
#include <MessageViewer/MessagePartRendererBase>
#include <KTextToHTML>
#include <QSharedPointer>
/** Check if the newline at position @p newLinePos in string @p s
seems to separate two paragraphs (important for correct BiDi
behavior, but is heuristic because paragraphs are not
well-defined) */
// Guesstimate if the newline at newLinePos actually separates paragraphs in the text s
// We use several heuristics:
// 1. If newLinePos points after or before (=at the very beginning of) text, it is not between paragraphs
// 2. If the previous line was longer than the wrap size, we want to consider it a paragraph on its own
// (some clients, notably Outlook, send each para as a line in the plain-text version).
// 3. Otherwise, we check if the newline could have been inserted for wrapping around; if this
// was the case, then the previous line will be shorter than the wrap size (which we already
// know because of item 2 above), but adding the first word from the next line will make it
// longer than the wrap size.
-bool looksLikeParaBreak(const QString &s, unsigned int newLinePos)
+bool looksLikeParaBreak(const QString &s, int newLinePos)
{
- const unsigned int WRAP_COL = 78;
+ const int WRAP_COL = 78;
- unsigned int length = s.length();
+ int length = s.length();
// 1. Is newLinePos at an end of the text?
if (newLinePos >= length - 1 || newLinePos == 0) {
return false;
}
// 2. Is the previous line really a paragraph -- longer than the wrap size?
// First char of prev line -- works also for first line
- unsigned prevStart = s.lastIndexOf(QLatin1Char('\n'), newLinePos - 1) + 1;
- unsigned prevLineLength = newLinePos - prevStart;
+ int prevStart = s.lastIndexOf(QLatin1Char('\n'), newLinePos - 1) + 1;
+ int prevLineLength = newLinePos - prevStart;
if (prevLineLength > WRAP_COL) {
return true;
}
// find next line to delimit search for first word
- unsigned int nextStart = newLinePos + 1;
+ int nextStart = newLinePos + 1;
int nextEnd = s.indexOf(QLatin1Char('\n'), nextStart);
if (nextEnd == -1) {
nextEnd = length;
}
QString nextLine = s.mid(nextStart, nextEnd - nextStart);
length = nextLine.length();
// search for first word in next line
- unsigned int wordStart;
+ int wordStart;
bool found = false;
for (wordStart = 0; !found && wordStart < length; wordStart++) {
switch (nextLine[wordStart].toLatin1()) {
case '>':
case '|':
case ' ': // spaces, tabs and quote markers don't count
case '\t':
case '\r':
break;
default:
found = true;
break;
}
} /* for() */
if (!found) {
// next line is essentially empty, it seems -- empty lines are
// para separators
return true;
}
//Find end of first word.
//Note: flowText (in kmmessage.cpp) separates words for wrap by
//spaces only. This should be consistent, which calls for some
//refactoring.
int wordEnd = nextLine.indexOf(QLatin1Char(' '), wordStart);
if (wordEnd == (-1)) {
wordEnd = length;
}
int wordLength = wordEnd - wordStart;
// 3. If adding a space and the first word to the prev line don't
// make it reach the wrap column, then the break was probably
// meaningful
return prevLineLength + wordLength + 1 < WRAP_COL;
}
void quotedHTML(const QString &s, MessageViewer::RenderContext *context, MessageViewer::HtmlWriter *htmlWriter)
{
const auto cssHelper = context->cssHelper();
Q_ASSERT(cssHelper);
- KTextToHTML::Options convertFlags = KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText;
+ KTextToHTML::Options convertFlags = KTextToHTML::PreserveSpaces | KTextToHTML::HighlightText | KTextToHTML::ConvertPhoneNumbers;
if (context->showEmoticons()) {
convertFlags |= KTextToHTML::ReplaceSmileys;
}
const QString normalStartTag = cssHelper->nonQuotedFontTag();
QString quoteFontTag[3];
QString deepQuoteFontTag[3];
for (int i = 0; i < 3; ++i) {
quoteFontTag[i] = cssHelper->quoteFontTag(i);
deepQuoteFontTag[i] = cssHelper->quoteFontTag(i + 3);
}
const QString normalEndTag = QStringLiteral("</div>");
const QString quoteEnd = QStringLiteral("</div>");
- const unsigned int length = s.length();
+ const int length = s.length();
bool paraIsRTL = false;
bool startNewPara = true;
- unsigned int pos, beg;
+ int pos, beg;
// skip leading empty lines
for (pos = 0; pos < length && s[pos] <= QLatin1Char(' '); ++pos) {
}
while (pos > 0 && (s[pos - 1] == QLatin1Char(' ') || s[pos - 1] == QLatin1Char('\t'))) {
pos--;
}
beg = pos;
int currQuoteLevel = -2; // -2 == no previous lines
bool curHidden = false; // no hide any block
QString collapseIconPath;
QString expandIconPath;
if (context->showExpandQuotesMark()) {
collapseIconPath = MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral(
"quotecollapse.png"));
expandIconPath
= MessageViewer::IconNameCache::instance()->iconPathFromLocal(QStringLiteral(
"quoteexpand.png"));
}
int previousQuoteDepth = -1;
while (beg < length) {
/* search next occurrence of '\n' */
pos = s.indexOf(QLatin1Char('\n'), beg, Qt::CaseInsensitive);
- if (pos == (unsigned int)(-1)) {
+ if (pos == -1) {
pos = length;
}
QString line(s.mid(beg, pos - beg));
beg = pos + 1;
bool foundQuote = false;
/* calculate line's current quoting depth */
int actQuoteLevel = -1;
const int numberOfCaracters(line.length());
int quoteLength = 0;
for (int p = 0; p < numberOfCaracters; ++p) {
switch (line[p].toLatin1()) {
case '>':
case '|':
- actQuoteLevel++;
- quoteLength = p;
- foundQuote = true;
+ if (p == 0 || foundQuote) {
+ actQuoteLevel++;
+ quoteLength = p;
+ foundQuote = true;
+ }
break;
case ' ': // spaces and tabs are allowed between the quote markers
case '\t':
case '\r':
quoteLength = p;
break;
default: // stop quoting depth calculation
p = numberOfCaracters;
break;
}
} /* for() */
if (!foundQuote) {
quoteLength = 0;
}
bool actHidden = false;
// This quoted line needs be hidden
if (context->showExpandQuotesMark() && context->levelQuote() >= 0
&& context->levelQuote() <= actQuoteLevel) {
actHidden = true;
}
if (actQuoteLevel != currQuoteLevel) {
/* finish last quotelevel */
if (currQuoteLevel == -1) {
htmlWriter->write(normalEndTag);
} else if (currQuoteLevel >= 0 && !curHidden) {
htmlWriter->write(quoteEnd);
}
//Close blockquote
if (previousQuoteDepth > actQuoteLevel) {
htmlWriter->write(cssHelper->addEndBlockQuote(previousQuoteDepth - actQuoteLevel));
}
/* start new quotelevel */
if (actQuoteLevel == -1) {
htmlWriter->write(normalStartTag);
} else {
if (context->showExpandQuotesMark()) {
// Add blockquote
if (previousQuoteDepth < actQuoteLevel) {
htmlWriter->write(cssHelper->addStartBlockQuote(actQuoteLevel - previousQuoteDepth));
}
if (actHidden) {
//only show the QuoteMark when is the first line of the level hidden
if (!curHidden) {
//Expand all quotes
- htmlWriter->write(QLatin1String("<div class=\"quotelevelmark\" >"));
+ htmlWriter->write(QStringLiteral("<div class=\"quotelevelmark\" >"));
htmlWriter->write(QStringLiteral("<a href=\"kmail:levelquote?%1 \">"
"<img src=\"%2\"/></a>")
.arg(-1)
.arg(expandIconPath));
- htmlWriter->write(QLatin1String("</div><br/>"));
+ htmlWriter->write(QStringLiteral("</div><br/>"));
}
} else {
- htmlWriter->write(QLatin1String("<div class=\"quotelevelmark\" >"));
+ htmlWriter->write(QStringLiteral("<div class=\"quotelevelmark\" >"));
htmlWriter->write(QStringLiteral("<a href=\"kmail:levelquote?%1 \">"
"<img src=\"%2\"/></a>")
.arg(actQuoteLevel)
.arg(collapseIconPath));
- htmlWriter->write(QLatin1String("</div>"));
+ htmlWriter->write(QStringLiteral("</div>"));
if (actQuoteLevel < 3) {
htmlWriter->write(quoteFontTag[actQuoteLevel]);
} else {
htmlWriter->write(deepQuoteFontTag[actQuoteLevel % 3]);
}
}
} else {
// Add blockquote
if (previousQuoteDepth < actQuoteLevel) {
htmlWriter->write(cssHelper->addStartBlockQuote(actQuoteLevel - previousQuoteDepth));
}
if (actQuoteLevel < 3) {
htmlWriter->write(quoteFontTag[actQuoteLevel]);
} else {
htmlWriter->write(deepQuoteFontTag[actQuoteLevel % 3]);
}
}
}
currQuoteLevel = actQuoteLevel;
}
curHidden = actHidden;
if (!actHidden) {
// don't write empty <div ...></div> blocks (they have zero height)
// ignore ^M DOS linebreaks
if (!line.remove(QLatin1Char('\015')).isEmpty()) {
if (startNewPara) {
paraIsRTL = line.isRightToLeft();
}
htmlWriter->write(QStringLiteral("<div dir=\"%1\">")
.arg(paraIsRTL ? QStringLiteral("rtl") : QStringLiteral("ltr")));
// if quoteLengh == 0 && foundQuote => a simple quote
if (foundQuote) {
quoteLength++;
const int rightString = (line.length()) - quoteLength;
if (rightString > 0) {
htmlWriter->write(QStringLiteral("<span class=\"quotemarks\">%1</span>")
.arg(line.left(quoteLength)));
htmlWriter->write(QStringLiteral("<font color=\"%1\">")
.arg(cssHelper->quoteColorName(actQuoteLevel)));
htmlWriter->write(KTextToHTML::convertToHtml(line.right(rightString), convertFlags, 4096, 512));
htmlWriter->write(QStringLiteral("</font>"));
} else {
htmlWriter->write(QStringLiteral("<span class=\"quotemarksemptyline\">%1</span>")
.arg(line.left(quoteLength)));
}
} else {
htmlWriter->write(KTextToHTML::convertToHtml(line, convertFlags, 4096, 512));
}
- htmlWriter->write(QLatin1String("</div>"));
+ htmlWriter->write(QStringLiteral("</div>"));
startNewPara = looksLikeParaBreak(s, pos);
} else {
- htmlWriter->write(QLatin1String("<br/>"));
+ htmlWriter->write(QStringLiteral("<br/>"));
// after an empty line, always start a new paragraph
startNewPara = true;
}
}
previousQuoteDepth = actQuoteLevel;
} /* while() */
/* really finish the last quotelevel */
if (currQuoteLevel == -1) {
htmlWriter->write(normalEndTag);
} else {
htmlWriter->write(quoteEnd + cssHelper->addEndBlockQuote(currQuoteLevel + 1));
}
}
diff --git a/messageviewer/src/viewerplugins/viewerplugin.cpp b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.cpp
similarity index 61%
copy from messageviewer/src/viewerplugins/viewerplugin.cpp
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.cpp
index 36fb75eb..0db610e6 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.cpp
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.cpp
@@ -1,63 +1,46 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "viewerplugin.h"
+#include "messageviewerconfiguresettingsplugin.h"
using namespace MessageViewer;
-
-class MessageViewer::ViewerPluginPrivate
+class MessageViewer::MessageViewerConfigureSettingsPluginPrivate
{
public:
- ViewerPluginPrivate()
+ MessageViewerConfigureSettingsPluginPrivate()
{
}
-
- bool mEnabled = false;
};
-ViewerPlugin::ViewerPlugin(QObject *parent)
+MessageViewerConfigureSettingsPlugin::MessageViewerConfigureSettingsPlugin(QObject *parent)
: QObject(parent)
- , d(new MessageViewer::ViewerPluginPrivate)
+ , d(new MessageViewer::MessageViewerConfigureSettingsPluginPrivate)
{
}
-ViewerPlugin::~ViewerPlugin()
+MessageViewerConfigureSettingsPlugin::~MessageViewerConfigureSettingsPlugin()
{
delete d;
}
-void ViewerPlugin::showConfigureDialog(QWidget *parent)
+void MessageViewerConfigureSettingsPlugin::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
-}
-
-bool ViewerPlugin::hasConfigureDialog() const
-{
- return false;
-}
-
-void ViewerPlugin::setIsEnabled(bool enabled)
-{
- d->mEnabled = enabled;
-}
-
-bool ViewerPlugin::isEnabled() const
-{
- return d->mEnabled;
+ //Reimplement
}
diff --git a/messageviewer/src/viewerplugins/viewerplugin.h b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.h
similarity index 59%
copy from messageviewer/src/viewerplugins/viewerplugin.h
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.h
index 80728b77..2470cd88 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.h
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingsplugin.h
@@ -1,52 +1,47 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef VIEWERPLUGIN_H
-#define VIEWERPLUGIN_H
+#ifndef MESSAGEVIEWERCONFIGURESETTINGSPLUGIN_H
+#define MESSAGEVIEWERCONFIGURESETTINGSPLUGIN_H
#include <QObject>
-#include "messageviewer_export.h"
-class KActionCollection;
+#include "messageviewer_export.h"
namespace MessageViewer {
-class ViewerPluginPrivate;
-class ViewerPluginInterface;
-class MESSAGEVIEWER_EXPORT ViewerPlugin : public QObject
+class MessageViewerConfigureSettingsPluginPrivate;
+class MESSAGEVIEWER_EXPORT MessageViewerConfigureSettingsPlugin : public QObject
{
Q_OBJECT
public:
- explicit ViewerPlugin(QObject *parent = nullptr);
- ~ViewerPlugin();
+ explicit MessageViewerConfigureSettingsPlugin(QObject *parent = nullptr);
+ ~MessageViewerConfigureSettingsPlugin();
- Q_REQUIRED_RESULT virtual MessageViewer::ViewerPluginInterface *createView(QWidget *parent, KActionCollection *ac) = 0;
- Q_REQUIRED_RESULT virtual QString viewerPluginName() const = 0;
- virtual void showConfigureDialog(QWidget *parent = nullptr);
- Q_REQUIRED_RESULT virtual bool hasConfigureDialog() const;
+ virtual void showConfigureDialog(QWidget *parent);
- void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
+ void setIsEnabled(bool enabled);
Q_SIGNALS:
void configChanged();
private:
- ViewerPluginPrivate *const d;
+ MessageViewerConfigureSettingsPluginPrivate *const d;
};
}
-#endif // VIEWERPLUGIN_H
+#endif // MESSAGEVIEWERCONFIGURESETTINGSPLUGIN_H
diff --git a/messageviewer/src/header/headerstylepluginmanager.cpp b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.cpp
similarity index 54%
copy from messageviewer/src/header/headerstylepluginmanager.cpp
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.cpp
index 75064ea3..2895af04 100644
--- a/messageviewer/src/header/headerstylepluginmanager.cpp
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.cpp
@@ -1,243 +1,225 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "headerstylepluginmanager.h"
-#include "headerstyleplugin.h"
+#include "messageviewerconfiguresettingspluginmanager.h"
+#include "messageviewerconfiguresettingsplugin.h"
#include "messageviewer_debug.h"
#include <KPluginFactory>
#include <KPluginLoader>
#include <kpluginmetadata.h>
#include <QFileInfo>
#include <QSet>
using namespace MessageViewer;
-class HeaderStylePluginInfo
+class ConfigureSettingsPluginInfo
{
public:
- HeaderStylePluginInfo()
+ ConfigureSettingsPluginInfo()
{
}
PimCommon::PluginUtilData pluginData;
QString metaDataFileNameBaseName;
QString metaDataFileName;
- MessageViewer::HeaderStylePlugin *plugin = nullptr;
- bool isEnabled = false;
+ MessageViewer::MessageViewerConfigureSettingsPlugin *plugin = nullptr;
};
-class MessageViewer::HeaderStylePluginManagerPrivate
+class MessageViewer::MessageViewerConfigureSettingsPluginManagerPrivate
{
public:
- HeaderStylePluginManagerPrivate(HeaderStylePluginManager *qq)
+ MessageViewerConfigureSettingsPluginManagerPrivate(MessageViewerConfigureSettingsPluginManager *qq)
: q(qq)
{
}
- QVector<MessageViewer::HeaderStylePlugin *> pluginsList() const;
+ QVector<MessageViewer::MessageViewerConfigureSettingsPlugin *> pluginsList() const;
QVector<PimCommon::PluginUtilData> pluginDataList() const;
void initializePluginList();
- void loadPlugin(HeaderStylePluginInfo *item);
+ void loadPlugin(ConfigureSettingsPluginInfo *item);
QString configGroupName() const;
QString configPrefixSettingKey() const;
- MessageViewer::HeaderStylePlugin *pluginFromIdentifier(const QString &id);
+ MessageViewerConfigureSettingsPlugin *pluginFromIdentifier(const QString &id);
private:
QVector<PimCommon::PluginUtilData> mPluginDataList;
- QVector<HeaderStylePluginInfo> mPluginList;
- HeaderStylePluginManager *q;
+ QVector<ConfigureSettingsPluginInfo> mPluginList;
+ MessageViewerConfigureSettingsPluginManager *q;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
-QVector<PimCommon::PluginUtilData> HeaderStylePluginManagerPrivate::pluginDataList() const
+QVector<PimCommon::PluginUtilData> MessageViewerConfigureSettingsPluginManagerPrivate::pluginDataList() const
{
return mPluginDataList;
}
-QString HeaderStylePluginManagerPrivate::configGroupName() const
+QString MessageViewerConfigureSettingsPluginManagerPrivate::configGroupName() const
{
- return QStringLiteral("HeaderStylePlugins");
+ return QStringLiteral("MessageViewerConfigureSettingsPlugins");
}
-QString HeaderStylePluginManagerPrivate::configPrefixSettingKey() const
+QString MessageViewerConfigureSettingsPluginManagerPrivate::configPrefixSettingKey() const
{
- return QStringLiteral("PluginHeaderStyle");
+ return QStringLiteral("MessageViewerConfigureSettingsPlugin");
}
-void HeaderStylePluginManagerPrivate::initializePluginList()
+void MessageViewerConfigureSettingsPluginManagerPrivate::initializePluginList()
{
const QVector<KPluginMetaData> plugins
= KPluginLoader::findPlugins(QStringLiteral("messageviewer"), [](
const KPluginMetaData &md) {
- return md.serviceTypes().contains(QLatin1String("MessageViewerHeaderStyle/Plugin"));
+ return md.serviceTypes().contains(QLatin1String("MessageViewerConfigureSettingsPlugin/Plugin"));
});
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(
configGroupName(), configPrefixSettingKey());
QSet<QString> unique;
QVector<int> listOrder;
while (i.hasPrevious()) {
- HeaderStylePluginInfo info;
+ ConfigureSettingsPluginInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
- const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first,
- pair.second,
- info.pluginData.mEnableByDefault,
- info.pluginData.mIdentifier);
- info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
const QString version = data.version();
if (pluginVersion() == version) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
const QVariant p
- = data.rawData().value(QStringLiteral("X-KDE-MessageViewer-Header-Order")).toVariant();
+ = data.rawData().value(QStringLiteral("X-KDE-MessageViewer-Configure-Order")).toVariant();
int order = -1;
if (p.isValid()) {
order = p.toInt();
}
int pos = 0;
for (; pos < listOrder.count(); ++pos) {
if (listOrder.at(pos) > order) {
pos--;
break;
}
}
pos = qMax(0, pos);
listOrder.insert(pos, order);
info.plugin = nullptr;
mPluginList.insert(pos, info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGEVIEWER_LOG) << "Plugin " << data.name()
<<
" doesn't have correction plugin version. It will not be loaded.";
}
}
- QVector<HeaderStylePluginInfo>::iterator end(mPluginList.end());
- for (QVector<HeaderStylePluginInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
+ QVector<ConfigureSettingsPluginInfo>::iterator end(mPluginList.end());
+ for (QVector<ConfigureSettingsPluginInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
}
-QVector<MessageViewer::HeaderStylePlugin *> HeaderStylePluginManagerPrivate::pluginsList() const
+QVector<MessageViewer::MessageViewerConfigureSettingsPlugin *> MessageViewerConfigureSettingsPluginManagerPrivate::pluginsList() const
{
- QVector<MessageViewer::HeaderStylePlugin *> lst;
- QVector<HeaderStylePluginInfo>::ConstIterator end(mPluginList.constEnd());
- for (QVector<HeaderStylePluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end;
+ QVector<MessageViewer::MessageViewerConfigureSettingsPlugin *> lst;
+ QVector<ConfigureSettingsPluginInfo>::ConstIterator end(mPluginList.constEnd());
+ for (QVector<ConfigureSettingsPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end;
++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
-void HeaderStylePluginManagerPrivate::loadPlugin(HeaderStylePluginInfo *item)
+void MessageViewerConfigureSettingsPluginManagerPrivate::loadPlugin(ConfigureSettingsPluginInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
- item->plugin = pluginLoader.factory()->create<MessageViewer::HeaderStylePlugin>(q,
- QVariantList()
- << item->metaDataFileNameBaseName);
- item->plugin->setIsEnabled(item->isEnabled);
- item->pluginData.mHasConfigureDialog = false;
+ item->plugin = pluginLoader.factory()->create<MessageViewer::MessageViewerConfigureSettingsPlugin>(q,
+ QVariantList()
+ << item->metaDataFileNameBaseName);
+ //By default it's true
+ item->pluginData.mHasConfigureDialog = true;
mPluginDataList.append(item->pluginData);
}
}
-MessageViewer::HeaderStylePlugin *HeaderStylePluginManagerPrivate::pluginFromIdentifier(
- const QString &id)
+MessageViewerConfigureSettingsPlugin *MessageViewerConfigureSettingsPluginManagerPrivate::pluginFromIdentifier(const QString &id)
{
- QVector<HeaderStylePluginInfo>::ConstIterator end(mPluginList.constEnd());
- for (QVector<HeaderStylePluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end;
- ++it) {
+ QVector<ConfigureSettingsPluginInfo>::ConstIterator end(mPluginList.constEnd());
+ for (QVector<ConfigureSettingsPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
-HeaderStylePluginManager *HeaderStylePluginManager::self()
+MessageViewerConfigureSettingsPluginManager *MessageViewerConfigureSettingsPluginManager::self()
{
- static HeaderStylePluginManager s_self;
+ static MessageViewerConfigureSettingsPluginManager s_self;
return &s_self;
}
-HeaderStylePluginManager::HeaderStylePluginManager(QObject *parent)
+MessageViewerConfigureSettingsPluginManager::MessageViewerConfigureSettingsPluginManager(QObject *parent)
: QObject(parent)
- , d(new MessageViewer::HeaderStylePluginManagerPrivate(this))
+ , d(new MessageViewer::MessageViewerConfigureSettingsPluginManagerPrivate(this))
{
d->initializePluginList();
}
-HeaderStylePluginManager::~HeaderStylePluginManager()
+MessageViewerConfigureSettingsPluginManager::~MessageViewerConfigureSettingsPluginManager()
{
delete d;
}
-QVector<MessageViewer::HeaderStylePlugin *> HeaderStylePluginManager::pluginsList() const
+QVector<MessageViewer::MessageViewerConfigureSettingsPlugin *> MessageViewerConfigureSettingsPluginManager::pluginsList() const
{
return d->pluginsList();
}
-QStringList HeaderStylePluginManager::pluginListName() const
-{
- QStringList lst;
- lst.reserve(d->pluginsList().count());
- for (MessageViewer::HeaderStylePlugin *plugin : d->pluginsList()) {
- lst << plugin->name();
- }
- return lst;
-}
-
-QString HeaderStylePluginManager::configGroupName() const
+QString MessageViewerConfigureSettingsPluginManager::configGroupName() const
{
return d->configGroupName();
}
-QString HeaderStylePluginManager::configPrefixSettingKey() const
+QString MessageViewerConfigureSettingsPluginManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
-QVector<PimCommon::PluginUtilData> HeaderStylePluginManager::pluginsDataList() const
+QVector<PimCommon::PluginUtilData> MessageViewerConfigureSettingsPluginManager::pluginsDataList() const
{
return d->pluginDataList();
}
-MessageViewer::HeaderStylePlugin *HeaderStylePluginManager::pluginFromIdentifier(const QString &id)
+MessageViewerConfigureSettingsPlugin *MessageViewerConfigureSettingsPluginManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messageviewer/src/header/headerstylepluginmanager.h b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.h
similarity index 56%
copy from messageviewer/src/header/headerstylepluginmanager.h
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.h
index 985c650b..af30f474 100644
--- a/messageviewer/src/header/headerstylepluginmanager.h
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginmanager.h
@@ -1,49 +1,48 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef HEADERSTYLEPLUGINMANAGER_H
-#define HEADERSTYLEPLUGINMANAGER_H
+#ifndef MESSAGEVIEWERCONFIGURESETTINGSPLUGINMANAGER_H
+#define MESSAGEVIEWERCONFIGURESETTINGSPLUGINMANAGER_H
#include <QObject>
#include "messageviewer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageViewer {
-class HeaderStylePlugin;
-class HeaderStylePluginManagerPrivate;
-class MESSAGEVIEWER_EXPORT HeaderStylePluginManager : public QObject
+class MessageViewerConfigureSettingsPlugin;
+class MessageViewerConfigureSettingsPluginManagerPrivate;
+class MESSAGEVIEWER_EXPORT MessageViewerConfigureSettingsPluginManager : public QObject
{
Q_OBJECT
public:
- static HeaderStylePluginManager *self();
+ static MessageViewerConfigureSettingsPluginManager *self();
- explicit HeaderStylePluginManager(QObject *parent = nullptr);
- ~HeaderStylePluginManager();
+ explicit MessageViewerConfigureSettingsPluginManager(QObject *parent = nullptr);
+ ~MessageViewerConfigureSettingsPluginManager();
- Q_REQUIRED_RESULT QVector<MessageViewer::HeaderStylePlugin *> pluginsList() const;
- Q_REQUIRED_RESULT QStringList pluginListName() const;
+ Q_REQUIRED_RESULT QVector<MessageViewer::MessageViewerConfigureSettingsPlugin *> pluginsList() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QString configGroupName() const;
- Q_REQUIRED_RESULT MessageViewer::HeaderStylePlugin *pluginFromIdentifier(const QString &id);
+ MessageViewerConfigureSettingsPlugin *pluginFromIdentifier(const QString &id);
private:
- HeaderStylePluginManagerPrivate *const d;
+ MessageViewerConfigureSettingsPluginManagerPrivate *const d;
};
}
-#endif // HEADERSTYLEPLUGINMANAGER_H
+#endif // MESSAGEVIEWERCONFIGURESETTINGSPLUGINMANAGER_H
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.cpp
similarity index 67%
copy from webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.cpp
index b5d6dc05..4e1705f4 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#include "networkpluginurlinterceptorconfigurewidget.h"
+#include "messageviewerconfiguresettingspluginwidget.h"
-using namespace WebEngineViewer;
+using namespace MessageViewer;
-NetworkPluginUrlInterceptorConfigureWidget::NetworkPluginUrlInterceptorConfigureWidget(QWidget *parent)
+MessageViewerConfigureSettingsPluginWidget::MessageViewerConfigureSettingsPluginWidget(QWidget *parent)
: QWidget(parent)
{
}
-NetworkPluginUrlInterceptorConfigureWidget::~NetworkPluginUrlInterceptorConfigureWidget()
+MessageViewerConfigureSettingsPluginWidget::~MessageViewerConfigureSettingsPluginWidget()
{
}
-QString NetworkPluginUrlInterceptorConfigureWidget::helpAnchor() const
+QString MessageViewerConfigureSettingsPluginWidget::helpAnchor() const
{
return QString();
}
diff --git a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.h
similarity index 68%
copy from messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h
copy to messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.h
index bfaa8e31..69b0606d 100644
--- a/messagecomposer/src/plugineditorcheckbeforesend/plugineditorcheckbeforesendconfigurewidget.h
+++ b/messageviewer/src/messageviewerconfigureplugins/messageviewerconfiguresettingspluginwidget.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-#ifndef PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
-#define PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
+#ifndef MESSAGEVIEWERCONFIGURESETTINGSPLUGINWIDGET_H
+#define MESSAGEVIEWERCONFIGURESETTINGSPLUGINWIDGET_H
-#include "messagecomposer_export.h"
+#include "messageviewer_export.h"
#include <QWidget>
-namespace MessageComposer {
-class MESSAGECOMPOSER_EXPORT PluginEditorCheckBeforeSendConfigureWidget : public QWidget
+namespace MessageViewer {
+class MESSAGEVIEWER_EXPORT MessageViewerConfigureSettingsPluginWidget : public QWidget
{
Q_OBJECT
public:
- explicit PluginEditorCheckBeforeSendConfigureWidget(QWidget *parent = nullptr);
- ~PluginEditorCheckBeforeSendConfigureWidget();
+ explicit MessageViewerConfigureSettingsPluginWidget(QWidget *parent = nullptr);
+ ~MessageViewerConfigureSettingsPluginWidget();
virtual void loadSettings() = 0;
virtual void saveSettings() = 0;
virtual void resetSettings() = 0;
virtual QString helpAnchor() const;
Q_SIGNALS:
void configureChanged();
};
}
-#endif // PLUGINEDITORCHECKBEFORESENDCONFIGUREWIDGET_H
+#endif // MESSAGEVIEWERCONFIGURESETTINGSPLUGINWIDGET_H
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.cpp b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.cpp
index 2a7a6f3a..34fb00e1 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.cpp
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.cpp
@@ -1,30 +1,30 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "defaultgrantleeheaderstrategy.h"
using namespace MessageViewer;
DefaultGrantleeHeaderStrategy::DefaultGrantleeHeaderStrategy()
{
}
DefaultGrantleeHeaderStrategy::~DefaultGrantleeHeaderStrategy()
{
}
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.h b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.h
index 53695636..1e429e3b 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.h
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstrategy.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DEFAULTGRANTLEEHEADERSTRATEGY_H
#define DEFAULTGRANTLEEHEADERSTRATEGY_H
#include <messageviewer/headerstrategy.h>
namespace MessageViewer {
class DefaultGrantleeHeaderStrategy : public HeaderStrategy
{
public:
DefaultGrantleeHeaderStrategy();
~DefaultGrantleeHeaderStrategy() override;
public:
Q_REQUIRED_RESULT const char *name() const override
{
return "defaultgrantlee";
}
Q_REQUIRED_RESULT DefaultPolicy defaultPolicy() const override
{
return Hide;
}
};
}
#endif // DEFAULTGRANTLEEHEADERSTRATEGY_H
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.cpp b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.cpp
index aa5882fd..ee81f82b 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.cpp
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.cpp
@@ -1,72 +1,72 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "defaultgrantleeheaderstyleinterface.h"
#include "messageviewer/headerstyleplugin.h"
#include "messageviewer/headerstyle.h"
#include <grantleetheme/grantleethememanager.h>
#include "globalsettings_base.h"
#include <KToggleAction>
#include <KActionCollection>
#include <QStandardPaths>
using namespace MessageViewer;
DefaultGrantleeHeaderStyleInterface::DefaultGrantleeHeaderStyleInterface(
MessageViewer::HeaderStylePlugin *plugin, QObject *parent)
: MessageViewer::HeaderStyleInterface(plugin, parent)
{
}
DefaultGrantleeHeaderStyleInterface::~DefaultGrantleeHeaderStyleInterface()
{
}
void DefaultGrantleeHeaderStyleInterface::createAction(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac)
{
const QStringList defaultThemePath = QStandardPaths::locateAll(
QStandardPaths::GenericDataLocation, QStringLiteral(
"messageviewer/defaultthemes/"), QStandardPaths::LocateDirectory);
if (!defaultThemePath.isEmpty()) {
const QString themeName = DefaultGrantleeHeaderStylePluginSettings::self()->themeName();
mDefaultTheme
= GrantleeTheme::ThemeManager::loadTheme(defaultThemePath.at(0) + QLatin1Char(
'/') + themeName,
themeName,
QStringLiteral(
"kmail_default.desktop"));
}
mHeaderStylePlugin->headerStyle()->setTheme(mDefaultTheme);
KToggleAction *act = new KToggleAction(mDefaultTheme.name(), this);
ac->addAction(QStringLiteral("default_grantlee_theme"), act);
connect(act, &KToggleAction::triggered, this,
&DefaultGrantleeHeaderStyleInterface::slotDefaultGrantleeHeaders);
mAction.append(act);
addActionToMenu(menu, actionGroup);
}
void DefaultGrantleeHeaderStyleInterface::activateAction()
{
mAction.at(0)->setChecked(true);
}
void DefaultGrantleeHeaderStyleInterface::slotDefaultGrantleeHeaders()
{
mHeaderStylePlugin->headerStyle()->setTheme(mDefaultTheme);
slotStyleChanged();
}
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.h b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.h
index d0066bff..168da6cc 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.h
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleinterface.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DEFAULTGRANTLEEHEADERSTYLEINTERFACE_H
#define DEFAULTGRANTLEEHEADERSTYLEINTERFACE_H
#include <messageviewer/headerstyleinterface.h>
#include <grantleetheme/grantleetheme.h>
namespace MessageViewer {
class DefaultGrantleeHeaderStyleInterface : public MessageViewer::HeaderStyleInterface
{
Q_OBJECT
public:
explicit DefaultGrantleeHeaderStyleInterface(HeaderStylePlugin *plugin, QObject *parent = nullptr);
~DefaultGrantleeHeaderStyleInterface() override;
void createAction(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac) override;
void activateAction() override;
private:
void slotDefaultGrantleeHeaders();
GrantleeTheme::Theme mDefaultTheme;
};
}
#endif // DEFAULTGRANTLEEHEADERSTYLEINTERFACE_H
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.cpp b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.cpp
index 9681bb75..47cfad42 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.cpp
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.cpp
@@ -1,74 +1,74 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "defaultgrantleeheaderstyleinterface.h"
#include "defaultgrantleeheaderstyleplugin.h"
#include "globalsettings_base.h"
#include <messageviewer/grantleeheaderstyle.h>
#include "defaultgrantleeheaderstrategy.h"
#include <kpluginfactory.h>
using namespace MessageViewer;
K_PLUGIN_CLASS_WITH_JSON(DefaultGrantleeHeaderStylePlugin,
"messageviewer_defaultgrantleeheaderstyleplugin.json")
DefaultGrantleeHeaderStylePlugin::DefaultGrantleeHeaderStylePlugin(QObject *parent, const QList<QVariant> &)
: MessageViewer::HeaderStylePlugin(parent)
, mHeaderStyle(new GrantleeHeaderStyle)
, mHeaderStrategy(new DefaultGrantleeHeaderStrategy)
{
initializePlugin();
}
DefaultGrantleeHeaderStylePlugin::~DefaultGrantleeHeaderStylePlugin()
{
delete mHeaderStyle;
delete mHeaderStrategy;
}
void DefaultGrantleeHeaderStylePlugin::initializePlugin()
{
}
HeaderStyle *DefaultGrantleeHeaderStylePlugin::headerStyle() const
{
return mHeaderStyle;
}
HeaderStrategy *DefaultGrantleeHeaderStylePlugin::headerStrategy() const
{
return mHeaderStrategy;
}
HeaderStyleInterface *DefaultGrantleeHeaderStylePlugin::createView(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac, QObject *parent)
{
MessageViewer::HeaderStyleInterface *view
= new MessageViewer::DefaultGrantleeHeaderStyleInterface(this, parent);
if (ac) {
view->createAction(menu, actionGroup, ac);
}
return view;
}
QString DefaultGrantleeHeaderStylePlugin::name() const
{
return QStringLiteral("defaultgrantlee");
}
#include "defaultgrantleeheaderstyleplugin.moc"
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.h b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.h
index 1af1af9c..a369cd8d 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.h
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/defaultgrantleeheaderstyleplugin.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2015-2019 Montel Laurent <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef DEFAULTGRANTLEEHEADERSTYLEPLUGIN_H
#define DEFAULTGRANTLEEHEADERSTYLEPLUGIN_H
#include "messageviewer/headerstyleplugin.h"
#include <QVariant>
namespace MessageViewer {
class GrantleeHeaderStyle;
class DefaultGrantleeHeaderStylePlugin : public MessageViewer::HeaderStylePlugin
{
Q_OBJECT
public:
explicit DefaultGrantleeHeaderStylePlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
~DefaultGrantleeHeaderStylePlugin() override;
Q_REQUIRED_RESULT HeaderStyle *headerStyle() const override;
Q_REQUIRED_RESULT HeaderStrategy *headerStrategy() const override;
Q_REQUIRED_RESULT HeaderStyleInterface *createView(KActionMenu *menu, QActionGroup *actionGroup, KActionCollection *ac, QObject *parent = nullptr) override;
Q_REQUIRED_RESULT QString name() const override;
private:
void initializePlugin();
GrantleeHeaderStyle *mHeaderStyle = nullptr;
HeaderStrategy *mHeaderStrategy = nullptr;
};
}
#endif // DEFAULTGRANTLEEHEADERSTYLEPLUGIN_H
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/messageviewer_defaultgrantleeheaderstyleplugin.json b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/messageviewer_defaultgrantleeheaderstyleplugin.json
index 52d29ff5..7f305c98 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/messageviewer_defaultgrantleeheaderstyleplugin.json
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/messageviewer_defaultgrantleeheaderstyleplugin.json
@@ -1,70 +1,72 @@
{
"KPlugin": {
"Description": "This plugin provides a default Grantlee mail header.",
"Description[ar]": "توفّر هذه الملحقة ترويسة بريد «غرانتلي» مبدئيّة.",
"Description[ca@valencia]": "Aquest connector proporciona una capçalera «Grantlee» predeterminada al correu.",
"Description[ca]": "Aquest connector proporciona una capçalera «Grantlee» predeterminada al correu.",
"Description[cs]": "Tento modul poskytuje výchozí hlavičku e-mailu Grantlee.",
"Description[de]": "Mit diesem Modul können Sie ein Vorspann-Design „Standard-Grantlee“ definieren",
+ "Description[en_GB]": "This plugin provides a default Grantlee mail header.",
"Description[es]": "Este complemento proporciona un encabezado de correo predeterminado Grantlee.",
"Description[fi]": "Tämä liitännäinen mahdollistaa oletusarvoisen Grantlee-viestiotsakkeen.",
"Description[fr]": "Ce module complémentaire fournit un style d'en-tête Grantlee par défaut.",
"Description[gl]": "Este complemento fornece unha cabeceira de correo predeterminada de Grantlee.",
"Description[it]": "Questa estensione fornisce un'intestazione predefinita per la posta basata su Grantlee.",
"Description[ko]": "이 플러그인은 기본 Grantlee 메일 헤더를 제공합니다.",
"Description[nl]": "Deze plug-in biedt een standaard Grantlee e-mailkop.",
"Description[pl]": "Ta wtyczka dostarcza domyślny nagłówek pocztowy Grantlee.",
"Description[pt]": "Este 'plugin' permite-lhe definir um cabeçalho por omissão do Grantlee.",
"Description[pt_BR]": "Este plugin permite-lhe definir um cabeçalho padrão do Grantlee.",
"Description[ru]": "Этот модуль определяет стиль заголовка Grantlee.",
"Description[sk]": "Tento plugin poskytuje predvolenú hlavičku mailu Grantlee.",
"Description[sl]": "Ta vstavek ponuja privzete glave sporočil Grantlee.",
"Description[sr@ijekavian]": "Овај прикључак пружа подразумевано Грантлијево заглавље порука",
"Description[sr@ijekavianlatin]": "Ovaj priključak pruža podrazumevano Grantleejevo zaglavlje poruka",
"Description[sr@latin]": "Ovaj priključak pruža podrazumevano Grantleejevo zaglavlje poruka",
"Description[sr]": "Овај прикључак пружа подразумевано Грантлијево заглавље порука",
"Description[sv]": "Insticksprogrammet tillhandahåller ett Grantlee-standardbrevhuvud.",
"Description[tr]": "Bu eklenti, varsayılan bir Grantlee posta başlığı sağlar.",
"Description[uk]": "Цей додаток визначає типовий заголовок повідомлення Grantlee.",
"Description[x-test]": "xxThis plugin provides a default Grantlee mail header.xx",
"Description[zh_CN]": "此插件提供默认 Grantlee 邮件头。",
"Description[zh_TW]": "這個附加元件提供了預設的 Grantlee 郵件標頭",
"EnabledByDefault": "true",
"Name": "Default Grantlee Header Style",
"Name[ar]": "نمط ترويسات «غرانتلي» المبدئيّ",
"Name[ca@valencia]": "Estil de capçalera «Grantlee» predeterminat",
"Name[ca]": "Estil de capçalera «Grantlee» predeterminat",
"Name[cs]": "Výchozí styl hlavičky Grantlee",
"Name[da]": "Standard Grantlee header-stil",
"Name[de]": "Vorspann-Design „Standard-Grantlee“",
+ "Name[en_GB]": "Default Grantlee Header Style",
"Name[es]": "Estilo de encabezado predeterminado Grantlee",
"Name[et]": "Grantlee vaikimisi päisestiil",
"Name[fi]": "Oletusarvoinen Grantlee-otsaketyyli",
"Name[fr]": "Style d'en-tête Grantlee par défaut",
"Name[gl]": "Estilo de cabeceira predeterminado de Grantlee",
"Name[it]": "Stile con intestazioni di Grantlee predefinito",
"Name[ko]": "기본 Grantlee 헤더 스타일",
"Name[nl]": "Stijl met standaard Grantlee header",
"Name[pl]": "Wygląd domyślnego nagłówka Grantlee",
"Name[pt]": "Estilo de Cabeçalhos Predefinido do Grantlee",
"Name[pt_BR]": "Estilo padrão de cabeçalho Grantlee",
"Name[ru]": "Стандартный стиль заголовков Grantlee",
"Name[sk]": "Predvolený štýl hlavičiek Grantlee",
"Name[sl]": "Privzet slog z glavami Grantlee",
"Name[sr@ijekavian]": "Подразумевани Грантлијев стил заглавља",
"Name[sr@ijekavianlatin]": "Podrazumevani Grantleejev stil zaglavlja",
"Name[sr@latin]": "Podrazumevani Grantleejev stil zaglavlja",
"Name[sr]": "Подразумевани Грантлијев стил заглавља",
"Name[sv]": "Normal Grantlee-huvudstil",
"Name[tr]": "Varsayılan Grantlee Başlık Biçimi",
"Name[uk]": "Типовий стиль заголовків Grantlee",
"Name[x-test]": "xxDefault Grantlee Header Stylexx",
"Name[zh_CN]": "默认 Grantlee 头样式",
"Name[zh_TW]": "預設 Grantlee 標頭檔案樣式",
"ServiceTypes": [
"MessageViewerHeaderStyle/Plugin"
],
"Version": "1.0"
},
"X-KDE-MessageViewer-Header-Order": "100"
}
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/header.html b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/header.html
index c4f0806f..9908765a 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/header.html
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/header.html
@@ -1,87 +1,91 @@
<link href="{{ header.absoluteThemePath }}/style.css" rel="stylesheet" type="text/css" />
<div id="kmailPage">
<div id="headerbox" dir="ltr">
<div id="subject" dir="{{ header.subjectDir }}">{{ header.subject|safe }}</div>
<div id="photo">
{% if header.photourl %}
<img src="{{ header.photourl }}" width="{{ header.photowidth }}" height="{{ header.photoheight }}" />
{% else %}
<img src="{{ header.absoluteThemePath }}/photo.svg" width="{{ header.photowidth }}" height="{{ header.photoheight }}" />
{% endif %}
</div>
<div class="table">
<div class="row">
<div class="headerleft">{{ header.fromi18n }}</div>
<div class="headerright">
{{ header.from.fullAddress|safe }}
{% if header.resentfrom.isSet %}
{{ header.resentfromi18n }}: {{ header.resentfrom.fullAddress|safe }}
{% endif %}
{% if header.vcardname %}
<a href="{{ header.vcardname|safe }}">{{ header.vcardi18n }}</a>
{% endif %}
{% if header.organization %}
({{ header.organization|safe }})
{% endif %}
</div>
</div>
{% if header.to.isSet %}
<div class="row">
<div class="headerleft">{{ header.toi18n }}</div>
<div class="headerright">{{ header.to.expandableTo|safe }}</div>
</div>
{% endif %}
{% if header.cc.isSet %}
<div class="row">
<div class="headerleft">{{ header.cci18n }}</div>
<div class="headerright">{{ header.cc.expandableCc|safe }}</div>
</div>
{% endif %}
{% if header.bcc.isSet %}
<div class="row">
<div class="headerleft">{{ header.bcci18n }}</div>
<div class="headerright">{{ header.bcc.fullAddress|safe }}</div>
</div>
{% endif %}
{% if header.sender %}
<div class="row">
<div class="headerleft">{{ header.senderi18n }}</div>
<div class="headerright">{{ header.sender|safe }}</div>
</div>
{% endif %}
{% if header.listid %}
<div class="row">
<div class="headerleft">{{ header.listidi18n }}</div>
<div class="headerright">{{ header.listid }}</div>
</div>
{% endif %}
<div class="row">
<div class="headerleft">{{ header.datei18n }}</div>
<div class="headerrightdate" dir="{{ header.applicationDir }}">{{ header.date.short }}</div>
</div>
{% if header.spamHTML %}
<div class="row">
- <div class="headerleft">{{ header.spamstatusi18n }}</div>
+ <div class="headerleftnowrap">{{ header.spamstatusi18n }}</div>
<div class="headerright">{{ header.spamHTML|safe }}</div>
</div>
{% endif %}
{% if header.messageHasSecurityInfo %}
<div class="row">
<div class="headerleft">{{ header.messageHasSecurityInfoI18n }}</div>
<div class="headerright">
{% if header.messageIsSigned %}
{{ header.signedIcon|safe }}
{% endif %}
{% if header.messageIsEncrypted %}
{{ header.encryptedIcon|safe }}
{% endif %}
</div>
</div>
{% endif %}
- </div>
- {% if header.hasAttachment %}
- <div>{{ header.attachmentHtml|safe }}</div>
- {% endif %}
- </div>
+ {% if header.hasAttachment %}
+ <div class="row">
+ <div class="headerleft">{{ header.attachmentI18n }}</div>
+ <div class="headerright">{{ header.attachmentHtml|safe }}</div>
+ </div>
+ {% endif %}
+
+ </div>
+ </div>
</div>
<div id="kmailContent">
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/kmail_default.desktop b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/kmail_default.desktop
index 0c0cd84c..58c4f141 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/kmail_default.desktop
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/kmail_default.desktop
@@ -1,68 +1,69 @@
[Desktop Entry]
Author=KMail 5.2 Default Theme
AuthorEmail=montel@kde.org
Description=KMail Default Theme 5.2 (Based on Breeze Flat author Kristofer Rickheden Gustavsson)
Description[ar]=سمة «بريدك» المبدئيّة ٥٫٢ (مبنيّة على ”نسيم مسطّح“ لمؤلّفها Kristofer Rickheden Gustavsson)
Description[ca]=Tema per omissió pel KMail 5.2 (basat en la Brisa Flat de l'autor Kristofer Rickheden Gustavsson)
Description[ca@valencia]=Tema per omissió pel KMail 5.2 (basat en la Brisa Flat de l'autor Kristofer Rickheden Gustavsson)
Description[da]=KMail standardtema 5.2 (baseret på Breeze Flat, ophavsmand Kristofer Rickheden Gustavsson)
Description[de]=KMail-Standarddesign 5.2 (Basiert auf „Breeze Flach“, Autor: Kristofer Rickheden Gustavsson)
Description[en_GB]=KMail Default Theme 5.2 (Based on Breeze Flat author Kristofer Rickheden Gustavsson)
Description[es]=Tema predeterminado de KMail 5.2 (basado en Brisa plano, autor Kristofer Rickheden Gustavsson)
Description[et]=KMaili vaiketeema 5.2 (aluseks on Breeze Flat, autor Kristofer Rickheden Gustavsson)
Description[fi]=KMailin oletusteema 5.2 (jonka pohjana on Kristofer Rickheden Gustavssonin Breeze Flat)
-Description[fr]=Thème par défaut de KMail 5.2 (partant de Brise, auteur Kristofer Rickheden Gustavsson)
+Description[fr]=Thème par défaut de KMail 5.2 (inspiré de Breeze, composé par Kristofer Rickheden Gustavsson)
Description[gl]=Versión 5.2 do tema predeterminado de KMail (baseado en Breeze Flat, obra de Kristofer Rickheden Gustavsson).
Description[it]=Tema predefinito di KMail 5.2 (basato su Breeze Flat di Kristofer Rickheden Gustavsson)
Description[ko]=KMail 기본 테마 5.2(Kristofer Rickheden Gustavsson의 Breeze Flat 테마 기반)
Description[nl]=KMail standaard thema 5.2 (gebaseerd op de auteur van Breeze Flat Kristofer Rickheden Gustavsson)
Description[pl]=Domyślny wystrój KMaila 5.2 (Oparty na Płaskiej Bryzie autorstwa Kristofer Rickheden Gustavsson)
Description[pt]=Tema por Omissão do KMail 5.2 (Baseado no autor do Brisa Plano Kristofer Rickheden Gustavsson)
Description[pt_BR]=Tema padrão do KMail 5.2 (Baseado no autor do Breeze Flat Kristofer Rickheden Gustavsson)
+Description[ru]=Оформление по умолчанию для KMail 5.2 (основано на оформлении Breeze Flat, автор Kristofer Rickheden Gustavsson)
Description[sk]=Predvolená téma KMail 5.2 (založená na Breeze Flat, autor Kristofer Rickheden Gustavsson)
Description[sl]=Privzeta tema za KMail 5.2 (Temelječa na ploski temi za Sapico, avtorja Kristoferja Rickhedena Gustavssona)
Description[sr]=Подразумевана тема К‑поште 5.2 (на основу Поветарца равног равног од Кристофера Рикхедена Густавсона)
Description[sr@ijekavian]=Подразумевана тема К‑поште 5.2 (на основу Поветарца равног равног од Кристофера Рикхедена Густавсона)
Description[sr@ijekavianlatin]=Podrazumevana tema K‑pošte 5.2 (na osnovu Povetarca ravnog ravnog od Kristofera Rikhedena Gustavsona)
Description[sr@latin]=Podrazumevana tema K‑pošte 5.2 (na osnovu Povetarca ravnog ravnog od Kristofera Rikhedena Gustavsona)
Description[sv]=Kmail 5.2 standardtema (baserat på Breeze platt, upphovsman Kristofer Rickheden Gustavsson)
Description[tr]=KMail Varsayılan Tema 5.2 (Breeze Flat temelli, yazarı Kristofer Rickheden Gustavsson)
Description[uk]=Типова тема KMail 5.2 (засновано на Breeze Flat, автором якої є Kristofer Rickheden Gustavsson)
Description[x-test]=xxKMail Default Theme 5.2 (Based on Breeze Flat author Kristofer Rickheden Gustavsson)xx
Description[zh_CN]=KMail 默认主题 5.2 (基于微风扁平化作者 Kristofer Rickheden Gustavsson)
Description[zh_TW]=KMail 預設主題 5.2(基於 Breeze Flat,作者為 Kristofer Rickheden Gustavsson)
FileName=header.html
Name=KMail 5.2
Name[ar]=بريدك ٥٫٢
-Name[ast]=KMail 5.2
Name[ca]=KMail 5.2
Name[ca@valencia]=KMail 5.2
Name[cs]=KMail 5.2
Name[da]=KMail 5.2
Name[de]=KMail 5.2
Name[en_GB]=KMail 5.2
Name[es]=KMail 5.2
Name[et]=KMail 5.2
Name[fi]=KMail 5.2
Name[fr]=KMail 5.2
Name[gl]=KMail 5.2
Name[ia]=KMail 5.2
Name[it]=KMail 5.2
Name[ko]=KMail 5.2
Name[nl]=KMail 5.2
Name[pl]=KMail 5.2
Name[pt]=KMail 5.2
Name[pt_BR]=KMail 5.2
+Name[ru]=KMail 5.2
Name[sk]=KMail 5.2
Name[sl]=KMail 5.2
Name[sr]=К‑пошта 5.2
Name[sr@ijekavian]=К‑пошта 5.2
Name[sr@ijekavianlatin]=K‑pošta 5.2
Name[sr@latin]=K‑pošta 5.2
Name[sv]=Kmail 5.2
Name[tr]=KMail 5.2
Name[uk]=KMail 5.2
Name[x-test]=xxKMail 5.2xx
Name[zh_CN]=KMail 5.2
Name[zh_TW]=KMail 5.2
ThemeVersion=1
diff --git a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/style.css b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/style.css
index 371d926a..49376f9d 100644
--- a/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/style.css
+++ b/messageviewer/src/messageviewerheaderplugins/defaultgrantleeheaderstyleplugin/theme/5.2/style.css
@@ -1,79 +1,84 @@
body {
margin: 0;
padding: 0;
}
div#headerbox {
border-radius: 3px;
border-bottom: 1px solid rgb(190, 190, 190);
color: rgb(100, 100, 100) !important;
overflow: auto;
padding: 5px 20px;
}
div#headerbox table.outer {
display: inline;
position: fixed !important;
top: -1px;
left: -1px;
bottom: -1px;
right: 0px;
border: none !important;
table-collapse: collapse;
}
div#headerbox div#subject {
color: rgb(100, 100, 100) !important;
font-size: 22px;
font-weight: normal;
line-height: 24px;
padding-bottom: 5px;
padding-top: 5px;
}
div#headerbox div#photo {
float: left;
margin: 5px;
}
div#headerbox div.table {
display: table;
padding-right: 12px;
padding-top: 6px;
}
div#headerbox div.row {
display: table-row;
}
div#headerbox div.headerleft {
display: table-cell;
padding: 0.1em 1em;
}
div#headerbox div.headerright {
display: table-cell;
font-weight: bold;
padding: 0.1em;
}
+div#headerbox div.headerleftnowrap {
+ display: table-cell;
+ padding: 0.1em 1em;
+ white-space: pre; /* this will avoid line breaks*/
+}
div#headerbox div.headerrightdate {
color: rgb(120, 125, 129) !important;
display: table-cell;
font-weight: bold;
padding: 0.1em;
}
div#headerbox .actiontable {
display: table;
width: 100%;
}
div#headerbox div.actionrowtable {
display:table-row;
}
div#headerbox div.theactioncell {
display: table-cell;
padding: 0px 2px; /* just some padding, if needed*/
white-space: pre; /* this will avoid line breaks*/
}
div#headerbox div.theactionbigcell{
display: table-cell;
width: 100%; /* this will shrink other cells */
}
#kmailContent {
clear: both;
margin-top: 10px;
padding: 20px;
}
diff --git a/messageviewer/src/scamdetection/autotests/scamattributetest.cpp b/messageviewer/src/scamdetection/autotests/scamattributetest.cpp
index 94bc1aaf..3f57aeb5 100644
--- a/messageviewer/src/scamdetection/autotests/scamattributetest.cpp
+++ b/messageviewer/src/scamdetection/autotests/scamattributetest.cpp
@@ -1,76 +1,76 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#include "scamattributetest.h"
#include "../scamattribute.h"
-#include <qtest.h>
+#include <QTest>
ScamAttributeTest::ScamAttributeTest(QObject *parent)
: QObject(parent)
{
}
ScamAttributeTest::~ScamAttributeTest()
{
}
void ScamAttributeTest::shouldHaveDefaultValue()
{
MessageViewer::ScamAttribute attr;
QVERIFY(!attr.isAScam());
}
void ScamAttributeTest::shouldAffectValue()
{
MessageViewer::ScamAttribute attr;
bool isScam = false;
attr.setIsAScam(isScam);
QCOMPARE(attr.isAScam(), isScam);
isScam = true;
attr.setIsAScam(isScam);
QCOMPARE(attr.isAScam(), isScam);
}
void ScamAttributeTest::shouldDeserializeValue()
{
MessageViewer::ScamAttribute attr;
const bool isScam = true;
attr.setIsAScam(isScam);
const QByteArray ba = attr.serialized();
MessageViewer::ScamAttribute result;
result.deserialize(ba);
QVERIFY(result == attr);
}
void ScamAttributeTest::shouldCloneAttribute()
{
MessageViewer::ScamAttribute attr;
const bool isScam = true;
attr.setIsAScam(isScam);
MessageViewer::ScamAttribute *cloneAttr = attr.clone();
QCOMPARE(attr.isAScam(), cloneAttr->isAScam());
delete cloneAttr;
}
void ScamAttributeTest::shouldHaveType()
{
MessageViewer::ScamAttribute attr;
QCOMPARE(attr.type(), QByteArray("ScamAttribute"));
}
QTEST_MAIN(ScamAttributeTest)
diff --git a/messageviewer/src/scamdetection/autotests/scamattributetest.h b/messageviewer/src/scamdetection/autotests/scamattributetest.h
index 5aced4b7..11bc6e6e 100644
--- a/messageviewer/src/scamdetection/autotests/scamattributetest.h
+++ b/messageviewer/src/scamdetection/autotests/scamattributetest.h
@@ -1,40 +1,40 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMATTRIBUTETEST_H
#define SCAMATTRIBUTETEST_H
#include <QObject>
class ScamAttributeTest : public QObject
{
Q_OBJECT
public:
explicit ScamAttributeTest(QObject *parent = nullptr);
~ScamAttributeTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAffectValue();
void shouldDeserializeValue();
void shouldCloneAttribute();
void shouldHaveType();
};
#endif // SCAMATTRIBUTETEST_H
diff --git a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp
index 2aa85460..562bfa61 100644
--- a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp
+++ b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.cpp
@@ -1,177 +1,182 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "scamdetectionwebenginetest.h"
#include "../scamdetectionwebengine.h"
#include <QHBoxLayout>
#include <QSignalSpy>
#include <QTest>
#include <QWebEngineView>
TestWebEngineScamDetection::TestWebEngineScamDetection(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *hbox = new QHBoxLayout(this);
mEngineView = new QWebEngineView(this);
mScamDetectionWebEngine = new MessageViewer::ScamDetectionWebEngine(this);
connect(mScamDetectionWebEngine, &MessageViewer::ScamDetectionWebEngine::resultScanDetection,
this, &TestWebEngineScamDetection::resultScanDetection);
connect(mEngineView, &QWebEngineView::loadFinished, this,
&TestWebEngineScamDetection::loadFinished);
hbox->addWidget(mEngineView);
}
TestWebEngineScamDetection::~TestWebEngineScamDetection()
{
}
void TestWebEngineScamDetection::setHtml(const QString &html)
{
mEngineView->setHtml(html);
}
void TestWebEngineScamDetection::loadFinished(bool b)
{
Q_UNUSED(b);
mScamDetectionWebEngine->scanPage(mEngineView->page());
}
ScamDetectionWebEngineTest::ScamDetectionWebEngineTest(QObject *parent)
: QObject(parent)
{
}
ScamDetectionWebEngineTest::~ScamDetectionWebEngineTest()
{
}
void ScamDetectionWebEngineTest::scamtest_data()
{
QTest::addColumn<QString>("html");
QTest::addColumn<bool>("result");
//No Scam
QTest::newRow("noscam1") << QStringLiteral(
"<html><body><a href=\"www.kde.org\">kde</a></body></html>") << false;
QTest::newRow("noscam2") << QStringLiteral(
"<html><body><a href=\"http://www.kde.org\" title=\"http://www.kde.org\">kde</a></body></html>")
<< false;
QTest::newRow("noscam3") << QStringLiteral(
"<html><body><a href=\"https://www.kde.org\" title=\"https://www.kde.org\">kde</a></body></html>")
<< false;
//Hexa value
QTest::newRow("hexavalue") << QStringLiteral(
"<html><body><a href=\"http://125.15.55.88/\" title=\"http://0x12.0x1e.0x0A.0x00\">test</a></body></html>")
<< true;
//Ip
QTest::newRow("Ip value") << QStringLiteral(
"<html><body><a href=\"http://127.0.0.1/\">test</a></body></html>") << false;
QTest::newRow("Ip scam1") << QStringLiteral(
"<html><body><a href=\"http://125.15.55.88/\" title=\"http://www.kde.org\">test</a></body></html>")
<< true;
QTest::newRow("Ip scam2") << QStringLiteral(
"<html><body><a href=\"http://125.15.55.88/\" title=\"http://125.15.55.88/\">test</a></body></html>")
<< true;
//Href no scam
QTest::newRow("Href no scam") << QStringLiteral(
"<html><body><a href=\"http://www.kde.org/\" title=\"http://www.kde.org\">test</a></body></html>")
<< false;
//Redirect href
QTest::newRow("Redirect scam") << QStringLiteral(
"<html><body><a href=\"http://www.google.fr/url?q=http://www.yahoo.com\">test</a></body></html>")
<< true;
QTest::newRow("Redirect no scam") << QStringLiteral(
"<html><body><a href=\"kmail:showAuditLog?log=http://www.foo.com%3http://www.bla.com\">test</a></body></html>")
<< false;
//Numeric value
QTest::newRow("numeric no scam") << QStringLiteral(
"<html><body><a href=\"http://baseball2.2ndhalfplays.com/nested/attribs/\">http://baseball2.2ndhalfplays.com/nested/attribs</html>")
<< false;
QTest::newRow("numeric scam1") << QStringLiteral(
"<html><body><a href=\"http://25.15.55.88/\">test</a></body></html>") << true;
QTest::newRow("numeric scam2") << QStringLiteral(
"<html><body><a href=\"http://255.0.1.1/\">test</a></body></html>") << true;
QTest::newRow("numeric scam3") << QStringLiteral(
"<html><body><a href=\"http://1.0.1.1/\">test</a></body></html>") << true;
QTest::newRow("numeric scam4") << QStringLiteral(
"<html><body><a href=\"http://255.500.1.1/\">test</a></body></html>") << true;
QTest::newRow("numeric scam5") << QStringLiteral(
"<html><body><a href=\"http://baseball.2ndhalfplays.com/nested/attribs/\">http://baseball2.2ndhalfplays.com/nested/attribs</html>")
<< true;
QTest::newRow("scam") << QStringLiteral(
"<html><body><a href=\"http://dfgdgsfdgsfdgsfd.foo.com/#contact@bla.org\">https://www.bli.com/manager/dedicated/index.html#/billing/mean</a></html>")
<< true;
QTest::newRow("scam-amp") << QStringLiteral(
"<a href=\"https://bugs.kde.org/enter_bug.cgi?format=guided&amp;product=gcompris\">https://bugs.kde.org/enter_bug.cgi?format=guided&amp;amp;product=gcompris</a></div>")
<< false;
QTest::newRow("scam-encoded-url1") << QStringLiteral(
"<a href=\"https://github.com/KDAB/KDStateMachineEditor.git|1.2\">https://github.com/KDAB/KDStateMachineEditor.git|1.2</a>")
<< false;
QTest::newRow("scam-lowercase") << QStringLiteral(
"<a href=\"http://www.Kde.org\">http://www.Kde.org</a>")
<< false;
QTest::newRow("scam-lowercase-2") << QStringLiteral(
"<a href=\"http://www.Kde.org/KDE/bla\">http://www.Kde.org/KDE/bla</a>")
<< false;
QTest::newRow("scam-lowercase-3") << QStringLiteral(
"<a href=\"http://code.qt.io/cgit/%7bnon-gerrit%7d/qt-labs/opencl.git\">http://code.qt.io/cgit/%7bnon-gerrit%7d/qt-labs/opencl.git</a>")
<< false;
QTest::newRow("toplevelrepo") << QStringLiteral(
"<a href=\"https://www.amazon.fr/gp/goldbox/ref=pe_btn/?nocache=1510065600354\">https://www.amazon.fr/gp/../gp/goldbox/ref=pe_btn/?nocache=1510065600354</a>")
<< false;
QTest::newRow("toplevelrepo2") << QStringLiteral(
"<a href=\"https://www.amazon.fr/gp/../gp/goldbox/ref=pe_btn/?nocache=1510065600354\">https://www.amazon.fr/gp/goldbox/ref=pe_btn/?nocache=1510065600354</a>")
<< false;
QTest::newRow("toplevelrepo3") << QStringLiteral(
"<a href=\"https://www.amazon.fr/gp/../gp/goldbox/ref=pe_d//\">https://www.amazon.fr/gp/../gp/goldbox/ref=pe_d//</a>")
<< false;
-#if 0
+ QTest::newRow("endwith%22") << QStringLiteral(
+ "<a href=\"http://www.kde.org/standards/kcfg/1.0/kcfg.xsd\" \"=\"\">http://www.kde.org/standards/kcfg/1.0/kcfg.xsd\"</a>")
+ << false;
+
+ QTest::newRow("contains%5C") << QStringLiteral(
+ "<a href=\"http://g-ecx.images-amazon.com/images/G/01/barcodes/blank003.jpg%5CnUse\">http://g-ecx.images-amazon.com/images/G/01/barcodes/blank003.jpg/nUse</a>")
+ << false;
QTest::newRow("wierd1") << QStringLiteral(
"<a href=\"http://www.weezevent.com?c=sys_mail\">http://www.weezevent.com?c=sys_mail</a>")
- << false;
-#endif
+ << false;
}
void ScamDetectionWebEngineTest::scamtest()
{
QFETCH(QString, html);
QFETCH(bool, result);
TestWebEngineScamDetection scamDetection;
- QSignalSpy scamDetectionSpy(&scamDetection, SIGNAL(resultScanDetection(bool)));
+ QSignalSpy scamDetectionSpy(&scamDetection, &TestWebEngineScamDetection::resultScanDetection);
scamDetection.setHtml(html);
QVERIFY(scamDetectionSpy.wait());
QCOMPARE(scamDetectionSpy.count(), 1);
const bool scamResult = scamDetectionSpy.at(0).at(0).toBool();
QCOMPARE(scamResult, result);
}
QTEST_MAIN(ScamDetectionWebEngineTest)
diff --git a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.h b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.h
index c47410c4..769fd977 100644
--- a/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.h
+++ b/messageviewer/src/scamdetection/autotests/scamdetectionwebenginetest.h
@@ -1,59 +1,59 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef SCAMDETECTIONWEBENGINETEST_H
#define SCAMDETECTIONWEBENGINETEST_H
#include <QObject>
#include <QWidget>
namespace MessageViewer {
class ScamDetectionWebEngine;
}
class QWebEngineView;
class TestWebEngineScamDetection : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngineScamDetection(QWidget *parent = nullptr);
~TestWebEngineScamDetection();
void setHtml(const QString &html);
private Q_SLOTS:
void loadFinished(bool b);
Q_SIGNALS:
void resultScanDetection(bool result);
private:
QWebEngineView *mEngineView = nullptr;
MessageViewer::ScamDetectionWebEngine *mScamDetectionWebEngine = nullptr;
};
class ScamDetectionWebEngineTest : public QObject
{
Q_OBJECT
public:
explicit ScamDetectionWebEngineTest(QObject *parent = nullptr);
~ScamDetectionWebEngineTest();
private Q_SLOTS:
void scamtest_data();
void scamtest();
};
#endif // SCAMDETECTIONWEBENGINETEST_H
diff --git a/messageviewer/src/scamdetection/scamattribute.cpp b/messageviewer/src/scamdetection/scamattribute.cpp
index 6f0c895d..754d2284 100644
--- a/messageviewer/src/scamdetection/scamattribute.cpp
+++ b/messageviewer/src/scamdetection/scamattribute.cpp
@@ -1,90 +1,90 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "scamattribute.h"
#include <QByteArray>
#include <QIODevice>
#include <QDataStream>
using namespace MessageViewer;
class MessageViewer::ScamAttributePrivate
{
public:
ScamAttributePrivate()
{
}
bool isAScam = false;
};
ScamAttribute::ScamAttribute()
: d(new ScamAttributePrivate)
{
}
ScamAttribute::~ScamAttribute()
{
delete d;
}
ScamAttribute *ScamAttribute::clone() const
{
ScamAttribute *attr = new ScamAttribute();
attr->setIsAScam(isAScam());
return attr;
}
QByteArray ScamAttribute::type() const
{
static const QByteArray sType("ScamAttribute");
return sType;
}
QByteArray ScamAttribute::serialized() const
{
QByteArray result;
QDataStream s(&result, QIODevice::WriteOnly);
s << isAScam();
return result;
}
void ScamAttribute::deserialize(const QByteArray &data)
{
QDataStream s(data);
bool value = false;
s >> value;
d->isAScam = value;
}
bool ScamAttribute::isAScam() const
{
return d->isAScam;
}
void ScamAttribute::setIsAScam(bool b)
{
d->isAScam = b;
}
bool ScamAttribute::operator==(const ScamAttribute &other) const
{
return d->isAScam == other.isAScam();
}
diff --git a/messageviewer/src/scamdetection/scamattribute.h b/messageviewer/src/scamdetection/scamattribute.h
index d78cf8cc..9e81cffe 100644
--- a/messageviewer/src/scamdetection/scamattribute.h
+++ b/messageviewer/src/scamdetection/scamattribute.h
@@ -1,50 +1,50 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMATTRIBUTE_H
#define SCAMATTRIBUTE_H
#include <AkonadiCore/attribute.h>
namespace MessageViewer {
class ScamAttributePrivate;
class ScamAttribute : public Akonadi::Attribute
{
public:
explicit ScamAttribute();
~ScamAttribute() override;
Q_REQUIRED_RESULT ScamAttribute *clone() const override;
Q_REQUIRED_RESULT QByteArray type() const override;
Q_REQUIRED_RESULT QByteArray serialized() const override;
void deserialize(const QByteArray &data) override;
Q_REQUIRED_RESULT bool isAScam() const;
void setIsAScam(bool b);
Q_REQUIRED_RESULT bool operator==(const ScamAttribute &other) const;
private:
friend class ScamAttributePrivate;
ScamAttributePrivate *const d;
};
}
#endif // SCAMATTRIBUTE_H
diff --git a/messageviewer/src/scamdetection/scamcheckshorturl.cpp b/messageviewer/src/scamdetection/scamcheckshorturl.cpp
index 298eebcf..b741e8f4 100644
--- a/messageviewer/src/scamdetection/scamcheckshorturl.cpp
+++ b/messageviewer/src/scamdetection/scamcheckshorturl.cpp
@@ -1,76 +1,76 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "scamcheckshorturl.h"
#include "scamexpandurljob.h"
#include "messageviewer_debug.h"
#include <QFile>
#include <QStandardPaths>
#include <QJsonDocument>
using namespace MessageViewer;
QStringList ScamCheckShortUrl::sSupportedServices = QStringList();
ScamCheckShortUrl::ScamCheckShortUrl(QObject *parent)
: QObject(parent)
{
loadLongUrlServices();
}
ScamCheckShortUrl::~ScamCheckShortUrl()
{
}
void ScamCheckShortUrl::expandedUrl(const QUrl &url)
{
MessageViewer::ScamExpandUrlJob *job = new MessageViewer::ScamExpandUrlJob(this);
job->expandedUrl(url);
}
bool ScamCheckShortUrl::isShortUrl(const QUrl &url)
{
if (!url.path().isEmpty()
&& QString::compare(url.path(),
QStringLiteral("/")) && sSupportedServices.contains(url.host())) {
return true;
} else {
return false;
}
}
void ScamCheckShortUrl::loadLongUrlServices()
{
QFile servicesFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(
"messageviewer/longurlServices.json")));
if (servicesFile.open(QIODevice::ReadOnly)) {
QJsonParseError error;
const QJsonDocument json = QJsonDocument::fromJson(servicesFile.readAll(), &error);
if (error.error != QJsonParseError::NoError || json.isNull()) {
qCDebug(MESSAGEVIEWER_LOG) << " Error during read longurlServices.json";
return;
}
const QMap<QString, QVariant> response = json.toVariant().toMap();
sSupportedServices = response.uniqueKeys();
} else {
qCDebug(MESSAGEVIEWER_LOG) << " json file \'longurlServices.json\' not found";
}
}
diff --git a/messageviewer/src/scamdetection/scamcheckshorturl.h b/messageviewer/src/scamdetection/scamcheckshorturl.h
index e56d4bc5..65e0f4a6 100644
--- a/messageviewer/src/scamdetection/scamcheckshorturl.h
+++ b/messageviewer/src/scamdetection/scamcheckshorturl.h
@@ -1,49 +1,49 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMCHECKSHORTURL_H
#define SCAMCHECKSHORTURL_H
#include "messageviewer_export.h"
#include <QObject>
#include <QUrl>
#include <QStringList>
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT ScamCheckShortUrl : public QObject
{
Q_OBJECT
public:
explicit ScamCheckShortUrl(QObject *parent = nullptr);
~ScamCheckShortUrl();
static bool isShortUrl(const QUrl &url);
void expandedUrl(const QUrl &url);
static void loadLongUrlServices();
private:
static QStringList sSupportedServices;
};
}
#endif // SCAMCHECKSHORTURL_H
diff --git a/messageviewer/src/scamdetection/scamcheckshorturlmanager.cpp b/messageviewer/src/scamdetection/scamcheckshorturlmanager.cpp
index 5d9be4ef..604de5a8 100644
--- a/messageviewer/src/scamdetection/scamcheckshorturlmanager.cpp
+++ b/messageviewer/src/scamdetection/scamcheckshorturlmanager.cpp
@@ -1,57 +1,57 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#include "scamcheckshorturl.h"
#include "scamcheckshorturlmanager.h"
using namespace MessageViewer;
class MessageViewer::ScamCheckShortUrlManagerPrivate
{
public:
ScamCheckShortUrlManagerPrivate()
{
}
ScamCheckShortUrl *mCheckShortUrl = nullptr;
};
ScamCheckShortUrlManager::ScamCheckShortUrlManager(QObject *parent)
: QObject(parent)
, d(new ScamCheckShortUrlManagerPrivate)
{
d->mCheckShortUrl = new ScamCheckShortUrl(this);
}
ScamCheckShortUrlManager::~ScamCheckShortUrlManager()
{
delete d;
}
ScamCheckShortUrlManager *ScamCheckShortUrlManager::self()
{
static ScamCheckShortUrlManager s_self;
return &s_self;
}
ScamCheckShortUrl *ScamCheckShortUrlManager::scamCheckShortUrl() const
{
return d->mCheckShortUrl;
}
diff --git a/messageviewer/src/scamdetection/scamcheckshorturlmanager.h b/messageviewer/src/scamdetection/scamcheckshorturlmanager.h
index 11a2c853..ef8ed973 100644
--- a/messageviewer/src/scamdetection/scamcheckshorturlmanager.h
+++ b/messageviewer/src/scamdetection/scamcheckshorturlmanager.h
@@ -1,41 +1,41 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMCHECKSHORTURLMANAGER_H
#define SCAMCHECKSHORTURLMANAGER_H
#include <QObject>
#include "messageviewer_export.h"
namespace MessageViewer {
class ScamCheckShortUrl;
class ScamCheckShortUrlManagerPrivate;
class MESSAGEVIEWER_EXPORT ScamCheckShortUrlManager : public QObject
{
Q_OBJECT
public:
explicit ScamCheckShortUrlManager(QObject *parent = nullptr);
~ScamCheckShortUrlManager();
static ScamCheckShortUrlManager *self();
Q_REQUIRED_RESULT ScamCheckShortUrl *scamCheckShortUrl() const;
private:
ScamCheckShortUrlManagerPrivate *const d;
};
}
#endif // SCAMCHECKSHORTURLMANAGER_H
diff --git a/messageviewer/src/scamdetection/scamdetectiondetailsdialog.cpp b/messageviewer/src/scamdetection/scamdetectiondetailsdialog.cpp
index 47e38aed..0ca3fccb 100644
--- a/messageviewer/src/scamdetection/scamdetectiondetailsdialog.cpp
+++ b/messageviewer/src/scamdetection/scamdetectiondetailsdialog.cpp
@@ -1,119 +1,119 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "scamdetectiondetailsdialog.h"
#include "messageviewer_debug.h"
#include "settings/messageviewersettings.h"
#include "kpimtextedit/richtexteditorwidget.h"
#include <KLocalizedString>
#include <QUrl>
#include <KStandardGuiItem>
#include <QFileDialog>
#include <QTextStream>
#include <QDialogButtonBox>
#include <KConfigGroup>
#include <QPushButton>
#include <KGuiItem>
#include <QVBoxLayout>
#include <memory>
using namespace MessageViewer;
ScamDetectionDetailsDialog::ScamDetectionDetailsDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18n("Details"));
setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
QPushButton *user1Button = new QPushButton(this);
buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
connect(buttonBox, &QDialogButtonBox::accepted, this, &ScamDetectionDetailsDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ScamDetectionDetailsDialog::reject);
KGuiItem::assign(user1Button, KStandardGuiItem::saveAs());
setModal(false);
mDetails = new KPIMTextEdit::RichTextEditorWidget(this);
mainLayout->addWidget(mDetails);
mainLayout->addWidget(buttonBox);
mDetails->setReadOnly(true);
connect(user1Button, &QPushButton::clicked, this, &ScamDetectionDetailsDialog::slotSaveAs);
readConfig();
}
ScamDetectionDetailsDialog::~ScamDetectionDetailsDialog()
{
writeConfig();
}
void ScamDetectionDetailsDialog::slotSaveAs()
{
QUrl url;
std::unique_ptr<QFileDialog> fdlg(new QFileDialog(this, QString(), url.path()));
fdlg->setAcceptMode(QFileDialog::AcceptSave);
fdlg->setFileMode(QFileDialog::AnyFile);
fdlg->selectFile(QStringLiteral("scam-detection.html"));
if (fdlg->exec() == QDialog::Accepted) {
const QStringList fileNames = fdlg->selectedFiles();
if (!fileNames.isEmpty()) {
QFile file(fileNames.at(0));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCDebug(MESSAGEVIEWER_LOG) << "We can't save in file :" << fileNames.at(0);
return;
}
QTextStream ts(&file);
ts.setCodec("UTF-8");
QString htmlStr = mDetails->toHtml();
htmlStr.replace(QLatin1String("meta name=\"qrichtext\" content=\"1\""),
QLatin1String(
"meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\""));
ts << htmlStr;
file.close();
}
}
}
void ScamDetectionDetailsDialog::setDetails(const QString &details)
{
mDetails->setHtml(details);
}
void ScamDetectionDetailsDialog::readConfig()
{
KConfigGroup group(
KSharedConfig::openConfig(), "ScamDetectionDetailsDialog");
const QSize size = group.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
void ScamDetectionDetailsDialog::writeConfig()
{
KConfigGroup group(
KSharedConfig::openConfig(), "ScamDetectionDetailsDialog");
group.writeEntry("Size", size());
group.sync();
}
diff --git a/messageviewer/src/scamdetection/scamdetectiondetailsdialog.h b/messageviewer/src/scamdetection/scamdetectiondetailsdialog.h
index 7aa8836c..7af51a4d 100644
--- a/messageviewer/src/scamdetection/scamdetectiondetailsdialog.h
+++ b/messageviewer/src/scamdetection/scamdetectiondetailsdialog.h
@@ -1,48 +1,48 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMDETECTIONDETAILSDIALOG_H
#define SCAMDETECTIONDETAILSDIALOG_H
#include <QDialog>
namespace KPIMTextEdit {
class RichTextEditorWidget;
}
namespace MessageViewer {
class ScamDetectionDetailsDialog : public QDialog
{
Q_OBJECT
public:
explicit ScamDetectionDetailsDialog(QWidget *parent = nullptr);
~ScamDetectionDetailsDialog();
void setDetails(const QString &details);
private:
void slotSaveAs();
void writeConfig();
void readConfig();
KPIMTextEdit::RichTextEditorWidget *mDetails = nullptr;
};
}
#endif // SCAMDETECTIONDETAILSDIALOG_H
diff --git a/messageviewer/src/scamdetection/scamdetectionwarningwidget.cpp b/messageviewer/src/scamdetection/scamdetectionwarningwidget.cpp
index 45772f86..a62a877d 100644
--- a/messageviewer/src/scamdetection/scamdetectionwarningwidget.cpp
+++ b/messageviewer/src/scamdetection/scamdetectionwarningwidget.cpp
@@ -1,114 +1,114 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "scamdetectionwarningwidget.h"
#include "settings/messageviewersettings.h"
#include <KLocalizedString>
#include <QAction>
#include <QMenu>
using namespace MessageViewer;
class MessageViewer::ScamDetectionWarningWidgetPrivate
{
public:
ScamDetectionWarningWidgetPrivate()
{
}
bool mUseInTestApps = false;
};
ScamDetectionWarningWidget::ScamDetectionWarningWidget(QWidget *parent)
: KMessageWidget(parent)
, d(new MessageViewer::ScamDetectionWarningWidgetPrivate)
{
setVisible(false);
setCloseButtonVisible(true);
setMessageType(Warning);
setWordWrap(true);
setText(i18n("This message may be a scam. <a href=\"scamdetails\">(Details...)</a>"));
connect(this, &ScamDetectionWarningWidget::linkActivated, this,
&ScamDetectionWarningWidget::slotShowDetails);
- QMenu *menu = new QMenu();
+ QMenu *menu = new QMenu(this);
QAction *action = new QAction(i18n("Move to Trash"), this);
connect(action, &QAction::triggered, this, &ScamDetectionWarningWidget::moveMessageToTrash);
action->setMenu(menu);
addAction(action);
action = new QAction(i18n("I confirm it's not a scam"), this);
menu->addAction(action);
connect(action, &QAction::triggered, this, &ScamDetectionWarningWidget::slotMessageIsNotAScam);
action = new QAction(i18n("Add email to whitelist"), this);
menu->addAction(action);
connect(action, &QAction::triggered, this, &ScamDetectionWarningWidget::slotAddToWhiteList);
action = new QAction(i18n("Disable scam detection for all messages"), this);
menu->addAction(action);
connect(action, &QAction::triggered, this,
&ScamDetectionWarningWidget::slotDisableScamDetection);
}
ScamDetectionWarningWidget::~ScamDetectionWarningWidget()
{
delete d;
}
void ScamDetectionWarningWidget::setUseInTestApps(bool b)
{
d->mUseInTestApps = b;
}
void ScamDetectionWarningWidget::slotMessageIsNotAScam()
{
Q_EMIT messageIsNotAScam();
setVisible(false);
}
void ScamDetectionWarningWidget::slotShowDetails(const QString &content)
{
if (content == QLatin1String("scamdetails")) {
Q_EMIT showDetails();
}
}
void ScamDetectionWarningWidget::slotShowWarning()
{
animatedShow();
}
void ScamDetectionWarningWidget::slotDisableScamDetection()
{
if (!d->mUseInTestApps) {
MessageViewer::MessageViewerSettings::self()->setScamDetectionEnabled(false);
MessageViewer::MessageViewerSettings::self()->save();
}
setVisible(false);
}
void ScamDetectionWarningWidget::slotAddToWhiteList()
{
setVisible(false);
Q_EMIT addToWhiteList();
}
diff --git a/messageviewer/src/scamdetection/scamdetectionwarningwidget.h b/messageviewer/src/scamdetection/scamdetectionwarningwidget.h
index b03d80dd..10d7f96e 100644
--- a/messageviewer/src/scamdetection/scamdetectionwarningwidget.h
+++ b/messageviewer/src/scamdetection/scamdetectionwarningwidget.h
@@ -1,59 +1,59 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMDETECTIONWARNINGWIDGET_H
#define SCAMDETECTIONWARNINGWIDGET_H
#include "messageviewer_private_export.h"
#include <KMessageWidget>
namespace MessageViewer {
class ScamDetectionWarningWidgetPrivate;
class MESSAGEVIEWER_TESTS_EXPORT ScamDetectionWarningWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit ScamDetectionWarningWidget(QWidget *parent = nullptr);
~ScamDetectionWarningWidget();
void setUseInTestApps(bool b);
public Q_SLOTS:
void slotShowWarning();
Q_SIGNALS:
void showDetails();
void moveMessageToTrash();
void messageIsNotAScam();
void addToWhiteList();
private:
void slotShowDetails(const QString &content);
void slotAddToWhiteList();
void slotDisableScamDetection();
void slotMessageIsNotAScam();
private:
ScamDetectionWarningWidgetPrivate *const d;
};
}
#endif // SCAMDETECTIONWARNINGWIDGET_H
diff --git a/messageviewer/src/scamdetection/scamdetectionwebengine.cpp b/messageviewer/src/scamdetection/scamdetectionwebengine.cpp
index abd89c2f..be699e17 100644
--- a/messageviewer/src/scamdetection/scamdetectionwebengine.cpp
+++ b/messageviewer/src/scamdetection/scamdetectionwebengine.cpp
@@ -1,223 +1,236 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#include "scamdetectionwebengine.h"
#include "scamcheckshorturlmanager.h"
#include "scamdetectiondetailsdialog.h"
#include "settings/messageviewersettings.h"
#include "MessageViewer/ScamCheckShortUrl"
#include "webengineviewer/webenginescript.h"
#include <WebEngineViewer/WebEngineManageScript>
#include <KLocalizedString>
#include <QRegularExpression>
#include <QPointer>
#include <QWebEnginePage>
using namespace MessageViewer;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
QPointer<R> receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
if (receiver) {
(receiver->*memberFunction)(result);
}
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
static QString addWarningColor(const QString &url)
{
const QString error = QStringLiteral("<font color=#FF0000>%1</font>").arg(url);
return error;
}
class MessageViewer::ScamDetectionWebEnginePrivate
{
public:
ScamDetectionWebEnginePrivate()
{
}
QString mDetails;
QPointer<MessageViewer::ScamDetectionDetailsDialog> mDetailsDialog;
};
ScamDetectionWebEngine::ScamDetectionWebEngine(QObject *parent)
: QObject(parent)
, d(new MessageViewer::ScamDetectionWebEnginePrivate)
{
}
ScamDetectionWebEngine::~ScamDetectionWebEngine()
{
delete d;
}
void ScamDetectionWebEngine::scanPage(QWebEnginePage *page)
{
if (MessageViewer::MessageViewerSettings::self()->scamDetectionEnabled()) {
page->runJavaScript(WebEngineViewer::WebEngineScript::findAllAnchorsAndForms(),
WebEngineViewer::WebEngineManageScript::scriptWordId(),
invoke(this, &ScamDetectionWebEngine::handleScanPage));
}
}
void ScamDetectionWebEngine::handleScanPage(const QVariant &result)
{
bool foundScam = false;
d->mDetails.clear();
const QVariantList resultList = result.toList();
if (resultList.count() != 1) {
Q_EMIT resultScanDetection(foundScam);
return;
}
- d->mDetails = QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>");
QRegularExpression ip4regExp(QStringLiteral(
"\\b[0-9]{1,3}\\.[0-9]{1,3}(?:\\.[0-9]{0,3})?(?:\\.[0-9]{0,3})?"));
const QVariantMap mapResult = resultList.at(0).toMap();
const QList<QVariant> lst = mapResult.value(QStringLiteral("anchors")).toList();
for (const QVariant &var : lst) {
QMap<QString, QVariant> mapVariant = var.toMap();
//qDebug()<<" mapVariant"<<mapVariant;
//1) detect if title has a url and title != href
const QString title = mapVariant.value(QStringLiteral("title")).toString();
const QString href = mapVariant.value(QStringLiteral("src")).toString();
const QUrl url(href);
if (!title.isEmpty()) {
if (title.startsWith(QLatin1String("http:"))
|| title.startsWith(QLatin1String("https:"))
|| title.startsWith(QLatin1String("www."))) {
if (title.startsWith(QLatin1String("www."))) {
const QString completUrl = url.scheme() + QLatin1String("://") + title;
if (completUrl != href
&& href != (completUrl + QLatin1Char('/'))) {
foundScam = true;
}
} else {
if (href != title) {
// http://www.kde.org == http://www.kde.org/
if (href != (title + QLatin1Char('/'))) {
foundScam = true;
}
}
}
if (foundScam) {
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a link which reads as '%1' in the text, but actually points to '%2'. This is often the case in scam emails to mislead the recipient",
addWarningColor(title), addWarningColor(href)) + QLatin1String("</li>");
}
}
}
if (!foundScam) {
//2) detect if url href has ip and not server name.
const QString hostname = url.host();
if (hostname.contains(ip4regExp) && !hostname.contains(QLatin1String("127.0.0.1"))) { //hostname
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a link which points to a numerical IP address (%1) instead of a typical textual website address. This is often the case in scam emails.",
addWarningColor(hostname)) + QLatin1String("</li>");
foundScam = true;
} else if (hostname.contains(QLatin1Char('%'))) { //Hexa value for ip
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a link which points to a hexadecimal IP address (%1) instead of a typical textual website address. This is often the case in scam emails.",
addWarningColor(hostname)) + QLatin1String("</li>");
foundScam = true;
} else if (url.toString().contains(QLatin1String("url?q="))) { //4) redirect url.
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a link (%1) which has a redirection",
addWarningColor(url.toString())) + QLatin1String("</li>");
foundScam = true;
} else if ((url.toString().count(QStringLiteral("http://")) > 1)
|| (url.toString().count(QStringLiteral("https://")) > 1)) { //5) more that 1 http in url.
if (!url.toString().contains(QLatin1String("kmail:showAuditLog"))) {
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a link (%1) which contains multiple http://. This is often the case in scam emails.",
addWarningColor(url.toString())) + QLatin1String("</li>");
foundScam = true;
}
}
}
//Check shortUrl
if (!foundScam) {
if (ScamCheckShortUrl::isShortUrl(url)) {
d->mDetails += QLatin1String("<li>") + i18n(
"This email contains a shorturl (%1). It can redirect to another server.", addWarningColor(
url.toString())) + QLatin1String("</li>");
foundScam = true;
}
}
if (!foundScam) {
- const QString text = QUrl(mapVariant.value(QStringLiteral("text")).toString()).toDisplayString();
+ QUrl displayUrl = QUrl(mapVariant.value(QStringLiteral("text")).toString());
+ QString text = displayUrl.toDisplayString(QUrl::StripTrailingSlash|QUrl::NormalizePathSegments);
+ if (text.endsWith(QLatin1String("%22"))) {
+ text.chop(3);
+ }
+ const QUrl normalizedHrefUrl = QUrl(href);
+ QString normalizedHref = normalizedHrefUrl.toDisplayString(QUrl::StripTrailingSlash|QUrl::NormalizePathSegments);
+ normalizedHref.replace(QStringLiteral("%5C"), QStringLiteral("/"));
+ //qDebug() << "text " << text << " href "<<href << " normalizedHref " << normalizedHref;
+
if (!text.isEmpty()) {
if (text.startsWith(QLatin1String("http:/")) || text.startsWith(QLatin1String("https:/"))) {
- if (text != href) {
- if (href != (text + QLatin1Char('/'))) {
- if (href.toHtmlEscaped() != text) {
- if (QString::fromUtf8(QUrl(text).toEncoded()) != href) {
- if (QUrl(href).toDisplayString() != text) {
- if (QUrl::fromUserInput(text).toDisplayString(QUrl::NormalizePathSegments) != QUrl::fromUserInput(href).toDisplayString(QUrl::NormalizePathSegments)) {
+ if (text != normalizedHref) {
+ if (normalizedHref != (text + QLatin1Char('/'))) {
+ if (normalizedHref.toHtmlEscaped() != text) {
+ if (QString::fromUtf8(QUrl(text).toEncoded()) != normalizedHref) {
+ if (QUrl(normalizedHref).toDisplayString() != text) {
+ const bool qurlqueryequal = displayUrl.query() == normalizedHrefUrl.query();
+ const QString displayUrlWithoutQuery = displayUrl.toDisplayString(QUrl::RemoveQuery|QUrl::StripTrailingSlash|QUrl::NormalizePathSegments);
+ const QString hrefUrlWithoutQuery = normalizedHrefUrl.toDisplayString(QUrl::RemoveQuery|QUrl::StripTrailingSlash|QUrl::NormalizePathSegments);
+ if (qurlqueryequal && (displayUrlWithoutQuery + QLatin1Char('/') != hrefUrlWithoutQuery)) {
d->mDetails += QLatin1String("<li>") + i18n(
- "This email contains a link which reads as '%1' in the text, but actually points to '%2'. This is often the case in scam emails to mislead the recipient",
- addWarningColor(text), addWarningColor(href)) + QLatin1String("</li>");
+ "This email contains a link which reads as '%1' in the text, but actually points to '%2'. This is often the case in scam emails to mislead the recipient",
+ addWarningColor(text), addWarningColor(normalizedHref)) + QLatin1String("</li>");
foundScam = true;
}
}
}
}
}
}
}
}
}
}
if (mapResult.value(QStringLiteral("forms")).toInt() > 0) {
d->mDetails += QLatin1String("<li></b>") + i18n(
"Message contains form element. This is often the case in scam emails.")
+ QLatin1String("</b></li>");
foundScam = true;
}
- d->mDetails += QLatin1String("</ul>");
- // qDebug()<<" d->mDetails "<< d->mDetails;
if (foundScam) {
- Q_EMIT messageMayBeAScam();
+ d->mDetails.prepend(QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>"));
+ d->mDetails += QLatin1String("</ul>");
+ if (foundScam) {
+ Q_EMIT messageMayBeAScam();
+ }
}
Q_EMIT resultScanDetection(foundScam);
}
void ScamDetectionWebEngine::showDetails()
{
if (!d->mDetailsDialog) {
d->mDetailsDialog = new MessageViewer::ScamDetectionDetailsDialog;
}
d->mDetailsDialog->setDetails(d->mDetails);
d->mDetailsDialog->show();
}
diff --git a/messageviewer/src/scamdetection/scamdetectionwebengine.h b/messageviewer/src/scamdetection/scamdetectionwebengine.h
index 487d6470..78b59f4e 100644
--- a/messageviewer/src/scamdetection/scamdetectionwebengine.h
+++ b/messageviewer/src/scamdetection/scamdetectionwebengine.h
@@ -1,53 +1,53 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMDETECTIONWEBENGINE_H
#define SCAMDETECTIONWEBENGINE_H
#include <QObject>
#include <QVariant>
#include "messageviewer_export.h"
class QWebEnginePage;
namespace MessageViewer {
class ScamDetectionWebEnginePrivate;
class MESSAGEVIEWER_EXPORT ScamDetectionWebEngine : public QObject
{
Q_OBJECT
public:
explicit ScamDetectionWebEngine(QObject *parent = nullptr);
~ScamDetectionWebEngine();
void scanPage(QWebEnginePage *page);
public Q_SLOTS:
void showDetails();
private Q_SLOTS:
void handleScanPage(const QVariant &result);
Q_SIGNALS:
void messageMayBeAScam();
void resultScanDetection(bool foundScam);
private:
ScamDetectionWebEnginePrivate *const d;
};
}
#endif // SCAMDETECTIONWEBENGINE_H
diff --git a/messageviewer/src/scamdetection/scamexpandurljob.cpp b/messageviewer/src/scamdetection/scamexpandurljob.cpp
index 9b17037d..d840c8a5 100644
--- a/messageviewer/src/scamdetection/scamexpandurljob.cpp
+++ b/messageviewer/src/scamdetection/scamexpandurljob.cpp
@@ -1,112 +1,120 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#include "scamexpandurljob.h"
#include "messageviewer_debug.h"
#include <Libkdepim/BroadcastStatus>
#include <PimCommon/NetworkManager>
#include <KLocalizedString>
#include <QJsonDocument>
#include <QNetworkAccessManager>
#include <QNetworkConfigurationManager>
#include <QNetworkReply>
using namespace MessageViewer;
class MessageViewer::ScamExpandUrlJobPrivate
{
public:
ScamExpandUrlJobPrivate()
{
}
~ScamExpandUrlJobPrivate()
{
}
QNetworkAccessManager *mNetworkAccessManager = nullptr;
};
ScamExpandUrlJob::ScamExpandUrlJob(QObject *parent)
: QObject(parent)
, d(new ScamExpandUrlJobPrivate)
{
d->mNetworkAccessManager = new QNetworkAccessManager(this);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this,
&ScamExpandUrlJob::slotExpandFinished);
}
ScamExpandUrlJob::~ScamExpandUrlJob()
{
delete d;
}
void ScamExpandUrlJob::expandedUrl(const QUrl &url)
{
if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) {
KPIM::BroadcastStatus::instance()->setStatusMsg(i18n(
"No network connection detected, we cannot expand url."));
deleteLater();
return;
}
- const QUrl newUrl(QStringLiteral("http://api.longurl.org/v2/expand?url=%1&format=json").arg(
+ const QUrl newUrl(QStringLiteral("https://lengthenurl.info/api/longurl/shorturl/?inputURL=%1&format=json").arg(
url.url()));
qCDebug(MESSAGEVIEWER_LOG) << " newUrl " << newUrl;
QNetworkReply *reply = d->mNetworkAccessManager->get(QNetworkRequest(newUrl));
reply->setProperty("shortUrl", url.url());
connect(reply,
- QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this,
+ qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this,
&ScamExpandUrlJob::slotError);
}
void ScamExpandUrlJob::slotExpandFinished(QNetworkReply *reply)
{
QUrl shortUrl;
if (!reply->property("shortUrl").isNull()) {
shortUrl.setUrl(reply->property("shortUrl").toString());
}
- QJsonDocument jsonDoc = QJsonDocument::fromBinaryData(reply->readAll());
+ const QByteArray ba = reply->readAll();
+ //qDebug() << " reply->readAll()" << ba;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(ba);
reply->deleteLater();
if (!jsonDoc.isNull()) {
const QMap<QString, QVariant> map = jsonDoc.toVariant().toMap();
QUrl longUrl;
- const QVariant longUrlVar = map.value(QStringLiteral("long-url"));
+ const QVariant longUrlVar = map.value(QStringLiteral("LongURL"));
if (longUrlVar.isValid()) {
longUrl.setUrl(longUrlVar.toString());
} else {
+ qCWarning(MESSAGEVIEWER_LOG) << "JSon is not corect" << ba;
+ KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Impossible to expand \'%1\'.",
+ shortUrl.url()));
deleteLater();
return;
}
KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Short url \'%1\' redirects to \'%2\'.",
shortUrl.url(),
longUrl.toDisplayString()));
+ } else {
+ KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Impossible to expand \'%1\'.",
+ shortUrl.url()));
}
deleteLater();
}
void ScamExpandUrlJob::slotError(QNetworkReply::NetworkError error)
{
Q_EMIT expandUrlError(error);
deleteLater();
}
diff --git a/messageviewer/src/scamdetection/scamexpandurljob.h b/messageviewer/src/scamdetection/scamexpandurljob.h
index 70b654e6..92d55a6f 100644
--- a/messageviewer/src/scamdetection/scamexpandurljob.h
+++ b/messageviewer/src/scamdetection/scamexpandurljob.h
@@ -1,49 +1,49 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#ifndef SCAMEXPANDURLJOB_H
#define SCAMEXPANDURLJOB_H
#include <QObject>
#include <QNetworkReply>
#include "messageviewer_export.h"
namespace MessageViewer {
class ScamExpandUrlJobPrivate;
class MESSAGEVIEWER_EXPORT ScamExpandUrlJob : public QObject
{
Q_OBJECT
public:
explicit ScamExpandUrlJob(QObject *parent = nullptr);
~ScamExpandUrlJob();
void expandedUrl(const QUrl &url);
Q_SIGNALS:
void urlExpanded(const QString &shortUrl, const QString &expandedUrl);
void expandUrlError(QNetworkReply::NetworkError error);
private:
void slotError(QNetworkReply::NetworkError error);
void slotExpandFinished(QNetworkReply *reply);
private:
ScamExpandUrlJobPrivate *const d;
};
}
#endif // SCAMEXPANDURLJOB_H
diff --git a/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.cpp b/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.cpp
index bf4ebdae..1f4ce1ae 100644
--- a/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.cpp
+++ b/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.cpp
@@ -1,112 +1,112 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "scamdetectionwebengine_gui.h"
#include "scamdetection/scamdetectionwarningwidget.h"
#include "scamdetection/scamdetectionwebengine.h"
#include <QUrl>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QWebEngineView>
ScamDetectionWebEngineTestWidget::ScamDetectionWebEngineTestWidget(const QString &filename, QWidget *parent)
: QWidget(parent)
{
mScamDetection = new MessageViewer::ScamDetectionWebEngine(this);
QVBoxLayout *lay = new QVBoxLayout(this);
mScamWarningWidget = new MessageViewer::ScamDetectionWarningWidget();
mScamWarningWidget->setUseInTestApps(true);
lay->addWidget(mScamWarningWidget);
mWebEngineView = new QWebEngineView;
connect(mWebEngineView, &QWebEngineView::loadFinished, this,
&ScamDetectionWebEngineTestWidget::slotLoadFinished);
lay->addWidget(mWebEngineView);
connect(mScamDetection, &MessageViewer::ScamDetectionWebEngine::messageMayBeAScam,
mScamWarningWidget, &MessageViewer::ScamDetectionWarningWidget::slotShowWarning);
connect(mScamWarningWidget, &MessageViewer::ScamDetectionWarningWidget::showDetails,
mScamDetection, &MessageViewer::ScamDetectionWebEngine::showDetails);
mWebEngineView->load(QUrl::fromLocalFile(filename));
QHBoxLayout *hbox = new QHBoxLayout;
QPushButton *openFile = new QPushButton(QStringLiteral("Open html..."));
connect(openFile, &QPushButton::clicked, this, &ScamDetectionWebEngineTestWidget::slotOpenHtml);
hbox->addWidget(openFile);
lay->addLayout(hbox);
}
ScamDetectionWebEngineTestWidget::~ScamDetectionWebEngineTestWidget()
{
}
void ScamDetectionWebEngineTestWidget::slotLoadFinished()
{
mScamDetection->scanPage(mWebEngineView->page());
}
void ScamDetectionWebEngineTestWidget::slotOpenHtml()
{
const QString fileName = QFileDialog::getOpenFileName(nullptr, QString(),
QString(), QStringLiteral("*.html"));
if (!fileName.isEmpty()) {
mScamWarningWidget->setVisible(false);
mWebEngineView->load(QUrl::fromLocalFile(fileName));
}
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+[url]"),
QStringLiteral("URL of an html file to be opened")));
parser.process(app);
QString fileName;
if (parser.positionalArguments().count()) {
fileName = parser.positionalArguments().at(0);
} else {
fileName
= QFileDialog::getOpenFileName(nullptr, QString(), QString(),
QStringLiteral("HTML File (*.html)"));
}
if (fileName.isEmpty()) {
return 0;
}
ScamDetectionWebEngineTestWidget *w = new ScamDetectionWebEngineTestWidget(fileName);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.h b/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.h
index 620ff76b..d3ee2b0c 100644
--- a/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.h
+++ b/messageviewer/src/scamdetection/tests/scamdetectionwebengine_gui.h
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TEST_SCAMDETECTIONWEBENGINE_GUI_H
#define TEST_SCAMDETECTIONWEBENGINE_GUI_H
#include <QWidget>
namespace MessageViewer {
class ScamDetectionWarningWidget;
class ScamDetectionWebEngine;
}
class QWebEngineView;
class ScamDetectionWebEngineTestWidget : public QWidget
{
Q_OBJECT
public:
explicit ScamDetectionWebEngineTestWidget(const QString &filename, QWidget *parent = nullptr);
~ScamDetectionWebEngineTestWidget();
private Q_SLOTS:
void slotLoadFinished();
void slotOpenHtml();
private:
MessageViewer::ScamDetectionWarningWidget *mScamWarningWidget;
MessageViewer::ScamDetectionWebEngine *mScamDetection;
QWebEngineView *mWebEngineView;
};
#endif
diff --git a/messageviewer/src/settings/messageviewer.kcfg.cmake b/messageviewer/src/settings/messageviewer.kcfg.cmake
index 6dc23a9a..acbae941 100644
--- a/messageviewer/src/settings/messageviewer.kcfg.cmake
+++ b/messageviewer/src/settings/messageviewer.kcfg.cmake
@@ -1,254 +1,254 @@
<?xml version="1.0" encoding="utf-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<include>QFontDatabase</include>
<kcfgfile name="mailviewerrc" />
<group name="Print">
<entry name="PrintSelectedText" type="Bool" key="print-selected-text">
<label>Only print selected text in viewer</label>
<default>false</default>
</entry>
<entry name="RespectExpandCollapseSettings" type="Bool" key="respect-expand-collapse">
<label>Respect expand/collapse quote mark which is defined in settings during printing.</label>
<default>false</default>
</entry>
<entry name="PrintBackgroundColorImages" type="Bool" key="print-background-color-images">
<label>Specifies whether the background color and images are also drawn when the page is printed.</label>
<default>true</default>
</entry>
<entry name="AlwaysShowEncryptionSignatureDetails" type="Bool" key="print-always-show-encryption-signature-details">
<label>Always show encryption/signature details when we print message</label>
<default>false</default>
</entry>
</group>
<group name="Fonts">
<entry name="FixedFont" type="Font" key="fixed-font">
<default code="true">QFontDatabase::systemFont(QFontDatabase::FixedFont)</default>
</entry>
<entry name="MinimumFontSize" type="Int">
<label>When we render html do not use font size inferior to minimum size.</label>
<default>8</default>
</entry>
<entry name="BodyFont" type="Font" key="body-font">
<label>Specifies the font to use for the message body</label>
<default code="true">QFontDatabase::systemFont(QFontDatabase::GeneralFont)</default>
</entry>
<entry name="PrintFont" type="Font" key="print-font">
<label>Specifies the font to use for printing</label>
<default code="true">QFontDatabase::systemFont(QFontDatabase::GeneralFont)</default>
</entry>
</group>
<group name="Security">
<entry name="CheckPhishingUrl" type="Bool">
<default>false</default>
</entry>
</group>
<group name="Reader">
<entry name="CloseAfterReplyOrForward" type="Bool">
<label>Close message window after replying or forwarding</label>
<default>false</default>
</entry>
<entry name="ZoomFactor" type="Double">
<default>100.0</default>
</entry>
<entry name="ShowEmoticons" type="Bool">
<default>true</default>
</entry>
<entry name="AutoImportKeys" type="Bool">
<default>false</default>
</entry>
<entry name="ShowExpandQuotesMark" type="Bool">
<default>false</default>
<label>Show expand/collapse quote marks</label>
<whatsthis>Enable this option to show different levels of quoted text. Disable to hide the levels of quoted text.</whatsthis>
</entry>
<entry name="CollapseQuoteLevelSpin" type="Int">
<label>Automatic collapse level:</label>
<default>3</default>
<min>0</min>
<max>10</max>
</entry>
<entry name="ShrinkQuotes" type="Bool">
<default>false</default>
<label>Reduce font size for quoted text</label>
<whatsthis>Enable this option to show quoted text with a smaller font.</whatsthis>
</entry>
<entry name="AlwaysDecrypt" type="Bool">
<default>false</default>
<label>Always decrypt messages when viewing or ask before decrypting</label>
</entry>
<entry name="MimeTreeMode2" type="Enum">
<label>Message Structure Viewer</label>
<choices>
<choice name="Never">
<label>Show never</label>
</choice>
<choice name="Always">
<label>Show always</label>
</choice>
</choices>
<default>Never</default>
</entry>
<entry name="MimePaneHeight" type="Int">
<default>100</default>
</entry>
<entry name="MessagePaneHeight" type="Int">
<default>180</default>
</entry>
<entry name="headerPluginStyleName" type="String" key="header-plugin-style-name">
<label>What style of headers should be displayed</label>
<default>defaultgrantlee</default>
</entry>
<entry name="headerStyle" type="String" key="header-style">
<label>What style of headers should be displayed</label>
<default>fancy</default>
</entry>
<entry name="headerSetDisplayed" type="String" key="header-set-displayed">
<label>How much of headers should be displayed</label>
<default>rich</default>
</entry>
<entry name="htmlMail" type="Bool">
<label>Prefer HTML to plain text</label>
<default>false</default>
</entry>
<entry name="htmlLoadExternal" type="Bool">
<label>Allow messages to load external references from the Internet</label>
<default>false</default>
</entry>
<entry name="attachmentStrategy" type="String" key="attachment-strategy">
<label>How attachments are shown</label>
<default>Smart</default>
</entry>
<entry name="RecycleQuoteColors" type="Bool">
<label>Specifies whether to reuse the quote color, beyond the 3rd level</label>
<default>false</default>
</entry>
<entry name="AccessKeyEnabled" type="Bool">
<label>Activate Access Key</label>
<default>true</default>
</entry>
<entry name="ScamDetectionEnabled" type="Bool">
<label>KMail can analyze messages for suspected email scams by looking for common techniques used to deceive you</label>
<default>true</default>
</entry>
<entry name="ScamDetectionWhiteList" type="StringList">
<label>List of emails in scam white list</label>
<default></default>
</entry>
<entry name="MailTrackingUrlEnabled" type="Bool">
<label>Enable Mail Tracking Url</label>
<default>true</default>
</entry>
</group>
<group name="MDN">
<entry name="notSendWhenEncrypted" type="Bool" key="not-send-when-encrypted">
<label>Do not send MDNs in response to encrypted messages</label>
<default>true</default>
</entry>
<entry name="DefaultPolicy" type="Int" key="default-policy">
<label>Specifies the default policy to use, for the Message Disposition Notifications (for internal use only)</label>
<default>0</default>
</entry>
<entry name="QuoteMessage" type="Int" key="quote-message">
<label>Specifies the default quoting action to take, when replying to a message (for internal use only)</label>
<default>0</default>
</entry>
</group>
<group name="Behaviour">
<entry name="DelayedMarkAsRead" type="Bool">
<default>true</default>
</entry>
<entry name="DelayedMarkTime" type="UInt">
<default>0</default>
</entry>
</group>
<group name="Invitations">
<entry name="LegacyMangleFromToHeaders" type="Bool">
<label>Mangle From:/To: headers in replies to replies</label>
<whatsthis>Microsoft Outlook has a number of shortcomings in its implementation of the iCalendar standard; this option works around one of them. If you have problems with Outlook users not being able to get your replies, try setting this option.</whatsthis>
<default>${LEGACY_MANGLE_FROM_TO_HEADERS}</default>
</entry>
<entry name="LegacyBodyInvites" type="Bool">
<label>Send groupware invitations in the mail body</label>
<whatsthis>Microsoft Outlook has a number of shortcomings in its implementation of the iCalendar standard; this option works around one of them. If you have problems with Outlook users not being able to get your invitations, try setting this option.</whatsthis>
<default>${LEGACY_BODY_INVITES}</default>
</entry>
<entry name="ExchangeCompatibleInvitations" type="Bool">
<label>Exchange-compatible invitation naming</label>
<whatsthis>Microsoft Outlook, when used in combination with a Microsoft Exchange server, has a problem understanding standards-compliant groupware email. Turn this option on to send groupware invitations in a way that Microsoft Exchange understands.</whatsthis>
<default>${EXCHANGE_COMPATIBLE_INVITATIONS}</default>
</entry>
<entry name="AutomaticSending" type="Bool">
<label>Automatic invitation sending</label>
<whatsthis>When this is checked, you will not see the mail composer window. Instead, all invitation mails are sent automatically. If you want to see the mail before sending it, you can uncheck this option. However, be aware that the text in the composer window is in iCalendar syntax, and you should not try modifying it by hand.</whatsthis>
<default>true</default>
</entry>
<entry name="DeleteInvitationEmailsAfterSendingReply" type="Bool">
<label>Delete invitation emails after the reply to them has been sent</label>
<whatsthis>When this is checked, received invitation emails that have been replied to will be moved to the Trash folder, once the reply has been successfully sent.</whatsthis>
- <default>true</default>
+ <default>false</default>
</entry>
<entry name="AskForCommentWhenReactingToInvitation" type="Enum">
<label></label>
<whatsthis></whatsthis>
<choices>
<choice name="NeverAsk"/>
<choice name="AskForAllButAcceptance"/>
<choice name="AlwaysAsk"/>
</choices>
<default>AskForAllButAcceptance</default>
</entry>
</group>
<!-- FIXME: Make a separate setting for this for the composer and the reader. Only the
reader setting should be here.
Regression from r1021989.
-->
<group name="Composer">
<entry name="UseFixedFont" type="Bool" key="use-fixed-font">
<label>Use Fi&amp;xed Font</label>
<default>false</default>
</entry>
</group>
<group name="Todo">
<entry name="LastSelectedFolder" type="LongLong">
<whatsthis>The most recent selected folder using for Todo.</whatsthis>
<default>-1</default>
</entry>
</group>
<group name="Event">
<entry name="LastEventSelectedFolder" type="LongLong">
<whatsthis>The most recent selected folder using for Event.</whatsthis>
<default>-1</default>
</entry>
</group>
<group name="Note">
<entry name="LastNoteSelectedFolder" type="LongLong">
<whatsthis>The most recent selected folder using for Notes.</whatsthis>
<default>-1</default>
</entry>
</group>
</kcfg>
diff --git a/messageviewer/src/settings/messageviewersettings.cpp b/messageviewer/src/settings/messageviewersettings.cpp
index c6eff7dc..64833b07 100644
--- a/messageviewer/src/settings/messageviewersettings.cpp
+++ b/messageviewer/src/settings/messageviewersettings.cpp
@@ -1,62 +1,63 @@
/*
This file is part of KMail.
Copyright (c) 2005 David Faure <faure@kde.org>
This program 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.
+ 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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#include "messageviewersettings.h"
#include <QTimer>
using namespace MessageViewer;
MessageViewerSettings *MessageViewerSettings::mSelf = nullptr;
MessageViewerSettings *MessageViewerSettings::self()
{
if (!mSelf) {
mSelf = new MessageViewerSettings();
mSelf->load();
}
return mSelf;
}
MessageViewerSettings::MessageViewerSettings()
{
mConfigSyncTimer = new QTimer(this);
mConfigSyncTimer->setSingleShot(true);
connect(mConfigSyncTimer, &QTimer::timeout, this, &MessageViewerSettings::slotSyncNow);
}
void MessageViewerSettings::requestSync()
{
if (!mConfigSyncTimer->isActive()) {
mConfigSyncTimer->start(0);
}
}
void MessageViewerSettings::slotSyncNow()
{
config()->sync();
}
MessageViewerSettings::~MessageViewerSettings()
{
}
diff --git a/messageviewer/src/settings/messageviewersettings.h b/messageviewer/src/settings/messageviewersettings.h
index a0f4bc09..fa6b7dec 100644
--- a/messageviewer/src/settings/messageviewersettings.h
+++ b/messageviewer/src/settings/messageviewersettings.h
@@ -1,57 +1,58 @@
/*
This file is part of KMail.
Copyright (c) 2005 David Faure <faure@kde.org>
This program 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.
+ 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.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#ifndef MESSAGEVIEWER_MESSAGEVIEWERSETTINGS_H
#define MESSAGEVIEWER_MESSAGEVIEWERSETTINGS_H
#include "globalsettings_messageviewer.h"
class QTimer;
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT MessageViewerSettings : public MessageViewer::MessageViewerSettingsBase
{
Q_OBJECT
public:
static MessageViewerSettings *self();
- /** Call this slot instead of directly @ref KConfig::sync() to
+ /** Call this slot instead of directly KConfig::sync() to
minimize the overall config writes. Calling this slot will
schedule a sync of the application config file using a timer, so
that many consecutive calls can be condensed into a single
sync, which is more efficient. */
void requestSync();
private Q_SLOTS:
void slotSyncNow();
private:
MessageViewerSettings();
~MessageViewerSettings() override;
static MessageViewerSettings *mSelf;
QTimer *mConfigSyncTimer = nullptr;
};
}
#endif
diff --git a/messageviewer/src/ui/settings.ui b/messageviewer/src/ui/settings.ui
index 63dff324..ae42428d 100644
--- a/messageviewer/src/ui/settings.ui
+++ b/messageviewer/src/ui/settings.ui
@@ -1,261 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<author>/* Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia &lt;andras@kdab.net&gt;
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.
*/</author>
<class>Settings</class>
<widget class="QWidget" name="Settings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<height>456</height>
</rect>
</property>
<property name="windowTitle">
<string>Viewer settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>O&amp;verride character encoding:</string>
</property>
<property name="buddy">
<cstring>overrideCharacterEncoding</cstring>
</property>
</widget>
</item>
<item row="2" column="2">
- <widget class="KComboBox" name="overrideCharacterEncoding">
+ <widget class="QComboBox" name="overrideCharacterEncoding">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="kcfg_MinimumFontSize">
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>30</number>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="kcfg_AccessKeyEnabled">
<property name="text">
<string>Enable access key</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_ShrinkQuotes">
<property name="text">
<string>Reduce font size for &amp;quoted text</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_ShowExpandQuotesMark">
<property name="text">
<string>Show &amp;expand/collapse quote marks</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>24</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="collapseQuoteLevelLabel">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Automatic collapse &amp;level:</string>
</property>
<property name="buddy">
<cstring>kcfg_CollapseQuoteLevelSpin</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="kcfg_CollapseQuoteLevelSpin">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>3</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item row="3" column="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="9" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Minimum font size:</string>
</property>
<property name="buddy">
<cstring>kcfg_MinimumFontSize</cstring>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
- <class>KComboBox</class>
+ <class>QComboBox</class>
<extends>QComboBox</extends>
<header>kcombobox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>kcfg_AccessKeyEnabled</tabstop>
<tabstop>kcfg_ShrinkQuotes</tabstop>
<tabstop>kcfg_ShowExpandQuotesMark</tabstop>
<tabstop>kcfg_CollapseQuoteLevelSpin</tabstop>
<tabstop>overrideCharacterEncoding</tabstop>
<tabstop>kcfg_MinimumFontSize</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>kcfg_ShowExpandQuotesMark</sender>
<signal>toggled(bool)</signal>
<receiver>collapseQuoteLevelLabel</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>94</x>
<y>125</y>
</hint>
<hint type="destinationlabel">
<x>138</x>
<y>153</y>
</hint>
</hints>
</connection>
<connection>
<sender>kcfg_ShowExpandQuotesMark</sender>
<signal>toggled(bool)</signal>
<receiver>kcfg_CollapseQuoteLevelSpin</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>179</x>
<y>125</y>
</hint>
<hint type="destinationlabel">
<x>228</x>
<y>153</y>
</hint>
</hints>
</connection>
</connections>
</ui>
diff --git a/messageviewer/src/widgets/autotests/CMakeLists.txt b/messageviewer/src/utils/autotests/CMakeLists.txt
similarity index 57%
copy from messageviewer/src/widgets/autotests/CMakeLists.txt
copy to messageviewer/src/utils/autotests/CMakeLists.txt
index 45e6d780..bc34175b 100644
--- a/messageviewer/src/widgets/autotests/CMakeLists.txt
+++ b/messageviewer/src/utils/autotests/CMakeLists.txt
@@ -1,12 +1,10 @@
-
-macro(add_messageviewer_widget_unittest _source)
+macro(add_messageviewer_utils_unittest _source)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source}
TEST_NAME ${_name}
NAME_PREFIX "messageviewer-"
LINK_LIBRARIES KF5::MessageViewer KF5::WebEngineViewer KF5::Libkleo QGpgme Qt5::Test
)
endmacro ()
-add_messageviewer_widget_unittest(mailtrackingwarningwidgettest.cpp)
-add_messageviewer_widget_unittest(mailtrackingdetailsdialogtest.cpp)
+add_messageviewer_utils_unittest(messageviewerutilstest.cpp)
diff --git a/messageviewer/src/utils/autotests/messageviewerutilstest.cpp b/messageviewer/src/utils/autotests/messageviewerutilstest.cpp
new file mode 100644
index 00000000..9ad2627e
--- /dev/null
+++ b/messageviewer/src/utils/autotests/messageviewerutilstest.cpp
@@ -0,0 +1,46 @@
+/*
+ Copyright (C) 2018-2019 Montel Laurent <montel@kde.org>
+
+ This program 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.
+
+ 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
+*/
+
+#include "messageviewerutilstest.h"
+#include "utils/messageviewerutil.h"
+#include <QTest>
+QTEST_GUILESS_MAIN(MessageViewerUtilsTest)
+
+MessageViewerUtilsTest::MessageViewerUtilsTest(QObject *parent)
+ : QObject(parent)
+{
+}
+
+void MessageViewerUtilsTest::shouldExcludeHeader_data()
+{
+ QTest::addColumn<QString>("header");
+ QTest::addColumn<bool>("exclude");
+ QTest::newRow("emptylist") << QString() << false;
+ QTest::newRow("REFRESH1") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv=\'REFRESH\'></head>") << true;
+ QTest::newRow("REFRESH2") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv=\"REFRESH\"></head>") << true;
+ QTest::newRow("REFRESH3") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv=\"refresh\"></head>") << true;
+ QTest::newRow("REFRESH4") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv=\"&#82;EFRESH\"></head>") << true;
+ QTest::newRow("REFRESH5") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv=\'&#82;EFRESH\'></head>") << true;
+ QTest::newRow("REFRESH6") << QStringLiteral("<meta content=\"0;URL=http://www.kde.org\" http-equiv= \"REFRESH\"></head>") << true;
+}
+
+void MessageViewerUtilsTest::shouldExcludeHeader()
+{
+ QFETCH(QString, header);
+ QFETCH(bool, exclude);
+ QCOMPARE(MessageViewer::Util::excludeExtraHeader(header), exclude);
+}
diff --git a/messageviewer/autotests/zoomactionmenutest.h b/messageviewer/src/utils/autotests/messageviewerutilstest.h
similarity index 65%
copy from messageviewer/autotests/zoomactionmenutest.h
copy to messageviewer/src/utils/autotests/messageviewerutilstest.h
index 164b413c..6b73c59d 100644
--- a/messageviewer/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/utils/autotests/messageviewerutilstest.h
@@ -1,34 +1,34 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2018-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef MESSAGEVIEWERUTILSTEST_H
+#define MESSAGEVIEWERUTILSTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class MessageViewerUtilsTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
+ explicit MessageViewerUtilsTest(QObject *parent = nullptr);
+ ~MessageViewerUtilsTest() = default;
private Q_SLOTS:
- void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
+ void shouldExcludeHeader_data();
+ void shouldExcludeHeader();
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // MESSAGEVIEWERUTILSTEST_H
diff --git a/messageviewer/src/utils/messageviewerutil.cpp b/messageviewer/src/utils/messageviewerutil.cpp
index bcc56739..3eb374bd 100644
--- a/messageviewer/src/utils/messageviewerutil.cpp
+++ b/messageviewer/src/utils/messageviewerutil.cpp
@@ -1,542 +1,552 @@
/*******************************************************************************
**
** Filename : util
** Created on : 03 April, 2005
** Copyright : (c) 2005 Till Adam
** Email : <adam@kde.org>
**
*******************************************************************************/
/*******************************************************************************
**
** 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.
**
** It 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.
**
*******************************************************************************/
#include "messageviewer/messageviewerutil.h"
#include "messageviewerutil_p.h"
#include "kpimtextedit/texttospeech.h"
#include <MimeTreeParser/NodeHelper>
#include "messageviewer_debug.h"
#include "MessageCore/MessageCoreSettings"
#include "MessageCore/NodeHelper"
#include "MessageCore/StringUtil"
#include "PimCommon/RenameFileDialog"
#include <AkonadiCore/item.h>
#include <kmbox/mbox.h>
#include <KMime/Message>
#include <KFileWidget>
#include <kcharsets.h>
#include <KLocalizedString>
#include <kmessagebox.h>
#include <QFileDialog>
#include <ktoolinvocation.h>
#include <KJobWidgets>
#include <KIO/StatJob>
#include <KIO/FileCopyJob>
#include <KRecentDirs>
#include <QAction>
#include <QIcon>
#include <QTemporaryFile>
#include <QWidget>
#include <QDBusConnectionInterface>
#include <QActionGroup>
#include <QDesktopServices>
+#include <QRegularExpression>
using namespace MessageViewer;
bool Util::checkOverwrite(const QUrl &url, QWidget *w)
{
bool fileExists = false;
if (url.isLocalFile()) {
fileExists = QFile::exists(url.toLocalFile());
} else {
auto job = KIO::stat(url, KIO::StatJob::DestinationSide, 0);
KJobWidgets::setWindow(job, w);
fileExists = job->exec();
}
if (fileExists) {
if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(
w,
i18n("A file named \"%1\" already exists. "
"Are you sure you want to overwrite it?", url.toDisplayString()),
i18n("Overwrite File?"),
KStandardGuiItem::overwrite())) {
return false;
}
}
return true;
}
bool Util::handleUrlWithQDesktopServices(const QUrl &url)
{
#if defined Q_OS_WIN || defined Q_OS_MACX
QDesktopServices::openUrl(url);
return true;
#else
// Always handle help through khelpcenter or browser
if (url.scheme() == QLatin1String("help")) {
QDesktopServices::openUrl(url);
return true;
}
return false;
#endif
}
KMime::Content::List Util::allContents(const KMime::Content *message)
{
KMime::Content::List result;
KMime::Content *child = MessageCore::NodeHelper::firstChild(message);
if (child) {
result += child;
result += allContents(child);
}
KMime::Content *next = MessageCore::NodeHelper::nextSibling(message);
if (next) {
result += next;
result += allContents(next);
}
return result;
}
bool Util::saveContents(QWidget *parent, const KMime::Content::List &contents, QList<QUrl> &urlList)
{
QUrl url, dirUrl;
QString recentDirClass;
QUrl currentFolder;
const bool multiple = (contents.count() > 1);
if (multiple) {
// get the dir
dirUrl = QFileDialog::getExistingDirectoryUrl(parent, i18n(
"Save Attachments To"),
KFileWidget::getStartUrl(QUrl(QStringLiteral(
"kfiledialog:///attachmentDir")),
recentDirClass));
if (!dirUrl.isValid()) {
return false;
}
// we may not get a slash-terminated url out of KFileDialog
if (!dirUrl.path().endsWith(QLatin1Char('/'))) {
dirUrl.setPath(dirUrl.path() + QLatin1Char('/'));
}
currentFolder = dirUrl;
} else {
// only one item, get the desired filename
KMime::Content *content = contents.first();
QString fileName = MimeTreeParser::NodeHelper::fileName(content);
fileName = MessageCore::StringUtil::cleanFileName(fileName);
if (fileName.isEmpty()) {
fileName = i18nc("filename for an unnamed attachment", "attachment.1");
}
QUrl localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral(
"kfiledialog:///attachmentDir")),
recentDirClass);
localUrl.setPath(localUrl.path() + QLatin1Char('/') + fileName);
QFileDialog::Options options = QFileDialog::DontConfirmOverwrite;
url = QFileDialog::getSaveFileUrl(parent, i18n("Save Attachment"), localUrl,
QString(), nullptr, options);
if (url.isEmpty()) {
return false;
}
currentFolder = KIO::upUrl(url);
}
if (!recentDirClass.isEmpty()) {
KRecentDirs::add(recentDirClass, currentFolder.path());
}
QMap< QString, int > renameNumbering;
bool globalResult = true;
int unnamedAtmCount = 0;
PimCommon::RenameFileDialog::RenameFileDialogResult result
= PimCommon::RenameFileDialog::RENAMEFILE_IGNORE;
for (KMime::Content *content : qAsConst(contents)) {
QUrl curUrl;
if (!dirUrl.isEmpty()) {
curUrl = dirUrl;
QString fileName = MimeTreeParser::NodeHelper::fileName(content);
fileName = MessageCore::StringUtil::cleanFileName(fileName);
if (fileName.isEmpty()) {
++unnamedAtmCount;
fileName = i18nc("filename for the %1-th unnamed attachment",
"attachment.%1", unnamedAtmCount);
}
if (!curUrl.path().endsWith(QLatin1Char('/'))) {
curUrl.setPath(curUrl.path() + QLatin1Char('/'));
}
curUrl.setPath(curUrl.path() + fileName);
} else {
curUrl = url;
}
if (!curUrl.isEmpty()) {
//Bug #312954
if (multiple && (curUrl.fileName() == QLatin1String("smime.p7s"))) {
continue;
}
// Rename the file if we have already saved one with the same name:
// try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
QString origFile = curUrl.fileName();
QString file = origFile;
while (renameNumbering.contains(file)) {
file = origFile;
int num = renameNumbering[file] + 1;
int dotIdx = file.lastIndexOf(QLatin1Char('.'));
file = file.insert((dotIdx >= 0) ? dotIdx : file.length(), QLatin1Char(
'_') + QString::number(num));
}
curUrl = curUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
curUrl.setPath(curUrl.path() + QLatin1Char('/') + file);
// Increment the counter for both the old and the new filename
if (!renameNumbering.contains(origFile)) {
renameNumbering[origFile] = 1;
} else {
renameNumbering[origFile]++;
}
if (file != origFile) {
if (!renameNumbering.contains(file)) {
renameNumbering[file] = 1;
} else {
renameNumbering[file]++;
}
}
if (!(result == PimCommon::RenameFileDialog::RENAMEFILE_OVERWRITEALL
|| result == PimCommon::RenameFileDialog::RENAMEFILE_IGNOREALL)) {
bool fileExists = false;
if (curUrl.isLocalFile()) {
fileExists = QFile::exists(curUrl.toLocalFile());
} else {
auto job = KIO::stat(curUrl, KIO::StatJob::DestinationSide, 0);
KJobWidgets::setWindow(job, parent);
fileExists = job->exec();
}
if (fileExists) {
QPointer<PimCommon::RenameFileDialog> dlg = new PimCommon::RenameFileDialog(
curUrl,
multiple,
parent);
result
= static_cast<PimCommon::RenameFileDialog::RenameFileDialogResult>(dlg->exec());
if (result == PimCommon::RenameFileDialog::RENAMEFILE_IGNORE
|| result == PimCommon::RenameFileDialog::RENAMEFILE_IGNOREALL) {
delete dlg;
continue;
} else if (result == PimCommon::RenameFileDialog::RENAMEFILE_RENAME) {
if (dlg) {
curUrl = dlg->newName();
}
}
delete dlg;
}
}
// save
if (result != PimCommon::RenameFileDialog::RENAMEFILE_IGNOREALL) {
const bool result = saveContent(parent, content, curUrl);
if (!result) {
globalResult = result;
} else {
urlList.append(curUrl);
}
}
}
}
return globalResult;
}
bool Util::saveContent(QWidget *parent, KMime::Content *content, const QUrl &url)
{
- // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediatley
+ // FIXME: This is all horribly broken. First of all, creating a NodeHelper and then immediately
// reading out the encryption/signature state will not work at all.
// Then, topLevel() will not work for attachments that are inside encrypted parts.
// What should actually be done is either passing in an ObjectTreeParser that has already
// parsed the message, or creating an OTP here (which would have the downside that the
// password dialog for decrypting messages is shown twice)
#if 0 // totally broken
KMime::Content *topContent = content->topLevel();
MimeTreeParser::NodeHelper *mNodeHelper = new MimeTreeParser::NodeHelper;
bool bSaveEncrypted = false;
bool bEncryptedParts = mNodeHelper->encryptionState(content)
!= MimeTreeParser::KMMsgNotEncrypted;
if (bEncryptedParts) {
if (KMessageBox::questionYesNo(parent,
i18n(
"The part %1 of the message is encrypted. Do you want to keep the encryption when saving?",
url.fileName()),
i18n("KMail Question"), KGuiItem(i18n("Keep Encryption")),
KGuiItem(i18n("Do Not Keep")))
== KMessageBox::Yes) {
bSaveEncrypted = true;
}
}
bool bSaveWithSig = true;
if (mNodeHelper->signatureState(content) != MessageViewer::MimeTreeParser::KMMsgNotSigned) {
if (KMessageBox::questionYesNo(parent,
i18n(
"The part %1 of the message is signed. Do you want to keep the signature when saving?",
url.fileName()),
i18n("KMail Question"), KGuiItem(i18n("Keep Signature")),
KGuiItem(i18n("Do Not Keep")))
!= KMessageBox::Yes) {
bSaveWithSig = false;
}
}
QByteArray data;
if (bSaveEncrypted || !bEncryptedParts) {
KMime::Content *dataNode = content;
QByteArray rawDecryptedBody;
bool gotRawDecryptedBody = false;
if (!bSaveWithSig) {
if (topContent->contentType()->mimeType() == "multipart/signed") {
// carefully look for the part that is *not* the signature part:
if (MimeTreeParser::ObjectTreeParser::findType(topContent,
"application/pgp-signature", true,
false)) {
dataNode = MimeTreeParser::ObjectTreeParser ::findTypeNot(topContent,
"application",
"pgp-signature", true,
false);
} else if (MimeTreeParser::ObjectTreeParser::findType(topContent,
"application/pkcs7-mime",
true, false)) {
dataNode = MimeTreeParser::ObjectTreeParser ::findTypeNot(topContent,
"application",
"pkcs7-mime", true,
false);
} else {
dataNode = MimeTreeParser::ObjectTreeParser ::findTypeNot(topContent,
"multipart", "", true,
false);
}
} else {
EmptySource emptySource;
MimeTreeParser::ObjectTreeParser otp(&emptySource, 0, 0, false, false);
// process this node and all it's siblings and descendants
mNodeHelper->setNodeUnprocessed(dataNode, true);
otp.parseObjectTree(dataNode);
rawDecryptedBody = otp.rawDecryptedBody();
gotRawDecryptedBody = true;
}
}
QByteArray cstr = gotRawDecryptedBody
? rawDecryptedBody
: dataNode->decodedContent();
data = KMime::CRLFtoLF(cstr);
}
#else
const QByteArray data = content->decodedContent();
qCWarning(MESSAGEVIEWER_LOG)
<< "Port the encryption/signature handling when saving a KMime::Content.";
#endif
QDataStream ds;
QFile file;
QTemporaryFile tf;
if (url.isLocalFile()) {
// save directly
file.setFileName(url.toLocalFile());
if (!file.open(QIODevice::WriteOnly)) {
KMessageBox::error(parent,
xi18nc("1 = file name, 2 = error string",
"<qt>Could not write to the file<br /><filename>%1</filename><br /><br />%2</qt>",
file.fileName(),
file.errorString()),
i18n("Error saving attachment"));
return false;
}
ds.setDevice(&file);
} else {
// tmp file for upload
tf.open();
ds.setDevice(&tf);
}
const int bytesWritten = ds.writeRawData(data.data(), data.size());
if (bytesWritten != data.size()) {
QFile *f = static_cast<QFile *>(ds.device());
KMessageBox::error(parent,
xi18nc("1 = file name, 2 = error string",
"<qt>Could not write to the file<br /><filename>%1</filename><br /><br />%2</qt>",
f->fileName(),
f->errorString()),
i18n("Error saving attachment"));
// Remove the newly created empty or partial file
f->remove();
return false;
}
if (!url.isLocalFile()) {
// QTemporaryFile::fileName() is only defined while the file is open
QString tfName = tf.fileName();
tf.close();
auto job = KIO::file_copy(QUrl::fromLocalFile(tfName), url);
KJobWidgets::setWindow(job, parent);
if (!job->exec()) {
KMessageBox::error(parent,
xi18nc("1 = file name, 2 = error string",
"<qt>Could not write to the file<br /><filename>%1</filename><br /><br />%2</qt>",
url.toDisplayString(),
job->errorString()),
i18n("Error saving attachment"));
return false;
}
} else {
file.close();
}
return true;
}
bool Util::saveAttachments(const KMime::Content::List &contents, QWidget *parent, QList<QUrl> &urlList)
{
if (contents.isEmpty()) {
KMessageBox::information(parent, i18n("Found no attachments to save."));
return false;
}
return Util::saveContents(parent, contents, urlList);
}
QString Util::generateMboxFileName(const Akonadi::Item &msgBase)
{
QString fileName;
if (msgBase.hasPayload<KMime::Message::Ptr>()) {
fileName
= MessageCore::StringUtil::cleanFileName(MessageCore::StringUtil::cleanSubject(
msgBase.
payload
<KMime::Message::Ptr>().data()).trimmed());
fileName.remove(QLatin1Char('\"'));
} else {
fileName = i18n("message");
}
if (!fileName.endsWith(QLatin1String(".mbox"))) {
fileName += QLatin1String(".mbox");
}
return fileName;
}
bool Util::saveMessageInMboxAndGetUrl(QUrl &url, const Akonadi::Item::List &retrievedMsgs, QWidget *parent, bool appendMessages)
{
if (retrievedMsgs.isEmpty()) {
return false;
}
const Akonadi::Item msgBase = retrievedMsgs.first();
QString fileName = generateMboxFileName(msgBase);
const QString filter = i18n("email messages (*.mbox);;all files (*)");
QString fileClass;
const QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral(
"kfiledialog:///savemessage")),
fileClass);
QFileDialog::Options opt;
if (appendMessages) {
opt |= QFileDialog::DontConfirmOverwrite;
}
QString localFile = startUrl.toLocalFile() + QLatin1Char('/') + fileName;
QString saveFileName
= QFileDialog::getSaveFileName(parent,
i18np("Save Message", "Save Messages",
retrievedMsgs.count()), localFile, filter, nullptr,
opt);
if (!saveFileName.isEmpty()) {
const QString localFileName = saveFileName;
if (!appendMessages) {
QFile::remove(localFileName);
}
KMBox::MBox mbox;
if (!mbox.load(localFileName)) {
if (appendMessages) {
KMessageBox::error(parent, i18n("File %1 could not be loaded.",
localFileName), i18n("Error loading message"));
} else {
KMessageBox::error(parent, i18n("File %1 could not be created.",
localFileName), i18n("Error saving message"));
}
return false;
}
for (const Akonadi::Item &item : qAsConst(retrievedMsgs)) {
if (item.hasPayload<KMime::Message::Ptr>()) {
mbox.appendMessage(item.payload<KMime::Message::Ptr>());
}
}
if (!mbox.save()) {
KMessageBox::error(parent, i18n("We cannot save message."),
i18n("Error saving message"));
return false;
}
QUrl localUrl = QUrl::fromLocalFile(saveFileName);
if (localUrl.isLocalFile()) {
KRecentDirs::add(fileClass, localUrl.adjusted(
QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
}
url = localUrl;
}
return true;
}
bool Util::saveMessageInMbox(const Akonadi::Item::List &retrievedMsgs, QWidget *parent, bool appendMessages)
{
QUrl url;
return saveMessageInMboxAndGetUrl(url, retrievedMsgs, parent, appendMessages);
}
QAction *Util::createAppAction(const KService::Ptr &service, bool singleOffer, QActionGroup *actionGroup, QObject *parent)
{
QString actionName(service->name().replace(QLatin1Char('&'), QStringLiteral("&&")));
if (singleOffer) {
actionName = i18n("Open &with %1", actionName);
} else {
actionName = i18nc("@item:inmenu Open With, %1 is application name", "%1", actionName);
}
QAction *act = new QAction(parent);
act->setIcon(QIcon::fromTheme(service->icon()));
act->setText(actionName);
actionGroup->addAction(act);
act->setData(QVariant::fromValue(service));
return act;
}
+
+bool Util::excludeExtraHeader(const QString &s)
+{
+ QRegularExpression ref(QStringLiteral("http-equiv=\\s*(\'|\")(&#82;|R)EFRESH(\'|\")"), QRegularExpression::CaseInsensitiveOption);
+ if (s.contains(ref)) {
+ return true;
+ }
+ return false;
+}
diff --git a/messageviewer/src/utils/messageviewerutil.h b/messageviewer/src/utils/messageviewerutil.h
index 183707f0..1f192a76 100644
--- a/messageviewer/src/utils/messageviewerutil.h
+++ b/messageviewer/src/utils/messageviewerutil.h
@@ -1,71 +1,72 @@
/*******************************************************************************
**
** Filename : util
** Created on : 03 April, 2005
** Copyright : (c) 2005 Till Adam
** Email : <adam@kde.org>
**
*******************************************************************************/
/*******************************************************************************
**
** 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.
**
** It 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.
**
*******************************************************************************/
#ifndef MESSAGEVIEWERUTIL_H
#define MESSAGEVIEWERUTIL_H
#include "messageviewer_export.h"
#include <KMime/Content>
#include <KService>
#include <AkonadiCore/Item>
class QUrl;
class QWidget;
class QActionGroup;
class QAction;
namespace KMime {
class Message;
}
namespace MessageViewer {
/**
* The Util namespace contains a collection of helper functions use in
* various places.
*/
namespace Util {
// return true if we should proceed, false if we should abort
Q_REQUIRED_RESULT bool MESSAGEVIEWER_EXPORT checkOverwrite(const QUrl &url, QWidget *w);
Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT bool saveMessageInMboxAndGetUrl(QUrl &url, const Akonadi::Item::List &retrievedMsgs, QWidget *parent, bool appendMessages = false);
Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT bool saveMessageInMbox(const Akonadi::Item::List &retrievedMsgs, QWidget *parent, bool appendMessages = false);
Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT QString generateMboxFileName(const Akonadi::Item &msgBase);
Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT bool saveAttachments(const KMime::Content::List &contents, QWidget *parent, QList<QUrl> &saveUrl);
Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT QAction *createAppAction(const KService::Ptr &service, bool singleOffer, QActionGroup *actionGroup, QObject *parent);
+Q_REQUIRED_RESULT MESSAGEVIEWER_EXPORT bool excludeExtraHeader(const QString &s);
}
}
Q_DECLARE_METATYPE(KService::Ptr)
#endif
diff --git a/messageviewer/src/utils/mimetype.h b/messageviewer/src/utils/mimetype.h
index 37b1566d..6ab703e9 100644
--- a/messageviewer/src/utils/mimetype.h
+++ b/messageviewer/src/utils/mimetype.h
@@ -1,66 +1,67 @@
/*
Copyright (C) 2005 Till Adam <adam@kde.org>
Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
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.
*/
#ifndef MESSAGEVIEWER_UTIL_MIMETYPE_H
#define MESSAGEVIEWER_UTIL_MIMETYPE_H
#include "messageviewer_export.h"
#include <QString>
namespace KMime {
class Content;
}
namespace MessageViewer {
/**
* The Util namespace contains a collection of helper functions use in
* various places.
*/
namespace Util {
/**
* Finds the filename of an icon based on the given mimetype or filenames.
*
* Always use this functions when looking up icon names for mime types, don't use
* KMimeType directly.
*
* Uses the IconNameCache internally to speed things up.
*
* @param mimeType The primary mime type used to find the icon, e.g. "application/zip". Alias
* mimetypes are resolved.
- * @param size Size of the requested icon, e.g. KIconLoader::Desktop
- * @param fallbackFileName(1|2) When the icon is not found by the given mime type, use the file
+ * @param iconSize Size of the requested icon, e.g. KIconLoader::Desktop
+ * @param fallbackFileName1 When the icon is not found by the given mime type, use the file
* name extensions of these file names to look the icon up.
* Example: "test.zip"
+ * @param fallbackFileName2 Fallback for @p fallbackFileName1.
* @return the full file name of the icon file
*/
Q_REQUIRED_RESULT QString MESSAGEVIEWER_EXPORT iconPathForMimetype(const QString &mimeType, int iconSize, const QString &fallbackFileName1 = QString(), const QString &fallbackFileName2 = QString());
Q_REQUIRED_RESULT QString MESSAGEVIEWER_EXPORT iconPathForContent(KMime::Content *node, int iconSize);
struct AttachmentDisplayInfo {
QString label;
QString icon;
bool displayInHeader = false;
};
Q_REQUIRED_RESULT AttachmentDisplayInfo attachmentDisplayInfo(KMime::Content *node);
}
}
#endif
diff --git a/messageviewer/src/viewer/attachmentstrategy.cpp b/messageviewer/src/viewer/attachmentstrategy.cpp
index f7be3ab3..fff654a2 100644
--- a/messageviewer/src/viewer/attachmentstrategy.cpp
+++ b/messageviewer/src/viewer/attachmentstrategy.cpp
@@ -1,371 +1,372 @@
/* -*- c++ -*-
attachmentstrategy.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
+ 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.
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.
*/
#include "attachmentstrategy.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/Util>
#include <KMime/Content>
#include <QIcon>
#include "messageviewer_debug.h"
using namespace MessageViewer;
static AttachmentStrategy::Display smartDisplay(KMime::Content *node)
{
const auto cd = node->contentDisposition(false);
if (cd && cd->disposition() == KMime::Headers::CDinline) {
// explict "inline" disposition:
return AttachmentStrategy::Inline;
}
if (cd && cd->disposition() == KMime::Headers::CDattachment) {
// explicit "attachment" disposition:
return AttachmentStrategy::AsIcon;
}
const auto ct = node->contentType(false);
if (ct && ct->isText() && ct->name().trimmed().isEmpty()
&& (!cd || cd->filename().trimmed().isEmpty())) {
// text/* w/o filename parameter:
return AttachmentStrategy::Inline;
}
return AttachmentStrategy::AsIcon;
}
//
// IconicAttachmentStrategy:
// show everything but the first text/plain body as icons
//
class IconicAttachmentStrategy : public AttachmentStrategy
{
friend class AttachmentStrategy;
protected:
IconicAttachmentStrategy() : AttachmentStrategy()
{
}
- virtual ~IconicAttachmentStrategy()
+ ~IconicAttachmentStrategy() override
{
}
public:
const char *name() const override
{
return "iconic";
}
bool inlineNestedMessages() const override
{
return false;
}
Display defaultDisplay(KMime::Content *node) const override
{
if (node->contentType()->isText()
&& (!node->parent()
|| (node->contentDisposition()->filename().trimmed().isEmpty()
&& node->contentType()->name().trimmed().isEmpty()))) {
// text/* w/o filename parameter:
return Inline;
}
return AsIcon;
}
};
//
// SmartAttachmentStrategy:
// in addition to Iconic, show all body parts
// with content-disposition == "inline" and
// all text parts without a filename or name parameter inline
//
class SmartAttachmentStrategy : public AttachmentStrategy
{
friend class AttachmentStrategy;
protected:
SmartAttachmentStrategy() : AttachmentStrategy()
{
}
- virtual ~SmartAttachmentStrategy()
+ ~SmartAttachmentStrategy() override
{
}
public:
const char *name() const override
{
return "smart";
}
bool inlineNestedMessages() const override
{
return true;
}
Display defaultDisplay(KMime::Content *node) const override
{
return smartDisplay(node);
}
};
//
// InlinedAttachmentStrategy:
// show everything possible inline
//
class InlinedAttachmentStrategy : public AttachmentStrategy
{
friend class AttachmentStrategy;
protected:
InlinedAttachmentStrategy() : AttachmentStrategy()
{
}
- virtual ~InlinedAttachmentStrategy()
+ ~InlinedAttachmentStrategy() override
{
}
public:
const char *name() const override
{
return "inlined";
}
bool inlineNestedMessages() const override
{
return true;
}
Display defaultDisplay(KMime::Content *) const override
{
return Inline;
}
};
//
// HiddenAttachmentStrategy
// show nothing except the first text/plain body part _at all_
//
class HiddenAttachmentStrategy : public AttachmentStrategy
{
friend class AttachmentStrategy;
protected:
HiddenAttachmentStrategy() : AttachmentStrategy()
{
}
- virtual ~HiddenAttachmentStrategy()
+ ~HiddenAttachmentStrategy() override
{
}
public:
const char *name() const override
{
return "hidden";
}
bool inlineNestedMessages() const override
{
return false;
}
Display defaultDisplay(KMime::Content *node) const override
{
if (node->contentType()->isText()
&& node->contentDisposition()->filename().trimmed().isEmpty()
&& node->contentType()->name().trimmed().isEmpty()) {
// text/* w/o filename parameter:
return Inline;
}
if (!node->parent()) {
return Inline;
}
if (node->parent() && node->parent()->contentType()->isMultipart()
&& node->parent()->contentType()->subType() == "related") {
return Inline;
}
return None;
}
};
class HeaderOnlyAttachmentStrategy : public AttachmentStrategy
{
friend class AttachmentStrategy;
protected:
HeaderOnlyAttachmentStrategy() : AttachmentStrategy()
{
}
- virtual ~HeaderOnlyAttachmentStrategy()
+ ~HeaderOnlyAttachmentStrategy() override
{
}
public:
const char *name() const override
{
return "headerOnly";
}
bool inlineNestedMessages() const override
{
return true;
}
Display defaultDisplay(KMime::Content *node) const override
{
if (MimeTreeParser::NodeHelper::isInEncapsulatedMessage(node)) {
return smartDisplay(node);
}
if (!MimeTreeParser::Util::labelForContent(node).isEmpty() && QIcon::hasThemeIcon(MimeTreeParser::Util::iconNameForContent(node)) && !MimeTreeParser::Util::isTypeBlacklisted(node)) {
return None;
}
return smartDisplay(node);
}
bool requiresAttachmentListInHeader() const override
{
return true;
}
};
//
// AttachmentStrategy abstract base:
//
AttachmentStrategy::AttachmentStrategy()
{
}
AttachmentStrategy::~AttachmentStrategy()
{
}
const AttachmentStrategy *AttachmentStrategy::create(Type type)
{
switch (type) {
case Iconic:
return iconic();
case Smart:
return smart();
case Inlined:
return inlined();
case Hidden:
return hidden();
case HeaderOnly:
return headerOnly();
}
qCCritical(MESSAGEVIEWER_LOG) << "Unknown attachment startegy ( type =="
- << (int)type << ") requested!";
+ << static_cast<int>(type) << ") requested!";
return nullptr; // make compiler happy
}
const AttachmentStrategy *AttachmentStrategy::create(const QString &type)
{
const QString lowerType = type.toLower();
if (lowerType == QLatin1String("iconic")) {
return iconic();
}
//if ( lowerType == "smart" ) return smart(); // not needed, see below
if (lowerType == QLatin1String("inlined")) {
return inlined();
}
if (lowerType == QLatin1String("hidden")) {
return hidden();
}
if (lowerType == QLatin1String("headeronly")) {
return headerOnly();
}
// don't kFatal here, b/c the strings are user-provided
// (KConfig), so fail gracefully to the default:
return smart();
}
static const AttachmentStrategy *iconicStrategy = nullptr;
static const AttachmentStrategy *smartStrategy = nullptr;
static const AttachmentStrategy *inlinedStrategy = nullptr;
static const AttachmentStrategy *hiddenStrategy = nullptr;
static const AttachmentStrategy *headerOnlyStrategy = nullptr;
const AttachmentStrategy *AttachmentStrategy::iconic()
{
if (!iconicStrategy) {
iconicStrategy = new IconicAttachmentStrategy();
}
return iconicStrategy;
}
const AttachmentStrategy *AttachmentStrategy::smart()
{
if (!smartStrategy) {
smartStrategy = new SmartAttachmentStrategy();
}
return smartStrategy;
}
const AttachmentStrategy *AttachmentStrategy::inlined()
{
if (!inlinedStrategy) {
inlinedStrategy = new InlinedAttachmentStrategy();
}
return inlinedStrategy;
}
const AttachmentStrategy *AttachmentStrategy::hidden()
{
if (!hiddenStrategy) {
hiddenStrategy = new HiddenAttachmentStrategy();
}
return hiddenStrategy;
}
const AttachmentStrategy *AttachmentStrategy::headerOnly()
{
if (!headerOnlyStrategy) {
headerOnlyStrategy = new HeaderOnlyAttachmentStrategy();
}
return headerOnlyStrategy;
}
bool AttachmentStrategy::requiresAttachmentListInHeader() const
{
return false;
}
diff --git a/messageviewer/src/viewer/attachmentstrategy.h b/messageviewer/src/viewer/attachmentstrategy.h
index d66a16ec..7da47bcf 100644
--- a/messageviewer/src/viewer/attachmentstrategy.h
+++ b/messageviewer/src/viewer/attachmentstrategy.h
@@ -1,88 +1,89 @@
/* -*- c++ -*-
attachmentstrategy.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_ATTACHMENTSTRATEGY_H
#define MESSAGEVIEWER_ATTACHMENTSTRATEGY_H
#include "messageviewer_export.h"
class QString;
namespace KMime {
class Content;
}
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT AttachmentStrategy
{
protected:
AttachmentStrategy();
virtual ~AttachmentStrategy();
public:
//
// Factory methods:
//
enum Type {
Iconic, Smart, Inlined, Hidden, HeaderOnly
};
static const AttachmentStrategy *create(Type type);
static const AttachmentStrategy *create(const QString &type);
static const AttachmentStrategy *iconic();
static const AttachmentStrategy *smart();
static const AttachmentStrategy *inlined();
static const AttachmentStrategy *hidden();
static const AttachmentStrategy *headerOnly();
//
// Navigation methods:
//
virtual const char *name() const = 0;
//
- // Bahavioural:
+ // Behavioral:
//
enum Display {
None, AsIcon, Inline
};
virtual bool inlineNestedMessages() const = 0;
virtual Display defaultDisplay(KMime::Content *node) const = 0;
virtual bool requiresAttachmentListInHeader() const;
};
}
#endif // MIMETREEPARSER_ATTACHMENTSTRATEGY_H
diff --git a/messageviewer/src/viewer/csshelper.cpp b/messageviewer/src/viewer/csshelper.cpp
index 9d807c40..67a85211 100644
--- a/messageviewer/src/viewer/csshelper.cpp
+++ b/messageviewer/src/viewer/csshelper.cpp
@@ -1,145 +1,141 @@
/*
csshelper.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#include "csshelper.h"
#include "settings/messageviewersettings.h"
#include "MessageCore/MessageCoreSettings"
#include <MessageCore/MessageCoreUtil>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <KColorScheme>
+#include <QApplication>
#include <QColor>
#include <QFont>
#include <QPalette>
using namespace MessageViewer;
CSSHelper::CSSHelper(const QPaintDevice *pd)
: CSSHelperBase(pd)
{
// initialize with defaults - should match the corresponding application defaults
mForegroundColor = QApplication::palette().color(QPalette::Text);
mLinkColor = MessageCore::ColorUtil::self()->linkColor();
mBackgroundColor = QApplication::palette().color(QPalette::Base);
cHtmlWarning = QColor(0xFF, 0x40, 0x40); // warning frame color: light red
cPgpEncrH = MessageCore::ColorUtil::self()->pgpEncryptedMessageColor();
cPgpEncrHT = MessageCore::ColorUtil::self()->pgpEncryptedTextColor();
cPgpOk1H = MessageCore::ColorUtil::self()->pgpSignedTrustedMessageColor();
cPgpOk1HT = MessageCore::ColorUtil::self()->pgpSignedTrustedTextColor();
cPgpOk0H = MessageCore::ColorUtil::self()->pgpSignedUntrustedMessageColor();
cPgpOk0HT = MessageCore::ColorUtil::self()->pgpSignedUntrustedTextColor();
cPgpWarnH = MessageCore::ColorUtil::self()->pgpSignedUntrustedMessageColor();
cPgpWarnHT = MessageCore::ColorUtil::self()->pgpSignedUntrustedTextColor();
cPgpErrH = MessageCore::ColorUtil::self()->pgpSignedBadMessageColor();
cPgpErrHT = MessageCore::ColorUtil::self()->pgpSignedBadTextColor();
- if (MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
- mQuoteColor[0] = MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor();
- mQuoteColor[1] = MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor();
- mQuoteColor[2] = MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor();
- } else {
- mQuoteColor[0] = MessageCore::MessageCoreSettings::self()->quotedText1();
- mQuoteColor[1] = MessageCore::MessageCoreSettings::self()->quotedText2();
- mQuoteColor[2] = MessageCore::MessageCoreSettings::self()->quotedText3();
- }
-
mRecycleQuoteColors = false;
QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
QFont defaultFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
mBodyFont
= MessageCore::MessageCoreSettings::self()->useDefaultFonts() ? defaultFont : MessageViewer
::
MessageViewerSettings::self()->bodyFont();
mPrintFont
= MessageCore::MessageCoreSettings::self()->useDefaultFonts() ? defaultFont : MessageViewer
::
MessageViewerSettings::self()->printFont();
mFixedFont = mFixedPrintFont = defaultFixedFont;
defaultFont.setItalic(true);
mQuoteFont = defaultFont;
KConfig *config = MessageViewer::MessageViewerSettings::self()->config();
KConfigGroup reader(config, "Reader");
KConfigGroup fonts(config, "Fonts");
mRecycleQuoteColors = reader.readEntry("RecycleQuoteColors", false);
mForegroundColor = KColorScheme(QPalette::Active).foreground().color();
- if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
+ if (MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
+ mQuoteColor[0] = MessageCore::ColorUtil::self()->quoteLevel1DefaultTextColor();
+ mQuoteColor[1] = MessageCore::ColorUtil::self()->quoteLevel2DefaultTextColor();
+ mQuoteColor[2] = MessageCore::ColorUtil::self()->quoteLevel3DefaultTextColor();
+ } else {
mLinkColor
= reader.readEntry("LinkColor", mLinkColor);
cPgpEncrH
= reader.readEntry("PGPMessageEncr", cPgpEncrH);
cPgpOk1H
= reader.readEntry("PGPMessageOkKeyOk", cPgpOk1H);
cPgpOk0H
= reader.readEntry("PGPMessageOkKeyBad", cPgpOk0H);
cPgpWarnH
= reader.readEntry("PGPMessageWarn", cPgpWarnH);
cPgpErrH
= reader.readEntry("PGPMessageErr", cPgpErrH);
- for (int i = 0; i < 3; ++i) {
- const QString key = QLatin1String("QuotedText") + QString::number(i + 1);
- mQuoteColor[i] = reader.readEntry(key, mQuoteColor[i]);
- }
+ mQuoteColor[0] = MessageCore::MessageCoreSettings::self()->quotedText1();
+ mQuoteColor[1] = MessageCore::MessageCoreSettings::self()->quotedText2();
+ mQuoteColor[2] = MessageCore::MessageCoreSettings::self()->quotedText3();
}
if (!MessageCore::MessageCoreSettings::self()->useDefaultFonts()) {
mBodyFont = fonts.readEntry("body-font", mBodyFont);
mPrintFont = fonts.readEntry("print-font", mPrintFont);
mFixedFont = fonts.readEntry("fixed-font", mFixedFont);
- mFixedPrintFont = mFixedFont; // FIXME when we have a separate fixed print font
+ mFixedPrintFont = mFixedFont;
}
mShrinkQuotes = MessageViewer::MessageViewerSettings::self()->shrinkQuotes();
+ mUseBrowserColor = MessageCore::MessageCoreSettings::self()->useRealHtmlMailColor();
recalculatePGPColors();
}
CSSHelper::~CSSHelper()
{
}
QString CSSHelper::htmlHead(bool fixed) const
{
return
QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html><head><title></title><style>\n")
+ cssDefinitions(fixed)
+QLatin1String("</style></head>\n"
"<body>\n");
}
diff --git a/messageviewer/src/viewer/csshelper.h b/messageviewer/src/viewer/csshelper.h
index 2278cd94..04f03534 100644
--- a/messageviewer/src/viewer/csshelper.h
+++ b/messageviewer/src/viewer/csshelper.h
@@ -1,52 +1,53 @@
/* -*- c++ -*-
csshelper.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_CSSHELPER_H
#define MESSAGEVIEWER_CSSHELPER_H
#include <MessageViewer/CSSHelperBase>
#include "messageviewer_export.h"
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT CSSHelper : public CSSHelperBase
{
public:
explicit CSSHelper(const QPaintDevice *pd);
~CSSHelper() override;
/** @return HTML head including style sheet definitions and the
&gt;body&lt; tag */
Q_REQUIRED_RESULT QString htmlHead(bool fixedFont = false) const override;
};
}
#endif // MESSAGEVIEWER_CSSHELPER_H
diff --git a/messageviewer/src/viewer/csshelperbase.cpp b/messageviewer/src/viewer/csshelperbase.cpp
index 75591f5b..b08dbbb2 100644
--- a/messageviewer/src/viewer/csshelperbase.cpp
+++ b/messageviewer/src/viewer/csshelperbase.cpp
@@ -1,772 +1,755 @@
/*
csshelper.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#include "csshelperbase.h"
+#include "header/headerstyleplugin.h"
#include "utils/iconnamecache.h"
#include <QApplication>
#include <QPaintDevice>
#include <QPalette>
#include <QUrl>
-
+#define USE_HTML_STYLE_COLOR 1
namespace MessageViewer {
namespace {
// some QColor manipulators that hide the ugly QColor API w.r.t. HSV:
inline QColor darker(const QColor &c)
{
int h, s, v;
c.getHsv(&h, &s, &v);
return QColor::fromHsv(h, s, v * 4 / 5);
}
inline QColor desaturate(const QColor &c)
{
int h, s, v;
c.getHsv(&h, &s, &v);
return QColor::fromHsv(h, s / 8, v);
}
inline QColor fixValue(const QColor &c, int newV)
{
int h, s, v;
c.getHsv(&h, &s, &v);
return QColor::fromHsv(h, s, newV);
}
inline int getValueOf(const QColor &c)
{
int h, s, v;
c.getHsv(&h, &s, &v);
return v;
}
}
CSSHelperBase::CSSHelperBase(const QPaintDevice *pd)
: mRecycleQuoteColors(false)
, mShrinkQuotes(false)
, cHtmlWarning(QColor(0xFF, 0x40, 0x40))
, mPaintDevice(pd)
{
recalculatePGPColors();
- const QString imgSrcShow = QStringLiteral("quicklistOpened.png");
- const QString imgSrcHide = QStringLiteral("quicklistClosed.png");
+ const QString imgSrcShow = QStringLiteral("quicklistClosed.png");
+ const QString imgSrcHide = QStringLiteral("quicklistOpened.png");
imgShowUrl = QUrl::fromLocalFile(MessageViewer::IconNameCache::instance()->iconPathFromLocal(imgSrcShow)).url();
imgHideUrl = QUrl::fromLocalFile(MessageViewer::IconNameCache::instance()->iconPathFromLocal(imgSrcHide)).url();
}
CSSHelperBase::~CSSHelperBase()
{
}
void CSSHelperBase::recalculatePGPColors()
{
// determine the frame and body color for PGP messages from the header color
// if the header color equals the background color then the other colors are
// also set to the background color (-> old style PGP message viewing)
// else
// the brightness of the frame is set to 4/5 of the brightness of the header
// and in case of a light background color
// the saturation of the body is set to 1/8 of the saturation of the header
// while in case of a dark background color
// the value of the body is set to the value of the background color
// Check whether the user uses a light color scheme
const int vBG = getValueOf(mBackgroundColor);
const bool lightBG = vBG >= 128;
if (cPgpOk1H == mBackgroundColor) {
cPgpOk1F = mBackgroundColor;
cPgpOk1B = mBackgroundColor;
} else {
cPgpOk1F = darker(cPgpOk1H);
cPgpOk1B = lightBG ? desaturate(cPgpOk1H) : fixValue(cPgpOk1H, vBG);
}
if (cPgpOk0H == mBackgroundColor) {
cPgpOk0F = mBackgroundColor;
cPgpOk0B = mBackgroundColor;
} else {
cPgpOk0F = darker(cPgpOk0H);
cPgpOk0B = lightBG ? desaturate(cPgpOk0H) : fixValue(cPgpOk0H, vBG);
}
if (cPgpWarnH == mBackgroundColor) {
cPgpWarnF = mBackgroundColor;
cPgpWarnB = mBackgroundColor;
} else {
cPgpWarnF = darker(cPgpWarnH);
cPgpWarnB = lightBG ? desaturate(cPgpWarnH) : fixValue(cPgpWarnH, vBG);
}
if (cPgpErrH == mBackgroundColor) {
cPgpErrF = mBackgroundColor;
cPgpErrB = mBackgroundColor;
} else {
cPgpErrF = darker(cPgpErrH);
cPgpErrB = lightBG ? desaturate(cPgpErrH) : fixValue(cPgpErrH, vBG);
}
if (cPgpEncrH == mBackgroundColor) {
cPgpEncrF = mBackgroundColor;
cPgpEncrB = mBackgroundColor;
} else {
cPgpEncrF = darker(cPgpEncrH);
cPgpEncrB = lightBG ? desaturate(cPgpEncrH) : fixValue(cPgpEncrH, vBG);
}
}
QString CSSHelperBase::addEndBlockQuote(int numberBlock) const
{
QString blockQuote;
for (int i = 0; i < numberBlock; ++i) {
blockQuote += QLatin1String("</blockquote>");
}
return blockQuote;
}
QString CSSHelperBase::addStartBlockQuote(int numberBlock) const
{
QString blockQuote;
for (int i = 0; i < numberBlock; ++i) {
blockQuote += QLatin1String("<blockquote>");
}
return blockQuote;
}
+QString CSSHelperBase::extraScreenCss(const QString &headerFont) const
+{
+ if (mHeaderPlugin) {
+ return mHeaderPlugin->extraScreenCss(headerFont);
+ }
+ return {};
+}
+
+QString CSSHelperBase::extraPrintCss(const QString &headerFont) const
+{
+ if (mHeaderPlugin) {
+ return mHeaderPlugin->extraPrintCss(headerFont);
+ }
+ return {};
+}
+
+QString CSSHelperBase::extraCommonCss(const QString &headerFont) const
+{
+ QString result;
+ if (mHeaderPlugin) {
+ result = mHeaderPlugin->extraCommonCss(headerFont);
+ if (result.isEmpty()) {
+ //Add default value
+ result = QStringLiteral("div.header table {\n"
+ " width: 100% ! important;\n"
+ " border-width: 0px ! important;\n"
+ " line-height: normal;\n"
+ "}\n\n");
+ }
+ }
+ return result;
+}
+
QString CSSHelperBase::cssDefinitions(bool fixed) const
{
return
commonCssDefinitions()
+
QLatin1String("@media screen {\n\n")
+
screenCssDefinitions(this, fixed)
+
QLatin1String("}\n"
"@media print {\n\n")
+
printCssDefinitions(fixed)
+
QLatin1String("}\n");
}
QString CSSHelperBase::htmlHead(bool fixedFont) const
{
Q_UNUSED(fixedFont);
return
QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
"<html><head><title></title></head>\n"
"<body>\n");
}
QString CSSHelperBase::quoteFontTag(int level) const
{
if (level < 0) {
level = 0;
}
static const int numQuoteLevels = 3;
const int effectiveLevel = mRecycleQuoteColors
? level % numQuoteLevels + 1
: qMin(level + 1, numQuoteLevels);
if (level >= numQuoteLevels) {
return QStringLiteral("<div class=\"deepquotelevel%1\">").arg(effectiveLevel);
} else {
return QStringLiteral("<div class=\"quotelevel%1\">").arg(effectiveLevel);
}
}
QString CSSHelperBase::fullAddressList() const
{
QString css = QStringLiteral("input[type=checkbox].addresslist_checkbox {display: none}\n"
".addresslist_label_short {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}\n"
".addresslist_label_full {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}\n");
css += QStringLiteral(".addresslist_label_short {background-image:url(%1);\nbackground-repeat: no-repeat}\n").arg(imgShowUrl);
css += QStringLiteral(".addresslist_label_full {background-image:url(%1);\nbackground-repeat: no-repeat}\n\n").arg(imgHideUrl);
for (const QString &str : {QStringLiteral("Cc"), QStringLiteral("To"), QStringLiteral("Bcc")}) {
css += QStringLiteral("input ~ span.fullFull%1AddressList {display: block}\n"
"input ~ span.shortFull%1AddressList {display: none}\n"
"input:checked ~ span.fullFull%1AddressList {display: none}\n"
"input:checked ~ span.shortFull%1AddressList {display: block}\n\n").arg(str);
}
return css;
}
QString CSSHelperBase::nonQuotedFontTag() const
{
return QStringLiteral("<div class=\"noquote\">");
}
QFont CSSHelperBase::bodyFont(bool fixed, bool print) const
{
return fixed ? (print ? mFixedPrintFont : mFixedFont)
: (print ? mPrintFont : mBodyFont);
}
int CSSHelperBase::fontSize(bool fixed, bool print) const
{
return bodyFont(fixed, print).pointSize();
}
namespace {
int pointsToPixel(const QPaintDevice *pd, int pointSize)
{
return (pointSize * pd->logicalDpiY() + 36) / 72;
}
}
+void CSSHelperBase::setHeaderPlugin(const HeaderStylePlugin *headerPlugin)
+{
+ mHeaderPlugin = headerPlugin;
+}
+
static const char *const quoteFontSizes[] = { "85", "80", "75" };
QString CSSHelperBase::printCssDefinitions(bool fixed) const
{
- const QString headerFont = QStringLiteral(" font-family: \"%1\" ! important;\n"
- " font-size: %2pt ! important;\n")
- .arg(mPrintFont.family())
- .arg(mPrintFont.pointSize());
- const QPalette &pal = QApplication::palette();
- const QString linkColor = mLinkColor.name();
+ const QString headerFont = defaultPrintHeaderFont();
const QFont printFont = bodyFont(fixed, true /* print */);
QString quoteCSS;
if (printFont.italic()) {
quoteCSS += QLatin1String(" font-style: italic ! important;\n");
}
if (printFont.bold()) {
quoteCSS += QLatin1String(" font-weight: bold ! important;\n");
}
if (!quoteCSS.isEmpty()) {
quoteCSS = QLatin1String("div.noquote {\n") + quoteCSS + QLatin1String("}\n\n");
}
quoteCSS += quoteCssDefinition();
return
QStringLiteral("body {\n"
" font-family: \"%1\" ! important;\n"
" font-size: %2pt ! important;\n"
" color: #000000 ! important;\n"
" background-color: #ffffff ! important\n"
"}\n\n")
.arg(printFont.family(),
QString::number(printFont.pointSize()))
- +
- QStringLiteral("a {\n"
- " color: %4 ! important;\n"
- " text-decoration: none ! important;\n"
- "}\n\n"
- "tr.textAtmH,\n"
- "tr.signInProgressH,\n"
- "tr.rfc822H,\n"
- "tr.encrH,\n"
- "tr.signOkKeyOkH,\n"
- "tr.signOkKeyBadH,\n"
- "tr.signWarnH,\n"
- "tr.signErrH,\n"
- "div.header {\n"
- "%1"
- "}\n\n"
-
- "div.fancy.header > div {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- " padding: 4px ! important;\n"
- " border: solid %3 1px ! important;\n"
- " line-height: normal;\n"
- "}\n\n"
-
- "div.fancy.header > div a[href] { color: %3 ! important; }\n\n"
-
- "div.fancy.header > table.outer{\n"
- " all: inherit;\n"
- " width: auto ! important;\n"
- " border-spacing: 0;\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- " border-bottom: solid %3 1px ! important;\n"
- " border-left: solid %3 1px ! important;\n"
- " border-right: solid %3 1px ! important;\n"
- "}\n\n"
-
- "div.spamheader {\n"
- " display:none ! important;\n"
- "}\n\n"
-
- "div.htmlWarn {\n"
- " border: 2px solid #ffffff ! important;\n"
- " line-height: normal;\n"
- "}\n\n"
-
- "div.senderpic{\n"
- " font-size:0.8em ! important;\n"
- " border:1px solid black ! important;\n"
- " background-color:%2 ! important;\n"
- "}\n\n"
-
- "div.senderstatus{\n"
- " text-align:center ! important;\n"
- "}\n\n"
-
- "div.noprint {\n"
- " display:none ! important;\n"
- "}\n\n"
- )
- .arg(headerFont,
- pal.color(QPalette::Background).name(),
- pal.color(QPalette::Foreground).name(),
- linkColor)
+ + linkColorDefinition()
+ +QStringLiteral(
+ "tr.textAtmH,\n"
+ "tr.signInProgressH,\n"
+ "tr.rfc822H,\n"
+ "tr.encrH,\n"
+ "tr.signOkKeyOkH,\n"
+ "tr.signOkKeyBadH,\n"
+ "tr.signWarnH,\n"
+ "tr.signErrH,\n"
+ "div.header {\n"
+ "%1"
+ "}\n\n"
+
+ "%2"
+
+ "div.spamheader {\n"
+ " display:none ! important;\n"
+ "}\n\n"
+
+ "div.htmlWarn {\n"
+ " border: 2px solid #ffffff ! important;\n"
+ " line-height: normal;\n"
+ "}\n\n"
+
+ "div.senderpic{\n"
+ " font-size:0.8em ! important;\n"
+ " border:1px solid black ! important;\n"
+ " background-color:%2 ! important;\n"
+ "}\n\n"
+
+ "div.senderstatus{\n"
+ " text-align:center ! important;\n"
+ "}\n\n"
+
+ "div.noprint {\n"
+ " display:none ! important;\n"
+ "}\n\n"
+ )
+ .arg(headerFont, extraPrintCss(headerFont))
+ quoteCSS + fullAddressList();
}
+QString CSSHelperBase::linkColorDefinition() const
+{
+ const QString linkColor = mLinkColor.name();
+ if (mUseBrowserColor) {
+#ifdef USE_HTML_STYLE_COLOR
+ const QString bgColor = mBackgroundColor.name();
+ const QString background = QStringLiteral(" background: %1 ! important;\n").arg(bgColor);
+
+ return QStringLiteral("div#headerbox a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n"
+ "div.htmlWarn a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n"
+ "div#header a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n"
+ "div.header {\n"
+ " %2"
+ "}\n\n"
+ "div#headerbox {\n"
+ " %2"
+ "}\n\n").arg(linkColor, background);
+#else
+ return QStringLiteral("div#headerbox a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n"
+ "div.htmlWarn a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n"
+ "div#header a:link {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n").arg(linkColor);
+#endif
+ } else {
+ return QStringLiteral("a {\n"
+ " color: %1 ! important;\n"
+ " text-decoration: none ! important;\n"
+ "}\n\n").arg(linkColor);
+ }
+}
+
QString CSSHelperBase::quoteCssDefinition() const
{
QString quoteCSS;
QString blockQuote;
for (int i = 0; i < 9; ++i) {
- blockQuote += QLatin1String("blockquote ");
- quoteCSS += QString::fromLatin1("%2{\n"
- " margin: 4pt 0 4pt 0;\n"
- " padding: 0 0 0 1em;\n"
- " border-left: 2px solid %1;\n"
- " unicode-bidi: -webkit-plaintext\n"
- "}\n\n").arg(quoteColorName(i)).arg(blockQuote);
+ blockQuote += QStringLiteral("blockquote ");
+ quoteCSS += QStringLiteral("%2{\n"
+ " margin: 4pt 0 4pt 0;\n"
+ " padding: 0 0 0 1em;\n"
+ " border-left: 2px solid %1;\n"
+ " unicode-bidi: -webkit-plaintext\n"
+ "}\n\n").arg(quoteColorName(i), blockQuote);
}
- quoteCSS += QLatin1String(".quotemarks{\n"
- " color:transparent;\n"
- " font-size:0px;\n"
- "}\n\n");
- quoteCSS += QLatin1String(".quotemarksemptyline{\n"
- " color:transparent;\n"
- " font-size:0px;\n"
- " line-height: 12pt;\n"
- "}\n\n");
+ quoteCSS += QStringLiteral(".quotemarks{\n"
+ " color:transparent;\n"
+ " font-size:0px;\n"
+ "}\n\n");
+ quoteCSS += QStringLiteral(".quotemarksemptyline{\n"
+ " color:transparent;\n"
+ " font-size:0px;\n"
+ " line-height: 12pt;\n"
+ "}\n\n");
return quoteCSS;
}
-QString CSSHelperBase::screenCssDefinitions(const CSSHelperBase *helper, bool fixed) const
+QString CSSHelperBase::defaultPrintHeaderFont() const
+{
+ const QString headerFont = QStringLiteral(" font-family: \"%1\" ! important;\n"
+ " font-size: %2pt ! important;\n")
+ .arg(mPrintFont.family())
+ .arg(mPrintFont.pointSize());
+ return headerFont;
+}
+
+QString CSSHelperBase::defaultScreenHeaderFont() const
{
- const QString fgColor = mForegroundColor.name();
- const QString bgColor = mBackgroundColor.name();
- const QString linkColor = mLinkColor.name();
const QString headerFont = QStringLiteral(" font-family: \"%1\" ! important;\n"
" font-size: %2px ! important;\n")
.arg(mBodyFont.family())
- .arg(pointsToPixel(helper->mPaintDevice, mBodyFont.pointSize()));
+ .arg(pointsToPixel(this->mPaintDevice, mBodyFont.pointSize()));
+ return headerFont;
+}
+
+QString CSSHelperBase::screenCssDefinitions(const CSSHelperBase *helper, bool fixed) const
+{
+ const QString bgColor = mBackgroundColor.name();
+ const QString headerFont = defaultScreenHeaderFont();
+#ifdef USE_HTML_STYLE_COLOR
+ const QString fgColor = mUseBrowserColor ? QStringLiteral("black") : mForegroundColor.name();
+ const QString background = mUseBrowserColor ? QString() : QStringLiteral(" background-color: %1 ! important;\n").arg(bgColor);
+ const QString signWarnBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpWarnB.name();
+ const QString cPgpErrBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpErrB.name();
+ const QString cPgpEncrBColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpEncrB.name();
+ const QString cPgpOk1BColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpOk1B.name();
+ const QString cPgpOk0BColorName = mUseBrowserColor ? QStringLiteral("white") : cPgpOk0B.name();
+#else
+ const QString fgColor = mForegroundColor.name();
const QString background = QStringLiteral(" background-color: %1 ! important;\n").arg(bgColor);
+ const QString signWarnBColorName = cPgpWarnB.name();
+ const QString cPgpErrBColorName = cPgpErrB.name();
+ const QString cPgpEncrBColorName = cPgpEncrB.name();
+ const QString cPgpOk1BColorName = cPgpOk1B.name();
+ const QString cPgpOk0BColorName = cPgpOk0B.name();
+#endif
const QString bodyFontSize = QString::number(pointsToPixel(helper->mPaintDevice, fontSize(
fixed))) + QLatin1String("px");
const QPalette &pal = QApplication::palette();
QString quoteCSS;
if (bodyFont(fixed).italic()) {
quoteCSS += QLatin1String(" font-style: italic ! important;\n");
}
if (bodyFont(fixed).bold()) {
quoteCSS += QLatin1String(" font-weight: bold ! important;\n");
}
if (!quoteCSS.isEmpty()) {
quoteCSS = QLatin1String("div.noquote {\n") + quoteCSS + QLatin1String("}\n\n");
}
// CSS definitions for quote levels 1-3
for (int i = 0; i < 3; ++i) {
quoteCSS += QStringLiteral("div.quotelevel%1 {\n"
" color: %2 ! important;\n")
.arg(QString::number(i + 1), quoteColorName(i));
if (mQuoteFont.italic()) {
- quoteCSS += QLatin1String(" font-style: italic ! important;\n");
+ quoteCSS += QStringLiteral(" font-style: italic ! important;\n");
}
if (mQuoteFont.bold()) {
- quoteCSS += QLatin1String(" font-weight: bold ! important;\n");
+ quoteCSS += QStringLiteral(" font-weight: bold ! important;\n");
}
if (mShrinkQuotes) {
quoteCSS += QLatin1String(" font-size: ") + QString::fromLatin1(quoteFontSizes[i])
+ QLatin1String("% ! important;\n");
}
- quoteCSS += QLatin1String("}\n\n");
+ quoteCSS += QStringLiteral("}\n\n");
}
// CSS definitions for quote levels 4+
for (int i = 0; i < 3; ++i) {
quoteCSS += QStringLiteral("div.deepquotelevel%1 {\n"
" color: %2 ! important;\n")
.arg(QString::number(i + 1), quoteColorName(i));
if (mQuoteFont.italic()) {
- quoteCSS += QLatin1String(" font-style: italic ! important;\n");
+ quoteCSS += QStringLiteral(" font-style: italic ! important;\n");
}
if (mQuoteFont.bold()) {
- quoteCSS += QLatin1String(" font-weight: bold ! important;\n");
+ quoteCSS += QStringLiteral(" font-weight: bold ! important;\n");
}
if (mShrinkQuotes) {
- quoteCSS += QLatin1String(" font-size: 70% ! important;\n");
+ quoteCSS += QStringLiteral(" font-size: 70% ! important;\n");
}
quoteCSS += QLatin1String("}\n\n");
}
quoteCSS += quoteCssDefinition();
return
QStringLiteral("body {\n"
" font-family: \"%1\" ! important;\n"
" font-size: %2 ! important;\n"
" color: %3 ! important;\n"
"%4"
"}\n\n")
.arg(bodyFont(fixed).family(),
bodyFontSize,
fgColor,
background)
+ + linkColorDefinition()
+
- /* This shouldn't be necessary because font properties are inherited
- automatically and causes wrong font settings with QTextBrowser
- because it doesn't understand the inherit statement
- QString::fromLatin1( "table {\n"
- " font-family: inherit ! important;\n"
- " font-size: inherit ! important;\n"
- " font-weight: inherit ! important;\n"
- "}\n\n" )
- +
- */
- QStringLiteral("a {\n"
- " color: %1 ! important;\n"
- " text-decoration: none ! important;\n"
- "}\n\n"
-
- "a.white {\n"
+ QStringLiteral("a.white {\n"
" color: white ! important;\n"
"}\n\n"
"a.black {\n"
" color: black ! important;\n"
"}\n\n"
- "table.textAtm { background-color: %2 ! important; }\n\n"
+ "table.textAtm { background-color: %1 ! important; }\n\n"
"tr.textAtmH {\n"
- " background-color: %3 ! important;\n"
- "%4"
+ " background-color: %2 ! important;\n"
+ "%3"
"}\n\n"
"tr.textAtmB {\n"
- " background-color: %3 ! important;\n"
+ " background-color: %2 ! important;\n"
"}\n\n"
"table.signInProgress,\n"
"table.rfc822 {\n"
- " background-color: %3 ! important;\n"
+ " background-color: %2 ! important;\n"
"}\n\n"
"tr.signInProgressH,\n"
"tr.rfc822H {\n"
- "%4"
- "}\n\n")
- .arg(linkColor, fgColor, bgColor, headerFont)
- +
- QStringLiteral("table.encr {\n"
- " background-color: %1 ! important;\n"
- "}\n\n"
-
- "tr.encrH {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- "%4"
- "}\n\n"
-
- "tr.encrB { background-color: %5 ! important; }\n\n")
+ "%3"
+ "}\n\n").arg(fgColor, bgColor, headerFont)
+ + QStringLiteral("table.encr {\n"
+ " background-color: %1 ! important;\n"
+ "}\n\n"
+
+ "tr.encrH {\n"
+ " background-color: %2 ! important;\n"
+ " color: %3 ! important;\n"
+ "%4"
+ "}\n\n"
+
+ "tr.encrB { background-color: %5 ! important; }\n\n")
.arg(cPgpEncrF.name(),
cPgpEncrH.name(),
cPgpEncrHT.name(),
headerFont,
- cPgpEncrB.name())
- +
- QStringLiteral("table.signOkKeyOk {\n"
- " background-color: %1 ! important;\n"
- "}\n\n"
-
- "tr.signOkKeyOkH {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- "%4"
- "}\n\n"
-
- "tr.signOkKeyOkB { background-color: %5 ! important; }\n\n")
+ cPgpEncrBColorName)
+ + QStringLiteral("table.signOkKeyOk {\n"
+ " background-color: %1 ! important;\n"
+ "}\n\n"
+
+ "tr.signOkKeyOkH {\n"
+ " background-color: %2 ! important;\n"
+ " color: %3 ! important;\n"
+ "%4"
+ "}\n\n"
+
+ "tr.signOkKeyOkB { background-color: %5 ! important; }\n\n")
.arg(cPgpOk1F.name(),
cPgpOk1H.name(),
cPgpOk1HT.name(),
headerFont,
- cPgpOk1B.name())
- +
- QStringLiteral("table.signOkKeyBad {\n"
- " background-color: %1 ! important;\n"
- "}\n\n"
-
- "tr.signOkKeyBadH {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- "%4"
- "}\n\n"
-
- "tr.signOkKeyBadB { background-color: %5 ! important; }\n\n")
+ cPgpOk1BColorName)
+ + QStringLiteral("table.signOkKeyBad {\n"
+ " background-color: %1 ! important;\n"
+ "}\n\n"
+
+ "tr.signOkKeyBadH {\n"
+ " background-color: %2 ! important;\n"
+ " color: %3 ! important;\n"
+ "%4"
+ "}\n\n"
+
+ "tr.signOkKeyBadB { background-color: %5 ! important; }\n\n")
.arg(cPgpOk0F.name(),
cPgpOk0H.name(),
cPgpOk0HT.name(),
headerFont,
- cPgpOk0B.name())
- +
- QStringLiteral("table.signWarn {\n"
- " background-color: %1 ! important;\n"
- "}\n\n"
-
- "tr.signWarnH {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- "%4"
- "}\n\n"
-
- "tr.signWarnB { background-color: %5 ! important; }\n\n")
+ cPgpOk0BColorName)
+ + QStringLiteral("table.signWarn {\n"
+ " background-color: %1 ! important;\n"
+ "}\n\n"
+
+ "tr.signWarnH {\n"
+ " background-color: %2 ! important;\n"
+ " color: %3 ! important;\n"
+ "%4"
+ "}\n\n"
+
+ "tr.signWarnB { background-color: %5 ! important; }\n\n")
.arg(cPgpWarnF.name(),
cPgpWarnH.name(),
cPgpWarnHT.name(),
headerFont,
- cPgpWarnB.name())
+ signWarnBColorName)
+
QStringLiteral("table.signErr {\n"
" background-color: %1 ! important;\n"
"}\n\n"
"tr.signErrH {\n"
" background-color: %2 ! important;\n"
" color: %3 ! important;\n"
"%4"
"}\n\n"
"tr.signErrB { background-color: %5 ! important; }\n\n")
.arg(cPgpErrF.name(),
cPgpErrH.name(),
cPgpErrHT.name(),
headerFont,
- cPgpErrB.name())
+ cPgpErrBColorName)
+
QStringLiteral("div.htmlWarn {\n"
" border: 2px solid %1 ! important;\n"
" line-height: normal;\n"
"}\n\n")
.arg(cHtmlWarning.name())
+
QStringLiteral("div.header {\n"
"%1"
"}\n\n"
- "div.fancy.header > div {\n"
- " background-color: %2 ! important;\n"
- " color: %3 ! important;\n"
- " border: solid %4 1px ! important;\n"
- " line-height: normal;\n"
- "}\n\n"
-
- "div.fancy.header > div a[href] { color: %3 ! important; }\n\n"
-
- "div.fancy.header > div a[href]:hover { text-decoration: underline ! important; }\n\n"
-
- "div.fancy.header > div.spamheader {\n"
- " background-color: #cdcdcd ! important;\n"
- " border-top: 0px ! important;\n"
- " padding: 3px ! important;\n"
- " color: black ! important;\n"
- " font-weight: bold ! important;\n"
- " font-size: smaller ! important;\n"
- "}\n\n"
-
- "div.fancy.header > table.outer {\n"
- " all: inherit;\n"
- " width: auto ! important;\n"
- " border-spacing: 0;\n"
- " background-color: %5 ! important;\n"
- " color: %4 ! important;\n"
- " border-bottom: solid %4 1px ! important;\n"
- " border-left: solid %4 1px ! important;\n"
- " border-right: solid %4 1px ! important;\n"
- "}\n\n"
+ "%2"
"div.senderpic{\n"
" padding: 0px ! important;\n"
" font-size:0.8em ! important;\n"
- " border:1px solid %6 ! important;\n"
- " background-color:%5 ! important;\n"
+ " border:1px solid %4 ! important;\n"
+ " background-color:%3 ! important;\n"
"}\n\n"
"div.senderstatus{\n"
" text-align:center ! important;\n"
"}\n\n"
)
- .arg(headerFont)
- .arg(pal.color(QPalette::Highlight).name(),
- pal.color(QPalette::HighlightedText).name(),
- pal.color(QPalette::Foreground).name(),
- pal.color(QPalette::Background).name())
- .arg(pal.color(QPalette::Mid).name())
+ .arg(headerFont, extraScreenCss(headerFont), pal.color(QPalette::Highlight).name(), pal.color(QPalette::Window).name())
+ quoteCSS + fullAddressList();
}
QString CSSHelperBase::commonCssDefinitions() const
{
- const QPalette &pal = QApplication::palette();
- const QString headerFont = QStringLiteral("font-family: \"%1\" ! important;\n"
- " font-size: %2px ! important;\n")
- .arg(mBodyFont.family())
- .arg(pointsToPixel(this->mPaintDevice, mBodyFont.pointSize()));
+ const QString headerFont = defaultScreenHeaderFont();
return
QStringLiteral("div.header {\n"
" margin-bottom: 10pt ! important;\n"
"}\n\n"
"table.textAtm {\n"
" margin-top: 10pt ! important;\n"
" margin-bottom: 10pt ! important;\n"
"}\n\n"
"tr.textAtmH,\n"
"tr.textAtmB,\n"
"tr.rfc822B {\n"
" font-weight: normal ! important;\n"
"}\n\n"
"tr.signInProgressH,\n"
"tr.rfc822H,\n"
"tr.encrH,\n"
"tr.signOkKeyOkH,\n"
"tr.signOkKeyBadH,\n"
"tr.signWarnH,\n"
"tr.signErrH {\n"
" font-weight: bold ! important;\n"
"}\n\n"
"tr.textAtmH td,\n"
"tr.textAtmB td {\n"
" padding: 3px ! important;\n"
"}\n\n"
"table.rfc822 {\n"
" width: 100% ! important;\n"
" border: solid 1px black ! important;\n"
" margin-top: 10pt ! important;\n"
" margin-bottom: 10pt ! important;\n"
"}\n\n"
"table.textAtm,\n"
"table.encr,\n"
"table.signWarn,\n"
"table.signErr,\n"
"table.signOkKeyBad,\n"
"table.signOkKeyOk,\n"
"table.signInProgress,\n"
- "div.fancy.header table {\n"
- " width: 100% ! important;\n"
- " border-width: 0px ! important;\n"
- " line-height: normal;\n"
- "}\n\n"
+
+ "%1"
"div.htmlWarn {\n"
" margin: 0px 5% ! important;\n"
" padding: 10px ! important;\n"
" text-align: left ! important;\n"
" line-height: normal;\n"
"}\n\n"
- "div.fancy.header > div {\n"
- " font-weight: bold ! important;\n"
- " padding: 4px ! important;\n"
- " line-height: normal;\n"
- "}\n\n"
-
- "div.fancy.header table {\n"
- " padding: 2px ! important;\n" // ### khtml bug: this is ignored
- " text-align: left ! important;\n"
- " border-collapse: separate ! important;\n"
- "}\n\n"
-
- "div.fancy.header table th {\n"
- " %3\n"
- " padding: 0px ! important;\n"
- " white-space: nowrap ! important;\n"
- " border-spacing: 0px ! important;\n"
- " text-align: left ! important;\n"
- " vertical-align: top ! important;\n"
- " background-color: %1 ! important;\n"
- " color: %2 ! important;\n"
- " border: 1px ! important;\n"
-
- "}\n\n"
-
- "div.fancy.header table td {\n"
- " %3\n"
- " padding: 0px ! important;\n"
- " border-spacing: 0px ! important;\n"
- " text-align: left ! important;\n"
- " vertical-align: top ! important;\n"
- " width: 100% ! important;\n"
- " background-color: %1 ! important;\n"
- " color: %2 ! important;\n"
- " border: 1px ! important;\n"
- "}\n\n"
-
- "div.fancy.header table a:hover {\n"
- " background-color: transparent ! important;\n"
- "}\n\n"
-
"div.quotelevelmark {\n"
" position: absolute;\n"
" margin-left:-10px;\n"
- "}\n\n").arg(pal.color(QPalette::Background).name()).arg(pal.color(QPalette::
- Foreground).name())
- .arg(headerFont)
- ;
+ "}\n\n").arg(extraCommonCss(headerFont));
}
void CSSHelperBase::setBodyFont(const QFont &font)
{
mBodyFont = font;
}
void CSSHelperBase::setPrintFont(const QFont &font)
{
mPrintFont = font;
}
QString CSSHelperBase::quoteColorName(int level) const
{
return quoteColor(level).name();
}
QColor CSSHelperBase::quoteColor(int level) const
{
const int actualLevel = qMax(level, 0) % 3;
return mQuoteColor[actualLevel];
}
QColor CSSHelperBase::pgpWarnColor() const
{
return cPgpWarnH;
}
}
diff --git a/messageviewer/src/viewer/csshelperbase.h b/messageviewer/src/viewer/csshelperbase.h
index 9007c69d..deda97ff 100644
--- a/messageviewer/src/viewer/csshelperbase.h
+++ b/messageviewer/src/viewer/csshelperbase.h
@@ -1,142 +1,155 @@
/* -*- c++ -*-
csshelper.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef MIMETREEPARSER_CSSHELPERBASE_H
#define MIMETREEPARSER_CSSHELPERBASE_H
#include "messageviewer_export.h"
#include <QColor>
#include <QFont>
#include <QUrl>
class QString;
class QPaintDevice;
namespace MessageViewer {
+class HeaderStylePlugin;
class MESSAGEVIEWER_EXPORT CSSHelperBase
{
public:
/** Construct a CSSHelper object and set its font and color settings to
default values.
Sub-Classes should put their config loading here.
*/
explicit CSSHelperBase(const QPaintDevice *pd);
virtual ~CSSHelperBase();
/** @return HTML head including style sheet definitions and the
&gt;body&lt; tag */
virtual QString htmlHead(bool fixedFont = false) const;
/** @return The collected CSS definitions as a string */
Q_REQUIRED_RESULT QString cssDefinitions(bool fixedFont = false) const;
/** @return a &lt;div&gt; start tag with embedded style
information suitable for quoted text with quote level @p level */
Q_REQUIRED_RESULT QString quoteFontTag(int level) const;
/** @return a &lt;div&gt; start tag with embedded style
information suitable for non-quoted text */
Q_REQUIRED_RESULT QString nonQuotedFontTag() const;
Q_REQUIRED_RESULT QFont bodyFont(bool fixedFont = false, bool printing = false) const;
void setBodyFont(const QFont &font);
void setPrintFont(const QFont &font);
/** @return the quote color for the given level, where level ranges from 0 to 2 **/
Q_REQUIRED_RESULT QColor quoteColor(int level) const;
Q_REQUIRED_RESULT QString quoteColorName(int level) const;
Q_REQUIRED_RESULT QColor pgpWarnColor() const;
Q_REQUIRED_RESULT QString addEndBlockQuote(int numberBlock) const;
Q_REQUIRED_RESULT QString addStartBlockQuote(int numberBlock) const;
+
+ virtual Q_REQUIRED_RESULT QString extraScreenCss(const QString &headerFont) const;
+ virtual Q_REQUIRED_RESULT QString extraPrintCss(const QString &headerFont) const;
+ virtual Q_REQUIRED_RESULT QString extraCommonCss(const QString &headerFont) const;
+
+ void setHeaderPlugin(const HeaderStylePlugin *headerPlugin);
+
protected:
/** Recalculate PGP frame and body colors (should be called after changing
color settings) */
void recalculatePGPColors();
protected:
QFont mBodyFont;
QFont mPrintFont;
QFont mFixedFont;
QFont mFixedPrintFont;
QFont mQuoteFont;
QColor mQuoteColor[3];
bool mRecycleQuoteColors;
bool mShrinkQuotes;
+ bool mUseBrowserColor = false;
QColor mForegroundColor;
QColor mLinkColor;
QColor mBackgroundColor;
// colors for PGP (Frame, Header, HeaderText, Body)
QColor cPgpOk1F;
QColor cPgpOk1H;
QColor cPgpOk1HT;
QColor cPgpOk1B;
QColor cPgpOk0F;
QColor cPgpOk0H;
QColor cPgpOk0HT;
QColor cPgpOk0B;
QColor cPgpWarnF;
QColor cPgpWarnH;
QColor cPgpWarnHT;
QColor cPgpWarnB;
QColor cPgpErrF;
QColor cPgpErrH;
QColor cPgpErrHT;
QColor cPgpErrB;
QColor cPgpEncrF;
QColor cPgpEncrH;
QColor cPgpEncrHT;
QColor cPgpEncrB;
// color of frame of warning preceding the source of HTML messages
QColor cHtmlWarning;
QString imgShowUrl;
QString imgHideUrl;
private:
QString quoteCssDefinition() const;
int fontSize(bool fixed, bool print = false) const;
// returns CSS rules specific to the print media type
QString printCssDefinitions(bool fixed) const;
// returns CSS rules specific to the screen media type
QString screenCssDefinitions(const CSSHelperBase *helper, bool fixed) const;
// returns CSS rules common to both screen and print media types
QString commonCssDefinitions() const;
QString fullAddressList() const;
+ QString linkColorDefinition() const;
+ QString defaultScreenHeaderFont() const;
+ QString defaultPrintHeaderFont() const;
-private:
const QPaintDevice *mPaintDevice = nullptr;
+ const HeaderStylePlugin *mHeaderPlugin = nullptr;
};
}
#endif
diff --git a/messageviewer/src/viewer/editorwatcher.cpp b/messageviewer/src/viewer/editorwatcher.cpp
index ac9973e0..c9c3dcc5 100644
--- a/messageviewer/src/viewer/editorwatcher.cpp
+++ b/messageviewer/src/viewer/editorwatcher.cpp
@@ -1,221 +1,221 @@
/*
Copyright (c) 2007 Volker Krause <vkrause@kde.org>
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.
*/
#include <config-messageviewer.h>
#include "editorwatcher.h"
#include "messageviewer_debug.h"
#include <KLocalizedString>
#include <kmessagebox.h>
#include <kopenwithdialog.h>
#include <kprocess.h>
#include <kmimetypetrader.h>
#include <KIO/DesktopExecParser>
-#include <qsocketnotifier.h>
+#include <QSocketNotifier>
#include <cassert>
#include <memory>
// inotify stuff taken from kdelibs/kio/kio/kdirwatch.cpp
#ifdef HAVE_SYS_INOTIFY_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#endif
using namespace MessageViewer;
EditorWatcher::EditorWatcher(const QUrl &url, const QString &mimeType, OpenWithOption option, QObject *parent, QWidget *parentWidget)
: QObject(parent)
, mUrl(url)
, mMimeType(mimeType)
, mParentWidget(parentWidget)
, mOpenWithOption(option)
{
assert(mUrl.isLocalFile());
mTimer.setSingleShot(true);
connect(&mTimer, &QTimer::timeout, this, &EditorWatcher::checkEditDone);
}
EditorWatcher::~EditorWatcher()
{
#ifdef HAVE_SYS_INOTIFY_H
::close(mInotifyFd);
#endif
}
EditorWatcher::ErrorEditorWatcher EditorWatcher::start()
{
// find an editor
QList<QUrl> list;
list.append(mUrl);
KService::Ptr offer
= KMimeTypeTrader::self()->preferredService(mMimeType, QStringLiteral("Application"));
if ((mOpenWithOption == OpenWithDialog) || !offer) {
std::unique_ptr<KOpenWithDialog> dlg(new KOpenWithDialog(list, i18n("Edit with:"),
QString(), mParentWidget));
const int dlgrc = dlg->exec();
if (dlgrc && dlg) {
offer = dlg->service();
}
if (!dlgrc) {
return Canceled;
}
if (!offer) {
return NoServiceFound;
}
}
#ifdef HAVE_SYS_INOTIFY_H
// monitor file
mInotifyFd = inotify_init();
if (mInotifyFd > 0) {
(void)fcntl(mInotifyFd, F_SETFD, FD_CLOEXEC);
mInotifyWatch = inotify_add_watch(mInotifyFd,
mUrl.path().toLatin1().constData(),
IN_CLOSE | IN_OPEN | IN_MODIFY | IN_ATTRIB);
if (mInotifyWatch >= 0) {
QSocketNotifier *sn = new QSocketNotifier(mInotifyFd, QSocketNotifier::Read, this);
connect(sn, &QSocketNotifier::activated, this, &EditorWatcher::inotifyEvent);
mHaveInotify = true;
mFileModified = false;
}
} else {
qCWarning(MESSAGEVIEWER_LOG) << "Failed to activate INOTIFY!";
}
#endif
// start the editor
KIO::DesktopExecParser parser(*offer, list);
parser.setUrlsAreTempFiles(false);
const QStringList params = parser.resultingArguments();
mEditor = new KProcess(this);
mEditor->setProgram(params);
- connect(mEditor, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished),
+ connect(mEditor, qOverload<int, QProcess::ExitStatus>(&KProcess::finished),
this, &EditorWatcher::editorExited);
mEditor->start();
if (!mEditor->waitForStarted()) {
return CannotStart;
}
mEditorRunning = true;
mEditTime.start();
return NoError;
}
bool EditorWatcher::fileChanged() const
{
return mFileModified;
}
QUrl EditorWatcher::url() const
{
return mUrl;
}
void EditorWatcher::inotifyEvent()
{
assert(mHaveInotify);
#ifdef HAVE_SYS_INOTIFY_H
int pending = -1;
int offsetStartRead = 0; // where we read into buffer
char buf[8192];
assert(mInotifyFd > -1);
ioctl(mInotifyFd, FIONREAD, &pending);
while (pending > 0) {
const int bytesToRead = qMin(pending, (int)sizeof(buf) - offsetStartRead);
int bytesAvailable = read(mInotifyFd, &buf[offsetStartRead], bytesToRead);
pending -= bytesAvailable;
bytesAvailable += offsetStartRead;
offsetStartRead = 0;
int offsetCurrent = 0;
while (bytesAvailable >= (int)sizeof(struct inotify_event)) {
const struct inotify_event *const event = (struct inotify_event *)&buf[offsetCurrent];
const int eventSize = sizeof(struct inotify_event) + event->len;
if (bytesAvailable < eventSize) {
break;
}
bytesAvailable -= eventSize;
offsetCurrent += eventSize;
if (event->mask & IN_OPEN) {
mFileOpen = true;
}
if (event->mask & IN_CLOSE) {
mFileOpen = false;
}
if (event->mask & (IN_MODIFY | IN_ATTRIB)) {
mFileModified = true;
}
}
if (bytesAvailable > 0) {
// copy partial event to beginning of buffer
memmove(buf, &buf[offsetCurrent], bytesAvailable);
offsetStartRead = bytesAvailable;
}
}
#endif
mTimer.start(500);
}
void EditorWatcher::editorExited()
{
mEditorRunning = false;
mTimer.start(500);
}
void EditorWatcher::checkEditDone()
{
if (mEditorRunning || (mFileOpen && mHaveInotify) || mDone) {
return;
}
static QStringList readOnlyMimeTypes;
if (readOnlyMimeTypes.isEmpty()) {
readOnlyMimeTypes << QStringLiteral("message/rfc822")
<< QStringLiteral("application/pdf");
}
// protect us against double-deletion by calling this method again while
// the subeventloop of the message box is running
mDone = true;
// check if it's a mime type that's mostly handled read-only
const bool isReadOnlyMimeType = (readOnlyMimeTypes.contains(mMimeType)
|| mMimeType.startsWith(QLatin1String("image/")));
// nobody can edit that fast, we seem to be unable to detect
// when the editor will be closed
if (mEditTime.elapsed() <= 3000 && !isReadOnlyMimeType) {
KMessageBox::information(mParentWidget,
i18n("KMail is unable to detect when the chosen editor is closed. "
"To avoid data loss, editing the attachment will be aborted."),
i18n("Unable to edit attachment"),
QStringLiteral("UnableToEditAttachment"));
}
Q_EMIT editDone(this);
deleteLater();
}
diff --git a/messageviewer/src/viewer/editorwatcher.h b/messageviewer/src/viewer/editorwatcher.h
index 8293b4c2..7c1c5422 100644
--- a/messageviewer/src/viewer/editorwatcher.h
+++ b/messageviewer/src/viewer/editorwatcher.h
@@ -1,92 +1,95 @@
/*
Copyright (c) 2007 Volker Krause <vkrause@kde.org>
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.
*/
#ifndef MESSAGEVIEWER_EDITORWATCHER_H
#define MESSAGEVIEWER_EDITORWATCHER_H
#include "messageviewer_export.h"
-#include <qurl.h>
+#include <QUrl>
-#include <qobject.h>
-#include <qtimer.h>
+#include <QObject>
+#include <QTimer>
#include <QTime>
class KProcess;
namespace MessageViewer {
/**
Starts an editor for the given URL and emits an signal when
editing has been finished. Both, the editor process as well
as the edited file are watched to work with as many as possible
editors.
*/
class MESSAGEVIEWER_EXPORT EditorWatcher : public QObject
{
Q_OBJECT
public:
enum OpenWithOption {
OpenWithDialog,
NoOpenWithDialog
};
enum ErrorEditorWatcher {
Unknown = 0,
Canceled,
NoServiceFound,
CannotStart,
NoError
};
/**
* Constructs an EditorWatcher.
+ * @param url the given URL.
+ * @param mimeType the data MIME type.
+ * @param option the open option.
* @param parent the parent object of this EditorWatcher, which will take care of deleting
* this EditorWatcher if the parent is deleted.
* @param parentWidget the parent widget of this EditorWatcher, which will be used as the parent
* widget for message dialogs.
*/
EditorWatcher(const QUrl &url, const QString &mimeType, OpenWithOption option, QObject *parent, QWidget *parentWidget);
~EditorWatcher();
ErrorEditorWatcher start();
Q_REQUIRED_RESULT bool fileChanged() const;
QUrl url() const;
Q_SIGNALS:
void editDone(MessageViewer::EditorWatcher *watcher);
private:
void editorExited();
void inotifyEvent();
void checkEditDone();
QUrl mUrl;
QString mMimeType;
QTimer mTimer;
QTime mEditTime;
KProcess *mEditor = nullptr;
QWidget *mParentWidget = nullptr;
int mInotifyFd = -1;
int mInotifyWatch = -1;
OpenWithOption mOpenWithOption;
bool mHaveInotify = false;
bool mFileOpen = false;
bool mEditorRunning = false;
bool mFileModified = true;
bool mDone = false;
};
}
#endif
diff --git a/messageviewer/src/viewer/messagedisplayformatattribute.cpp b/messageviewer/src/viewer/messagedisplayformatattribute.cpp
index 72dd8567..b86196dc 100644
--- a/messageviewer/src/viewer/messagedisplayformatattribute.cpp
+++ b/messageviewer/src/viewer/messagedisplayformatattribute.cpp
@@ -1,105 +1,105 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "messagedisplayformatattribute.h"
#include <QByteArray>
#include <QDataStream>
using namespace MessageViewer;
class MessageViewer::MessageDisplayFormatAttributePrivate
{
public:
MessageDisplayFormatAttributePrivate()
{
}
Viewer::DisplayFormatMessage messageFormat = Viewer::UseGlobalSetting;
bool remoteContent = false;
};
MessageDisplayFormatAttribute::MessageDisplayFormatAttribute()
: d(new MessageDisplayFormatAttributePrivate)
{
}
MessageDisplayFormatAttribute::~MessageDisplayFormatAttribute()
{
delete d;
}
MessageDisplayFormatAttribute *MessageDisplayFormatAttribute::clone() const
{
MessageDisplayFormatAttribute *messageDisplayFormatAttr = new MessageDisplayFormatAttribute();
messageDisplayFormatAttr->setMessageFormat(messageFormat());
messageDisplayFormatAttr->setRemoteContent(remoteContent());
return messageDisplayFormatAttr;
}
QByteArray MessageDisplayFormatAttribute::type() const
{
static const QByteArray sType("MessageDisplayFormatAttribute");
return sType;
}
QByteArray MessageDisplayFormatAttribute::serialized() const
{
QByteArray result;
QDataStream s(&result, QIODevice::WriteOnly);
s << messageFormat();
s << remoteContent();
return result;
}
void MessageDisplayFormatAttribute::setMessageFormat(Viewer::DisplayFormatMessage format)
{
d->messageFormat = format;
}
void MessageDisplayFormatAttribute::setRemoteContent(bool remote)
{
d->remoteContent = remote;
}
bool MessageDisplayFormatAttribute::remoteContent() const
{
return d->remoteContent;
}
bool MessageDisplayFormatAttribute::operator==(const MessageDisplayFormatAttribute &other) const
{
return (d->messageFormat == other.messageFormat())
&& (d->remoteContent == other.remoteContent());
}
Viewer::DisplayFormatMessage MessageDisplayFormatAttribute::messageFormat() const
{
return d->messageFormat;
}
void MessageDisplayFormatAttribute::deserialize(const QByteArray &data)
{
QDataStream s(data);
int value = 0;
s >> value;
d->messageFormat = static_cast<Viewer::DisplayFormatMessage>(value);
s >> d->remoteContent;
}
diff --git a/messageviewer/src/viewer/messagedisplayformatattribute.h b/messageviewer/src/viewer/messagedisplayformatattribute.h
index f4016b4c..7dd2103c 100644
--- a/messageviewer/src/viewer/messagedisplayformatattribute.h
+++ b/messageviewer/src/viewer/messagedisplayformatattribute.h
@@ -1,55 +1,55 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef MESSAGEDISPLAYFORMATATTRIBUTE_H
#define MESSAGEDISPLAYFORMATATTRIBUTE_H
#include <AkonadiCore/attribute.h>
#include "messageviewer_export.h"
#include "messageviewer/viewer.h"
namespace MessageViewer {
class MessageDisplayFormatAttributePrivate;
class MESSAGEVIEWER_EXPORT MessageDisplayFormatAttribute : public Akonadi::Attribute
{
public:
explicit MessageDisplayFormatAttribute();
~MessageDisplayFormatAttribute() override;
Q_REQUIRED_RESULT MessageDisplayFormatAttribute *clone() const override;
Q_REQUIRED_RESULT QByteArray type() const override;
Q_REQUIRED_RESULT QByteArray serialized() const override;
void deserialize(const QByteArray &data) override;
void setMessageFormat(Viewer::DisplayFormatMessage format);
Q_REQUIRED_RESULT Viewer::DisplayFormatMessage messageFormat() const;
void setRemoteContent(bool remote);
Q_REQUIRED_RESULT bool remoteContent() const;
Q_REQUIRED_RESULT bool operator==(const MessageDisplayFormatAttribute &other) const;
private:
friend class MessageDisplayFormatAttributePrivate;
MessageDisplayFormatAttributePrivate *const d;
};
}
#endif // MESSAGEDISPLAYFORMATATTRIBUTE_H
diff --git a/messageviewer/src/viewer/mimeparttree/mimeparttreeview.cpp b/messageviewer/src/viewer/mimeparttree/mimeparttreeview.cpp
index 5ff645cd..f1fb6026 100644
--- a/messageviewer/src/viewer/mimeparttree/mimeparttreeview.cpp
+++ b/messageviewer/src/viewer/mimeparttree/mimeparttreeview.cpp
@@ -1,102 +1,102 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "mimeparttreeview.h"
#include "mimetreemodel.h"
#include "settings/messageviewersettings.h"
#include <KMime/Content>
#include <KConfigGroup>
#include <QHeaderView>
using namespace MessageViewer;
MimePartTreeView::MimePartTreeView(QWidget *parent)
: QTreeView(parent)
{
setObjectName(QStringLiteral("mMimePartTree"));
mMimePartModel = new MimeTreeModel(this);
setModel(mMimePartModel);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
connect(this, &MimePartTreeView::destroyed, this, &MimePartTreeView::slotMimePartDestroyed);
setContextMenuPolicy(Qt::CustomContextMenu);
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
connect(mMimePartModel, &MimeTreeModel::modelReset, this, &MimePartTreeView::expandAll);
restoreMimePartTreeConfig();
setDragEnabled(true);
}
MimePartTreeView::~MimePartTreeView()
{
disconnect(mMimePartModel, &MimeTreeModel::modelReset, this, &MimePartTreeView::expandAll);
saveMimePartTreeConfig();
}
MimeTreeModel *MimePartTreeView::mimePartModel() const
{
return mMimePartModel;
}
void MimePartTreeView::restoreMimePartTreeConfig()
{
KConfigGroup grp(KSharedConfig::openConfig(), "MimePartTree");
header()->restoreState(grp.readEntry("State", QByteArray()));
}
void MimePartTreeView::saveMimePartTreeConfig()
{
KConfigGroup grp(KSharedConfig::openConfig(), "MimePartTree");
grp.writeEntry("State", header()->saveState());
}
void MimePartTreeView::slotMimePartDestroyed()
{
//root is either null or a modified tree that we need to clean up
clearModel();
}
void MimePartTreeView::clearModel()
{
delete mMimePartModel->root();
mMimePartModel->setRoot(nullptr);
}
void MimePartTreeView::setRoot(KMime::Content *root)
{
delete mMimePartModel->root();
mMimePartModel->setRoot(root);
}
-KMime::Content::List MimePartTreeView::selectedContents()
+KMime::Content::List MimePartTreeView::selectedContents() const
{
KMime::Content::List contents;
QItemSelectionModel *selectModel = selectionModel();
const QModelIndexList selectedRows = selectModel->selectedRows();
for (const QModelIndex &index : selectedRows) {
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
if (content) {
contents.append(content);
}
}
return contents;
}
diff --git a/messageviewer/src/viewer/mimeparttree/mimeparttreeview.h b/messageviewer/src/viewer/mimeparttree/mimeparttreeview.h
index 5dcc423d..68f0caaf 100644
--- a/messageviewer/src/viewer/mimeparttree/mimeparttreeview.h
+++ b/messageviewer/src/viewer/mimeparttree/mimeparttreeview.h
@@ -1,50 +1,50 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef MIMEPARTTREEVIEW_H
#define MIMEPARTTREEVIEW_H
#include <QTreeView>
#include <KMime/Message>
namespace MessageViewer {
class MimeTreeModel;
class MimePartTreeView : public QTreeView
{
Q_OBJECT
public:
explicit MimePartTreeView(QWidget *parent = nullptr);
~MimePartTreeView();
MessageViewer::MimeTreeModel *mimePartModel() const;
void clearModel();
void setRoot(KMime::Content *root);
- KMime::Content::List selectedContents();
+ Q_REQUIRED_RESULT KMime::Content::List selectedContents() const;
private:
void slotMimePartDestroyed();
void saveMimePartTreeConfig();
void restoreMimePartTreeConfig();
MimeTreeModel *mMimePartModel = nullptr;
};
}
#endif // MIMEPARTTREEVIEW_H
diff --git a/messageviewer/src/viewer/mimeparttree/mimetreemodel.cpp b/messageviewer/src/viewer/mimeparttree/mimetreemodel.cpp
index 8ee7124a..0f18dc0e 100644
--- a/messageviewer/src/viewer/mimeparttree/mimetreemodel.cpp
+++ b/messageviewer/src/viewer/mimeparttree/mimetreemodel.cpp
@@ -1,347 +1,346 @@
/*
Copyright (c) 2007, 2008 Volker Krause <vkrause@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.
*/
#include "mimetreemodel.h"
#include "messageviewer_debug.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/Util>
#include <KMime/Content>
#include <KMime/Message>
#include <KLocalizedString>
#include <KFormat>
#include <QIcon>
#include <QMimeDatabase>
#include <QTemporaryDir>
#include <QMimeData>
#include <QUrl>
using namespace MessageViewer;
class Q_DECL_HIDDEN MimeTreeModel::Private
{
public:
Private()
- : root(nullptr)
{
}
~Private()
{
qDeleteAll(tempDirs);
}
void clearTempDir()
{
qDeleteAll(tempDirs);
tempDirs.clear();
}
QString descriptionForContent(KMime::Content *content)
{
KMime::Message *const message = dynamic_cast<KMime::Message *>(content);
if (message && message->subject(false)) {
return message->subject()->asUnicodeString();
}
const QString name = MimeTreeParser::NodeHelper::fileName(content);
if (!name.isEmpty()) {
return name;
}
if (content->contentDescription(false)) {
const QString desc = content->contentDescription()->asUnicodeString();
if (!desc.isEmpty()) {
return desc;
}
}
return i18n("body part");
}
QString mimeTypeForContent(KMime::Content *content)
{
if (content->contentType(false)) {
return QString::fromLatin1(content->contentType()->mimeType());
}
return QString();
}
QString typeForContent(KMime::Content *content)
{
if (content->contentType(false)) {
const QString contentMimeType = QString::fromLatin1(content->contentType()->mimeType());
auto mimeType = m_mimeDb.mimeTypeForName(contentMimeType);
if (!mimeType.isValid()) {
return contentMimeType;
}
return mimeType.comment();
} else {
return QString();
}
}
QString sizeOfContent(KMime::Content *content)
{
if (content->body().isEmpty()) {
return QString();
}
return KFormat().formatByteSize(content->body().size());
}
QIcon iconForContent(KMime::Content *content)
{
if (content->contentType(false)) {
auto iconName
= MimeTreeParser::Util::iconNameForMimetype(QLatin1String(content->contentType()->
mimeType()));
auto mimeType
= m_mimeDb.mimeTypeForName(QString::fromLatin1(content->contentType()->mimeType()));
if (!mimeType.isValid()
|| mimeType.name() == QLatin1String("application/octet-stream")) {
const QString name = descriptionForContent(content);
mimeType = MimeTreeParser::Util::mimetype(name);
}
if (mimeType.isValid() && mimeType.name().startsWith(QLatin1String("multipart/"))) {
return QIcon::fromTheme(QStringLiteral("folder"));
} else if (!iconName.isEmpty() && iconName != QStringLiteral("unknown")) {
return QIcon::fromTheme(iconName);
} else if (mimeType.isValid() && !mimeType.iconName().isEmpty()) {
return QIcon::fromTheme(mimeType.iconName());
}
return QIcon();
} else {
return QIcon();
}
}
QList<QTemporaryDir *> tempDirs;
KMime::Content *root = nullptr;
QMimeDatabase m_mimeDb;
};
MimeTreeModel::MimeTreeModel(QObject *parent)
: QAbstractItemModel(parent)
, d(new Private)
{
}
MimeTreeModel::~MimeTreeModel()
{
delete d;
}
void MimeTreeModel::setRoot(KMime::Content *root)
{
if (d->root != root) {
beginResetModel();
d->clearTempDir();
d->root = root;
endResetModel();
}
}
KMime::Content *MimeTreeModel::root()
{
return d->root;
}
QModelIndex MimeTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!parent.isValid()) {
if (row != 0) {
return QModelIndex();
}
return createIndex(row, column, d->root);
}
KMime::Content *parentContent = static_cast<KMime::Content *>(parent.internalPointer());
if (!parentContent || parentContent->contents().count() <= row || row < 0) {
return QModelIndex();
}
KMime::Content *content = parentContent->contents().at(row);
return createIndex(row, column, content);
}
QModelIndex MimeTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) {
return QModelIndex();
}
KMime::Content *currentContent = static_cast<KMime::Content *>(index.internalPointer());
if (!currentContent) {
return QModelIndex();
}
KMime::ContentIndex currentIndex = d->root->indexForContent(currentContent);
if (!currentIndex.isValid()) {
return QModelIndex();
}
currentIndex.up();
KMime::Content *parentContent = d->root->content(currentIndex);
int row = 0;
if (currentIndex.isValid()) {
row = currentIndex.up() - 1; // 1 based -> 0 based
}
return createIndex(row, 0, parentContent);
}
int MimeTreeModel::rowCount(const QModelIndex &parent) const
{
if (!d->root) {
return 0;
}
if (!parent.isValid()) {
return 1;
}
KMime::Content *parentContent = static_cast<KMime::Content *>(parent.internalPointer());
if (parentContent) {
return parentContent->contents().count();
}
return 0;
}
int MimeTreeModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 3;
}
QVariant MimeTreeModel::data(const QModelIndex &index, int role) const
{
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
if (!content) {
return QVariant();
}
if (role == Qt::ToolTipRole) {
// TODO
//return d->root->indexForContent( content ).toString();
return QVariant();
}
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0:
return d->descriptionForContent(content);
case 1:
return d->typeForContent(content);
case 2:
return d->sizeOfContent(content);
}
}
if (role == Qt::DecorationRole && index.column() == 0) {
return d->iconForContent(content);
}
if (role == ContentIndexRole) {
return QVariant::fromValue(d->root->indexForContent(content));
}
if (role == ContentRole) {
return QVariant::fromValue(content);
}
if (role == MimeTypeRole) {
return d->mimeTypeForContent(content);
}
if (role == MainBodyPartRole) {
KMime::Message *topLevelMsg = dynamic_cast<KMime::Message *>(d->root);
if (!topLevelMsg) {
return false;
}
return topLevelMsg->mainBodyPart() == content;
}
if (role == AlternativeBodyPartRole) {
KMime::Message *topLevelMsg = dynamic_cast<KMime::Message *>(d->root);
if (!topLevelMsg) {
return false;
}
return topLevelMsg->mainBodyPart(content->contentType()->mimeType()) == content;
}
return QVariant();
}
QVariant MimeTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return i18n("Description");
case 1:
return i18n("Type");
case 2:
return i18n("Size");
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QMimeData *MimeTreeModel::mimeData(const QModelIndexList &indexes) const
{
QList<QUrl> urls;
for (const QModelIndex &index : indexes) {
//create dnd for one item not for all three columns.
if (index.column() != 0) {
continue;
}
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
if (!content) {
continue;
}
const QByteArray data = content->decodedContent();
if (data.isEmpty()) {
continue;
}
QTemporaryDir *tempDir = new QTemporaryDir; // Will remove the directory on destruction.
d->tempDirs.append(tempDir);
const QString fileName = tempDir->path() + QLatin1Char('/') + d->descriptionForContent(
content);
QFile f(fileName);
if (!f.open(QIODevice::WriteOnly)) {
qCWarning(MESSAGEVIEWER_LOG) << "Cannot write attachment:" << f.errorString();
continue;
}
if (f.write(data) != data.length()) {
qCWarning(MESSAGEVIEWER_LOG) << "Failed to write all data to file!";
continue;
}
f.close();
const QUrl url = QUrl::fromLocalFile(fileName);
qCDebug(MESSAGEVIEWER_LOG) << " temporary file " << url;
urls.append(url);
}
QMimeData *mimeData = new QMimeData;
mimeData->setUrls(urls);
return mimeData;
}
Qt::ItemFlags MimeTreeModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
return Qt::ItemIsDragEnabled | defaultFlags;
}
QStringList MimeTreeModel::mimeTypes() const
{
const QStringList types = {QStringLiteral("text/uri-list")};
return types;
}
diff --git a/messageviewer/src/viewer/stl_util.h b/messageviewer/src/viewer/stl_util.h
index 6e218450..78e919e7 100644
--- a/messageviewer/src/viewer/stl_util.h
+++ b/messageviewer/src/viewer/stl_util.h
@@ -1,46 +1,47 @@
/* -*- c++ -*-
stl_util.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2004 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
-#ifndef KDEPIM__MESSAGEVIEWER__STL_UTIL_H
-#define KDEPIM__MESSAGEVIEWER__STL_UTIL_H
+#ifndef KDEPIM_MESSAGEVIEWER_STL_UTIL_H
+#define KDEPIM_MESSAGEVIEWER_STL_UTIL_H
namespace MessageViewer {
template<typename T>
struct DeleteAndSetToZero {
void operator()(const T * &t)
{
delete t;
t = nullptr;
}
};
}
#endif // KDEPIM__MESSAGEVIEWER__STL_UTIL_H
diff --git a/messageviewer/src/viewer/urlhandlermanager.cpp b/messageviewer/src/viewer/urlhandlermanager.cpp
index ee94e4b1..a034b1f1 100644
--- a/messageviewer/src/viewer/urlhandlermanager.cpp
+++ b/messageviewer/src/viewer/urlhandlermanager.cpp
@@ -1,939 +1,940 @@
/* -*- c++ -*-
urlhandlermanager.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
+ 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.
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.
*/
#include "urlhandlermanager.h"
#include "urlhandlermanager_p.h"
#include "messageviewer_debug.h"
#include "interfaces/bodyparturlhandler.h"
#include "utils/mimetype.h"
#include "viewer/viewer_p.h"
#include "messageviewer/messageviewerutil.h"
#include "../utils/messageviewerutil_p.h"
#include "stl_util.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/PartNodeBodyPart>
#include <MessageCore/StringUtil>
#include <Libkdepim/BroadcastStatus>
#include <LibkdepimAkonadi/OpenEmailAddressJob>
#include <Akonadi/Contact/ContactSearchJob>
#include <KMime/Content>
#include <KEmailAddress>
#include <kmbox/mbox.h>
#include "messageflags.h"
#include <KRun>
#include <KIconLoader>
#include <KLocalizedString>
#include <kmessagebox.h>
#include <QMenu>
#include <QIcon>
#include <QApplication>
#include <QClipboard>
#include <QProcess>
#include <QFile>
#include <QMimeData>
#include <QDrag>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QUrl>
#include <QUrlQuery>
#include <algorithm>
#include <Libkleo/MessageBox>
using std::for_each;
using std::remove;
using std::find;
using namespace MessageViewer;
using namespace MessageCore;
URLHandlerManager *URLHandlerManager::self = nullptr;
//
//
// BodyPartURLHandlerManager
//
//
BodyPartURLHandlerManager::~BodyPartURLHandlerManager()
{
for_each(mHandlers.begin(), mHandlers.end(),
[](QVector<const Interface::BodyPartURLHandler *> &handlers) {
for_each(handlers.begin(), handlers.end(),
DeleteAndSetToZero<Interface::BodyPartURLHandler>());
});
}
void BodyPartURLHandlerManager::registerHandler(
const Interface::BodyPartURLHandler *handler, const QString &mimeType)
{
if (!handler) {
return;
}
unregisterHandler(handler); // don't produce duplicates
const auto mt = mimeType.toLatin1();
auto it = mHandlers.find(mt);
if (it == mHandlers.end()) {
it = mHandlers.insert(mt, {});
}
it->push_back(handler);
}
void BodyPartURLHandlerManager::unregisterHandler(
const Interface::BodyPartURLHandler *handler)
{
// don't delete them, only remove them from the list!
auto it = mHandlers.begin();
while (it != mHandlers.end()) {
it->erase(remove(it->begin(), it->end(), handler), it->end());
if (it->isEmpty()) {
it = mHandlers.erase(it);
} else {
++it;
}
}
}
static KMime::Content *partNodeFromXKMailUrl(const QUrl &url, ViewerPrivate *w, QString *path)
{
Q_ASSERT(path);
if (!w || url.scheme() != QLatin1String("x-kmail")) {
return nullptr;
}
const QString urlPath = url.path();
// urlPath format is: /bodypart/<random number>/<part id>/<path>
qCDebug(MESSAGEVIEWER_LOG) << "BodyPartURLHandler: urlPath ==" << urlPath;
if (!urlPath.startsWith(QLatin1String("/bodypart/"))) {
return nullptr;
}
const QStringList urlParts = urlPath.mid(10).split(QLatin1Char('/'));
if (urlParts.size() != 3) {
return nullptr;
}
//KMime::ContentIndex index( urlParts[1] );
QByteArray query(urlParts.at(2).toLatin1());
if (url.hasQuery()) {
query += "?" + url.query().toLatin1();
}
*path = QUrl::fromPercentEncoding(query);
return w->nodeFromUrl(QUrl(urlParts.at(1)));
}
QVector<const Interface::BodyPartURLHandler *> BodyPartURLHandlerManager::handlersForPart(KMime::Content *node) const
{
if (auto ct = node->contentType(false)) {
const auto mimeType = ct->mimeType();
if (!mimeType.isEmpty()) {
return mHandlers.value(mimeType);
}
}
return {};
}
bool BodyPartURLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
{
QString path;
KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
if (!node) {
return false;
}
MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr,
w->message().data(), node, w->nodeHelper());
for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) {
for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
if ((*it)->handleClick(w->viewer(), &part, path)) {
return true;
}
}
}
return false;
}
bool BodyPartURLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
{
QString path;
KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
if (!node) {
return false;
}
MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr,
w->message().data(), node, w->nodeHelper());
for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) {
for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
if ((*it)->handleContextMenuRequest(&part, path, p)) {
return true;
}
}
}
return false;
}
QString BodyPartURLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
{
QString path;
KMime::Content *node = partNodeFromXKMailUrl(url, w, &path);
if (!node) {
return QString();
}
MimeTreeParser::PartNodeBodyPart part(nullptr, nullptr,
w->message().data(), node, w->nodeHelper());
for (const auto &handlers : { handlersForPart(node), mHandlers.value({}) }) {
for (auto it = handlers.cbegin(), end = handlers.cend(); it != end; ++it) {
const QString msg = (*it)->statusBarMessage(&part, path);
if (!msg.isEmpty()) {
return msg;
}
}
}
return QString();
}
//
//
// URLHandlerManager
//
//
URLHandlerManager::URLHandlerManager()
{
registerHandler(new KMailProtocolURLHandler());
registerHandler(new ExpandCollapseQuoteURLManager());
registerHandler(new SMimeURLHandler());
registerHandler(new MailToURLHandler());
registerHandler(new ContactUidURLHandler());
registerHandler(new HtmlAnchorHandler());
registerHandler(new AttachmentURLHandler());
registerHandler(mBodyPartURLHandlerManager = new BodyPartURLHandlerManager());
registerHandler(new ShowAuditLogURLHandler());
registerHandler(new InternalImageURLHandler);
registerHandler(new KRunURLHandler());
}
URLHandlerManager::~URLHandlerManager()
{
for_each(mHandlers.begin(), mHandlers.end(),
DeleteAndSetToZero<MimeTreeParser::URLHandler>());
}
URLHandlerManager *URLHandlerManager::instance()
{
if (!self) {
self = new URLHandlerManager();
}
return self;
}
void URLHandlerManager::registerHandler(const MimeTreeParser::URLHandler *handler)
{
if (!handler) {
return;
}
unregisterHandler(handler); // don't produce duplicates
mHandlers.push_back(handler);
}
void URLHandlerManager::unregisterHandler(const MimeTreeParser::URLHandler *handler)
{
// don't delete them, only remove them from the list!
mHandlers.erase(remove(mHandlers.begin(), mHandlers.end(), handler), mHandlers.end());
}
void URLHandlerManager::registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType)
{
if (mBodyPartURLHandlerManager) {
mBodyPartURLHandlerManager->registerHandler(handler, mimeType);
}
}
void URLHandlerManager::unregisterHandler(const Interface::BodyPartURLHandler *handler)
{
if (mBodyPartURLHandlerManager) {
mBodyPartURLHandlerManager->unregisterHandler(handler);
}
}
bool URLHandlerManager::handleClick(const QUrl &url, ViewerPrivate *w) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
if ((*it)->handleClick(url, w)) {
return true;
}
}
return false;
}
bool URLHandlerManager::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
if ((*it)->handleShiftClick(url, window)) {
return true;
}
}
return false;
}
bool URLHandlerManager::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
if ((*it)->willHandleDrag(url, window)) {
return true;
}
}
return false;
}
bool URLHandlerManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
if ((*it)->handleDrag(url, window)) {
return true;
}
}
return false;
}
bool URLHandlerManager::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
if ((*it)->handleContextMenuRequest(url, p, w)) {
return true;
}
}
return false;
}
QString URLHandlerManager::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
{
HandlerList::const_iterator end(mHandlers.constEnd());
for (HandlerList::const_iterator it = mHandlers.constBegin(); it != end; ++it) {
const QString msg = (*it)->statusBarMessage(url, w);
if (!msg.isEmpty()) {
return msg;
}
}
return QString();
}
//
//
// URLHandler
//
//
bool KMailProtocolURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
if (url.scheme() == QLatin1String("kmail")) {
if (!w) {
return false;
}
const QString urlPath(url.path());
if (urlPath == QLatin1String("showHTML")) {
w->setDisplayFormatMessageOverwrite(MessageViewer::Viewer::Html);
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("goOnline")) {
w->goOnline();
return true;
} else if (urlPath == QLatin1String("goResourceOnline")) {
w->goResourceOnline();
return true;
} else if (urlPath == QLatin1String("loadExternal")) {
w->setHtmlLoadExtOverride(!w->htmlLoadExtOverride());
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("decryptMessage")) {
w->setDecryptMessageOverwrite(true);
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("showSignatureDetails")) {
w->setShowSignatureDetails(true);
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("hideSignatureDetails")) {
w->setShowSignatureDetails(false);
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("showEncryptionDetails")) {
w->setShowEncryptionDetails(true);
w->update(MimeTreeParser::Force);
return true;
} else if (urlPath == QLatin1String("hideEncryptionDetails")) {
w->setShowEncryptionDetails(false);
w->update(MimeTreeParser::Force);
return true;
}
}
return false;
}
QString KMailProtocolURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
const QString schemeStr = url.scheme();
if (schemeStr == QLatin1String("kmail")) {
const QString urlPath(url.path());
if (urlPath == QLatin1String("showHTML")) {
return i18n("Turn on HTML rendering for this message.");
} else if (urlPath == QLatin1String("loadExternal")) {
return i18n("Load external references from the Internet for this message.");
} else if (urlPath == QLatin1String("goOnline")) {
return i18n("Work online.");
} else if (urlPath == QLatin1String("goResourceOnline")) {
return i18n("Make account online.");
} else if (urlPath == QLatin1String("decryptMessage")) {
return i18n("Decrypt message.");
} else if (urlPath == QLatin1String("showSignatureDetails")) {
return i18n("Show signature details.");
} else if (urlPath == QLatin1String("hideSignatureDetails")) {
return i18n("Hide signature details.");
} else if (urlPath == QLatin1String("showEncryptionDetails")) {
return i18n("Show encryption details.");
} else if (urlPath == QLatin1String("hideEncryptionDetails")) {
return i18n("Hide encryption details.");
} else {
return QString();
}
} else if (schemeStr == QLatin1String("help")) {
return i18n("Open Documentation");
}
return QString();
}
bool ExpandCollapseQuoteURLManager::handleClick(const QUrl &url, ViewerPrivate *w) const
{
// kmail:levelquote/?num -> the level quote to collapse.
// kmail:levelquote/?-num -> expand all levels quote.
if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) {
const QString levelStr = url.query();
bool isNumber = false;
const int levelQuote = levelStr.toInt(&isNumber);
if (isNumber) {
w->slotLevelQuote(levelQuote);
}
return true;
}
return false;
}
bool ExpandCollapseQuoteURLManager::handleDrag(const QUrl &url, ViewerPrivate *window) const
{
Q_UNUSED(url);
Q_UNUSED(window);
return false;
}
QString ExpandCollapseQuoteURLManager::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
if (url.scheme() == QLatin1String("kmail") && url.path() == QLatin1String("levelquote")) {
const QString query = url.query();
if (query.length() >= 1) {
if (query[ 0 ] == QLatin1Char('-')) {
return i18n("Expand all quoted text.");
} else {
return i18n("Collapse quoted text.");
}
}
}
return QString();
}
bool foundSMIMEData(const QString &aUrl, QString &displayName, QString &libName, QString &keyId)
{
static QString showCertMan(QStringLiteral("showCertificate#"));
displayName.clear();
libName.clear();
keyId.clear();
int i1 = aUrl.indexOf(showCertMan);
if (-1 < i1) {
i1 += showCertMan.length();
int i2 = aUrl.indexOf(QLatin1String(" ### "), i1);
if (i1 < i2) {
displayName = aUrl.mid(i1, i2 - i1);
i1 = i2 + 5;
i2 = aUrl.indexOf(QLatin1String(" ### "), i1);
if (i1 < i2) {
libName = aUrl.mid(i1, i2 - i1);
i2 += 5;
keyId = aUrl.mid(i2);
/*
int len = aUrl.length();
if( len > i2+1 ) {
keyId = aUrl.mid( i2, 2 );
i2 += 2;
while( len > i2+1 ) {
keyId += ':';
keyId += aUrl.mid( i2, 2 );
i2 += 2;
}
}
*/
}
}
}
return !keyId.isEmpty();
}
bool SMimeURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
if (!url.hasFragment()) {
return false;
}
QString displayName, libName, keyId;
if (!foundSMIMEData(url.path() + QLatin1Char('#')
+QUrl::fromPercentEncoding(url.fragment().toLatin1()),
displayName, libName, keyId)) {
return false;
}
QStringList lst;
lst << QStringLiteral("--parent-windowid")
<< QString::number(static_cast<qlonglong>(w->viewer()->mainWindow()->winId()))
<< QStringLiteral("--query") << keyId;
if (!QProcess::startDetached(QStringLiteral("kleopatra"), lst)) {
KMessageBox::error(w->mMainWindow, i18n("Could not start certificate manager. "
"Please check your installation."),
i18n("KMail Error"));
}
return true;
}
QString SMimeURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
QString displayName, libName, keyId;
if (!foundSMIMEData(url.path() + QLatin1Char('#')
+QUrl::fromPercentEncoding(url.fragment().toLatin1()),
displayName, libName, keyId)) {
return QString();
}
return i18n("Show certificate 0x%1", keyId);
}
bool HtmlAnchorHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
if (!url.host().isEmpty() || !url.hasFragment()) {
return false;
}
w->scrollToAnchor(url.fragment());
return true;
}
QString MailToURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
if (url.scheme() == QLatin1String("mailto")) {
return KEmailAddress::decodeMailtoUrl(url);
}
return QString();
}
static QString searchFullEmailByUid(const QString &uid)
{
QString fullEmail;
Akonadi::ContactSearchJob *job = new Akonadi::ContactSearchJob();
job->setLimit(1);
job->setQuery(Akonadi::ContactSearchJob::ContactUid, uid,
Akonadi::ContactSearchJob::ExactMatch);
job->exec();
const KContacts::Addressee::List res = job->contacts();
if (!res.isEmpty()) {
KContacts::Addressee addr = res.at(0);
fullEmail = addr.fullEmail();
}
return fullEmail;
}
static void runKAddressBook(const QUrl &url)
{
KPIM::OpenEmailAddressJob *job = new KPIM::OpenEmailAddressJob(url.path(), nullptr);
job->start();
}
bool ContactUidURLHandler::handleClick(const QUrl &url, ViewerPrivate *) const
{
if (url.scheme() == QLatin1String("uid")) {
runKAddressBook(url);
return true;
} else {
return false;
}
}
bool ContactUidURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *) const
{
if (url.scheme() != QLatin1String("uid") || url.path().isEmpty()) {
return false;
}
QMenu menu;
QAction *open
= menu.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")),
i18n("&Open in Address Book"));
#ifndef QT_NO_CLIPBOARD
QAction *copy
= menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")),
i18n("&Copy Email Address"));
#endif
QAction *a = menu.exec(p);
if (a == open) {
runKAddressBook(url);
#ifndef QT_NO_CLIPBOARD
} else if (a == copy) {
const QString fullEmail = searchFullEmailByUid(url.path());
if (!fullEmail.isEmpty()) {
QClipboard *clip = QApplication::clipboard();
clip->setText(fullEmail, QClipboard::Clipboard);
clip->setText(fullEmail, QClipboard::Selection);
KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
}
#endif
}
return true;
}
QString ContactUidURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
if (url.scheme() == QLatin1String("uid")) {
return i18n("Lookup the contact in KAddressbook");
} else {
return QString();
}
}
KMime::Content *AttachmentURLHandler::nodeForUrl(const QUrl &url, ViewerPrivate *w) const
{
if (!w || !w->mMessage) {
return nullptr;
}
if (url.scheme() == QLatin1String("attachment")) {
KMime::Content *node = w->nodeFromUrl(url);
return node;
}
return nullptr;
}
bool AttachmentURLHandler::attachmentIsInHeader(const QUrl &url) const
{
bool inHeader = false;
QUrlQuery query(url);
const QString place = query.queryItemValue(QStringLiteral("place")).toLower();
if (!place.isNull()) {
inHeader = (place == QLatin1String("header"));
}
return inHeader;
}
bool AttachmentURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
KMime::Content *node = nodeForUrl(url, w);
if (!node) {
return false;
}
const bool inHeader = attachmentIsInHeader(url);
const bool shouldShowDialog = !w->nodeHelper()->isNodeDisplayedEmbedded(node) || !inHeader;
if (inHeader) {
w->scrollToAttachment(node);
}
- if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
- w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
- }
+ //if (shouldShowDialog || w->nodeHelper()->isNodeDisplayedHidden(node)) {
+ w->openAttachment(node, w->nodeHelper()->tempFileUrlFromNode(node));
+ //}
return true;
}
bool AttachmentURLHandler::handleShiftClick(const QUrl &url, ViewerPrivate *window) const
{
KMime::Content *node = nodeForUrl(url, window);
if (!node) {
return false;
}
if (!window) {
return false;
}
if (node->contentType()->mimeType() == "text/x-moz-deleted") {
return false;
}
const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
KMime::Message::Ptr message(new KMime::Message);
message->setContent(node->parent()->bodyAsMessage()->encodedContent());
message->parse();
Akonadi::Item item;
item.setPayload<KMime::Message::Ptr>(message);
Akonadi::MessageFlags::copyMessageFlags(*message, item);
item.setMimeType(KMime::Message::mimeType());
QUrl url;
if (MessageViewer::Util::saveMessageInMboxAndGetUrl(url, Akonadi::Item::List() << item, window->viewer())) {
window->viewer()->showOpenAttachmentFolderWidget(QList<QUrl>() << url);
}
} else {
QList<QUrl> urlList;
if (Util::saveContents(window->viewer(), KMime::Content::List() << node, urlList)) {
window->viewer()->showOpenAttachmentFolderWidget(urlList);
}
}
return true;
}
bool AttachmentURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
{
return nodeForUrl(url, window) != nullptr;
}
bool AttachmentURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
{
#ifndef QT_NO_DRAGANDDROP
KMime::Content *node = nodeForUrl(url, window);
if (!node) {
return false;
}
if (node->contentType()->mimeType() == "text/x-moz-deleted") {
return false;
}
QString fileName;
QUrl tUrl;
const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
KMime::Message::Ptr message(new KMime::Message);
message->setContent(node->parent()->bodyAsMessage()->encodedContent());
message->parse();
Akonadi::Item item;
item.setPayload<KMime::Message::Ptr>(message);
Akonadi::MessageFlags::copyMessageFlags(*message, item);
item.setMimeType(KMime::Message::mimeType());
fileName = window->nodeHelper()->writeFileToTempFile(node, Util::generateMboxFileName(item));
KMBox::MBox mbox;
QFile::remove(fileName);
if (!mbox.load(fileName)) {
qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to open file";
return false;
}
mbox.appendMessage(item.payload<KMime::Message::Ptr>());
if (!mbox.save()) {
qCWarning(MESSAGEVIEWER_LOG) << "MBOX: Impossible to save file";
return false;
}
tUrl = QUrl::fromLocalFile(fileName);
} else {
if (node->header<KMime::Headers::Subject>()) {
if (!node->contents().isEmpty()) {
node = node->contents().constLast();
fileName = window->nodeHelper()->writeNodeToTempFile(node);
tUrl = QUrl::fromLocalFile(fileName);
}
}
if (fileName.isEmpty()) {
tUrl = window->nodeHelper()->tempFileUrlFromNode(node);
fileName = tUrl.path();
}
}
if (!fileName.isEmpty()) {
QFile f(fileName);
f.setPermissions(
QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::ReadGroup
| QFile::ReadOther);
const QString icon = Util::iconPathForContent(node, KIconLoader::Small);
QDrag *drag = new QDrag(window->viewer());
QMimeData *mimeData = new QMimeData();
mimeData->setUrls(QList<QUrl>() << tUrl);
drag->setMimeData(mimeData);
if (!icon.isEmpty()) {
drag->setPixmap(QIcon::fromTheme(icon).pixmap(16, 16));
}
drag->start();
return true;
} else
#endif
return false;
}
bool AttachmentURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w) const
{
KMime::Content *node = nodeForUrl(url, w);
if (!node) {
return false;
}
// PENDING(romain_kdab) : replace with toLocalFile() ?
w->showAttachmentPopup(node, w->nodeHelper()->tempFileUrlFromNode(node).path(), p);
return true;
}
QString AttachmentURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *w) const
{
KMime::Content *node = nodeForUrl(url, w);
if (!node) {
return QString();
}
const QString name = MimeTreeParser::NodeHelper::fileName(node);
if (!name.isEmpty()) {
return i18n("Attachment: %1", name);
} else if (dynamic_cast<KMime::Message *>(node)) {
if (node->header<KMime::Headers::Subject>()) {
return i18n("Encapsulated Message (Subject: %1)",
node->header<KMime::Headers::Subject>()->asUnicodeString());
} else {
return i18n("Encapsulated Message");
}
}
return i18n("Unnamed attachment");
}
static QString extractAuditLog(const QUrl &url)
{
if (url.scheme() != QLatin1String("kmail")
|| url.path() != QLatin1String("showAuditLog")) {
return QString();
}
QUrlQuery query(url);
Q_ASSERT(!query.queryItemValue(QStringLiteral("log")).isEmpty());
return query.queryItemValue(QStringLiteral("log"));
}
bool ShowAuditLogURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
const QString auditLog = extractAuditLog(url);
if (auditLog.isEmpty()) {
return false;
}
Kleo::MessageBox::auditLog(w->mMainWindow, auditLog);
return true;
}
bool ShowAuditLogURLHandler::handleContextMenuRequest(const QUrl &url, const QPoint &, ViewerPrivate *w) const
{
Q_UNUSED(w);
// disable RMB for my own links:
return !extractAuditLog(url).isEmpty();
}
QString ShowAuditLogURLHandler::statusBarMessage(const QUrl &url, ViewerPrivate *) const
{
if (extractAuditLog(url).isEmpty()) {
return QString();
} else {
return i18n("Show GnuPG Audit Log for this operation");
}
}
bool ShowAuditLogURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
{
Q_UNUSED(url);
Q_UNUSED(window);
return true;
}
bool InternalImageURLHandler::handleDrag(const QUrl &url, ViewerPrivate *window) const
{
Q_UNUSED(window);
Q_UNUSED(url);
// This will only be called when willHandleDrag() was true. Return false here, that will
// notify ViewerPrivate::eventFilter() that no drag was started.
return false;
}
bool InternalImageURLHandler::willHandleDrag(const QUrl &url, ViewerPrivate *window) const
{
Q_UNUSED(window);
if (url.scheme() == QLatin1String("data") && url.path().startsWith(QLatin1String("image"))) {
return true;
}
const QString imagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(
"libmessageviewer/pics/"),
QStandardPaths::LocateDirectory);
return url.path().contains(imagePath);
}
bool KRunURLHandler::handleClick(const QUrl &url, ViewerPrivate *w) const
{
const QString scheme(url.scheme());
if ((scheme == QLatin1String("http")) || (scheme == QLatin1String("https"))
|| (scheme == QLatin1String("ftp")) || (scheme == QLatin1String("file"))
|| (scheme == QLatin1String("ftps")) || (scheme == QLatin1String("sftp"))
|| (scheme == QLatin1String("help")) || (scheme == QLatin1String("vnc"))
|| (scheme == QLatin1String("smb")) || (scheme == QLatin1String("fish"))
- || (scheme == QLatin1String("news"))) {
+ || (scheme == QLatin1String("news")) || (scheme == QLatin1String("tel"))) {
KPIM::BroadcastStatus::instance()->setTransientStatusMsg(i18n("Opening URL..."));
QTimer::singleShot(2000, KPIM::BroadcastStatus::instance(), &KPIM::BroadcastStatus::reset);
QMimeDatabase mimeDb;
auto mime = mimeDb.mimeTypeForUrl(url);
if (mime.name() == QLatin1String("application/x-desktop")
|| mime.name() == QLatin1String("application/x-executable")
|| mime.name() == QLatin1String("application/x-ms-dos-executable")
|| mime.name() == QLatin1String("application/x-shellscript")) {
if (KMessageBox::warningYesNo(nullptr,
xi18nc("@info",
"Do you really want to execute <filename>%1</filename>?",
url.toDisplayString(QUrl::PreferLocalFile)),
QString(), KGuiItem(i18n("Execute")),
KStandardGuiItem::cancel()) != KMessageBox::Yes) {
return true;
}
}
w->checkPhishingUrl();
return true;
} else {
return false;
}
}
diff --git a/messageviewer/src/viewer/urlhandlermanager.h b/messageviewer/src/viewer/urlhandlermanager.h
index e67d0a5d..e8ced05f 100644
--- a/messageviewer/src/viewer/urlhandlermanager.h
+++ b/messageviewer/src/viewer/urlhandlermanager.h
@@ -1,89 +1,90 @@
/* -*- c++ -*-
urlhandlermanager.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
+ 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.
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.
*/
#ifndef MESSAGEVIEWER_URLHANDLERMANAGER_H
#define MESSAGEVIEWER_URLHANDLERMANAGER_H
#include "messageviewer_private_export.h"
#include <QVector>
class QUrl;
class QString;
class QPoint;
namespace MimeTreeParser {
class URLHandler;
}
namespace MessageViewer {
namespace Interface {
class BodyPartURLHandler;
}
class ViewerPrivate;
class BodyPartURLHandlerManager;
/**
* @short Singleton to manage the list of URLHandlers
* @author Marc Mutz <mutz@kde.org>
*/
class MESSAGEVIEWER_TESTS_EXPORT URLHandlerManager
{
static URLHandlerManager *self;
URLHandlerManager();
public:
~URLHandlerManager();
static URLHandlerManager *instance();
void registerHandler(const MimeTreeParser::URLHandler *handler);
void unregisterHandler(const MimeTreeParser::URLHandler *handler);
void registerHandler(const Interface::BodyPartURLHandler *handler, const QString &mimeType);
void unregisterHandler(const Interface::BodyPartURLHandler *handler);
Q_REQUIRED_RESULT bool handleClick(const QUrl &url, ViewerPrivate *w = nullptr) const;
Q_REQUIRED_RESULT bool handleShiftClick(const QUrl &url, ViewerPrivate *window = nullptr) const;
Q_REQUIRED_RESULT bool handleContextMenuRequest(const QUrl &url, const QPoint &p, ViewerPrivate *w = nullptr) const;
Q_REQUIRED_RESULT bool willHandleDrag(const QUrl &url, ViewerPrivate *window = nullptr) const;
Q_REQUIRED_RESULT bool handleDrag(const QUrl &url, ViewerPrivate *window = nullptr) const;
Q_REQUIRED_RESULT QString statusBarMessage(const QUrl &url, ViewerPrivate *w = nullptr) const;
private:
typedef QVector<const MimeTreeParser::URLHandler *> HandlerList;
HandlerList mHandlers;
BodyPartURLHandlerManager *mBodyPartURLHandlerManager = nullptr;
};
}
#endif // MESSAGEVIEWER_URLHANDLERMANAGER_H
diff --git a/messageviewer/src/viewer/viewer.cpp b/messageviewer/src/viewer/viewer.cpp
index ee96a44d..bef524bd 100644
--- a/messageviewer/src/viewer/viewer.cpp
+++ b/messageviewer/src/viewer/viewer.cpp
@@ -1,734 +1,749 @@
/*
This file is part of KMail, the KDE mail client.
Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
*/
// define this to copy all html that is written to the readerwindow to
// filehtmlwriter.out in the current working directory
//#define KMAIL_READER_HTML_DEBUG 1
#include "viewer.h"
#include "viewer_p.h"
#include "widgets/configurewidget.h"
#include "csshelper.h"
#include "settings/messageviewersettings.h"
#include "viewer/webengine/mailwebengineview.h"
#include <WebEngineViewer/WebHitTestResult>
#include <WebEngineViewer/WebEngineManageScript>
#include "viewer/mimeparttree/mimetreemodel.h"
#include "viewer/mimeparttree/mimeparttreeview.h"
#include "widgets/zoomactionmenu.h"
#include <Akonadi/KMime/MessageParts>
#include <AkonadiCore/itemfetchjob.h>
#include <AkonadiCore/itemfetchscope.h>
#include <QUrl>
#include <mailtransportakonadi/errorattribute.h>
#include <KLocalizedString>
#include <QAction>
namespace MessageViewer {
class AbstractMessageLoadedHandler::Private
{
public:
Akonadi::Session *mSession = nullptr;
};
AbstractMessageLoadedHandler::AbstractMessageLoadedHandler()
: d(new Private)
{
}
AbstractMessageLoadedHandler::~AbstractMessageLoadedHandler()
{
delete d;
}
void AbstractMessageLoadedHandler::setSession(Akonadi::Session *session)
{
d->mSession = session;
}
Akonadi::Session *AbstractMessageLoadedHandler::session() const
{
return d->mSession;
}
Viewer::Viewer(QWidget *aParent, QWidget *mainWindow, KActionCollection *actionCollection)
: QWidget(aParent)
, d_ptr(new ViewerPrivate(this, mainWindow, actionCollection))
{
initialize();
}
Viewer::~Viewer()
{
//the d_ptr is automatically deleted
}
void Viewer::initialize()
{
connect(d_ptr, &ViewerPrivate::displayPopupMenu,
this, &Viewer::displayPopupMenu);
connect(d_ptr, &ViewerPrivate::popupMenu,
this, &Viewer::popupMenu);
connect(d_ptr, SIGNAL(urlClicked(Akonadi::Item,QUrl)),
SIGNAL(urlClicked(Akonadi::Item,QUrl)));
connect(d_ptr, &ViewerPrivate::requestConfigSync, this, &Viewer::requestConfigSync);
connect(d_ptr, &ViewerPrivate::makeResourceOnline, this, &Viewer::makeResourceOnline);
connect(d_ptr, &ViewerPrivate::showReader,
this, &Viewer::showReader);
connect(d_ptr, &ViewerPrivate::showMessage, this, &Viewer::showMessage);
connect(d_ptr, &ViewerPrivate::replyMessageTo, this, &Viewer::replyMessageTo);
connect(d_ptr, &ViewerPrivate::showStatusBarMessage,
this, &Viewer::showStatusBarMessage);
connect(d_ptr, &ViewerPrivate::itemRemoved,
this, &Viewer::itemRemoved);
connect(d_ptr, &ViewerPrivate::changeDisplayMail, this, &Viewer::slotChangeDisplayMail);
connect(d_ptr, &ViewerPrivate::moveMessageToTrash, this, &Viewer::moveMessageToTrash);
connect(d_ptr, &ViewerPrivate::pageIsScrolledToBottom, this, &Viewer::pageIsScrolledToBottom);
connect(d_ptr, &ViewerPrivate::printingFinished, this, &Viewer::printingFinished);
connect(d_ptr, &ViewerPrivate::zoomChanged, this, &Viewer::zoomChanged);
+ connect(d_ptr, &ViewerPrivate::showNextMessage, this, &Viewer::showNextMessage);
+ connect(d_ptr, &ViewerPrivate::showPreviousMessage, this, &Viewer::showPreviousMessage);
+
setMessage(KMime::Message::Ptr(), MimeTreeParser::Delayed);
}
void Viewer::changeEvent(QEvent *event)
{
Q_D(Viewer);
if (event->type() == QEvent::FontChange) {
d->slotGeneralFontChanged();
}
QWidget::changeEvent(event);
}
void Viewer::setMessage(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode)
{
Q_D(Viewer);
if (message == d->message()) {
return;
}
d->setMessage(message, updateMode);
}
void Viewer::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode)
{
Q_D(Viewer);
if (d->messageItem() == item) {
return;
}
if (!item.isValid() || item.loadedPayloadParts().contains(Akonadi::MessagePart::Body)) {
d->setMessageItem(item, updateMode);
} else {
Akonadi::ItemFetchJob *job = createFetchJob(item);
- connect(job, &Akonadi::ItemFetchJob::result, [this, d](KJob *job) {
+ connect(job, &Akonadi::ItemFetchJob::result, [ d](KJob *job) {
d->itemFetchResult(job);
});
d->displaySplashPage(i18n("Loading message..."));
}
}
QString Viewer::messagePath() const
{
Q_D(const Viewer);
return d->mMessagePath;
}
void Viewer::setMessagePath(const QString &path)
{
Q_D(Viewer);
d->mMessagePath = path;
}
void Viewer::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain)
{
Q_D(Viewer);
d->displaySplashPage(templateName, data, domain);
}
void Viewer::enableMessageDisplay()
{
Q_D(Viewer);
d->enableMessageDisplay();
}
void Viewer::printMessage(const Akonadi::Item &msg)
{
Q_D(Viewer);
d->printMessage(msg);
}
void Viewer::printPreviewMessage(const Akonadi::Item &message)
{
Q_D(Viewer);
d->printPreviewMessage(message);
}
void Viewer::printPreview()
{
Q_D(Viewer);
d->slotPrintPreview();
}
void Viewer::print()
{
Q_D(Viewer);
d->slotPrintMessage();
}
void Viewer::resizeEvent(QResizeEvent *)
{
Q_D(Viewer);
if (!d->mResizeTimer.isActive()) {
//
// Combine all resize operations that are requested as long a
// the timer runs.
//
d->mResizeTimer.start(100);
}
}
void Viewer::closeEvent(QCloseEvent *e)
{
Q_D(Viewer);
QWidget::closeEvent(e);
d->writeConfig();
}
void Viewer::slotAttachmentSaveAs()
{
Q_D(Viewer);
d->slotAttachmentSaveAs();
}
void Viewer::slotAttachmentSaveAll()
{
Q_D(Viewer);
d->slotAttachmentSaveAll();
}
void Viewer::slotSaveMessage()
{
Q_D(Viewer);
d->slotSaveMessage();
}
void Viewer::slotScrollUp()
{
Q_D(Viewer);
d->mViewer->scrollUp(10);
}
void Viewer::slotScrollDown()
{
Q_D(Viewer);
d->mViewer->scrollDown(10);
}
void Viewer::atBottom()
{
Q_D(const Viewer);
d->mViewer->isScrolledToBottom();
}
void Viewer::slotJumpDown()
{
Q_D(Viewer);
d->mViewer->scrollPageDown(100);
}
void Viewer::slotScrollPrior()
{
Q_D(Viewer);
d->mViewer->scrollPageUp(80);
}
void Viewer::slotScrollNext()
{
Q_D(Viewer);
d->mViewer->scrollPageDown(80);
}
QString Viewer::selectedText() const
{
Q_D(const Viewer);
return d->mViewer->selectedText();
}
Viewer::DisplayFormatMessage Viewer::displayFormatMessageOverwrite() const
{
Q_D(const Viewer);
return d->displayFormatMessageOverwrite();
}
void Viewer::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format)
{
Q_D(Viewer);
d->setDisplayFormatMessageOverwrite(format);
}
void Viewer::setHtmlLoadExtDefault(bool loadExtDefault)
{
Q_D(Viewer);
d->setHtmlLoadExtDefault(loadExtDefault);
}
void Viewer::setHtmlLoadExtOverride(bool loadExtOverride)
{
Q_D(Viewer);
d->setHtmlLoadExtOverride(loadExtOverride);
}
bool Viewer::htmlLoadExtOverride() const
{
Q_D(const Viewer);
return d->htmlLoadExtOverride();
}
bool Viewer::htmlMail() const
{
Q_D(const Viewer);
return d->htmlMail();
}
bool Viewer::htmlLoadExternal() const
{
Q_D(const Viewer);
return d->htmlLoadExternal();
}
bool Viewer::isFixedFont() const
{
Q_D(const Viewer);
return d->mUseFixedFont;
}
void Viewer::setUseFixedFont(bool useFixedFont)
{
Q_D(Viewer);
d->setUseFixedFont(useFixedFont);
}
QWidget *Viewer::mainWindow()
{
Q_D(Viewer);
return d->mMainWindow;
}
void Viewer::setDecryptMessageOverwrite(bool overwrite)
{
Q_D(Viewer);
d->setDecryptMessageOverwrite(overwrite);
}
KMime::Message::Ptr Viewer::message() const
{
Q_D(const Viewer);
return d->mMessage;
}
Akonadi::Item Viewer::messageItem() const
{
Q_D(const Viewer);
return d->mMessageItem;
}
bool Viewer::event(QEvent *e)
{
Q_D(Viewer);
if (e->type() == QEvent::PaletteChange) {
- delete d->mCSSHelper;
- d->mCSSHelper = new CSSHelper(d->mViewer);
+ d->recreateCssHelper();
d->update(MimeTreeParser::Force);
e->accept();
return true;
}
return QWidget::event(e);
}
void Viewer::slotFind()
{
Q_D(Viewer);
d->slotFind();
}
const AttachmentStrategy *Viewer::attachmentStrategy() const
{
Q_D(const Viewer);
return d->attachmentStrategy();
}
void Viewer::setAttachmentStrategy(const AttachmentStrategy *strategy)
{
Q_D(Viewer);
d->setAttachmentStrategy(strategy);
}
QString Viewer::overrideEncoding() const
{
Q_D(const Viewer);
return d->overrideEncoding();
}
void Viewer::setOverrideEncoding(const QString &encoding)
{
Q_D(Viewer);
d->setOverrideEncoding(encoding);
}
CSSHelper *Viewer::cssHelper() const
{
Q_D(const Viewer);
return d->cssHelper();
}
KToggleAction *Viewer::toggleFixFontAction() const
{
Q_D(const Viewer);
return d->mToggleFixFontAction;
}
bool Viewer::mimePartTreeIsEmpty() const
{
Q_D(const Viewer);
return d->mimePartTreeIsEmpty();
}
KToggleAction *Viewer::toggleMimePartTreeAction() const
{
Q_D(const Viewer);
return d->mToggleMimePartTreeAction;
}
QAction *Viewer::selectAllAction() const
{
Q_D(const Viewer);
return d->mSelectAllAction;
}
QAction *Viewer::viewSourceAction() const
{
Q_D(const Viewer);
return d->mViewSourceAction;
}
QAction *Viewer::copyURLAction() const
{
Q_D(const Viewer);
return d->mCopyURLAction;
}
QAction *Viewer::copyAction() const
{
Q_D(const Viewer);
return d->mCopyAction;
}
QAction *Viewer::speakTextAction() const
{
Q_D(const Viewer);
return d->mSpeakTextAction;
}
QAction *Viewer::copyImageLocation() const
{
Q_D(const Viewer);
return d->mCopyImageLocation;
}
QAction *Viewer::saveAsAction() const
{
Q_D(const Viewer);
return d->mSaveMessageAction;
}
QAction *Viewer::urlOpenAction() const
{
Q_D(const Viewer);
return d->mUrlOpenAction;
}
bool Viewer::printingMode() const
{
Q_D(const Viewer);
return d->printingMode();
}
void Viewer::setPrinting(bool enable)
{
Q_D(Viewer);
d->setPrinting(enable);
}
void Viewer::writeConfig(bool force)
{
Q_D(Viewer);
d->writeConfig(force);
}
QUrl Viewer::urlClicked() const
{
Q_D(const Viewer);
return d->mClickedUrl;
}
QUrl Viewer::imageUrlClicked() const
{
Q_D(const Viewer);
return d->imageUrl();
}
void Viewer::update(MimeTreeParser::UpdateMode updateMode)
{
Q_D(Viewer);
d->update(updateMode);
}
void Viewer::setMessagePart(KMime::Content *aMsgPart)
{
Q_D(Viewer);
d->setMessagePart(aMsgPart);
}
void Viewer::clear(MimeTreeParser::UpdateMode updateMode)
{
setMessage(KMime::Message::Ptr(), updateMode);
}
void Viewer::slotShowMessageSource()
{
Q_D(Viewer);
d->slotShowMessageSource();
}
void Viewer::readConfig()
{
Q_D(Viewer);
d->readConfig();
}
QAbstractItemModel *Viewer::messageTreeModel() const
{
#ifndef QT_NO_TREEVIEW
return d_func()->mMimePartTree->mimePartModel();
#else
return nullptr;
#endif
}
Akonadi::ItemFetchJob *Viewer::createFetchJob(const Akonadi::Item &item)
{
Q_D(Viewer);
Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, d->mSession);
job->fetchScope().fetchAllAttributes();
job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
job->fetchScope().fetchFullPayload(true);
job->fetchScope().setFetchRelations(true); // needed to know if we have notes or not
job->fetchScope().fetchAttribute<MailTransport::ErrorAttribute>();
return job;
}
void Viewer::addMessageLoadedHandler(AbstractMessageLoadedHandler *handler)
{
Q_D(Viewer);
if (!handler) {
return;
}
handler->setSession(d->mSession);
d->mMessageLoadedHandlers.insert(handler);
}
void Viewer::removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler)
{
Q_D(Viewer);
d->mMessageLoadedHandlers.remove(handler);
}
void Viewer::deleteMessage()
{
Q_D(Viewer);
Q_EMIT deleteMessage(d->messageItem());
}
void Viewer::selectAll()
{
Q_D(Viewer);
d->selectAll();
}
void Viewer::copySelectionToClipboard()
{
Q_D(Viewer);
d->slotCopySelectedText();
}
void Viewer::setZoomFactor(qreal zoomFactor)
{
Q_D(Viewer);
d->mZoomActionMenu->setZoomFactor(zoomFactor);
}
void Viewer::slotZoomReset()
{
Q_D(Viewer);
d->mZoomActionMenu->slotZoomReset();
}
void Viewer::slotZoomIn()
{
Q_D(Viewer);
d->mZoomActionMenu->slotZoomIn();
}
void Viewer::slotZoomOut()
{
Q_D(Viewer);
d->mZoomActionMenu->slotZoomOut();
}
QAction *Viewer::findInMessageAction() const
{
Q_D(const Viewer);
return d->mFindInMessageAction;
}
void Viewer::slotChangeDisplayMail(Viewer::DisplayFormatMessage mode, bool loadExternal)
{
- setHtmlLoadExtOverride(loadExternal);
- setDisplayFormatMessageOverwrite(mode);
- update(MimeTreeParser::Force);
+ if ((htmlLoadExtOverride() != loadExternal) || (displayFormatMessageOverwrite() != mode)) {
+ setHtmlLoadExtOverride(loadExternal);
+ setDisplayFormatMessageOverwrite(mode);
+ update(MimeTreeParser::Force);
+ }
}
QAction *Viewer::saveMessageDisplayFormatAction() const
{
Q_D(const Viewer);
return d->mSaveMessageDisplayFormat;
}
QAction *Viewer::resetMessageDisplayFormatAction() const
{
Q_D(const Viewer);
return d->mResetMessageDisplayFormat;
}
KToggleAction *Viewer::disableEmoticonAction() const
{
Q_D(const Viewer);
return d->mDisableEmoticonAction;
}
void Viewer::saveMainFrameScreenshotInFile(const QString &filename)
{
Q_D(Viewer);
- return d->saveMainFrameScreenshotInFile(filename);
+ d->saveMainFrameScreenshotInFile(filename);
}
KActionMenu *Viewer::shareServiceUrlMenu() const
{
Q_D(const Viewer);
return d->mShareServiceUrlMenu;
}
HeaderStylePlugin *Viewer::headerStylePlugin() const
{
Q_D(const Viewer);
return d->mHeaderStylePlugin;
}
void Viewer::setPluginName(const QString &pluginName)
{
Q_D(Viewer);
- return d->setPluginName(pluginName);
+ d->setPluginName(pluginName);
}
void Viewer::showOpenAttachmentFolderWidget(const QList<QUrl> &urls)
{
Q_D(Viewer);
d->showOpenAttachmentFolderWidget(urls);
}
QList<QAction *> Viewer::viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features)
{
Q_D(Viewer);
return d->viewerPluginActionList(features);
}
QList<QAction *> Viewer::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result)
const
{
Q_D(const Viewer);
return d->interceptorUrlActions(result);
}
void Viewer::runJavaScript(const QString &code)
{
Q_D(Viewer);
d->mViewer->page()->runJavaScript(code, WebEngineViewer::WebEngineManageScript::scriptWordId());
}
void Viewer::setPrintElementBackground(bool printElementBackground)
{
Q_D(Viewer);
d->mViewer->setPrintElementBackground(printElementBackground);
}
bool Viewer::showSignatureDetails() const
{
Q_D(const Viewer);
return d->showSignatureDetails();
}
void Viewer::setShowSignatureDetails(bool showDetails)
{
Q_D(Viewer);
d->setShowSignatureDetails(showDetails);
}
bool Viewer::showEncryptionDetails() const
{
Q_D(const Viewer);
return d->showEncryptionDetails();
}
+void Viewer::hasMultiMessages(bool messages)
+{
+ Q_D(Viewer);
+ d->hasMultiMessages(messages);
+}
+
+void Viewer::updateShowMultiMessagesButton(bool enablePreviousButton, bool enableNextButton)
+{
+ Q_D(Viewer);
+ d->updateShowMultiMessagesButton(enablePreviousButton, enableNextButton);
+}
+
void Viewer::setShowEncryptionDetails(bool showDetails)
{
Q_D(Viewer);
d->setShowEncryptionDetails(showDetails);
}
qreal Viewer::webViewZoomFactor() const
{
Q_D(const Viewer);
return d->webViewZoomFactor();
}
void Viewer::setWebViewZoomFactor(qreal factor)
{
Q_D(Viewer);
d->setWebViewZoomFactor(factor);
}
-
}
diff --git a/messageviewer/src/viewer/viewer.h b/messageviewer/src/viewer/viewer.h
index ad707c5b..a6c37c78 100644
--- a/messageviewer/src/viewer/viewer.h
+++ b/messageviewer/src/viewer/viewer.h
@@ -1,422 +1,427 @@
/*
This file is part of KMail, the KDE mail client.
Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#ifndef MESSAGEVIEWER_H
#define MESSAGEVIEWER_H
#include "messageviewer_export.h"
-#include "config-messageviewer.h"
#include "messageviewer/viewerplugininterface.h"
#include <MimeTreeParser/Enums>
#include <KMime/Message>
#include <QWidget>
namespace Akonadi {
class Item;
class ItemFetchJob;
}
class KActionCollection;
class QAction;
class KToggleAction;
class KActionMenu;
class QAbstractItemModel;
class QCloseEvent;
class QEvent;
class QResizeEvent;
namespace WebEngineViewer {
class WebHitTestResult;
}
namespace MessageViewer {
class WebHitTestResult;
class AttachmentStrategy;
class HeaderStylePlugin;
class CSSHelper;
class ViewerPrivate;
/**
* An interface to plug in a handler that is called when
* an message item has been loaded into the view.
*/
class Viewer;
class MESSAGEVIEWER_EXPORT AbstractMessageLoadedHandler
{
public:
AbstractMessageLoadedHandler();
virtual ~AbstractMessageLoadedHandler();
/**
* This method is called whenever a message item has been loaded
* into the view.
*
* @param item The message item that has been loaded.
*/
virtual void setItem(const Akonadi::Item &item) = 0;
protected:
Akonadi::Session *session() const;
private:
void setSession(Akonadi::Session *session);
friend class Viewer;
class Private;
Private *const d;
};
//TODO(Andras) once only those methods are public that really need to be public, probably export the whole class instead of just some methods
/**
* This is the main widget for the viewer.
* See the documentation of ViewerPrivate for implementation details.
* See Mainpage.dox for an overview of the classes in the messageviewer library.
*/
class MESSAGEVIEWER_EXPORT Viewer : public QWidget
{
Q_OBJECT
Q_DECLARE_PRIVATE(Viewer)
public:
/**
* Create a mail viewer widget
* @param parent parent widget
- * @param mainWindow the application's main window
+ * @param widget the application's main widget
* @param actionCollection the action collection where the widget's actions will belong to
- * @param f window flags
*/
explicit Viewer(QWidget *parent, QWidget *widget = nullptr, KActionCollection *actionCollection = nullptr);
~Viewer() override;
/**
* Returns the current message displayed in the viewer.
*/
Q_REQUIRED_RESULT KMime::Message::Ptr message() const;
/**
* Returns the current message item displayed in the viewer.
*/
Q_REQUIRED_RESULT Akonadi::Item messageItem() const;
enum DisplayFormatMessage {
UseGlobalSetting = 0,
Text = 1,
Html = 2,
Unknown = 3,
ICal = 4
};
enum AttachmentAction {
Open = 1,
OpenWith,
View,
Save,
Properties,
Delete,
Copy,
ScrollTo,
ReplyMessageToAuthor,
ReplyMessageToAll
};
enum ResourceOnlineMode {
AllResources = 0,
SelectedResource = 1
};
/**
* Set the message that shall be shown.
- * @param msg - the message to be shown. If 0, an empty page is displayed.
+ * @param message - the message to be shown. If 0, an empty page is displayed.
* @param updateMode - update the display immediately or not. See UpdateMode.
*/
void setMessage(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
/**
* Set the Akonadi item that will be displayed.
* @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data),
* an empty page is shown.
* @param updateMode - update the display immediately or not. See UpdateMode.
*/
void setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
/**
* The path to the message in terms of Akonadi collection hierarchy.
*/
Q_REQUIRED_RESULT QString messagePath() const;
/**
* Set the path to the message in terms of Akonadi collection hierarchy.
*/
void setMessagePath(const QString &path);
/**
* Instead of settings a message to be shown sets a message part
* to be shown
*/
void setMessagePart(KMime::Content *aMsgPart);
/**
* Convenience method to clear the reader and discard the current message. Sets the internal message pointer
* returned by message() to 0.
* @param updateMode - update the display immediately or not. See UpdateMode.
*/
void clear(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
void update(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
/**
* Sets a message as the current one and print it immediately.
- * @param message the message to display and print
+ * @param msg the message to display and print
*/
void printMessage(const Akonadi::Item &msg);
void printPreviewMessage(const Akonadi::Item &message);
/** Print the currently displayed message */
void print();
void printPreview();
/** Get the html override setting */
Q_REQUIRED_RESULT Viewer::DisplayFormatMessage displayFormatMessageOverwrite() const;
/** Override default html mail setting */
void setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format);
/** Get the load external references override setting */
bool htmlLoadExtOverride() const;
/** Default behavior for loading external references.
* Use this for specifying the external reference loading behavior as
* specified in the user settings.
* @see setHtmlLoadExtOverride
*/
void setHtmlLoadExtDefault(bool loadExtDefault);
/** Override default load external references setting
* @warning This must only be called when the user has explicitly
* been asked to retrieve external references!
* @see setHtmlLoadExtDefault
*/
void setHtmlLoadExtOverride(bool loadExtOverride);
/** Is html mail to be supported? Takes into account override */
Q_REQUIRED_RESULT bool htmlMail() const;
/** Is loading ext. references to be supported? Takes into account override */
Q_REQUIRED_RESULT bool htmlLoadExternal() const;
/**
* Display a generic HTML splash page instead of a message.
* @param templateName - the template to be loaded
* @param data - data for the template
+ * @param domain the domain.
*/
void displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain = QByteArray());
/** Enable the displaying of messages again after an splash (or other) page was displayed */
void enableMessageDisplay();
/** Returns true if the message view is scrolled to the bottom. */
void atBottom();
Q_REQUIRED_RESULT bool isFixedFont() const;
void setUseFixedFont(bool useFixedFont);
Q_REQUIRED_RESULT QWidget *mainWindow();
/** Enforce message decryption. */
void setDecryptMessageOverwrite(bool overwrite = true);
/**
* Initiates a delete, by sending a signal to delete the message item */
void deleteMessage();
Q_REQUIRED_RESULT const AttachmentStrategy *attachmentStrategy() const;
void setAttachmentStrategy(const AttachmentStrategy *strategy);
Q_REQUIRED_RESULT QString overrideEncoding() const;
void setOverrideEncoding(const QString &encoding);
Q_REQUIRED_RESULT CSSHelper *cssHelper() const;
void setPrinting(bool enable);
void selectAll();
void copySelectionToClipboard();
void setZoomFactor(qreal zoomFactor);
Q_REQUIRED_RESULT KToggleAction *toggleFixFontAction() const;
Q_REQUIRED_RESULT KToggleAction *toggleMimePartTreeAction() const;
Q_REQUIRED_RESULT QAction *selectAllAction() const;
Q_REQUIRED_RESULT QAction *copyURLAction() const;
Q_REQUIRED_RESULT QAction *copyAction() const;
Q_REQUIRED_RESULT QAction *urlOpenAction() const;
Q_REQUIRED_RESULT QAction *speakTextAction() const;
Q_REQUIRED_RESULT QAction *copyImageLocation() const;
Q_REQUIRED_RESULT QAction *viewSourceAction() const;
Q_REQUIRED_RESULT QAction *findInMessageAction() const;
Q_REQUIRED_RESULT QAction *saveAsAction() const;
Q_REQUIRED_RESULT QAction *saveMessageDisplayFormatAction() const;
Q_REQUIRED_RESULT QAction *resetMessageDisplayFormatAction() const;
Q_REQUIRED_RESULT KToggleAction *disableEmoticonAction() const;
Q_REQUIRED_RESULT KActionMenu *shareServiceUrlMenu() const;
Q_REQUIRED_RESULT HeaderStylePlugin *headerStylePlugin() const;
void setPluginName(const QString &pluginName);
void writeConfig(bool withSync = true);
Q_REQUIRED_RESULT QUrl urlClicked() const;
Q_REQUIRED_RESULT QUrl imageUrlClicked() const;
void readConfig();
/** A QAIM tree model of the message structure. */
QAbstractItemModel *messageTreeModel() const;
/**
* Create an item fetch job that is suitable for using to fetch the message item that will
* be displayed on this viewer.
* It will set the correct fetch scope.
* You still need to connect to the job's result signal.
*/
Akonadi::ItemFetchJob *createFetchJob(const Akonadi::Item &item);
/**
* Adds a @p handler for actions that will be executed when the message
* has been loaded into the view.
*/
void addMessageLoadedHandler(AbstractMessageLoadedHandler *handler);
/**
* Removes the @p handler for actions that will be executed when the message
* has been loaded into the view.
*/
void removeMessageLoadedHandler(AbstractMessageLoadedHandler *handler);
QString selectedText() const;
void setExternalWindow(bool b);
void saveMainFrameScreenshotInFile(const QString &filename);
bool mimePartTreeIsEmpty() const;
void showOpenAttachmentFolderWidget(const QList<QUrl> &urls);
QList<QAction *> viewerPluginActionList(
MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features);
QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
void runJavaScript(const QString &code);
void setPrintElementBackground(bool printElementBackground);
bool printingMode() const;
Q_REQUIRED_RESULT bool showSignatureDetails() const;
void setShowSignatureDetails(bool showDetails);
Q_REQUIRED_RESULT qreal webViewZoomFactor() const;
void setWebViewZoomFactor(qreal factor);
Q_REQUIRED_RESULT bool showEncryptionDetails() const;
void setShowEncryptionDetails(bool showDetails);
+ void hasMultiMessages(bool messages);
+ void updateShowMultiMessagesButton(bool enablePreviousButton, bool enableNextButton);
+
Q_SIGNALS:
void moveMessageToTrash();
void pageIsScrolledToBottom(bool);
/**
* Emitted when a status bar message is shown. Note that the status bar message is also set to
* KPIM::BroadcastStatus in addition.
*/
void showStatusBarMessage(const QString &message);
/** The user presses the right mouse button. 'url' may be 0. */
void popupMenu(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &mousePos);
void displayPopupMenu(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &mousePos);
/**
* The message viewer handles some types of urls itself, most notably http(s)
* and ftp(s). When it can't handle the url it will Q_EMIT this signal.
*/
void urlClicked(const Akonadi::Item &, const QUrl &);
void requestConfigSync();
/// Emitted when the content should be shown in a separate window
void showReader(KMime::Content *aMsgPart, bool aHTML, const QString &encoding);
/// Emitted when the message should be shown in a separate window
void showMessage(const KMime::Message::Ptr &message, const QString &encoding);
void replyMessageTo(const KMime::Message::Ptr &message, bool replyToAll);
void deleteMessage(const Akonadi::Item &);
/// Emitted when the item, previously set with setMessageItem, has been removed.
void itemRemoved();
void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode);
void printingFinished();
void zoomChanged(qreal zoomFactor);
+ void showNextMessage();
+ void showPreviousMessage();
+
private:
void initialize();
public Q_SLOTS:
/**
* HTML Widget scrollbar and layout handling.
*
* Scrolling always happens in the direction of the slot that is called. I.e.
* the methods take the absolute value of
*/
void slotScrollUp();
void slotScrollDown();
void slotScrollPrior();
void slotScrollNext();
void slotJumpDown();
void slotFind();
void slotSaveMessage();
void slotAttachmentSaveAs();
void slotAttachmentSaveAll();
void slotShowMessageSource();
void slotZoomIn();
void slotZoomOut();
void slotZoomReset();
void slotChangeDisplayMail(Viewer::DisplayFormatMessage, bool);
protected:
/** Some necessary event handling. */
void closeEvent(QCloseEvent *) override;
void resizeEvent(QResizeEvent *) override;
/** Watch for palette changes */
bool event(QEvent *e) override;
void changeEvent(QEvent *event) override;
ViewerPrivate *const d_ptr;
};
}
#endif
diff --git a/messageviewer/src/viewer/viewer_p.cpp b/messageviewer/src/viewer/viewer_p.cpp
index 81792689..dd9a4928 100644
--- a/messageviewer/src/viewer/viewer_p.cpp
+++ b/messageviewer/src/viewer/viewer_p.cpp
@@ -1,3124 +1,3142 @@
/*
Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
Copyright (c) 2010 Torgny Nyblom <nyblom@kde.org>
- Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2011-2019 Laurent Montel <montel@kde.org>
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.
*/
#include "viewer_p.h"
#include "viewer.h"
#include "messageviewer_debug.h"
#include "utils/mimetype.h"
#include "viewer/objecttreeemptysource.h"
#include "viewer/objecttreeviewersource.h"
#include "messagedisplayformatattribute.h"
#include "utils/iconnamecache.h"
#include "scamdetection/scamdetectionwarningwidget.h"
#include "scamdetection/scamattribute.h"
#include "viewer/mimeparttree/mimeparttreeview.h"
#include "widgets/openattachmentfolderwidget.h"
#include "messageviewer/headerstyle.h"
#include "messageviewer/headerstrategy.h"
#include "kpimtextedit/slidecontainer.h"
#include <Gravatar/GravatarCache>
#include <gravatar/gravatarsettings.h>
#include "job/modifymessagedisplayformatjob.h"
#include "config-messageviewer.h"
#include "viewerplugins/viewerplugintoolmanager.h"
#include <KContacts/VCardConverter>
#include "htmlwriter/webengineembedpart.h"
#include <PimCommon/NetworkUtil>
#include <unistd.h> // link()
//KDE includes
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QAction>
#include <KActionCollection>
#include <KActionMenu>
#include <KCharsets>
#include <QPrintPreviewDialog>
#include <QMenu>
#include <KMessageBox>
#include <KMimeTypeChooser>
#include <KMimeTypeTrader>
#include <KRun>
#include <KSelectAction>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QTemporaryDir>
#include <KToggleAction>
#include <QIcon>
#include <kfileitemactions.h>
#include <KLocalizedString>
#include <QMimeData>
#include <KEmailAddress>
#include <AkonadiCore/ItemModifyJob>
#include <AkonadiCore/ItemCreateJob>
#include <messageflags.h>
#include <mailtransportakonadi/errorattribute.h>
//Qt includes
#include <QClipboard>
#include <QDesktopWidget>
#include <QFileInfo>
#include <QItemSelectionModel>
#include <QSplitter>
#include <QTreeView>
#include <QPrinter>
#include <QPrintDialog>
#include <QMimeDatabase>
#include <QWheelEvent>
#include <QPointer>
#include <WebEngineViewer/WebEngineExportHtmlPageJob>
//libkdepim
#include "Libkdepim/BroadcastStatus"
#include <MessageCore/AttachmentPropertiesDialog>
#include <AkonadiCore/collection.h>
#include <AkonadiCore/itemfetchjob.h>
#include <AkonadiCore/itemfetchscope.h>
#include <Akonadi/KMime/MessageStatus>
#include <Akonadi/KMime/SpecialMailCollections>
#include <AkonadiCore/attributefactory.h>
#include <Akonadi/KMime/MessageParts>
//own includes
#include "widgets/attachmentdialog.h"
#include "csshelper.h"
#include "settings/messageviewersettings.h"
#include "widgets/htmlstatusbar.h"
#include "viewer/attachmentstrategy.h"
#include "viewer/mimeparttree/mimetreemodel.h"
#include "viewer/urlhandlermanager.h"
#include "messageviewer/messageviewerutil.h"
#include "utils/messageviewerutil_p.h"
#include "widgets/vcardviewer.h"
+#include "widgets/shownextmessagewidget.h"
#include <WebEngineViewer/FindBarWebEngineView>
#include "viewer/webengine/mailwebengineview.h"
#include "htmlwriter/webengineparthtmlwriter.h"
#include <widgets/mailsourcewebengineviewer.h>
#include <WebEngineViewer/WebHitTestResult>
#include "header/headerstylemenumanager.h"
#include "widgets/submittedformwarningwidget.h"
#include <WebEngineViewer/LocalDataBaseManager>
#include <MimeTreeParser/BodyPart>
#include "interfaces/htmlwriter.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/ObjectTreeParser>
#include <MessageCore/StringUtil>
#include <MessageCore/NodeHelper>
#include <MessageCore/MessageCoreSettings>
#include <AkonadiCore/agentinstance.h>
#include <AkonadiCore/agentmanager.h>
#include <AkonadiCore/CollectionFetchJob>
#include <AkonadiCore/collectionfetchscope.h>
#include <boost/bind.hpp>
#include <KJobWidgets/KJobWidgets>
#include <QApplication>
#include <QStandardPaths>
#include <QWebEngineSettings>
#include <header/headerstyleplugin.h>
#include <viewerplugins/viewerplugininterface.h>
#include <WebEngineViewer/ZoomActionMenu>
#include <kpimtextedit/texttospeechwidget.h>
#include <widgets/mailtrackingwarningwidget.h>
#include <grantleetheme/grantleethememanager.h>
#include <grantleetheme/grantleetheme.h>
using namespace boost;
using namespace MailTransport;
using namespace MessageViewer;
using namespace MessageCore;
static QAtomicInt _k_attributeInitialized;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFun)(Arg);
void operator()(Arg result)
{
(receiver->*memberFun)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
return wrapper;
}
ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection)
: QObject(aParent)
, mNodeHelper(new MimeTreeParser::NodeHelper)
, mOldGlobalOverrideEncoding(QStringLiteral("---"))
, mMsgDisplay(true)
- , // init with dummy value
- mCSSHelper(nullptr)
+ , mCSSHelper(nullptr)
, mMainWindow(mainWindow)
, mActionCollection(actionCollection)
, mCanStartDrag(false)
, mRecursionCountForDisplayMessage(0)
, q(aParent)
, mSession(new Akonadi::Session("MessageViewer-"
+ QByteArray::number(reinterpret_cast<quintptr>(this)), this))
, mPreviouslyViewedItem(-1)
{
mMimePartTree = nullptr;
if (!mainWindow) {
mMainWindow = aParent;
}
if (_k_attributeInitialized.testAndSetAcquire(0, 1)) {
Akonadi::AttributeFactory::registerAttribute<MessageViewer::MessageDisplayFormatAttribute>();
Akonadi::AttributeFactory::registerAttribute<MessageViewer::ScamAttribute>();
}
mPhishingDatabase = new WebEngineViewer::LocalDataBaseManager(this);
mPhishingDatabase->initialize();
connect(mPhishingDatabase, &WebEngineViewer::LocalDataBaseManager::checkUrlFinished,
this, &ViewerPrivate::slotCheckedUrlFinished);
mShareServiceManager = new PimCommon::ShareServiceUrlManager(this);
mDisplayFormatMessageOverwrite = MessageViewer::Viewer::UseGlobalSetting;
mHtmlLoadExtOverride = false;
mHtmlLoadExternalDefaultSetting = false;
mHtmlMailGlobalSetting = false;
mUpdateReaderWinTimer.setObjectName(QStringLiteral("mUpdateReaderWinTimer"));
mResizeTimer.setObjectName(QStringLiteral("mResizeTimer"));
mPrinting = false;
createWidgets();
createActions();
initHtmlWidget();
readConfig();
mLevelQuote = MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1;
mResizeTimer.setSingleShot(true);
connect(&mResizeTimer, &QTimer::timeout,
this, &ViewerPrivate::slotDelayedResize);
mUpdateReaderWinTimer.setSingleShot(true);
connect(&mUpdateReaderWinTimer, &QTimer::timeout,
this, &ViewerPrivate::updateReaderWin);
connect(mNodeHelper, &MimeTreeParser::NodeHelper::update,
this, &ViewerPrivate::update);
connect(mColorBar, &HtmlStatusBar::clicked,
this, &ViewerPrivate::slotToggleHtmlMode);
// FIXME: Don't use the full payload here when attachment loading on demand is used, just
// like in KMMainWidget::slotMessageActivated().
mMonitor.setObjectName(QStringLiteral("MessageViewerMonitor"));
mMonitor.setSession(mSession);
Akonadi::ItemFetchScope fs;
fs.fetchFullPayload();
fs.fetchAttribute<MailTransport::ErrorAttribute>();
fs.fetchAttribute<MessageViewer::MessageDisplayFormatAttribute>();
fs.fetchAttribute<MessageViewer::ScamAttribute>();
mMonitor.setItemFetchScope(fs);
connect(&mMonitor, &Akonadi::Monitor::itemChanged,
this, &ViewerPrivate::slotItemChanged);
connect(&mMonitor, &Akonadi::Monitor::itemRemoved,
this, &ViewerPrivate::slotClear);
connect(&mMonitor, &Akonadi::Monitor::itemMoved,
this, &ViewerPrivate::slotItemMoved);
}
ViewerPrivate::~ViewerPrivate()
{
MessageViewer::MessageViewerSettings::self()->save();
delete mHtmlWriter;
mHtmlWriter = nullptr;
delete mViewer;
mViewer = nullptr;
delete mCSSHelper;
mNodeHelper->forceCleanTempFiles();
qDeleteAll(mListMailSourceViewer);
delete mNodeHelper;
}
//-----------------------------------------------------------------------------
KMime::Content *ViewerPrivate::nodeFromUrl(const QUrl &url) const
{
return mNodeHelper->fromHREF(mMessage, url);
}
void ViewerPrivate::openAttachment(KMime::Content *node, const QUrl &url)
{
if (!node) {
return;
}
if (node->contentType(false)) {
if (node->contentType()->mimeType() == "text/x-moz-deleted") {
return;
}
if (node->contentType()->mimeType() == "message/external-body") {
if (node->contentType()->hasParameter(QStringLiteral("url"))) {
KRun::RunFlags flags;
flags |= KRun::RunExecutables;
const QString url = node->contentType()->parameter(QStringLiteral("url"));
KRun::runUrl(QUrl(url), QStringLiteral("text/html"), q, flags);
return;
}
}
}
const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
// the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation
// in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree.
KMime::Message::Ptr m(new KMime::Message);
m->setContent(node->parent()->bodyAsMessage()->encodedContent());
m->parse();
atmViewMsg(m);
return;
}
// determine the MIME type of the attachment
// prefer the value of the Content-Type header
QMimeDatabase mimeDb;
auto mimetype
= mimeDb.mimeTypeForName(QString::fromLatin1(node->contentType()->mimeType().toLower()));
if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) {
showVCard(node);
return;
}
// special case treatment on mac and windows
QUrl atmUrl = url;
if (url.isEmpty()) {
atmUrl = mNodeHelper->tempFileUrlFromNode(node);
}
if (Util::handleUrlWithQDesktopServices(atmUrl)) {
return;
}
if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
mimetype = MimeTreeParser::Util::mimetype(
url.isLocalFile() ? url.toLocalFile() : url.fileName());
}
KService::Ptr offer
= KMimeTypeTrader::self()->preferredService(mimetype.name(), QStringLiteral("Application"));
const QString filenameText = MimeTreeParser::NodeHelper::fileName(node);
QPointer<AttachmentDialog> dialog = new AttachmentDialog(mMainWindow, filenameText,
offer,
QLatin1String(
"askSave_") + mimetype.name());
const int choice = dialog->exec();
delete dialog;
if (choice == AttachmentDialog::Save) {
QList<QUrl> urlList;
if (Util::saveContents(mMainWindow, KMime::Content::List() << node, urlList)) {
showOpenAttachmentFolderWidget(urlList);
}
} else if (choice == AttachmentDialog::Open) { // Open
if (offer) {
attachmentOpenWith(node, offer);
} else {
attachmentOpen(node);
}
} else if (choice == AttachmentDialog::OpenWith) {
attachmentOpenWith(node);
} else { // Cancel
qCDebug(MESSAGEVIEWER_LOG) << "Canceled opening attachment";
}
}
bool ViewerPrivate::deleteAttachment(KMime::Content *node, bool showWarning)
{
if (!node) {
return true;
}
KMime::Content *parent = node->parent();
if (!parent) {
return true;
}
QList<KMime::Content *> extraNodes = mNodeHelper->extraContents(mMessage.data());
if (extraNodes.contains(node->topLevel())) {
KMessageBox::error(mMainWindow,
i18n(
"Deleting an attachment from an encrypted or old-style mailman message is not supported."),
i18n("Delete Attachment"));
return true; //cancelled
}
if (showWarning && KMessageBox::warningContinueCancel(mMainWindow,
i18n(
"Deleting an attachment might invalidate any digital signature on this message."),
i18n("Delete Attachment"),
KStandardGuiItem::del(),
KStandardGuiItem::cancel(),
QStringLiteral(
"DeleteAttachmentSignatureWarning"))
!= KMessageBox::Continue) {
return false; //cancelled
}
//don't confuse the model
#ifndef QT_NO_TREEVIEW
mMimePartTree->clearModel();
#endif
QString filename;
QString name;
QByteArray mimetype;
if (node->contentDisposition(false)) {
filename = node->contentDisposition()->filename();
}
if (node->contentType(false)) {
name = node->contentType()->name();
mimetype = node->contentType()->mimeType();
}
// text/plain part:
KMime::Content *deletePart = new KMime::Content(parent);
deletePart->contentType()->setMimeType("text/x-moz-deleted");
deletePart->contentType()->setName(QStringLiteral("Deleted: %1").arg(name), "utf8");
deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
deletePart->contentDisposition()->setFilename(QStringLiteral("Deleted: %1").arg(name));
deletePart->contentType()->setCharset("utf-8");
deletePart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
QByteArray bodyMessage = QByteArrayLiteral(
"\nYou deleted an attachment from this message. The original MIME headers for the attachment were:");
bodyMessage += ("\nContent-Type: ") + mimetype;
bodyMessage += ("\nname=\"") + name.toUtf8() + "\"";
bodyMessage += ("\nfilename=\"") + filename.toUtf8() + "\"";
deletePart->setBody(bodyMessage);
parent->replaceContent(node, deletePart);
parent->assemble();
KMime::Message *modifiedMessage = mNodeHelper->messageWithExtraContent(mMessage.data());
#ifndef QT_NO_TREEVIEW
mMimePartTree->mimePartModel()->setRoot(modifiedMessage);
#endif
mMessageItem.setPayloadFromData(modifiedMessage->encodedContent());
Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob(mMessageItem, mSession);
job->disableRevisionCheck();
connect(job, &KJob::result, this, &ViewerPrivate::itemModifiedResult);
return true;
}
void ViewerPrivate::itemModifiedResult(KJob *job)
{
if (job->error()) {
qCDebug(MESSAGEVIEWER_LOG) << "Item update failed:" << job->errorString();
} else {
setMessageItem(mMessageItem, MimeTreeParser::Force);
}
}
void ViewerPrivate::scrollToAnchor(const QString &anchor)
{
mViewer->scrollToAnchor(anchor);
}
void ViewerPrivate::createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent)
{
const KService::List offers = KFileItemActions::associatedApplications(
QStringList() << contentTypeStr, QString());
if (!offers.isEmpty()) {
QMenu *menu = topMenu;
QActionGroup *actionGroup = new QActionGroup(menu);
if (fromCurrentContent) {
connect(actionGroup, &QActionGroup::triggered, this,
&ViewerPrivate::slotOpenWithActionCurrentContent);
} else {
connect(actionGroup, &QActionGroup::triggered, this,
&ViewerPrivate::slotOpenWithAction);
}
if (offers.count() > 1) { // submenu 'open with'
menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
menu->menuAction()->setObjectName(QStringLiteral("openWith_submenu")); // for the unittest
topMenu->addMenu(menu);
}
//qCDebug(MESSAGEVIEWER_LOG) << offers.count() << "offers" << topMenu << menu;
KService::List::ConstIterator it = offers.constBegin();
KService::List::ConstIterator end = offers.constEnd();
for (; it != end; ++it) {
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...");
}
QAction *openWithAct = new QAction(menu);
openWithAct->setText(openWithActionName);
if (fromCurrentContent) {
connect(openWithAct, &QAction::triggered, this,
&ViewerPrivate::slotOpenWithDialogCurrentContent);
} else {
connect(openWithAct, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog);
}
menu->addAction(openWithAct);
} else { // no app offers -> Open With...
QAction *act = new QAction(topMenu);
act->setText(i18nc("@title:menu", "&Open With..."));
if (fromCurrentContent) {
connect(act, &QAction::triggered, this,
&ViewerPrivate::slotOpenWithDialogCurrentContent);
} else {
connect(act, &QAction::triggered, this, &ViewerPrivate::slotOpenWithDialog);
}
topMenu->addAction(act);
}
}
void ViewerPrivate::slotOpenWithDialogCurrentContent()
{
if (!mCurrentContent) {
return;
}
attachmentOpenWith(mCurrentContent);
}
void ViewerPrivate::slotOpenWithDialog()
{
auto contents = selectedContents();
if (contents.count() == 1) {
attachmentOpenWith(contents.first());
}
}
void ViewerPrivate::slotOpenWithActionCurrentContent(QAction *act)
{
if (!mCurrentContent) {
return;
}
KService::Ptr app = act->data().value<KService::Ptr>();
attachmentOpenWith(mCurrentContent, app);
}
void ViewerPrivate::slotOpenWithAction(QAction *act)
{
KService::Ptr app = act->data().value<KService::Ptr>();
auto contents = selectedContents();
if (contents.count() == 1) {
attachmentOpenWith(contents.first(), app);
}
}
void ViewerPrivate::showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &globalPos)
{
Q_UNUSED(name);
prepareHandleAttachment(node);
bool deletedAttachment = false;
if (node->contentType(false)) {
deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted");
}
//Not necessary to show popup menu when attachment was removed
if (deletedAttachment) {
return;
}
QMenu menu;
const QString contentTypeStr = QLatin1String(node->contentType()->mimeType());
QAction *action
= menu.addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18nc("to open",
"Open"));
action->setEnabled(!deletedAttachment);
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::Open);
});
if (!deletedAttachment) {
createOpenWithMenu(&menu, contentTypeStr, true);
}
QMimeDatabase mimeDb;
auto mimetype = mimeDb.mimeTypeForName(contentTypeStr);
if (mimetype.isValid()) {
const QStringList parentMimeType = mimetype.parentMimeTypes();
if ((contentTypeStr == QLatin1String("text/plain"))
|| (contentTypeStr == QLatin1String("image/png"))
|| (contentTypeStr == QLatin1String("image/jpeg"))
|| parentMimeType.contains(QLatin1String("text/plain"))
|| parentMimeType.contains(QLatin1String("image/png"))
|| parentMimeType.contains(QLatin1String("image/jpeg"))
) {
action = menu.addAction(i18nc("to view something", "View"));
action->setEnabled(!deletedAttachment);
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::View);
});
}
}
action = menu.addAction(i18n("Scroll To"));
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::ScrollTo);
});
action = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n(
"Save As..."));
action->setEnabled(!deletedAttachment);
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::Save);
});
action = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"));
action->setEnabled(!deletedAttachment);
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::Copy);
});
const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage();
const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid()
&& (mMessageItem.parentCollection().rights()
!= Akonadi::Collection::ReadOnly)
&& !isEncapsulatedMessage;
action
= menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
i18n("Delete Attachment"));
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::Delete);
});
action->setEnabled(canChange && !deletedAttachment);
#if 0
menu->addSeparator();
action
= menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-sender")),
i18n("Reply To Author"));
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::ReplyMessageToAuthor);
});
menu->addSeparator();
action = menu->addAction(QIcon::fromTheme(QStringLiteral("mail-reply-all")), i18n(
"Reply To All"));
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::ReplyMessageToAll);
});
#endif
menu.addSeparator();
action = menu.addAction(i18n("Properties"));
connect(action, &QAction::triggered, this, [this]() {
slotHandleAttachment(Viewer::Properties);
});
menu.exec(globalPos);
}
void ViewerPrivate::prepareHandleAttachment(KMime::Content *node)
{
mCurrentContent = node;
}
QString ViewerPrivate::createAtmFileLink(const QString &atmFileName) const
{
QFileInfo atmFileInfo(atmFileName);
// tempfile name is /TMP/attachmentsRANDOM/atmFileInfo.fileName()"
const QString tmpPath = QDir::tempPath() + QLatin1String("/attachments");
QDir().mkpath(tmpPath);
QTemporaryDir *linkDir = new QTemporaryDir(tmpPath);
QString linkPath = linkDir->path() + QLatin1Char('/') + atmFileInfo.fileName();
QFile *linkFile = new QFile(linkPath);
linkFile->open(QIODevice::ReadWrite);
const QString linkName = linkFile->fileName();
delete linkFile;
delete linkDir;
if (::link(QFile::encodeName(atmFileName).constData(),
QFile::encodeName(linkName).constData()) == 0) {
return linkName; // success
}
return QString();
}
KService::Ptr ViewerPrivate::getServiceOffer(KMime::Content *content)
{
const QString fileName = mNodeHelper->writeNodeToTempFile(content);
const QString contentTypeStr = QLatin1String(content->contentType()->mimeType());
// determine the MIME type of the attachment
// prefer the value of the Content-Type header
QMimeDatabase mimeDb;
auto mimetype = mimeDb.mimeTypeForName(contentTypeStr);
if (mimetype.isValid() && mimetype.inherits(KContacts::Addressee::mimeType())) {
attachmentView(content);
return KService::Ptr(nullptr);
}
if (!mimetype.isValid() || mimetype.name() == QLatin1String("application/octet-stream")) {
/*TODO(Andris) port when on-demand loading is done && msgPart.isComplete() */
mimetype = MimeTreeParser::Util::mimetype(fileName);
}
return KMimeTypeTrader::self()->preferredService(mimetype.name(),
QStringLiteral("Application"));
}
KMime::Content::List ViewerPrivate::selectedContents()
{
return mMimePartTree->selectedContents();
}
void ViewerPrivate::attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer)
{
QString name = mNodeHelper->writeNodeToTempFile(node);
// Make sure that it will not deleted when we switch from message.
QTemporaryDir *tmpDir
= new QTemporaryDir(QDir::tempPath() + QLatin1String("/messageviewer_attachment_XXXXXX"));
if (tmpDir->isValid()) {
tmpDir->setAutoRemove(false);
const QString path = tmpDir->path();
delete tmpDir;
QFile f(name);
const QUrl tmpFileName = QUrl::fromLocalFile(name);
const QString newPath = path + QLatin1Char('/') + tmpFileName.fileName();
if (!f.copy(newPath)) {
qCDebug(MESSAGEVIEWER_LOG) << " File was not able to copy: filename: " << name
<< " to " << path;
} else {
name = newPath;
}
f.close();
} else {
delete tmpDir;
}
QList<QUrl> lst;
const QFileDevice::Permissions perms = QFile::permissions(name);
QFile::setPermissions(name, perms | QFileDevice::ReadUser | QFileDevice::WriteUser);
const QUrl url = QUrl::fromLocalFile(name);
lst.append(url);
if (offer) {
if ((!KRun::runService(*offer, lst, nullptr, true))) {
QFile::remove(url.toLocalFile());
}
} else {
if ((!KRun::displayOpenWithDialog(lst, mMainWindow, true))) {
QFile::remove(url.toLocalFile());
}
}
}
void ViewerPrivate::attachmentOpen(KMime::Content *node)
{
KService::Ptr offer = getServiceOffer(node);
if (!offer) {
qCDebug(MESSAGEVIEWER_LOG) << "got no offer";
return;
}
attachmentOpenWith(node, offer);
}
bool ViewerPrivate::showEmoticons() const
{
return mForceEmoticons;
}
HtmlWriter *ViewerPrivate::htmlWriter() const
{
return mHtmlWriter;
}
CSSHelper *ViewerPrivate::cssHelper() const
{
return mCSSHelper;
}
MimeTreeParser::NodeHelper *ViewerPrivate::nodeHelper() const
{
return mNodeHelper;
}
Viewer *ViewerPrivate::viewer() const
{
return q;
}
Akonadi::Item ViewerPrivate::messageItem() const
{
return mMessageItem;
}
KMime::Message::Ptr ViewerPrivate::message() const
{
return mMessage;
}
bool ViewerPrivate::decryptMessage() const
{
if (!MessageViewer::MessageViewerSettings::self()->alwaysDecrypt()) {
return mDecrytMessageOverwrite;
} else {
return true;
}
}
void ViewerPrivate::displaySplashPage(const QString &message)
{
displaySplashPage(QStringLiteral("status.html"), {
{ QStringLiteral("icon"), QStringLiteral("kmail") },
{ QStringLiteral("name"), i18n("KMail") },
{ QStringLiteral("subtitle"), i18n("The KDE Mail Client") },
{ QStringLiteral("message"), message }
});
}
void ViewerPrivate::displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain)
{
mMsgDisplay = false;
adjustLayout();
GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"),
QStringLiteral("splash.theme"),
nullptr,
QStringLiteral("messageviewer/about/"));
GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
if (theme.isValid()) {
mViewer->setHtml(theme.render(templateName, data, domain),
QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
} else {
qCDebug(MESSAGEVIEWER_LOG) << "Theme error: failed to find splash theme";
}
mViewer->show();
}
void ViewerPrivate::enableMessageDisplay()
{
if (mMsgDisplay) {
return;
}
mMsgDisplay = true;
adjustLayout();
}
void ViewerPrivate::displayMessage()
{
showHideMimeTree();
mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec());
if (mMessageItem.hasAttribute<MessageViewer::MessageDisplayFormatAttribute>()) {
const MessageViewer::MessageDisplayFormatAttribute *const attr
= mMessageItem.attribute<MessageViewer::MessageDisplayFormatAttribute>();
setHtmlLoadExtOverride(attr->remoteContent());
setDisplayFormatMessageOverwrite(attr->messageFormat());
}
htmlWriter()->begin();
- htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont));
+ htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont));
if (!mMainWindow) {
q->setWindowTitle(mMessage->subject()->asUnicodeString());
}
// Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker.
// It is updated right after parseMsg() instead.
mColorBar->setMode(MimeTreeParser::Util::Normal, HtmlStatusBar::NoUpdate);
if (mMessageItem.hasAttribute<ErrorAttribute>()) {
//TODO: Insert link to clear error so that message might be resent
const ErrorAttribute *const attr = mMessageItem.attribute<ErrorAttribute>();
Q_ASSERT(attr);
if (!mForegroundError.isValid()) {
const KColorScheme scheme = KColorScheme(QPalette::Active, KColorScheme::View);
mForegroundError = scheme.foreground(KColorScheme::NegativeText).color();
mBackgroundError = scheme.background(KColorScheme::NegativeBackground).color();
}
htmlWriter()->write(QStringLiteral(
"<div style=\"background:%1;color:%2;border:1px solid %3\">%4</div>").arg(
mBackgroundError.
name(),
mForegroundError
.
name(),
mForegroundError
.
name(), attr->message().toHtmlEscaped()));
htmlWriter()->write(QStringLiteral("<p></p>"));
}
parseContent(mMessage.data());
#ifndef QT_NO_TREEVIEW
mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(mMessage.data()));
#endif
mColorBar->update();
htmlWriter()->write(QStringLiteral("</body></html>"));
connect(mViewer, &MailWebEngineView::loadFinished, this,
&ViewerPrivate::executeCustomScriptsAfterLoading, Qt::UniqueConnection);
connect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this, &ViewerPrivate::slotMessageRendered,
Qt::UniqueConnection);
htmlWriter()->end();
}
void ViewerPrivate::parseContent(KMime::Content *content)
{
assert(content != nullptr);
mNodeHelper->removeTempFiles();
// Check if any part of this message is a v-card
// v-cards can be either text/x-vcard or text/directory, so we need to check
// both.
KMime::Content *vCardContent = findContentByType(content, "text/x-vcard");
if (!vCardContent) {
vCardContent = findContentByType(content, "text/directory");
}
bool hasVCard = false;
if (vCardContent) {
// ### FIXME: We should only do this if the vCard belongs to the sender,
// ### i.e. if the sender's email address is contained in the vCard.
const QByteArray vCard = vCardContent->decodedContent();
KContacts::VCardConverter t;
if (!t.parseVCards(vCard).isEmpty()) {
hasVCard = true;
mNodeHelper->writeNodeToTempFile(vCardContent);
}
}
KMime::Message *message = dynamic_cast<KMime::Message *>(content);
if (message) {
htmlWriter()->write(writeMessageHeader(message, hasVCard ? vCardContent : nullptr, true));
}
// Pass control to the OTP now, which does the real work
mNodeHelper->setNodeUnprocessed(mMessage.data(), true);
MailViewerSource otpSource(this);
MimeTreeParser::ObjectTreeParser otp(&otpSource, mNodeHelper);
//TODO: needs to end up in renderer: mMessage.data() != content /* show only single node */);
otp.setAllowAsync(!mPrinting);
otp.parseObjectTree(content, mMessage.data() != content /* parse/show only single node */);
// TODO: Setting the signature state to nodehelper is not enough, it should actually
// be added to the store, so that the message list correctly displays the signature state
// of messages that were parsed at least once
// store encrypted/signed status information in the KMMessage
// - this can only be done *after* calling parseObjectTree()
MimeTreeParser::KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState(
content);
MimeTreeParser::KMMsgSignatureState signatureState
= mNodeHelper->overallSignatureState(content);
mNodeHelper->setEncryptionState(content, encryptionState);
// Don't reset the signature state to "not signed" (e.g. if one canceled the
// decryption of a signed messages which has already been decrypted before).
if (signatureState != MimeTreeParser::KMMsgNotSigned
|| mNodeHelper->signatureState(content) == MimeTreeParser::KMMsgSignatureStateUnknown) {
mNodeHelper->setSignatureState(content, signatureState);
}
showHideMimeTree();
}
QString ViewerPrivate::writeMessageHeader(KMime::Message *aMsg, KMime::Content *vCardNode, bool topLevel)
{
if (!headerStylePlugin()) {
qCCritical(MESSAGEVIEWER_LOG) << "trying to writeMessageHeader() without a header style set!";
return {};
}
QString href;
if (vCardNode) {
href = mNodeHelper->asHREF(vCardNode, QStringLiteral("body"));
}
headerStylePlugin()->headerStyle()->setHeaderStrategy(headerStylePlugin()->headerStrategy());
headerStylePlugin()->headerStyle()->setVCardName(href);
headerStylePlugin()->headerStyle()->setPrinting(mPrinting);
headerStylePlugin()->headerStyle()->setTopLevel(topLevel);
headerStylePlugin()->headerStyle()->setAllowAsync(true);
headerStylePlugin()->headerStyle()->setSourceObject(this);
headerStylePlugin()->headerStyle()->setNodeHelper(mNodeHelper);
headerStylePlugin()->headerStyle()->setMessagePath(mMessagePath);
headerStylePlugin()->headerStyle()->setAttachmentHtml(attachmentHtml());
if (mMessageItem.isValid()) {
Akonadi::MessageStatus status;
status.setStatusFromFlags(mMessageItem.flags());
headerStylePlugin()->headerStyle()->setMessageStatus(status);
headerStylePlugin()->headerStyle()->setCollectionName(
mMessageItem.parentCollection().displayName());
} else {
headerStylePlugin()->headerStyle()->setCollectionName(QString());
headerStylePlugin()->headerStyle()->setReadOnlyMessage(true);
}
return headerStylePlugin()->headerStyle()->format(aMsg);
}
void ViewerPrivate::showVCard(KMime::Content *msgPart)
{
const QByteArray vCard = msgPart->decodedContent();
VCardViewer *vcv = new VCardViewer(mMainWindow, vCard);
vcv->setAttribute(Qt::WA_DeleteOnClose);
vcv->show();
}
void ViewerPrivate::initHtmlWidget()
{
if (!htmlWriter()) {
mPartHtmlWriter = new WebEnginePartHtmlWriter(mViewer, nullptr);
mHtmlWriter = mPartHtmlWriter;
}
connect(mViewer->page(), &QWebEnginePage::linkHovered,
this, &ViewerPrivate::slotUrlOn);
connect(mViewer, &MailWebEngineView::openUrl,
this, &ViewerPrivate::slotUrlOpen, Qt::QueuedConnection);
connect(mViewer, &MailWebEngineView::popupMenu,
this, &ViewerPrivate::slotUrlPopup);
connect(mViewer, &MailWebEngineView::wheelZoomChanged,
this, &ViewerPrivate::slotWheelZoomChanged);
connect(mViewer, &MailWebEngineView::messageMayBeAScam, this,
&ViewerPrivate::slotMessageMayBeAScam);
connect(mViewer, &MailWebEngineView::formSubmittedForbidden, this,
&ViewerPrivate::slotFormSubmittedForbidden);
connect(mViewer, &MailWebEngineView::mailTrackingFound, this,
&ViewerPrivate::slotMailTrackingFound);
connect(mScamDetectionWarning, &ScamDetectionWarningWidget::showDetails, mViewer,
&MailWebEngineView::slotShowDetails);
connect(mScamDetectionWarning, &ScamDetectionWarningWidget::moveMessageToTrash, this,
&ViewerPrivate::moveMessageToTrash);
connect(mScamDetectionWarning, &ScamDetectionWarningWidget::messageIsNotAScam, this,
&ViewerPrivate::slotMessageIsNotAScam);
connect(mScamDetectionWarning, &ScamDetectionWarningWidget::addToWhiteList, this,
&ViewerPrivate::slotAddToWhiteList);
connect(mViewer, &MailWebEngineView::pageIsScrolledToBottom, this,
&ViewerPrivate::pageIsScrolledToBottom);
}
void ViewerPrivate::applyZoomValue(qreal factor, bool saveConfig)
{
if (mZoomActionMenu) {
if (factor >= 10 && factor <= 300) {
if (mZoomActionMenu->zoomFactor() != factor) {
mZoomActionMenu->setZoomFactor(factor);
mZoomActionMenu->setWebViewerZoomFactor(factor / 100.0);
if (saveConfig) {
MessageViewer::MessageViewerSettings::self()->setZoomFactor(factor);
}
}
}
}
}
void ViewerPrivate::setWebViewZoomFactor(qreal factor)
{
applyZoomValue(factor, false);
}
qreal ViewerPrivate::webViewZoomFactor() const
{
qreal zoomFactor = -1;
if (mZoomActionMenu) {
zoomFactor = mZoomActionMenu->zoomFactor();
}
return zoomFactor;
}
void ViewerPrivate::slotWheelZoomChanged(int numSteps)
{
const qreal factor = mZoomActionMenu->zoomFactor() + numSteps * 10;
applyZoomValue(factor);
}
void ViewerPrivate::readConfig()
{
- delete mCSSHelper;
- mCSSHelper = new CSSHelper(mViewer);
+ recreateCssHelper();
mForceEmoticons = MessageViewer::MessageViewerSettings::self()->showEmoticons();
if (mDisableEmoticonAction) {
mDisableEmoticonAction->setChecked(!mForceEmoticons);
}
if (headerStylePlugin()) {
headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons);
}
mUseFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont();
if (mToggleFixFontAction) {
mToggleFixFontAction->setChecked(mUseFixedFont);
}
mHtmlMailGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail();
readGravatarConfig();
if (mHeaderStyleMenuManager) {
mHeaderStyleMenuManager->readConfig();
}
setAttachmentStrategy(AttachmentStrategy::create(MessageViewer::
MessageViewerSettings::self()->
attachmentStrategy()));
KToggleAction *raction = actionForAttachmentStrategy(attachmentStrategy());
if (raction) {
raction->setChecked(true);
}
adjustLayout();
readGlobalOverrideCodec();
mViewer->readConfig();
mViewer->settings()->setFontSize(QWebEngineSettings::MinimumFontSize,
MessageViewer::MessageViewerSettings::self()->minimumFontSize());
mViewer->settings()->setFontSize(QWebEngineSettings::MinimumLogicalFontSize,
MessageViewer::MessageViewerSettings::self()->minimumFontSize());
if (mMessage) {
update();
}
mColorBar->update();
applyZoomValue(MessageViewer::MessageViewerSettings::self()->zoomFactor(), false);
}
void ViewerPrivate::readGravatarConfig()
{
Gravatar::GravatarCache::self()->setMaximumSize(
Gravatar::GravatarSettings::self()->gravatarCacheSize());
if (!Gravatar::GravatarSettings::self()->gravatarSupportEnabled()) {
Gravatar::GravatarCache::self()->clear();
}
}
-void ViewerPrivate::slotGeneralFontChanged()
+void ViewerPrivate::recreateCssHelper()
{
delete mCSSHelper;
mCSSHelper = new CSSHelper(mViewer);
+}
+
+void ViewerPrivate::hasMultiMessages(bool messages)
+{
+ mShowNextMessageWidget->setVisible(messages);
+}
+
+void ViewerPrivate::slotGeneralFontChanged()
+{
+ recreateCssHelper();
if (mMessage) {
update();
}
}
void ViewerPrivate::writeConfig(bool sync)
{
MessageViewer::MessageViewerSettings::self()->setShowEmoticons(mForceEmoticons);
MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mUseFixedFont);
if (attachmentStrategy()) {
MessageViewer::MessageViewerSettings::self()->setAttachmentStrategy(QLatin1String(
attachmentStrategy()
->name()));
}
saveSplitterSizes();
if (sync) {
Q_EMIT requestConfigSync();
}
}
const AttachmentStrategy *ViewerPrivate::attachmentStrategy() const
{
return mAttachmentStrategy;
}
void ViewerPrivate::setAttachmentStrategy(const AttachmentStrategy *strategy)
{
if (mAttachmentStrategy == strategy) {
return;
}
mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart();
update(MimeTreeParser::Force);
}
QString ViewerPrivate::overrideEncoding() const
{
return mOverrideEncoding;
}
void ViewerPrivate::setOverrideEncoding(const QString &encoding)
{
if (encoding == mOverrideEncoding) {
return;
}
mOverrideEncoding = encoding;
if (mSelectEncodingAction) {
if (encoding.isEmpty()) {
mSelectEncodingAction->setCurrentItem(0);
} else {
const QStringList encodings = mSelectEncodingAction->items();
int i = 0;
for (QStringList::const_iterator it = encodings.constBegin(),
end = encodings.constEnd();
it != end; ++it, ++i) {
if (MimeTreeParser::NodeHelper::encodingForName(*it) == encoding) {
mSelectEncodingAction->setCurrentItem(i);
break;
}
}
if (i == encodings.size()) {
// the value of encoding is unknown => use Auto
qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding" << encoding
<< ". Using Auto instead.";
mSelectEncodingAction->setCurrentItem(0);
mOverrideEncoding.clear();
}
}
}
update(MimeTreeParser::Force);
}
void ViewerPrivate::setPrinting(bool enable)
{
mPrinting = enable;
}
bool ViewerPrivate::printingMode() const
{
return mPrinting;
}
void ViewerPrivate::printMessage(const Akonadi::Item &message)
{
disconnect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintMessage);
connect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintMessage);
setMessageItem(message, MimeTreeParser::Force);
}
void ViewerPrivate::printPreviewMessage(const Akonadi::Item &message)
{
disconnect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintPreview);
connect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintPreview);
setMessageItem(message, MimeTreeParser::Force);
}
void ViewerPrivate::resetStateForNewMessage()
{
mHtmlLoadExtOverride = false;
mClickedUrl.clear();
mImageUrl.clear();
enableMessageDisplay(); // just to make sure it's on
mMessage.reset();
mNodeHelper->clear();
mMessagePartNode = nullptr;
#ifndef QT_NO_TREEVIEW
mMimePartTree->clearModel();
#endif
mViewer->clearRelativePosition();
mViewer->hideAccessKeys();
if (!mPrinting) {
setShowSignatureDetails(false);
}
mFindBar->closeBar();
mViewerPluginToolManager->closeAllTools();
mScamDetectionWarning->setVisible(false);
mOpenAttachmentFolderWidget->setVisible(false);
mSubmittedFormWarning->setVisible(false);
mMailTrackingWarning->hideAndClear();
if (mPrinting) {
if (MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettings()) {
if (MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark()) {
mLevelQuote
= MessageViewer::MessageViewerSettings::self()->collapseQuoteLevelSpin() - 1;
} else {
mLevelQuote = -1;
}
} else {
mLevelQuote = -1;
}
} else {
- mDisplayFormatMessageOverwrite
- = (mDisplayFormatMessageOverwrite
- == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting
- :
- MessageViewer::Viewer::Unknown;
+// mDisplayFormatMessageOverwrite
+// = (mDisplayFormatMessageOverwrite
+// == MessageViewer::Viewer::UseGlobalSetting) ? MessageViewer::Viewer::UseGlobalSetting
+// :
+// MessageViewer::Viewer::Unknown;
}
}
void ViewerPrivate::setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode)
{
mViewerPluginToolManager->updateActions(mMessageItem);
mMessage = message;
if (message) {
mNodeHelper->setOverrideCodec(mMessage.data(), overrideCodec());
}
#ifndef QT_NO_TREEVIEW
mMimePartTree->setRoot(mNodeHelper->messageWithExtraContent(message.data()));
update(updateMode);
#endif
}
void ViewerPrivate::setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode)
{
resetStateForNewMessage();
foreach (const Akonadi::Item::Id monitoredId, mMonitor.itemsMonitoredEx()) {
mMonitor.setItemMonitored(Akonadi::Item(monitoredId), false);
}
Q_ASSERT(mMonitor.itemsMonitoredEx().isEmpty());
mMessageItem = item;
if (mMessageItem.isValid()) {
mMonitor.setItemMonitored(mMessageItem, true);
}
if (!mMessageItem.hasPayload<KMime::Message::Ptr>()) {
if (mMessageItem.isValid()) {
qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!";
}
return;
}
setMessageInternal(mMessageItem.payload<KMime::Message::Ptr>(), updateMode);
}
void ViewerPrivate::setMessage(const KMime::Message::Ptr &aMsg, MimeTreeParser::UpdateMode updateMode)
{
resetStateForNewMessage();
Akonadi::Item item;
item.setMimeType(KMime::Message::mimeType());
item.setPayload(aMsg);
mMessageItem = item;
setMessageInternal(aMsg, updateMode);
}
void ViewerPrivate::setMessagePart(KMime::Content *node)
{
// Cancel scheduled updates of the reader window, as that would stop the
// timer of the HTML writer, which would make viewing attachment not work
// anymore as not all HTML is written to the HTML part.
// We're updating the reader window here ourselves anyway.
mUpdateReaderWinTimer.stop();
if (node) {
mMessagePartNode = node;
if (node->bodyIsMessage()) {
mMainWindow->setWindowTitle(node->bodyAsMessage()->subject()->asUnicodeString());
} else {
QString windowTitle = MimeTreeParser::NodeHelper::fileName(node);
if (windowTitle.isEmpty()) {
windowTitle = node->contentDescription()->asUnicodeString();
}
if (!windowTitle.isEmpty()) {
mMainWindow->setWindowTitle(i18n("View Attachment: %1", windowTitle));
}
}
htmlWriter()->begin();
- htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont));
+ htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont));
parseContent(node);
htmlWriter()->write(QStringLiteral("</body></html>"));
htmlWriter()->end();
}
}
void ViewerPrivate::showHideMimeTree()
{
#ifndef QT_NO_TREEVIEW
if (mimePartTreeIsEmpty()) {
mMimePartTree->hide();
return;
}
bool showMimeTree = false;
if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2()
== MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always) {
mMimePartTree->show();
showMimeTree = true;
} else {
// don't rely on QSplitter maintaining sizes for hidden widgets:
saveSplitterSizes();
mMimePartTree->hide();
showMimeTree = false;
}
if (mToggleMimePartTreeAction && (mToggleMimePartTreeAction->isChecked() != showMimeTree)) {
mToggleMimePartTreeAction->setChecked(showMimeTree);
}
#endif
}
void ViewerPrivate::atmViewMsg(const KMime::Message::Ptr &message)
{
Q_ASSERT(message);
Q_EMIT showMessage(message, overrideEncoding());
}
void ViewerPrivate::adjustLayout()
{
#ifndef QT_NO_TREEVIEW
const int mimeH = MessageViewer::MessageViewerSettings::self()->mimePaneHeight();
const int messageH = MessageViewer::MessageViewerSettings::self()->messagePaneHeight();
QList<int> splitterSizes;
splitterSizes << messageH << mimeH;
mSplitter->addWidget(mMimePartTree);
mSplitter->setSizes(splitterSizes);
if (MessageViewer::MessageViewerSettings::self()->mimeTreeMode2()
== MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always
&& mMsgDisplay) {
mMimePartTree->show();
} else {
mMimePartTree->hide();
}
#endif
if (mMsgDisplay) {
mColorBar->show();
} else {
mColorBar->hide();
}
}
void ViewerPrivate::saveSplitterSizes() const
{
#ifndef QT_NO_TREEVIEW
if (!mSplitter || !mMimePartTree) {
return;
}
if (mMimePartTree->isHidden()) {
return; // don't rely on QSplitter maintaining sizes for hidden widgets.
}
MessageViewer::MessageViewerSettings::self()->setMimePaneHeight(mSplitter->sizes().at(1));
MessageViewer::MessageViewerSettings::self()->setMessagePaneHeight(mSplitter->sizes().at(0));
#endif
}
void ViewerPrivate::createWidgets()
{
//TODO: Make a MDN bar similar to Mozillas password bar and show MDNs here as soon as a
// MDN enabled message is shown.
QVBoxLayout *vlay = new QVBoxLayout(q);
- vlay->setMargin(0);
+ vlay->setContentsMargins(0, 0, 0, 0);
mSplitter = new QSplitter(Qt::Vertical, q);
connect(mSplitter, &QSplitter::splitterMoved, this, &ViewerPrivate::saveSplitterSizes);
mSplitter->setObjectName(QStringLiteral("mSplitter"));
mSplitter->setChildrenCollapsible(false);
vlay->addWidget(mSplitter);
#ifndef QT_NO_TREEVIEW
mMimePartTree = new MimePartTreeView(mSplitter);
connect(mMimePartTree, &QAbstractItemView::activated, this,
&ViewerPrivate::slotMimePartSelected);
connect(mMimePartTree, &QWidget::customContextMenuRequested, this,
&ViewerPrivate::slotMimeTreeContextMenuRequested);
#endif
mBox = new QWidget(mSplitter);
QHBoxLayout *mBoxHBoxLayout = new QHBoxLayout(mBox);
- mBoxHBoxLayout->setMargin(0);
+ mBoxHBoxLayout->setContentsMargins(0, 0, 0, 0);
mColorBar = new HtmlStatusBar(mBox);
mBoxHBoxLayout->addWidget(mColorBar);
QWidget *readerBox = new QWidget(mBox);
QVBoxLayout *readerBoxVBoxLayout = new QVBoxLayout(readerBox);
- readerBoxVBoxLayout->setMargin(0);
+ readerBoxVBoxLayout->setContentsMargins(0, 0, 0, 0);
mBoxHBoxLayout->addWidget(readerBox);
mColorBar->setObjectName(QStringLiteral("mColorBar"));
mColorBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+
+ mShowNextMessageWidget = new MessageViewer::ShowNextMessageWidget(readerBox);
+ mShowNextMessageWidget->setObjectName(QStringLiteral("shownextmessagewidget"));
+ readerBoxVBoxLayout->addWidget(mShowNextMessageWidget);
+ mShowNextMessageWidget->hide();
+ connect(mShowNextMessageWidget, &ShowNextMessageWidget::showPreviousMessage, this, &ViewerPrivate::showPreviousMessage);
+ connect(mShowNextMessageWidget, &ShowNextMessageWidget::showNextMessage, this, &ViewerPrivate::showNextMessage);
+
mSubmittedFormWarning = new SubmittedFormWarningWidget(readerBox);
mSubmittedFormWarning->setObjectName(QStringLiteral("submittedformwarning"));
readerBoxVBoxLayout->addWidget(mSubmittedFormWarning);
mMailTrackingWarning = new MailTrackingWarningWidget(readerBox);
mMailTrackingWarning->setObjectName(QStringLiteral("mailtrackingwarning"));
readerBoxVBoxLayout->addWidget(mMailTrackingWarning);
mScamDetectionWarning = new ScamDetectionWarningWidget(readerBox);
mScamDetectionWarning->setObjectName(QStringLiteral("scandetectionwarning"));
readerBoxVBoxLayout->addWidget(mScamDetectionWarning);
mOpenAttachmentFolderWidget = new OpenAttachmentFolderWidget(readerBox);
mOpenAttachmentFolderWidget->setObjectName(QStringLiteral("openattachementfolderwidget"));
readerBoxVBoxLayout->addWidget(mOpenAttachmentFolderWidget);
mTextToSpeechWidget = new KPIMTextEdit::TextToSpeechWidget(readerBox);
mTextToSpeechWidget->setObjectName(QStringLiteral("texttospeechwidget"));
readerBoxVBoxLayout->addWidget(mTextToSpeechWidget);
mViewer = new MailWebEngineView(mActionCollection, readerBox);
mViewer->setViewer(this);
readerBoxVBoxLayout->addWidget(mViewer);
mViewer->setObjectName(QStringLiteral("mViewer"));
mViewerPluginToolManager = new MessageViewer::ViewerPluginToolManager(readerBox, this);
mViewerPluginToolManager->setActionCollection(mActionCollection);
mViewerPluginToolManager->setPluginName(QStringLiteral("messageviewer"));
mViewerPluginToolManager->setServiceTypeName(QStringLiteral("MessageViewer/ViewerPlugin"));
if (!mViewerPluginToolManager->initializePluginList()) {
qCDebug(MESSAGEVIEWER_LOG) << " Impossible to initialize plugins";
}
mViewerPluginToolManager->createView();
connect(mViewerPluginToolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this,
&ViewerPrivate::slotActivatePlugin);
mSliderContainer = new KPIMTextEdit::SlideContainer(readerBox);
mSliderContainer->setObjectName(QStringLiteral("slidercontainer"));
readerBoxVBoxLayout->addWidget(mSliderContainer);
mFindBar = new WebEngineViewer::FindBarWebEngineView(mViewer, q);
connect(mFindBar, &WebEngineViewer::FindBarWebEngineView::hideFindBar, mSliderContainer,
&KPIMTextEdit::SlideContainer::slideOut);
mSliderContainer->setContent(mFindBar);
#ifndef QT_NO_TREEVIEW
mSplitter->setStretchFactor(mSplitter->indexOf(mMimePartTree), 0);
#endif
}
void ViewerPrivate::slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin)
{
+ mCSSHelper->setHeaderPlugin(plugin);
mHeaderStylePlugin = plugin;
update(MimeTreeParser::Force);
}
void ViewerPrivate::slotStyleUpdated()
{
update(MimeTreeParser::Force);
}
void ViewerPrivate::createActions()
{
KActionCollection *ac = mActionCollection;
mHeaderStyleMenuManager = new MessageViewer::HeaderStyleMenuManager(ac, this);
connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleChanged, this,
&ViewerPrivate::slotStyleChanged);
connect(mHeaderStyleMenuManager, &MessageViewer::HeaderStyleMenuManager::styleUpdated, this,
&ViewerPrivate::slotStyleUpdated);
if (!ac) {
return;
}
mZoomActionMenu = new WebEngineViewer::ZoomActionMenu(this);
connect(mZoomActionMenu, &WebEngineViewer::ZoomActionMenu::zoomChanged, this, &ViewerPrivate::slotZoomChanged);
mZoomActionMenu->setActionCollection(ac);
mZoomActionMenu->createZoomActions();
// attachment style
KActionMenu *attachmentMenu = new KActionMenu(i18nc("View->", "&Attachments"), this);
ac->addAction(QStringLiteral("view_attachments"), attachmentMenu);
addHelpTextAction(attachmentMenu, i18n("Choose display style of attachments"));
QActionGroup *group = new QActionGroup(this);
KToggleAction *raction = new KToggleAction(i18nc("View->attachments->", "&As Icons"), this);
ac->addAction(QStringLiteral("view_attachments_as_icons"), raction);
connect(raction, &QAction::triggered, this, &ViewerPrivate::slotIconicAttachments);
addHelpTextAction(raction, i18n("Show all attachments as icons. Click to see them."));
group->addAction(raction);
attachmentMenu->addAction(raction);
raction = new KToggleAction(i18nc("View->attachments->", "&Smart"), this);
ac->addAction(QStringLiteral("view_attachments_smart"), raction);
connect(raction, &QAction::triggered, this, &ViewerPrivate::slotSmartAttachments);
addHelpTextAction(raction, i18n("Show attachments as suggested by sender."));
group->addAction(raction);
attachmentMenu->addAction(raction);
raction = new KToggleAction(i18nc("View->attachments->", "&Inline"), this);
ac->addAction(QStringLiteral("view_attachments_inline"), raction);
connect(raction, &QAction::triggered, this, &ViewerPrivate::slotInlineAttachments);
addHelpTextAction(raction, i18n("Show all attachments inline (if possible)"));
group->addAction(raction);
attachmentMenu->addAction(raction);
raction = new KToggleAction(i18nc("View->attachments->", "&Hide"), this);
ac->addAction(QStringLiteral("view_attachments_hide"), raction);
connect(raction, &QAction::triggered, this, &ViewerPrivate::slotHideAttachments);
addHelpTextAction(raction, i18n("Do not show attachments in the message viewer"));
group->addAction(raction);
attachmentMenu->addAction(raction);
mHeaderOnlyAttachmentsAction = new KToggleAction(i18nc("View->attachments->",
"In Header Only"), this);
ac->addAction(QStringLiteral("view_attachments_headeronly"), mHeaderOnlyAttachmentsAction);
connect(mHeaderOnlyAttachmentsAction, &QAction::triggered,
this, &ViewerPrivate::slotHeaderOnlyAttachments);
addHelpTextAction(mHeaderOnlyAttachmentsAction,
i18n("Show Attachments only in the header of the mail"));
group->addAction(mHeaderOnlyAttachmentsAction);
attachmentMenu->addAction(mHeaderOnlyAttachmentsAction);
// Set Encoding submenu
mSelectEncodingAction = new KSelectAction(QIcon::fromTheme(QStringLiteral(
"character-set")),
i18n("&Set Encoding"), this);
mSelectEncodingAction->setToolBarMode(KSelectAction::MenuMode);
ac->addAction(QStringLiteral("encoding"), mSelectEncodingAction);
- connect(mSelectEncodingAction, QOverload<int>::of(&KSelectAction::triggered),
+ connect(mSelectEncodingAction, qOverload<int>(&KSelectAction::triggered),
this, &ViewerPrivate::slotSetEncoding);
QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false);
encodings.prepend(i18n("Auto"));
mSelectEncodingAction->setItems(encodings);
mSelectEncodingAction->setCurrentItem(0);
//
// Message Menu
//
// copy selected text to clipboard
mCopyAction = ac->addAction(KStandardAction::Copy, QStringLiteral("kmail_copy"));
mCopyAction->setText(i18n("Copy Text"));
connect(mCopyAction, &QAction::triggered, this, &ViewerPrivate::slotCopySelectedText);
connect(mViewer, &MailWebEngineView::selectionChanged,
this, &ViewerPrivate::viewerSelectionChanged);
viewerSelectionChanged();
// copy all text to clipboard
mSelectAllAction = new QAction(i18n("Select All Text"), this);
ac->addAction(QStringLiteral("mark_all_text"), mSelectAllAction);
connect(mSelectAllAction, &QAction::triggered, this, &ViewerPrivate::selectAll);
ac->setDefaultShortcut(mSelectAllAction, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A));
// copy Email address to clipboard
mCopyURLAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")),
i18n("Copy Link Address"), this);
ac->addAction(QStringLiteral("copy_url"), mCopyURLAction);
connect(mCopyURLAction, &QAction::triggered, this, &ViewerPrivate::slotUrlCopy);
// open URL
mUrlOpenAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n(
"Open URL"), this);
ac->addAction(QStringLiteral("open_url"), mUrlOpenAction);
connect(mUrlOpenAction, &QAction::triggered, this, &ViewerPrivate::slotOpenUrl);
// use fixed font
mToggleFixFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this);
ac->addAction(QStringLiteral("toggle_fixedfont"), mToggleFixFontAction);
connect(mToggleFixFontAction, &QAction::triggered, this, &ViewerPrivate::slotToggleFixedFont);
ac->setDefaultShortcut(mToggleFixFontAction, QKeySequence(Qt::Key_X));
// Show message structure viewer
mToggleMimePartTreeAction = new KToggleAction(i18n("Show Message Structure"), this);
ac->addAction(QStringLiteral("toggle_mimeparttree"), mToggleMimePartTreeAction);
connect(mToggleMimePartTreeAction, &QAction::toggled,
this, &ViewerPrivate::slotToggleMimePartTree);
ac->setDefaultShortcut(mToggleMimePartTreeAction, QKeySequence(Qt::Key_D + Qt::CTRL + Qt::ALT));
mViewSourceAction = new QAction(i18n("&View Source"), this);
ac->addAction(QStringLiteral("view_source"), mViewSourceAction);
connect(mViewSourceAction, &QAction::triggered, this, &ViewerPrivate::slotShowMessageSource);
ac->setDefaultShortcut(mViewSourceAction, QKeySequence(Qt::Key_V));
mSaveMessageAction
= new QAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n(
"&Save message..."), this);
ac->addAction(QStringLiteral("save_message"), mSaveMessageAction);
connect(mSaveMessageAction, &QAction::triggered, this, &ViewerPrivate::slotSaveMessage);
//Laurent: conflict with kmail shortcut
//mSaveMessageAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
mSaveMessageDisplayFormat = new QAction(i18n("&Save Display Format"), this);
ac->addAction(QStringLiteral("save_message_display_format"), mSaveMessageDisplayFormat);
connect(mSaveMessageDisplayFormat, &QAction::triggered, this,
&ViewerPrivate::slotSaveMessageDisplayFormat);
mResetMessageDisplayFormat = new QAction(i18n("&Reset Display Format"), this);
ac->addAction(QStringLiteral("reset_message_display_format"), mResetMessageDisplayFormat);
connect(mResetMessageDisplayFormat, &QAction::triggered, this,
&ViewerPrivate::slotResetMessageDisplayFormat);
//
// Scroll actions
//
mScrollUpAction = new QAction(i18n("Scroll Message Up"), this);
ac->setDefaultShortcut(mScrollUpAction, QKeySequence(Qt::Key_Up));
ac->addAction(QStringLiteral("scroll_up"), mScrollUpAction);
connect(mScrollUpAction, &QAction::triggered,
q, &Viewer::slotScrollUp);
mScrollDownAction = new QAction(i18n("Scroll Message Down"), this);
ac->setDefaultShortcut(mScrollDownAction, QKeySequence(Qt::Key_Down));
ac->addAction(QStringLiteral("scroll_down"), mScrollDownAction);
connect(mScrollDownAction, &QAction::triggered,
q, &Viewer::slotScrollDown);
mScrollUpMoreAction = new QAction(i18n("Scroll Message Up (More)"), this);
ac->setDefaultShortcut(mScrollUpMoreAction, QKeySequence(Qt::Key_PageUp));
ac->addAction(QStringLiteral("scroll_up_more"), mScrollUpMoreAction);
connect(mScrollUpMoreAction, &QAction::triggered,
q, &Viewer::slotScrollPrior);
mScrollDownMoreAction = new QAction(i18n("Scroll Message Down (More)"), this);
ac->setDefaultShortcut(mScrollDownMoreAction, QKeySequence(Qt::Key_PageDown));
ac->addAction(QStringLiteral("scroll_down_more"), mScrollDownMoreAction);
connect(mScrollDownMoreAction, &QAction::triggered,
q, &Viewer::slotScrollNext);
//
// Actions not in menu
//
// Toggle HTML display mode.
mToggleDisplayModeAction = new KToggleAction(i18n("Toggle HTML Display Mode"), this);
ac->addAction(QStringLiteral("toggle_html_display_mode"), mToggleDisplayModeAction);
ac->setDefaultShortcut(mToggleDisplayModeAction, QKeySequence(Qt::SHIFT + Qt::Key_H));
connect(mToggleDisplayModeAction, &QAction::triggered,
this, &ViewerPrivate::slotToggleHtmlMode);
addHelpTextAction(mToggleDisplayModeAction,
i18n("Toggle display mode between HTML and plain text"));
// Load external reference
QAction *loadExternalReferenceAction = new QAction(i18n("Load external references"), this);
ac->addAction(QStringLiteral("load_external_reference"), loadExternalReferenceAction);
ac->setDefaultShortcut(loadExternalReferenceAction,
QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_R));
connect(loadExternalReferenceAction, &QAction::triggered,
this, &ViewerPrivate::slotLoadExternalReference);
addHelpTextAction(loadExternalReferenceAction,
i18n("Load external references from the Internet for this message."));
mSpeakTextAction = new QAction(i18n("Speak Text"), this);
mSpeakTextAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-text-to-speech")));
ac->addAction(QStringLiteral("speak_text"), mSpeakTextAction);
connect(mSpeakTextAction, &QAction::triggered,
this, &ViewerPrivate::slotSpeakText);
mCopyImageLocation = new QAction(i18n("Copy Image Location"), this);
mCopyImageLocation->setIcon(QIcon::fromTheme(QStringLiteral("view-media-visualization")));
ac->addAction(QStringLiteral("copy_image_location"), mCopyImageLocation);
ac->setShortcutsConfigurable(mCopyImageLocation, false);
connect(mCopyImageLocation, &QAction::triggered,
this, &ViewerPrivate::slotCopyImageLocation);
mFindInMessageAction
= new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n(
"&Find in Message..."), this);
ac->addAction(QStringLiteral("find_in_messages"), mFindInMessageAction);
connect(mFindInMessageAction, &QAction::triggered, this, &ViewerPrivate::slotFind);
ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first());
mShareServiceUrlMenu = mShareServiceManager->menu();
ac->addAction(QStringLiteral("shareservice_menu"), mShareServiceUrlMenu);
connect(mShareServiceManager, &PimCommon::ShareServiceUrlManager::serviceUrlSelected, this,
&ViewerPrivate::slotServiceUrlSelected);
mDisableEmoticonAction = new KToggleAction(i18n("Disable Emoticon"), this);
ac->addAction(QStringLiteral("disable_emoticon"), mDisableEmoticonAction);
connect(mDisableEmoticonAction, &QAction::triggered, this, &ViewerPrivate::slotToggleEmoticons);
ac->setDefaultShortcut(mFindInMessageAction, KStandardShortcut::find().first());
}
void ViewerPrivate::showContextMenu(KMime::Content *content, const QPoint &pos)
{
#ifndef QT_NO_TREEVIEW
if (!content) {
return;
}
if (content->contentType(false)) {
if (content->contentType()->mimeType() == "text/x-moz-deleted") {
return;
}
}
const bool isAttachment = !content->contentType()->isMultipart() && !content->isTopLevel();
const bool isRoot = (content == mMessage.data());
const auto hasAttachments = KMime::hasAttachment(mMessage.data());
QMenu popup;
if (!isRoot) {
popup.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save &As..."),
this, &ViewerPrivate::slotAttachmentSaveAs);
if (isAttachment) {
popup.addAction(QIcon::fromTheme(QStringLiteral("document-open")),
i18nc("to open", "Open"),
this, &ViewerPrivate::slotAttachmentOpen);
if (selectedContents().count() == 1) {
createOpenWithMenu(&popup, QLatin1String(content->contentType()->mimeType()),
false);
} else {
popup.addAction(i18n("Open With..."), this, &ViewerPrivate::slotAttachmentOpenWith);
}
popup.addAction(i18nc("to view something",
"View"), this, &ViewerPrivate::slotAttachmentView);
}
}
if (hasAttachments) {
popup.addAction(i18n("Save All Attachments..."), this,
&ViewerPrivate::slotAttachmentSaveAll);
}
// edit + delete only for attachments
if (!isRoot) {
if (isAttachment) {
popup.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"),
this, &ViewerPrivate::slotAttachmentCopy);
}
if (!content->isTopLevel()) {
popup.addSeparator();
popup.addAction(i18n("Properties"), this, &ViewerPrivate::slotAttachmentProperties);
}
}
popup.exec(mMimePartTree->viewport()->mapToGlobal(pos));
#endif
}
KToggleAction *ViewerPrivate::actionForAttachmentStrategy(
const AttachmentStrategy *as)
{
if (!mActionCollection) {
return nullptr;
}
QString actionName;
if (as == AttachmentStrategy::iconic()) {
actionName = QStringLiteral("view_attachments_as_icons");
} else if (as == AttachmentStrategy::smart()) {
actionName = QStringLiteral("view_attachments_smart");
} else if (as == AttachmentStrategy::inlined()) {
actionName = QStringLiteral("view_attachments_inline");
} else if (as == AttachmentStrategy::hidden()) {
actionName = QStringLiteral("view_attachments_hide");
} else if (as == AttachmentStrategy::headerOnly()) {
actionName = QStringLiteral("view_attachments_headeronly");
}
if (actionName.isEmpty()) {
return nullptr;
} else {
return static_cast<KToggleAction *>(mActionCollection->action(actionName));
}
}
void ViewerPrivate::readGlobalOverrideCodec()
{
// if the global character encoding wasn't changed then there's nothing to do
if (MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding()
== mOldGlobalOverrideEncoding) {
return;
}
setOverrideEncoding(MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding());
mOldGlobalOverrideEncoding
= MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding();
}
const QTextCodec *ViewerPrivate::overrideCodec() const
{
if (mOverrideEncoding.isEmpty() || mOverrideEncoding == QLatin1String("Auto")) { // Auto
return nullptr;
} else {
return ViewerPrivate::codecForName(mOverrideEncoding.toLatin1());
}
}
static QColor nextColor(const QColor &c)
{
int h, s, v;
c.getHsv(&h, &s, &v);
return QColor::fromHsv((h + 50) % 360, qMax(s, 64), v);
}
QString ViewerPrivate::renderAttachments(KMime::Content *node, const QColor &bgColor) const
{
if (!node) {
return QString();
}
QString html;
KMime::Content *child = MessageCore::NodeHelper::firstChild(node);
if (child) {
QString subHtml = renderAttachments(child, nextColor(bgColor));
if (!subHtml.isEmpty()) {
QString margin;
if (node != mMessage.data() || headerStylePlugin()->hasMargin()) {
margin = QStringLiteral("padding:2px; margin:2px; ");
}
QString align = headerStylePlugin()->alignment();
const QByteArray mediaTypeLower = node->contentType()->mediaType().toLower();
const bool result
= (mediaTypeLower == "message" || mediaTypeLower == "multipart"
|| node == mMessage.data());
if (result) {
html += QStringLiteral("<div style=\"background:%1; %2"
"vertical-align:middle; float:%3;\">").arg(bgColor.name()).
- arg(margin).arg(align);
+ arg(margin, align);
}
html += subHtml;
if (result) {
html += QLatin1String("</div>");
}
}
} else {
Util::AttachmentDisplayInfo info = Util::attachmentDisplayInfo(node);
if (info.displayInHeader) {
html += QLatin1String("<div style=\"float:left;\">");
html += QStringLiteral(
"<span style=\"white-space:nowrap; border-width: 0px; border-left-width: 5px; border-color: %1; 2px; border-left-style: solid;\">")
.arg(bgColor.name());
mNodeHelper->writeNodeToTempFile(node);
const QString href = mNodeHelper->asHREF(node, QStringLiteral("header"));
html += QLatin1String("<a href=\"") + href
+QLatin1String("\">");
const QString imageMaxSize = QStringLiteral("width=\"16\" height=\"16\"");
#if 0
if (!info.icon.isEmpty()) {
QImage tmpImg(info.icon);
if (tmpImg.width() > 48 || tmpImg.height() > 48) {
imageMaxSize = QStringLiteral("width=\"48\" height=\"48\"");
}
}
#endif
html += QStringLiteral("<img %1 style=\"vertical-align:middle;\" src=\"").arg(
imageMaxSize) + info.icon + QLatin1String("\"/>&nbsp;");
const int elidedTextSize = headerStylePlugin()->elidedTextSize();
if (elidedTextSize == -1) {
html += info.label;
} else {
- QFont bodyFont = mCSSHelper->bodyFont(mUseFixedFont);
+ QFont bodyFont = cssHelper()->bodyFont(mUseFixedFont);
QFontMetrics fm(bodyFont);
html += fm.elidedText(info.label, Qt::ElideRight, elidedTextSize);
}
html += QLatin1String("</a></span></div> ");
}
}
- Q_FOREACH (KMime::Content *extraNode, mNodeHelper->extraContents(node)) {
+ for (KMime::Content *extraNode : mNodeHelper->extraContents(node)) {
html += renderAttachments(extraNode, bgColor);
}
KMime::Content *next = MessageCore::NodeHelper::nextSibling(node);
if (next) {
html += renderAttachments(next, nextColor(bgColor));
}
return html;
}
KMime::Content *ViewerPrivate::findContentByType(KMime::Content *content, const QByteArray &type)
{
const auto list = content->contents();
for (KMime::Content *c : list) {
if (c->contentType()->mimeType() == type) {
return c;
}
}
return nullptr;
}
//-----------------------------------------------------------------------------
const QTextCodec *ViewerPrivate::codecForName(const QByteArray &_str)
{
if (_str.isEmpty()) {
return nullptr;
}
QByteArray codec = _str.toLower();
return KCharsets::charsets()->codecForName(QLatin1String(codec));
}
void ViewerPrivate::update(MimeTreeParser::UpdateMode updateMode)
{
// Avoid flicker, somewhat of a cludge
if (updateMode == MimeTreeParser::Force) {
// stop the timer to avoid calling updateReaderWin twice
mUpdateReaderWinTimer.stop();
saveRelativePosition();
updateReaderWin();
} else if (mUpdateReaderWinTimer.isActive()) {
mUpdateReaderWinTimer.setInterval(150);
} else {
mUpdateReaderWinTimer.start(0);
}
}
void ViewerPrivate::slotOpenUrl()
{
slotUrlOpen();
}
void ViewerPrivate::slotUrlOpen(const QUrl &url)
{
if (!url.isEmpty()) {
mClickedUrl = url;
}
// First, let's see if the URL handler manager can handle the URL. If not, try KRun for some
// known URLs, otherwise fallback to emitting a signal.
// That signal is caught by KMail, and in case of mailto URLs, a composer is shown.
if (URLHandlerManager::instance()->handleClick(mClickedUrl, this)) {
return;
}
Q_EMIT urlClicked(mMessageItem, mClickedUrl);
}
void ViewerPrivate::checkPhishingUrl()
{
if (!PimCommon::NetworkUtil::self()->lowBandwidth()
&& MessageViewer::MessageViewerSettings::self()->checkPhishingUrl()
&& (mClickedUrl.scheme() != QLatin1String("mailto"))) {
mPhishingDatabase->checkUrl(mClickedUrl);
} else {
executeRunner(mClickedUrl);
}
}
void ViewerPrivate::executeRunner(const QUrl &url)
{
if (!MessageViewer::Util::handleUrlWithQDesktopServices(url)) {
KRun *runner = new KRun(url, viewer()); // will delete itself
runner->setRunExecutables(false);
}
}
void ViewerPrivate::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status)
{
switch (status) {
case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork:
KMessageBox::error(mMainWindow, i18n("The network is broken."), i18n("Check Phishing URL"));
break;
case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl:
KMessageBox::error(mMainWindow, i18n("The URL %1 is not valid.", url.toString()),
i18n("Check Phishing URL"));
break;
case WebEngineViewer::CheckPhishingUrlUtil::Ok:
break;
case WebEngineViewer::CheckPhishingUrlUtil::MalWare:
if (!urlIsAMalwareButContinue()) {
return;
}
break;
case WebEngineViewer::CheckPhishingUrlUtil::Unknown:
qCWarning(MESSAGEVIEWER_LOG) << "WebEngineViewer::slotCheckedUrlFinished unknown error ";
break;
}
executeRunner(url);
}
bool ViewerPrivate::urlIsAMalwareButContinue()
{
if (KMessageBox::No
== KMessageBox::warningYesNo(mMainWindow,
i18n(
"This web site is a malware, do you want to continue to show it?"),
i18n("Malware"))) {
return false;
}
return true;
}
void ViewerPrivate::slotUrlOn(const QString &link)
{
// The "link" we get here is not URL-encoded, and therefore there is no way QUrl could
// parse it correctly. To workaround that, we use QWebFrame::hitTestContent() on the mouse position
// to get the URL before WebKit managed to mangle it.
QUrl url(link);
const QString protocol = url.scheme();
if (protocol == QLatin1String("kmail")
|| protocol == QLatin1String("x-kmail")
|| protocol == QLatin1String("attachment")
|| (protocol.isEmpty() && url.path().isEmpty())) {
mViewer->setAcceptDrops(false);
} else {
mViewer->setAcceptDrops(true);
}
mViewer->setLinkHovered(url);
if (link.trimmed().isEmpty()) {
KPIM::BroadcastStatus::instance()->reset();
Q_EMIT showStatusBarMessage(QString());
return;
}
QString msg = URLHandlerManager::instance()->statusBarMessage(url, this);
if (msg.isEmpty()) {
msg = link;
}
Q_EMIT showStatusBarMessage(msg);
}
void ViewerPrivate::slotUrlPopup(const WebEngineViewer::WebHitTestResult &result)
{
if (!mMsgDisplay) {
return;
}
mClickedUrl = result.linkUrl();
mImageUrl = result.imageUrl();
const QPoint aPos = mViewer->mapToGlobal(result.pos());
if (URLHandlerManager::instance()->handleContextMenuRequest(mClickedUrl, aPos, this)) {
return;
}
if (!mActionCollection) {
return;
}
if (mClickedUrl.scheme() == QLatin1String("mailto")) {
mCopyURLAction->setText(i18n("Copy Email Address"));
} else {
mCopyURLAction->setText(i18n("Copy Link Address"));
}
Q_EMIT displayPopupMenu(mMessageItem, result, aPos);
Q_EMIT popupMenu(mMessageItem, mClickedUrl, mImageUrl, aPos);
}
void ViewerPrivate::slotLoadExternalReference()
{
if (mColorBar->isNormal() || htmlLoadExtOverride()) {
return;
}
setHtmlLoadExtOverride(true);
update(MimeTreeParser::Force);
}
Viewer::DisplayFormatMessage translateToDisplayFormat(MimeTreeParser::Util::HtmlMode mode)
{
switch (mode) {
case MimeTreeParser::Util::Normal:
return Viewer::Unknown;
case MimeTreeParser::Util::Html:
return Viewer::Html;
case MimeTreeParser::Util::MultipartPlain:
return Viewer::Text;
case MimeTreeParser::Util::MultipartHtml:
return Viewer::Html;
case MimeTreeParser::Util::MultipartIcal:
return Viewer::ICal;
}
return Viewer::Unknown;
}
void ViewerPrivate::slotToggleHtmlMode()
{
const auto availableModes = mColorBar->availableModes();
const int availableModeSize(availableModes.size());
// for (int i = 0; i < availableModeSize; ++i) {
// qDebug() << " Mode " << MimeTreeParser::Util::htmlModeToString(availableModes.at(i));
// }
// qDebug() << " availableModeSize"<<availableModeSize;
if (mColorBar->isNormal() || availableModeSize < 2) {
return;
}
mScamDetectionWarning->setVisible(false);
const MimeTreeParser::Util::HtmlMode mode = mColorBar->mode();
const int pos = (availableModes.indexOf(mode) + 1) % availableModeSize;
setDisplayFormatMessageOverwrite(translateToDisplayFormat(availableModes[pos]));
update(MimeTreeParser::Force);
// for (int i = 0; i < availableModeSize; ++i) {
// qDebug() << "AFTER Mode " << MimeTreeParser::Util::htmlModeToString(availableModes.at(i));
// }
// qDebug() << " Assign modes " << availableModes;
mColorBar->setAvailableModes(availableModes);
}
void ViewerPrivate::slotFind()
{
if (mViewer->hasSelection()) {
mFindBar->setText(mViewer->selectedText());
}
mSliderContainer->slideIn();
mFindBar->focusAndSetCursor();
}
void ViewerPrivate::slotToggleFixedFont()
{
mUseFixedFont = !mUseFixedFont;
update(MimeTreeParser::Force);
}
void ViewerPrivate::slotToggleMimePartTree()
{
if (mToggleMimePartTreeAction->isChecked()) {
MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2(
MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Always);
} else {
MessageViewer::MessageViewerSettings::self()->setMimeTreeMode2(
MessageViewer::MessageViewerSettings::EnumMimeTreeMode2::Never);
}
showHideMimeTree();
}
void ViewerPrivate::slotShowMessageSource()
{
if (!mMessage) {
return;
}
mNodeHelper->messageWithExtraContent(mMessage.data());
QPointer<MailSourceWebEngineViewer> viewer = new MailSourceWebEngineViewer; // deletes itself upon close
mListMailSourceViewer.append(viewer);
viewer->setWindowTitle(i18n("Message as Plain Text"));
const QString rawMessage = QString::fromLatin1(mMessage->encodedContent());
viewer->setRawSource(rawMessage);
viewer->setDisplayedSource(mViewer->page());
if (mUseFixedFont) {
viewer->setFixedFont();
}
viewer->show();
}
void ViewerPrivate::updateReaderWin()
{
if (!mMsgDisplay) {
return;
}
if (mRecursionCountForDisplayMessage + 1 > 1) {
// This recursion here can happen because the ObjectTreeParser in parseMsg() can exec() an
// eventloop.
// This happens in two cases:
// 1) The ContactSearchJob started by FancyHeaderStyle::format
// 2) Various modal passphrase dialogs for decryption of a message (bug 96498)
//
// While the exec() eventloop is running, it is possible that a timer calls updateReaderWin(),
// and not aborting here would confuse the state terribly.
qCWarning(MESSAGEVIEWER_LOG) << "Danger, recursion while displaying a message!";
return;
}
mRecursionCountForDisplayMessage++;
mViewer->setAllowExternalContent(htmlLoadExternal());
htmlWriter()->reset();
//TODO: if the item doesn't have the payload fetched, try to fetch it? Maybe not here, but in setMessageItem.
if (mMessage) {
mColorBar->show();
displayMessage();
} else if (mMessagePartNode) {
setMessagePart(mMessagePartNode);
} else {
mColorBar->hide();
#ifndef QT_NO_TREEVIEW
mMimePartTree->hide();
#endif
htmlWriter()->begin();
- htmlWriter()->write(mCSSHelper->htmlHead(mUseFixedFont) + QLatin1String("</body></html>"));
+ htmlWriter()->write(cssHelper()->htmlHead(mUseFixedFont) + QLatin1String("</body></html>"));
htmlWriter()->end();
}
mRecursionCountForDisplayMessage--;
}
void ViewerPrivate::slotMimePartSelected(const QModelIndex &index)
{
#ifndef QT_NO_TREEVIEW
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
if (!mMimePartTree->mimePartModel()->parent(index).isValid() && index.row() == 0) {
update(MimeTreeParser::Force);
} else {
setMessagePart(content);
}
#endif
}
void ViewerPrivate::slotIconicAttachments()
{
setAttachmentStrategy(AttachmentStrategy::iconic());
}
void ViewerPrivate::slotSmartAttachments()
{
setAttachmentStrategy(AttachmentStrategy::smart());
}
void ViewerPrivate::slotInlineAttachments()
{
setAttachmentStrategy(AttachmentStrategy::inlined());
}
void ViewerPrivate::slotHideAttachments()
{
setAttachmentStrategy(AttachmentStrategy::hidden());
}
void ViewerPrivate::slotHeaderOnlyAttachments()
{
setAttachmentStrategy(AttachmentStrategy::headerOnly());
}
void ViewerPrivate::attachmentView(KMime::Content *atmNode)
{
if (atmNode) {
const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
atmViewMsg(atmNode->parent()->bodyAsMessage());
} else if ((qstricmp(atmNode->contentType()->mediaType().constData(), "text") == 0)
&& ((qstricmp(atmNode->contentType()->subType().constData(), "x-vcard") == 0)
|| (qstricmp(atmNode->contentType()->subType().constData(),
"directory") == 0))) {
setMessagePart(atmNode);
} else {
Q_EMIT showReader(atmNode, htmlMail(), overrideEncoding());
}
}
}
void ViewerPrivate::slotDelayedResize()
{
mSplitter->setGeometry(0, 0, q->width(), q->height());
}
void ViewerPrivate::slotPrintPreview()
{
disconnect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintPreview);
if (!mMessage) {
return;
}
//Need to delay
QTimer::singleShot(1 * 1000, this, &ViewerPrivate::slotDelayPrintPreview);
}
void ViewerPrivate::slotDelayPrintPreview()
{
QPrintPreviewDialog *dialog = new QPrintPreviewDialog(q);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(800, 750);
connect(dialog, &QPrintPreviewDialog::paintRequested, this, [=](QPrinter *printing) {
QApplication::setOverrideCursor(Qt::WaitCursor);
mViewer->execPrintPreviewPage(printing, 10*1000);
QApplication::restoreOverrideCursor();
});
dialog->open(this, SIGNAL(printingFinished()));
}
void ViewerPrivate::slotOpenInBrowser()
{
WebEngineViewer::WebEngineExportHtmlPageJob *job
= new WebEngineViewer::WebEngineExportHtmlPageJob;
job->setEngineView(mViewer);
connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed, this,
&ViewerPrivate::slotExportHtmlPageFailed);
connect(job, &WebEngineViewer::WebEngineExportHtmlPageJob::success, this,
&ViewerPrivate::slotExportHtmlPageSuccess);
job->start();
}
void ViewerPrivate::slotExportHtmlPageSuccess(const QString &filename)
{
const QUrl url(QUrl::fromLocalFile(filename));
KRun::RunFlags flags;
flags |= KRun::DeleteTemporaryFiles;
KRun::runUrl(url, QStringLiteral("text/html"), q, flags);
Q_EMIT printingFinished();
}
void ViewerPrivate::slotExportHtmlPageFailed()
{
qCDebug(MESSAGEVIEWER_LOG) << " Export HTML failed";
Q_EMIT printingFinished();
}
void ViewerPrivate::slotPrintMessage()
{
disconnect(
mPartHtmlWriter.data(), &WebEnginePartHtmlWriter::finished, this,
&ViewerPrivate::slotPrintMessage);
if (!mMessage) {
return;
}
if (mCurrentPrinter) {
return;
}
mCurrentPrinter = new QPrinter();
QPointer<QPrintDialog> dialog = new QPrintDialog(mCurrentPrinter, mMainWindow);
dialog->setWindowTitle(i18n("Print Document"));
if (dialog->exec() != QDialog::Accepted) {
slotHandlePagePrinted(false);
delete dialog;
return;
}
delete dialog;
mViewer->page()->print(mCurrentPrinter, invoke(this, &ViewerPrivate::slotHandlePagePrinted));
}
void ViewerPrivate::slotHandlePagePrinted(bool result)
{
Q_UNUSED(result);
delete mCurrentPrinter;
mCurrentPrinter = nullptr;
Q_EMIT printingFinished();
}
void ViewerPrivate::slotSetEncoding()
{
if (mSelectEncodingAction) {
if (mSelectEncodingAction->currentItem() == 0) { // Auto
mOverrideEncoding.clear();
} else {
mOverrideEncoding = MimeTreeParser::NodeHelper::encodingForName(
mSelectEncodingAction->currentText());
}
update(MimeTreeParser::Force);
}
}
HeaderStylePlugin *ViewerPrivate::headerStylePlugin() const
{
return mHeaderStylePlugin;
}
QString ViewerPrivate::attachmentHtml() const
{
const QColor background
= KColorScheme(QPalette::Active, KColorScheme::View).background().color();
QString html = renderAttachments(mMessage.data(), background);
if (!html.isEmpty()) {
- QString textAlign = QStringLiteral("right");
-
const bool isFancyTheme = (headerStylePlugin()->name() == QStringLiteral("fancy"));
- if (isFancyTheme) {
- textAlign = QStringLiteral("left");
- }
-
if (isFancyTheme) {
html.prepend(QStringLiteral("<div style=\"float:left;\">%1&nbsp;</div>").arg(i18n(
"Attachments:")));
}
}
return html;
}
void ViewerPrivate::executeCustomScriptsAfterLoading()
{
disconnect(mViewer, &MailWebEngineView::loadFinished, this,
&ViewerPrivate::executeCustomScriptsAfterLoading);
// inject attachments in header view
// we have to do that after the otp has run so we also see encrypted parts
mViewer->scrollToRelativePosition(mViewer->relativePosition());
mViewer->clearRelativePosition();
}
void ViewerPrivate::slotSettingsChanged()
{
update(MimeTreeParser::Force);
}
void ViewerPrivate::slotMimeTreeContextMenuRequested(const QPoint &pos)
{
#ifndef QT_NO_TREEVIEW
QModelIndex index = mMimePartTree->indexAt(pos);
if (index.isValid()) {
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
showContextMenu(content, pos);
}
#endif
}
void ViewerPrivate::slotAttachmentOpenWith()
{
#ifndef QT_NO_TREEVIEW
QItemSelectionModel *selectionModel = mMimePartTree->selectionModel();
const QModelIndexList selectedRows = selectionModel->selectedRows();
for (const QModelIndex &index : selectedRows) {
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
attachmentOpenWith(content);
}
#endif
}
void ViewerPrivate::slotAttachmentOpen()
{
#ifndef QT_NO_TREEVIEW
QItemSelectionModel *selectionModel = mMimePartTree->selectionModel();
const QModelIndexList selectedRows = selectionModel->selectedRows();
for (const QModelIndex &index : selectedRows) {
KMime::Content *content = static_cast<KMime::Content *>(index.internalPointer());
attachmentOpen(content);
}
#endif
}
void ViewerPrivate::showOpenAttachmentFolderWidget(const QList<QUrl> &urls)
{
mOpenAttachmentFolderWidget->setUrls(urls);
mOpenAttachmentFolderWidget->slotShowWarning();
}
bool ViewerPrivate::mimePartTreeIsEmpty() const
{
#ifndef QT_NO_TREEVIEW
return mMimePartTree->model()->rowCount() == 0;
#else
return false;
#endif
}
void ViewerPrivate::setPluginName(const QString &pluginName)
{
mHeaderStyleMenuManager->setPluginName(pluginName);
}
QList<QAction *> ViewerPrivate::viewerPluginActionList(
ViewerPluginInterface::SpecificFeatureTypes features)
{
if (mViewerPluginToolManager) {
return mViewerPluginToolManager->viewerPluginActionList(features);
}
return QList<QAction *>();
}
void ViewerPrivate::slotActivatePlugin(ViewerPluginInterface *interface)
{
interface->setMessage(mMessage);
interface->setMessageItem(mMessageItem);
interface->setUrl(mClickedUrl);
interface->setCurrentCollection(mMessageItem.parentCollection());
const QString text = mViewer->selectedText();
if (!text.isEmpty()) {
interface->setText(text);
}
interface->execute();
}
void ViewerPrivate::slotAttachmentSaveAs()
{
const auto contents = selectedContents();
QList<QUrl> urlList;
if (Util::saveAttachments(contents, mMainWindow, urlList)) {
showOpenAttachmentFolderWidget(urlList);
}
}
void ViewerPrivate::slotAttachmentSaveAll()
{
const auto contents = mMessage->attachments();
QList<QUrl> urlList;
if (Util::saveAttachments(contents, mMainWindow, urlList)) {
showOpenAttachmentFolderWidget(urlList);
}
}
void ViewerPrivate::slotAttachmentView()
{
const auto contents = selectedContents();
for (KMime::Content *content : contents) {
attachmentView(content);
}
}
void ViewerPrivate::slotAttachmentProperties()
{
const auto contents = selectedContents();
if (contents.isEmpty()) {
return;
}
for (KMime::Content *content : contents) {
attachmentProperties(content);
}
}
void ViewerPrivate::attachmentProperties(KMime::Content *content)
{
MessageCore::AttachmentPropertiesDialog *dialog = new MessageCore::AttachmentPropertiesDialog(
content, mMainWindow);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
void ViewerPrivate::slotAttachmentCopy()
{
#ifndef QT_NO_CLIPBOARD
attachmentCopy(selectedContents());
#endif
}
void ViewerPrivate::attachmentCopy(const KMime::Content::List &contents)
{
#ifndef QT_NO_CLIPBOARD
if (contents.isEmpty()) {
return;
}
QList<QUrl> urls;
for (KMime::Content *content : contents) {
auto url = QUrl::fromLocalFile(mNodeHelper->writeNodeToTempFile(content));
if (!url.isValid()) {
continue;
}
urls.append(url);
}
if (urls.isEmpty()) {
return;
}
QMimeData *mimeData = new QMimeData;
mimeData->setUrls(urls);
QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
#endif
}
void ViewerPrivate::slotLevelQuote(int l)
{
if (mLevelQuote != l) {
mLevelQuote = l;
update(MimeTreeParser::Force);
}
}
void ViewerPrivate::slotHandleAttachment(int choice)
{
if (!mCurrentContent) {
return;
}
switch (choice) {
case Viewer::Delete:
deleteAttachment(mCurrentContent);
break;
case Viewer::Properties:
attachmentProperties(mCurrentContent);
break;
case Viewer::Save:
{
const bool isEncapsulatedMessage = mCurrentContent->parent() && mCurrentContent->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
KMime::Message::Ptr message(new KMime::Message);
message->setContent(mCurrentContent->parent()->bodyAsMessage()->encodedContent());
message->parse();
Akonadi::Item item;
item.setPayload<KMime::Message::Ptr>(message);
Akonadi::MessageFlags::copyMessageFlags(*message, item);
item.setMimeType(KMime::Message::mimeType());
QUrl url;
if (MessageViewer::Util::saveMessageInMboxAndGetUrl(url, Akonadi::Item::List() << item, mMainWindow)) {
showOpenAttachmentFolderWidget(QList<QUrl>() << url);
}
} else {
QList<QUrl> urlList;
if (Util::saveContents(mMainWindow, KMime::Content::List() << mCurrentContent, urlList)) {
showOpenAttachmentFolderWidget(urlList);
}
}
break;
}
case Viewer::OpenWith:
attachmentOpenWith(mCurrentContent);
break;
case Viewer::Open:
attachmentOpen(mCurrentContent);
break;
case Viewer::View:
attachmentView(mCurrentContent);
break;
case Viewer::Copy:
attachmentCopy(KMime::Content::List() << mCurrentContent);
break;
case Viewer::ScrollTo:
scrollToAttachment(mCurrentContent);
break;
case Viewer::ReplyMessageToAuthor:
replyMessageToAuthor(mCurrentContent);
break;
case Viewer::ReplyMessageToAll:
replyMessageToAll(mCurrentContent);
break;
}
}
void ViewerPrivate::replyMessageToAuthor(KMime::Content *atmNode)
{
if (atmNode) {
const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), false);
}
}
}
void ViewerPrivate::replyMessageToAll(KMime::Content *atmNode)
{
if (atmNode) {
const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage();
if (isEncapsulatedMessage) {
Q_EMIT replyMessageTo(atmNode->parent()->bodyAsMessage(), true);
}
}
}
void ViewerPrivate::slotSpeakText()
{
const QString text = mViewer->selectedText();
if (!text.isEmpty()) {
mTextToSpeechWidget->say(text);
}
}
QUrl ViewerPrivate::imageUrl() const
{
QUrl url;
if (mImageUrl.scheme() == QLatin1String("cid")) {
url = QUrl(MessageViewer::WebEngineEmbedPart::self()->contentUrl(mImageUrl.path()));
} else {
url = mImageUrl;
}
return url;
}
void ViewerPrivate::slotCopyImageLocation()
{
#ifndef QT_NO_CLIPBOARD
QApplication::clipboard()->setText(imageUrl().url());
#endif
}
void ViewerPrivate::slotCopySelectedText()
{
#ifndef QT_NO_CLIPBOARD
QString selection = mViewer->selectedText();
selection.replace(QChar::Nbsp, QLatin1Char(' '));
QApplication::clipboard()->setText(selection);
#endif
}
void ViewerPrivate::viewerSelectionChanged()
{
mActionCollection->action(QStringLiteral("kmail_copy"))->setEnabled(
!mViewer->selectedText().isEmpty());
}
void ViewerPrivate::selectAll()
{
mViewer->selectAll();
}
void ViewerPrivate::slotUrlCopy()
{
#ifndef QT_NO_CLIPBOARD
QClipboard *clip = QApplication::clipboard();
if (mClickedUrl.scheme() == QLatin1String("mailto")) {
// put the url into the mouse selection and the clipboard
const QString address = KEmailAddress::decodeMailtoUrl(mClickedUrl);
clip->setText(address, QClipboard::Clipboard);
clip->setText(address, QClipboard::Selection);
KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("Address copied to clipboard."));
} else {
// put the url into the mouse selection and the clipboard
- clip->setText(mClickedUrl.url(), QClipboard::Clipboard);
- clip->setText(mClickedUrl.url(), QClipboard::Selection);
+ const QString clickedUrl = mClickedUrl.url();
+ clip->setText(clickedUrl, QClipboard::Clipboard);
+ clip->setText(clickedUrl, QClipboard::Selection);
KPIM::BroadcastStatus::instance()->setStatusMsg(i18n("URL copied to clipboard."));
}
#endif
}
void ViewerPrivate::slotSaveMessage()
{
if (!mMessageItem.hasPayload<KMime::Message::Ptr>()) {
if (mMessageItem.isValid()) {
qCWarning(MESSAGEVIEWER_LOG) << "Payload is not a MessagePtr!";
}
return;
}
if (!Util::saveMessageInMbox(Akonadi::Item::List() << mMessageItem, mMainWindow)) {
qCWarning(MESSAGEVIEWER_LOG) << "Impossible to save as mbox";
}
}
void ViewerPrivate::saveRelativePosition()
{
mViewer->saveRelativePosition();
}
//TODO(Andras) inline them
bool ViewerPrivate::htmlMail() const
{
if (mDisplayFormatMessageOverwrite == Viewer::UseGlobalSetting) {
return mHtmlMailGlobalSetting;
} else {
return mDisplayFormatMessageOverwrite == Viewer::Html;
}
}
bool ViewerPrivate::htmlLoadExternal() const
{
if (!mNodeHelper || !mMessage) {
return mHtmlLoadExtOverride;
}
// when displaying an encrypted message, only load external resources on explicit request
if (mNodeHelper->overallEncryptionState(mMessage.data()) != MimeTreeParser::KMMsgNotEncrypted) {
return mHtmlLoadExtOverride;
}
return (mHtmlLoadExternalDefaultSetting && !mHtmlLoadExtOverride)
|| (!mHtmlLoadExternalDefaultSetting && mHtmlLoadExtOverride);
}
void ViewerPrivate::setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format)
{
mDisplayFormatMessageOverwrite = format;
// keep toggle display mode action state in sync.
if (mToggleDisplayModeAction) {
mToggleDisplayModeAction->setChecked(htmlMail());
}
}
bool ViewerPrivate::htmlMailGlobalSetting() const
{
return mHtmlMailGlobalSetting;
}
Viewer::DisplayFormatMessage ViewerPrivate::displayFormatMessageOverwrite() const
{
return mDisplayFormatMessageOverwrite;
}
void ViewerPrivate::setHtmlLoadExtDefault(bool loadExtDefault)
{
mHtmlLoadExternalDefaultSetting = loadExtDefault;
}
void ViewerPrivate::setHtmlLoadExtOverride(bool loadExtOverride)
{
mHtmlLoadExtOverride = loadExtOverride;
}
bool ViewerPrivate::htmlLoadExtOverride() const
{
return mHtmlLoadExtOverride;
}
void ViewerPrivate::setDecryptMessageOverwrite(bool overwrite)
{
mDecrytMessageOverwrite = overwrite;
}
bool ViewerPrivate::showSignatureDetails() const
{
return mShowSignatureDetails;
}
void ViewerPrivate::setShowSignatureDetails(bool showDetails)
{
mShowSignatureDetails = showDetails;
}
void ViewerPrivate::setShowEncryptionDetails(bool showEncDetails)
{
mShowEncryptionDetails = showEncDetails;
}
bool ViewerPrivate::showEncryptionDetails() const
{
return mShowEncryptionDetails;
}
void ViewerPrivate::scrollToAttachment(KMime::Content *node)
{
const QString indexStr = node->index().toString();
// The anchors for this are created in ObjectTreeParser::parseObjectTree()
mViewer->scrollToAnchor(QLatin1String("attachmentDiv") + indexStr);
// Remove any old color markings which might be there
const KMime::Content *root = node->topLevel();
const int totalChildCount = Util::allContents(root).size();
for (int i = 0; i < totalChildCount + 1; ++i) {
//Not optimal I need to optimize it. But for the moment it removes yellow mark
mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv%1").arg(i + 1));
mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv1.%1").arg(i + 1));
mViewer->removeAttachmentMarking(QStringLiteral("attachmentDiv2.%1").arg(i + 1));
}
// Don't mark hidden nodes, that would just produce a strange yellow line
if (mNodeHelper->isNodeDisplayedHidden(node)) {
return;
}
// Now, color the div of the attachment in yellow, so that the user sees what happened.
// We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser,
// find and modify that now.
mViewer->markAttachment(QLatin1String("attachmentDiv") + indexStr,
QStringLiteral("border:2px solid %1").arg(cssHelper()->pgpWarnColor().
name()));
}
void ViewerPrivate::setUseFixedFont(bool useFixedFont)
{
mUseFixedFont = useFixedFont;
if (mToggleFixFontAction) {
mToggleFixFontAction->setChecked(mUseFixedFont);
}
}
void ViewerPrivate::itemFetchResult(KJob *job)
{
if (job->error()) {
displaySplashPage(i18n("Message loading failed: %1.", job->errorText()));
} else {
Akonadi::ItemFetchJob *fetch = qobject_cast<Akonadi::ItemFetchJob *>(job);
Q_ASSERT(fetch);
if (fetch->items().isEmpty()) {
displaySplashPage(i18n("Message not found."));
} else {
setMessageItem(fetch->items().constFirst());
}
}
}
void ViewerPrivate::slotItemChanged(const Akonadi::Item &item, const QSet<QByteArray> &parts)
{
if (item.id() != messageItem().id()) {
qCDebug(MESSAGEVIEWER_LOG) << "Update for an already forgotten item. Weird.";
return;
}
if (parts.contains("PLD:RFC822")) {
setMessageItem(item, MimeTreeParser::Force);
}
}
void ViewerPrivate::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &, const Akonadi::Collection &)
{
// clear the view after the current item has been moved somewhere else (e.g. to trash)
if (item.id() == messageItem().id()) {
slotClear();
}
}
void ViewerPrivate::slotClear()
{
q->clear(MimeTreeParser::Force);
Q_EMIT itemRemoved();
}
void ViewerPrivate::slotMessageRendered()
{
if (!mMessageItem.isValid()) {
return;
}
/**
* This slot might be called multiple times for the same message if
* some asynchronous mementos are involved in rendering. Therefor we
* have to make sure we execute the MessageLoadedHandlers only once.
*/
if (mMessageItem.id() == mPreviouslyViewedItem) {
return;
}
mPreviouslyViewedItem = mMessageItem.id();
for (AbstractMessageLoadedHandler *handler : qAsConst(mMessageLoadedHandlers)) {
handler->setItem(mMessageItem);
}
}
void ViewerPrivate::setZoomFactor(qreal zoomFactor)
{
mZoomActionMenu->setWebViewerZoomFactor(zoomFactor);
}
void ViewerPrivate::goOnline()
{
Q_EMIT makeResourceOnline(Viewer::AllResources);
}
void ViewerPrivate::goResourceOnline()
{
Q_EMIT makeResourceOnline(Viewer::SelectedResource);
}
void ViewerPrivate::slotSaveMessageDisplayFormat()
{
if (mMessageItem.isValid()) {
MessageViewer::ModifyMessageDisplayFormatJob *job
= new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this);
job->setMessageFormat(displayFormatMessageOverwrite());
job->setMessageItem(mMessageItem);
job->setRemoteContent(htmlLoadExtOverride());
job->start();
}
}
void ViewerPrivate::slotResetMessageDisplayFormat()
{
if (mMessageItem.isValid()) {
if (mMessageItem.hasAttribute<MessageViewer::MessageDisplayFormatAttribute>()) {
MessageViewer::ModifyMessageDisplayFormatJob *job
= new MessageViewer::ModifyMessageDisplayFormatJob(mSession, this);
job->setMessageItem(mMessageItem);
job->setResetFormat(true);
job->start();
}
}
}
void ViewerPrivate::slotMessageMayBeAScam()
{
if (mMessageItem.isValid()) {
if (mMessageItem.hasAttribute<MessageViewer::ScamAttribute>()) {
const MessageViewer::ScamAttribute *const attr
= mMessageItem.attribute<MessageViewer::ScamAttribute>();
if (attr && !attr->isAScam()) {
return;
}
}
if (mMessageItem.hasPayload<KMime::Message::Ptr>()) {
KMime::Message::Ptr message = mMessageItem.payload<KMime::Message::Ptr>();
const QString email
= QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString(
false)));
const QStringList lst
= MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList();
if (lst.contains(email)) {
return;
}
}
}
mScamDetectionWarning->slotShowWarning();
}
void ViewerPrivate::slotMessageIsNotAScam()
{
if (mMessageItem.isValid()) {
MessageViewer::ScamAttribute *attr = mMessageItem.attribute<MessageViewer::ScamAttribute>(
Akonadi::Item::AddIfMissing);
attr->setIsAScam(false);
Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob(mMessageItem, mSession);
modify->setIgnorePayload(true);
modify->disableRevisionCheck();
connect(modify, &KJob::result, this, &ViewerPrivate::slotModifyItemDone);
}
}
void ViewerPrivate::slotModifyItemDone(KJob *job)
{
if (job && job->error()) {
qCWarning(MESSAGEVIEWER_LOG) << " Error trying to change attribute:" << job->errorText();
}
}
void ViewerPrivate::saveMainFrameScreenshotInFile(const QString &filename)
{
mViewer->saveMainFrameScreenshotInFile(filename);
}
void ViewerPrivate::slotAddToWhiteList()
{
if (mMessageItem.isValid()) {
if (mMessageItem.hasPayload<KMime::Message::Ptr>()) {
KMime::Message::Ptr message = mMessageItem.payload<KMime::Message::Ptr>();
const QString email
= QLatin1String(KEmailAddress::firstEmailAddress(message->from()->as7BitString(
false)));
QStringList lst
= MessageViewer::MessageViewerSettings::self()->scamDetectionWhiteList();
if (lst.contains(email)) {
return;
}
lst << email;
MessageViewer::MessageViewerSettings::self()->setScamDetectionWhiteList(lst);
MessageViewer::MessageViewerSettings::self()->save();
}
}
}
void ViewerPrivate::slotMailTrackingFound(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &blacklist)
{
mMailTrackingWarning->addTracker(blacklist);
}
void ViewerPrivate::slotFormSubmittedForbidden()
{
mSubmittedFormWarning->showWarning();
}
void ViewerPrivate::addHelpTextAction(QAction *act, const QString &text)
{
act->setStatusTip(text);
act->setToolTip(text);
act->setWhatsThis(text);
}
void ViewerPrivate::slotRefreshMessage(const Akonadi::Item &item)
{
if (item.id() == mMessageItem.id()) {
setMessageItem(item, MimeTreeParser::Force);
}
}
void ViewerPrivate::slotServiceUrlSelected(
PimCommon::ShareServiceUrlManager::ServiceType serviceType)
{
const QUrl url = mShareServiceManager->generateServiceUrl(mClickedUrl.toString(),
QString(), serviceType);
mShareServiceManager->openUrl(url);
}
QList<QAction *> ViewerPrivate::interceptorUrlActions(
const WebEngineViewer::WebHitTestResult &result) const
{
return mViewer->interceptorUrlActions(result);
}
void ViewerPrivate::setPrintElementBackground(bool printElementBackground)
{
mViewer->setPrintElementBackground(printElementBackground);
}
void ViewerPrivate::slotToggleEmoticons()
{
mForceEmoticons = !mForceEmoticons;
//Save value
MessageViewer::MessageViewerSettings::self()->setShowEmoticons(mForceEmoticons);
headerStylePlugin()->headerStyle()->setShowEmoticons(mForceEmoticons);
update(MimeTreeParser::Force);
}
void ViewerPrivate::slotZoomChanged(qreal zoom)
{
mViewer->slotZoomChanged(zoom);
const qreal zoomFactor = zoom * 100;
MessageViewer::MessageViewerSettings::self()->setZoomFactor(zoomFactor);
Q_EMIT zoomChanged(zoomFactor);
}
+
+void ViewerPrivate::updateShowMultiMessagesButton(bool enablePreviousButton, bool enableNextButton)
+{
+ mShowNextMessageWidget->updateButton(enablePreviousButton, enableNextButton);
+}
diff --git a/messageviewer/src/viewer/viewer_p.h b/messageviewer/src/viewer/viewer_p.h
index eecf34b6..6a896e73 100644
--- a/messageviewer/src/viewer/viewer_p.h
+++ b/messageviewer/src/viewer/viewer_p.h
@@ -1,701 +1,711 @@
/*
Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#ifndef MAILVIEWER_P_H
#define MAILVIEWER_P_H
#include "messageviewer_private_export.h"
#include <MimeTreeParser/NodeHelper>
-#include "config-messageviewer.h"
#include "viewer.h" //not so nice, it is actually for the enums from MailViewer
#include "PimCommon/ShareServiceUrlManager"
#include "messageviewer/viewerplugininterface.h"
#include <WebEngineViewer/CheckPhishingUrlJob>
#include <AkonadiCore/item.h>
#include <AkonadiCore/monitor.h>
#include <AkonadiCore/session.h>
#include <kio/job.h>
#include <KMime/Message>
#include <kservice.h>
#include <ksharedconfig.h>
#include <QPointer>
#include <QUrl>
#include <viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h>
#include <QObject>
#include <QTimer>
namespace KIO {
class Job;
}
class QAction;
class KActionCollection;
class KSelectAction;
class KToggleAction;
class QMenu;
class KActionMenu;
class QPoint;
class QSplitter;
class QModelIndex;
class QPrinter;
namespace KPIMTextEdit {
class SlideContainer;
class TextToSpeechWidget;
}
namespace PimCommon {
class ShareServiceUrlManager;
}
namespace MimeTreeParser {
class ObjectTreeParser;
}
namespace WebEngineViewer {
class WebHitTestResult;
class FindBarWebEngineView;
class ZoomActionMenu;
class LocalDataBaseManager;
}
namespace MessageViewer {
class AttachmentStrategy;
class HeaderStylePlugin;
class HtmlWriter;
class CSSHelper;
class MailWebEngineView;
class WebEnginePartHtmlWriter;
class HtmlStatusBar;
class ScamDetectionWarningWidget;
class MimePartTreeView;
class OpenAttachmentFolderWidget;
class HeaderStyleMenuManager;
class ViewerPluginToolManager;
class ViewerPluginInterface;
class SubmittedFormWarningWidget;
class MailSourceWebEngineViewer;
class MailTrackingWarningWidget;
+class ShowNextMessageWidget;
/**
\brief Private class for the Viewer, the main widget in the messageviewer library.
This class creates all subwidgets, like the MailWebView, the HtmlStatusBar and the FindBarMailWebView.
Also, ViewerPrivate creates and exposes all actions.
\par Displaying a message
Before displaying a message, a message needs to be set. This can be done in two ways, with
setMessageItem() and with setMessage(). setMessageItem() is the preferred way, as the viewer can
then remember the Akonadi::Item belonging to the message. The Akonadi::Item is needed when modifying
the message, for example when editing or deleting an attachment.
Sometimes passing an Akonadi::Item to the viewer is not possible, for example when double-clicking
an attached message, in which case a new KMime::Message is constructed out of the attachment, and a
separate window is opened for it. In this case, the KMime::Message has no associated Akonadi::Item.
If there is an Akonadi::Item available, it will be monitored for changes and the viewer
automatically updated on external changes.
Once a message is set, update() is called. update() can also be called after the message has already
been displayed. As an example, this is the case when the user decides to decrypt the message. The
decryption can happen async, and once the decryption is finished, update() is called to display the
now decrypted content. See the documentation of MimeTreeParser::ObjectTreeParser on how exactly decryption is
handled.
update() is just a thin wrapper that calls updateReaderWin(). The only difference is that update()
has a timer that prevents too many slow calls to updateReaderWin() in a short time frame.
updateReaderWin() again is only a thin wrapper that resets some state and then calls
displayMessage().
displayMessage() itself is again a thin wrapper, which starts the HtmlWriter and then calls
parseMsg().
-Finally, parseMsg() does the real work. It uses MimeTreeParser::ObjectTreeParser ::parseObjectTree() to let the
+Finally, parseMsg() does the real work. It uses MimeTreeParser::ObjectTreeParser parseObjectTree() to let the
MimeTreeParser::ObjectTreeParser parse the message and generate the HTML code for it.
As mentioned before, it can happen that the MimeTreeParser::ObjectTreeParser needs to do some operation that happens
async, for example decrypting. In this case, the MimeTreeParser::ObjectTreeParser will create a BodyPartMemento,
which basically is a wrapper around the job that does the async operation. Once the async operation
is finished. the BodyPartMemento will trigger an update() of ViewerPrivate, so that
-MimeTreeParser::ObjectTreeParser ::parseObjectTree() gets called again and the MimeTreeParser::ObjectTreeParser then can generate
+MimeTreeParser::ObjectTreeParser parseObjectTree() gets called again and the MimeTreeParser::ObjectTreeParser then can generate
HTML which has the decrypted content of the message. Again, see the documentation of MimeTreeParser::ObjectTreeParser for the details.
Additionally, parseMsg() does some evil hack for saving unencrypted messages should the config
option for that be set.
\par Displaying a MIME part of the message
The viewer can show only a part of the message, for example by clicking on a MIME part in the
message structure viewer or by double-clicking an attached message. In this case, setMessagePart()
is called. There are two of these functions. One even has special handling for images, special
handling for binary attachments and special handling of attached messages. In the last case, a new
KMime::Message is constructed and set as the main message with setMessage().
\par Attachment Handling
Some of those actions are actions that operate on a single attachment. For those, there is usually
a slot, like slotAttachmentCopy(). These actions are triggered from the attachment context menu,
which is shown in showAttachmentPopup(). The actions are connected to slotHandleAttachment() when
they are activated.
The action to edit an attachment uses the EditorWatcher to detect when editing with an external
editor is finished. Upon finishing, slotAttachmentEditDone() is called, which then creates an
ItemModifyJob to store the changes of the attachment. A map of currently active EditorWatcher and
their KMime::Content is available in mEditorWatchers.
For most attachment actions, the attachment is first written to a temp file. The action is then
executed on this temp file. Writing the attachment to a temp file is done with
MimeTreeParser::NodeHelper::writeNodeToTempFile(). This method is called before opening or copying an attachment or
when rendering the attachment list. The MimeTreeParser::ObjectTreeParser also calls MimeTreeParser::NodeHelper::writeNodeToTempFile()
in some places. Once the temp file is written, MimeTreeParser::NodeHelper::tempFileUrlFromNode() can be used to get
the file name of the temp file for a specific MIME part. This is for example used by the handler for
'attachment:' URLs, AttachmentURLHandler.
Since URLs for attachments are in the "attachment:" scheme, dragging them as-is to outside applications
wouldn't work, since other applications don't understand this scheme. Therefore, the viewer has
special handling for dragging URLs: In eventFilter(), drags are detected, and the URL handler is
called to deal with the drag. The attachment URL handler then starts a drag with the file:// URL
of the temp file of the attachment, which it gets with MimeTreeParser::NodeHelper::tempFileUrlFromNode().
TODO: How are attachment handled that are loaded on demand? How does prepareHandleAttachment() work?
TODO: This temp file handling is a big mess and could use a rewrite, especially in the face of load
on demand. There shouldn't be the need to write out tempfiles until really needed.
Some header styles display an attachment list in the header. The HTML code for the attachment list
cannot be generated by the HeaderStyle itself, since that does not know about all attachments.
Therefore, the attachment list needs to be created by ViewerPrivate. For this, the HeaderStyle
writes out a placeholder for the attachment list when it creates the HTML for the header. Once the
MimeTreeParser::ObjectTreeParser is finished with the message, injectAttachments() is called. injectAttachments()
searches for the placeholder and replaces that with the real HTML code for the attachments.
One of the attachment actions is to scoll to the attachment. That action is only available when
right-clicking the header. The action scrolls to the attachment in the body and draws a yellow frame
around the attachment. This is done in scrollToAttachment(). The attachment in the body and the div
which is used for the colored frame are both created by the MimeTreeParser::ObjectTreeParser .
\par Misc
ViewerPrivate holds the MimeTreeParser::NodeHelper, which is passed on to the MimeTreeParser::ObjectTreeParser when it needs it.
It also holds the HeaderStyle, HeaderStrategy, MimeTreeParser::AttachmentStrategy, CSSHelper, HtmlWriter and more,
some of them again passed to the MimeTreeParser::ObjectTreeParser when it needs it.
@author andras@kdab.net
*/
class MESSAGEVIEWER_TESTS_EXPORT ViewerPrivate : public QObject
{
Q_OBJECT
public:
ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection);
~ViewerPrivate() override;
/** Returns message part from given URL or null if invalid. The URL's path is a KMime::ContentIndex path, or an index for the extra nodes,
followed by : and the ContentIndex path. */
KMime::Content *nodeFromUrl(const QUrl &url) const;
/** Open the attachment pointed to the node.
- * @param fileName - if not empty, use this file to load the attachment content
+ * @param node the node
+ * @param url - if not empty, use this file to load the attachment content
*/
void openAttachment(KMime::Content *node, const QUrl &url);
- /** Delete the attachment the @param node points to. Returns false if the user
+ /** Delete the attachment the @p node points to. Returns false if the user
cancelled the deletion, true in all other cases (including failure to delete
- the attachment!) */
+ the attachment!)
+ * @param node the node
+ * @param showWarning whether some warning should be shown
+ */
bool deleteAttachment(KMime::Content *node, bool showWarning = true);
void attachmentProperties(KMime::Content *node);
void attachmentCopy(const KMime::Content::List &contents);
void scrollToAnchor(const QString &anchor);
void showAttachmentPopup(KMime::Content *node, const QString &name, const QPoint &p);
/**
* Sets the current attachment ID and the current attachment temporary filename
* to the given values.
* Call this so that slotHandleAttachment() knows which attachment to handle.
*/
void prepareHandleAttachment(KMime::Content *node);
QString createAtmFileLink(const QString &atmFileName) const;
KService::Ptr getServiceOffer(KMime::Content *content);
KMime::Content::List selectedContents();
void attachmentOpenWith(KMime::Content *node, const KService::Ptr &offer = KService::Ptr());
void attachmentOpen(KMime::Content *node);
/** Return the HtmlWriter connected to the MailWebView we use */
HtmlWriter *htmlWriter() const;
HeaderStylePlugin *headerStylePlugin() const;
CSSHelper *cssHelper() const;
MimeTreeParser::NodeHelper *nodeHelper() const;
Viewer *viewer() const;
Akonadi::Item messageItem() const;
KMime::Message::Ptr message() const;
- /** Returns whether the message should be decryted. */
+ /** Returns whether the message should be decrypted. */
bool decryptMessage() const;
/** Display a generic HTML splash page instead of a message. */
void displaySplashPage(const QString &templateName, const QVariantHash &data, const QByteArray &domain = QByteArray());
void displaySplashPage(const QString &message);
/** Enable the displaying of messages again after an splash (or other) page was displayed */
void enableMessageDisplay();
/** Feeds the HTML viewer with the contents of the given message.
HTML begin/end parts are written around the message. */
void displayMessage();
/** Parse the given content and generate HTML out of it for display */
void parseContent(KMime::Content *content);
/** Creates a nice mail header depending on the current selected
header style. */
QString writeMessageHeader(KMime::Message *aMsg, KMime::Content *vCardNode = nullptr, bool topLevel = false);
/** show window containing information about a vCard. */
void showVCard(KMime::Content *msgPart);
void saveMainFrameScreenshotInFile(const QString &filename);
private:
/** HTML initialization. */
void initHtmlWidget();
void createOpenWithMenu(QMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent);
public:
void itemFetchResult(KJob *job);
/** Read settings from app's config file. */
void readConfig();
/** Write settings to app's config file. Calls sync() if withSync is true. */
void writeConfig(bool withSync = true);
/** Get/set the message attachment strategy. */
const AttachmentStrategy *attachmentStrategy() const;
void setAttachmentStrategy(const AttachmentStrategy *strategy);
/** Get selected override character encoding.
@return The encoding selected by the user or an empty string if auto-detection
is selected. */
QString overrideEncoding() const;
/** Set the override character encoding. */
void setOverrideEncoding(const QString &encoding);
/** Set printing mode */
void setPrinting(bool enable);
bool printingMode() const;
/** Print message. */
void printMessage(const Akonadi::Item &msg);
void printPreviewMessage(const Akonadi::Item &message);
void resetStateForNewMessage();
void setMessageInternal(const KMime::Message::Ptr &message, MimeTreeParser::UpdateMode updateMode);
/** Set the Akonadi item that will be displayed.
* @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data),
* an empty page is shown.
* @param updateMode - update the display immediately or not. See MailViewer::UpdateMode.
*/
void setMessageItem(const Akonadi::Item &item, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
/** Set the message that shall be shown.
* @param msg - the message to be shown. If 0, an empty page is displayed.
* @param updateMode - update the display immediately or not. See MailViewer::UpdateMode.
*/
void setMessage(const KMime::Message::Ptr &msg, MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
/** Instead of settings a message to be shown sets a message part
to be shown */
void setMessagePart(KMime::Content *node);
/** Show or hide the Mime Tree Viewer if configuration
is set to smart mode. */
void showHideMimeTree();
/** View message part of type message/RFC822 in extra viewer window. */
void atmViewMsg(const KMime::Message::Ptr &message);
void adjustLayout();
void createWidgets();
void createActions();
void showContextMenu(KMime::Content *content, const QPoint &point);
KToggleAction *actionForAttachmentStrategy(const AttachmentStrategy *);
/** Read override codec from configuration */
void readGlobalOverrideCodec();
/** Get codec corresponding to the currently selected override character encoding.
@return The override codec or 0 if auto-detection is selected. */
const QTextCodec *overrideCodec() const;
QString renderAttachments(KMime::Content *node, const QColor &bgColor) const;
KMime::Content *findContentByType(KMime::Content *content, const QByteArray &type); //TODO(Andras) move to MimeTreeParser::NodeHelper
/** Return a QTextCodec for the specified charset.
* This function is a bit more tolerant, than QTextCodec::codecForName */
static const QTextCodec *codecForName(const QByteArray &_str); //TODO(Andras) move to a utility class?
/** Saves the relative position of the scroll view. Call this before calling update()
if you want to preserve the current view. */
void saveRelativePosition();
bool htmlMail() const;
bool htmlLoadExternal() const;
bool htmlMailGlobalSetting() const;
/** Get the html override setting */
Viewer::DisplayFormatMessage displayFormatMessageOverwrite() const;
/** Override default html mail setting */
void setDisplayFormatMessageOverwrite(Viewer::DisplayFormatMessage format);
/** Get the load external references override setting */
bool htmlLoadExtOverride() const;
/** Default behavior for loading external references.
* Use this for specifying the external reference loading behavior as
* specified in the user settings.
* @see setHtmlLoadExtOverride
*/
void setHtmlLoadExtDefault(bool loadExtDefault);
/** Override default load external references setting
* @warning This must only be called when the user has explicitly
* been asked to retrieve external references!
* @see setHtmlLoadExtDefault
*/
void setHtmlLoadExtOverride(bool loadExtOverride);
/** Enforce message decryption. */
void setDecryptMessageOverwrite(bool overwrite = true);
/** Show signature details. */
bool showSignatureDetails() const;
/** Show signature details. */
void setShowSignatureDetails(bool showDetails = true);
/* show or hide encryption details */
void setShowEncryptionDetails(bool showEncDetails);
bool showEncryptionDetails() const;
void scrollToAttachment(KMime::Content *node);
void setUseFixedFont(bool useFixedFont);
void attachmentView(KMime::Content *atmNode);
void setZoomFactor(qreal zoomFactor);
void goOnline();
void goResourceOnline();
void showOpenAttachmentFolderWidget(const QList<QUrl> &urls);
bool mimePartTreeIsEmpty() const;
void setPluginName(const QString &pluginName);
QList<QAction *> viewerPluginActionList(
MessageViewer::ViewerPluginInterface::SpecificFeatureTypes features);
QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
void setPrintElementBackground(bool printElementBackground);
bool showEmoticons() const;
void checkPhishingUrl();
void executeRunner(const QUrl &url);
QUrl imageUrl() const;
Q_REQUIRED_RESULT qreal webViewZoomFactor() const;
void setWebViewZoomFactor(qreal factor);
+ void recreateCssHelper();
+ void hasMultiMessages(bool messages);
+ void updateShowMultiMessagesButton(bool enablePreviousButton, bool enableNextButton);
private Q_SLOTS:
void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
void slotModifyItemDone(KJob *job);
void slotMessageMayBeAScam();
void slotMessageIsNotAScam();
void slotAddToWhiteList();
void slotFormSubmittedForbidden();
void slotMailTrackingFound(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &blacklist);
void slotItemChanged(const Akonadi::Item &item, const QSet<QByteArray> &partIdentifiers);
void slotItemMoved(const Akonadi::Item &, const Akonadi::Collection &, const Akonadi::Collection &);
void itemModifiedResult(KJob *job);
void slotClear();
void slotMessageRendered();
void slotOpenWithAction(QAction *act);
void slotOpenWithActionCurrentContent(QAction *act);
void slotOpenWithDialog();
void slotOpenWithDialogCurrentContent();
void saveSplitterSizes() const;
void slotRefreshMessage(const Akonadi::Item &item);
void slotServiceUrlSelected(PimCommon::ShareServiceUrlManager::ServiceType serviceType);
void slotStyleChanged(MessageViewer::HeaderStylePlugin *plugin);
void slotStyleUpdated();
void slotWheelZoomChanged(int numSteps);
void slotOpenInBrowser();
void slotExportHtmlPageFailed();
void slotExportHtmlPageSuccess(const QString &filename);
void slotHandlePagePrinted(bool result);
void slotToggleEmoticons();
public Q_SLOTS:
/** An URL has been activate with a click. */
void slotUrlOpen(const QUrl &url = QUrl());
void slotOpenUrl();
/** The mouse has moved on or off an URL. */
void slotUrlOn(const QString &link);
/** The user presses the right mouse button on an URL. */
void slotUrlPopup(const WebEngineViewer::WebHitTestResult &result);
/** The user selected "Find" from the menu. */
void slotFind();
/** The user toggled the "Fixed Font" flag from the view menu. */
void slotToggleFixedFont();
void slotToggleMimePartTree();
/** Show the message source */
void slotShowMessageSource();
/** Refresh the reader window */
void updateReaderWin();
void slotMimePartSelected(const QModelIndex &index);
void slotIconicAttachments();
void slotSmartAttachments();
void slotInlineAttachments();
void slotHideAttachments();
void slotHeaderOnlyAttachments();
/** Some attachment operations. */
void slotDelayedResize();
/** Print message. Called on as a response of finished() signal of mPartHtmlWriter
after rendering is finished.
In the very end it deletes the KMReaderWin window that was created
for the purpose of rendering. */
void slotPrintMessage();
void slotPrintPreview();
void slotSetEncoding();
void executeCustomScriptsAfterLoading();
void slotSettingsChanged();
void slotMimeTreeContextMenuRequested(const QPoint &pos);
void slotAttachmentOpenWith();
void slotAttachmentOpen();
void slotAttachmentSaveAs();
void slotAttachmentSaveAll();
void slotAttachmentView();
void slotAttachmentProperties();
void slotAttachmentCopy();
void slotLevelQuote(int l);
/** Toggle display mode between HTML and plain text. */
void slotToggleHtmlMode();
void slotLoadExternalReference();
/**
* Does an action for the current attachment.
* The action is defined by the KMHandleAttachmentCommand::AttachmentAction
* enum.
* prepareHandleAttachment() needs to be called before calling this to set the
* correct attachment ID.
*/
void slotHandleAttachment(int action);
/** Copy the selected text to the clipboard */
void slotCopySelectedText();
void viewerSelectionChanged();
/** Select message body. */
void selectAll();
/** Copy URL in mUrlCurrent to clipboard. Removes "mailto:" at
beginning of URL before copying. */
void slotUrlCopy();
void slotSaveMessage();
/** Re-parse the current message. */
void update(MimeTreeParser::UpdateMode updateMode = MimeTreeParser::Delayed);
void slotSpeakText();
void slotCopyImageLocation();
void slotSaveMessageDisplayFormat();
void slotResetMessageDisplayFormat();
void slotGeneralFontChanged();
Q_SIGNALS:
void showStatusBarMessage(const QString &message);
void popupMenu(const Akonadi::Item &msg, const QUrl &url, const QUrl &imageUrl, const QPoint &mousePos);
void displayPopupMenu(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &mousePos);
void urlClicked(const Akonadi::Item &msg, const QUrl &url);
void requestConfigSync();
void showReader(KMime::Content *aMsgPart, bool aHTML, const QString &encoding);
void showMessage(const KMime::Message::Ptr &message, const QString &encoding);
void replyMessageTo(const KMime::Message::Ptr &message, bool replyToAll);
void itemRemoved();
void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode);
void changeDisplayMail(Viewer::DisplayFormatMessage, bool);
void moveMessageToTrash();
void pageIsScrolledToBottom(bool);
void printingFinished();
void zoomChanged(qreal zoomFactor);
+ void showNextMessage();
+ void showPreviousMessage();
private:
QString attachmentHtml() const;
Akonadi::Relation relatedNoteRelation() const;
void addHelpTextAction(QAction *act, const QString &text);
void readGravatarConfig();
void replyMessageToAuthor(KMime::Content *atmNode);
void replyMessageToAll(KMime::Content *atmNode);
bool urlIsAMalwareButContinue();
void slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status);
void slotDelayPrintPreview();
void applyZoomValue(qreal factor, bool saveConfig = true);
void slotZoomChanged(qreal zoom);
MimeTreeParser::NodeHelper *mNodeHelper = nullptr;
bool mHtmlMailGlobalSetting;
bool mHtmlLoadExternalDefaultSetting;
bool mHtmlLoadExtOverride;
public:
KMime::Message::Ptr mMessage; //the current message, if it was set manually
Akonadi::Item mMessageItem; //the message item from Akonadi
// widgets:
QSplitter *mSplitter = nullptr;
QWidget *mBox = nullptr;
HtmlStatusBar *mColorBar = nullptr;
#ifndef QT_NO_TREEVIEW
MimePartTreeView *mMimePartTree = nullptr;
#endif
MailWebEngineView *mViewer = nullptr;
WebEngineViewer::FindBarWebEngineView *mFindBar = nullptr;
const AttachmentStrategy *mAttachmentStrategy = nullptr;
QTimer mUpdateReaderWinTimer;
QTimer mResizeTimer;
QString mOverrideEncoding;
QString mOldGlobalOverrideEncoding; // used to detect changes of the global override character encoding
QString mPicsPath;
/// This is true if the viewer currently is displaying a message. Can be false, for example when
/// the splash/busy page is displayed.
bool mMsgDisplay;
CSSHelper *mCSSHelper = nullptr;
bool mUseFixedFont;
bool mPrinting;
QWidget *mMainWindow = nullptr;
KActionCollection *mActionCollection = nullptr;
QAction *mCopyAction = nullptr;
QAction *mCopyURLAction = nullptr;
QAction *mUrlOpenAction = nullptr;
QAction *mSelectAllAction = nullptr;
QAction *mScrollUpAction = nullptr;
QAction *mScrollDownAction = nullptr;
QAction *mScrollUpMoreAction = nullptr;
QAction *mScrollDownMoreAction = nullptr;
QAction *mViewSourceAction = nullptr;
QAction *mSaveMessageAction = nullptr;
QAction *mFindInMessageAction = nullptr;
QAction *mSaveMessageDisplayFormat = nullptr;
QAction *mResetMessageDisplayFormat = nullptr;
KToggleAction *mDisableEmoticonAction = nullptr;
KToggleAction *mHeaderOnlyAttachmentsAction = nullptr;
KSelectAction *mSelectEncodingAction = nullptr;
KToggleAction *mToggleFixFontAction = nullptr;
KToggleAction *mToggleDisplayModeAction = nullptr;
KToggleAction *mToggleMimePartTreeAction = nullptr;
QAction *mSpeakTextAction = nullptr;
QAction *mCopyImageLocation = nullptr;
QUrl mHoveredUrl;
QUrl mClickedUrl;
QUrl mImageUrl;
QPoint mLastClickPosition;
bool mCanStartDrag;
HtmlWriter *mHtmlWriter = nullptr;
/** Used only to be able to connect and disconnect finished() signal
in printMsg() and slotPrintMsg() since mHtmlWriter points only to abstract non-QObject class. */
QPointer<WebEnginePartHtmlWriter> mPartHtmlWriter;
int mLevelQuote;
bool mDecrytMessageOverwrite = false;
bool mShowSignatureDetails = false;
bool mShowEncryptionDetails = false;
bool mForceEmoticons = true;
int mRecursionCountForDisplayMessage;
KMime::Content *mCurrentContent = nullptr;
KMime::Content *mMessagePartNode = nullptr;
QString mMessagePath;
QColor mForegroundError;
QColor mBackgroundError;
Viewer *const q;
Akonadi::Session *mSession = nullptr;
Akonadi::Monitor mMonitor;
QSet<AbstractMessageLoadedHandler *> mMessageLoadedHandlers;
Akonadi::Item::Id mPreviouslyViewedItem;
MessageViewer::ScamDetectionWarningWidget *mScamDetectionWarning = nullptr;
MessageViewer::OpenAttachmentFolderWidget *mOpenAttachmentFolderWidget = nullptr;
MessageViewer::SubmittedFormWarningWidget *mSubmittedFormWarning = nullptr;
MessageViewer::MailTrackingWarningWidget *mMailTrackingWarning = nullptr;
KPIMTextEdit::TextToSpeechWidget *mTextToSpeechWidget = nullptr;
Viewer::DisplayFormatMessage mDisplayFormatMessageOverwrite;
KPIMTextEdit::SlideContainer *mSliderContainer = nullptr;
PimCommon::ShareServiceUrlManager *mShareServiceManager = nullptr;
KActionMenu *mShareServiceUrlMenu = nullptr;
MessageViewer::HeaderStylePlugin *mHeaderStylePlugin = nullptr;
MessageViewer::HeaderStyleMenuManager *mHeaderStyleMenuManager = nullptr;
MessageViewer::ViewerPluginToolManager *mViewerPluginToolManager = nullptr;
WebEngineViewer::ZoomActionMenu *mZoomActionMenu = nullptr;
QPrinter *mCurrentPrinter = nullptr;
QList<QPointer<MessageViewer::MailSourceWebEngineViewer> > mListMailSourceViewer;
WebEngineViewer::LocalDataBaseManager *mPhishingDatabase = nullptr;
+ MessageViewer::ShowNextMessageWidget *mShowNextMessageWidget = nullptr;
};
}
#endif
diff --git a/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp b/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp
index 71cc8961..f53b0ef3 100644
--- a/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp
+++ b/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "blockexternalresourcesurlinterceptor.h"
#include <QWebEngineUrlRequestInfo>
using namespace MessageViewer;
BlockExternalResourcesUrlInterceptor::BlockExternalResourcesUrlInterceptor(QObject *parent)
: WebEngineViewer::NetworkPluginUrlInterceptorInterface(parent)
{
}
BlockExternalResourcesUrlInterceptor::~BlockExternalResourcesUrlInterceptor()
{
}
bool BlockExternalResourcesUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
const QString scheme = info.requestUrl().scheme();
if (scheme == QStringLiteral("data")
|| scheme == QStringLiteral("file")) {
return false;
}
const QWebEngineUrlRequestInfo::ResourceType resourceType = info.resourceType();
const QWebEngineUrlRequestInfo::NavigationType navigationType = info.navigationType();
if (resourceType == QWebEngineUrlRequestInfo::ResourceTypeMedia
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypePing
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypePrefetch
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeFavicon
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeXhr
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeObject
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeScript
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeServiceWorker
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeSharedWorker
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeWorker
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeSubResource
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypePluginResource
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeCspReport
|| resourceType == QWebEngineUrlRequestInfo::ResourceTypeUnknown) {
return true;
} else if (navigationType == QWebEngineUrlRequestInfo::NavigationTypeFormSubmitted) {
Q_EMIT formSubmittedForbidden();
return true;
} else if (navigationType == QWebEngineUrlRequestInfo::NavigationTypeReload
|| navigationType == QWebEngineUrlRequestInfo::NavigationTypeTyped
|| navigationType == QWebEngineUrlRequestInfo::NavigationTypeBackForward
|| navigationType == QWebEngineUrlRequestInfo::NavigationTypeOther) {
return true;
}
return false;
}
diff --git a/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h b/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h
index 77a7063e..b99d2b7c 100644
--- a/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h
+++ b/messageviewer/src/viewer/webengine/blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef BLOCKEXTERNALRESOURCESURLINTERCEPTOR_H
#define BLOCKEXTERNALRESOURCESURLINTERCEPTOR_H
#include <WebEngineViewer/NetworkPluginUrlInterceptorInterface>
namespace MessageViewer {
class BlockExternalResourcesUrlInterceptor : public WebEngineViewer::
NetworkPluginUrlInterceptorInterface
{
Q_OBJECT
public:
explicit BlockExternalResourcesUrlInterceptor(QObject *parent = nullptr);
~BlockExternalResourcesUrlInterceptor() override;
- bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
+ Q_REQUIRED_RESULT bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
void setAllowExternalContent(bool b);
- bool allowExternalContent() const;
+ Q_REQUIRED_RESULT bool allowExternalContent() const;
Q_SIGNALS:
void formSubmittedForbidden();
};
}
#endif // BLOCKEXTERNALRESOURCESURLINTERCEPTOR_H
diff --git a/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.cpp b/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.cpp
index a1c44463..786b323f 100644
--- a/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.cpp
+++ b/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.cpp
@@ -1,77 +1,77 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "blockmailtrackingurlinterceptor.h"
#include <QWebEngineUrlRequestInfo>
#include <QDebug>
using namespace MessageViewer;
BlockMailTrackingUrlInterceptor::BlockMailTrackingUrlInterceptor(QObject *parent)
: WebEngineViewer::NetworkPluginUrlInterceptorInterface(parent)
{
qRegisterMetaType<MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList>();
initializeList();
}
-MessageViewer::BlockMailTrackingUrlInterceptor::~BlockMailTrackingUrlInterceptor()
+BlockMailTrackingUrlInterceptor::~BlockMailTrackingUrlInterceptor()
{
}
bool BlockMailTrackingUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
const QUrl urlRequestUrl(info.requestUrl());
for (int i = 0; i < mBackList.size(); ++i) {
if (urlRequestUrl.url().startsWith(mBackList.at(i).mCompanyUrl)) {
Q_EMIT mailTrackingFound(mBackList.at(i));
return true;
}
}
return false;
}
void BlockMailTrackingUrlInterceptor::initializeList()
{
mBackList = {
{QStringLiteral("Sidekick"), QStringLiteral("t.signaux"), QStringLiteral("http://getsidekick.com") },
{QStringLiteral("Sidekick"), QStringLiteral("t.sidekickopen"), QStringLiteral("http://getsidekick.com")},
{QStringLiteral("Sidekick"), QStringLiteral("t.sigopn"), QStringLiteral("http://getsidekick.com")},
{QStringLiteral("Banana Tag"), QStringLiteral("bl-1.com"), QStringLiteral("http://bananatag.com")},
{QStringLiteral("Boomerang"), QStringLiteral("mailstat.us/tr"), QStringLiteral("http://boomeranggmail.com")},
- {QStringLiteral("Cirrus Inisght"), QStringLiteral("tracking.cirrusinsight.com"), QStringLiteral("http://cirrusinsight.com")},
+ {QStringLiteral("Cirrus Insight"), QStringLiteral("tracking.cirrusinsight.com"), QStringLiteral("http://cirrusinsight.com")},
{QStringLiteral("Yesware"), QStringLiteral("app.yesware.com"), QStringLiteral("http://yesware.com")},
{QStringLiteral("Yesware"), QStringLiteral("t.yesware.com"), QStringLiteral("http://yesware.com")},
{QStringLiteral("Streak"), QStringLiteral("mailfoogae.appspot.com"), QStringLiteral("http://streak.com")},
{QStringLiteral("LaunchBit"), QStringLiteral("launchbit.com/taz-pixel"), QStringLiteral("http://launchbit.com")},
{QStringLiteral("MailChimp"), QStringLiteral("list-manage.com/track"), QStringLiteral("http://mailchimp.com")},
{QStringLiteral("Postmark"), QStringLiteral("cmail1.com/t"), QStringLiteral("http://postmarkapp.com")},
{QStringLiteral("iContact"), QStringLiteral("click.icptrack.com/icp/"), QStringLiteral("http://icontact.com")},
{QStringLiteral("Infusionsoft"), QStringLiteral("infusionsoft.com/app/emailOpened"), QStringLiteral("http://infusionsoft.com")},
{QStringLiteral("Intercom"), QStringLiteral("via.intercom.io/o"), QStringLiteral("http://intercom.io")},
{QStringLiteral("Mandrill"), QStringLiteral("mandrillapp.com/track"), QStringLiteral("http://mandrillapp.com")},
{QStringLiteral("Hubspot"), QStringLiteral("t.hsms06.com"), QStringLiteral("http://hubspot.com")},
{QStringLiteral("RelateIQ"), QStringLiteral("app.relateiq.com/t.png"), QStringLiteral("http://relateiq.com")},
{QStringLiteral("RJ Metrics"), QStringLiteral("go.rjmetrics.com"), QStringLiteral("http://rjmetrics.com")},
{QStringLiteral("Mixpanel"), QStringLiteral("api.mixpanel.com/track"), QStringLiteral("http://mixpanel.com")},
{QStringLiteral("Front App"), QStringLiteral("web.frontapp.com/api"), QStringLiteral("http://frontapp.com")},
{QStringLiteral("Mailtrack.io"), QStringLiteral("mailtrack.io/trace"), QStringLiteral("http://mailtrack.io")},
{QStringLiteral("ToutApp"), QStringLiteral("go.toutapp.com"), QStringLiteral("http://toutapp.com")},
{QStringLiteral("Outreach"), QStringLiteral("app.outreach.io"), QStringLiteral("http://outreach.io")}
};
}
diff --git a/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h b/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h
index df6b58dc..bfa7ba06 100644
--- a/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h
+++ b/messageviewer/src/viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef BLOCKMAILTRACKINGURLINTERCEPTOR_H
#define BLOCKMAILTRACKINGURLINTERCEPTOR_H
#include <WebEngineViewer/NetworkPluginUrlInterceptorInterface>
-
+#include "messageviewer_export.h"
#include <QVector>
namespace MessageViewer {
-class BlockMailTrackingUrlInterceptor : public WebEngineViewer::NetworkPluginUrlInterceptorInterface
+class MESSAGEVIEWER_EXPORT BlockMailTrackingUrlInterceptor : public WebEngineViewer::NetworkPluginUrlInterceptorInterface
{
Q_OBJECT
public:
struct MailTrackerBlackList
{
MailTrackerBlackList() = default;
MailTrackerBlackList(const QString &company, const QString &pattern, const QString &url)
: mCompanyName(company)
, mCompanyUrl(url)
, mPattern(pattern)
{
}
QString mCompanyName;
QString mCompanyUrl;
QString mPattern;
};
explicit BlockMailTrackingUrlInterceptor(QObject *parent = nullptr);
~BlockMailTrackingUrlInterceptor() override;
- bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
+ Q_REQUIRED_RESULT bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
Q_SIGNALS:
void mailTrackingFound(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &);
private:
void initializeList();
QVector<MailTrackerBlackList> mBackList;
};
}
Q_DECLARE_TYPEINFO(MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList)
#endif // BLOCKMAILTRACKINGURLINTERCEPTOR_H
diff --git a/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp b/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp
index b0947275..4c9605dd 100644
--- a/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp
+++ b/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.cpp
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "cidreferencesurlinterceptor.h"
#include "htmlwriter/webengineembedpart.h"
#include <QWebEngineUrlRequestInfo>
using namespace MessageViewer;
CidReferencesUrlInterceptor::CidReferencesUrlInterceptor(QObject *parent)
: WebEngineViewer::NetworkPluginUrlInterceptorInterface(parent)
{
}
CidReferencesUrlInterceptor::~CidReferencesUrlInterceptor()
{
}
bool CidReferencesUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
const QUrl urlRequestUrl(info.requestUrl());
if (urlRequestUrl.scheme() == QLatin1String("cid")) {
const QString newUrl = MessageViewer::WebEngineEmbedPart::self()->contentUrl(
urlRequestUrl.path());
if (!newUrl.isEmpty()) {
info.redirect(QUrl(newUrl));
}
}
return false;
}
diff --git a/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.h b/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.h
index f182bfa8..21e6854f 100644
--- a/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.h
+++ b/messageviewer/src/viewer/webengine/cidreferencesurlinterceptor/cidreferencesurlinterceptor.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CIDREFERENCESURLINTERCEPTOR_H
#define CIDREFERENCESURLINTERCEPTOR_H
#include <WebEngineViewer/NetworkPluginUrlInterceptorInterface>
namespace MessageViewer {
class CidReferencesUrlInterceptor : public WebEngineViewer::NetworkPluginUrlInterceptorInterface
{
Q_OBJECT
public:
explicit CidReferencesUrlInterceptor(QObject *parent = nullptr);
~CidReferencesUrlInterceptor() override;
- bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
+ Q_REQUIRED_RESULT bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
};
}
#endif // CIDREFERENCESURLINTERCEPTOR_H
diff --git a/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp b/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp
index 9a35a43a..cc5ac6a3 100644
--- a/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp
+++ b/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.cpp
@@ -1,62 +1,62 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "loadexternalreferencesurlinterceptor.h"
#include <QWebEngineUrlRequestInfo>
using namespace MessageViewer;
LoadExternalReferencesUrlInterceptor::LoadExternalReferencesUrlInterceptor(QObject *parent)
: WebEngineViewer::NetworkPluginUrlInterceptorInterface(parent)
{
}
LoadExternalReferencesUrlInterceptor::~LoadExternalReferencesUrlInterceptor()
{
}
bool LoadExternalReferencesUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
const QString scheme = info.requestUrl().scheme();
if (scheme == QStringLiteral("data")
|| scheme == QStringLiteral("file")) {
return false;
}
if (mAllowLoadExternalReference) {
return false;
} else {
if (info.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeImage
&& !info.requestUrl().isLocalFile()
&& (scheme != QLatin1String("cid"))) {
return true;
}
}
return false;
}
void LoadExternalReferencesUrlInterceptor::setAllowExternalContent(bool b)
{
mAllowLoadExternalReference = b;
}
bool LoadExternalReferencesUrlInterceptor::allowExternalContent() const
{
return mAllowLoadExternalReference;
}
diff --git a/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h b/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h
index c2e88912..c7060502 100644
--- a/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h
+++ b/messageviewer/src/viewer/webengine/loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h
@@ -1,39 +1,39 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOADEXTERNALREFERENCESURLINTERCEPTOR_H
#define LOADEXTERNALREFERENCESURLINTERCEPTOR_H
#include <WebEngineViewer/NetworkPluginUrlInterceptorInterface>
namespace MessageViewer {
class LoadExternalReferencesUrlInterceptor : public WebEngineViewer::NetworkPluginUrlInterceptorInterface
{
Q_OBJECT
public:
explicit LoadExternalReferencesUrlInterceptor(QObject *parent = nullptr);
~LoadExternalReferencesUrlInterceptor() override;
bool interceptRequest(QWebEngineUrlRequestInfo &info) override;
void setAllowExternalContent(bool b);
- bool allowExternalContent() const;
+ Q_REQUIRED_RESULT bool allowExternalContent() const;
private:
bool mAllowLoadExternalReference = false;
};
}
#endif // LOADEXTERNALREFERENCESURLINTERCEPTOR_H
diff --git a/messageviewer/src/viewer/webengine/mailwebenginepage.cpp b/messageviewer/src/viewer/webengine/mailwebenginepage.cpp
index e4710161..74c64898 100644
--- a/messageviewer/src/viewer/webengine/mailwebenginepage.cpp
+++ b/messageviewer/src/viewer/webengine/mailwebenginepage.cpp
@@ -1,81 +1,75 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "mailwebenginepage.h"
#include <QFontDatabase>
#include <QWebEngineSettings>
#include <QWebEngineProfile>
+#include <QtWebEngineWidgets>
using namespace MessageViewer;
MailWebEnginePage::MailWebEnginePage(QObject *parent)
: WebEngineViewer::WebEnginePage(parent)
{
initialize();
}
-MailWebEnginePage::MailWebEnginePage(QWebEngineProfile *profile, QObject *parent)
- : WebEngineViewer::WebEnginePage(profile, parent)
-{
- initialize();
-}
-
-MailWebEnginePage::~MailWebEnginePage()
-{
-}
-
void MailWebEnginePage::initialize()
{
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, false);
settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false);
settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
settings()->setAttribute(QWebEngineSettings::HyperlinkAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, false);
settings()->setAttribute(QWebEngineSettings::WebGLEnabled, false);
settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false);
settings()->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, false);
settings()->setAttribute(QWebEngineSettings::WebGLEnabled, false);
settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, false);
+#if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+ settings()->setAttribute(QWebEngineSettings::PdfViewerEnabled, false);
+#endif
profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
profile()->setHttpCacheType(QWebEngineProfile::MemoryHttpCache);
const QFontInfo font(QFontDatabase().systemFont(QFontDatabase::GeneralFont));
settings()->setFontFamily(QWebEngineSettings::StandardFont, font.family());
settings()->setFontSize(QWebEngineSettings::DefaultFontSize, font.pixelSize());
connect(this, &QWebEnginePage::featurePermissionRequested,
this, &MailWebEnginePage::slotFeaturePermissionRequested);
}
void MailWebEnginePage::setPrintElementBackground(bool printElementBackground)
{
settings()->setAttribute(QWebEngineSettings::PrintElementBackgrounds, printElementBackground);
}
void MailWebEnginePage::slotFeaturePermissionRequested(const QUrl &url, QWebEnginePage::Feature feature)
{
//Denied all permissions.
setFeaturePermission(url, feature, QWebEnginePage::PermissionDeniedByUser);
}
diff --git a/messageviewer/src/viewer/webengine/mailwebenginepage.h b/messageviewer/src/viewer/webengine/mailwebenginepage.h
index 429146b0..d581efda 100644
--- a/messageviewer/src/viewer/webengine/mailwebenginepage.h
+++ b/messageviewer/src/viewer/webengine/mailwebenginepage.h
@@ -1,41 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEPAGE_H
#define MAILWEBENGINEPAGE_H
#include "messageviewer_export.h"
#include <WebEngineViewer/WebEnginePage>
namespace MessageViewer {
class MESSAGEVIEWER_EXPORT MailWebEnginePage : public WebEngineViewer::WebEnginePage
{
Q_OBJECT
public:
explicit MailWebEnginePage(QObject *parent = nullptr);
- explicit MailWebEnginePage(QWebEngineProfile *profile, QObject *parent = nullptr);
- ~MailWebEnginePage();
+ virtual ~MailWebEnginePage() = default;
void setPrintElementBackground(bool printElementBackground);
private:
void slotFeaturePermissionRequested(const QUrl &url, QWebEnginePage::Feature feature);
void initialize();
};
}
#endif // MAILWEBENGINEPAGE_H
diff --git a/messageviewer/src/viewer/webengine/mailwebengineview.cpp b/messageviewer/src/viewer/webengine/mailwebengineview.cpp
index ee36f016..2cf94934 100644
--- a/messageviewer/src/viewer/webengine/mailwebengineview.cpp
+++ b/messageviewer/src/viewer/webengine/mailwebengineview.cpp
@@ -1,391 +1,384 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "mailwebengineview.h"
#include "mailwebenginepage.h"
#include "webengineviewer/webengineaccesskey.h"
#include "webengineviewer/webenginescript.h"
#include "messageviewer/messageviewersettings.h"
#include "../urlhandlermanager.h"
#include "loadexternalreferencesurlinterceptor/loadexternalreferencesurlinterceptor.h"
#include "blockexternalresourcesurlinterceptor/blockexternalresourcesurlinterceptor.h"
#include "blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h"
#include "cidreferencesurlinterceptor/cidreferencesurlinterceptor.h"
#include <WebEngineViewer/InterceptorManager>
#include <WebEngineViewer/WebEngineManageScript>
#include "scamdetection/scamdetectionwebengine.h"
#include "scamdetection/scamcheckshorturl.h"
#include <QContextMenuEvent>
#include <WebEngineViewer/WebHitTest>
-#include <QWebEngineProfile>
#include <QPrinter>
#include <WebEngineViewer/WebHitTestResult>
using namespace MessageViewer;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
class MessageViewer::MailWebEngineViewPrivate
{
public:
MailWebEngineViewPrivate()
{
}
QUrl mHoveredUrl;
QPoint mLastClickPosition;
ScamDetectionWebEngine *mScamDetection = nullptr;
WebEngineViewer::WebEngineAccessKey *mWebViewAccessKey = nullptr;
MessageViewer::LoadExternalReferencesUrlInterceptor *mExternalReference = nullptr;
MailWebEnginePage *mPageEngine = nullptr;
WebEngineViewer::InterceptorManager *mNetworkAccessManager = nullptr;
MessageViewer::ViewerPrivate *mViewer = nullptr;
MessageViewer::BlockMailTrackingUrlInterceptor *mBlockMailTrackingUrl = nullptr;
bool mCanStartDrag = false;
};
MailWebEngineView::MailWebEngineView(KActionCollection *ac, QWidget *parent)
: WebEngineViewer::WebEngineView(parent)
, d(new MessageViewer::MailWebEngineViewPrivate)
{
- d->mPageEngine = new MailWebEnginePage(new QWebEngineProfile(this), this);
+ d->mPageEngine = new MailWebEnginePage(this);
setPage(d->mPageEngine);
d->mWebViewAccessKey = new WebEngineViewer::WebEngineAccessKey(this, this);
d->mWebViewAccessKey->setActionCollection(ac);
d->mScamDetection = new ScamDetectionWebEngine(this);
connect(d->mScamDetection, &ScamDetectionWebEngine::messageMayBeAScam, this,
&MailWebEngineView::messageMayBeAScam);
connect(d->mWebViewAccessKey, &WebEngineViewer::WebEngineAccessKey::openUrl, this,
&MailWebEngineView::openUrl);
connect(this, &MailWebEngineView::loadFinished, this, &MailWebEngineView::slotLoadFinished);
d->mNetworkAccessManager = new WebEngineViewer::InterceptorManager(this, ac, this);
d->mExternalReference = new MessageViewer::LoadExternalReferencesUrlInterceptor(this);
d->mNetworkAccessManager->addInterceptor(d->mExternalReference);
MessageViewer::CidReferencesUrlInterceptor *cidReference
= new MessageViewer::CidReferencesUrlInterceptor(this);
d->mNetworkAccessManager->addInterceptor(cidReference);
MessageViewer::BlockExternalResourcesUrlInterceptor *blockExternalUrl
= new MessageViewer::BlockExternalResourcesUrlInterceptor(this);
connect(blockExternalUrl, &BlockExternalResourcesUrlInterceptor::formSubmittedForbidden, this,
&MailWebEngineView::formSubmittedForbidden);
d->mNetworkAccessManager->addInterceptor(blockExternalUrl);
d->mBlockMailTrackingUrl
= new MessageViewer::BlockMailTrackingUrlInterceptor(this);
connect(d->mBlockMailTrackingUrl, &BlockMailTrackingUrlInterceptor::mailTrackingFound, this,
&MailWebEngineView::mailTrackingFound);
setFocusPolicy(Qt::WheelFocus);
connect(d->mPageEngine, &MailWebEnginePage::urlClicked, this, &MailWebEngineView::openUrl);
connect(
page(), &QWebEnginePage::scrollPositionChanged, d->mWebViewAccessKey,
&WebEngineViewer::WebEngineAccessKey::hideAccessKeys);
- initializeScripts();
}
MailWebEngineView::~MailWebEngineView()
{
delete d;
}
void MailWebEngineView::readConfig()
{
if (MessageViewer::MessageViewerSettings::self()->mailTrackingUrlEnabled()) {
d->mNetworkAccessManager->addInterceptor(d->mBlockMailTrackingUrl);
- } else {
+ } else {
d->mNetworkAccessManager->removeInterceptor(d->mBlockMailTrackingUrl);
}
}
void MailWebEngineView::setLinkHovered(const QUrl &url)
{
//TODO we need to detect image url too.
d->mHoveredUrl = url;
}
void MailWebEngineView::runJavaScriptInWordId(const QString &script)
{
page()->runJavaScript(script, WebEngineViewer::WebEngineManageScript::scriptWordId());
}
void MailWebEngineView::setViewer(MessageViewer::ViewerPrivate *viewer)
{
d->mViewer = viewer;
}
-void MailWebEngineView::initializeScripts()
-{
- initializeJQueryScript();
-}
-
void MailWebEngineView::contextMenuEvent(QContextMenuEvent *e)
{
WebEngineViewer::WebHitTest *webHit = d->mPageEngine->hitTestContent(e->pos());
connect(webHit, &WebEngineViewer::WebHitTest::finished, this,
&MailWebEngineView::slotWebHitFinished);
}
void MailWebEngineView::slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result)
{
Q_EMIT popupMenu(result);
}
void MailWebEngineView::scrollUp(int pixels)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollUp(pixels));
}
void MailWebEngineView::scrollDown(int pixels)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollDown(pixels));
}
void MailWebEngineView::selectAll()
{
page()->triggerAction(QWebEnginePage::SelectAll);
}
void MailWebEngineView::slotZoomChanged(qreal zoom)
{
setZoomFactor(zoom);
}
void MailWebEngineView::scamCheck()
{
d->mScamDetection->scanPage(page());
}
void MailWebEngineView::slotShowDetails()
{
d->mScamDetection->showDetails();
}
void MailWebEngineView::forwardKeyReleaseEvent(QKeyEvent *e)
{
if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
d->mWebViewAccessKey->keyReleaseEvent(e);
}
}
void MailWebEngineView::forwardMousePressEvent(QMouseEvent *event)
{
if (d->mViewer && !d->mHoveredUrl.isEmpty()) {
if (event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier)) {
// special processing for shift+click
if (URLHandlerManager::instance()->handleShiftClick(d->mHoveredUrl, d->mViewer)) {
event->accept();
return;
}
}
if (event->button() == Qt::LeftButton) {
d->mCanStartDrag = URLHandlerManager::instance()->willHandleDrag(d->mHoveredUrl,
d->mViewer);
d->mLastClickPosition = event->pos();
}
}
}
void MailWebEngineView::forwardMouseMoveEvent(QMouseEvent *event)
{
if (d->mViewer && !d->mHoveredUrl.isEmpty()) {
// If we are potentially handling a drag, deal with that.
if (d->mCanStartDrag && (event->buttons() & Qt::LeftButton)) {
if ((d->mLastClickPosition - event->pos()).manhattanLength()
> QApplication::startDragDistance()) {
if (URLHandlerManager::instance()->handleDrag(d->mHoveredUrl, d->mViewer)) {
// If the URL handler manager started a drag, don't handle this in the future
d->mCanStartDrag = false;
}
}
event->accept();
}
}
}
void MailWebEngineView::forwardMouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
d->mCanStartDrag = false;
}
void MailWebEngineView::forwardKeyPressEvent(QKeyEvent *e)
{
if (e && hasFocus()) {
if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
d->mWebViewAccessKey->keyPressEvent(e);
}
}
}
void MailWebEngineView::forwardWheelEvent(QWheelEvent *e)
{
if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
d->mWebViewAccessKey->wheelEvent(e);
}
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
const int numDegrees = e->delta() / 8;
const int numSteps = numDegrees / 15;
Q_EMIT wheelZoomChanged(numSteps);
e->accept();
}
}
void MailWebEngineView::resizeEvent(QResizeEvent *e)
{
if (MessageViewer::MessageViewerSettings::self()->accessKeyEnabled()) {
d->mWebViewAccessKey->resizeEvent(e);
}
QWebEngineView::resizeEvent(e);
}
void MailWebEngineView::saveMainFrameScreenshotInFile(const QString &filename)
{
//TODO need to verify it
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::TextAntialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
render(&painter);
painter.end();
image.save(filename);
}
void MailWebEngineView::showAccessKeys()
{
d->mWebViewAccessKey->showAccessKeys();
}
void MailWebEngineView::hideAccessKeys()
{
d->mWebViewAccessKey->hideAccessKeys();
}
void MailWebEngineView::isScrolledToBottom()
{
page()->runJavaScript(WebEngineViewer::WebEngineScript::isScrolledToBottom(),
WebEngineViewer::WebEngineManageScript::scriptWordId(),
invoke(this, &MailWebEngineView::handleIsScrolledToBottom));
}
void MailWebEngineView::setElementByIdVisible(const QString &id, bool visible)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setElementByIdVisible(id, visible));
}
void MailWebEngineView::removeAttachmentMarking(const QString &id)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::removeStyleToElement(id));
}
void MailWebEngineView::markAttachment(const QString &id, const QString &style)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::setStyleToElement(id, style));
}
void MailWebEngineView::scrollToAnchor(const QString &anchor)
{
page()->runJavaScript(WebEngineViewer::WebEngineScript::searchElementPosition(anchor),
WebEngineViewer::WebEngineManageScript::scriptWordId(),
invoke(this, &MailWebEngineView::handleScrollToAnchor));
}
void MailWebEngineView::handleIsScrolledToBottom(const QVariant &result)
{
bool scrolledToBottomResult = false;
if (result.isValid()) {
scrolledToBottomResult = result.toBool();
}
Q_EMIT pageIsScrolledToBottom(scrolledToBottomResult);
}
void MailWebEngineView::handleScrollToAnchor(const QVariant &result)
{
if (result.isValid()) {
const QList<QVariant> lst = result.toList();
if (lst.count() == 2) {
const QPoint pos(lst.at(0).toInt(), lst.at(1).toInt());
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToPosition(pos));
}
}
}
void MailWebEngineView::scrollPageDown(int percent)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollPercentage(percent));
}
void MailWebEngineView::scrollPageUp(int percent)
{
scrollPageDown(-percent);
}
void MailWebEngineView::scrollToRelativePosition(qreal pos)
{
runJavaScriptInWordId(WebEngineViewer::WebEngineScript::scrollToRelativePosition(pos));
}
void MailWebEngineView::setAllowExternalContent(bool b)
{
if (d->mExternalReference->allowExternalContent() != b) {
d->mExternalReference->setAllowExternalContent(b);
reload();
}
}
QList<QAction *> MailWebEngineView::interceptorUrlActions(
const WebEngineViewer::WebHitTestResult &result) const
{
return d->mNetworkAccessManager->interceptorUrlActions(result);
}
void MailWebEngineView::slotLoadFinished()
{
scamCheck();
}
void MailWebEngineView::setPrintElementBackground(bool printElementBackground)
{
d->mPageEngine->setPrintElementBackground(printElementBackground);
}
bool MailWebEngineView::execPrintPreviewPage(QPrinter *printer, int timeout)
{
return d->mPageEngine->execPrintPreviewPage(printer, timeout);
}
diff --git a/messageviewer/src/viewer/webengine/mailwebengineview.h b/messageviewer/src/viewer/webengine/mailwebengineview.h
index 4f226fca..344b9770 100644
--- a/messageviewer/src/viewer/webengine/mailwebengineview.h
+++ b/messageviewer/src/viewer/webengine/mailwebengineview.h
@@ -1,111 +1,108 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINE_H
#define MAILWEBENGINE_H
#include "messageviewer_export.h"
#include <WebEngineViewer/WebEngineView>
#include <boost/function.hpp>
-#include <viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h>
+#include <MessageViewer/BlockMailTrackingUrlInterceptor>
class QPrinter;
class KActionCollection;
namespace WebEngineViewer {
class WebHitTestResult;
}
namespace MessageViewer {
class ViewerPrivate;
class MailWebEngineViewPrivate;
class MESSAGEVIEWER_EXPORT MailWebEngineView : public WebEngineViewer::WebEngineView
{
Q_OBJECT
public:
explicit MailWebEngineView(KActionCollection *ac, QWidget *parent = nullptr);
~MailWebEngineView() override;
void scrollUp(int pixels);
void scrollDown(int pixels);
void selectAll();
void scamCheck();
void saveMainFrameScreenshotInFile(const QString &filename);
void showAccessKeys();
void hideAccessKeys();
void isScrolledToBottom();
void setElementByIdVisible(const QString &id, bool visible);
void removeAttachmentMarking(const QString &id);
void markAttachment(const QString &id, const QString &style);
void scrollToAnchor(const QString &anchor);
void scrollPageDown(int percent);
void scrollPageUp(int percent);
void scrollToRelativePosition(qreal pos);
void setAllowExternalContent(bool b);
Q_REQUIRED_RESULT QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
void setPrintElementBackground(bool printElementBackground);
void setLinkHovered(const QUrl &url);
void setViewer(MessageViewer::ViewerPrivate *viewer);
bool execPrintPreviewPage(QPrinter *printer, int timeout);
void readConfig();
public Q_SLOTS:
void slotZoomChanged(qreal zoom);
void slotShowDetails();
protected:
void forwardWheelEvent(QWheelEvent *event) override;
void forwardKeyPressEvent(QKeyEvent *event) override;
void forwardKeyReleaseEvent(QKeyEvent *event) override;
void forwardMousePressEvent(QMouseEvent *event) override;
void forwardMouseMoveEvent(QMouseEvent *event) override;
void forwardMouseReleaseEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
Q_SIGNALS:
void wheelZoomChanged(int numSteps);
void openUrl(const QUrl &url);
void messageMayBeAScam();
void formSubmittedForbidden();
void mailTrackingFound(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &);
/// Emitted when the user right-clicks somewhere
- /// @param url if an URL was under the cursor, this parameter contains it. Otherwise empty
- /// @param point position where the click happened, in local coordinates
void popupMenu(const WebEngineViewer::WebHitTestResult &result);
void pageIsScrolledToBottom(bool);
private Q_SLOTS:
void handleScrollToAnchor(const QVariant &result);
void handleIsScrolledToBottom(const QVariant &result);
void slotWebHitFinished(const WebEngineViewer::WebHitTestResult &result);
void slotLoadFinished();
private:
- void initializeScripts();
void runJavaScriptInWordId(const QString &script);
MailWebEngineViewPrivate *const d;
};
}
#endif // MAILWEBENGINE_H
diff --git a/messageviewer/src/viewer/webengine/tests/CMakeLists.txt b/messageviewer/src/viewer/webengine/tests/CMakeLists.txt
index f7e90b94..7fac6e41 100644
--- a/messageviewer/src/viewer/webengine/tests/CMakeLists.txt
+++ b/messageviewer/src/viewer/webengine/tests/CMakeLists.txt
@@ -1,75 +1,65 @@
add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
set(scrolladdattachment_test_SRCS
testwebenginescrolladdattachment.cpp
)
add_executable(scrolladdattachmenttest ${scrolladdattachment_test_SRCS})
target_link_libraries(scrolladdattachmenttest
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
)
####
set(testmailwebengine_test_SRCS
testmailwebengine.cpp
)
add_executable(testmailwebengine ${testmailwebengine_test_SRCS})
target_link_libraries(testmailwebengine
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer Qt5::PrintSupport KF5::I18n
)
#####
#set(testwebengineaccesskey_test_SRCS
# testwebengineaccesskey.cpp
# )
#add_executable(testwebengineaccesskey ${testwebengineaccesskey_test_SRCS})
#target_link_libraries(testwebengineaccesskey
# Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::MessageViewer
# )
####
set(testmailwebengineselection_test_SRCS
testmailwebengineselection.cpp
)
add_executable(testmailwebengineselection ${testmailwebengineselection_test_SRCS})
target_link_libraries(testmailwebengineselection
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
)
######
set(testmaildndattachment_test_SRCS
testmaildndattachment.cpp
)
add_executable(testmaildndattachment ${testmaildndattachment_test_SRCS})
target_link_libraries(testmaildndattachment
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
)
-#####
-set(testjquerysupportmailwebengine_test_SRCS
- testjquerysupportmailwebengine.cpp
- )
-add_executable(testjquerysupportmailwebengine ${testjquerysupportmailwebengine_test_SRCS})
-
-target_link_libraries(testjquerysupportmailwebengine
- Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
- )
-
#####
set(testmailmboxwebengine_test_SRCS
testmailmboxwebengine.cpp
)
add_executable(testmailmboxwebengine ${testmailmboxwebengine_test_SRCS})
target_link_libraries(testmailmboxwebengine
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
)
diff --git a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp b/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp
deleted file mode 100644
index a34905b2..00000000
--- a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- Copyright (C) 2016-2018 Laurent Montel <montel@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.
-*/
-
-#include "testjquerysupportmailwebengine.h"
-#include <KActionCollection>
-#include <QApplication>
-#include <WebEngineViewer/WebEnginePage>
-#include <QDebug>
-#include <webengineview.h>
-#include <QPushButton>
-#include <QTextEdit>
-#include <QFile>
-#include <MessageViewer/Viewer>
-#include <MessageViewer/AttachmentStrategy>
-#include <QVBoxLayout>
-
-TestJQuerySupportMailWebEngine::TestJQuerySupportMailWebEngine(QWidget *parent)
- : QWidget(parent)
-{
- QVBoxLayout *vboxLayout = new QVBoxLayout(this);
- viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(this));
- vboxLayout->addWidget(viewer);
- viewer->setMessage(readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox")));
- viewer->setPluginName(QStringLiteral("enterprise"));
- viewer->setAttachmentStrategy(MessageViewer::AttachmentStrategy::headerOnly());
-
- mEditor = new QTextEdit(this);
- mEditor->setAcceptRichText(false);
- mEditor->setPlainText(QStringLiteral(
- "qt.jQuery('img').each( function () { qt.jQuery(this).css('-webkit-transition', '-webkit-transform 2s'); qt.jQuery(this).css('-webkit-transform', 'rotate(180deg)') } ); undefined"));
- vboxLayout->addWidget(mEditor);
-
- QPushButton *executeQuery = new QPushButton(QStringLiteral("Execute Query"), this);
- connect(executeQuery, &QPushButton::clicked, this,
- &TestJQuerySupportMailWebEngine::slotExecuteQuery);
- vboxLayout->addWidget(executeQuery);
-}
-
-TestJQuerySupportMailWebEngine::~TestJQuerySupportMailWebEngine()
-{
-}
-
-KMime::Message::Ptr TestJQuerySupportMailWebEngine::readAndParseMail(const QString &mailFile)
-{
- QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
- file.open(QIODevice::ReadOnly);
- QByteArray ba = file.readAll();
- qDebug() << ba;
- const QByteArray data = ba;
- Q_ASSERT(!data.isEmpty());
- KMime::Message::Ptr msg(new KMime::Message);
- msg->setContent(data);
- msg->parse();
- return msg;
-}
-
-void TestJQuerySupportMailWebEngine::slotExecuteQuery()
-{
- const QString code = mEditor->toPlainText();
- if (!code.isEmpty()) {
- viewer->runJavaScript(code);
- }
-}
-
-int main(int argc, char *argv[])
-{
- QApplication app(argc, argv);
- app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
- TestJQuerySupportMailWebEngine *testWebEngine = new TestJQuerySupportMailWebEngine;
- testWebEngine->show();
- const int ret = app.exec();
- return ret;
-}
diff --git a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.js b/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.js
deleted file mode 100644
index 37ba3870..00000000
--- a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.js
+++ /dev/null
@@ -1,10 +0,0 @@
-qt.jQuery('#kmailshowattachment').click(function(){
-qt.jQuery('#kmailshowattachment').hide();
-qt.jQuery("#kmailhideattachment").show();
-qt.jQuery("#attachmentlist").hide()}
-);
-qt.jQuery('#kmailhideattachment').click(function(){
-qt.jQuery("#kmailhideattachment").hide();
-qt.jQuery("#kmailshowattachment").show();
-qt.jQuery("#attachmentlist").show()}
-);
diff --git a/messageviewer/src/viewer/webengine/tests/testmaildndattachment.cpp b/messageviewer/src/viewer/webengine/tests/testmaildndattachment.cpp
index cbc8d252..67738f39 100644
--- a/messageviewer/src/viewer/webengine/tests/testmaildndattachment.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testmaildndattachment.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testmaildndattachment.h"
#include <KMime/Content>
#include <KActionCollection>
#include <QApplication>
#include <QFile>
#include <QVBoxLayout>
#include <QDebug>
#include <MessageViewer/MailWebEngineView>
#include <MessageViewer/Viewer>
TestMailDndAttachment::TestMailDndAttachment(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
MessageViewer::Viewer *viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(
this));
vbox->addWidget(viewer);
viewer->setMessage(readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox")) /*KMime::Message::Ptr(msg)*/);
viewer->setPluginName(QStringLiteral("longheaderstyleplugin"));
}
TestMailDndAttachment::~TestMailDndAttachment()
{
}
KMime::Message::Ptr TestMailDndAttachment::readAndParseMail(const QString &mailFile)
{
QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
file.open(QIODevice::ReadOnly);
QByteArray ba = file.readAll();
qDebug() << ba;
const QByteArray data = ba;
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestMailDndAttachment *testWebEngine = new TestMailDndAttachment;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testmaildndattachment.h b/messageviewer/src/viewer/webengine/tests/testmaildndattachment.h
index cb886f72..5cb2006d 100644
--- a/messageviewer/src/viewer/webengine/tests/testmaildndattachment.h
+++ b/messageviewer/src/viewer/webengine/tests/testmaildndattachment.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTMAILDNDATTACHMENT_H
#define TESTMAILDNDATTACHMENT_H
#include <QWidget>
#include <KMime/Message>
class TestMailDndAttachment : public QWidget
{
Q_OBJECT
public:
explicit TestMailDndAttachment(QWidget *parent = nullptr);
~TestMailDndAttachment();
private:
KMime::Message::Ptr readAndParseMail(const QString &mailFile);
};
#endif // TESTMAILDNDATTACHMENT_H
diff --git a/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.cpp b/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.cpp
index 7e6e5149..6ea53a7a 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.cpp
@@ -1,69 +1,69 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testmailmboxwebengine.h"
#include <KMime/Content>
#include <KActionCollection>
#include <QApplication>
#include <QFile>
#include <QVBoxLayout>
#include <QDebug>
#include <MessageViewer/MailWebEngineView>
#include <MessageViewer/Viewer>
TestMailMBoxWebEngine::TestMailMBoxWebEngine(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
MessageViewer::Viewer *viewer = new MessageViewer::Viewer(nullptr, nullptr, new KActionCollection(
this));
vbox->addWidget(viewer);
viewer->setMessage(readAndParseMail(QStringLiteral("html.mbox")));
viewer->setPluginName(QStringLiteral("longheaderstyleplugin"));
}
TestMailMBoxWebEngine::~TestMailMBoxWebEngine()
{
}
KMime::Message::Ptr TestMailMBoxWebEngine::readAndParseMail(const QString &mailFile)
{
QFile file(QLatin1String(MAIL_DATA_DIR) + QLatin1Char('/') + mailFile);
file.open(QIODevice::ReadOnly);
QByteArray ba = file.readAll();
qDebug() << ba;
const QByteArray data = ba;
Q_ASSERT(!data.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
msg->setContent(data);
msg->parse();
return msg;
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestMailMBoxWebEngine *testWebEngine = new TestMailMBoxWebEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.h b/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.h
index 6f2bfab0..caed2ab0 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.h
+++ b/messageviewer/src/viewer/webengine/tests/testmailmboxwebengine.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTMAILMBOXWEBENGINE_H
#define TESTMAILMBOXWEBENGINE_H
#include <QWidget>
#include <KMime/Message>
class TestMailMBoxWebEngine : public QWidget
{
Q_OBJECT
public:
explicit TestMailMBoxWebEngine(QWidget *parent = nullptr);
~TestMailMBoxWebEngine();
private:
KMime::Message::Ptr readAndParseMail(const QString &mailFile);
};
#endif // TESTMAILMBOXWEBENGINE_H
diff --git a/messageviewer/src/viewer/webengine/tests/testmailwebengine.cpp b/messageviewer/src/viewer/webengine/tests/testmailwebengine.cpp
index fc91c11c..491a1488 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailwebengine.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testmailwebengine.cpp
@@ -1,134 +1,134 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testmailwebengine.h"
#include "webenginescript.h"
#include <KActionCollection>
#include <QApplication>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWebEngineSettings>
#include <MessageViewer/MailWebEngineView>
#include <WebEngineViewer/WebEngineManageScript>
TestMailWebEngine::TestMailWebEngine(QWidget *parent)
: QWidget(parent)
{
mZoom = 1.0;
QVBoxLayout *vbox = new QVBoxLayout(this);
mTestWebEngine = new MessageViewer::MailWebEngineView(new KActionCollection(this), this);
connect(mTestWebEngine, &MessageViewer::MailWebEngineView::openUrl, this,
&TestMailWebEngine::slotOpenUrl);
//mTestWebEngine->load(QUrl(QStringLiteral("http://www.kde.org")));
QString str = QStringLiteral(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n <style type=\"text/css\">\n /*<![CDATA[*/\n @import \"main.css\";\n /*]]>*/\n\n.links {\n margin: auto;\n}\n\n.links td {\n padding-top: 5px;\n padding-bottom: 5px;\n}\n\n </style>\n\n <title>Akregator</title>\n</head>\n\n<body>\n <div id=\"header\"><img src=\"file:///opt/kde5/share/icons/maia/apps/scalable/akregator.svg\" align=\"top\" height=\"128\" width=\"128\" alt=\"akregator\" title=\"\" />\n <div id=\"title\">\n <h1>Akregator</h1>Akregator est un agrégateur de flux pour KDE.\n </div>\n </div>\n\n <div id=\"box\">\n <div id=\"boxInner\">\n\n<div class=\"center\">\n <p>Feed readers provide a convenient way to browse different kinds of content, including news, blogs, and other content from online sites. Instead of checking all your favorite web sites manually for updates, Akregator collects the content for you.</p>\n <p> For more information about using Akregator, check the <a href='http://akregator.kde.org/'>Akregator website</a>. If you do not want to see this page anymore, <a href='config:/disable_introduction'>click here</a>.</p>\n <p>We hope that you will enjoy Akregator.</p>\n <p>Thank you, The Akregator Team </p>\n</div>\n\n </div>\n </div>\n</body>\n</html>\n\n<!-- vim:set sw=2 et nocindent smartindent: -->\n");
mTestWebEngine->setHtml(str, QUrl(QStringLiteral("file:///")));
mTestWebEngine->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
vbox->addWidget(mTestWebEngine);
QHBoxLayout *hButtonBox = new QHBoxLayout;
vbox->addLayout(hButtonBox);
QPushButton *scrollUp = new QPushButton(QStringLiteral("scrollUp 10px"), this);
connect(scrollUp, &QPushButton::clicked, this, &TestMailWebEngine::slotScrollUp);
hButtonBox->addWidget(scrollUp);
QPushButton *scrollDown = new QPushButton(QStringLiteral("scrollDown 10px"), this);
connect(scrollDown, &QPushButton::clicked, this, &TestMailWebEngine::slotScrollDown);
hButtonBox->addWidget(scrollDown);
hButtonBox = new QHBoxLayout;
vbox->addLayout(hButtonBox);
QPushButton *zoomUp = new QPushButton(QStringLiteral("zoom Up"), this);
connect(zoomUp, &QPushButton::clicked, this, &TestMailWebEngine::slotZoomUp);
hButtonBox->addWidget(zoomUp);
QPushButton *zoomDown = new QPushButton(QStringLiteral("zoom Down"), this);
connect(zoomDown, &QPushButton::clicked, this, &TestMailWebEngine::slotZoomDown);
hButtonBox->addWidget(zoomDown);
QPushButton *printPreview = new QPushButton(QStringLiteral("Print Preview"), this);
connect(printPreview, &QPushButton::clicked, this, &TestMailWebEngine::slotPrintPreview);
hButtonBox->addWidget(printPreview);
}
TestMailWebEngine::~TestMailWebEngine()
{
}
void TestMailWebEngine::slotOpenUrl(const QUrl &url)
{
mTestWebEngine->load(url);
}
void TestMailWebEngine::slotScrollDown()
{
mTestWebEngine->page()->runJavaScript(WebEngineViewer::WebEngineScript::scrollDown(
10),
WebEngineViewer::WebEngineManageScript::scriptWordId());
}
void TestMailWebEngine::slotScrollUp()
{
mTestWebEngine->page()->runJavaScript(WebEngineViewer::WebEngineScript::scrollUp(
10),
WebEngineViewer::WebEngineManageScript::scriptWordId());
}
void TestMailWebEngine::slotZoomDown()
{
mZoom -= 0.2;
mTestWebEngine->setZoomFactor(mZoom);
}
void TestMailWebEngine::slotZoomUp()
{
mZoom += 0.2;
mTestWebEngine->setZoomFactor(mZoom);
}
void TestMailWebEngine::slotPrintPreview()
{
QPrintPreviewDialog *dialog = new QPrintPreviewDialog(this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->resize(800, 750);
connect(dialog, &QPrintPreviewDialog::paintRequested, this, [=](QPrinter *printing) {
QApplication::setOverrideCursor(Qt::WaitCursor);
mTestWebEngine->execPrintPreviewPage(printing, 10*1000);
QApplication::restoreOverrideCursor();
});
dialog->open();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestMailWebEngine *testWebEngine = new TestMailWebEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testmailwebengine.h b/messageviewer/src/viewer/webengine/tests/testmailwebengine.h
index 66691074..fc8cb03f 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailwebengine.h
+++ b/messageviewer/src/viewer/webengine/tests/testmailwebengine.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTMAILWEBENGINE_H
#define TESTMAILWEBENGINE_H
#include <QWidget>
namespace MessageViewer {
class MailWebEngineView;
}
class TestMailWebEngine : public QWidget
{
Q_OBJECT
public:
explicit TestMailWebEngine(QWidget *parent = nullptr);
~TestMailWebEngine();
private Q_SLOTS:
void slotScrollUp();
void slotScrollDown();
void slotZoomUp();
void slotZoomDown();
void slotOpenUrl(const QUrl &url);
void slotPrintPreview();
private:
MessageViewer::MailWebEngineView *mTestWebEngine;
qreal mZoom;
};
#endif // TESTMAILWEBENGINE_H
diff --git a/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.cpp b/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.cpp
index 486482c3..8ae005fe 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.cpp
@@ -1,87 +1,87 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testmailwebengineselection.h"
#include <KActionCollection>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWebEngineSettings>
#include <QMessageBox>
#include <MessageViewer/MailWebEngineView>
TestMailWebEngineSelection::TestMailWebEngineSelection(QWidget *parent)
: QWidget(parent)
, mNumber(0)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
mTestWebEngine = new MessageViewer::MailWebEngineView(new KActionCollection(this), this);
connect(mTestWebEngine, &MessageViewer::MailWebEngineView::openUrl, this,
&TestMailWebEngineSelection::slotOpenUrl);
QString str = QStringLiteral(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n <style type=\"text/css\">\n /*<![CDATA[*/\n @import \"main.css\";\n /*]]>*/\n\n.links {\n margin: auto;\n}\n\n.links td {\n padding-top: 5px;\n padding-bottom: 5px;\n}\n\n </style>\n\n <title>Akregator</title>\n</head>\n\n<body>\n <div id=\"header\"><img src=\"file:///opt/kde5/share/icons/maia/apps/scalable/akregator.svg\" align=\"top\" height=\"128\" width=\"128\" alt=\"akregator\" title=\"\" />\n <div id=\"title\">\n <h1>Akregator</h1>Akregator est un agrégateur de flux pour KDE.\n </div>\n </div>\n\n <div id=\"box\">\n <div id=\"boxInner\">\n\n<div class=\"center\">\n <p>Feed readers provide a convenient way to browse different kinds of content, including news, blogs, and other content from online sites. Instead of checking all your favorite web sites manually for updates, Akregator collects the content for you.</p>\n <p> For more information about using Akregator, check the <a href='http://akregator.kde.org/'>Akregator website</a>. If you do not want to see this page anymore, <a href='config:/disable_introduction'>click here</a>.</p>\n <p>We hope that you will enjoy Akregator.</p>\n <p>Thank you, The Akregator Team </p>\n</div>\n\n </div>\n </div>\n</body>\n</html>\n\n<!-- vim:set sw=2 et nocindent smartindent: -->\n");
mTestWebEngine->setHtml(str, QUrl(QStringLiteral("file:///")));
mTestWebEngine->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
vbox->addWidget(mTestWebEngine);
QHBoxLayout *hButtonBox = new QHBoxLayout;
vbox->addLayout(hButtonBox);
QPushButton *changeHtml = new QPushButton(QStringLiteral("switch html"), this);
connect(changeHtml, &QPushButton::clicked, this, &TestMailWebEngineSelection::slotSwitchHtml);
hButtonBox->addWidget(changeHtml);
QPushButton *showSelection = new QPushButton(QStringLiteral("Show Selection"), this);
connect(showSelection, &QPushButton::clicked, this,
&TestMailWebEngineSelection::slotShowSelection);
hButtonBox->addWidget(showSelection);
}
TestMailWebEngineSelection::~TestMailWebEngineSelection()
{
}
void TestMailWebEngineSelection::slotSwitchHtml()
{
QString str = QStringLiteral(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n <style type=\"text/css\">\n /*<![CDATA[*/\n @import \"main.css\";\n /*]]>*/\n\n.links {\n margin: auto;\n}\n\n.links td {\n padding-top: 5px;\n padding-bottom: 5px;\n}\n\n </style>\n\n <title>Akregator</title>\n</head>\n\n<body>\n <div id=\"header\"><img src=\"file:///opt/kde5/share/icons/maia/apps/scalable/akregator.svg\" align=\"top\" height=\"128\" width=\"128\" alt=\"akregator\" title=\"\" />\n <div id=\"title\">\n <h1>Akregator</h1>Akregator est un agrégateur de flux pour KDE.\n </div>\n </div>\n\n <div id=\"box\">\n <div id=\"boxInner\">\n\n<div class=\"center\">\n <p>Feed readers provide a convenient way to browse different kinds of content, including news, blogs, and other content from online sites. Instead of checking all your favorite web sites manually for updates, Akregator collects the content for you.</p>\n <p> For more information about using Akregator, check the <a href='http://akregator.kde.org/'>Akregator website</a>. If you do not want to see this page anymore, <a href='config:/disable_introduction'>click here</a>.</p>\n <p>We hope that you will enjoy Akregator.</p>\n <p>Thank you, number %1 </p>\n</div>\n\n </div>\n </div>\n</body>\n</html>\n\n<!-- vim:set sw=2 et nocindent smartindent: -->\n")
.arg(mNumber);
mTestWebEngine->setHtml(str, QUrl(QStringLiteral("file:///")));
mNumber++;
}
void TestMailWebEngineSelection::slotShowSelection()
{
QMessageBox::information(this, QStringLiteral("selection"), mTestWebEngine->selectedText());
}
void TestMailWebEngineSelection::slotOpenUrl(const QUrl &url)
{
mTestWebEngine->load(url);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestMailWebEngineSelection *testWebEngine = new TestMailWebEngineSelection;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.h b/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.h
index ece21904..d82c86f4 100644
--- a/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.h
+++ b/messageviewer/src/viewer/webengine/tests/testmailwebengineselection.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTMAILWEBENGINESELECTION_H
#define TESTMAILWEBENGINESELECTION_H
#include <QWidget>
namespace MessageViewer {
class MailWebEngineView;
}
class TestMailWebEngineSelection : public QWidget
{
Q_OBJECT
public:
explicit TestMailWebEngineSelection(QWidget *parent = nullptr);
~TestMailWebEngineSelection();
private Q_SLOTS:
void slotOpenUrl(const QUrl &url);
void slotSwitchHtml();
void slotShowSelection();
private:
MessageViewer::MailWebEngineView *mTestWebEngine;
int mNumber;
};
#endif // TESTMAILWEBENGINE_H
diff --git a/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.cpp b/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.cpp
index 7f273eb0..57d1a84b 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.cpp
@@ -1,84 +1,84 @@
/*
Copyright (C) 2016 Laurent Montel <montel@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.
*/
#include "testwebengineaccesskey.h"
#include "../mailwebengineview.h"
#include "messageviewer/messageviewersettings.h"
#include <QApplication>
#include <QHBoxLayout>
#include <QWebEngineSettings>
#include <QStandardPaths>
#include <QLabel>
#include <QPushButton>
TestWidget::TestWidget(QWidget *parent)
: QWidget(parent)
{
WebEngineViewer::MessageViewerSettings::self()->setAccessKeyEnabled(true);
QHBoxLayout *hbox = new QHBoxLayout(this);
- hbox->setMargin(0);
+ hbox->setContentsMargins(0, 0, 0, 0);
TestWebEngineAccesskey *webEngine = new TestWebEngineAccesskey(this);
hbox->addWidget(webEngine);
TestWebKitAccesskey *webKit = new TestWebKitAccesskey(this);
hbox->addWidget(webKit);
}
TestWidget::~TestWidget()
{
}
TestWebEngineAccesskey::TestWebEngineAccesskey(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vboxLayout = new QVBoxLayout(this);
QLabel *label = new QLabel(QStringLiteral("WebEngine"));
vboxLayout->addWidget(label);
mTestWebEngine = new WebEngineViewer::MailWebEngineView(new KActionCollection(this), this);
mTestWebEngine->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
vboxLayout->addWidget(mTestWebEngine);
mTestWebEngine->load(QUrl(QStringLiteral("http://www.kde.org")));
QPushButton *searchAccessKey = new QPushButton(QStringLiteral("AccessKey"), this);
vboxLayout->addWidget(searchAccessKey);
connect(searchAccessKey, &QPushButton::clicked, this,
&TestWebEngineAccesskey::slotShowAccessKey);
}
TestWebEngineAccesskey::~TestWebEngineAccesskey()
{
}
void TestWebEngineAccesskey::slotShowAccessKey()
{
mTestWebEngine->showAccessKeys();
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestWidget *testWebEngine = new TestWidget;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.h b/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.h
index 371b1beb..b3dc2872 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.h
+++ b/messageviewer/src/viewer/webengine/tests/testwebengineaccesskey.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTWEBENGINEACCESSKEY_H
#define TESTWEBENGINEACCESSKEY_H
#include <QWidget>
namespace WebEngineViewer {
class MailWebEngineView;
}
class TestWidget : public QWidget
{
Q_OBJECT
public:
explicit TestWidget(QWidget *parent = nullptr);
~TestWidget();
};
class TestWebEngineAccesskey : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngineAccesskey(QWidget *parent = nullptr);
~TestWebEngineAccesskey();
private Q_SLOTS:
void slotShowAccessKey();
private:
WebEngineViewer::MailWebEngineView *mTestWebEngine;
};
#endif // TESTWEBENGINEACCESSKEY_H
diff --git a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.cpp b/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.cpp
index dd67ca42..eadded79 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.cpp
+++ b/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.cpp
@@ -1,76 +1,76 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testwebenginescrolladdattachment.h"
#include <KActionCollection>
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWebEngineSettings>
#include <messageviewer/mailwebengineview.h>
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
TestWebEngineScrollAddAttachment::TestWebEngineScrollAddAttachment(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vboxLayout = new QVBoxLayout(this);
mTestWebEngine = new MessageViewer::MailWebEngineView(new KActionCollection(this), this);
mTestWebEngine->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
vboxLayout->addWidget(mTestWebEngine);
mTestWebEngine->load(QUrl(QStringLiteral("http://www.kde.org")));
QPushButton *scrollToButton = new QPushButton(QStringLiteral("Scroll to Attachment"), this);
vboxLayout->addWidget(scrollToButton);
connect(scrollToButton, &QPushButton::clicked, this,
&TestWebEngineScrollAddAttachment::slotScrollToAttachment);
}
void TestWebEngineScrollAddAttachment::slotScrollToAttachment()
{
mTestWebEngine->scrollToAnchor(QStringLiteral("module"));
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestWebEngineScrollAddAttachment *testWebEngine = new TestWebEngineScrollAddAttachment;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h b/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
index c11ebb3a..f233b05f 100644
--- a/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
+++ b/messageviewer/src/viewer/webengine/tests/testwebenginescrolladdattachment.h
@@ -1,39 +1,39 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTWEBENGINESCROLLADDATTACHMENT_H
#define TESTWEBENGINESCROLLADDATTACHMENT_H
#include <QWidget>
namespace MessageViewer {
class MailWebEngineView;
}
class TestWebEngineScrollAddAttachment : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngineScrollAddAttachment(QWidget *parent = nullptr);
private Q_SLOTS:
void slotScrollToAttachment();
private:
MessageViewer::MailWebEngineView *mTestWebEngine;
};
#endif // TESTWEBENGINESCROLLADDATTACHMENT_H
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.cpp b/messageviewer/src/viewerplugins/tests/viewerplugin_gui.cpp
index 05e2053d..37d51848 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.cpp
+++ b/messageviewer/src/viewerplugins/tests/viewerplugin_gui.cpp
@@ -1,111 +1,111 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "viewerplugin_gui.h"
#include "viewerplugins/viewerplugintoolmanager.h"
#include <QStandardPaths>
#include <KActionCollection>
#include <QApplication>
#include <QTextEdit>
#include <QCommandLineParser>
#include <QVBoxLayout>
#include <QMenuBar>
#include <viewerplugins/viewerplugininterface.h>
ViewerPluginTest::ViewerPluginTest(QWidget *parent)
: QWidget(parent)
{
QMenuBar *menuBar = new QMenuBar(this);
QVBoxLayout *vbox = new QVBoxLayout(this);
vbox->addWidget(menuBar);
QTextEdit *textEdit = new QTextEdit(this);
vbox->addWidget(textEdit);
QWidget *toolManagerWidget = new QWidget(this);
vbox->addWidget(toolManagerWidget);
vbox = new QVBoxLayout;
- vbox->setMargin(0);
+ vbox->setContentsMargins(0, 0, 0, 0);
vbox->setSpacing(0);
toolManagerWidget->setLayout(vbox);
MessageViewer::ViewerPluginToolManager *toolManager
= new MessageViewer::ViewerPluginToolManager(toolManagerWidget, this);
connect(toolManager, &MessageViewer::ViewerPluginToolManager::activatePlugin, this,
&ViewerPluginTest::slotActivatePlugin);
toolManager->setPluginName(QStringLiteral("messageviewer"));
toolManager->setServiceTypeName(QStringLiteral("MessageViewer/ViewerPlugin"));
if (!toolManager->initializePluginList()) {
qDebug() << " Impossible to initialize plugins";
}
toolManager->setActionCollection(new KActionCollection(this));
toolManager->createView();
QMenu *menu = new QMenu(this);
menu->setTitle(QStringLiteral("tools"));
menu->addActions(toolManager->viewerPluginActionList(MessageViewer::ViewerPluginInterface::All));
menuBar->addMenu(menu);
menu = new QMenu(this);
menu->setTitle(QStringLiteral("selected tools"));
menu->addActions(toolManager->viewerPluginActionList(MessageViewer::ViewerPluginInterface::
NeedSelection));
menuBar->addMenu(menu);
menu = new QMenu(this);
menu->setTitle(QStringLiteral("message tools"));
menu->addActions(toolManager->viewerPluginActionList(MessageViewer::ViewerPluginInterface::
NeedMessage));
menuBar->addMenu(menu);
menu = new QMenu(this);
menu->setTitle(QStringLiteral("message tools and selected tools"));
MessageViewer::ViewerPluginInterface::SpecificFeatureTypes featureTypes;
featureTypes |= MessageViewer::ViewerPluginInterface::NeedMessage;
featureTypes |= MessageViewer::ViewerPluginInterface::NeedSelection;
featureTypes |= MessageViewer::ViewerPluginInterface::NeedUrl;
menu->addActions(toolManager->viewerPluginActionList(featureTypes));
menuBar->addMenu(menu);
}
ViewerPluginTest::~ViewerPluginTest()
{
}
void ViewerPluginTest::slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface)
{
interface->execute();
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
ViewerPluginTest *w = new ViewerPluginTest();
w->resize(800, 200);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h b/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
index f860aeed..a7a2aa57 100644
--- a/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
+++ b/messageviewer/src/viewerplugins/tests/viewerplugin_gui.h
@@ -1,39 +1,39 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef TEST_VIEWERPLUGIN_GUI_H
#define TEST_VIEWERPLUGIN_GUI_H
#include <QWidget>
namespace MessageViewer {
class ViewerPluginInterface;
}
class ViewerPluginTest : public QWidget
{
Q_OBJECT
public:
explicit ViewerPluginTest(QWidget *parent = nullptr);
~ViewerPluginTest();
private Q_SLOTS:
void slotActivatePlugin(MessageViewer::ViewerPluginInterface *interface);
};
#endif
diff --git a/messageviewer/src/viewerplugins/viewerplugin.cpp b/messageviewer/src/viewerplugins/viewerplugin.cpp
index 36fb75eb..569d82ce 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.cpp
+++ b/messageviewer/src/viewerplugins/viewerplugin.cpp
@@ -1,63 +1,63 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "viewerplugin.h"
using namespace MessageViewer;
class MessageViewer::ViewerPluginPrivate
{
public:
ViewerPluginPrivate()
{
}
bool mEnabled = false;
};
ViewerPlugin::ViewerPlugin(QObject *parent)
: QObject(parent)
, d(new MessageViewer::ViewerPluginPrivate)
{
}
ViewerPlugin::~ViewerPlugin()
{
delete d;
}
void ViewerPlugin::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
}
bool ViewerPlugin::hasConfigureDialog() const
{
return false;
}
void ViewerPlugin::setIsEnabled(bool enabled)
{
d->mEnabled = enabled;
}
bool ViewerPlugin::isEnabled() const
{
return d->mEnabled;
}
diff --git a/messageviewer/src/viewerplugins/viewerplugin.h b/messageviewer/src/viewerplugins/viewerplugin.h
index 80728b77..c018c8e4 100644
--- a/messageviewer/src/viewerplugins/viewerplugin.h
+++ b/messageviewer/src/viewerplugins/viewerplugin.h
@@ -1,52 +1,52 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef VIEWERPLUGIN_H
#define VIEWERPLUGIN_H
#include <QObject>
#include "messageviewer_export.h"
class KActionCollection;
namespace MessageViewer {
class ViewerPluginPrivate;
class ViewerPluginInterface;
class MESSAGEVIEWER_EXPORT ViewerPlugin : public QObject
{
Q_OBJECT
public:
explicit ViewerPlugin(QObject *parent = nullptr);
~ViewerPlugin();
Q_REQUIRED_RESULT virtual MessageViewer::ViewerPluginInterface *createView(QWidget *parent, KActionCollection *ac) = 0;
Q_REQUIRED_RESULT virtual QString viewerPluginName() const = 0;
virtual void showConfigureDialog(QWidget *parent = nullptr);
Q_REQUIRED_RESULT virtual bool hasConfigureDialog() const;
void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
Q_SIGNALS:
void configChanged();
private:
ViewerPluginPrivate *const d;
};
}
#endif // VIEWERPLUGIN_H
diff --git a/messageviewer/src/viewerplugins/viewerplugininterface.cpp b/messageviewer/src/viewerplugins/viewerplugininterface.cpp
index c0c4735e..29b17102 100644
--- a/messageviewer/src/viewerplugins/viewerplugininterface.cpp
+++ b/messageviewer/src/viewerplugins/viewerplugininterface.cpp
@@ -1,118 +1,118 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "viewerplugininterface.h"
#include <QAction>
using namespace MessageViewer;
class MessageViewer::ViewerPluginInterfacePrivate
{
public:
ViewerPluginInterfacePrivate()
{
}
};
ViewerPluginInterface::ViewerPluginInterface(QObject *parent)
: QObject(parent)
, d(new MessageViewer::ViewerPluginInterfacePrivate)
{
}
ViewerPluginInterface::~ViewerPluginInterface()
{
delete d;
}
void ViewerPluginInterface::execute()
{
showWidget();
}
void ViewerPluginInterface::setText(const QString &text)
{
Q_UNUSED(text);
// Reimplement in subclass.
}
QList<QAction *> ViewerPluginInterface::actions() const
{
// Reimplement in subclass
return {};
}
void ViewerPluginInterface::setUrl(const QUrl &url)
{
Q_UNUSED(url);
// Reimplement in subclass
}
void ViewerPluginInterface::setMessage(const KMime::Message::Ptr &value)
{
Q_UNUSED(value);
// Reimplement in subclass
}
void ViewerPluginInterface::setMessageItem(const Akonadi::Item &item)
{
Q_UNUSED(item);
// Reimplement in subclass
}
void ViewerPluginInterface::setCurrentCollection(const Akonadi::Collection &col)
{
Q_UNUSED(col);
// Reimplement in subclass
}
void ViewerPluginInterface::closePlugin()
{
// Reimplement in subclass
}
void ViewerPluginInterface::showWidget()
{
// Reimplement in subclass
}
void ViewerPluginInterface::updateAction(const Akonadi::Item &item)
{
Q_UNUSED(item);
// Reimplement in subclass
}
void ViewerPluginInterface::refreshActionList(KActionCollection *ac)
{
//TODO
Q_UNUSED(ac);
}
void ViewerPluginInterface::addHelpTextAction(QAction *act, const QString &text)
{
act->setStatusTip(text);
act->setToolTip(text);
if (act->whatsThis().isEmpty()) {
act->setWhatsThis(text);
}
}
void ViewerPluginInterface::slotActivatePlugin()
{
Q_EMIT activatePlugin(this);
}
diff --git a/messageviewer/src/viewerplugins/viewerplugininterface.h b/messageviewer/src/viewerplugins/viewerplugininterface.h
index d1828107..a4be5558 100644
--- a/messageviewer/src/viewerplugins/viewerplugininterface.h
+++ b/messageviewer/src/viewerplugins/viewerplugininterface.h
@@ -1,76 +1,76 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef VIEWERPLUGININTERFACE_H
#define VIEWERPLUGININTERFACE_H
#include <QObject>
#include "messageviewer_export.h"
#include <KMime/Message>
#include <AkonadiCore/Item>
class QAction;
class KActionCollection;
namespace MessageViewer {
class ViewerPluginInterfacePrivate;
class MESSAGEVIEWER_EXPORT ViewerPluginInterface : public QObject
{
Q_OBJECT
public:
explicit ViewerPluginInterface(QObject *parent = nullptr);
~ViewerPluginInterface();
enum SpecificFeatureType {
None = 0,
NeedSelection = 2,
NeedMessage = 4,
NeedUrl = 8,
All = 16
};
Q_FLAGS(SpecificFeatureTypes)
Q_DECLARE_FLAGS(SpecificFeatureTypes, SpecificFeatureType)
virtual void execute();
virtual void setText(const QString &text);
virtual QList<QAction *> actions() const;
virtual void setUrl(const QUrl &url);
virtual void setMessage(const KMime::Message::Ptr &value);
virtual void setMessageItem(const Akonadi::Item &item);
virtual void setCurrentCollection(const Akonadi::Collection &col);
virtual void closePlugin();
virtual ViewerPluginInterface::SpecificFeatureTypes featureTypes() const = 0;
virtual void updateAction(const Akonadi::Item &item);
virtual void refreshActionList(KActionCollection *ac);
protected:
virtual void showWidget();
void addHelpTextAction(QAction *act, const QString &text);
protected Q_SLOTS:
void slotActivatePlugin();
Q_SIGNALS:
void activatePlugin(MessageViewer::ViewerPluginInterface *);
private:
ViewerPluginInterfacePrivate *const d;
};
}
#endif // VIEWERPLUGININTERFACE_H
diff --git a/messageviewer/src/viewerplugins/viewerpluginmanager.cpp b/messageviewer/src/viewerplugins/viewerpluginmanager.cpp
index eafaf7f9..6e5fed62 100644
--- a/messageviewer/src/viewerplugins/viewerpluginmanager.cpp
+++ b/messageviewer/src/viewerplugins/viewerpluginmanager.cpp
@@ -1,257 +1,257 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "viewerpluginmanager.h"
#include "viewerplugin.h"
#include "messageviewer_debug.h"
#include <KSharedConfig>
#include <kpluginmetadata.h>
#include <KPluginLoader>
#include <KPluginFactory>
#include <QFileInfo>
#include <QSet>
using namespace MessageViewer;
class ViewerPluginInfo
{
public:
ViewerPluginInfo()
{
}
QString metaDataFileNameBaseName;
QString metaDataFileName;
PimCommon::PluginUtilData pluginData;
MessageViewer::ViewerPlugin *plugin = nullptr;
bool isEnabled = false;
};
class MessageViewer::ViewerPluginManagerPrivate
{
public:
explicit ViewerPluginManagerPrivate(ViewerPluginManager *qq)
: q(qq)
{
}
bool initializePluginList();
void loadPlugin(ViewerPluginInfo *item);
QVector<MessageViewer::ViewerPlugin *> pluginsList() const;
QVector<PimCommon::PluginUtilData> pluginDataList() const;
QString serviceTypeName;
QString pluginName;
QString configGroupName() const;
QString configPrefixSettingKey() const;
ViewerPlugin *pluginFromIdentifier(const QString &id);
private:
QVector<ViewerPluginInfo> mPluginList;
QVector<PimCommon::PluginUtilData> mPluginDataList;
ViewerPluginManager *q;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("2.0");
}
}
QString ViewerPluginManagerPrivate::configGroupName() const
{
return QStringLiteral("PluginMessageViewer%1").arg(pluginName);
}
QString ViewerPluginManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("MessageViewerPlugins");
}
bool ViewerPluginManagerPrivate::initializePluginList()
{
if (!mPluginList.isEmpty()) {
return true;
}
if (serviceTypeName.isEmpty() || pluginName.isEmpty()) {
return false;
}
static const QString s_serviceTypeName = serviceTypeName;
QVector<KPluginMetaData> plugins
= KPluginLoader::findPlugins(pluginName, [](const KPluginMetaData &md) {
return md.serviceTypes().contains(s_serviceTypeName);
});
// We need common plugin to avoid to duplicate code between akregator/kmail
plugins
+= KPluginLoader::findPlugins(QStringLiteral("messageviewer"), [](
const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("MessageViewer/ViewerCommonPlugin"));
});
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(
configGroupName(), configPrefixSettingKey());
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
while (i.hasPrevious()) {
ViewerPluginInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first,
pair.second,
info.pluginData.mEnableByDefault,
info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(MESSAGEVIEWER_LOG) << "Plugin name :" << data.name()
<< " doesn't have correct plugin version. Please update it";
}
}
QVector<ViewerPluginInfo>::iterator end(mPluginList.end());
for (QVector<ViewerPluginInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
return true;
}
void ViewerPluginManagerPrivate::loadPlugin(ViewerPluginInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<MessageViewer::ViewerPlugin>(q,
QVariantList()
<< item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
QVector<ViewerPlugin *> ViewerPluginManagerPrivate::pluginsList() const
{
QVector<MessageViewer::ViewerPlugin *> lst;
QVector<ViewerPluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<ViewerPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
QVector<PimCommon::PluginUtilData> ViewerPluginManagerPrivate::pluginDataList() const
{
return mPluginDataList;
}
ViewerPlugin *ViewerPluginManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<ViewerPluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<ViewerPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
ViewerPluginManager::ViewerPluginManager(QObject *parent)
: QObject(parent)
, d(new MessageViewer::ViewerPluginManagerPrivate(this))
{
}
MessageViewer::ViewerPluginManager::~ViewerPluginManager()
{
delete d;
}
bool ViewerPluginManager::initializePluginList()
{
return d->initializePluginList();
}
ViewerPluginManager *ViewerPluginManager::self()
{
static ViewerPluginManager s_self;
return &s_self;
}
QVector<MessageViewer::ViewerPlugin *> ViewerPluginManager::pluginsList() const
{
return d->pluginsList();
}
void ViewerPluginManager::setServiceTypeName(const QString &serviceName)
{
d->serviceTypeName = serviceName;
}
QString ViewerPluginManager::serviceTypeName() const
{
return d->serviceTypeName;
}
void ViewerPluginManager::setPluginName(const QString &pluginName)
{
d->pluginName = pluginName;
}
QString ViewerPluginManager::pluginName() const
{
return d->pluginName;
}
QVector<PimCommon::PluginUtilData> ViewerPluginManager::pluginsDataList() const
{
return d->pluginDataList();
}
QString ViewerPluginManager::configGroupName() const
{
return d->configGroupName();
}
QString ViewerPluginManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
MessageViewer::ViewerPlugin *ViewerPluginManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/messageviewer/src/viewerplugins/viewerpluginmanager.h b/messageviewer/src/viewerplugins/viewerpluginmanager.h
index 18bc30e0..d9f82daf 100644
--- a/messageviewer/src/viewerplugins/viewerpluginmanager.h
+++ b/messageviewer/src/viewerplugins/viewerpluginmanager.h
@@ -1,58 +1,58 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef VIEWERPLUGINMANAGER_H
#define VIEWERPLUGINMANAGER_H
#include <QObject>
#include "messageviewer_export.h"
#include <PimCommon/PluginUtil>
namespace MessageViewer {
class ViewerPluginManagerPrivate;
class ViewerPlugin;
class MESSAGEVIEWER_EXPORT ViewerPluginManager : public QObject
{
Q_OBJECT
public:
explicit ViewerPluginManager(QObject *parent = nullptr);
~ViewerPluginManager();
static ViewerPluginManager *self();
Q_REQUIRED_RESULT QVector<MessageViewer::ViewerPlugin *> pluginsList() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
void setServiceTypeName(const QString &serviceName);
Q_REQUIRED_RESULT QString serviceTypeName() const;
void setPluginName(const QString &pluginName);
Q_REQUIRED_RESULT QString pluginName() const;
Q_REQUIRED_RESULT bool initializePluginList();
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT MessageViewer::ViewerPlugin *pluginFromIdentifier(const QString &id);
private:
Q_DISABLE_COPY(ViewerPluginManager)
ViewerPluginManagerPrivate *const d;
};
}
#endif // VIEWERPLUGINMANAGER_H
diff --git a/messageviewer/src/viewerplugins/viewerplugintoolmanager.cpp b/messageviewer/src/viewerplugins/viewerplugintoolmanager.cpp
index 0a750e90..5c36cfcd 100644
--- a/messageviewer/src/viewerplugins/viewerplugintoolmanager.cpp
+++ b/messageviewer/src/viewerplugins/viewerplugintoolmanager.cpp
@@ -1,198 +1,198 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "viewerplugintoolmanager.h"
#include "viewerpluginmanager.h"
#include "viewerplugin.h"
#include "viewerplugininterface.h"
#include <QVector>
using namespace MessageViewer;
class MessageViewer::ViewerPluginToolManagerPrivate
{
public:
ViewerPluginToolManagerPrivate(ViewerPluginToolManager *qq, QWidget *parentWidget)
: mParentWidget(parentWidget)
, q(qq)
{
}
void setServiceTypeName(const QString &serviceName);
QString serviceTypeName() const;
void setPluginName(const QString &pluginName);
QString pluginName() const;
void createView();
void refreshActionList();
void closeAllTools();
void setActionCollection(KActionCollection *ac);
void updateActions(const Akonadi::Item &messageItem);
QList<QAction *> actionList(ViewerPluginInterface::SpecificFeatureTypes features) const;
QList<MessageViewer::ViewerPluginInterface *> mListInterface;
KActionCollection *mActionCollection = nullptr;
QWidget *mParentWidget = nullptr;
ViewerPluginToolManager *q = nullptr;
};
void ViewerPluginToolManagerPrivate::setServiceTypeName(const QString &serviceName)
{
MessageViewer::ViewerPluginManager::self()->setServiceTypeName(serviceName);
}
QString ViewerPluginToolManagerPrivate::serviceTypeName() const
{
return MessageViewer::ViewerPluginManager::self()->serviceTypeName();
}
void ViewerPluginToolManagerPrivate::setPluginName(const QString &pluginName)
{
MessageViewer::ViewerPluginManager::self()->setPluginName(pluginName);
}
QString ViewerPluginToolManagerPrivate::pluginName() const
{
return MessageViewer::ViewerPluginManager::self()->pluginName();
}
void ViewerPluginToolManagerPrivate::refreshActionList()
{
for (MessageViewer::ViewerPluginInterface *interface : qAsConst(mListInterface)) {
interface->refreshActionList(mActionCollection);
}
}
void ViewerPluginToolManagerPrivate::createView()
{
const QVector<MessageViewer::ViewerPlugin *> listPlugin
= MessageViewer::ViewerPluginManager::self()->pluginsList();
for (MessageViewer::ViewerPlugin *plugin : listPlugin) {
if (plugin->isEnabled()) {
MessageViewer::ViewerPluginInterface *interface = plugin->createView(mParentWidget,
mActionCollection);
q->connect(interface, &MessageViewer::ViewerPluginInterface::activatePlugin, q,
&ViewerPluginToolManager::activatePlugin);
q->connect(plugin, &ViewerPlugin::configChanged, q,
&ViewerPluginToolManager::refreshActionList);
mListInterface.append(interface);
}
}
}
void ViewerPluginToolManagerPrivate::closeAllTools()
{
for (MessageViewer::ViewerPluginInterface *interface : qAsConst(mListInterface)) {
interface->closePlugin();
}
}
void ViewerPluginToolManagerPrivate::setActionCollection(KActionCollection *ac)
{
mActionCollection = ac;
}
QList<QAction *> ViewerPluginToolManagerPrivate::actionList(
ViewerPluginInterface::SpecificFeatureTypes features) const
{
QList<QAction *> lstAction;
for (MessageViewer::ViewerPluginInterface *interface : qAsConst(mListInterface)) {
if (features & ViewerPluginInterface::All) {
lstAction.append(interface->actions());
} else {
if (interface->featureTypes() & features) {
lstAction.append(interface->actions());
}
}
}
return lstAction;
}
void ViewerPluginToolManagerPrivate::updateActions(const Akonadi::Item &messageItem)
{
for (MessageViewer::ViewerPluginInterface *interface : qAsConst(mListInterface)) {
interface->updateAction(messageItem);
}
}
ViewerPluginToolManager::ViewerPluginToolManager(QWidget *parentWidget, QObject *parent)
: QObject(parent)
, d(new MessageViewer::ViewerPluginToolManagerPrivate(this, parentWidget))
{
}
ViewerPluginToolManager::~ViewerPluginToolManager()
{
delete d;
}
void ViewerPluginToolManager::closeAllTools()
{
d->closeAllTools();
}
void ViewerPluginToolManager::refreshActionList()
{
d->refreshActionList();
}
void ViewerPluginToolManager::createView()
{
d->createView();
}
void ViewerPluginToolManager::setActionCollection(KActionCollection *ac)
{
d->setActionCollection(ac);
}
void ViewerPluginToolManager::setServiceTypeName(const QString &serviceName)
{
d->setServiceTypeName(serviceName);
}
QString ViewerPluginToolManager::serviceTypeName() const
{
return d->serviceTypeName();
}
void ViewerPluginToolManager::setPluginName(const QString &pluginName)
{
d->setPluginName(pluginName);
}
QString ViewerPluginToolManager::pluginName() const
{
return d->pluginName();
}
bool ViewerPluginToolManager::initializePluginList()
{
return MessageViewer::ViewerPluginManager::self()->initializePluginList();
}
QList<QAction *> ViewerPluginToolManager::viewerPluginActionList(
ViewerPluginInterface::SpecificFeatureTypes features) const
{
return d->actionList(features);
}
void ViewerPluginToolManager::updateActions(const Akonadi::Item &messageItem)
{
d->updateActions(messageItem);
}
diff --git a/messageviewer/src/viewerplugins/viewerplugintoolmanager.h b/messageviewer/src/viewerplugins/viewerplugintoolmanager.h
index 28dc1c10..36385fcf 100644
--- a/messageviewer/src/viewerplugins/viewerplugintoolmanager.h
+++ b/messageviewer/src/viewerplugins/viewerplugintoolmanager.h
@@ -1,65 +1,65 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef VIEWERPLUGINTOOLMANAGER_H
#define VIEWERPLUGINTOOLMANAGER_H
#include <Item>
#include <QObject>
#include "messageviewer_export.h"
#include "viewerplugininterface.h"
class KActionCollection;
class QAction;
namespace MessageViewer {
class ViewerPluginToolManagerPrivate;
class ViewerPluginInterface;
class MESSAGEVIEWER_EXPORT ViewerPluginToolManager : public QObject
{
Q_OBJECT
public:
explicit ViewerPluginToolManager(QWidget *parentWidget, QObject *parent = nullptr);
~ViewerPluginToolManager();
void closeAllTools();
void createView();
void setActionCollection(KActionCollection *ac);
void setServiceTypeName(const QString &serviceName);
Q_REQUIRED_RESULT QString serviceTypeName() const;
void setPluginName(const QString &pluginName);
Q_REQUIRED_RESULT QString pluginName() const;
Q_REQUIRED_RESULT bool initializePluginList();
Q_REQUIRED_RESULT QList<QAction *> viewerPluginActionList(ViewerPluginInterface::SpecificFeatureTypes features) const;
void updateActions(const Akonadi::Item &messageItem);
/**
* @brief refreshActionList Refresh the list of action menu.
*/
void refreshActionList();
Q_SIGNALS:
void activatePlugin(MessageViewer::ViewerPluginInterface *);
private:
ViewerPluginToolManagerPrivate *const d;
};
}
#endif // VIEWERPLUGINTOOLMANAGER_H
diff --git a/messageviewer/src/widgets/autotests/CMakeLists.txt b/messageviewer/src/widgets/autotests/CMakeLists.txt
index 45e6d780..323d893a 100644
--- a/messageviewer/src/widgets/autotests/CMakeLists.txt
+++ b/messageviewer/src/widgets/autotests/CMakeLists.txt
@@ -1,12 +1,13 @@
macro(add_messageviewer_widget_unittest _source)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source}
TEST_NAME ${_name}
NAME_PREFIX "messageviewer-"
LINK_LIBRARIES KF5::MessageViewer KF5::WebEngineViewer KF5::Libkleo QGpgme Qt5::Test
)
endmacro ()
add_messageviewer_widget_unittest(mailtrackingwarningwidgettest.cpp)
add_messageviewer_widget_unittest(mailtrackingdetailsdialogtest.cpp)
+add_messageviewer_widget_unittest(shownextmessagewidgettest.cpp)
diff --git a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp b/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp
index 9d49b0d0..67ca6239 100644
--- a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp
+++ b/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "mailtrackingdetailsdialogtest.h"
#include "widgets/mailtrackingdetailsdialog.h"
#include <QTest>
#include <QStandardPaths>
QTEST_MAIN(MailTrackingDetailsDialogTest)
MailTrackingDetailsDialogTest::MailTrackingDetailsDialogTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
void MailTrackingDetailsDialogTest::shouldHaveDefaultValue()
{
//TOOD
}
diff --git a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.h b/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.h
index 81b17731..caa34a57 100644
--- a/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.h
+++ b/messageviewer/src/widgets/autotests/mailtrackingdetailsdialogtest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MAILTRACKINGDETAILSDIALOGTEST_H
#define MAILTRACKINGDETAILSDIALOGTEST_H
#include <QObject>
class MailTrackingDetailsDialogTest : public QObject
{
Q_OBJECT
public:
explicit MailTrackingDetailsDialogTest(QObject *parent = nullptr);
~MailTrackingDetailsDialogTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // MAILTRACKINGDETAILSDIALOGTEST_H
diff --git a/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.cpp b/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.cpp
index 8d0a9dd8..85a532e7 100644
--- a/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.cpp
+++ b/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.cpp
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "mailtrackingwarningwidgettest.h"
#include "widgets/mailtrackingwarningwidget.h"
#include <QTest>
QTEST_MAIN(MailTrackingWarningWidgetTest)
MailTrackingWarningWidgetTest::MailTrackingWarningWidgetTest(QObject *parent)
: QObject(parent)
{
}
void MailTrackingWarningWidgetTest::shouldHaveDefaultValues()
{
MessageViewer::MailTrackingWarningWidget w;
QVERIFY(!w.isVisible());
QVERIFY(!w.isCloseButtonVisible());
QVERIFY(w.wordWrap());
QVERIFY(!w.text().isEmpty());
}
diff --git a/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.h b/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.h
index f8724aa9..df061437 100644
--- a/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.h
+++ b/messageviewer/src/widgets/autotests/mailtrackingwarningwidgettest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MAILTRACKINGWARNINGWIDGETTEST_H
#define MAILTRACKINGWARNINGWIDGETTEST_H
#include <QObject>
class MailTrackingWarningWidgetTest : public QObject
{
Q_OBJECT
public:
explicit MailTrackingWarningWidgetTest(QObject *parent = nullptr);
~MailTrackingWarningWidgetTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValues();
};
#endif // MAILTRACKINGWARNINGWIDGETTEST_H
diff --git a/messageviewer/src/widgets/autotests/shownextmessagewidgettest.cpp b/messageviewer/src/widgets/autotests/shownextmessagewidgettest.cpp
new file mode 100644
index 00000000..0a5a0069
--- /dev/null
+++ b/messageviewer/src/widgets/autotests/shownextmessagewidgettest.cpp
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2019 Laurent Montel <montel@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.
+*/
+
+#include "shownextmessagewidgettest.h"
+#include "widgets/shownextmessagewidget.h"
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QTest>
+QTEST_MAIN(ShowNextMessageWidgetTest)
+ShowNextMessageWidgetTest::ShowNextMessageWidgetTest(QObject *parent)
+ : QObject(parent)
+{
+
+}
+
+void ShowNextMessageWidgetTest::shouldHaveDefaultValue()
+{
+ MessageViewer::ShowNextMessageWidget w;
+
+
+ QHBoxLayout *mainLayout = w.findChild<QHBoxLayout *>(QStringLiteral("mainlayout"));
+ QVERIFY(mainLayout);
+ //mainLayout->setContentsMargins(0, 0, 0, 0);
+
+ QPushButton *mPreviousMessage = w.findChild<QPushButton *>(QStringLiteral("previous_message"));
+ QVERIFY(mPreviousMessage);
+ QVERIFY(!mPreviousMessage->text().isEmpty());
+ QVERIFY(!mPreviousMessage->isEnabled());
+
+ QPushButton *mNextMessage = w.findChild<QPushButton *>(QStringLiteral("next_message"));
+ QVERIFY(mNextMessage);
+ QVERIFY(!mNextMessage->text().isEmpty());
+ QVERIFY(!mNextMessage->isEnabled());
+}
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/messageviewer/src/widgets/autotests/shownextmessagewidgettest.h
similarity index 72%
copy from webengineviewer/src/autotests/zoomactionmenutest.h
copy to messageviewer/src/widgets/autotests/shownextmessagewidgettest.h
index 000e7ca0..f8cad3da 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/messageviewer/src/widgets/autotests/shownextmessagewidgettest.h
@@ -1,36 +1,34 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2019 Laurent Montel <montel@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.
*/
-
-#ifndef ZOOMACTIONMENUTEST_H
-#define ZOOMACTIONMENUTEST_H
+#ifndef SHOWNEXTMESSAGEWIDGETTEST_H
+#define SHOWNEXTMESSAGEWIDGETTEST_H
#include <QObject>
-class ZoomActionMenuTest : public QObject
+class ShowNextMessageWidgetTest : public QObject
{
Q_OBJECT
public:
- explicit ZoomActionMenuTest(QObject *parent = nullptr);
- ~ZoomActionMenuTest();
+ explicit ShowNextMessageWidgetTest(QObject *parent = nullptr);
+ ~ShowNextMessageWidgetTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
- void shouldAssignZoomFactor();
};
-#endif // ZOOMACTIONMENUTEST_H
+#endif // SHOWNEXTMESSAGEWIDGETTEST_H
diff --git a/messageviewer/src/widgets/configurewidget.cpp b/messageviewer/src/widgets/configurewidget.cpp
index 1ababa31..a3c8088c 100644
--- a/messageviewer/src/widgets/configurewidget.cpp
+++ b/messageviewer/src/widgets/configurewidget.cpp
@@ -1,122 +1,122 @@
/*
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#include "configurewidget.h"
#include "messageviewer_debug.h"
#include "ui_settings.h"
#include "utils/messageviewerutil.h"
#include "settings/messageviewersettings.h"
#include <MimeTreeParser/NodeHelper>
#include "MessageCore/MessageCoreSettings"
#include <KLocalizedString>
using namespace MessageViewer;
class MessageViewer::ConfigureWidgetPrivate
{
public:
ConfigureWidgetPrivate()
{
}
~ConfigureWidgetPrivate()
{
delete mSettingsUi;
mSettingsUi = nullptr;
}
Ui_Settings *mSettingsUi = nullptr;
};
ConfigureWidget::ConfigureWidget(QWidget *parent)
: QWidget(parent)
, d(new MessageViewer::ConfigureWidgetPrivate)
{
d->mSettingsUi = new Ui_Settings;
d->mSettingsUi->setupUi(this);
layout()->setContentsMargins(0, 0, 0, 0);
QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false);
encodings.prepend(i18n("Auto"));
d->mSettingsUi->overrideCharacterEncoding->addItems(encodings);
d->mSettingsUi->overrideCharacterEncoding->setCurrentIndex(0);
d->mSettingsUi->overrideCharacterEncoding->setWhatsThis(
MessageCore::MessageCoreSettings::self()->overrideCharacterEncodingItem()->whatsThis());
d->mSettingsUi->kcfg_ShrinkQuotes->setWhatsThis(
MessageViewer::MessageViewerSettings::self()->shrinkQuotesItem()->whatsThis());
d->mSettingsUi->kcfg_ShowExpandQuotesMark->setWhatsThis(
MessageViewer::MessageViewerSettings::self()->showExpandQuotesMarkItem()->whatsThis());
- connect(d->mSettingsUi->overrideCharacterEncoding, QOverload<int>::of(&KComboBox::currentIndexChanged), this,
+ connect(d->mSettingsUi->overrideCharacterEncoding, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureWidget::settingsChanged);
}
ConfigureWidget::~ConfigureWidget()
{
delete d;
}
void ConfigureWidget::readConfig()
{
readCurrentOverrideCodec();
d->mSettingsUi->kcfg_CollapseQuoteLevelSpin->setEnabled(
MessageViewer::MessageViewerSettings::self()->showExpandQuotesMark());
}
void ConfigureWidget::writeConfig()
{
MessageCore::MessageCoreSettings::self()->setOverrideCharacterEncoding(
d->mSettingsUi->overrideCharacterEncoding->currentIndex() == 0
? QString()
: MimeTreeParser::NodeHelper::encodingForName(d->mSettingsUi->overrideCharacterEncoding->
currentText()));
}
void ConfigureWidget::readCurrentOverrideCodec()
{
const QString &currentOverrideEncoding
= MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding();
if (currentOverrideEncoding.isEmpty()) {
d->mSettingsUi->overrideCharacterEncoding->setCurrentIndex(0);
return;
}
QStringList encodings = MimeTreeParser::NodeHelper::supportedEncodings(false);
encodings.prepend(i18n("Auto"));
QStringList::ConstIterator it(encodings.constBegin());
const QStringList::ConstIterator end(encodings.constEnd());
int i = 0;
for (; it != end; ++it) {
if (MimeTreeParser::NodeHelper::encodingForName(*it) == currentOverrideEncoding) {
d->mSettingsUi->overrideCharacterEncoding->setCurrentIndex(i);
break;
}
++i;
}
if (i == encodings.size()) {
// the current value of overrideCharacterEncoding is an unknown encoding => reset to Auto
qCWarning(MESSAGEVIEWER_LOG) << "Unknown override character encoding"
<< currentOverrideEncoding
<< ". Resetting to Auto.";
d->mSettingsUi->overrideCharacterEncoding->setCurrentIndex(0);
MessageCore::MessageCoreSettings::self()->setOverrideCharacterEncoding(QString());
}
}
diff --git a/messageviewer/src/widgets/htmlstatusbar.cpp b/messageviewer/src/widgets/htmlstatusbar.cpp
index 91c9f952..98c98be1 100644
--- a/messageviewer/src/widgets/htmlstatusbar.cpp
+++ b/messageviewer/src/widgets/htmlstatusbar.cpp
@@ -1,216 +1,217 @@
/* -*- c++ -*-
htmlstatusbar.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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.
+ 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.
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.
*/
#include "htmlstatusbar.h"
#include "settings/messageviewersettings.h"
#include "MessageCore/MessageCoreSettings"
#include <KLocalizedString>
#include <kconfiggroup.h>
#include <KSharedConfig>
#include <QMouseEvent>
using namespace MessageViewer;
HtmlStatusBar::HtmlStatusBar(QWidget *parent)
: QLabel(parent)
, mMode(MimeTreeParser::Util::Normal)
{
setAlignment(Qt::AlignHCenter | Qt::AlignTop);
setAutoFillBackground(true);
setCursor(QCursor(Qt::PointingHandCursor));
update();
}
HtmlStatusBar::~HtmlStatusBar()
{
}
MimeTreeParser::Util::HtmlMode HtmlStatusBar::mode() const
{
return mMode;
}
bool HtmlStatusBar::isHtml() const
{
return mode() == MimeTreeParser::Util::Html;
}
bool HtmlStatusBar::isNormal() const
{
return mode() == MimeTreeParser::Util::Normal;
}
void HtmlStatusBar::update()
{
QPalette pal = palette();
pal.setColor(backgroundRole(), bgColor());
pal.setColor(foregroundRole(), fgColor());
setPalette(pal);
setText(message());
setToolTip(toolTip());
}
void HtmlStatusBar::setNormalMode()
{
setMode(MimeTreeParser::Util::Normal);
}
void HtmlStatusBar::setHtmlMode()
{
setMode(MimeTreeParser::Util::Html);
}
void HtmlStatusBar::setAvailableModes(const QList<MimeTreeParser::Util::HtmlMode> &availableModes)
{
mAvailableModes = availableModes;
}
const QList< MimeTreeParser::Util::HtmlMode > &HtmlStatusBar::availableModes()
{
return mAvailableModes;
}
void HtmlStatusBar::setMode(MimeTreeParser::Util::HtmlMode m, UpdateMode mode)
{
if (mMode != m) {
mMode = m;
if (mode == Update) {
update();
}
}
}
void HtmlStatusBar::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
Q_EMIT clicked();
}
}
QString HtmlStatusBar::message() const
{
switch (mode()) {
case MimeTreeParser::Util::Html: // bold: "HTML Message"
case MimeTreeParser::Util::MultipartHtml:
return i18nc("'HTML Message' with html linebreaks between each letter and in bold text.",
"<qt><b><br />H<br />T<br />M<br />L<br /> "
"<br />M<br />e<br />s<br />s<br />a<br />g<br />e</b></qt>");
case MimeTreeParser::Util::Normal: // normal: "No HTML Message"
return i18nc("'No HTML Message' with html linebreaks between each letter.",
"<qt><br />N<br />o<br /> "
"<br />H<br />T<br />M<br />L<br /> "
"<br />M<br />e<br />s<br />s<br />a<br />g<br />e</qt>");
case MimeTreeParser::Util::MultipartPlain: // normal: "Plain Message"
return i18nc("'Plain Message' with html linebreaks between each letter.",
"<qt><br />P<br />l<br />a<br />i<br />n<br /> "
"<br />M<br />e<br />s<br />s<br />a<br />g<br />e<br /></qt>");
case MimeTreeParser::Util::MultipartIcal: // normal: "Calendar Message"
return i18nc("'Calendar Message' with html linebreaks between each letter.",
"<qt><br />C<br />a<br />l<br />e<br />n<br />d<br />a<br />r<br /> "
"<br />M<br />e<br />s<br />s<br />a<br />g<br />e<br /></qt>");
default:
return QString();
}
}
QString HtmlStatusBar::toolTip() const
{
switch (mode()) {
case MimeTreeParser::Util::Html:
case MimeTreeParser::Util::MultipartHtml:
case MimeTreeParser::Util::MultipartPlain:
case MimeTreeParser::Util::MultipartIcal:
return i18n("Click to toggle between HTML, plain text and calendar.");
default:
case MimeTreeParser::Util::Normal:
break;
}
return QString();
}
QColor HtmlStatusBar::fgColor() const
{
KConfigGroup conf(KSharedConfig::openConfig(), "Reader");
QColor defaultColor, color;
switch (mode()) {
case MimeTreeParser::Util::Html:
case MimeTreeParser::Util::MultipartHtml:
defaultColor = Qt::white;
color = defaultColor;
if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
color = conf.readEntry("ColorbarForegroundHTML", defaultColor);
}
return color;
case MimeTreeParser::Util::Normal:
case MimeTreeParser::Util::MultipartPlain:
case MimeTreeParser::Util::MultipartIcal:
defaultColor = Qt::black;
color = defaultColor;
if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
color = conf.readEntry("ColorbarForegroundPlain", defaultColor);
}
return color;
}
return Qt::black;
}
QColor HtmlStatusBar::bgColor() const
{
KConfigGroup conf(KSharedConfig::openConfig(), "Reader");
QColor defaultColor, color;
switch (mode()) {
case MimeTreeParser::Util::Html:
case MimeTreeParser::Util::MultipartHtml:
defaultColor = Qt::black;
color = defaultColor;
if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
color = conf.readEntry("ColorbarBackgroundHTML", defaultColor);
}
return color;
case MimeTreeParser::Util::Normal:
case MimeTreeParser::Util::MultipartPlain:
case MimeTreeParser::Util::MultipartIcal:
defaultColor = Qt::lightGray;
color = defaultColor;
if (!MessageCore::MessageCoreSettings::self()->useDefaultColors()) {
color = conf.readEntry("ColorbarBackgroundPlain", defaultColor);
}
return color;
}
return Qt::white;
}
diff --git a/messageviewer/src/widgets/htmlstatusbar.h b/messageviewer/src/widgets/htmlstatusbar.h
index 5d8eb536..7464d431 100644
--- a/messageviewer/src/widgets/htmlstatusbar.h
+++ b/messageviewer/src/widgets/htmlstatusbar.h
@@ -1,111 +1,112 @@
/* -*- c++ -*-
htmlstatusbar.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#ifndef _MESSAGEVIEWER_HTMLSTATUSBAR_H_
#define _MESSAGEVIEWER_HTMLSTATUSBAR_H_
#include <MimeTreeParser/Util>
#include "messageviewer/messageviewerutil.h"
#include <QLabel>
class QMouseEvent;
namespace MessageViewer {
/**
* @short The HTML statusbar widget for use with the reader.
*
* The HTML status bar is a small widget that acts as an indicator
* for the message content. It can be in one of four modes:
*
* <dl>
* <dt><code>Normal</code></dt>
* <dd>Default. No HTML.</dd>
* <dt><code>Html</code></dt>
* <dd>HTML content is being shown. Since HTML mails can mimic all sorts
* of KMail markup in the reader, this provides out-of-band information
* about the presence of (rendered) HTML.</dd>
* <dt><code>MultipartPlain</code></dt>
* <dd>Viewed as plain text with HTML part also available.</dd>
* <dt><code>MultipartHtml</code></dt>
* <dd>Viewed as Html with plain text part also available.</dd>
* </dl>
*
* @author Ingo Kloecker <kloecker@kde.org>, Marc Mutz <mutz@kde.org>
**/
class HtmlStatusBar : public QLabel
{
Q_OBJECT
public:
enum UpdateMode {
NoUpdate,
Update
};
explicit HtmlStatusBar(QWidget *parent = nullptr);
~HtmlStatusBar() override;
/** @return current mode. */
Q_REQUIRED_RESULT MimeTreeParser::Util::HtmlMode mode() const;
Q_REQUIRED_RESULT bool isHtml() const;
Q_REQUIRED_RESULT bool isNormal() const;
// Update the status bar, for example when the color scheme changed.
void update();
void setAvailableModes(const QList<MimeTreeParser::Util::HtmlMode> &availableModes);
Q_REQUIRED_RESULT const QList<MimeTreeParser::Util::HtmlMode> &availableModes();
public Q_SLOTS:
void setHtmlMode();
/** Switch to "normal mode". */
void setNormalMode();
/** Switch to mode @p m */
void setMode(MimeTreeParser::Util::HtmlMode m, UpdateMode mode = Update);
Q_SIGNALS:
/** The user has clicked the status bar. */
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override;
private:
QString message() const;
QString toolTip() const;
QColor bgColor() const;
QColor fgColor() const;
MimeTreeParser::Util::HtmlMode mMode;
QList<MimeTreeParser::Util::HtmlMode> mAvailableModes;
};
}
#endif // _KMAIL_HTMLSTATUSBAR_H_
diff --git a/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.cpp b/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.cpp
index 8bb84e26..fb6f5cb5 100644
--- a/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.cpp
+++ b/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.cpp
@@ -1,182 +1,183 @@
/*
*
* This file is part of KMail, the KDE mail client.
*
* Copyright (c) 2002-2003 Carsten Pfeiffer <pfeiffer@kde.org>
* Copyright (c) 2003 Zack Rusin <zack@kde.org>
*
* 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.
+ * 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.
*
* 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.
*/
#include "messageviewer_debug.h"
#include "mailsourceviewtextbrowserwidget.h"
#include "messageviewer/messageviewerutil.h"
#include "findbar/findbarsourceview.h"
#include "kpimtextedit/slidecontainer.h"
#include "PimCommon/PimUtil"
#include "kpimtextedit/texttospeechwidget.h"
#include "kpimtextedit/texttospeechinterface.h"
#include <KSyntaxHighlighting/SyntaxHighlighter>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Theme>
#include <KLocalizedString>
#include <KStandardAction>
#include <QAction>
#include <QIcon>
#include <KIconTheme>
#include <QShortcut>
#include <QVBoxLayout>
#include <QContextMenuEvent>
#include <QMenu>
#include <QFontDatabase>
#include <QPushButton>
using namespace MessageViewer;
MailSourceViewTextBrowserWidget::MailSourceViewTextBrowserWidget(const QString &syntax, QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *lay = new QVBoxLayout(this);
- lay->setMargin(0);
+ lay->setContentsMargins(0, 0, 0, 0);
mTextToSpeechWidget = new KPIMTextEdit::TextToSpeechWidget;
mTextToSpeechWidget->setObjectName(QStringLiteral("texttospeech"));
lay->addWidget(mTextToSpeechWidget);
KPIMTextEdit::TextToSpeechInterface *textToSpeechInterface
= new KPIMTextEdit::TextToSpeechInterface(mTextToSpeechWidget, this);
mTextBrowser = new MailSourceViewTextBrowser(textToSpeechInterface);
mTextBrowser->setObjectName(QStringLiteral("textbrowser"));
mTextBrowser->setLineWrapMode(QPlainTextEdit::NoWrap);
mTextBrowser->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
const KSyntaxHighlighting::Definition def = mRepo.definitionForName(syntax);
if (!def.isValid()) {
qCWarning(MESSAGEVIEWER_LOG) << "Invalid definition name";
}
KSyntaxHighlighting::SyntaxHighlighter *hl = new KSyntaxHighlighting::SyntaxHighlighter(
mTextBrowser->document());
hl->setTheme((palette().color(QPalette::Base).lightness() < 128)
? mRepo.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme)
: mRepo.defaultTheme(KSyntaxHighlighting::Repository::LightTheme));
hl->setDefinition(def);
connect(mTextBrowser, &MailSourceViewTextBrowser::findText, this,
&MailSourceViewTextBrowserWidget::slotFind);
lay->addWidget(mTextBrowser);
mSliderContainer = new KPIMTextEdit::SlideContainer(this);
mFindBar = new FindBarSourceView(mTextBrowser, this);
mFindBar->setObjectName(QStringLiteral("findbar"));
connect(mFindBar, &FindBarSourceView::hideFindBar, mSliderContainer,
&KPIMTextEdit::SlideContainer::slideOut);
mSliderContainer->setContent(mFindBar);
lay->addWidget(mSliderContainer);
QShortcut *shortcut = new QShortcut(this);
shortcut->setKey(Qt::Key_F + Qt::CTRL);
connect(shortcut, &QShortcut::activated, this, &MailSourceViewTextBrowserWidget::slotFind);
}
void MailSourceViewTextBrowserWidget::slotFind()
{
if (mTextBrowser->textCursor().hasSelection()) {
mFindBar->setText(mTextBrowser->textCursor().selectedText());
}
mSliderContainer->slideIn();
mFindBar->focusAndSetCursor();
}
void MailSourceViewTextBrowserWidget::setText(const QString &text)
{
mTextBrowser->setPlainText(text);
}
void MailSourceViewTextBrowserWidget::setPlainText(const QString &text)
{
mTextBrowser->setPlainText(text);
}
void MailSourceViewTextBrowserWidget::setFixedFont()
{
mTextBrowser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
MessageViewer::MailSourceViewTextBrowser *MailSourceViewTextBrowserWidget::textBrowser() const
{
return mTextBrowser;
}
MailSourceViewTextBrowser::MailSourceViewTextBrowser(
KPIMTextEdit::TextToSpeechInterface *textToSpeechInterface, QWidget *parent)
: QPlainTextEdit(parent)
, mTextToSpeechInterface(textToSpeechInterface)
{
}
void MailSourceViewTextBrowser::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *popup = createStandardContextMenu();
if (popup) {
popup->addSeparator();
popup->addAction(KStandardAction::find(this, &MailSourceViewTextBrowser::findText, this));
//Code from KTextBrowser
KIconTheme::assignIconsToContextMenu(isReadOnly() ? KIconTheme::ReadOnlyText
: KIconTheme::TextEditor,
popup->actions());
if (mTextToSpeechInterface->isReady()) {
popup->addSeparator();
popup->addAction(QIcon::fromTheme(QStringLiteral(
"preferences-desktop-text-to-speech")), i18n(
"Speak Text"), this, &MailSourceViewTextBrowser::slotSpeakText);
}
popup->addSeparator();
popup->addAction(KStandardAction::saveAs(this, &MailSourceViewTextBrowser::slotSaveAs,
this));
popup->exec(event->globalPos());
delete popup;
}
}
void MailSourceViewTextBrowser::slotSaveAs()
{
PimCommon::Util::saveTextAs(toPlainText(), QString(), this);
}
void MailSourceViewTextBrowser::slotSpeakText()
{
QString text;
if (textCursor().hasSelection()) {
text = textCursor().selectedText();
} else {
text = toPlainText();
}
mTextToSpeechInterface->say(text);
}
diff --git a/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.h b/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.h
index 8a250ccb..8fb6baa1 100644
--- a/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.h
+++ b/messageviewer/src/widgets/mailsourceviewtextbrowserwidget.h
@@ -1,91 +1,92 @@
/*
*
* This file is part of KMail, the KDE mail client.
*
* Copyright (c) 2002-2003 Carsten Pfeiffer <pfeiffer@kde.org>
* Copyright (c) 2003 Zack Rusin <zack@kde.org>
*
* 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.
+ * 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.
*
* 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.
*/
#ifndef MAILSOURCEVIEWTEXTBROWSERWIDGET_H
#define MAILSOURCEVIEWTEXTBROWSERWIDGET_H
#include <QSyntaxHighlighter>
#include <QPlainTextEdit>
#include <KSyntaxHighlighting/Repository>
namespace KPIMTextEdit {
class SlideContainer;
class TextToSpeechWidget;
class TextToSpeechInterface;
}
namespace MessageViewer {
class FindBarSourceView;
/**
* A tiny little class to use for displaying raw messages, textual
* attachments etc.
*
* Auto-deletes itself when closed.
*
* @author Carsten Pfeiffer <pfeiffer@kde.org>
*/
class MailSourceViewTextBrowser;
class MailSourceViewTextBrowserWidget : public QWidget
{
Q_OBJECT
public:
explicit MailSourceViewTextBrowserWidget(const QString &syntax, QWidget *parent = nullptr);
void setText(const QString &text);
void setPlainText(const QString &text);
void setFixedFont();
Q_REQUIRED_RESULT MessageViewer::MailSourceViewTextBrowser *textBrowser() const;
private:
void slotFind();
KSyntaxHighlighting::Repository mRepo;
MailSourceViewTextBrowser *mTextBrowser = nullptr;
FindBarSourceView *mFindBar = nullptr;
KPIMTextEdit::SlideContainer *mSliderContainer = nullptr;
KPIMTextEdit::TextToSpeechWidget *mTextToSpeechWidget = nullptr;
};
class MailSourceViewTextBrowser : public QPlainTextEdit
{
Q_OBJECT
public:
explicit MailSourceViewTextBrowser(KPIMTextEdit::TextToSpeechInterface *textToSpeechInterface, QWidget *parent = nullptr);
protected:
void contextMenuEvent(QContextMenuEvent *event) override;
Q_SIGNALS:
void findText();
private:
void slotSpeakText();
void slotSaveAs();
KPIMTextEdit::TextToSpeechInterface *mTextToSpeechInterface = nullptr;
};
}
#endif // MAILSOURCEVIEWTEXTBROWSERWIDGET_H
diff --git a/messageviewer/src/widgets/mailsourcewebengineviewer.cpp b/messageviewer/src/widgets/mailsourcewebengineviewer.cpp
index 36cc2b64..11de624e 100644
--- a/messageviewer/src/widgets/mailsourcewebengineviewer.cpp
+++ b/messageviewer/src/widgets/mailsourcewebengineviewer.cpp
@@ -1,147 +1,147 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "mailsourcewebengineviewer.h"
using namespace MessageViewer;
#include "mailsourceviewtextbrowserwidget.h"
#include "messageviewer/messageviewerutil.h"
#include "findbar/findbarsourceview.h"
#include "kpimtextedit/slidecontainer.h"
#include <KSyntaxHighlighting/SyntaxHighlighter>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Theme>
#include "PimCommon/PimUtil"
#include <kiconloader.h>
#include <KLocalizedString>
#include <kwindowsystem.h>
#include <QTabWidget>
#include <QApplication>
#include <KConfigGroup>
#include <QShortcut>
#include <QVBoxLayout>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QWebEnginePage>
using namespace MessageViewer;
MailSourceWebEngineViewer::MailSourceWebEngineViewer(QWidget *parent)
: QDialog(parent)
{
setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mRawBrowser = new MailSourceViewTextBrowserWidget(QStringLiteral("Email"), this);
#ifndef NDEBUG
mShowHtmlSource = true;
#endif
mShowHtmlSource = mShowHtmlSource || !qEnvironmentVariableIsEmpty("KDEPIM_DEBUGGING");
if (mShowHtmlSource) {
mTabWidget = new QTabWidget(this);
mainLayout->addWidget(mTabWidget);
mTabWidget->addTab(mRawBrowser, i18nc("Unchanged mail message", "Raw Source"));
mTabWidget->setTabToolTip(0,
i18n(
"Raw, unmodified mail as it is stored on the filesystem or on the server"));
mHtmlBrowser = new MailSourceViewTextBrowserWidget(QStringLiteral("HTML"), this);
mTabWidget->addTab(mHtmlBrowser, i18nc("Mail message as shown, in HTML format", "HTML Source"));
mTabWidget->setTabToolTip(1, i18n("HTML code for displaying the message to the user"));
mTabWidget->setCurrentIndex(0);
} else {
mainLayout->addWidget(mRawBrowser);
}
// combining the shortcuts in one qkeysequence() did not work...
QShortcut *shortcut = new QShortcut(this);
shortcut->setKey(Qt::Key_Escape);
connect(shortcut, &QShortcut::activated, this, &MailSourceWebEngineViewer::close);
shortcut = new QShortcut(this);
shortcut->setKey(Qt::Key_W + Qt::CTRL);
connect(shortcut, &QShortcut::activated, this, &MailSourceWebEngineViewer::close);
KWindowSystem::setIcons(winId(),
qApp->windowIcon().pixmap(IconSize(KIconLoader::Desktop),
IconSize(KIconLoader::Desktop)),
qApp->windowIcon().pixmap(IconSize(KIconLoader::Small),
IconSize(KIconLoader::Small)));
mRawBrowser->textBrowser()->setFocus();
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &MailSourceWebEngineViewer::reject);
connect(buttonBox->button(
QDialogButtonBox::Close), &QPushButton::clicked, this,
&MailSourceWebEngineViewer::close);
mainLayout->addWidget(buttonBox);
readConfig();
}
MailSourceWebEngineViewer::~MailSourceWebEngineViewer()
{
writeConfig();
}
void MailSourceWebEngineViewer::readConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "MailSourceWebEngineViewer");
const QSize size = group.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
void MailSourceWebEngineViewer::writeConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "MailSourceWebEngineViewer");
group.writeEntry("Size", size());
group.sync();
}
void MailSourceWebEngineViewer::setRawSource(const QString &source)
{
mRawBrowser->setText(source);
}
void MailSourceWebEngineViewer::setDisplayedSource(QWebEnginePage *page)
{
if (mShowHtmlSource) {
if (page) {
MailSourceViewTextBrowserWidget *browser = mHtmlBrowser;
page->toHtml([browser](const QString &result) {
browser->setPlainText(result);
});
}
}
}
void MailSourceWebEngineViewer::setFixedFont()
{
mRawBrowser->setFixedFont();
if (mShowHtmlSource) {
mHtmlBrowser->setFixedFont();
}
}
diff --git a/messageviewer/src/widgets/mailsourcewebengineviewer.h b/messageviewer/src/widgets/mailsourcewebengineviewer.h
index c20d1761..864d9f2f 100644
--- a/messageviewer/src/widgets/mailsourcewebengineviewer.h
+++ b/messageviewer/src/widgets/mailsourcewebengineviewer.h
@@ -1,51 +1,50 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILSOURCEWEBENGINEVIEWER_H
#define MAILSOURCEWEBENGINEVIEWER_H
#include <QDialog>
-#include "config-messageviewer.h"
class QTabWidget;
class QWebEnginePage;
namespace MessageViewer {
class FindBarSourceView;
class MailSourceViewTextBrowserWidget;
class MailSourceWebEngineViewer : public QDialog
{
Q_OBJECT
public:
explicit MailSourceWebEngineViewer(QWidget *parent = nullptr);
~MailSourceWebEngineViewer();
void setRawSource(const QString &source);
void setDisplayedSource(QWebEnginePage *page);
void setFixedFont();
private:
void readConfig();
void writeConfig();
MailSourceViewTextBrowserWidget *mRawBrowser = nullptr;
FindBarSourceView *mFindBar = nullptr;
QTabWidget *mTabWidget = nullptr;
MailSourceViewTextBrowserWidget *mHtmlBrowser = nullptr;
bool mShowHtmlSource = false;
};
}
#endif // MAILSOURCEWEBENGINEVIEWER_H
diff --git a/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp b/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp
index b2a2b806..cc07cb6c 100644
--- a/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp
+++ b/messageviewer/src/widgets/mailtrackingdetailsdialog.cpp
@@ -1,79 +1,79 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "mailtrackingdetailsdialog.h"
#include <QVBoxLayout>
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <KConfigGroup>
#include <QPushButton>
#include <KSharedConfig>
#include <KPIMTextEdit/RichTextEditorWidget>
using namespace MessageViewer;
MailTrackingDetailsDialog::MailTrackingDetailsDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(i18n("Details"));
setAttribute(Qt::WA_DeleteOnClose);
setModal(false);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setObjectName(QStringLiteral("mainLayout"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
buttonBox->setObjectName(QStringLiteral("buttonbox"));
connect(buttonBox, &QDialogButtonBox::rejected, this, &MailTrackingDetailsDialog::reject);
connect(buttonBox->button(
QDialogButtonBox::Close), &QPushButton::clicked, this,
&MailTrackingDetailsDialog::close);
mDetails = new KPIMTextEdit::RichTextEditorWidget(this);
mDetails->setObjectName(QStringLiteral("detail"));
mainLayout->addWidget(mDetails);
mainLayout->addWidget(buttonBox);
mDetails->setReadOnly(true);
readConfig();
}
MailTrackingDetailsDialog::~MailTrackingDetailsDialog()
{
writeConfig();
}
void MailTrackingDetailsDialog::readConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "MailTrackingDetailsDialog");
const QSize size = group.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
void MailTrackingDetailsDialog::writeConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "MailTrackingDetailsDialog");
group.writeEntry("Size", size());
group.sync();
}
void MailTrackingDetailsDialog::setDetails(const QString &details)
{
mDetails->setHtml(details);
}
diff --git a/messageviewer/src/widgets/mailtrackingdetailsdialog.h b/messageviewer/src/widgets/mailtrackingdetailsdialog.h
index 571712b4..e3b4dc97 100644
--- a/messageviewer/src/widgets/mailtrackingdetailsdialog.h
+++ b/messageviewer/src/widgets/mailtrackingdetailsdialog.h
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MAILTRACKINGDETAILSDIALOG_H
#define MAILTRACKINGDETAILSDIALOG_H
#include <QDialog>
#include "messageviewer_private_export.h"
namespace KPIMTextEdit {
class RichTextEditorWidget;
}
namespace MessageViewer {
class MESSAGEVIEWER_TESTS_EXPORT MailTrackingDetailsDialog : public QDialog
{
Q_OBJECT
public:
explicit MailTrackingDetailsDialog(QWidget *parent = nullptr);
~MailTrackingDetailsDialog();
void setDetails(const QString &details);
private:
void writeConfig();
void readConfig();
KPIMTextEdit::RichTextEditorWidget *mDetails = nullptr;
};
}
#endif // MAILTRACKINGDETAILSDIALOG_H
diff --git a/messageviewer/src/widgets/mailtrackingwarningwidget.cpp b/messageviewer/src/widgets/mailtrackingwarningwidget.cpp
index 821f61b2..e8b89f33 100644
--- a/messageviewer/src/widgets/mailtrackingwarningwidget.cpp
+++ b/messageviewer/src/widgets/mailtrackingwarningwidget.cpp
@@ -1,87 +1,87 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "mailtrackingwarningwidget.h"
#include "mailtrackingdetailsdialog.h"
#include <KLocalizedString>
using namespace MessageViewer;
MailTrackingWarningWidget::MailTrackingWarningWidget(QWidget *parent)
: KMessageWidget(parent)
{
setVisible(false);
setCloseButtonVisible(true);
setMessageType(Warning);
setWordWrap(true);
setText(i18n("Some Mail Tracker was found and was blocked.<a href=\"mailtrackingdetails\">(Details...)"));
connect(this, &MailTrackingWarningWidget::linkActivated, this,
&MailTrackingWarningWidget::slotShowDetails);
}
MailTrackingWarningWidget::~MailTrackingWarningWidget()
{
}
void MailTrackingWarningWidget::slotShowDetails(const QString &content)
{
if (content == QLatin1String("mailtrackingdetails")) {
if (!mMailTrackingDetailDialog) {
mMailTrackingDetailDialog = new MessageViewer::MailTrackingDetailsDialog;
}
mMailTrackingDetailDialog->setDetails(generateDetails());
mMailTrackingDetailDialog->show();
}
}
QString MailTrackingWarningWidget::generateDetails() const
{
QString details = QLatin1String("<b>") + i18n("Details:") + QLatin1String("</b><ul>");
QMapIterator<QString, blackListFound> i(mBackLists);
while (i.hasNext()) {
i.next();
details += QLatin1String("<li>") + i18np("1 tracker from the company %2 (%3)",
"%1 trackers from the company %2 (%3)",
i.value().number, i.key(), i.value().url);
}
details += QLatin1String("</ul>");
return details;
}
void MailTrackingWarningWidget::addTracker(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &tracker)
{
blackListFound item = mBackLists.value(tracker.mCompanyName);
if (item.url.isEmpty()) {
item.url = tracker.mCompanyUrl;
mBackLists.insert(tracker.mCompanyName, item);
} else {
item.number = item.number + 1;
mBackLists.insert(tracker.mCompanyName, item);
}
if (!isVisible()) {
animatedShow();
}
}
void MailTrackingWarningWidget::hideAndClear()
{
mBackLists.clear();
setVisible(false);
}
diff --git a/messageviewer/src/widgets/mailtrackingwarningwidget.h b/messageviewer/src/widgets/mailtrackingwarningwidget.h
index 3f788a00..e920a5ae 100644
--- a/messageviewer/src/widgets/mailtrackingwarningwidget.h
+++ b/messageviewer/src/widgets/mailtrackingwarningwidget.h
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef MAILTRACKINGWARNINGWIDGET_H
#define MAILTRACKINGWARNINGWIDGET_H
#include <KMessageWidget>
#include "messageviewer_private_export.h"
#include <QMap>
#include <QPointer>
#include <viewer/webengine/blockmailtrackingurlinterceptor/blockmailtrackingurlinterceptor.h>
namespace MessageViewer {
class MailTrackingDetailsDialog;
class MESSAGEVIEWER_TESTS_EXPORT MailTrackingWarningWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit MailTrackingWarningWidget(QWidget *parent = nullptr);
~MailTrackingWarningWidget();
void addTracker(const MessageViewer::BlockMailTrackingUrlInterceptor::MailTrackerBlackList &);
void hideAndClear();
private:
void slotShowDetails(const QString &content);
QString generateDetails() const;
struct blackListFound {
QString url;
int number = 1;
};
QMap<QString, blackListFound> mBackLists;
QPointer<MailTrackingDetailsDialog> mMailTrackingDetailDialog;
};
}
#endif // MAILTRACKINGWARNINGWIDGET_H
diff --git a/messageviewer/src/widgets/openattachmentfolderwidget.cpp b/messageviewer/src/widgets/openattachmentfolderwidget.cpp
index 876e6fbf..07a2e29d 100644
--- a/messageviewer/src/widgets/openattachmentfolderwidget.cpp
+++ b/messageviewer/src/widgets/openattachmentfolderwidget.cpp
@@ -1,98 +1,98 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "openattachmentfolderwidget.h"
#include <KLocalizedString>
#include <KIO/OpenFileManagerWindowJob>
#include <QTimer>
#include <QAction>
using namespace MessageViewer;
OpenAttachmentFolderWidget::OpenAttachmentFolderWidget(QWidget *parent)
: KMessageWidget(parent)
{
mTimer = new QTimer(this);
mTimer->setSingleShot(true);
mTimer->setInterval(5000); // 5 seconds
connect(mTimer, &QTimer::timeout, this, &OpenAttachmentFolderWidget::slotTimeOut);
setVisible(false);
setCloseButtonVisible(true);
setMessageType(Positive);
setWordWrap(true);
QAction *action = this->findChild<QAction *>(); // should give us the close action...
if (action) {
connect(action, &QAction::triggered, this,
&OpenAttachmentFolderWidget::slotExplicitlyClosed);
}
action = new QAction(i18n("Open folder where attachment was saved"), this);
connect(action, &QAction::triggered, this,
&OpenAttachmentFolderWidget::slotOpenAttachmentFolder);
addAction(action);
}
OpenAttachmentFolderWidget::~OpenAttachmentFolderWidget()
{
}
void OpenAttachmentFolderWidget::slotExplicitlyClosed()
{
if (mTimer->isActive()) {
mTimer->stop();
}
}
void OpenAttachmentFolderWidget::setUrls(const QList<QUrl> &urls)
{
mUrls = urls;
}
void OpenAttachmentFolderWidget::slotOpenAttachmentFolder()
{
if (!mUrls.isEmpty()) {
KIO::highlightInFileManager(mUrls);
slotHideWarning();
}
}
void OpenAttachmentFolderWidget::slotHideWarning()
{
if (mTimer->isActive()) {
mTimer->stop();
}
animatedHide();
}
void OpenAttachmentFolderWidget::slotShowWarning()
{
if (mTimer->isActive()) {
mTimer->stop();
}
mTimer->start();
animatedShow();
}
void OpenAttachmentFolderWidget::slotTimeOut()
{
animatedHide();
}
diff --git a/messageviewer/src/widgets/openattachmentfolderwidget.h b/messageviewer/src/widgets/openattachmentfolderwidget.h
index 4274e689..06f3dede 100644
--- a/messageviewer/src/widgets/openattachmentfolderwidget.h
+++ b/messageviewer/src/widgets/openattachmentfolderwidget.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef OPENATTACHMENTFOLDERWIDGET_H
#define OPENATTACHMENTFOLDERWIDGET_H
#include <KMessageWidget>
#include <QUrl>
class QTimer;
namespace MessageViewer {
class OpenAttachmentFolderWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit OpenAttachmentFolderWidget(QWidget *parent = nullptr);
~OpenAttachmentFolderWidget();
void setUrls(const QList<QUrl> &urls);
public Q_SLOTS:
void slotShowWarning();
void slotHideWarning();
private:
void slotOpenAttachmentFolder();
void slotTimeOut();
void slotExplicitlyClosed();
QList<QUrl> mUrls;
QTimer *mTimer = nullptr;
};
}
#endif // OPENATTACHMENTFOLDERWIDGET_H
diff --git a/messageviewer/src/widgets/printingsettings.cpp b/messageviewer/src/widgets/printingsettings.cpp
index 3fe4f9b5..7cfc927d 100644
--- a/messageviewer/src/widgets/printingsettings.cpp
+++ b/messageviewer/src/widgets/printingsettings.cpp
@@ -1,98 +1,99 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "printingsettings.h"
#include "ui_printingsettings.h"
#include "settings/messageviewersettings.h"
#include "PimCommon/ConfigureImmutableWidgetUtils"
using namespace PimCommon::ConfigureImmutableWidgetUtils;
using namespace MessageViewer;
class MessageViewer::PrintingSettingsPrivate
{
public:
PrintingSettingsPrivate()
: mPrintingUi(new Ui_PrintingSettings)
{
}
~PrintingSettingsPrivate()
{
delete mPrintingUi;
}
Ui_PrintingSettings *mPrintingUi = nullptr;
};
PrintingSettings::PrintingSettings(QWidget *parent)
: QWidget(parent)
, d(new MessageViewer::PrintingSettingsPrivate)
{
d->mPrintingUi->setupUi(this);
connect(d->mPrintingUi->mPrintEmptySelectedText, &QCheckBox::toggled, this,
&PrintingSettings::changed);
connect(d->mPrintingUi->respectExpandCollapseSettings, &QCheckBox::toggled, this,
&PrintingSettings::changed);
connect(d->mPrintingUi->printBackgroundColorAndImages, &QCheckBox::toggled, this,
&PrintingSettings::changed);
connect(d->mPrintingUi->alwaysShowEncryptionSignatureDetail, &QCheckBox::toggled, this,
&PrintingSettings::changed);
}
PrintingSettings::~PrintingSettings()
{
delete d;
}
void PrintingSettings::save()
{
saveCheckBox(d->mPrintingUi->mPrintEmptySelectedText,
MessageViewer::MessageViewerSettings::self()->printSelectedTextItem());
saveCheckBox(d->mPrintingUi->respectExpandCollapseSettings,
MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettingsItem());
saveCheckBox(d->mPrintingUi->printBackgroundColorAndImages,
MessageViewer::MessageViewerSettings::self()->printBackgroundColorImagesItem());
saveCheckBox(d->mPrintingUi->alwaysShowEncryptionSignatureDetail,
MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetailsItem());
}
void PrintingSettings::doLoadFromGlobalSettings()
{
loadWidget(d->mPrintingUi->mPrintEmptySelectedText,
MessageViewer::MessageViewerSettings::self()->printSelectedTextItem());
loadWidget(d->mPrintingUi->respectExpandCollapseSettings,
MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettingsItem());
loadWidget(d->mPrintingUi->printBackgroundColorAndImages,
MessageViewer::MessageViewerSettings::self()->printBackgroundColorImagesItem());
loadWidget(d->mPrintingUi->alwaysShowEncryptionSignatureDetail,
- MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetailsItem());
+ MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetailsItem());
}
void PrintingSettings::doResetToDefaultsOther()
{
const bool bUseDefaults = MessageViewer::MessageViewerSettings::self()->useDefaults(true);
loadWidget(d->mPrintingUi->mPrintEmptySelectedText,
MessageViewer::MessageViewerSettings::self()->printSelectedTextItem());
loadWidget(d->mPrintingUi->respectExpandCollapseSettings,
MessageViewer::MessageViewerSettings::self()->respectExpandCollapseSettingsItem());
loadWidget(d->mPrintingUi->printBackgroundColorAndImages,
MessageViewer::MessageViewerSettings::self()->printBackgroundColorImagesItem());
loadWidget(d->mPrintingUi->alwaysShowEncryptionSignatureDetail,
- MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetailsItem());
+ MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetailsItem());
MessageViewer::MessageViewerSettings::self()->useDefaults(bUseDefaults);
}
diff --git a/messageviewer/src/widgets/printingsettings.h b/messageviewer/src/widgets/printingsettings.h
index ecff7985..49d18b2f 100644
--- a/messageviewer/src/widgets/printingsettings.h
+++ b/messageviewer/src/widgets/printingsettings.h
@@ -1,44 +1,45 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef PRINTINGSETTINGS_H
#define PRINTINGSETTINGS_H
#include "messageviewer_export.h"
#include <QWidget>
namespace MessageViewer {
class PrintingSettingsPrivate;
class MESSAGEVIEWER_EXPORT PrintingSettings : public QWidget
{
Q_OBJECT
public:
explicit PrintingSettings(QWidget *parent = nullptr);
~PrintingSettings();
void save();
void doLoadFromGlobalSettings();
void doResetToDefaultsOther();
Q_SIGNALS:
void changed();
private:
PrintingSettingsPrivate *const d;
};
}
#endif // PRINTINGSETTINGS_H
diff --git a/messageviewer/src/widgets/shownextmessagewidget.cpp b/messageviewer/src/widgets/shownextmessagewidget.cpp
new file mode 100644
index 00000000..fe3be2bf
--- /dev/null
+++ b/messageviewer/src/widgets/shownextmessagewidget.cpp
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2019 Laurent Montel <montel@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.
+*/
+
+#include "shownextmessagewidget.h"
+#include <QHBoxLayout>
+#include <KLocalizedString>
+#include <QPushButton>
+
+using namespace MessageViewer;
+ShowNextMessageWidget::ShowNextMessageWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ QHBoxLayout *mainLayout = new QHBoxLayout(this);
+ mainLayout->setObjectName(QStringLiteral("mainlayout"));
+ mainLayout->setContentsMargins(0, 0, 0, 0);
+
+
+ mPreviousMessage = new QPushButton(i18n("Previous Message"), this);
+ mPreviousMessage->setObjectName(QStringLiteral("previous_message"));
+ mPreviousMessage->setEnabled(false);
+ mainLayout->addWidget(mPreviousMessage);
+ connect(mPreviousMessage, &QPushButton::clicked, this, &ShowNextMessageWidget::showPreviousMessage);
+
+ mainLayout->addStretch(1);
+ mNextMessage = new QPushButton(i18n("Next Message"), this);
+ mNextMessage->setObjectName(QStringLiteral("next_message"));
+ mNextMessage->setEnabled(false);
+ connect(mNextMessage, &QPushButton::clicked, this, &ShowNextMessageWidget::showNextMessage);
+ mainLayout->addWidget(mNextMessage);
+ setMaximumHeight(mNextMessage->height() + 4);
+}
+
+ShowNextMessageWidget::~ShowNextMessageWidget()
+{
+
+}
+
+void ShowNextMessageWidget::updateButton(bool hasPreviousMessage, bool hasNextMessage)
+{
+ mPreviousMessage->setEnabled(hasPreviousMessage);
+ mNextMessage->setEnabled(hasNextMessage);
+}
diff --git a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.h b/messageviewer/src/widgets/shownextmessagewidget.h
similarity index 58%
rename from messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.h
rename to messageviewer/src/widgets/shownextmessagewidget.h
index 9598f652..3f1ff6ec 100644
--- a/messageviewer/src/viewer/webengine/tests/testjquerysupportmailwebengine.h
+++ b/messageviewer/src/widgets/shownextmessagewidget.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2019 Laurent Montel <montel@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.
*/
-#ifndef TestJQuerySupportMailWebEngine_H
-#define TestJQuerySupportMailWebEngine_H
+
+#ifndef SHOWNEXTMESSAGEWIDGET_H
+#define SHOWNEXTMESSAGEWIDGET_H
#include <QWidget>
-#include <KMime/Message>
+#include "messageviewer_private_export.h"
+class QPushButton;
namespace MessageViewer {
-class Viewer;
-}
-
-class QTextEdit;
-class TestJQuerySupportMailWebEngine : public QWidget
+class MESSAGEVIEWER_TESTS_EXPORT ShowNextMessageWidget : public QWidget
{
Q_OBJECT
public:
- explicit TestJQuerySupportMailWebEngine(QWidget *parent = nullptr);
- ~TestJQuerySupportMailWebEngine();
+ explicit ShowNextMessageWidget(QWidget *parent = nullptr);
+ ~ShowNextMessageWidget();
-private Q_SLOTS:
- void slotExecuteQuery();
+ void updateButton(bool hasPreviousMessage, bool hasNextMessage);
+Q_SIGNALS:
+ void showNextMessage();
+ void showPreviousMessage();
private:
- KMime::Message::Ptr readAndParseMail(const QString &mailFile);
- MessageViewer::Viewer *viewer = nullptr;
- QTextEdit *mEditor = nullptr;
+ QPushButton *mNextMessage = nullptr;
+ QPushButton *mPreviousMessage = nullptr;
};
+}
-#endif
+#endif // SHOWNEXTMESSAGEWIDGET_H
diff --git a/messageviewer/src/widgets/submittedformwarningwidget.cpp b/messageviewer/src/widgets/submittedformwarningwidget.cpp
index a2d574f5..1f365771 100644
--- a/messageviewer/src/widgets/submittedformwarningwidget.cpp
+++ b/messageviewer/src/widgets/submittedformwarningwidget.cpp
@@ -1,44 +1,44 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#include "submittedformwarningwidget.h"
#include <KLocalizedString>
using namespace MessageViewer;
SubmittedFormWarningWidget::SubmittedFormWarningWidget(QWidget *parent)
: KMessageWidget(parent)
{
setVisible(false);
setCloseButtonVisible(true);
setMessageType(Warning);
setWordWrap(true);
setText(i18n("Submit form is not allowed in mailer. Please open url in a browser."));
}
SubmittedFormWarningWidget::~SubmittedFormWarningWidget()
{
}
void SubmittedFormWarningWidget::showWarning()
{
animatedShow();
}
diff --git a/messageviewer/src/widgets/submittedformwarningwidget.h b/messageviewer/src/widgets/submittedformwarningwidget.h
index da70e9f2..c0299973 100644
--- a/messageviewer/src/widgets/submittedformwarningwidget.h
+++ b/messageviewer/src/widgets/submittedformwarningwidget.h
@@ -1,40 +1,40 @@
/*
- Copyright (c) 2016-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2016-2019 Montel Laurent <montel@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.
*/
#ifndef SUBMITTEDFORMWARNINGWIDGET_H
#define SUBMITTEDFORMWARNINGWIDGET_H
#include <KMessageWidget>
namespace MessageViewer {
class SubmittedFormWarningWidgetPrivate;
class SubmittedFormWarningWidget : public KMessageWidget
{
Q_OBJECT
public:
explicit SubmittedFormWarningWidget(QWidget *parent = nullptr);
~SubmittedFormWarningWidget();
public Q_SLOTS:
void showWarning();
};
}
#endif // SUBMITTEDFORMWARNINGWIDGET_H
diff --git a/messageviewer/src/widgets/vcardviewer.cpp b/messageviewer/src/widgets/vcardviewer.cpp
index 9ac7b01a..c589a3d3 100644
--- a/messageviewer/src/widgets/vcardviewer.cpp
+++ b/messageviewer/src/widgets/vcardviewer.cpp
@@ -1,138 +1,138 @@
/* This file is part of the KDE project
Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "vcardviewer.h"
#include "settings/messageviewersettings.h"
#include "KaddressbookGrantlee/GrantleeContactViewer"
#include <kcontacts/vcardconverter.h>
using KContacts::VCardConverter;
using KContacts::Addressee;
#include <ContactDefaultActions>
#include <KLocalizedString>
#include <LibkdepimAkonadi/AddContactJob>
#include <QDialogButtonBox>
#include <KConfigGroup>
#include <QPushButton>
#include <KGuiItem>
#include <QVBoxLayout>
using namespace MessageViewer;
VCardViewer::VCardViewer(QWidget *parent, const QByteArray &vCard)
: QDialog(parent)
{
setWindowTitle(i18n("vCard Viewer"));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
QPushButton *user1Button = new QPushButton;
buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
mUser2Button = new QPushButton;
mUser3Button = new QPushButton;
buttonBox->addButton(mUser3Button, QDialogButtonBox::ActionRole);
buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
connect(buttonBox, &QDialogButtonBox::rejected, this, &VCardViewer::reject);
setModal(false);
buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
KGuiItem::assign(user1Button, KGuiItem(i18n("&Import")));
KGuiItem::assign(mUser2Button, KGuiItem(i18n("&Next Card")));
KGuiItem::assign(mUser3Button, KGuiItem(i18n("&Previous Card")));
mContactViewer = new KAddressBookGrantlee::GrantleeContactViewer(this);
Akonadi::ContactDefaultActions *actions = new Akonadi::ContactDefaultActions(this);
actions->connectToView(mContactViewer);
mContactViewer->setForceDisableQRCode(true);
mainLayout->addWidget(mContactViewer);
mainLayout->addWidget(buttonBox);
VCardConverter vcc;
mAddresseeList = vcc.parseVCards(vCard);
if (!mAddresseeList.empty()) {
mContactViewer->setRawContact(mAddresseeList.at(0));
if (mAddresseeList.size() <= 1) {
mUser2Button->setVisible(false);
mUser3Button->setVisible(false);
} else {
mUser3Button->setEnabled(false);
}
connect(user1Button, &QPushButton::clicked, this, &VCardViewer::slotUser1);
connect(mUser2Button, &QPushButton::clicked, this, &VCardViewer::slotUser2);
connect(mUser3Button, &QPushButton::clicked, this, &VCardViewer::slotUser3);
} else {
mContactViewer->setRawContact(KContacts::Addressee());
user1Button->setEnabled(false);
mUser2Button->setVisible(false);
mUser3Button->setVisible(false);
}
readConfig();
}
VCardViewer::~VCardViewer()
{
writeConfig();
}
void VCardViewer::readConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "VCardViewer");
const QSize size = group.readEntry("Size", QSize(300, 400));
if (size.isValid()) {
resize(size);
}
}
void VCardViewer::writeConfig()
{
KConfigGroup group(KSharedConfig::openConfig(), "VCardViewer");
group.writeEntry("Size", size());
group.sync();
}
void VCardViewer::slotUser1()
{
const KContacts::Addressee contact = mAddresseeList.at(mAddresseeListIndex);
KPIM::AddContactJob *job = new KPIM::AddContactJob(contact, this, this);
job->start();
}
void VCardViewer::slotUser2()
{
// next vcard
mContactViewer->setRawContact(mAddresseeList.at(++mAddresseeListIndex));
if ((mAddresseeListIndex + 1) == (mAddresseeList.count())) {
mUser2Button->setEnabled(false);
}
mUser3Button->setEnabled(true);
}
void VCardViewer::slotUser3()
{
// previous vcard
mContactViewer->setRawContact(mAddresseeList.at(--mAddresseeListIndex));
if (mAddresseeListIndex == 0) {
mUser3Button->setEnabled(false);
}
mUser2Button->setEnabled(true);
}
diff --git a/messageviewer/src/widgets/vcardviewer.h b/messageviewer/src/widgets/vcardviewer.h
index 098bffae..c49ecbb5 100644
--- a/messageviewer/src/widgets/vcardviewer.h
+++ b/messageviewer/src/widgets/vcardviewer.h
@@ -1,53 +1,53 @@
/* This file is part of the KDE project
Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
- Copyright (C) 2013-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2013-2019 Laurent Montel <montel@kde.org>
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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef MESSAGEVIEWER_VCARDVIEWER_H
#define MESSAGEVIEWER_VCARDVIEWER_H
#include <QDialog>
#include <kcontacts/addressee.h>
namespace KAddressBookGrantlee {
class GrantleeContactViewer;
}
class QPushButton;
namespace MessageViewer {
class VCardViewer : public QDialog
{
Q_OBJECT
public:
explicit VCardViewer(QWidget *parent, const QByteArray &vCard);
~VCardViewer();
private:
void slotUser1();
void slotUser2();
void slotUser3();
void readConfig();
void writeConfig();
KContacts::Addressee::List mAddresseeList;
int mAddresseeListIndex = 0;
KAddressBookGrantlee::GrantleeContactViewer *mContactViewer = nullptr;
QPushButton *mUser2Button = nullptr;
QPushButton *mUser3Button = nullptr;
};
}
#endif // VCARDVIEWER_H
diff --git a/messageviewer/tests/viewertest_gui.cpp b/messageviewer/tests/viewertest_gui.cpp
index 1fcea768..993b7e61 100644
--- a/messageviewer/tests/viewertest_gui.cpp
+++ b/messageviewer/tests/viewertest_gui.cpp
@@ -1,99 +1,99 @@
/* Copyright 2010 David Faure <faure@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "messageviewer/viewer.h"
#include "messageviewer/headerstylepluginmanager.h"
#include <KActionCollection>
#include <QApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDebug>
#include <QFile>
#include <QStandardPaths>
#include "messageviewer_debug.h"
using namespace MessageViewer;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("+[file]"),
QStringLiteral("File containing an email")));
QCommandLineOption headerStylePluginOption(QStringList() << QStringLiteral(
"headerstyleplugin"), QStringLiteral(
"Header Style Plugin"), QStringLiteral(
"headerstyleplugin"));
parser.addOption(headerStylePluginOption);
QCommandLineOption listOption(QStringList() << QStringLiteral("list"), QStringLiteral(
"Show list of plugins installed."));
parser.addOption(listOption);
parser.process(app);
if (parser.isSet(listOption)) {
qDebug() << "List of Plugin :"
<< MessageViewer::HeaderStylePluginManager::self()->pluginListName();
return 0;
}
KMime::Message *msg = new KMime::Message;
if (parser.positionalArguments().count() == 0) {
QByteArray mail = "From: dfaure@example.com\n"
"To: kde@example.com\n"
"Sender: dfaure@example.com\n"
"MIME-Version: 1.0\n"
"Date: 02 Jul 2010 23:58:21 -0000\n"
"Subject: Akademy\n"
"Content-Type: text/plain\n"
"X-Length: 0\n"
"X-UID: 6161\n"
"\n"
"Hello this is a test mail\n";
msg->setContent(mail);
} else {
const QString fileName = parser.positionalArguments().at(0);
QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) {
msg->setContent(file.readAll());
} else {
qCWarning(MESSAGEVIEWER_LOG) << "Couldn't read" << fileName;
}
}
msg->parse();
Viewer *viewer = new Viewer(nullptr, nullptr, new KActionCollection(&app));
if (parser.isSet(headerStylePluginOption)) {
viewer->setPluginName(parser.value(headerStylePluginOption));
}
viewer->setMessage(KMime::Message::Ptr(msg));
viewer->show();
const int ret = app.exec();
delete viewer;
return ret;
}
diff --git a/metainfo.yaml b/metainfo.yaml
new file mode 100644
index 00000000..b9319135
--- /dev/null
+++ b/metainfo.yaml
@@ -0,0 +1,34 @@
+name: messagelib
+description: Messagelib Library which contains MessageComposer, MessageCore, MessageList, MessageViewer, MimeTreeParser, TemplateParser and WebEngineViewer
+maintainer: mlaurent
+public_lib: true
+type: functional
+group: kdepim
+platforms:
+ - name: linux
+irc: akonadi
+portingAid: false
+deprecated: false
+libraries:
+ - qmake: MessageComposer
+ cmake: "KF5::MessageComposer"
+cmakename: KF5MessageComposer
+ - qmake: MessageCore
+ cmake: "KF5::MessageCore"
+cmakename: KF5MessageCore
+ - qmake: MessageList
+ cmake: "KF5::MessageList"
+cmakename: KF5MessageList
+ - qmake: MessageViewer
+ cmake: "KF5::MessageViewer"
+cmakename: KF5MessageViewer
+ - qmake: MimeTreeParser
+ cmake: "KF5::MimeTreeParser"
+cmakename: KF5MimeTreeParser
+ - qmake: TemplateParser
+ cmake: "KF5::TemplateParser"
+cmakename: KF5TemplateParser
+ - qmake: WebEngineViewer
+ cmake: "KF5::WebEngineViewer"
+cmakename: KF5WebEngineViewer
+
diff --git a/mimetreeparser/autotests/CMakeLists.txt b/mimetreeparser/autotests/CMakeLists.txt
index a6ef8bef..b9e43f44 100644
--- a/mimetreeparser/autotests/CMakeLists.txt
+++ b/mimetreeparser/autotests/CMakeLists.txt
@@ -1,42 +1,43 @@
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR})
add_definitions( -DMAIL_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
include(${CMAKE_SOURCE_DIR}/cmake/modules/kdepim_add_gpg_crypto_test.cmake)
# convenience macro to add qtest unit tests
macro(add_mimetreeparser_unittest _source)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source} util.cpp setupenv.cpp
TEST_NAME ${_name}
NAME_PREFIX "mimetreeparser-"
LINK_LIBRARIES KF5::MimeTreeParser Qt5::Test KF5::Mime Gpgmepp
)
endmacro ()
macro(add_mimetreeparser_class_unittest _source _additionalSource)
get_filename_component(_name ${_source} NAME_WE)
ecm_add_test(${_source} ${_additionalSource}
TEST_NAME ${_name}
NAME_PREFIX "mimetreeparser-"
LINK_LIBRARIES KF5::MimeTreeParser Qt5::Test KF5::Mime Gpgmepp
)
endmacro ()
macro(add_mimetreeparser_crypto_unittest _source)
set(_test ${_source} util.cpp)
get_filename_component(_name ${_source} NAME_WE)
add_executable( ${_name} ${_test} setupenv.cpp)
ecm_mark_as_test(mimetreeparser-${_name})
target_link_libraries( ${_name}
KF5::MimeTreeParser
Qt5::Test
KF5::Mime
QGpgme
)
add_gpg_crypto_test(${_name} mimetreeparser-${_name})
endmacro ()
add_mimetreeparser_crypto_unittest(attachmenttest.cpp)
+add_mimetreeparser_crypto_unittest(basicobjecttreeparsertest.cpp)
add_mimetreeparser_unittest(bodypartformatterbasefactorytest.cpp)
add_mimetreeparser_unittest(nodehelpertest.cpp)
add_mimetreeparser_class_unittest(cryptohelpertest.cpp "../src/cryptohelper.cpp")
diff --git a/mimetreeparser/autotests/attachmenttest.cpp b/mimetreeparser/autotests/attachmenttest.cpp
index ea17af0d..a1c0dd38 100644
--- a/mimetreeparser/autotests/attachmenttest.cpp
+++ b/mimetreeparser/autotests/attachmenttest.cpp
@@ -1,69 +1,69 @@
/*
Copyright (c) 2015 Volker Krause <vkrause@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.
*/
#include "objecttreeparser.h"
#include "util.h"
#include "setupenv.h"
#include <MimeTreeParser/SimpleObjectTreeSource>
-#include <qtest.h>
+#include <QTest>
using namespace MimeTreeParser;
class AttachmentTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testEncryptedAttachment_data();
void testEncryptedAttachment();
};
QTEST_MAIN(AttachmentTest)
void AttachmentTest::initTestCase()
{
MimeTreeParser::Test::setupEnv();
}
void AttachmentTest::testEncryptedAttachment_data()
{
QTest::addColumn<QString>("mbox");
QTest::newRow("encrypted") << "openpgp-encrypted-two-attachments.mbox";
QTest::newRow("signed") << "openpgp-signed-two-attachments.mbox";
QTest::newRow("signed+encrypted") << "openpgp-signed-encrypted-two-attachments.mbox";
QTest::newRow("encrypted+partial signed") << "openpgp-encrypted-partially-signed-attachments.mbox";
}
void AttachmentTest::testEncryptedAttachment()
{
QFETCH(QString, mbox);
auto msg = readAndParseMail(mbox);
NodeHelper nodeHelper;
SimpleObjectTreeSource testSource;
testSource.setDecryptMessage(true);
ObjectTreeParser otp(&testSource, &nodeHelper);
otp.parseObjectTree(msg.data());
auto attachments = msg->attachments();
auto encAtts = nodeHelper.attachmentsOfExtraContents();
QCOMPARE(attachments.size() + encAtts.size(), 2);
}
#include "attachmenttest.moc"
diff --git a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp b/mimetreeparser/autotests/basicobjecttreeparsertest.cpp
similarity index 53%
copy from messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp
copy to mimetreeparser/autotests/basicobjecttreeparsertest.cpp
index 00642a67..a01d090b 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.cpp
+++ b/mimetreeparser/autotests/basicobjecttreeparsertest.cpp
@@ -1,351 +1,309 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
- Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
+ Copyright (c) 2019 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.
*/
-#include "unencryptedmessagetest.h"
+#include "basicobjecttreeparsertest.h"
#include "util.h"
#include "setupenv.h"
#include <MimeTreeParser/NodeHelper>
#include <MimeTreeParser/ObjectTreeParser>
-#include <MessageViewer/BufferedHtmlWriter>
+#include <MimeTreeParser/SimpleObjectTreeSource>
#include <QTest>
-using namespace MessageViewer;
+using namespace MimeTreeParser;
-QTEST_MAIN(UnencryptedMessageTest)
+QTEST_MAIN(ObjectTreeParserTest)
-void UnencryptedMessageTest::initTestCase()
+void ObjectTreeParserTest::initTestCase()
{
Test::setupEnv();
}
-void UnencryptedMessageTest::testMailWithoutEncryption()
+void ObjectTreeParserTest::testMailWithoutEncryption()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ = readAndParseMail(QStringLiteral("encapsulated-with-attachment.mbox"));
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
otp.parseObjectTree(originalMessage.data());
QVERIFY(!nodeHelper.unencryptedMessage(originalMessage));
}
-void UnencryptedMessageTest::testSignedForwardedOpenPGPSignedEncrypted()
+void ObjectTreeParserTest::testSignedForwardedOpenPGPSignedEncrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("signed-forward-openpgp-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ = readAndParseMail(QStringLiteral("signed-forward-openpgp-signed-encrypted.mbox"));
+
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
otp.parseObjectTree(originalMessage.data());
- testWriter.end();
QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallyEncrypted);
+ originalMessage.data()), KMMsgPartiallyEncrypted);
QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
+ originalMessage.data()), KMMsgFullySigned);
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QVERIFY(!unencryptedMessage); // We must not invalidate the outer signature
}
-void UnencryptedMessageTest::testForwardedOpenPGPSignedEncrypted()
+void ObjectTreeParserTest::testForwardedOpenPGPSignedEncrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("forward-openpgp-signed-encrypted.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ = readAndParseMail(QStringLiteral("forward-openpgp-signed-encrypted.mbox"));
+
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
- testWriter.end();
QCOMPARE(otp.plainTextContent().toLatin1().data(), "bla bla bla"); // The textual content doesn't include the encrypted encapsulated message by design
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallyEncrypted);
+ originalMessage.data()), KMMsgPartiallyEncrypted);
QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgPartiallySigned);
+ originalMessage.data()), KMMsgPartiallySigned);
// Now, test that the unencrypted message is generated correctly
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QVERIFY(unencryptedMessage.data());
QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/mixed");
QCOMPARE(unencryptedMessage->contents().size(), 2);
QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
"text/plain");
QCOMPARE(unencryptedMessage->contents().first()->decodedContent().data(), "bla bla bla");
QCOMPARE(unencryptedMessage->contents().at(
1)->contentType()->mimeType().data(), "message/rfc822");
KMime::Message::Ptr encapsulated = unencryptedMessage->contents().at(1)->bodyAsMessage();
QCOMPARE(encapsulated->contentType()->mimeType().data(), "multipart/signed");
QCOMPARE(encapsulated->contents().size(), 2);
QCOMPARE(encapsulated->contents().first()->contentType()->mimeType().data(), "text/plain");
QCOMPARE(encapsulated->contents().at(
1)->contentType()->mimeType().data(), "application/pgp-signature");
QCOMPARE(encapsulated->contents().first()->decodedContent().data(), "encrypted message text");
// TODO: Check that the signature is valid
}
-void UnencryptedMessageTest::testSMIMESignedEncrypted()
+void ObjectTreeParserTest::testSMIMESignedEncrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("smime-signed-encrypted.mbox"));
+ = readAndParseMail(QStringLiteral("smime-signed-encrypted.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
+ originalMessage.data()), KMMsgFullyEncrypted);
QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
+ originalMessage.data()), KMMsgFullySigned);
// Now, test that the unencrypted message is generated correctly
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed");
QCOMPARE(unencryptedMessage->contents().size(), 2);
QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
"text/plain");
QCOMPARE(unencryptedMessage->contents().at(
1)->contentType()->mimeType().data(), "application/pkcs7-signature");
QCOMPARE(
unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text");
// TODO: Check that the signature is valid
}
-void UnencryptedMessageTest::testOpenPGPSignedEncrypted()
+void ObjectTreeParserTest::testOpenPGPSignedEncrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-signed-encrypted.mbox"));
+ = readAndParseMail(QStringLiteral("openpgp-signed-encrypted.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
+ originalMessage.data()), KMMsgFullyEncrypted);
QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
+ originalMessage.data()), KMMsgFullySigned);
// Now, test that the unencrypted message is generated correctly
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "multipart/signed");
QCOMPARE(unencryptedMessage->contents().size(), 2);
QCOMPARE(unencryptedMessage->contents().first()->contentType()->mimeType().data(),
"text/plain");
QCOMPARE(unencryptedMessage->contents().at(
1)->contentType()->mimeType().data(), "application/pgp-signature");
QCOMPARE(
unencryptedMessage->contents().first()->decodedContent().data(), "encrypted message text");
// TODO: Check that the signature is valid
}
-void UnencryptedMessageTest::testOpenPGPEncryptedAndSigned()
+void ObjectTreeParserTest::testOpenPGPEncryptedAndSigned()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted+signed.mbox"));
+ = readAndParseMail(QStringLiteral("openpgp-encrypted+signed.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
+ originalMessage.data()), KMMsgFullyEncrypted);
QCOMPARE(nodeHelper.overallSignatureState(
- originalMessage.data()), MimeTreeParser::KMMsgFullySigned);
+ originalMessage.data()), KMMsgFullySigned);
// Now, test that the unencrypted message is generated correctly
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain");
QCOMPARE(unencryptedMessage->contents().size(), 0);
QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text");
// TODO: Check that the signature is valid
}
-void UnencryptedMessageTest::testOpenPGPEncrypted()
+void ObjectTreeParserTest::testOpenPGPEncrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
+ = readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(otp.plainTextContent().toLatin1().data(), "encrypted message text");
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
+ originalMessage.data()), KMMsgFullyEncrypted);
// Now, test that the unencrypted message is generated correctly
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QCOMPARE(unencryptedMessage->contentType()->mimeType().data(), "text/plain");
QCOMPARE(unencryptedMessage->decodedContent().data(), "encrypted message text");
QCOMPARE(unencryptedMessage->contents().size(), 0);
}
-void UnencryptedMessageTest::testOpenPGPEncryptedNotDecrypted()
+void ObjectTreeParserTest::testOpenPGPEncryptedNotDecrypted()
{
KMime::Message::Ptr originalMessage
- = Test::readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
+ = readAndParseMail(QStringLiteral("openpgp-encrypted.mbox"));
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(false);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(nodeHelper.overallEncryptionState(
- originalMessage.data()), MimeTreeParser::KMMsgFullyEncrypted);
+ originalMessage.data()), KMMsgFullyEncrypted);
QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
QVERIFY(!unencryptedMessage);
}
-void UnencryptedMessageTest::testAsync_data()
+void ObjectTreeParserTest::testAsync_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("output");
QTest::newRow("openpgp-encrypt") << QStringLiteral("openpgp-encrypted.mbox") << QStringLiteral(
"encrypted message text");
QTest::newRow("smime-opaque-sign") << QStringLiteral("smime-opaque-sign.mbox")
<< QStringLiteral("A simple signed only test.");
QTest::newRow("smime-encrypt") << QStringLiteral("smime-encrypted.mbox") << QStringLiteral(
"The quick brown fox jumped over the lazy dog.");
QTest::newRow("openpgp-inline-encrypt") << QStringLiteral(
"openpgp-inline-charset-encrypted.mbox") << QStringLiteral(
"asdasd asd asd asdf sadf sdaf sadf \u00F6\u00E4\u00FC");
}
-void UnencryptedMessageTest::testAsync()
+void ObjectTreeParserTest::testAsync()
{
QFETCH(QString, mailFileName);
QFETCH(QString, output);
- KMime::Message::Ptr originalMessage = Test::readAndParseMail(mailFileName);
- MimeTreeParser::NodeHelper nodeHelper;
- Test::ObjectTreeSource emptySource(nullptr, nullptr);
- emptySource.setAllowDecryption(true);
+ KMime::Message::Ptr originalMessage = readAndParseMail(mailFileName);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ testSource.setDecryptMessage(true);
{
QEventLoop loop;
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ ObjectTreeParser otp(&testSource, &nodeHelper);
- connect(&nodeHelper, &MimeTreeParser::NodeHelper::update, &loop, &QEventLoop::quit);
+ connect(&nodeHelper, &NodeHelper::update, &loop, &QEventLoop::quit);
otp.setAllowAsync(true);
otp.parseObjectTree(originalMessage.data());
loop.exec();
}
// Job ended
{
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ ObjectTreeParser otp(&testSource, &nodeHelper);
otp.setAllowAsync(true);
otp.parseObjectTree(originalMessage.data());
QCOMPARE(otp.plainTextContent(), output);
}
}
-void UnencryptedMessageTest::testNotDecrypted_data()
+void ObjectTreeParserTest::testHtmlContent_data()
{
QTest::addColumn<QString>("mailFileName");
- QTest::addColumn<bool>("decryptMessage");
+ QTest::addColumn<QString>("output");
- QTest::newRow("openpgp-inline") << QStringLiteral("inlinepgpencrypted.mbox") << true;
- QTest::newRow("openpgp-encrypt") << QStringLiteral("openpgp-encrypted.mbox") << true;
- QTest::newRow("smime-encrypt") << QStringLiteral("smime-encrypted.mbox") << true;
- QTest::newRow("openpgp-inline-encrypt") << QStringLiteral(
- "openpgp-inline-charset-encrypted.mbox") << true;
- QTest::newRow("smime-opaque-sign") << QStringLiteral("smime-opaque-sign.mbox") << false;
- QTest::newRow("openpgp-inline-signed") << QStringLiteral("openpgp-inline-signed.mbox") << false;
- QTest::newRow("openpgp-mime-signed") << QStringLiteral("openpgp-signed-mailinglist.mbox")
- << false;
+ QTest::newRow("html-attachments1") << QStringLiteral("html-attachment1.mbox") << QStringLiteral(
+ "<html><head></head><body><p><span style=\"font-family:Arial;\">A Body Text</span></p></body></html>");
+ QTest::newRow("html-attachments2") << QStringLiteral("html-attachment2.mbox")
+ << QStringLiteral("<html><head></head><body>HTML Text</body></html>");
}
-void UnencryptedMessageTest::testNotDecrypted()
+void ObjectTreeParserTest::testHtmlContent()
{
QFETCH(QString, mailFileName);
- QFETCH(bool, decryptMessage);
- KMime::Message::Ptr originalMessage = Test::readAndParseMail(mailFileName);
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- emptySource.setAllowDecryption(false);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
- otp.parseObjectTree(originalMessage.data());
- testWriter.end();
-
- if (decryptMessage) {
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
- } else {
- QVERIFY(otp.plainTextContent().toLatin1().data());
- }
- QCOMPARE(testWriter.data().contains("<a href=\"kmail:decryptMessage\">"), decryptMessage);
-
- KMime::Message::Ptr unencryptedMessage = nodeHelper.unencryptedMessage(originalMessage);
- QCOMPARE((bool)unencryptedMessage, false);
-}
+ QFETCH(QString, output);
-void UnencryptedMessageTest::testSMimeAutoCertImport()
-{
- KMime::Message::Ptr originalMessage = Test::readAndParseMail(QStringLiteral("smime-cert.mbox"));
-
- MimeTreeParser::NodeHelper nodeHelper;
- BufferedHtmlWriter testWriter;
- testWriter.begin();
- Test::CSSHelper testCSSHelper;
- Test::ObjectTreeSource emptySource(&testWriter, &testCSSHelper);
- MimeTreeParser::ObjectTreeParser otp(&emptySource, &nodeHelper);
+ KMime::Message::Ptr originalMessage = readAndParseMail(mailFileName);
+ NodeHelper nodeHelper;
+ SimpleObjectTreeSource testSource;
+ ObjectTreeParser otp(&testSource, &nodeHelper);
+ testSource.setDecryptMessage(true);
otp.parseObjectTree(originalMessage.data());
- testWriter.end();
- QCOMPARE(otp.plainTextContent().toLatin1().data(), "");
- QVERIFY(testWriter.data().contains("Sorry, certificate could not be imported."));
+ QVERIFY(otp.plainTextContent().isEmpty());
+ QCOMPARE(otp.htmlContent(), output);
}
diff --git a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h b/mimetreeparser/autotests/basicobjecttreeparsertest.h
similarity index 78%
copy from messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h
copy to mimetreeparser/autotests/basicobjecttreeparsertest.h
index 7799fa29..069035a2 100644
--- a/messageviewer/src/messagepartthemes/default/autotests/unencryptedmessagetest.h
+++ b/mimetreeparser/autotests/basicobjecttreeparsertest.h
@@ -1,46 +1,45 @@
/*
Copyright (c) 2010 Thomas McGuire <thomas.mcguire@kdab.com>
- Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
+ Copyright (c) 2019 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.
*/
-#ifndef MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
-#define MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
+#ifndef MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H
+#define MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H
#include <QObject>
-class UnencryptedMessageTest : public QObject
+class ObjectTreeParserTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testMailWithoutEncryption();
void testSMIMESignedEncrypted();
void testOpenPGPSignedEncrypted();
void testOpenPGPEncryptedAndSigned();
void testForwardedOpenPGPSignedEncrypted();
void testSignedForwardedOpenPGPSignedEncrypted();
void testOpenPGPEncrypted();
void testOpenPGPEncryptedNotDecrypted();
void testAsync_data();
void testAsync();
- void testNotDecrypted_data();
- void testNotDecrypted();
- void testSMimeAutoCertImport();
+ void testHtmlContent_data();
+ void testHtmlContent();
};
-#endif // MESSAGEVIEWER_TESTS_UNENCRYPTEDMESSAGETEST_H
+#endif // MIMETREEPARSER_TESTS_OBJECTTREEPARSERTEST_H
diff --git a/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp b/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
index 4f34350a..becde0ba 100644
--- a/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
+++ b/mimetreeparser/autotests/bodypartformatterbasefactorytest.cpp
@@ -1,138 +1,138 @@
/*
Copyright (c) 2017 Volker Krause <vkrause@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.
*/
#include "bodypartformatterfactory.h"
#include "interfaces/bodypartformatter.h"
-#include <qtest.h>
+#include <QTest>
using namespace MimeTreeParser;
class DummyFormatter : public Interface::BodyPartFormatter
{
MessagePartPtr process(Interface::BodyPart &) const override
{
return {};
}
};
class TestFactory : public BodyPartFormatterFactory
{
public:
void loadPlugins() override
{
textCalFormatter = new DummyFormatter;
insert(QStringLiteral("TEXT/CALENDAR"), textCalFormatter, 100);
}
DummyFormatter *textCalFormatter = nullptr;
};
using namespace MimeTreeParser;
class BodyPartFormatterBaseFactoryTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFactory()
{
TestFactory fac;
auto l = fac.formattersForType(QStringLiteral("application/octet-stream"));
QCOMPARE(l.size(), 3);
const auto application_octet_stream_f = l.at(2);
QVERIFY(application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("application/pgp-encrypted"));
QCOMPARE(l.size(), 6);
QVERIFY(l.at(0) != application_octet_stream_f);
QCOMPARE(l.at(5), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("application/unknown"));
QCOMPARE(l.size(), 3);
QCOMPARE(l.at(2), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("text/plain"));
QCOMPARE(l.size(), 5);
const auto text_plain_f1 = l.at(0);
const auto text_plain_f2 = l.at(1);
QVERIFY(text_plain_f1);
QVERIFY(text_plain_f2);
QVERIFY(text_plain_f1 != text_plain_f2);
QCOMPARE(l.at(4), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("text/calendar"));
QCOMPARE(l.size(), 6);
QVERIFY(fac.textCalFormatter);
QCOMPARE(l.at(0), fac.textCalFormatter);
QCOMPARE(l.at(1), text_plain_f1);
QCOMPARE(l.at(2), text_plain_f2);
QCOMPARE(l.at(5), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("text/x-vcalendar"));
QCOMPARE(l.size(), 6);
QCOMPARE(l.at(0), fac.textCalFormatter);
l = fac.formattersForType(QStringLiteral("TEXT/X-VCALENDAR"));
QCOMPARE(l.size(), 6);
QCOMPARE(l.at(0), fac.textCalFormatter);
l = fac.formattersForType(QStringLiteral("text/html"));
QCOMPARE(l.size(), 6);
QCOMPARE(l.at(1), text_plain_f1);
QCOMPARE(l.at(2), text_plain_f2);
QCOMPARE(l.at(5), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("text/rtf"));
QCOMPARE(l.size(), 6);
QCOMPARE(l.at(0), application_octet_stream_f);
QCOMPARE(l.at(5), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("multipart/mixed"));
QCOMPARE(l.size(), 1);
const auto multipart_mixed_f = l.at(0);
QVERIFY(multipart_mixed_f);
l = fac.formattersForType(QStringLiteral("multipart/random"));
QCOMPARE(l.size(), 1);
QCOMPARE(l.at(0), multipart_mixed_f);
l = fac.formattersForType(QStringLiteral("multipart/encrypted"));
QCOMPARE(l.size(), 2);
QVERIFY(l.at(0) != multipart_mixed_f);
QCOMPARE(l.at(1), multipart_mixed_f);
l = fac.formattersForType(QStringLiteral("image/png"));
QCOMPARE(l.size(), 4);
QCOMPARE(l.at(3), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("vendor/random"));
QCOMPARE(l.size(), 3);
QCOMPARE(l.at(2), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("message/rfc822"));
QCOMPARE(l.size(), 6);
QCOMPARE(l.at(5), application_octet_stream_f);
l = fac.formattersForType(QStringLiteral("message/news"));
QCOMPARE(l.size(), 5); // ### news does not inherit rfc822
}
};
QTEST_MAIN(BodyPartFormatterBaseFactoryTest)
#include "bodypartformatterbasefactorytest.moc"
diff --git a/mimetreeparser/autotests/cryptohelpertest.cpp b/mimetreeparser/autotests/cryptohelpertest.cpp
index f40f73bd..47d4b8fe 100644
--- a/mimetreeparser/autotests/cryptohelpertest.cpp
+++ b/mimetreeparser/autotests/cryptohelpertest.cpp
@@ -1,144 +1,144 @@
/* Copyright 2015 Sandro Knauß <knauss@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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "cryptohelpertest.h"
#include "cryptohelper.h"
#include <QTest>
using namespace MimeTreeParser;
void CryptoHelperTest::testPMFDEmpty()
{
QCOMPARE(prepareMessageForDecryption("").count(), 0);
}
void CryptoHelperTest::testPMFDWithNoPGPBlock()
{
const QByteArray text = "testblabla";
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 1);
QCOMPARE(blocks[0].text(), text);
QCOMPARE(blocks[0].type(), NoPgpBlock);
}
void CryptoHelperTest::testPGPBlockType()
{
const QString blockText = QStringLiteral("text");
const QString preString = QStringLiteral("before\n");
for (int i = 1; i <= PrivateKeyBlock; ++i) {
QString name;
switch (i) {
case PgpMessageBlock:
name = QStringLiteral("MESSAGE");
break;
case MultiPgpMessageBlock:
name = QStringLiteral("MESSAGE PART");
break;
case SignatureBlock:
name = QStringLiteral("SIGNATURE");
break;
case ClearsignedBlock:
name = QStringLiteral("SIGNED MESSAGE");
break;
case PublicKeyBlock:
name = QStringLiteral("PUBLIC KEY BLOCK");
break;
case PrivateKeyBlock:
name = QStringLiteral("PRIVATE KEY BLOCK");
break;
}
QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1String("\n") + blockText;
QList<Block> blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1());
QCOMPARE(blocks.count(), 1);
QCOMPARE(blocks[0].type(), UnknownBlock);
text += QLatin1String("\n-----END PGP ") + name + QLatin1String("\n");
blocks = prepareMessageForDecryption(preString.toLatin1() + text.toLatin1());
QCOMPARE(blocks.count(), 2);
QCOMPARE(blocks[1].text(), text.toLatin1());
QCOMPARE(blocks[1].type(), static_cast<PGPBlockType>(i));
}
}
void CryptoHelperTest::testDeterminePGPBlockType()
{
const QString blockText = QStringLiteral("text");
for (int i = 1; i <= PrivateKeyBlock; ++i) {
QString name;
switch (i) {
case PgpMessageBlock:
name = QStringLiteral("MESSAGE");
break;
case MultiPgpMessageBlock:
name = QStringLiteral("MESSAGE PART");
break;
case SignatureBlock:
name = QStringLiteral("SIGNATURE");
break;
case ClearsignedBlock:
name = QStringLiteral("SIGNED MESSAGE");
break;
case PublicKeyBlock:
name = QStringLiteral("PUBLIC KEY BLOCK");
break;
case PrivateKeyBlock:
name = QStringLiteral("PRIVATE KEY BLOCK");
break;
}
const QString text = QLatin1String("-----BEGIN PGP ") + name + QLatin1String("\n") + blockText + QLatin1String("\n");
const Block block = Block(text.toLatin1());
QCOMPARE(block.text(), text.toLatin1());
QCOMPARE(block.type(), static_cast<PGPBlockType>(i));
}
}
void CryptoHelperTest::testEmbededPGPBlock()
{
const QByteArray text = QByteArray("before\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\nafter");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 3);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after"));
}
void CryptoHelperTest::testClearSignedMessage()
{
const QByteArray text = QByteArray("before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\nafter");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 3);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after"));
}
void CryptoHelperTest::testMultipleBlockMessage()
{
const QByteArray text = QByteArray(
"before\n-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\nafter\n-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n");
const QList<Block> blocks = prepareMessageForDecryption(text);
QCOMPARE(blocks.count(), 4);
QCOMPARE(blocks[0].text(), QByteArray("before\n"));
QCOMPARE(blocks[1].text(), QByteArray("-----BEGIN PGP SIGNED MESSAGE-----\nsigned content\n-----BEGIN PGP SIGNATURE-----\nfancy signature\n-----END PGP SIGNATURE-----\n"));
QCOMPARE(blocks[2].text(), QByteArray("after\n"));
QCOMPARE(blocks[3].text(), QByteArray("-----BEGIN PGP MESSAGE-----\ncrypted - you see :)\n-----END PGP MESSAGE-----\n"));
}
QTEST_APPLESS_MAIN(CryptoHelperTest)
diff --git a/mimetreeparser/autotests/cryptohelpertest.h b/mimetreeparser/autotests/cryptohelpertest.h
index d6ae0835..130582a4 100644
--- a/mimetreeparser/autotests/cryptohelpertest.h
+++ b/mimetreeparser/autotests/cryptohelpertest.h
@@ -1,39 +1,39 @@
/* Copyright 2009 Thomas McGuire <mcguire@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef CRYPTOHELPERTEST_H
#define CRYPTOHELPERTEST_H
#include <QObject>
namespace MimeTreeParser {
class CryptoHelperTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testPMFDEmpty();
void testPMFDWithNoPGPBlock();
void testPGPBlockType();
void testDeterminePGPBlockType();
void testEmbededPGPBlock();
void testClearSignedMessage();
void testMultipleBlockMessage();
};
}
#endif
diff --git a/mimetreeparser/autotests/data/html-attachment1.mbox b/mimetreeparser/autotests/data/html-attachment1.mbox
new file mode 100644
index 00000000..805030e5
--- /dev/null
+++ b/mimetreeparser/autotests/data/html-attachment1.mbox
@@ -0,0 +1,84 @@
+Return-path: <sender@example.com>
+Envelope-to: gunter@ohrner.net
+Delivery-date: Mon, 12 Sep 2016 13:38:39 +0200
+Received: from mail.from example.com ([178.251.88.150])
+ by luggage.ohrner.net with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:256)
+ (Exim 4.80)
+ (envelope-from <sender@example.com>)
+ id 1bjPYi-0000oA-Re
+ for gunter@ohrner.net; Mon, 12 Sep 2016 13:38:39 +0200
+Received: from example.com.local (172.17.124.205) by from example.com.local
+ (172.17.124.1) with Microsoft SMTP Server id 14.3.266.1; Mon, 12 Sep 2016
+ 13:37:53 +0200
+From: <sender@example.com>
+To: <receiver@example.com>
+Message-ID: <8614416.18.1473680273823.JavaMail.sender@example.com>
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="----=_Part_16_9312243.1473680273820"
+Date: Mon, 12 Sep 2016 13:37:53 +0200
+X-TM-AS-Product-Ver: SMEX-11.1.0.1278-8.000.1202-22570.004
+X-TM-AS-Result: No--23.664000-5.000000-31
+X-TM-AS-User-Approved-Sender: No
+X-TM-AS-User-Blocked-Sender: No
+Subject: A Subject Line
+
+------=_Part_16_9312243.1473680273820
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+<html><head></head><body><p><span style="font-family:Arial;">A Body Text</span></p></body></html>
+------=_Part_16_9312243.1473680273820
+Content-Type: application/octet-stream;
+ name="Attachment1.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820
+Content-Type: application/octet-stream; name="Attachment2.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820
+Content-Type: application/octet-stream; name="Attachment3.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820--
+
diff --git a/mimetreeparser/autotests/data/html-attachment2.mbox b/mimetreeparser/autotests/data/html-attachment2.mbox
new file mode 100644
index 00000000..04723637
--- /dev/null
+++ b/mimetreeparser/autotests/data/html-attachment2.mbox
@@ -0,0 +1,30 @@
+From maillists@whattf.com Tue Jul 05 19:20:58 2011
+Return-path: <maillists@whattf.com>
+Envelope-to: maillists@whattf.com
+Date: Mon, 24 Oct 2016 11:53:00 +0100
+To: maillists@whattf.com
+From: maillists@whattf.com
+MIME-Version: 1.0
+Subject: Test
+Content-Type: multipart/related; boundary="----=_Part_20324_1054669183.1477306380301"
+MIME-Version: 1.0
+
+------=_Part_20324_1054669183.1477306380301
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+
+<html><head></head><body>HTML Text</body></html>
+------=_Part_20324_1054669183.1477306380301
+Content-Type: image/png
+Content-Disposition: attachment; filename="image1.png"
+Content-ID: cid1
+
+Image1
+------=_Part_20324_1054669183.1477306380301
+Content-Type: image/png
+Content-Disposition: attachment; filename="image2.png"
+Content-ID: cid2
+
+Image2
+------=_Part_20324_1054669183.1477306380301--
+
diff --git a/mimetreeparser/autotests/data/mailheader.css b/mimetreeparser/autotests/data/mailheader.css
index 3893622a..a648e6a6 100644
--- a/mimetreeparser/autotests/data/mailheader.css
+++ b/mimetreeparser/autotests/data/mailheader.css
@@ -1,548 +1,453 @@
div.header {
margin-bottom: 10pt ! important;
}
table.textAtm {
margin-top: 10pt ! important;
margin-bottom: 10pt ! important;
}
tr.textAtmH,
tr.textAtmB,
tr.rfc822B {
font-weight: normal ! important;
}
tr.signInProgressH,
tr.rfc822H,
tr.encrH,
tr.signOkKeyOkH,
tr.signOkKeyBadH,
tr.signWarnH,
tr.signErrH {
font-weight: bold ! important;
}
tr.textAtmH td,
tr.textAtmB td {
padding: 3px ! important;
}
table.rfc822 {
width: 100% ! important;
border: solid 1px black ! important;
margin-top: 10pt ! important;
margin-bottom: 10pt ! important;
}
table.textAtm,
table.encr,
table.signWarn,
table.signErr,
table.signOkKeyBad,
table.signOkKeyOk,
table.signInProgress,
-div.fancy.header table {
- width: 100% ! important;
- border-width: 0px ! important;
- line-height: normal;
-}
div.htmlWarn {
margin: 0px 5% ! important;
padding: 10px ! important;
text-align: left ! important;
line-height: normal;
}
-div.fancy.header > div {
- font-weight: bold ! important;
- padding: 4px ! important;
- line-height: normal;
-}
-
-div.fancy.header table {
- padding: 2px ! important;
- text-align: left ! important;
- border-collapse: separate ! important;
-}
-
-div.fancy.header table th {
- font-family: "Sans Serif" ! important;
- font-size: 0px ! important;
-
- padding: 0px ! important;
- white-space: nowrap ! important;
- border-spacing: 0px ! important;
- text-align: left ! important;
- vertical-align: top ! important;
- background-color: #d6d2d0 ! important;
- color: #221f1e ! important;
- border: 1px ! important;
-}
-
-div.fancy.header table td {
- font-family: "Sans Serif" ! important;
- font-size: 0px ! important;
-
- padding: 0px ! important;
- border-spacing: 0px ! important;
- text-align: left ! important;
- vertical-align: top ! important;
- width: 100% ! important;
- background-color: #d6d2d0 ! important;
- color: #221f1e ! important;
- border: 1px ! important;
-}
-
-div.fancy.header table a:hover {
- background-color: transparent ! important;
-}
-
div.quotelevelmark {
position: absolute;
margin-left:-10px;
}
@media screen {
body {
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
color: #1f1c1b ! important;
background-color: #ffffff ! important;
}
a {
color: #0057ae ! important;
text-decoration: none ! important;
}
a.white {
color: white ! important;
}
a.black {
color: black ! important;
}
table.textAtm { background-color: #1f1c1b ! important; }
tr.textAtmH {
background-color: #ffffff ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.textAtmB {
background-color: #ffffff ! important;
}
table.signInProgress,
table.rfc822 {
background-color: #ffffff ! important;
}
tr.signInProgressH,
tr.rfc822H {
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
table.encr {
background-color: #0069cc ! important;
}
tr.encrH {
background-color: #0080ff ! important;
color: #ffffff ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.encrB { background-color: #e0f0ff ! important; }
table.signOkKeyOk {
background-color: #33cc33 ! important;
}
tr.signOkKeyOkH {
background-color: #40ff40 ! important;
color: #27ae60 ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.signOkKeyOkB { background-color: #e8ffe8 ! important; }
table.signOkKeyBad {
background-color: #cccc33 ! important;
}
tr.signOkKeyBadH {
background-color: #ffff40 ! important;
color: #f67400 ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.signOkKeyBadB { background-color: #ffffe8 ! important; }
table.signWarn {
background-color: #cccc33 ! important;
}
tr.signWarnH {
background-color: #ffff40 ! important;
color: #f67400 ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.signWarnB { background-color: #ffffe8 ! important; }
table.signErr {
background-color: #cc0000 ! important;
}
tr.signErrH {
background-color: #ff0000 ! important;
color: #da4453 ! important;
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
tr.signErrB { background-color: #ffe0e0 ! important; }
div.htmlWarn {
border: 2px solid #ff4040 ! important;
line-height: normal;
}
div.header {
font-family: "Sans Serif" ! important;
font-size: 0px ! important;
}
-div.fancy.header > div {
- background-color: #43ace8 ! important;
- color: #ffffff ! important;
- border: solid #221f1e 1px ! important;
- line-height: normal;
-}
-
-div.fancy.header > div a[href] { color: #ffffff ! important; }
-
-div.fancy.header > div a[href]:hover { text-decoration: underline ! important; }
-
-div.fancy.header > div.spamheader {
- background-color: #cdcdcd ! important;
- border-top: 0px ! important;
- padding: 3px ! important;
- color: black ! important;
- font-weight: bold ! important;
- font-size: smaller ! important;
-}
-
-div.fancy.header > table.outer {
- background-color: #d6d2d0 ! important;
- color: #221f1e ! important;
- border-bottom: solid #221f1e 1px ! important;
- border-left: solid #221f1e 1px ! important;
- border-right: solid #221f1e 1px ! important;
-}
-
div.senderpic{
padding: 0px ! important;
font-size:0.8em ! important;
border:1px solid #b3aba7 ! important;
background-color:#d6d2d0 ! important;
}
div.senderstatus{
text-align:center ! important;
}
div.quotelevel1 {
color: #008000 ! important;
font-style: italic ! important;
}
div.quotelevel2 {
color: #007000 ! important;
font-style: italic ! important;
}
div.quotelevel3 {
color: #006000 ! important;
font-style: italic ! important;
}
div.deepquotelevel1 {
color: #008000 ! important;
font-style: italic ! important;
}
div.deepquotelevel2 {
color: #007000 ! important;
font-style: italic ! important;
}
div.deepquotelevel3 {
color: #006000 ! important;
font-style: italic ! important;
}
blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
.quotemarks{
color:transparent;
font-size:0px;
}
input[type=checkbox].addresslist_checkbox {display: none}
.addresslist_label_short {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}
.addresslist_label_full {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}
.addresslist_label_short {background-image:url(file:///opt/kde5-qt5.10/share/libmessageviewer/pics/quicklistOpened.png);
background-repeat: no-repeat}
.addresslist_label_full {background-image:url(file:///opt/kde5-qt5.10/share/libmessageviewer/pics/quicklistClosed.png);
background-repeat: no-repeat}
input ~ span.fullFullCcAddressList {display: block}
input ~ span.shortFullCcAddressList {display: none}
input:checked ~ span.fullFullCcAddressList {display: none}
input:checked ~ span.shortFullCcAddressList {display: block}
input ~ span.fullFullToAddressList {display: block}
input ~ span.shortFullToAddressList {display: none}
input:checked ~ span.fullFullToAddressList {display: none}
input:checked ~ span.shortFullToAddressList {display: block}
input ~ span.fullFullBccAddressList {display: block}
input ~ span.shortFullBccAddressList {display: none}
input:checked ~ span.fullFullBccAddressList {display: none}
input:checked ~ span.shortFullBccAddressList {display: block}
}
@media print {
body {
font-family: "Sans Serif" ! important;
font-size: 9pt ! important;
color: #000000 ! important;
background-color: #ffffff ! important
}
tr.textAtmH,
tr.signInProgressH,
tr.rfc822H,
tr.encrH,
tr.signOkKeyOkH,
tr.signOkKeyBadH,
tr.signWarnH,
tr.signErrH,
div.header {
font-family: "Sans Serif" ! important;
font-size: 9pt ! important;
}
-div.fancy.header > div {
- background-color: #d6d2d0 ! important;
- color: #221f1e ! important;
- padding: 4px ! important;
- border: solid #221f1e 1px ! important;
- line-height: normal;
-}
-
-div.fancy.header > div a[href] { color: #221f1e ! important; }
-
-div.fancy.header > table.outer{
- background-color: #d6d2d0 ! important;
- color: #221f1e ! important;
- border-bottom: solid #221f1e 1px ! important;
- border-left: solid #221f1e 1px ! important;
- border-right: solid #221f1e 1px ! important;
-}
-
div.spamheader {
display:none ! important;
}
div.htmlWarn {
border: 2px solid #ffffff ! important;
line-height: normal;
}
div.senderpic{
font-size:0.8em ! important;
border:1px solid black ! important;
background-color:#d6d2d0 ! important;
}
div.senderstatus{
text-align:center ! important;
}
div.noprint {
display:none ! important;
}
blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #008000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #007000;
unicode-bidi: -webkit-plaintext
}
blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote blockquote {
margin: 4pt 0 4pt 0;
padding: 0 0 0 1em;
border-left: 2px solid #006000;
unicode-bidi: -webkit-plaintext
}
.quotemarks{
color:transparent;
font-size:0px;
}
.quotemarksemptyline{
color:transparent;
font-size:0px;
line-height: 12pt;
}
input[type=checkbox].addresslist_checkbox {display: none}
.addresslist_label_short {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}
.addresslist_label_full {border: 1px; border-radius: 5px; padding: 0px 10px 0px 10px; white-space: nowrap}
.addresslist_label_short {background-image:url(file:///opt/kde5-qt5.10/share/libmessageviewer/pics/quicklistOpened.png);
background-repeat: no-repeat}
.addresslist_label_full {background-image:url(file:///opt/kde5-qt5.10/share/libmessageviewer/pics/quicklistClosed.png);
background-repeat: no-repeat}
input ~ span.fullFullCcAddressList {display: block}
input ~ span.shortFullCcAddressList {display: none}
input:checked ~ span.fullFullCcAddressList {display: none}
input:checked ~ span.shortFullCcAddressList {display: block}
input ~ span.fullFullToAddressList {display: block}
input ~ span.shortFullToAddressList {display: none}
input:checked ~ span.fullFullToAddressList {display: none}
input:checked ~ span.shortFullToAddressList {display: block}
input ~ span.fullFullBccAddressList {display: block}
input ~ span.shortFullBccAddressList {display: none}
input:checked ~ span.fullFullBccAddressList {display: none}
input:checked ~ span.shortFullBccAddressList {display: block}
}
diff --git a/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox
new file mode 100644
index 00000000..e42eccd7
--- /dev/null
+++ b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox
@@ -0,0 +1,51 @@
+Subject: Testcase 'reply-decrytion-oracle' (PGP INLINE)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf7BxmM0vO8nG37hKqoqOHb35JqprJM+sqF7JFmrsuWe6V2
+PAyyE2wdtq+AhvXjVnggxYLwU+DEFpBTmWr1rsanyV8hWXRbecfN/9gN/4/N9y7Z
+XSx2OeE/uA5z8Kz5vrv/ywMqcVHjB5MQPTcLC2Zlg8MVltpriy6mdAkON4I3t7kl
+j9uwQRY7HeKvsib63HWnYAOV/fYPXXor/lioeYIll08uuCiTh3Z9fEhXQI/az5Ft
+e/xa70xGqviux+OvhoNUSZspzl7vK7e/NTBlC+LF1zVXUXT8prrd+ZFNwKvtn0Hl
+W4KfNqTM9TJB8vpE5FWnH6+B365ZvxZopZ5F/9szp9JGAUCNdX5WujBreg7nTLui
+UrnDNwOvjvsE/gsoO3n3jARK+Tu8PfUl8V1bHiCeGJz/mkA9uGJ/IApcT4rYsoHB
+nVQjW1NJ6A==
+=zrF/
+-----END PGP MESSAGE-----
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MCqjMjAB80hAMAHfa7bdk/6L4DJQBQn+zHRv6oYzzYFC
+8l79DDIE2uorQNFj1ZBw5+pi7+/2QmAANnG2ug5W0HRphg2WPXTUswy5H+mg08PM
+MXRsP9lX5pAXEbLZVp61tvOQHnO/ltBhHHBwRaIq2tiirUUhy5erqLwlkSyN8xHM
+Bh0u/dIJw7ewMk0l3BtF/GuP7l6PtUxT7P0Vwit4h1FV1bc9mSFmBNN16dvixJ4l
+jK0mYEqT97SNZpg0MPOxx8E3xuJptzea4qmACv5zx4gYHlZRM0ZlKNqffmRauWOe
+pDCjZv2F1IUJOg28NzZhKCBVhmhBmP1VmLNYFKGAsNJHAV+3uN2YYWzbhoOJAE0N
+UxLI0EQN4y7OkAnGiRH45HygLxAjTk6dPiP5OD9OhUnSqofAjajlmqzfAAVMxY1a
+epnRKPsnCZU=
+=dqBN
+-----END PGP MESSAGE-----
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MNDSEBVsF78knI+uirDbLSLrHicrXExTocmXr2DZOggI
+zMYCAHyg7ohINA40/8ZuR0bC9h6qCZjjhR+VFe2edRFshXlbuzykjpXNYcSv61Sm
+9TAVpgAExzS5VhAxYIJ6+zWJR8+hgv63oREZPWlJ23utBDAMkEeY7cga3wn1HZMZ
+g4XQZ94a8s9s/I+s3dLOdHGdxw+hmSnxjMhI6TMcZV/Kvr1MkkW10N0h0+hiuq2O
+4owEztpm4See8fCkRfhr0TO+a8ElCtIXjVwqeB0tQh0fU3QaaNiDXYawoFMQXG8N
+nwCP92glfOeAvJn9KuLwO3ee+WKwcrJhsFRMmjziDdJGAUvptVDNrk2P/0fzo/Xl
+ypmw8zhir6ch+4C2+5yFCtVSmC+3Y7+NQ4YE4AR/z5rGvA1lxclulU1DSGkhFTbJ
+XEVyg8o23A==
+=Bs3d
+-----END PGP MESSAGE-----
+
+whoo three encrypted parts inside.
diff --git a/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.html b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.html
new file mode 100644
index 00000000..8b602a22
--- /dev/null
+++ b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.html
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <body>
+ <div style="position: relative; word-wrap: break-word">
+ <a name="att"/>
+ <div id="attachmentDiv">
+ <div class="noquote">
+ <div dir="ltr">Please reply to this message</div>
+ <div dir="ltr">.</div>
+ <div dir="ltr">.</div>
+ <div dir="ltr">.</div>
+ <br/>
+ </div>
+ <table cellspacing="1" cellpadding="1" class="encr">
+ <tr class="encrH">
+ <td dir="ltr">
+ <div class="enc-simple">Encrypted message<a href="kmail:showEncryptionDetails" style="display:block;float:right;">Show Details</a></div>
+ </td>
+ </tr>
+ <tr class="encrB">
+ <td>
+ <div class="noquote">
+ <div dir="ltr">first part</div>
+ </div>
+ </td>
+ </tr>
+ <tr class="encrH">
+ <td dir="ltr">End of encrypted message</td>
+ </tr>
+ </table>
+ <table cellspacing="1" cellpadding="1" class="encr">
+ <tr class="encrH">
+ <td dir="ltr">
+ <div class="enc-simple">Encrypted message<a href="kmail:showEncryptionDetails" style="display:block;float:right;">Show Details</a></div>
+ </td>
+ </tr>
+ <tr class="encrB">
+ <td>
+ <div class="noquote">
+ <div dir="ltr">second part</div>
+ </div>
+ </td>
+ </tr>
+ <tr class="encrH">
+ <td dir="ltr">End of encrypted message</td>
+ </tr>
+ </table>
+ <table cellspacing="1" cellpadding="1" class="encr">
+ <tr class="encrH">
+ <td dir="ltr">
+ <div class="enc-simple">Encrypted message<a href="kmail:showEncryptionDetails" style="display:block;float:right;">Show Details</a></div>
+ </td>
+ </tr>
+ <tr class="encrB">
+ <td>
+ <div class="noquote">
+ <div dir="ltr">third part</div>
+ </div>
+ </td>
+ </tr>
+ <tr class="encrH">
+ <td dir="ltr">End of encrypted message</td>
+ </tr>
+ </table>
+ <div class="noquote">
+ <div dir="ltr">whoo three encrypted parts inside.</div>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.tree b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.tree
new file mode 100644
index 00000000..bbf0edfc
--- /dev/null
+++ b/mimetreeparser/autotests/data/openpgp-inline-multiple.mbox.tree
@@ -0,0 +1,7 @@
+ * MimeTreeParser::MessagePartList
+ * MimeTreeParser::TextMessagePart
+ * MimeTreeParser::MessagePart
+ * MimeTreeParser::EncryptedMessagePart
+ * MimeTreeParser::EncryptedMessagePart
+ * MimeTreeParser::EncryptedMessagePart
+ * MimeTreeParser::MessagePart
diff --git a/mimetreeparser/autotests/nodehelpertest.cpp b/mimetreeparser/autotests/nodehelpertest.cpp
index e9a44495..fe5653dc 100644
--- a/mimetreeparser/autotests/nodehelpertest.cpp
+++ b/mimetreeparser/autotests/nodehelpertest.cpp
@@ -1,273 +1,273 @@
/* Copyright 2015 Sandro Knauß <bugs@sandroknauss.de>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nodehelpertest.h"
#include "nodehelper.h"
-#include <qtest.h>
+#include <QTest>
using namespace MimeTreeParser;
NodeHelperTest::NodeHelperTest()
: QObject()
{
}
void NodeHelperTest::testPersistentIndex()
{
NodeHelper helper;
KMime::Content *node = new KMime::Content();
KMime::Content *node2 = new KMime::Content();
KMime::Content *node2Extra = new KMime::Content();
KMime::Content *subNode = new KMime::Content();
KMime::Content *subsubNode = new KMime::Content(), *subsubNode2 = new KMime::Content();
KMime::Content *node2ExtraSubNode = new KMime::Content();
KMime::Content *node2ExtraSubsubNode = new KMime::Content();
KMime::Content *node2ExtraSubsubNode2 = new KMime::Content();
KMime::Content *extra = new KMime::Content(), *extra2 = new KMime::Content();
KMime::Content *subExtra = new KMime::Content();
KMime::Content *subsubExtra = new KMime::Content();
KMime::Content *subsubExtraNode = new KMime::Content();
subNode->addContent(subsubNode);
subNode->addContent(subsubNode2);
node->addContent(subNode);
subsubExtra->addContent(subsubExtraNode);
helper.attachExtraContent(node, extra);
helper.attachExtraContent(node, extra2);
helper.attachExtraContent(subNode, subExtra);
helper.attachExtraContent(subsubNode2, subsubExtra);
// This simulates Opaque S/MIME signed and encrypted message with attachment
// (attachment is node2SubsubNode2)
node2Extra->addContent(node2ExtraSubNode);
node2ExtraSubNode->addContent(node2ExtraSubsubNode);
node2ExtraSubNode->addContent(node2ExtraSubsubNode2);
helper.attachExtraContent(node2, node2Extra);
/* all content has a internal first child, so indexes starts at 2
* node ""
* -> subNode "2"
* -> subsubNode "2.2"
* -> subsubNode2 "2.3"
*
* node ""
* -> extra "e0"
* -> extra2 "e1"
*
* subNode "2"
* -> subExtra "2:e0"
*
* subsubNode2 "2.3"
* -> subsubExtra "2.3:e0"
* -> subsubExtraNode "2.3:e0:2"
*
* node2 ""
*
* node2 ""
* -> node2Extra "e0"
* -> node2ExtraSubNode "e0:2"
* -> node2ExtraSubsubNode "e0:2.2"
* -> node2ExtraSubsubNode2 "e0:2.3"
*/
- QCOMPARE(helper.persistentIndex(node), QStringLiteral(""));
- QCOMPARE(helper.contentFromIndex(node, QStringLiteral("")), node);
+ QCOMPARE(helper.persistentIndex(node), QString());
+ QCOMPARE(helper.contentFromIndex(node, QString()), node);
QCOMPARE(helper.persistentIndex(node->contents()[0]), QStringLiteral("1"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("1")), node->contents()[0]);
QCOMPARE(helper.persistentIndex(subNode), QStringLiteral("2"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2")), subNode);
QCOMPARE(helper.persistentIndex(subsubNode), QStringLiteral("2.2"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2.2")), subsubNode);
QCOMPARE(helper.persistentIndex(subsubNode2), QStringLiteral("2.3"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2.3")), subsubNode2);
QCOMPARE(helper.persistentIndex(extra), QStringLiteral("e0"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("e0")), extra);
QCOMPARE(helper.persistentIndex(extra2), QStringLiteral("e1"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("e1")), extra2);
QCOMPARE(helper.persistentIndex(subExtra), QStringLiteral("2:e0"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2:e0")), subExtra);
QCOMPARE(helper.persistentIndex(subsubExtra), QStringLiteral("2.3:e0"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2.3:e0")), subsubExtra);
QCOMPARE(helper.persistentIndex(subsubExtraNode), QStringLiteral("2.3:e0:2"));
QCOMPARE(helper.contentFromIndex(node, QStringLiteral("2.3:e0:2")), subsubExtraNode);
QCOMPARE(helper.persistentIndex(node2ExtraSubsubNode2), QStringLiteral("e0:2.3"));
QCOMPARE(helper.contentFromIndex(node2, QStringLiteral("e0:2.3")), node2ExtraSubsubNode2);
delete node;
}
void NodeHelperTest::testHREF()
{
NodeHelper helper;
KMime::Message::Ptr msg(new KMime::Message);
QUrl url;
KMime::Content *node = msg->topLevel();
KMime::Content *subNode = new KMime::Content();
KMime::Content *subsubNode = new KMime::Content(), *subsubNode2 = new KMime::Content();
KMime::Content *extra = new KMime::Content(), *extra2 = new KMime::Content();
KMime::Content *subExtra = new KMime::Content();
KMime::Content *subsubExtra = new KMime::Content();
KMime::Content *subsubExtraNode = new KMime::Content();
subNode->addContent(subsubNode);
subNode->addContent(subsubNode2);
node->addContent(subNode);
subsubExtra->addContent(subsubExtraNode);
helper.attachExtraContent(node, extra);
helper.attachExtraContent(node, extra2);
helper.attachExtraContent(subNode, subExtra);
helper.attachExtraContent(subsubNode2, subsubExtra);
- url = QUrl(QStringLiteral(""));
+ url = QUrl(QString());
QCOMPARE(helper.fromHREF(msg, url), node);
url = QUrl(QStringLiteral("attachment:e0?place=body"));
QCOMPARE(helper.fromHREF(msg, url), extra);
url = QUrl(QStringLiteral("attachment:2.2?place=body"));
QCOMPARE(helper.fromHREF(msg, url), subsubNode);
url = QUrl(QStringLiteral("attachment:2.3:e0:2?place=body"));
QCOMPARE(helper.fromHREF(msg, url), subsubExtraNode);
QCOMPARE(helper.asHREF(node, QStringLiteral("body")), QStringLiteral("attachment:?place=body"));
QCOMPARE(helper.asHREF(extra, QStringLiteral("body")), QStringLiteral("attachment:e0?place=body"));
QCOMPARE(helper.asHREF(subsubNode, QStringLiteral("body")), QStringLiteral("attachment:2.2?place=body"));
QCOMPARE(helper.asHREF(subsubExtraNode, QStringLiteral("body")), QStringLiteral("attachment:2.3:e0:2?place=body"));
}
void NodeHelperTest::testLocalFiles()
{
NodeHelper helper;
KMime::Message::Ptr msg(new KMime::Message);
KMime::Content *node = msg->topLevel();
KMime::Content *subNode = new KMime::Content();
KMime::Content *subsubNode = new KMime::Content(), *subsubNode2 = new KMime::Content();
KMime::Content *extra = new KMime::Content(), *extra2 = new KMime::Content();
KMime::Content *subExtra = new KMime::Content();
KMime::Content *subsubExtra = new KMime::Content();
KMime::Content *subsubExtraNode = new KMime::Content();
subNode->addContent(subsubNode);
subNode->addContent(subsubNode2);
node->addContent(subNode);
subsubExtra->addContent(subsubExtraNode);
helper.attachExtraContent(node, extra);
helper.attachExtraContent(node, extra2);
helper.attachExtraContent(subNode, subExtra);
helper.attachExtraContent(subsubNode2, subsubExtra);
helper.writeNodeToTempFile(node);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(node)), node);
helper.writeNodeToTempFile(subNode);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subNode)), subNode);
helper.writeNodeToTempFile(subsubNode);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subsubNode)), subsubNode);
helper.writeNodeToTempFile(subsubNode2);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subsubNode2)), subsubNode2);
helper.writeNodeToTempFile(extra);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(extra)), extra);
helper.writeNodeToTempFile(subExtra);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subExtra)), subExtra);
helper.writeNodeToTempFile(subsubExtra);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subsubExtra)), subsubExtra);
helper.writeNodeToTempFile(subsubExtraNode);
QCOMPARE(helper.fromHREF(msg, helper.tempFileUrlFromNode(subsubExtraNode)), subsubExtraNode);
}
void NodeHelperTest::testCreateTempDir()
{
QString path;
{
NodeHelper helper;
path = helper.createTempDir(QStringLiteral("foo"));
QVERIFY(path.endsWith(QLatin1String(".index.foo")));
QVERIFY(QDir(path).exists());
QVERIFY(QFile(path).permissions() & QFileDevice::WriteUser);
QVERIFY(QFile(path).permissions() & QFileDevice::ExeUser);
QVERIFY(QFile(path).permissions() & QFileDevice::ReadUser);
}
QVERIFY(!QDir(path).exists());
}
void NodeHelperTest::testFromAsString()
{
const QString tlSender = QStringLiteral("Foo <foo@example.com>");
const QString encSender = QStringLiteral("Bar <bar@example.com>");
NodeHelper helper;
// msg (KMime::Message)
// |- subNode
// |- encNode (KMime::Message)
// |- encSubNode
//
// subNode
// |- subExtra
//
// encSubNode
// |- encSubExtra
KMime::Message msg;
msg.from(true)->fromUnicodeString(tlSender, "UTF-8");
auto node = msg.topLevel();
auto subNode = new KMime::Content();
auto subExtra = new KMime::Content();
// Encapsulated message
KMime::Message *encMsg = new KMime::Message;
encMsg->from(true)->fromUnicodeString(encSender, "UTF-8");
auto encNode = encMsg->topLevel();
auto encSubNode = new KMime::Content();
auto encSubExtra = new KMime::Content();
node->addContent(subNode);
node->addContent(encMsg);
encNode->addContent(encSubNode);
helper.attachExtraContent(subNode, subExtra);
helper.attachExtraContent(encSubNode, encSubExtra);
QCOMPARE(helper.fromAsString(node), tlSender);
QCOMPARE(helper.fromAsString(subNode), tlSender);
QCOMPARE(helper.fromAsString(subExtra), tlSender);
QEXPECT_FAIL("", "Returning sender of encapsulated message is not yet implemented", Continue);
QCOMPARE(helper.fromAsString(encNode), encSender);
QEXPECT_FAIL("", "Returning sender of encapsulated message is not yet implemented", Continue);
QCOMPARE(helper.fromAsString(encSubNode), encSender);
QEXPECT_FAIL("", "Returning sender of encapsulated message is not yet implemented", Continue);
QCOMPARE(helper.fromAsString(encSubExtra), encSender);
}
QTEST_GUILESS_MAIN(NodeHelperTest)
diff --git a/mimetreeparser/autotests/nodehelpertest.h b/mimetreeparser/autotests/nodehelpertest.h
index 569d6cb0..c1e14e94 100644
--- a/mimetreeparser/autotests/nodehelpertest.h
+++ b/mimetreeparser/autotests/nodehelpertest.h
@@ -1,42 +1,42 @@
/* Copyright 2015 Sandro Knauß <bugs@sandroknauss.de>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NODEHELPERTEST_H
#define NODEHELPERTEST_H
#include <QObject>
#include <KMime/Message>
namespace MimeTreeParser {
class NodeHelperTest : public QObject
{
Q_OBJECT
public:
NodeHelperTest();
private Q_SLOTS:
void testPersistentIndex();
void testLocalFiles();
void testHREF();
void testCreateTempDir();
void testFromAsString();
};
}
#endif
diff --git a/mimetreeparser/autotests/setupenv.cpp b/mimetreeparser/autotests/setupenv.cpp
index 4836fe62..5f82b868 100644
--- a/mimetreeparser/autotests/setupenv.cpp
+++ b/mimetreeparser/autotests/setupenv.cpp
@@ -1,33 +1,33 @@
/*
Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Copyright (c) 2010 Leo Franchi <lfranchi@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.
*/
#include "setupenv.h"
#include <QStandardPaths>
#include <QFile>
#include <QDir>
void MimeTreeParser::Test::setupEnv()
{
qputenv("LC_ALL", "C");
- qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QString::fromLatin1("/.qttest")).constData());
+ qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.qttest")).constData());
QStandardPaths::setTestModeEnabled(true);
}
diff --git a/mimetreeparser/metainfo.yaml b/mimetreeparser/metainfo.yaml
index 7459afaa..05426646 100644
--- a/mimetreeparser/metainfo.yaml
+++ b/mimetreeparser/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer:
description: MimeTreeParser Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: MimeTreeParser
cmake: "KF5::MimeTreeParser"
- cmakename: KF5MimeTreeParser
+cmakename: KF5MimeTreeParser
diff --git a/mimetreeparser/src/CMakeLists.txt b/mimetreeparser/src/CMakeLists.txt
index 30efc4fa..6741ee26 100644
--- a/mimetreeparser/src/CMakeLists.txt
+++ b/mimetreeparser/src/CMakeLists.txt
@@ -1,153 +1,152 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libmimetreeparser\")
# target_include_directories does not handle empty include paths
include_directories(${GPGME_INCLUDES})
set(libmimetreeparser_main_SRCS
bodyformatter/applicationpgpencrypted.cpp
bodyformatter/applicationpkcs7mime.cpp
bodyformatter/encrypted.cpp
bodyformatter/mailman.cpp
bodyformatter/multipartalternative.cpp
bodyformatter/multipartencrypted.cpp
bodyformatter/multipartmixed.cpp
bodyformatter/multipartsigned.cpp
bodyformatter/textplain.cpp
bodyformatter/texthtml.cpp
bodyformatter/utils.cpp
interfaces/bodypartformatter.cpp
interfaces/objecttreesource.cpp
interfaces/bodypart.cpp
job/qgpgmejobexecutor.cpp
utils/util.cpp
bodypartformatter.cpp
bodypartformatterfactory.cpp
cryptohelper.cpp
nodehelper.cpp
objecttreeparser.cpp
messagepart.cpp
partnodebodypart.cpp
simpleobjecttreesource.cpp
memento/cryptobodypartmemento.cpp
memento/decryptverifybodypartmemento.cpp
memento/verifydetachedbodypartmemento.cpp
memento/verifyopaquebodypartmemento.cpp
)
set(mimetreeparser_temporaryfile_SRCS
temporaryfile/attachmenttemporaryfilesdirs.cpp
)
ecm_generate_headers(MimeTreeParser_Camelcasemain_HEADERS
HEADER_NAMES
BodyPartFormatterFactory
Enums
MessagePart
NodeHelper
ObjectTreeParser
PartMetaData
PartNodeBodyPart
SimpleObjectTreeSource
REQUIRED_HEADERS MimeTreeParser_main_HEADERS
PREFIX MimeTreeParser
)
ecm_generate_headers(MimeTreeParser_Camelcaseutils_HEADERS
HEADER_NAMES
Util
REQUIRED_HEADERS MimeTreeParser_utils_HEADERS
PREFIX MimeTreeParser
RELATIVE utils
)
ecm_generate_headers(MimeTreeParser_Camelcaseinterfaces_HEADERS
HEADER_NAMES
BodyPartFormatter
BodyPart
ObjectTreeSource
REQUIRED_HEADERS MimeTreeParser_interfaces_HEADERS
PREFIX MimeTreeParser
RELATIVE interfaces
)
ecm_generate_headers(MimeTreeParser_Camelcasetemporaryfile_HEADERS
HEADER_NAMES
AttachmentTemporaryFilesDirs
REQUIRED_HEADERS MimeTreeParser_temporaryfile_HEADERS
PREFIX MimeTreeParser
RELATIVE temporaryfile
)
install(FILES
${MimeTreeParser_Camelcaseutils_HEADERS}
${MimeTreeParser_Camelcaseinterfaces_HEADERS}
${MimeTreeParser_Camelcasemain_HEADERS}
${MimeTreeParser_Camelcasetemporaryfile_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/MimeTreeParser
COMPONENT Devel
)
install(FILES
${MimeTreeParser_utils_HEADERS}
${MimeTreeParser_interfaces_HEADERS}
${MimeTreeParser_main_HEADERS}
${MimeTreeParser_temporaryfile_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_export.h
- ${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_debug.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/mimetreeparser
COMPONENT Devel
)
ecm_generate_pri_file(BASE_NAME MimeTreeParser
LIB_NAME KF5MimeTreeParser
FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/MimeTreeParser
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR}
)
set(libmimetreeparser_SRCS
${libmimetreeparser_main_SRCS}
${libmimetreeparser_extra_SRCS}
${mimetreeparser_temporaryfile_SRCS}
)
ecm_qt_declare_logging_category(libmimetreeparser_SRCS HEADER mimetreeparser_debug.h IDENTIFIER MIMETREEPARSER_LOG CATEGORY_NAME org.kde.pim.mimetreeparser)
add_library(KF5MimeTreeParser
${libmimetreeparser_SRCS}
)
generate_export_header(KF5MimeTreeParser BASE_NAME mimetreeparser)
add_library(KF5::MimeTreeParser ALIAS KF5MimeTreeParser)
set(mimetreeparser_LINK_LIBRARIES
)
target_link_libraries(KF5MimeTreeParser
PRIVATE
QGpgme
KF5::Codecs
KF5::I18n
KF5::CoreAddons
KF5::Mime
Qt5::Gui
)
install(TARGETS
KF5MimeTreeParser
EXPORT KF5MimeTreeParserTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK}
)
set_target_properties(KF5MimeTreeParser PROPERTIES
VERSION ${MIMETREEPARSER_VERSION_STRING}
SOVERSION ${MIMETREEPARSER_SOVERSION}
EXPORT_NAME MimeTreeParser
)
target_include_directories(KF5MimeTreeParser INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/MimeTreeParser/;${KDE_INSTALL_INCLUDEDIR_KF5}/mimetreeparser>")
diff --git a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
index 5d1889cf..73650b0c 100644
--- a/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
+++ b/mimetreeparser/src/bodyformatter/applicationpkcs7mime.cpp
@@ -1,169 +1,169 @@
/*
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.
*/
#include "applicationpkcs7mime.h"
#include "utils.h"
#include "objecttreeparser.h"
#include "messagepart.h"
#include <QGpgME/Protocol>
#include <KMime/Content>
#include <QTextCodec>
#include "mimetreeparser_debug.h"
using namespace MimeTreeParser;
const ApplicationPkcs7MimeBodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::self;
const Interface::BodyPartFormatter *ApplicationPkcs7MimeBodyPartFormatter::create()
{
if (!self) {
self = new ApplicationPkcs7MimeBodyPartFormatter();
}
return self;
}
MessagePart::Ptr ApplicationPkcs7MimeBodyPartFormatter::process(Interface::BodyPart &part) const
{
KMime::Content *node = part.content();
if (node->head().isEmpty()) {
return MessagePart::Ptr();
}
const auto smimeCrypto = QGpgME::smime();
if (!smimeCrypto) {
return MessagePart::Ptr();
}
// we are also registered for octet-stream, in that case stop here if that's not a part for us
const auto mt = node->contentType()->mimeType();
const auto isCorrectMimeType = mt == QByteArrayLiteral("application/pkcs7-mime") || mt == QByteArrayLiteral("application/x-pkcs7-mime");
const auto hasCorrectName = mt == QByteArrayLiteral("application/octet-stream")
&& (node->contentType()->name().endsWith(QLatin1String("p7m"))
|| node->contentType()->name().endsWith(QLatin1String("p7s"))
|| node->contentType()->name().endsWith(QLatin1String("p7c")));
if (!isCorrectMimeType && !hasCorrectName) {
return {};
}
const QString smimeType = node->contentType()->parameter(QStringLiteral("smime-type")).toLower();
if (smimeType == QLatin1String("certs-only")) {
part.processResult()->setNeverDisplayInline(true);
CertMessagePart::Ptr mp(new CertMessagePart(part.objectTreeParser(), node, smimeCrypto, part.source()->autoImportKeys()));
return mp;
}
bool isSigned = (smimeType == QLatin1String("signed-data"));
bool isEncrypted = (smimeType == QLatin1String("enveloped-data"));
// Analyze "signTestNode" node to find/verify a signature.
// If zero part.objectTreeParser() verification was successfully done after
// decrypting via recursion by insertAndParseNewChildNode().
KMime::Content *signTestNode = isEncrypted ? nullptr : node;
// We try decrypting the content
// if we either *know* that it is an encrypted message part
// or there is neither signed nor encrypted parameter.
MessagePart::Ptr mp;
if (!isSigned) {
if (isEncrypted) {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data";
} else {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?";
}
auto _mp = EncryptedMessagePart::Ptr(new EncryptedMessagePart(part.objectTreeParser(),
node->decodedText(), smimeCrypto,
part.nodeHelper()->fromAsString(node), node));
mp = _mp;
_mp->setIsEncrypted(true);
_mp->setDecryptMessage(part.source()->decryptMessage());
PartMetaData *messagePart(_mp->partMetaData());
if (!part.source()->decryptMessage()) {
isEncrypted = true;
signTestNode = nullptr; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
} else {
_mp->startDecryption();
if (messagePart->isDecryptable) {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - encryption found - enveloped (encrypted) data !";
isEncrypted = true;
part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted);
signTestNode = nullptr;
} else {
// decryption failed, which could be because the part was encrypted but
// decryption failed, or because we didn't know if it was encrypted, tried,
// and failed. If the message was not actually encrypted, we continue
// assuming it's signed
if (_mp->passphraseError() || (smimeType.isEmpty() && messagePart->isEncrypted)) {
isEncrypted = true;
signTestNode = nullptr;
}
if (isEncrypted) {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !";
} else {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO encryption found";
}
}
}
if (isEncrypted) {
part.nodeHelper()->setEncryptionState(node, KMMsgFullyEncrypted);
}
}
- // We now try signature verification if necessarry.
+ // We now try signature verification if necessary.
if (signTestNode) {
if (isSigned) {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime == S/MIME TYPE: opaque signed data";
} else {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - type unknown - opaque signed data ?";
}
const QTextCodec *aCodec(part.objectTreeParser()->codecFor(signTestNode));
const QByteArray signaturetext = signTestNode->decodedContent();
auto _mp = SignedMessagePart::Ptr(new SignedMessagePart(part.objectTreeParser(),
aCodec->toUnicode(signaturetext), smimeCrypto,
part.nodeHelper()->fromAsString(node), signTestNode));
mp = _mp;
_mp->startVerificationDetached(signaturetext, nullptr, QByteArray());
if (_mp->isSigned()) {
if (!isSigned) {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - signature found - opaque signed data !";
}
if (signTestNode != node) {
part.nodeHelper()->setSignatureState(node, KMMsgFullySigned);
}
} else {
qCDebug(MIMETREEPARSER_LOG) << "pkcs7 mime - NO signature found :-(";
}
}
return mp;
}
diff --git a/mimetreeparser/src/bodyformatter/mailman.cpp b/mimetreeparser/src/bodyformatter/mailman.cpp
index affff518..ee5c89e6 100644
--- a/mimetreeparser/src/bodyformatter/mailman.cpp
+++ b/mimetreeparser/src/bodyformatter/mailman.cpp
@@ -1,172 +1,173 @@
/*
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.
*/
#include "mailman.h"
#include "utils.h"
#include "objecttreeparser.h"
#include "messagepart.h"
#include <KMime/Content>
#include "mimetreeparser_debug.h"
using namespace MimeTreeParser;
const MailmanBodyPartFormatter *MailmanBodyPartFormatter::self;
const Interface::BodyPartFormatter *MailmanBodyPartFormatter::create()
{
if (!self) {
self = new MailmanBodyPartFormatter();
}
return self;
}
bool MailmanBodyPartFormatter::isMailmanMessage(KMime::Content *curNode) const
{
if (!curNode || curNode->head().isEmpty()) {
return false;
}
if (curNode->hasHeader("X-Mailman-Version")) {
return true;
}
if (KMime::Headers::Base *header = curNode->headerByType("X-Mailer")) {
if (header->asUnicodeString().contains(QLatin1String("MAILMAN"), Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
MessagePart::Ptr MailmanBodyPartFormatter::process(Interface::BodyPart &part) const
{
KMime::Content *curNode = part.content();
if (!isMailmanMessage(curNode)) {
return MessagePart::Ptr();
}
+ //Latin1 or utf8 ?
const QString str = QString::fromLatin1(curNode->decodedContent());
//###
const QLatin1String delim1("--__--__--\n\nMessage:");
const QLatin1String delim2("--__--__--\r\n\r\nMessage:");
const QLatin1String delimZ2("--__--__--\n\n_____________");
const QLatin1String delimZ1("--__--__--\r\n\r\n_____________");
QString partStr, digestHeaderStr;
int thisDelim = str.indexOf(delim1, Qt::CaseInsensitive);
if (thisDelim == -1) {
thisDelim = str.indexOf(delim2, Qt::CaseInsensitive);
}
if (thisDelim == -1) {
return MessagePart::Ptr();
}
int nextDelim = str.indexOf(delim1, thisDelim + 1, Qt::CaseInsensitive);
if (-1 == nextDelim) {
nextDelim = str.indexOf(delim2, thisDelim + 1, Qt::CaseInsensitive);
}
if (-1 == nextDelim) {
nextDelim = str.indexOf(delimZ1, thisDelim + 1, Qt::CaseInsensitive);
}
if (-1 == nextDelim) {
nextDelim = str.indexOf(delimZ2, thisDelim + 1, Qt::CaseInsensitive);
}
if (nextDelim < 0) {
return MessagePart::Ptr();
}
//if ( curNode->mRoot )
// curNode = curNode->mRoot;
// at least one message found: build a mime tree
digestHeaderStr = QStringLiteral("Content-Type: text/plain\nContent-Description: digest header\n\n");
digestHeaderStr += str.midRef(0, thisDelim);
MessagePartList::Ptr mpl(new MessagePartList(part.objectTreeParser()));
mpl->appendSubPart(createAndParseTempNode(part, part.topLevelContent(), digestHeaderStr.toLatin1().constData(), "Digest Header"));
//mReader->queueHtml("<br><hr><br>");
- // temporarily change curent node's Content-Type
+ // temporarily change current node's Content-Type
// to get our embedded RfC822 messages properly inserted
curNode->contentType()->setMimeType("multipart/digest");
while (-1 < nextDelim) {
int thisEoL = str.indexOf(QLatin1String("\nMessage:"), thisDelim, Qt::CaseInsensitive);
if (-1 < thisEoL) {
thisDelim = thisEoL + 1;
} else {
thisEoL = str.indexOf(QLatin1String("\n_____________"), thisDelim, Qt::CaseInsensitive);
if (-1 < thisEoL) {
thisDelim = thisEoL + 1;
}
}
thisEoL = str.indexOf(QLatin1Char('\n'), thisDelim);
if (-1 < thisEoL) {
thisDelim = thisEoL + 1;
} else {
thisDelim = thisDelim + 1;
}
//while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
// ++thisDelim;
partStr = QStringLiteral("Content-Type: message/rfc822\nContent-Description: embedded message\n\n");
partStr += str.midRef(thisDelim, nextDelim - thisDelim);
QString subject = QStringLiteral("embedded message");
QString subSearch = QStringLiteral("\nSubject:");
int subPos = partStr.indexOf(subSearch, 0, Qt::CaseInsensitive);
if (-1 < subPos) {
subject = partStr.mid(subPos + subSearch.length());
thisEoL = subject.indexOf(QLatin1Char('\n'));
if (-1 < thisEoL) {
subject.truncate(thisEoL);
}
}
qCDebug(MIMETREEPARSER_LOG) << " embedded message found: \"" << subject;
mpl->appendSubPart(createAndParseTempNode(part, part.topLevelContent(), partStr.toLatin1().constData(), subject.toLatin1().constData()));
//mReader->queueHtml("<br><hr><br>");
thisDelim = nextDelim + 1;
nextDelim = str.indexOf(delim1, thisDelim, Qt::CaseInsensitive);
if (-1 == nextDelim) {
nextDelim = str.indexOf(delim2, thisDelim, Qt::CaseInsensitive);
}
if (-1 == nextDelim) {
nextDelim = str.indexOf(delimZ1, thisDelim, Qt::CaseInsensitive);
}
if (-1 == nextDelim) {
nextDelim = str.indexOf(delimZ2, thisDelim, Qt::CaseInsensitive);
}
}
- // reset curent node's Content-Type
+ // reset current node's Content-Type
curNode->contentType()->setMimeType("text/plain");
int thisEoL = str.indexOf(QLatin1String("_____________"), thisDelim);
if (-1 < thisEoL) {
thisDelim = thisEoL;
thisEoL = str.indexOf(QLatin1Char('\n'), thisDelim);
if (-1 < thisEoL) {
thisDelim = thisEoL + 1;
}
} else {
thisDelim = thisDelim + 1;
}
partStr = QStringLiteral("Content-Type: text/plain\nContent-Description: digest footer\n\n");
partStr += str.midRef(thisDelim);
mpl->appendSubPart(createAndParseTempNode(part, part.topLevelContent(), partStr.toLatin1().constData(), "Digest Footer"));
return mpl;
}
diff --git a/mimetreeparser/src/bodyformatter/multipartalternative.cpp b/mimetreeparser/src/bodyformatter/multipartalternative.cpp
index 4d489a27..febd792a 100644
--- a/mimetreeparser/src/bodyformatter/multipartalternative.cpp
+++ b/mimetreeparser/src/bodyformatter/multipartalternative.cpp
@@ -1,90 +1,90 @@
/*
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.
*/
#include "multipartalternative.h"
#include "utils.h"
#include "objecttreeparser.h"
#include "messagepart.h"
#include <KMime/Content>
#include "mimetreeparser_debug.h"
using namespace MimeTreeParser;
const MultiPartAlternativeBodyPartFormatter *MultiPartAlternativeBodyPartFormatter::self;
const Interface::BodyPartFormatter *MultiPartAlternativeBodyPartFormatter::create()
{
if (!self) {
self = new MultiPartAlternativeBodyPartFormatter();
}
return self;
}
MessagePart::Ptr MultiPartAlternativeBodyPartFormatter::process(Interface::BodyPart &part) const
{
KMime::Content *node = part.content();
if (node->contents().isEmpty()) {
return MessagePart::Ptr();
}
auto preferredMode = part.source()->preferredMode();
AlternativeMessagePart::Ptr mp(new AlternativeMessagePart(part.objectTreeParser(), node, preferredMode));
if (mp->childParts().isEmpty()) {
MimeMessagePart::Ptr _mp(new MimeMessagePart(part.objectTreeParser(), node->contents().at(0), false));
return _mp;
}
KMime::Content *dataIcal = mp->childParts().contains(Util::MultipartIcal) ? mp->childParts()[Util::MultipartIcal]->content() : nullptr;
KMime::Content *dataHtml = mp->childParts().contains(Util::MultipartHtml) ? mp->childParts()[Util::MultipartHtml]->content() : nullptr;
KMime::Content *dataPlain = mp->childParts().contains(Util::MultipartPlain) ? mp->childParts()[Util::MultipartPlain]->content() : nullptr;
// Make sure that in default ical is preferred over html and plain text
if (dataIcal && ((preferredMode != Util::MultipartHtml && preferredMode != Util::MultipartPlain))) {
if (dataHtml) {
part.nodeHelper()->setNodeProcessed(dataHtml, false);
}
if (dataPlain) {
part.nodeHelper()->setNodeProcessed(dataPlain, false);
}
preferredMode = Util::MultipartIcal;
} else if ((dataHtml && (preferredMode == Util::MultipartHtml || preferredMode == Util::Html))
|| (dataHtml && dataPlain && dataPlain->body().isEmpty())) {
if (dataPlain) {
part.nodeHelper()->setNodeProcessed(dataPlain, false);
}
preferredMode = Util::MultipartHtml;
} else if (!(preferredMode == Util::MultipartHtml) && dataPlain) {
part.nodeHelper()->setNodeProcessed(dataHtml, false);
preferredMode = Util::MultipartPlain;
}
// qDebug() << " MessagePart::Ptr MultiPartAlternativeBodyPartFormatter::process(Interface::BodyPart &part) const";
// for (int i = 0; i < mp->availableModes().count(); ++i) {
-// qDebug() << "MultiPartAlternativeBodyPartFormatter::processe Modes " << MimeTreeParser::Util::htmlModeToString(mp->availableModes().at(i));
+// qDebug() << "MultiPartAlternativeBodyPartFormatter::processed Modes " << MimeTreeParser::Util::htmlModeToString(mp->availableModes().at(i));
// }
// qDebug() << "MultiPartAlternativeBodyPartFormatter::process preferred " << MimeTreeParser::Util::htmlModeToString(preferredMode);
part.source()->setHtmlMode(preferredMode, mp->availableModes());
mp->setPreferredMode(preferredMode);
return mp;
}
diff --git a/mimetreeparser/src/bodypartformatter.cpp b/mimetreeparser/src/bodypartformatter.cpp
index de7f4bc1..728cbb40 100644
--- a/mimetreeparser/src/bodypartformatter.cpp
+++ b/mimetreeparser/src/bodypartformatter.cpp
@@ -1,176 +1,177 @@
/* -*- c++ -*-
bodypartformatter.cpp
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
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.
+ 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.
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.
*/
#include "mimetreeparser_debug.h"
#include "bodyformatter/applicationpgpencrypted.h"
#include "bodyformatter/applicationpkcs7mime.h"
#include "bodyformatter/encrypted.h"
#include "bodyformatter/mailman.h"
#include "bodyformatter/multipartalternative.h"
#include "bodyformatter/multipartmixed.h"
#include "bodyformatter/multipartencrypted.h"
#include "bodyformatter/multipartsigned.h"
#include "bodyformatter/texthtml.h"
#include "bodyformatter/textplain.h"
#include "interfaces/bodypartformatter.h"
#include "interfaces/bodypart.h"
#include "bodypartformatterfactory.h"
#include "bodypartformatterfactory_p.h"
#include "objecttreeparser.h"
#include "messagepart.h"
#include <KMime/Content>
#include <QUrl>
using namespace MimeTreeParser;
namespace {
class AnyTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
static const AnyTypeBodyPartFormatter *self;
public:
MessagePart::Ptr process(Interface::BodyPart &part) const override
{
KMime::Content *node = part.content();
const auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(part.objectTreeParser(), node, part.source()->decryptMessage()));
part.processResult()->setInlineSignatureState(mp->signatureState());
part.processResult()->setInlineEncryptionState(mp->encryptionState());
part.processResult()->setNeverDisplayInline(true);
mp->setNeverDisplayInline(true);
mp->setIsImage(false);
return mp;
}
static const MimeTreeParser::Interface::BodyPartFormatter *create()
{
if (!self) {
self = new AnyTypeBodyPartFormatter();
}
return self;
}
};
const AnyTypeBodyPartFormatter *AnyTypeBodyPartFormatter::self = nullptr;
class ImageTypeBodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
static const ImageTypeBodyPartFormatter *self;
public:
static const MimeTreeParser::Interface::BodyPartFormatter *create()
{
if (!self) {
self = new ImageTypeBodyPartFormatter();
}
return self;
}
MessagePart::Ptr process(Interface::BodyPart &part) const override
{
KMime::Content *node = part.content();
auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(part.objectTreeParser(), node, part.source()->decryptMessage()));
mp->setIsImage(true);
part.processResult()->setInlineSignatureState(mp->signatureState());
part.processResult()->setInlineEncryptionState(mp->encryptionState());
auto preferredMode = part.source()->preferredMode();
const bool isHtmlPreferred = (preferredMode == Util::Html) || (preferredMode == Util::MultipartHtml);
if (node->parent() && node->parent()->contentType()->subType() == "related" && isHtmlPreferred) {
part.nodeHelper()->setNodeDisplayedEmbedded(node, true);
part.nodeHelper()->setNodeDisplayedHidden(node, true);
return mp;
}
return mp;
}
};
const ImageTypeBodyPartFormatter *ImageTypeBodyPartFormatter::self = nullptr;
class MessageRfc822BodyPartFormatter : public MimeTreeParser::Interface::BodyPartFormatter
{
static const MessageRfc822BodyPartFormatter *self;
public:
MessagePart::Ptr process(Interface::BodyPart &) const override;
static const MimeTreeParser::Interface::BodyPartFormatter *create();
};
const MessageRfc822BodyPartFormatter *MessageRfc822BodyPartFormatter::self;
const MimeTreeParser::Interface::BodyPartFormatter *MessageRfc822BodyPartFormatter::create()
{
if (!self) {
self = new MessageRfc822BodyPartFormatter();
}
return self;
}
MessagePart::Ptr MessageRfc822BodyPartFormatter::process(Interface::BodyPart &part) const
{
const KMime::Message::Ptr message = part.content()->bodyAsMessage();
return MessagePart::Ptr(new EncapsulatedRfc822MessagePart(part.objectTreeParser(), part.content(), message));
}
} // anon namespace
void BodyPartFormatterFactoryPrivate::messageviewer_create_builtin_bodypart_formatters()
{
insert(QStringLiteral("application/pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create());
insert(QStringLiteral("application/x-pkcs7-mime"), ApplicationPkcs7MimeBodyPartFormatter::create());
insert(QStringLiteral("application/pgp-encrypted"), ApplicationPGPEncryptedBodyPartFormatter::create());
insert(QStringLiteral("application/octet-stream"), ApplicationPkcs7MimeBodyPartFormatter::create());
insert(QStringLiteral("application/octet-stream"), EncryptedBodyPartFormatter::create());
insert(QStringLiteral("application/octet-stream"), AnyTypeBodyPartFormatter::create());
insert(QStringLiteral("text/pgp"), EncryptedBodyPartFormatter::create());
insert(QStringLiteral("text/html"), TextHtmlBodyPartFormatter::create());
insert(QStringLiteral("text/rtf"), AnyTypeBodyPartFormatter::create());
insert(QStringLiteral("text/plain"), MailmanBodyPartFormatter::create());
insert(QStringLiteral("text/plain"), TextPlainBodyPartFormatter::create());
insert(QStringLiteral("image/png"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/jpeg"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/gif"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/svg+xml"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/bmp"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("image/vnd.microsoft.icon"), ImageTypeBodyPartFormatter::create());
insert(QStringLiteral("message/rfc822"), MessageRfc822BodyPartFormatter::create());
insert(QStringLiteral("multipart/alternative"), MultiPartAlternativeBodyPartFormatter::create());
insert(QStringLiteral("multipart/encrypted"), MultiPartEncryptedBodyPartFormatter::create());
insert(QStringLiteral("multipart/signed"), MultiPartSignedBodyPartFormatter::create());
insert(QStringLiteral("multipart/mixed"), MultiPartMixedBodyPartFormatter::create());
}
diff --git a/mimetreeparser/src/job/qgpgmejobexecutor.cpp b/mimetreeparser/src/job/qgpgmejobexecutor.cpp
index 8a11e039..2f62d55a 100644
--- a/mimetreeparser/src/job/qgpgmejobexecutor.cpp
+++ b/mimetreeparser/src/job/qgpgmejobexecutor.cpp
@@ -1,149 +1,149 @@
/*
Copyright (c) 2008 Volker Krause <vkrause@kde.org>
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.
*/
#include "qgpgmejobexecutor.h"
#include "mimetreeparser_debug.h"
#include <QGpgME/DecryptVerifyJob>
#include <QGpgME/ImportJob>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>
#include <QEventLoop>
#include <cassert>
using namespace GpgME;
using namespace MimeTreeParser;
QGpgMEJobExecutor::QGpgMEJobExecutor(QObject *parent) : QObject(parent)
{
setObjectName(QStringLiteral("KleoJobExecutor"));
mEventLoop = new QEventLoop(this);
}
GpgME::VerificationResult QGpgMEJobExecutor::exec(
QGpgME::VerifyDetachedJob *job, const QByteArray &signature, const QByteArray &signedData)
{
qCDebug(MIMETREEPARSER_LOG) << "Starting detached verification job";
- connect(job, &QGpgME::VerifyDetachedJob::result, this, QOverload<const GpgME::VerificationResult &>::of(&QGpgMEJobExecutor::verificationResult));
+ connect(job, &QGpgME::VerifyDetachedJob::result, this, qOverload<const GpgME::VerificationResult &>(&QGpgMEJobExecutor::verificationResult));
GpgME::Error err = job->start(signature, signedData);
if (err) {
return VerificationResult(err);
}
mEventLoop->exec(QEventLoop::ExcludeUserInputEvents);
return mVerificationResult;
}
GpgME::VerificationResult QGpgMEJobExecutor::exec(
QGpgME::VerifyOpaqueJob *job, const QByteArray &signedData, QByteArray &plainText)
{
qCDebug(MIMETREEPARSER_LOG) << "Starting opaque verification job";
- connect(job, &QGpgME::VerifyOpaqueJob::result, this, QOverload<const GpgME::VerificationResult &, const QByteArray &>::of(&QGpgMEJobExecutor::verificationResult ));
+ connect(job, &QGpgME::VerifyOpaqueJob::result, this, qOverload<const GpgME::VerificationResult &, const QByteArray &>(&QGpgMEJobExecutor::verificationResult));
GpgME::Error err = job->start(signedData);
if (err) {
plainText.clear();
return VerificationResult(err);
}
mEventLoop->exec(QEventLoop::ExcludeUserInputEvents);
plainText = mData;
return mVerificationResult;
}
std::pair< GpgME::DecryptionResult, GpgME::VerificationResult > QGpgMEJobExecutor::exec(
QGpgME::DecryptVerifyJob *job, const QByteArray &cipherText, QByteArray &plainText)
{
qCDebug(MIMETREEPARSER_LOG) << "Starting decryption job";
connect(job, &QGpgME::DecryptVerifyJob::result, this, &QGpgMEJobExecutor::decryptResult);
GpgME::Error err = job->start(cipherText);
if (err) {
plainText.clear();
return std::make_pair(DecryptionResult(err), VerificationResult(err));
}
mEventLoop->exec(QEventLoop::ExcludeUserInputEvents);
plainText = mData;
return std::make_pair(mDecryptResult, mVerificationResult);
}
GpgME::ImportResult QGpgMEJobExecutor::exec(QGpgME::ImportJob *job, const QByteArray &certData)
{
connect(job, &QGpgME::AbstractImportJob::result, this, &QGpgMEJobExecutor::importResult);
GpgME::Error err = job->start(certData);
if (err) {
return ImportResult(err);
}
mEventLoop->exec(QEventLoop::ExcludeUserInputEvents);
return mImportResult;
}
Error QGpgMEJobExecutor::auditLogError() const
{
return mAuditLogError;
}
void QGpgMEJobExecutor::verificationResult(const GpgME::VerificationResult &result)
{
qCDebug(MIMETREEPARSER_LOG) << "Detached verification job finished";
QGpgME::Job *job = qobject_cast<QGpgME::Job *>(sender());
assert(job);
mVerificationResult = result;
mAuditLogError = job->auditLogError();
mAuditLog = job->auditLogAsHtml();
mEventLoop->quit();
}
void QGpgMEJobExecutor::verificationResult(const GpgME::VerificationResult &result, const QByteArray &plainText)
{
qCDebug(MIMETREEPARSER_LOG) << "Opaque verification job finished";
QGpgME::Job *job = qobject_cast<QGpgME::Job *>(sender());
assert(job);
mVerificationResult = result;
mData = plainText;
mAuditLogError = job->auditLogError();
mAuditLog = job->auditLogAsHtml();
mEventLoop->quit();
}
void QGpgMEJobExecutor::decryptResult(
const GpgME::DecryptionResult &decryptionresult, const GpgME::VerificationResult &verificationresult, const QByteArray &plainText)
{
qCDebug(MIMETREEPARSER_LOG) << "Decryption job finished";
QGpgME::Job *job = qobject_cast<QGpgME::Job *>(sender());
assert(job);
mVerificationResult = verificationresult;
mDecryptResult = decryptionresult;
mData = plainText;
mAuditLogError = job->auditLogError();
mAuditLog = job->auditLogAsHtml();
mEventLoop->quit();
}
void QGpgMEJobExecutor::importResult(const GpgME::ImportResult &result)
{
QGpgME::Job *job = qobject_cast<QGpgME::Job *>(sender());
assert(job);
mImportResult = result;
mAuditLogError = job->auditLogError();
mAuditLog = job->auditLogAsHtml();
mEventLoop->quit();
}
QString QGpgMEJobExecutor::auditLogAsHtml() const
{
return mAuditLog;
}
diff --git a/mimetreeparser/src/memento/cryptobodypartmemento.cpp b/mimetreeparser/src/memento/cryptobodypartmemento.cpp
index 1378e0ad..9ab74616 100644
--- a/mimetreeparser/src/memento/cryptobodypartmemento.cpp
+++ b/mimetreeparser/src/memento/cryptobodypartmemento.cpp
@@ -1,53 +1,54 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "cryptobodypartmemento.h"
using namespace GpgME;
using namespace MimeTreeParser;
CryptoBodyPartMemento::CryptoBodyPartMemento()
: QObject(nullptr)
, Interface::BodyPartMemento()
, m_running(false)
{
}
CryptoBodyPartMemento::~CryptoBodyPartMemento()
{
}
bool CryptoBodyPartMemento::isRunning() const
{
return m_running;
}
void CryptoBodyPartMemento::setAuditLog(const Error &err, const QString &log)
{
m_auditLogError = err;
m_auditLog = log;
}
void CryptoBodyPartMemento::setRunning(bool running)
{
m_running = running;
}
void CryptoBodyPartMemento::detach()
{
disconnect(this, SIGNAL(update(MimeTreeParser::UpdateMode)), nullptr, nullptr);
}
diff --git a/mimetreeparser/src/memento/cryptobodypartmemento.h b/mimetreeparser/src/memento/cryptobodypartmemento.h
index 2416e787..d200c6cb 100644
--- a/mimetreeparser/src/memento/cryptobodypartmemento.h
+++ b/mimetreeparser/src/memento/cryptobodypartmemento.h
@@ -1,72 +1,73 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef MIMETREEPARSER_CRYPTOBODYPARTMEMENTO_H
#define MIMETREEPARSER_CRYPTOBODYPARTMEMENTO_H
#include <gpgme++/error.h>
#include <QObject>
#include <QString>
#include "interfaces/bodypart.h"
#include "enums.h"
namespace MimeTreeParser {
class CryptoBodyPartMemento : public QObject, public Interface::BodyPartMemento
{
Q_OBJECT
public:
CryptoBodyPartMemento();
~CryptoBodyPartMemento() override;
virtual bool start() = 0;
virtual void exec() = 0;
bool isRunning() const;
const QString &auditLogAsHtml() const
{
return m_auditLog;
}
GpgME::Error auditLogError() const
{
return m_auditLogError;
}
void detach() override;
Q_SIGNALS:
void update(MimeTreeParser::UpdateMode);
protected Q_SLOTS:
void notify()
{
Q_EMIT update(MimeTreeParser::Force);
}
protected:
void setAuditLog(const GpgME::Error &err, const QString &log);
void setRunning(bool running);
private:
bool m_running;
QString m_auditLog;
GpgME::Error m_auditLogError;
};
}
#endif // MIMETREEPARSER_CRYPTOBODYPARTMEMENTO_H
diff --git a/mimetreeparser/src/memento/decryptverifybodypartmemento.cpp b/mimetreeparser/src/memento/decryptverifybodypartmemento.cpp
index d0fa9901..f77f98ca 100644
--- a/mimetreeparser/src/memento/decryptverifybodypartmemento.cpp
+++ b/mimetreeparser/src/memento/decryptverifybodypartmemento.cpp
@@ -1,82 +1,83 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "decryptverifybodypartmemento.h"
#include <QGpgME/DecryptVerifyJob>
-#include <qstringlist.h>
+#include <QStringList>
using namespace QGpgME;
using namespace GpgME;
using namespace MimeTreeParser;
DecryptVerifyBodyPartMemento::DecryptVerifyBodyPartMemento(DecryptVerifyJob *job, const QByteArray &cipherText)
: CryptoBodyPartMemento()
, m_cipherText(cipherText)
, m_job(job)
{
Q_ASSERT(m_job);
}
DecryptVerifyBodyPartMemento::~DecryptVerifyBodyPartMemento()
{
if (m_job) {
m_job->slotCancel();
}
}
bool DecryptVerifyBodyPartMemento::start()
{
Q_ASSERT(m_job);
if (const Error err = m_job->start(m_cipherText)) {
m_dr = DecryptionResult(err);
return false;
}
connect(m_job.data(), &DecryptVerifyJob::result,
this, &DecryptVerifyBodyPartMemento::slotResult);
setRunning(true);
return true;
}
void DecryptVerifyBodyPartMemento::exec()
{
Q_ASSERT(m_job);
QByteArray plainText;
setRunning(true);
const std::pair<DecryptionResult, VerificationResult> p = m_job->exec(m_cipherText, plainText);
saveResult(p.first, p.second, plainText);
m_job->deleteLater(); // exec'ed jobs don't delete themselves
m_job = nullptr;
}
void DecryptVerifyBodyPartMemento::saveResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText)
{
Q_ASSERT(m_job);
setRunning(false);
m_dr = dr;
m_vr = vr;
m_plainText = plainText;
setAuditLog(m_job->auditLogError(), m_job->auditLogAsHtml());
}
void DecryptVerifyBodyPartMemento::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText)
{
saveResult(dr, vr, plainText);
m_job = nullptr;
notify();
}
diff --git a/mimetreeparser/src/memento/decryptverifybodypartmemento.h b/mimetreeparser/src/memento/decryptverifybodypartmemento.h
index d60dbc0a..cc4b1c6c 100644
--- a/mimetreeparser/src/memento/decryptverifybodypartmemento.h
+++ b/mimetreeparser/src/memento/decryptverifybodypartmemento.h
@@ -1,75 +1,76 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef MIMETREEPARSER_DECRYPTVERIFYBODYPARTMEMENTO_H
#define MIMETREEPARSER_DECRYPTVERIFYBODYPARTMEMENTO_H
#include "cryptobodypartmemento.h"
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <QPointer>
#include "interfaces/bodypart.h"
namespace QGpgME {
class DecryptVerifyJob;
}
namespace MimeTreeParser {
class DecryptVerifyBodyPartMemento : public CryptoBodyPartMemento
{
Q_OBJECT
public:
- DecryptVerifyBodyPartMemento(QGpgME::DecryptVerifyJob *job, const QByteArray &cipherText);
+ explicit DecryptVerifyBodyPartMemento(QGpgME::DecryptVerifyJob *job, const QByteArray &cipherText);
~DecryptVerifyBodyPartMemento() override;
bool start() override;
void exec() override;
const QByteArray &plainText() const
{
return m_plainText;
}
const GpgME::DecryptionResult &decryptResult() const
{
return m_dr;
}
const GpgME::VerificationResult &verifyResult() const
{
return m_vr;
}
private Q_SLOTS:
void slotResult(const GpgME::DecryptionResult &dr, const GpgME::VerificationResult &vr, const QByteArray &plainText);
private:
void saveResult(const GpgME::DecryptionResult &, const GpgME::VerificationResult &, const QByteArray &);
private:
// input:
const QByteArray m_cipherText;
QPointer<QGpgME::DecryptVerifyJob> m_job;
// output:
GpgME::DecryptionResult m_dr;
GpgME::VerificationResult m_vr;
QByteArray m_plainText;
};
}
#endif // MIMETREEPARSER_DECRYPTVERIFYBODYPARTMEMENTO_H
diff --git a/mimetreeparser/src/memento/verifydetachedbodypartmemento.cpp b/mimetreeparser/src/memento/verifydetachedbodypartmemento.cpp
index 3b78452a..2c3a86f4 100644
--- a/mimetreeparser/src/memento/verifydetachedbodypartmemento.cpp
+++ b/mimetreeparser/src/memento/verifydetachedbodypartmemento.cpp
@@ -1,174 +1,175 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "verifydetachedbodypartmemento.h"
#include "mimetreeparser_debug.h"
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/KeyListJob>
#include <gpgme++/keylistresult.h>
-#include <qstringlist.h>
+#include <QStringList>
#include <cassert>
using namespace QGpgME;
using namespace GpgME;
using namespace MimeTreeParser;
VerifyDetachedBodyPartMemento::VerifyDetachedBodyPartMemento(VerifyDetachedJob *job, KeyListJob *klj, const QByteArray &signature, const QByteArray &plainText)
: CryptoBodyPartMemento()
, m_signature(signature)
, m_plainText(plainText)
, m_job(job)
, m_keylistjob(klj)
{
assert(m_job);
}
VerifyDetachedBodyPartMemento::~VerifyDetachedBodyPartMemento()
{
if (m_job) {
m_job->slotCancel();
}
if (m_keylistjob) {
m_keylistjob->slotCancel();
}
}
bool VerifyDetachedBodyPartMemento::start()
{
assert(m_job);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento started";
#endif
connect(m_job.data(), &VerifyDetachedJob::result,
this, &VerifyDetachedBodyPartMemento::slotResult);
if (const Error err = m_job->start(m_signature, m_plainText)) {
m_vr = VerificationResult(err);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento stopped with error";
#endif
return false;
}
setRunning(true);
return true;
}
void VerifyDetachedBodyPartMemento::exec()
{
assert(m_job);
setRunning(true);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento execed";
#endif
saveResult(m_job->exec(m_signature, m_plainText));
m_job->deleteLater(); // exec'ed jobs don't delete themselves
m_job = nullptr;
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento after execed";
#endif
if (canStartKeyListJob()) {
std::vector<GpgME::Key> keys;
m_keylistjob->exec(keyListPattern(), /*secretOnly=*/ false, keys);
if (!keys.empty()) {
m_key = keys.back();
}
}
if (m_keylistjob) {
m_keylistjob->deleteLater(); // exec'ed jobs don't delete themselves
}
m_keylistjob = nullptr;
setRunning(false);
}
bool VerifyDetachedBodyPartMemento::canStartKeyListJob() const
{
if (!m_keylistjob) {
return false;
}
const char *const fpr = m_vr.signature(0).fingerprint();
return fpr && *fpr;
}
QStringList VerifyDetachedBodyPartMemento::keyListPattern() const
{
assert(canStartKeyListJob());
return QStringList(QString::fromLatin1(m_vr.signature(0).fingerprint()));
}
void VerifyDetachedBodyPartMemento::saveResult(const VerificationResult &vr)
{
assert(m_job);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento::saveResult called";
#endif
m_vr = vr;
setAuditLog(m_job->auditLogError(), m_job->auditLogAsHtml());
}
void VerifyDetachedBodyPartMemento::slotResult(const VerificationResult &vr)
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento::slotResult called";
#endif
saveResult(vr);
m_job = nullptr;
if (canStartKeyListJob() && startKeyListJob()) {
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento: canStartKeyListJob && startKeyListJob";
#endif
return;
}
if (m_keylistjob) {
m_keylistjob->deleteLater();
}
m_keylistjob = nullptr;
setRunning(false);
notify();
}
bool VerifyDetachedBodyPartMemento::startKeyListJob()
{
assert(canStartKeyListJob());
if (const GpgME::Error err = m_keylistjob->start(keyListPattern())) {
return false;
}
connect(m_keylistjob.data(), &Job::done, this, &VerifyDetachedBodyPartMemento::slotKeyListJobDone);
connect(m_keylistjob.data(), &KeyListJob::nextKey,
this, &VerifyDetachedBodyPartMemento::slotNextKey);
return true;
}
void VerifyDetachedBodyPartMemento::slotNextKey(const GpgME::Key &key)
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento::slotNextKey called";
#endif
m_key = key;
}
void VerifyDetachedBodyPartMemento::slotKeyListJobDone()
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyDetachedBodyPartMemento::slotKeyListJobDone called";
#endif
m_keylistjob = nullptr;
setRunning(false);
notify();
}
diff --git a/mimetreeparser/src/memento/verifydetachedbodypartmemento.h b/mimetreeparser/src/memento/verifydetachedbodypartmemento.h
index bce0b5cf..36611a15 100644
--- a/mimetreeparser/src/memento/verifydetachedbodypartmemento.h
+++ b/mimetreeparser/src/memento/verifydetachedbodypartmemento.h
@@ -1,80 +1,81 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef MIMETREEPARSER_VERIFYDETACHEDBODYPARTMEMENTO_H
#define MIMETREEPARSER_VERIFYDETACHEDBODYPARTMEMENTO_H
#include "cryptobodypartmemento.h"
#include <gpgme++/verificationresult.h>
#include <gpgme++/key.h>
#include <QString>
#include <QPointer>
#include "interfaces/bodypart.h"
namespace QGpgME {
class VerifyDetachedJob;
class KeyListJob;
}
class QStringList;
namespace MimeTreeParser {
class VerifyDetachedBodyPartMemento : public CryptoBodyPartMemento
{
Q_OBJECT
public:
- VerifyDetachedBodyPartMemento(QGpgME::VerifyDetachedJob *job, QGpgME::KeyListJob *klj, const QByteArray &signature, const QByteArray &plainText);
+ explicit VerifyDetachedBodyPartMemento(QGpgME::VerifyDetachedJob *job, QGpgME::KeyListJob *klj, const QByteArray &signature, const QByteArray &plainText);
~VerifyDetachedBodyPartMemento() override;
bool start() override;
void exec() override;
const GpgME::VerificationResult &verifyResult() const
{
return m_vr;
}
const GpgME::Key &signingKey() const
{
return m_key;
}
private Q_SLOTS:
void slotResult(const GpgME::VerificationResult &vr);
void slotKeyListJobDone();
void slotNextKey(const GpgME::Key &);
private:
void saveResult(const GpgME::VerificationResult &);
bool canStartKeyListJob() const;
QStringList keyListPattern() const;
bool startKeyListJob();
private:
// input:
const QByteArray m_signature;
const QByteArray m_plainText;
QPointer<QGpgME::VerifyDetachedJob> m_job;
QPointer<QGpgME::KeyListJob> m_keylistjob;
// output:
GpgME::VerificationResult m_vr;
GpgME::Key m_key;
};
}
#endif // MIMETREEPARSER_VERIFYDETACHEDBODYPARTMEMENTO_H
diff --git a/mimetreeparser/src/memento/verifyopaquebodypartmemento.cpp b/mimetreeparser/src/memento/verifyopaquebodypartmemento.cpp
index 18494890..591ac9a4 100644
--- a/mimetreeparser/src/memento/verifyopaquebodypartmemento.cpp
+++ b/mimetreeparser/src/memento/verifyopaquebodypartmemento.cpp
@@ -1,175 +1,176 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "verifyopaquebodypartmemento.h"
#include "mimetreeparser_debug.h"
#include <QGpgME/VerifyOpaqueJob>
#include <QGpgME/KeyListJob>
#include <gpgme++/keylistresult.h>
-#include <qstringlist.h>
+#include <QStringList>
#include <cassert>
using namespace QGpgME;
using namespace GpgME;
using namespace MimeTreeParser;
VerifyOpaqueBodyPartMemento::VerifyOpaqueBodyPartMemento(VerifyOpaqueJob *job, KeyListJob *klj, const QByteArray &signature)
: CryptoBodyPartMemento()
, m_signature(signature)
, m_job(job)
, m_keylistjob(klj)
{
assert(m_job);
}
VerifyOpaqueBodyPartMemento::~VerifyOpaqueBodyPartMemento()
{
if (m_job) {
m_job->slotCancel();
}
if (m_keylistjob) {
m_keylistjob->slotCancel();
}
}
bool VerifyOpaqueBodyPartMemento::start()
{
assert(m_job);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento started";
#endif
if (const Error err = m_job->start(m_signature)) {
m_vr = VerificationResult(err);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento stopped with error";
#endif
return false;
}
connect(m_job.data(), &VerifyOpaqueJob::result,
this, &VerifyOpaqueBodyPartMemento::slotResult);
setRunning(true);
return true;
}
void VerifyOpaqueBodyPartMemento::exec()
{
assert(m_job);
setRunning(true);
QByteArray plainText;
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento execed";
#endif
saveResult(m_job->exec(m_signature, plainText), plainText);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento after execed";
#endif
m_job->deleteLater(); // exec'ed jobs don't delete themselves
m_job = nullptr;
if (canStartKeyListJob()) {
std::vector<GpgME::Key> keys;
m_keylistjob->exec(keyListPattern(), /*secretOnly=*/ false, keys);
if (!keys.empty()) {
m_key = keys.back();
}
}
if (m_keylistjob) {
m_keylistjob->deleteLater(); // exec'ed jobs don't delete themselves
}
m_keylistjob = nullptr;
setRunning(false);
}
bool VerifyOpaqueBodyPartMemento::canStartKeyListJob() const
{
if (!m_keylistjob) {
return false;
}
const char *const fpr = m_vr.signature(0).fingerprint();
return fpr && *fpr;
}
QStringList VerifyOpaqueBodyPartMemento::keyListPattern() const
{
assert(canStartKeyListJob());
return QStringList(QString::fromLatin1(m_vr.signature(0).fingerprint()));
}
void VerifyOpaqueBodyPartMemento::saveResult(const VerificationResult &vr, const QByteArray &plainText)
{
assert(m_job);
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento::saveResult called";
#endif
m_vr = vr;
m_plainText = plainText;
setAuditLog(m_job->auditLogError(), m_job->auditLogAsHtml());
}
void VerifyOpaqueBodyPartMemento::slotResult(const VerificationResult &vr, const QByteArray &plainText)
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento::slotResult called";
#endif
saveResult(vr, plainText);
m_job = nullptr;
if (canStartKeyListJob() && startKeyListJob()) {
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento: canStartKeyListJob && startKeyListJob";
#endif
return;
}
if (m_keylistjob) {
m_keylistjob->deleteLater();
}
m_keylistjob = nullptr;
setRunning(false);
notify();
}
bool VerifyOpaqueBodyPartMemento::startKeyListJob()
{
assert(canStartKeyListJob());
if (const GpgME::Error err = m_keylistjob->start(keyListPattern())) {
return false;
}
connect(m_keylistjob.data(), &Job::done, this, &VerifyOpaqueBodyPartMemento::slotKeyListJobDone);
connect(m_keylistjob.data(), &KeyListJob::nextKey,
this, &VerifyOpaqueBodyPartMemento::slotNextKey);
return true;
}
void VerifyOpaqueBodyPartMemento::slotNextKey(const GpgME::Key &key)
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento::slotNextKey called";
#endif
m_key = key;
}
void VerifyOpaqueBodyPartMemento::slotKeyListJobDone()
{
#ifdef DEBUG_SIGNATURE
qCDebug(MIMETREEPARSER_LOG) << "tokoe: VerifyOpaqueBodyPartMemento::slotKeyListJobDone called";
#endif
m_keylistjob = nullptr;
setRunning(false);
notify();
}
diff --git a/mimetreeparser/src/memento/verifyopaquebodypartmemento.h b/mimetreeparser/src/memento/verifyopaquebodypartmemento.h
index 41526700..cbe8d9fc 100644
--- a/mimetreeparser/src/memento/verifyopaquebodypartmemento.h
+++ b/mimetreeparser/src/memento/verifyopaquebodypartmemento.h
@@ -1,86 +1,87 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef MIMETREEPARSER_VERIFYOPAQUEBODYPARTMEMENTO_H
#define MIMETREEPARSER_VERIFYOPAQUEBODYPARTMEMENTO_H
#include "cryptobodypartmemento.h"
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
#include <QString>
#include <QPointer>
#include "interfaces/bodypart.h"
namespace QGpgME {
class VerifyOpaqueJob;
class KeyListJob;
}
class QStringList;
namespace MimeTreeParser {
class VerifyOpaqueBodyPartMemento : public CryptoBodyPartMemento
{
Q_OBJECT
public:
- VerifyOpaqueBodyPartMemento(QGpgME::VerifyOpaqueJob *job, QGpgME::KeyListJob *klj, const QByteArray &signature);
+ explicit VerifyOpaqueBodyPartMemento(QGpgME::VerifyOpaqueJob *job, QGpgME::KeyListJob *klj, const QByteArray &signature);
~VerifyOpaqueBodyPartMemento() override;
bool start() override;
void exec() override;
const QByteArray &plainText() const
{
return m_plainText;
}
const GpgME::VerificationResult &verifyResult() const
{
return m_vr;
}
const GpgME::Key &signingKey() const
{
return m_key;
}
private Q_SLOTS:
void slotResult(const GpgME::VerificationResult &vr, const QByteArray &plainText);
void slotKeyListJobDone();
void slotNextKey(const GpgME::Key &);
private:
void saveResult(const GpgME::VerificationResult &, const QByteArray &);
bool canStartKeyListJob() const;
QStringList keyListPattern() const;
bool startKeyListJob();
private:
// input:
const QByteArray m_signature;
QPointer<QGpgME::VerifyOpaqueJob> m_job;
QPointer<QGpgME::KeyListJob> m_keylistjob;
// output:
GpgME::VerificationResult m_vr;
QByteArray m_plainText;
GpgME::Key m_key;
};
}
#endif // MIMETREEPARSER_VERIFYOPAQUEBODYPARTMEMENTO_H
diff --git a/mimetreeparser/src/messagepart.cpp b/mimetreeparser/src/messagepart.cpp
index 4ed4330d..5a64a50e 100644
--- a/mimetreeparser/src/messagepart.cpp
+++ b/mimetreeparser/src/messagepart.cpp
@@ -1,1364 +1,1369 @@
/*
Copyright (c) 2015 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.
*/
#include "messagepart.h"
#include "mimetreeparser_debug.h"
#include "cryptohelper.h"
#include "objecttreeparser.h"
#include "job/qgpgmejobexecutor.h"
#include "memento/cryptobodypartmemento.h"
#include "memento/decryptverifybodypartmemento.h"
#include "memento/verifydetachedbodypartmemento.h"
#include "memento/verifyopaquebodypartmemento.h"
#include "bodyformatter/utils.h"
#include <KMime/Content>
#include <QGpgME/DN>
#include <QGpgME/Protocol>
#include <QGpgME/ImportJob>
#include <QGpgME/KeyListJob>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/VerifyOpaqueJob>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme.h>
#include <KLocalizedString>
#include <QTextCodec>
#include <QUrl>
using namespace MimeTreeParser;
//------MessagePart-----------------------
namespace MimeTreeParser {
class MessagePartPrivate
{
public:
MessagePart *mParentPart = nullptr;
QVector<MessagePart::Ptr> mBlocks;
KMime::Content *mNode = nullptr;
KMime::Content *mAttachmentNode = nullptr;
QString mText;
PartMetaData mMetaData;
bool mRoot = false;
bool mIsImage = false;
bool mNeverDisplayInline = false;
};
}
MessagePart::MessagePart(ObjectTreeParser *otp, const QString &text)
: mOtp(otp)
, d(new MessagePartPrivate)
{
d->mText = text;
}
MessagePart::~MessagePart() = default;
MessagePart *MessagePart::parentPart() const
{
return d->mParentPart;
}
void MessagePart::setParentPart(MessagePart *parentPart)
{
d->mParentPart = parentPart;
}
QString MessagePart::htmlContent() const
{
return text();
}
QString MessagePart::plaintextContent() const
{
return text();
}
PartMetaData *MessagePart::partMetaData() const
{
return &d->mMetaData;
}
Interface::BodyPartMemento *MessagePart::memento() const
{
return nodeHelper()->bodyPartMemento(content(), "__plugin__");
}
void MessagePart::setMemento(Interface::BodyPartMemento *memento)
{
nodeHelper()->setBodyPartMemento(content(), "__plugin__", memento);
}
KMime::Content *MessagePart::content() const
{
return d->mNode;
}
void MessagePart::setContent(KMime::Content *node)
{
d->mNode = node;
}
KMime::Content *MessagePart::attachmentContent() const
{
return d->mAttachmentNode;
}
void MessagePart::setAttachmentContent(KMime::Content *node)
{
d->mAttachmentNode = node;
}
bool MessagePart::isAttachment() const
{
return d->mAttachmentNode;
}
QString MessagePart::attachmentIndex() const
{
return attachmentContent()->index().toString();
}
QString MessagePart::attachmentLink() const
{
return mOtp->nodeHelper()->asHREF(content(), QStringLiteral("body"));
}
QString MessagePart::makeLink(const QString &path) const
{
// FIXME: use a PRNG for the first arg, instead of a serial number
static int serial = 0;
if (path.isEmpty()) {
return {};
}
return QStringLiteral("x-kmail:/bodypart/%1/%2/%3")
.arg(serial++).arg(content()->index().toString())
.arg(QString::fromLatin1(QUrl::toPercentEncoding(path, "/")));
}
void MessagePart::setIsRoot(bool root)
{
d->mRoot = root;
}
bool MessagePart::isRoot() const
{
return d->mRoot;
}
QString MessagePart::text() const
{
return d->mText;
}
void MessagePart::setText(const QString &text)
{
d->mText = text;
}
bool MessagePart::isHtml() const
{
return false;
}
Interface::ObjectTreeSource *MessagePart::source() const
{
Q_ASSERT(mOtp);
return mOtp->mSource;
}
NodeHelper *MessagePart::nodeHelper() const
{
Q_ASSERT(mOtp);
return mOtp->nodeHelper();
}
void MessagePart::parseInternal(KMime::Content *node, bool onlyOneMimePart)
{
auto subMessagePart = mOtp->parseObjectTreeInternal(node, onlyOneMimePart);
d->mRoot = subMessagePart->isRoot();
foreach (const auto &part, subMessagePart->subParts()) {
appendSubPart(part);
}
}
QString MessagePart::renderInternalText() const
{
QString text;
foreach (const auto &mp, subParts()) {
text += mp->text();
}
return text;
}
void MessagePart::fix() const
{
foreach (const auto &mp, subParts()) {
const auto m = mp.dynamicCast<MessagePart>();
if (m) {
m->fix();
}
}
}
void MessagePart::appendSubPart(const MessagePart::Ptr &messagePart)
{
messagePart->setParentPart(this);
d->mBlocks.append(messagePart);
}
const QVector<MessagePart::Ptr> &MessagePart::subParts() const
{
return d->mBlocks;
}
bool MessagePart::hasSubParts() const
{
return !d->mBlocks.isEmpty();
}
void MessagePart::clearSubParts()
{
d->mBlocks.clear();
}
bool MessagePart::neverDisplayInline() const
{
return d->mNeverDisplayInline;
}
void MessagePart::setNeverDisplayInline(bool displayInline)
{
d->mNeverDisplayInline = displayInline;
}
bool MessagePart::isImage() const
{
return d->mIsImage;
}
void MessagePart::setIsImage(bool image)
{
d->mIsImage = image;
}
//-----MessagePartList----------------------
MessagePartList::MessagePartList(ObjectTreeParser *otp)
: MessagePart(otp, QString())
{
}
MessagePartList::~MessagePartList()
{
}
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, bool decryptMessage)
: MessagePartList(otp)
, mDecryptMessage(decryptMessage)
{
if (!node) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setContent(node);
parseContent();
}
TextMessagePart::~TextMessagePart()
{
}
bool TextMessagePart::decryptMessage() const
{
return mDecryptMessage;
}
void TextMessagePart::parseContent()
{
const auto aCodec = mOtp->codecFor(content());
const QString &fromAddress = mOtp->nodeHelper()->fromAsString(content());
mSignatureState = KMMsgNotSigned;
mEncryptionState = KMMsgNotEncrypted;
const auto blocks = prepareMessageForDecryption(content()->decodedContent());
const auto cryptProto = QGpgME::openpgp();
if (!blocks.isEmpty()) {
/* 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
+ * the status is set to unencrypted
*/
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(block.text()))));
} else if (block.type() == PgpMessageBlock) {
EncryptedMessagePart::Ptr mp(new EncryptedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
mp->setDecryptMessage(decryptMessage());
mp->setIsEncrypted(true);
appendSubPart(mp);
if (!decryptMessage()) {
continue;
}
mp->startDecryption(block.text(), aCodec);
if (mp->partMetaData()->inProgress) {
continue;
}
} else if (block.type() == ClearsignedBlock) {
SignedMessagePart::Ptr mp(new SignedMessagePart(mOtp, QString(), cryptProto, fromAddress, nullptr));
appendSubPart(mp);
mp->startVerification(block.text(), aCodec);
} 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(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
{
return mEncryptionState;
}
KMMsgSignatureState TextMessagePart::signatureState() const
{
return mSignatureState;
}
bool TextMessagePart::showLink() const
{
return !temporaryFilePath().isEmpty();
}
bool TextMessagePart::isFirstTextPart() const
{
return content()->topLevel()->textContent() == content();
}
bool TextMessagePart::hasLabel() const
{
return !NodeHelper::fileName(content()).isEmpty();
}
QString TextMessagePart::label() const
{
const QString name = content()->contentType()->name();
QString label = name.isEmpty() ? NodeHelper::fileName(content()) : name;
if (label.isEmpty()) {
label = i18nc("display name for an unnamed attachment", "Unnamed");
}
return label;
}
QString TextMessagePart::comment() const
{
const QString comment = content()->contentDescription()->asUnicodeString();
if (comment == label()) {
return {};
}
return comment;
}
QString TextMessagePart::temporaryFilePath() const
{
return nodeHelper()->writeNodeToTempFile(content());
}
//-----AttachmentMessageBlock----------------------
AttachmentMessagePart::AttachmentMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage)
: TextMessagePart(otp, node, decryptMessage)
{
}
AttachmentMessagePart::~AttachmentMessagePart()
{
}
//-----HtmlMessageBlock----------------------
HtmlMessagePart::HtmlMessagePart(ObjectTreeParser *otp, KMime::Content *node, Interface::ObjectTreeSource *source)
: MessagePart(otp, QString())
, mSource(source)
{
if (!node) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setContent(node);
const QByteArray partBody(node->decodedContent());
mBodyHTML = mOtp->codecFor(node)->toUnicode(partBody);
mCharset = NodeHelper::charset(node);
}
HtmlMessagePart::~HtmlMessagePart()
{
}
void HtmlMessagePart::fix() const
{
mOtp->mHtmlContent += mBodyHTML;
mOtp->mHtmlContentCharset = mCharset;
}
QString HtmlMessagePart::text() const
{
return mBodyHTML;
}
+QString MimeTreeParser::HtmlMessagePart::plaintextContent() const
+{
+ return QString();
+}
+
bool HtmlMessagePart::isHtml() const
{
return true;
}
QString HtmlMessagePart::bodyHtml() const
{
return mBodyHTML;
}
//-----MimeMessageBlock----------------------
MimeMessagePart::MimeMessagePart(ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart)
: MessagePart(otp, QString())
, mOnlyOneMimePart(onlyOneMimePart)
{
if (!node) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setContent(node);
parseInternal(node, mOnlyOneMimePart);
}
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, Util::HtmlMode preferredMode)
: MessagePart(otp, QString())
, mPreferredMode(preferredMode)
{
setContent(node);
KMime::Content *dataIcal = findTypeInDirectChilds(node, "text/calendar");
KMime::Content *dataHtml = findTypeInDirectChilds(node, "text/html");
KMime::Content *dataText = findTypeInDirectChilds(node, "text/plain");
if (!dataHtml) {
// 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.
dataHtml = findTypeInDirectChilds(node, "multipart/related");
// Still not found? Stupid apple mail actually puts the attachments inside of the
// multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
// here.
- // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden
+ // Do this only when preferring HTML mail, though, since otherwise the attachments are hidden
// when displaying plain text.
if (!dataHtml) {
dataHtml = findTypeInDirectChilds(node, "multipart/mixed");
}
}
if (dataIcal) {
mChildNodes[Util::MultipartIcal] = dataIcal;
}
if (dataText) {
mChildNodes[Util::MultipartPlain] = dataText;
}
if (dataHtml) {
mChildNodes[Util::MultipartHtml] = dataHtml;
}
if (mChildNodes.isEmpty()) {
qCWarning(MIMETREEPARSER_LOG) << "no valid nodes";
return;
}
QMapIterator<Util::HtmlMode, KMime::Content *> i(mChildNodes);
while (i.hasNext()) {
i.next();
mChildParts[i.key()] = MimeMessagePart::Ptr(new MimeMessagePart(mOtp, i.value(), true));
}
}
AlternativeMessagePart::~AlternativeMessagePart()
{
}
Util::HtmlMode AlternativeMessagePart::preferredMode() const
{
return mPreferredMode;
}
void AlternativeMessagePart::setPreferredMode(Util::HtmlMode preferredMode)
{
mPreferredMode = preferredMode;
}
QList<Util::HtmlMode> AlternativeMessagePart::availableModes()
{
return mChildParts.keys();
}
QString AlternativeMessagePart::text() const
{
if (mChildParts.contains(Util::MultipartPlain)) {
return mChildParts[Util::MultipartPlain]->text();
}
return QString();
}
void AlternativeMessagePart::fix() const
{
if (mChildParts.contains(Util::MultipartPlain)) {
mChildParts[Util::MultipartPlain]->fix();
}
const auto mode = preferredMode();
if (mode != Util::MultipartPlain && mChildParts.contains(mode)) {
mChildParts[mode]->fix();
}
}
const QMap<Util::HtmlMode, MimeMessagePart::Ptr> &AlternativeMessagePart::childParts() const
{
return mChildParts;
}
bool AlternativeMessagePart::isHtml() const
{
return mChildParts.contains(Util::MultipartHtml);
}
QString AlternativeMessagePart::plaintextContent() const
{
return text();
}
QString AlternativeMessagePart::htmlContent() const
{
if (mChildParts.contains(Util::MultipartHtml)) {
return mChildParts[Util::MultipartHtml]->text();
} else {
return plaintextContent();
}
}
//-----CertMessageBlock----------------------
CertMessagePart::CertMessagePart(ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport)
: MessagePart(otp, QString())
, mAutoImport(autoImport)
, mCryptoProto(cryptoProto)
{
if (!node) {
qCWarning(MIMETREEPARSER_LOG) << "not a valid node";
return;
}
setContent(node);
if (!mAutoImport) {
return;
}
const QByteArray certData = node->decodedContent();
QGpgME::ImportJob *import = mCryptoProto->importJob();
QGpgMEJobExecutor executor;
mImportResult = executor.exec(import, certData);
}
CertMessagePart::~CertMessagePart()
{
}
QString CertMessagePart::text() const
{
return QString();
}
const GpgME::ImportResult &CertMessagePart::importResult() const
{
return mImportResult;
}
//-----SignedMessageBlock---------------------
SignedMessagePart::SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node)
: MessagePart(otp, text)
, mCryptoProto(cryptoProto)
, mFromAddress(fromAddress)
{
setContent(node);
partMetaData()->technicalProblem = (mCryptoProto == nullptr);
partMetaData()->isSigned = true;
partMetaData()->isGoodSignature = false;
partMetaData()->keyTrust = GpgME::Signature::Unknown;
partMetaData()->status = i18n("Wrong Crypto Plug-In.");
partMetaData()->status_code = GPGME_SIG_STAT_NONE;
}
SignedMessagePart::~SignedMessagePart()
{
}
void SignedMessagePart::setIsSigned(bool isSigned)
{
partMetaData()->isSigned = isSigned;
}
bool SignedMessagePart::isSigned() const
{
return partMetaData()->isSigned;
}
bool SignedMessagePart::okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode)
{
NodeHelper *nodeHelper = mOtp->nodeHelper();
partMetaData()->isSigned = false;
partMetaData()->technicalProblem = (mCryptoProto == nullptr);
partMetaData()->keyTrust = GpgME::Signature::Unknown;
partMetaData()->status = i18n("Wrong Crypto Plug-In.");
partMetaData()->status_code = GPGME_SIG_STAT_NONE;
const QByteArray mementoName = "verification";
CryptoBodyPartMemento *m = dynamic_cast<CryptoBodyPartMemento *>(nodeHelper->bodyPartMemento(content(), mementoName));
Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
if (!m && mCryptoProto) {
if (!signature.isEmpty()) {
QGpgME::VerifyDetachedJob *job = mCryptoProto->verifyDetachedJob();
if (job) {
m = new VerifyDetachedBodyPartMemento(job, mCryptoProto->keyListJob(), signature, data);
}
} else {
QGpgME::VerifyOpaqueJob *job = mCryptoProto->verifyOpaqueJob();
if (job) {
m = new VerifyOpaqueBodyPartMemento(job, mCryptoProto->keyListJob(), data);
}
}
if (m) {
if (mOtp->allowAsync()) {
QObject::connect(m, &CryptoBodyPartMemento::update,
nodeHelper, &NodeHelper::update);
if (m->start()) {
partMetaData()->inProgress = true;
mOtp->mHasPendingAsyncJobs = true;
}
} else {
m->exec();
}
nodeHelper->setBodyPartMemento(content(), mementoName, m);
}
} else if (m && m->isRunning()) {
partMetaData()->inProgress = true;
mOtp->mHasPendingAsyncJobs = true;
} else {
partMetaData()->inProgress = false;
mOtp->mHasPendingAsyncJobs = false;
}
if (m && !partMetaData()->inProgress) {
if (!signature.isEmpty()) {
mVerifiedText = data;
}
setVerificationResult(m, textNode);
}
if (!m && !partMetaData()->inProgress) {
QString errorMsg;
QString cryptPlugLibName;
QString cryptPlugDisplayName;
if (mCryptoProto) {
cryptPlugLibName = mCryptoProto->name();
cryptPlugDisplayName = mCryptoProto->displayName();
}
if (!mCryptoProto) {
if (cryptPlugDisplayName.isEmpty()) {
errorMsg = i18n("No appropriate crypto plug-in was found.");
} else {
errorMsg = i18nc("%1 is either 'OpenPGP' or 'S/MIME'",
"No %1 plug-in was found.",
cryptPlugDisplayName);
}
} else {
errorMsg = i18n("Crypto plug-in \"%1\" cannot verify signatures.",
cryptPlugLibName);
}
partMetaData()->errorText = i18n("The message is signed, but the "
"validity of the signature cannot be "
"verified.<br />"
"Reason: %1",
errorMsg);
}
return partMetaData()->isSigned;
}
static int signatureToStatus(const GpgME::Signature &sig)
{
switch (sig.status().code()) {
case GPG_ERR_NO_ERROR:
return GPGME_SIG_STAT_GOOD;
case GPG_ERR_BAD_SIGNATURE:
return GPGME_SIG_STAT_BAD;
case GPG_ERR_NO_PUBKEY:
return GPGME_SIG_STAT_NOKEY;
case GPG_ERR_NO_DATA:
return GPGME_SIG_STAT_NOSIG;
case GPG_ERR_SIG_EXPIRED:
return GPGME_SIG_STAT_GOOD_EXP;
case GPG_ERR_KEY_EXPIRED:
return GPGME_SIG_STAT_GOOD_EXPKEY;
default:
return GPGME_SIG_STAT_ERROR;
}
}
QString prettifyDN(const char *uid)
{
return QGpgME::DN(uid).prettyDN();
}
void SignedMessagePart::sigStatusToMetaData()
{
GpgME::Key key;
if (partMetaData()->isSigned) {
GpgME::Signature signature = mSignatures.front();
partMetaData()->status_code = signatureToStatus(signature);
partMetaData()->isGoodSignature = partMetaData()->status_code & GPGME_SIG_STAT_GOOD;
// save extended signature status flags
partMetaData()->sigSummary = signature.summary();
if (partMetaData()->isGoodSignature && !key.keyID()) {
// Search for the key by its fingerprint so that we can check for
// trust etc.
QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs
if (!job) {
qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. ";
} else {
std::vector<GpgME::Key> found_keys;
// As we are local it is ok to make this synchronous
GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(signature.fingerprint())), false, found_keys);
if (res.error()) {
qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << signature.fingerprint();
}
if (found_keys.size() > 1) {
// Should not Happen
qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint();
}
if (found_keys.size() != 1) {
// Should not Happen at this point
qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint();
} else {
key = found_keys[0];
}
delete job;
}
}
if (key.keyID()) {
partMetaData()->keyId = key.keyID();
}
if (partMetaData()->keyId.isEmpty()) {
partMetaData()->keyId = signature.fingerprint();
}
partMetaData()->keyTrust = signature.validity();
if (key.numUserIDs() > 0 && key.userID(0).id()) {
partMetaData()->signer = prettifyDN(key.userID(0).id());
}
for (uint iMail = 0; iMail < key.numUserIDs(); ++iMail) {
// The following if /should/ always result in TRUE but we
- // won't trust implicitely the plugin that gave us these data.
+ // won't trust implicitly the plugin that gave us these data.
if (key.userID(iMail).email()) {
QString email = QString::fromUtf8(key.userID(iMail).email());
// ### work around gpgme 0.3.QString text() const override;x / cryptplug bug where the
// ### email addresses are specified as angle-addr, not addr-spec:
if (email.startsWith(QLatin1Char('<')) && email.endsWith(QLatin1Char('>'))) {
email = email.mid(1, email.length() - 2);
}
if (!email.isEmpty()) {
partMetaData()->signerMailAddresses.append(email);
}
}
}
if (signature.creationTime()) {
partMetaData()->creationTime.setSecsSinceEpoch(signature.creationTime());
} else {
partMetaData()->creationTime = QDateTime();
}
if (partMetaData()->signer.isEmpty()) {
if (key.numUserIDs() > 0 && key.userID(0).name()) {
partMetaData()->signer = prettifyDN(key.userID(0).name());
}
if (!partMetaData()->signerMailAddresses.empty()) {
if (partMetaData()->signer.isEmpty()) {
partMetaData()->signer = partMetaData()->signerMailAddresses.front();
} else {
partMetaData()->signer += QLatin1String(" <") + partMetaData()->signerMailAddresses.front() + QLatin1Char('>');
}
}
}
}
}
void SignedMessagePart::startVerification(const QByteArray &text, const QTextCodec *aCodec)
{
startVerificationDetached(text, nullptr, QByteArray());
if (!content() && partMetaData()->isSigned) {
setText(aCodec->toUnicode(mVerifiedText));
}
}
void SignedMessagePart::startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature)
{
partMetaData()->isEncrypted = false;
partMetaData()->isDecryptable = false;
if (textNode) {
parseInternal(textNode, false);
}
okVerify(text, signature, textNode);
if (!partMetaData()->isSigned) {
partMetaData()->creationTime = QDateTime();
}
}
void SignedMessagePart::setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode)
{
{
const auto vm = dynamic_cast<const VerifyDetachedBodyPartMemento *>(m);
if (vm) {
mSignatures = vm->verifyResult().signatures();
}
}
{
const auto vm = dynamic_cast<const VerifyOpaqueBodyPartMemento *>(m);
if (vm) {
mVerifiedText = vm->plainText();
mSignatures = vm->verifyResult().signatures();
}
}
{
const auto vm = dynamic_cast<const DecryptVerifyBodyPartMemento *>(m);
if (vm) {
mVerifiedText = vm->plainText();
mSignatures = vm->verifyResult().signatures();
}
}
partMetaData()->auditLogError = m->auditLogError();
partMetaData()->auditLog = m->auditLogAsHtml();
partMetaData()->isSigned = !mSignatures.empty();
if (partMetaData()->isSigned) {
sigStatusToMetaData();
if (content()) {
mOtp->nodeHelper()->setSignatureState(content(), KMMsgFullySigned);
if (!textNode) {
mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
if (!mVerifiedText.isEmpty()) {
auto tempNode = new KMime::Content();
tempNode->setContent(KMime::CRLFtoLF(mVerifiedText.constData()));
tempNode->parse();
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString("signed data");
}
mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
parseInternal(tempNode, false);
}
}
}
}
}
QString SignedMessagePart::plaintextContent() const
{
if (!content()) {
return MessagePart::text();
} else {
return QString();
}
}
QString SignedMessagePart::htmlContent() const
{
if (!content()) {
return MessagePart::text();
} else {
return QString();
}
}
const QGpgME::Protocol *SignedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
QString SignedMessagePart::fromAddress() const
{
return mFromAddress;
}
//-----CryptMessageBlock---------------------
EncryptedMessagePart::EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node)
: MessagePart(otp, text)
, mPassphraseError(false)
, mNoSecKey(false)
, mCryptoProto(cryptoProto)
, mFromAddress(fromAddress)
, mDecryptMessage(false)
{
setContent(node);
partMetaData()->technicalProblem = (mCryptoProto == nullptr);
partMetaData()->isSigned = false;
partMetaData()->isGoodSignature = false;
partMetaData()->isEncrypted = false;
partMetaData()->isDecryptable = false;
partMetaData()->keyTrust = GpgME::Signature::Unknown;
partMetaData()->status = i18n("Wrong Crypto Plug-In.");
partMetaData()->status_code = GPGME_SIG_STAT_NONE;
}
EncryptedMessagePart::~EncryptedMessagePart()
{
}
void EncryptedMessagePart::setDecryptMessage(bool decrypt)
{
mDecryptMessage = decrypt;
}
bool EncryptedMessagePart::decryptMessage() const
{
return mDecryptMessage;
}
void EncryptedMessagePart::setIsEncrypted(bool encrypted)
{
partMetaData()->isEncrypted = encrypted;
}
bool EncryptedMessagePart::isEncrypted() const
{
return partMetaData()->isEncrypted;
}
bool EncryptedMessagePart::isDecryptable() const
{
return partMetaData()->isDecryptable;
}
bool EncryptedMessagePart::isNoSecKey() const
{
return mNoSecKey;
}
bool EncryptedMessagePart::passphraseError() const
{
return mPassphraseError;
}
void EncryptedMessagePart::startDecryption(const QByteArray &text, const QTextCodec *aCodec)
{
KMime::Content *content = new KMime::Content;
content->setBody(text);
content->parse();
startDecryption(content);
if (!partMetaData()->inProgress && partMetaData()->isDecryptable) {
if (hasSubParts()) {
auto _mp = (subParts()[0]).dynamicCast<SignedMessagePart>();
if (_mp) {
_mp->setText(aCodec->toUnicode(mDecryptedData));
} else {
setText(aCodec->toUnicode(mDecryptedData));
}
} else {
setText(aCodec->toUnicode(mDecryptedData));
}
}
}
bool EncryptedMessagePart::okDecryptMIME(KMime::Content &data)
{
mPassphraseError = false;
partMetaData()->inProgress = false;
partMetaData()->errorText.clear();
partMetaData()->auditLogError = GpgME::Error();
partMetaData()->auditLog.clear();
bool bDecryptionOk = false;
bool cannotDecrypt = false;
NodeHelper *nodeHelper = mOtp->nodeHelper();
Q_ASSERT(decryptMessage());
// Check whether the memento contains a result from last time:
const DecryptVerifyBodyPartMemento *m
= dynamic_cast<DecryptVerifyBodyPartMemento *>(nodeHelper->bodyPartMemento(&data, "decryptverify"));
Q_ASSERT(!m || mCryptoProto); //No CryptoPlugin and having a bodyPartMemento -> there is something completely wrong
if (!m && mCryptoProto) {
QGpgME::DecryptVerifyJob *job = mCryptoProto->decryptVerifyJob();
if (!job) {
cannotDecrypt = true;
} else {
const QByteArray ciphertext = data.decodedContent();
DecryptVerifyBodyPartMemento *newM
= new DecryptVerifyBodyPartMemento(job, ciphertext);
if (mOtp->allowAsync()) {
QObject::connect(newM, &CryptoBodyPartMemento::update,
nodeHelper, &NodeHelper::update);
if (newM->start()) {
partMetaData()->inProgress = true;
mOtp->mHasPendingAsyncJobs = true;
} else {
m = newM;
}
} else {
newM->exec();
m = newM;
}
nodeHelper->setBodyPartMemento(&data, "decryptverify", newM);
}
} else if (m && m->isRunning()) {
partMetaData()->inProgress = true;
mOtp->mHasPendingAsyncJobs = true;
m = nullptr;
}
if (m) {
const QByteArray &plainText = m->plainText();
const GpgME::DecryptionResult &decryptResult = m->decryptResult();
const GpgME::VerificationResult &verifyResult = m->verifyResult();
partMetaData()->isSigned = verifyResult.signatures().size() > 0;
- if (verifyResult.signatures().size() > 0) {
+ if (partMetaData()->isSigned) {
auto subPart = SignedMessagePart::Ptr(new SignedMessagePart(mOtp, MessagePart::text(), mCryptoProto, mFromAddress, content()));
subPart->setVerificationResult(m, nullptr);
appendSubPart(subPart);
}
mDecryptRecipients.clear();
bDecryptionOk = !decryptResult.error();
// std::stringstream ss;
// ss << decryptResult << '\n' << verifyResult;
// qCDebug(MIMETREEPARSER_LOG) << ss.str().c_str();
for (const auto &recipient : decryptResult.recipients()) {
if (!recipient.status()) {
bDecryptionOk = true;
}
GpgME::Key key;
QGpgME::KeyListJob *job = mCryptoProto->keyListJob(false, false, false); // local, no sigs
if (!job) {
qCDebug(MIMETREEPARSER_LOG) << "The Crypto backend does not support listing keys. ";
} else {
std::vector<GpgME::Key> found_keys;
// As we are local it is ok to make this synchronous
GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(recipient.keyID())), false, found_keys);
if (res.error()) {
qCDebug(MIMETREEPARSER_LOG) << "Error while searching key for Fingerprint: " << recipient.keyID();
}
if (found_keys.size() > 1) {
// Should not Happen
qCDebug(MIMETREEPARSER_LOG) << "Oops: Found more then one Key for Fingerprint: " << recipient.keyID();
}
if (found_keys.size() != 1) {
// Should not Happen at this point
qCDebug(MIMETREEPARSER_LOG) << "Oops: Found no Key for Fingerprint: " << recipient.keyID();
} else {
key = found_keys[0];
}
}
mDecryptRecipients.push_back(std::make_pair(recipient, key));
}
if (!bDecryptionOk && partMetaData()->isSigned) {
//Only a signed part
partMetaData()->isEncrypted = false;
bDecryptionOk = true;
mDecryptedData = plainText;
} else {
mPassphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
partMetaData()->isEncrypted = bDecryptionOk || decryptResult.error().code() != GPG_ERR_NO_DATA;
partMetaData()->errorText = QString::fromLocal8Bit(decryptResult.error().asString());
if (partMetaData()->isEncrypted && decryptResult.numRecipients() > 0) {
partMetaData()->keyId = decryptResult.recipient(0).keyID();
}
if (bDecryptionOk) {
mDecryptedData = plainText;
} else {
mNoSecKey = true;
foreach (const GpgME::DecryptionResult::Recipient &recipient, decryptResult.recipients()) {
mNoSecKey &= (recipient.status().code() == GPG_ERR_NO_SECKEY);
}
if (!mPassphraseError && !mNoSecKey) { // GpgME do not detect passphrase error correctly
mPassphraseError = true;
}
}
}
}
if (!bDecryptionOk) {
QString cryptPlugLibName;
if (mCryptoProto) {
cryptPlugLibName = mCryptoProto->name();
}
if (!mCryptoProto) {
partMetaData()->errorText = i18n("No appropriate crypto plug-in was found.");
} else if (cannotDecrypt) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" cannot decrypt messages.",
cryptPlugLibName);
} else if (!passphraseError()) {
partMetaData()->errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName)
+ QLatin1String("<br />")
+ i18n("Error: %1", partMetaData()->errorText);
}
}
return bDecryptionOk;
}
void EncryptedMessagePart::startDecryption(KMime::Content *data)
{
if (!content() && !data) {
return;
}
if (!data) {
data = content();
}
partMetaData()->isEncrypted = true;
bool bOkDecrypt = okDecryptMIME(*data);
if (partMetaData()->inProgress) {
return;
}
partMetaData()->isDecryptable = bOkDecrypt;
if (!partMetaData()->isDecryptable) {
setText(QString::fromUtf8(mDecryptedData.constData()));
}
if (partMetaData()->isEncrypted && !decryptMessage()) {
partMetaData()->isDecryptable = true;
}
if (content() && !partMetaData()->isSigned) {
mOtp->nodeHelper()->setPartMetaData(content(), *partMetaData());
if (decryptMessage()) {
auto tempNode = new KMime::Content();
tempNode->setContent(KMime::CRLFtoLF(mDecryptedData.constData()));
tempNode->parse();
if (!tempNode->head().isEmpty()) {
tempNode->contentDescription()->from7BitString("encrypted data");
}
mOtp->nodeHelper()->attachExtraContent(content(), tempNode);
parseInternal(tempNode, false);
}
}
}
QString EncryptedMessagePart::plaintextContent() const
{
if (!content()) {
return MessagePart::text();
} else {
return QString();
}
}
QString EncryptedMessagePart::htmlContent() const
{
if (!content()) {
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();
}
}
const QGpgME::Protocol *EncryptedMessagePart::cryptoProto() const
{
return mCryptoProto;
}
QString EncryptedMessagePart::fromAddress() const
{
return mFromAddress;
}
const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> > &EncryptedMessagePart::decryptRecipients() const
{
return mDecryptRecipients;
}
EncapsulatedRfc822MessagePart::EncapsulatedRfc822MessagePart(ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message)
: MessagePart(otp, QString())
, mMessage(message)
{
setContent(node);
partMetaData()->isEncrypted = false;
partMetaData()->isSigned = false;
partMetaData()->isEncapsulatedRfc822Message = true;
mOtp->nodeHelper()->setNodeDisplayedEmbedded(node, true);
mOtp->nodeHelper()->setPartMetaData(node, *partMetaData());
if (!mMessage) {
qCWarning(MIMETREEPARSER_LOG) << "Node is of type message/rfc822 but doesn't have a message!";
return;
}
// The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
// since the user can click the link and expect to have normal attachment operations there.
mOtp->nodeHelper()->writeNodeToTempFile(message.data());
parseInternal(message.data(), false);
}
EncapsulatedRfc822MessagePart::~EncapsulatedRfc822MessagePart()
{
}
QString EncapsulatedRfc822MessagePart::text() const
{
return renderInternalText();
}
void EncapsulatedRfc822MessagePart::fix() const
{
}
const KMime::Message::Ptr EncapsulatedRfc822MessagePart::message() const
{
return mMessage;
}
diff --git a/mimetreeparser/src/messagepart.h b/mimetreeparser/src/messagepart.h
index 2a3e9981..6fc60855 100644
--- a/mimetreeparser/src/messagepart.h
+++ b/mimetreeparser/src/messagepart.h
@@ -1,419 +1,420 @@
/*
Copyright (c) 2015 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.
*/
#ifndef MIMETREEPARSER_MESSAGEPART_H
#define MIMETREEPARSER_MESSAGEPART_H
#include "mimetreeparser_export.h"
#include "mimetreeparser/bodypartformatter.h"
#include "mimetreeparser/util.h"
#include <KMime/Message>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/importresult.h>
#include <gpgme++/key.h>
#include <QString>
#include <QSharedPointer>
#include <memory>
class QTextCodec;
namespace GpgME {
class ImportResult;
}
namespace QGpgME {
class Protocol;
}
namespace KMime {
class Content;
}
namespace MimeTreeParser {
class CryptoBodyPartMemento;
class MessagePartPrivate;
namespace Interface {
class ObjectTreeSource;
}
class MIMETREEPARSER_EXPORT MessagePart : public QObject
{
Q_OBJECT
Q_PROPERTY(QString plaintextContent READ plaintextContent)
Q_PROPERTY(QString htmlContent READ htmlContent)
Q_PROPERTY(bool isAttachment READ isAttachment)
Q_PROPERTY(bool root READ isRoot)
Q_PROPERTY(bool isHtml READ isHtml)
Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(bool neverDisplayInline READ neverDisplayInline CONSTANT)
Q_PROPERTY(QString attachmentIndex READ attachmentIndex CONSTANT)
Q_PROPERTY(QString link READ attachmentLink CONSTANT)
public:
typedef QSharedPointer<MessagePart> Ptr;
MessagePart(ObjectTreeParser *otp, const QString &text);
~MessagePart();
void setParentPart(MessagePart *parentPart);
MessagePart *parentPart() const;
virtual QString text() const;
void setText(const QString &text);
virtual QString plaintextContent() const;
virtual QString htmlContent() const;
/** The KMime::Content* node that's represented by this part.
* Can be @c nullptr, e.g. for sub-parts of an inline signed body part.
*/
KMime::Content *content() const;
void setContent(KMime::Content *node);
/** The KMime::Content* node that's the source of this part.
* This is not necessarily the same as content(), for example for
* broken-up multipart nodes.
*/
KMime::Content *attachmentContent() const;
void setAttachmentContent(KMime::Content *node);
bool isAttachment() const;
/** @see KMime::Content::index() */
QString attachmentIndex() const;
/** @see NodeHelper::asHREF */
QString attachmentLink() const;
/** Returns a string representation of an URL that can be used
* to invoke a BodyPartURLHandler for this body part.
*/
QString makeLink(const QString &path) const;
void setIsRoot(bool root);
bool isRoot() const;
virtual bool isHtml() const;
bool neverDisplayInline() const;
void setNeverDisplayInline(bool displayInline);
bool isImage() const;
void setIsImage(bool image);
PartMetaData *partMetaData() const;
Interface::BodyPartMemento *memento() const;
void setMemento(Interface::BodyPartMemento *memento);
/* only a function that should be removed if the refactoring is over */
virtual void fix() const;
void appendSubPart(const MessagePart::Ptr &messagePart);
const QVector<MessagePart::Ptr> &subParts() const;
bool hasSubParts() const;
void clearSubParts();
Interface::ObjectTreeSource *source() const;
NodeHelper *nodeHelper() const;
protected:
void parseInternal(KMime::Content *node, bool onlyOneMimePart);
QString renderInternalText() const;
ObjectTreeParser *mOtp = nullptr;
private:
std::unique_ptr<MessagePartPrivate> d;
};
class MIMETREEPARSER_EXPORT MimeMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<MimeMessagePart> Ptr;
MimeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool onlyOneMimePart);
~MimeMessagePart() override;
QString text() const override;
QString plaintextContent() const override;
QString htmlContent() const override;
private:
bool mOnlyOneMimePart;
};
class MIMETREEPARSER_EXPORT MessagePartList : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<MessagePartList> Ptr;
explicit MessagePartList(MimeTreeParser::ObjectTreeParser *otp);
~MessagePartList() override;
QString text() const override;
QString plaintextContent() const override;
QString htmlContent() const override;
};
enum IconType {
NoIcon = 0,
IconExternal,
IconInline
};
class MIMETREEPARSER_EXPORT TextMessagePart : public MessagePartList
{
Q_OBJECT
Q_PROPERTY(bool showLink READ showLink CONSTANT)
Q_PROPERTY(bool isFirstTextPart READ isFirstTextPart CONSTANT)
Q_PROPERTY(bool hasLabel READ hasLabel CONSTANT)
Q_PROPERTY(QString label READ label CONSTANT)
Q_PROPERTY(QString comment READ comment CONSTANT)
public:
typedef QSharedPointer<TextMessagePart> Ptr;
TextMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage);
~TextMessagePart() override;
KMMsgSignatureState signatureState() const;
KMMsgEncryptionState encryptionState() const;
bool decryptMessage() const;
bool showLink() const;
bool isFirstTextPart() const;
bool hasLabel() const;
/** The attachment filename, or the closest approximation thereof we have. */
QString label() const;
/** A description of this attachment, if provided. */
QString comment() const;
/** Temporary file containing the part content. */
QString temporaryFilePath() const;
private:
void parseContent();
KMMsgSignatureState mSignatureState;
KMMsgEncryptionState mEncryptionState;
bool mDecryptMessage;
};
class MIMETREEPARSER_EXPORT AttachmentMessagePart : public TextMessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<AttachmentMessagePart> Ptr;
AttachmentMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, bool decryptMessage);
~AttachmentMessagePart() override;
};
class MIMETREEPARSER_EXPORT HtmlMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<HtmlMessagePart> Ptr;
HtmlMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, MimeTreeParser::Interface::ObjectTreeSource *source);
~HtmlMessagePart() override;
QString text() const override;
+ QString plaintextContent() const override;
void fix() const override;
bool isHtml() const override;
QString bodyHtml() const;
private:
Interface::ObjectTreeSource *mSource;
QString mBodyHTML;
QByteArray mCharset;
};
class MIMETREEPARSER_EXPORT AlternativeMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<AlternativeMessagePart> Ptr;
AlternativeMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, Util::HtmlMode preferredMode);
~AlternativeMessagePart() override;
QString text() const override;
Util::HtmlMode preferredMode() const;
void setPreferredMode(Util::HtmlMode preferredMode);
bool isHtml() const override;
QString plaintextContent() const override;
QString htmlContent() const override;
QList<Util::HtmlMode> availableModes();
void fix() const override;
const QMap<Util::HtmlMode, MimeMessagePart::Ptr> &childParts() const;
private:
Util::HtmlMode mPreferredMode;
QMap<Util::HtmlMode, KMime::Content *> mChildNodes;
QMap<Util::HtmlMode, MimeMessagePart::Ptr> mChildParts;
};
class MIMETREEPARSER_EXPORT CertMessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<CertMessagePart> Ptr;
CertMessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const QGpgME::Protocol *cryptoProto, bool autoImport);
~CertMessagePart() override;
QString text() const override;
const GpgME::ImportResult &importResult() const;
private:
bool mAutoImport;
GpgME::ImportResult mImportResult;
const QGpgME::Protocol *mCryptoProto;
};
class MIMETREEPARSER_EXPORT EncapsulatedRfc822MessagePart : public MessagePart
{
Q_OBJECT
public:
typedef QSharedPointer<EncapsulatedRfc822MessagePart> Ptr;
EncapsulatedRfc822MessagePart(MimeTreeParser::ObjectTreeParser *otp, KMime::Content *node, const KMime::Message::Ptr &message);
~EncapsulatedRfc822MessagePart() override;
QString text() const override;
void fix() const override;
const KMime::Message::Ptr message() const;
private:
const KMime::Message::Ptr mMessage;
};
class MIMETREEPARSER_EXPORT EncryptedMessagePart : public MessagePart
{
Q_OBJECT
Q_PROPERTY(bool decryptMessage READ decryptMessage WRITE setDecryptMessage)
Q_PROPERTY(bool isEncrypted READ isEncrypted)
Q_PROPERTY(bool isNoSecKey READ isNoSecKey)
Q_PROPERTY(bool passphraseError READ passphraseError)
public:
typedef QSharedPointer<EncryptedMessagePart> Ptr;
EncryptedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node);
~EncryptedMessagePart() override;
QString text() const override;
void setDecryptMessage(bool decrypt);
bool decryptMessage() const;
void setIsEncrypted(bool encrypted);
bool isEncrypted() const;
bool isDecryptable() const;
bool isNoSecKey() const;
bool passphraseError() const;
void startDecryption(const QByteArray &text, const QTextCodec *aCodec);
void startDecryption(KMime::Content *data = nullptr);
QByteArray mDecryptedData;
QString plaintextContent() const override;
QString htmlContent() const override;
const QGpgME::Protocol *cryptoProto() const;
QString fromAddress() const;
const std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> > &decryptRecipients() const;
private:
/** Handles the decryption of a given content
* returns true if the decryption was successful
* if used in async mode, check if mMetaData.inPogress is true, it initiates a running decryption process.
*/
bool okDecryptMIME(KMime::Content &data);
protected:
bool mPassphraseError;
bool mNoSecKey;
const QGpgME::Protocol *mCryptoProto;
QString mFromAddress;
bool mDecryptMessage;
QByteArray mVerifiedText;
std::vector<std::pair<GpgME::DecryptionResult::Recipient, GpgME::Key> > mDecryptRecipients;
friend class EncryptedBodyPartFormatter;
};
class MIMETREEPARSER_EXPORT SignedMessagePart : public MessagePart
{
Q_OBJECT
Q_PROPERTY(bool isSigned READ isSigned)
public:
typedef QSharedPointer<SignedMessagePart> Ptr;
SignedMessagePart(ObjectTreeParser *otp, const QString &text, const QGpgME::Protocol *cryptoProto, const QString &fromAddress, KMime::Content *node);
~SignedMessagePart() override;
void setIsSigned(bool isSigned);
bool isSigned() const;
void startVerification(const QByteArray &text, const QTextCodec *aCodec);
void startVerificationDetached(const QByteArray &text, KMime::Content *textNode, const QByteArray &signature);
QByteArray mDecryptedData;
std::vector<GpgME::Signature> mSignatures;
QString plaintextContent() const override;
QString htmlContent() const override;
const QGpgME::Protocol *cryptoProto() const;
QString fromAddress() const;
private:
/** Handles the verification of data
* If signature is empty it is handled as inline signature otherwise as detached signature mode.
* Returns true if the verification was successful and the block is signed.
* If used in async mode, check if mMetaData.inProgress is true, it initiates a running verification process.
*/
bool okVerify(const QByteArray &data, const QByteArray &signature, KMime::Content *textNode);
void sigStatusToMetaData();
void setVerificationResult(const CryptoBodyPartMemento *m, KMime::Content *textNode);
protected:
const QGpgME::Protocol *mCryptoProto;
QString mFromAddress;
QByteArray mVerifiedText;
friend EncryptedMessagePart;
};
}
#endif //__MIMETREEPARSER_MESSAGEPART_H
diff --git a/mimetreeparser/src/nodehelper.cpp b/mimetreeparser/src/nodehelper.cpp
index da5a6d89..958f343e 100644
--- a/mimetreeparser/src/nodehelper.cpp
+++ b/mimetreeparser/src/nodehelper.cpp
@@ -1,1071 +1,1068 @@
/*
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#include "nodehelper.h"
#include "mimetreeparser_debug.h"
#include "partmetadata.h"
#include "interfaces/bodypart.h"
#include "temporaryfile/attachmenttemporaryfilesdirs.h"
#include <KMime/Content>
#include <KMime/Message>
#include <KMime/Headers>
#include <QTemporaryFile>
#include <KLocalizedString>
#include <kcharsets.h>
#include <QUrl>
#include <QDir>
#include <QTextCodec>
#include <string>
#include <sstream>
#include <algorithm>
#include <KCharsets>
#include <QMimeDatabase>
#include <QMimeType>
#include <QFileDevice>
namespace MimeTreeParser {
-QStringList replySubjPrefixes(QStringList() << QStringLiteral("Re\\s*:") << QStringLiteral("Re\\[\\d+\\]:") << QStringLiteral("Re\\d+:"));
-QStringList forwardSubjPrefixes(QStringList() << QStringLiteral("Fwd:") << QStringLiteral("FW:"));
-
NodeHelper::NodeHelper()
: mAttachmentFilesDir(new AttachmentTemporaryFilesDirs())
{
//TODO(Andras) add methods to modify these prefixes
mLocalCodec = QTextCodec::codecForLocale();
// In the case of Japan. Japanese locale name is "eucjp" but
// The Japanese mail systems normally used "iso-2022-jp" of locale name.
// We want to change locale name from eucjp to iso-2022-jp at KMail only.
// (Introduction to i18n, 6.6 Limit of Locale technology):
// EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP
// is the standard for Internet, and Shift-JIS is the encoding
// for Windows and Macintosh.
if (mLocalCodec) {
const QByteArray codecNameLower = mLocalCodec->name().toLower();
if (codecNameLower == "eucjp"
#if defined Q_OS_WIN || defined Q_OS_MACX
|| codecNameLower == "shift-jis" // OK?
#endif
) {
mLocalCodec = QTextCodec::codecForName("jis7");
// QTextCodec *cdc = QTextCodec::codecForName("jis7");
// QTextCodec::setCodecForLocale(cdc);
// KLocale::global()->setEncoding(cdc->mibEnum());
}
}
}
NodeHelper::~NodeHelper()
{
if (mAttachmentFilesDir) {
mAttachmentFilesDir->forceCleanTempFiles();
delete mAttachmentFilesDir;
mAttachmentFilesDir = nullptr;
}
clear();
}
void NodeHelper::setNodeProcessed(KMime::Content *node, bool recurse)
{
if (!node) {
return;
}
mProcessedNodes.append(node);
qCDebug(MIMETREEPARSER_LOG) << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
//<< " decodedContent" << node->decodedContent();
if (recurse) {
const auto contents = node->contents();
for (KMime::Content *c : contents) {
setNodeProcessed(c, true);
}
}
}
void NodeHelper::setNodeUnprocessed(KMime::Content *node, bool recurse)
{
if (!node) {
return;
}
mProcessedNodes.removeAll(node);
//avoid double addition of extra nodes, eg. encrypted attachments
const QMap<KMime::Content *, QList<KMime::Content *> >::iterator it = mExtraContents.find(node);
if (it != mExtraContents.end()) {
Q_FOREACH (KMime::Content *c, it.value()) {
KMime::Content *p = c->parent();
if (p) {
p->removeContent(c);
}
}
qDeleteAll(it.value());
qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
mExtraContents.erase(it);
}
qCDebug(MIMETREEPARSER_LOG) << "Node UNprocessed: " << node;
if (recurse) {
const auto contents = node->contents();
for (KMime::Content *c : contents) {
setNodeUnprocessed(c, true);
}
}
}
bool NodeHelper::nodeProcessed(KMime::Content *node) const
{
if (!node) {
return true;
}
return mProcessedNodes.contains(node);
}
static void clearBodyPartMemento(QMap<QByteArray, Interface::BodyPartMemento *> &bodyPartMementoMap)
{
for (QMap<QByteArray, Interface::BodyPartMemento *>::iterator
it = bodyPartMementoMap.begin(), end = bodyPartMementoMap.end();
it != end; ++it) {
Interface::BodyPartMemento *memento = it.value();
memento->detach();
delete memento;
}
bodyPartMementoMap.clear();
}
void NodeHelper::clear()
{
mProcessedNodes.clear();
mEncryptionState.clear();
mSignatureState.clear();
mOverrideCodecs.clear();
std::for_each(mBodyPartMementoMap.begin(), mBodyPartMementoMap.end(),
&clearBodyPartMemento);
mBodyPartMementoMap.clear();
QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator end(mExtraContents.constEnd());
for (QMap<KMime::Content *, QList<KMime::Content *> >::ConstIterator it = mExtraContents.constBegin(); it != end; ++it) {
Q_FOREACH (KMime::Content *c, it.value()) {
KMime::Content *p = c->parent();
if (p) {
p->removeContent(c);
}
}
qDeleteAll(it.value());
qCDebug(MIMETREEPARSER_LOG) << "mExtraContents deleted for" << it.key();
}
mExtraContents.clear();
mDisplayEmbeddedNodes.clear();
mDisplayHiddenNodes.clear();
}
void NodeHelper::setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state)
{
mEncryptionState[node] = state;
}
KMMsgEncryptionState NodeHelper::encryptionState(const KMime::Content *node) const
{
return mEncryptionState.value(node, KMMsgNotEncrypted);
}
void NodeHelper::setSignatureState(const KMime::Content *node, const KMMsgSignatureState state)
{
mSignatureState[node] = state;
}
KMMsgSignatureState NodeHelper::signatureState(const KMime::Content *node) const
{
return mSignatureState.value(node, KMMsgNotSigned);
}
PartMetaData NodeHelper::partMetaData(KMime::Content *node)
{
return mPartMetaDatas.value(node, PartMetaData());
}
void NodeHelper::setPartMetaData(KMime::Content *node, const PartMetaData &metaData)
{
mPartMetaDatas.insert(node, metaData);
}
QString NodeHelper::writeFileToTempFile(KMime::Content *node, const QString &filename)
{
QString fname = createTempDir(persistentIndex(node));
if (fname.isEmpty()) {
return QString();
}
fname += QLatin1Char('/') + filename;
QFile f(fname);
if (!f.open(QIODevice::ReadWrite)) {
qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
mAttachmentFilesDir->addTempFile(fname);
return QString();
}
f.write(QByteArray());
mAttachmentFilesDir->addTempFile(fname);
// make file read-only so that nobody gets the impression that he might
// edit attached files (cf. bug #52813)
f.setPermissions(QFileDevice::ReadUser);
f.close();
return fname;
}
QString NodeHelper::writeNodeToTempFile(KMime::Content *node)
{
// If the message part is already written to a file, no point in doing it again.
// This function is called twice actually, once from the rendering of the attachment
// in the body and once for the header.
QUrl existingFileName = tempFileUrlFromNode(node);
if (!existingFileName.isEmpty()) {
return existingFileName.toLocalFile();
}
QString fname = createTempDir(persistentIndex(node));
if (fname.isEmpty()) {
return QString();
}
QString fileName = NodeHelper::fileName(node);
// strip off a leading path
int slashPos = fileName.lastIndexOf(QLatin1Char('/'));
if (-1 != slashPos) {
fileName = fileName.mid(slashPos + 1);
}
if (fileName.isEmpty()) {
fileName = QStringLiteral("unnamed");
}
fname += QLatin1Char('/') + fileName;
qCDebug(MIMETREEPARSER_LOG) << "Create temp file: " << fname;
QByteArray data = node->decodedContent();
if (node->contentType()->isText() && !data.isEmpty()) {
// convert CRLF to LF before writing text attachments to disk
data = KMime::CRLFtoLF(data);
}
QFile f(fname);
if (!f.open(QIODevice::ReadWrite)) {
qCWarning(MIMETREEPARSER_LOG) << "Failed to write note to file:" << f.errorString();
mAttachmentFilesDir->addTempFile(fname);
return QString();
}
f.write(data);
mAttachmentFilesDir->addTempFile(fname);
// make file read-only so that nobody gets the impression that he might
// edit attached files (cf. bug #52813)
f.setPermissions(QFileDevice::ReadUser);
f.close();
return fname;
}
QUrl NodeHelper::tempFileUrlFromNode(const KMime::Content *node)
{
if (!node) {
return QUrl();
}
const QString index = persistentIndex(node);
foreach (const QString &path, mAttachmentFilesDir->temporaryFiles()) {
const int right = path.lastIndexOf(QLatin1Char('/'));
int left = path.lastIndexOf(QLatin1String(".index."), right);
if (left != -1) {
left += 7;
}
QStringRef storedIndex(&path, left, right - left);
if (left != -1 && storedIndex == index) {
return QUrl::fromLocalFile(path);
}
}
return QUrl();
}
QString NodeHelper::createTempDir(const QString &param)
{
QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/messageviewer_XXXXXX") + QLatin1String(".index.") + param);
tempFile->open();
const QString fname = tempFile->fileName();
delete tempFile;
QFile fFile(fname);
if (!(fFile.permissions() & QFileDevice::WriteUser)) {
// Not there or not writable
if (!QDir().mkpath(fname)
|| !fFile.setPermissions(QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser)) {
mAttachmentFilesDir->addTempDir(fname);
return QString(); //failed create
}
}
Q_ASSERT(!fname.isNull());
mAttachmentFilesDir->addTempDir(fname);
return fname;
}
void NodeHelper::forceCleanTempFiles()
{
mAttachmentFilesDir->forceCleanTempFiles();
delete mAttachmentFilesDir;
mAttachmentFilesDir = nullptr;
}
void NodeHelper::removeTempFiles()
{
- //Don't delete it it will delete in class
+ //Don't delete as it will be deleted in class
mAttachmentFilesDir->removeTempFiles();
mAttachmentFilesDir = new AttachmentTemporaryFilesDirs();
}
void NodeHelper::addTempFile(const QString &file)
{
mAttachmentFilesDir->addTempFile(file);
}
bool NodeHelper::isInEncapsulatedMessage(KMime::Content *node)
{
const KMime::Content *const topLevel = node->topLevel();
const KMime::Content *cur = node;
while (cur && cur != topLevel) {
const bool parentIsMessage = cur->parent() && cur->parent()->contentType(false)
&& cur->parent()->contentType()->mimeType().toLower() == "message/rfc822";
if (parentIsMessage && cur->parent() != topLevel) {
return true;
}
cur = cur->parent();
}
return false;
}
QByteArray NodeHelper::charset(KMime::Content *node)
{
if (node->contentType(false)) {
return node->contentType(false)->charset();
} else {
return node->defaultCharset();
}
}
KMMsgEncryptionState NodeHelper::overallEncryptionState(KMime::Content *node) const
{
KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
if (!node) {
return myState;
}
KMime::Content *parent = node->parent();
auto contents = parent ? parent->contents() : KMime::Content::List();
if (contents.isEmpty()) {
contents.append(node);
}
int i = contents.indexOf(const_cast<KMime::Content *>(node));
if (i < 0) {
return myState;
}
for (; i < contents.size(); ++i) {
auto next = contents.at(i);
KMMsgEncryptionState otherState = encryptionState(next);
// NOTE: children are tested ONLY when parent is not encrypted
if (otherState == KMMsgNotEncrypted && !next->contents().isEmpty()) {
otherState = overallEncryptionState(next->contents().at(0));
}
if (otherState == KMMsgNotEncrypted && !extraContents(next).isEmpty()) {
otherState = overallEncryptionState(extraContents(next).at(0));
}
if (next == node) {
myState = otherState;
}
switch (otherState) {
case KMMsgEncryptionStateUnknown:
break;
case KMMsgNotEncrypted:
if (myState == KMMsgFullyEncrypted) {
myState = KMMsgPartiallyEncrypted;
} else if (myState != KMMsgPartiallyEncrypted) {
myState = KMMsgNotEncrypted;
}
break;
case KMMsgPartiallyEncrypted:
myState = KMMsgPartiallyEncrypted;
break;
case KMMsgFullyEncrypted:
if (myState != KMMsgFullyEncrypted) {
myState = KMMsgPartiallyEncrypted;
}
break;
case KMMsgEncryptionProblematic:
break;
}
}
qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgEncryptionState:" << myState;
return myState;
}
KMMsgSignatureState NodeHelper::overallSignatureState(KMime::Content *node) const
{
KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
if (!node) {
return myState;
}
KMime::Content *parent = node->parent();
auto contents = parent ? parent->contents() : KMime::Content::List();
if (contents.isEmpty()) {
contents.append(node);
}
int i = contents.indexOf(const_cast<KMime::Content *>(node));
if (i < 0) { //Be safe
return myState;
}
for (; i < contents.size(); ++i) {
auto next = contents.at(i);
KMMsgSignatureState otherState = signatureState(next);
// NOTE: children are tested ONLY when parent is not encrypted
if (otherState == KMMsgNotSigned && !next->contents().isEmpty()) {
otherState = overallSignatureState(next->contents().at(0));
}
if (otherState == KMMsgNotSigned && !extraContents(next).isEmpty()) {
otherState = overallSignatureState(extraContents(next).at(0));
}
if (next == node) {
myState = otherState;
}
switch (otherState) {
case KMMsgSignatureStateUnknown:
break;
case KMMsgNotSigned:
if (myState == KMMsgFullySigned) {
myState = KMMsgPartiallySigned;
} else if (myState != KMMsgPartiallySigned) {
myState = KMMsgNotSigned;
}
break;
case KMMsgPartiallySigned:
myState = KMMsgPartiallySigned;
break;
case KMMsgFullySigned:
if (myState != KMMsgFullySigned) {
myState = KMMsgPartiallySigned;
}
break;
case KMMsgSignatureProblematic:
break;
}
}
qCDebug(MIMETREEPARSER_LOG) << "\n\n KMMsgSignatureState:" << myState;
return myState;
}
void NodeHelper::magicSetType(KMime::Content *node, bool aAutoDecode)
{
- const QByteArray body = (aAutoDecode) ? node->decodedContent() : node->body();
+ const QByteArray body = aAutoDecode ? node->decodedContent() : node->body();
QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(body);
QString mimetype = mime.name();
node->contentType()->setMimeType(mimetype.toLatin1());
}
bool NodeHelper::hasMailHeader(const char *header, const KMime::Message *message) const
{
return message->hasHeader(header);
}
KMime::Headers::Base * NodeHelper::mailHeaderAsBase(const char *header, const KMime::Message *message) const
{
return message->headerByType(header);
}
KMime::Headers::Generics::AddressList * NodeHelper::mailHeaderAsAddressList(const char *header, KMime::Message *message) const
{
/* works without this is maybe faster ?
if(strcmp(header, "to") == 0) {
return message->to();
} else if(strcmp(header, "replyTo") == 0) {
return message->replyTo();
} else if(strcmp(header, "bcc") == 0) {
return message->bcc();
} else if(strcmp(header, "cc") == 0) {
return message->cc();
} */
auto addressList = new KMime::Headers::Generics::AddressList();
const auto hrd = message->headerByType(header);
const QByteArray &data = hrd->as7BitString(false);
addressList->from7BitString(data);
return addressList;
}
QDateTime NodeHelper::dateHeader(KMime::Message *message) const
{
return message->date()->dateTime();
}
void NodeHelper::setOverrideCodec(KMime::Content *node, const QTextCodec *codec)
{
if (!node) {
return;
}
mOverrideCodecs[node] = codec;
}
const QTextCodec *NodeHelper::codec(KMime::Content *node)
{
if (!node) {
return mLocalCodec;
}
const QTextCodec *c = mOverrideCodecs.value(node, nullptr);
if (!c) {
// no override-codec set for this message, try the CT charset parameter:
QByteArray charset = node->contentType()->charset();
// utf-8 is a superset of us-ascii, so we don't loose anything, if we it insead
// utf-8 is nowadays that widely, that it is a good guess to use it to fix issus with broken clients.
if (charset.toLower() == "us-ascii") {
charset = "utf-8";
}
c = codecForName(charset);
}
if (!c) {
// no charset means us-ascii (RFC 2045), so using local encoding should
// be okay
c = mLocalCodec;
}
return c;
}
const QTextCodec *NodeHelper::codecForName(const QByteArray &_str)
{
if (_str.isEmpty()) {
return nullptr;
}
QByteArray codec = _str.toLower();
return KCharsets::charsets()->codecForName(QLatin1String(codec));
}
QString NodeHelper::fileName(const KMime::Content *node)
{
QString name = const_cast<KMime::Content *>(node)->contentDisposition()->filename();
if (name.isEmpty()) {
name = const_cast<KMime::Content *>(node)->contentType()->name();
}
name = name.trimmed();
return name;
}
//FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
Interface::BodyPartMemento *NodeHelper::bodyPartMemento(KMime::Content *node, const QByteArray &which) const
{
const QMap< QString, QMap<QByteArray, Interface::BodyPartMemento *> >::const_iterator nit
= mBodyPartMementoMap.find(persistentIndex(node));
if (nit == mBodyPartMementoMap.end()) {
return nullptr;
}
const QMap<QByteArray, Interface::BodyPartMemento *>::const_iterator it
= nit->find(which.toLower());
return it != nit->end() ? it.value() : nullptr;
}
//FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
void NodeHelper::setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento)
{
QMap<QByteArray, Interface::BodyPartMemento *> &mementos
= mBodyPartMementoMap[persistentIndex(node)];
const QByteArray whichLower = which.toLower();
const QMap<QByteArray, Interface::BodyPartMemento *>::iterator it
= mementos.lowerBound(whichLower);
if (it != mementos.end() && it.key() == whichLower) {
delete it.value();
if (memento) {
it.value() = memento;
} else {
mementos.erase(it);
}
} else {
mementos.insert(whichLower, memento);
}
}
bool NodeHelper::isNodeDisplayedEmbedded(KMime::Content *node) const
{
qCDebug(MIMETREEPARSER_LOG) << "IS NODE: " << mDisplayEmbeddedNodes.contains(node);
return mDisplayEmbeddedNodes.contains(node);
}
void NodeHelper::setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded)
{
qCDebug(MIMETREEPARSER_LOG) << "SET NODE: " << node << displayedEmbedded;
if (displayedEmbedded) {
mDisplayEmbeddedNodes.insert(node);
} else {
mDisplayEmbeddedNodes.remove(node);
}
}
bool NodeHelper::isNodeDisplayedHidden(KMime::Content *node) const
{
return mDisplayHiddenNodes.contains(node);
}
void NodeHelper::setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden)
{
if (displayedHidden) {
mDisplayHiddenNodes.insert(node);
} else {
mDisplayEmbeddedNodes.remove(node);
}
}
/*!
Creates a persistent index string that bridges the gap between the
permanent nodes and the temporary ones.
Used internally for robust indexing.
*/
QString NodeHelper::persistentIndex(const KMime::Content *node) const
{
if (!node) {
return QString();
}
QString indexStr = node->index().toString();
if (indexStr.isEmpty()) {
QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
while (it.hasNext()) {
it.next();
const auto &extraNodes = it.value();
for (int i = 0; i < extraNodes.size(); i++) {
if (extraNodes[i] == node) {
- indexStr = QString::fromLatin1("e%1").arg(i);
+ indexStr = QStringLiteral("e%1").arg(i);
const QString parentIndex = persistentIndex(it.key());
if (!parentIndex.isEmpty()) {
- indexStr = QString::fromLatin1("%1:%2").arg(parentIndex, indexStr);
+ indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
}
return indexStr;
}
}
}
} else {
const KMime::Content *const topLevel = node->topLevel();
//if the node is an extra node, prepend the index of the extra node to the url
QMapIterator<KMime::Message::Content *, QList<KMime::Content *> > it(mExtraContents);
while (it.hasNext()) {
it.next();
const QList<KMime::Content *> &extraNodes = extraContents(it.key());
for (int i = 0; i < extraNodes.size(); ++i) {
KMime::Content *const extraNode = extraNodes[i];
if (topLevel == extraNode) {
indexStr.prepend(QStringLiteral("e%1:").arg(i));
const QString parentIndex = persistentIndex(it.key());
if (!parentIndex.isEmpty()) {
indexStr = QStringLiteral("%1:%2").arg(parentIndex, indexStr);
}
return indexStr;
}
}
}
}
return indexStr;
}
KMime::Content *NodeHelper::contentFromIndex(KMime::Content *node, const QString &persistentIndex) const
{
KMime::Content *c = node->topLevel();
if (c) {
const QStringList pathParts = persistentIndex.split(QLatin1Char(':'), QString::SkipEmptyParts);
const int pathPartsSize(pathParts.size());
for (int i = 0; i < pathPartsSize; ++i) {
const QString &path = pathParts[i];
if (path.startsWith(QLatin1Char('e'))) {
const QList<KMime::Content *> &extraParts = mExtraContents.value(c);
const int idx = path.midRef(1, -1).toInt();
c = (idx < extraParts.size()) ? extraParts[idx] : nullptr;
} else {
c = c->content(KMime::ContentIndex(path));
}
if (!c) {
break;
}
}
}
return c;
}
QString NodeHelper::asHREF(const KMime::Content *node, const QString &place) const
{
return QStringLiteral("attachment:%1?place=%2").arg(persistentIndex(node), place);
}
KMime::Content *NodeHelper::fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &url) const
{
if (url.isEmpty()) {
return mMessage.data();
}
if (!url.isLocalFile()) {
return contentFromIndex(mMessage.data(), url.adjusted(QUrl::StripTrailingSlash).path());
} else {
const QString path = url.toLocalFile();
// extract from /<path>/qttestn28554.index.2.3:0:2/unnamed -> "2.3:0:2"
// start of the index is something that is not a number followed by a dot: \D.
// index is only made of numbers,"." and ":": ([0-9.:]+)
// index is the last part of the folder name: /
const QRegExp rIndex(QStringLiteral("\\D\\.([e0-9.:]+)/"));
//search the occurrence at most at the end
if (rIndex.lastIndexIn(path) != -1) {
return contentFromIndex(mMessage.data(), rIndex.cap(1));
}
return mMessage.data();
}
}
QString NodeHelper::fixEncoding(const QString &encoding)
{
QString returnEncoding = encoding;
// According to http://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;
}
//-----------------------------------------------------------------------------
QString NodeHelper::encodingForName(const QString &descriptiveName)
{
QString encoding = KCharsets::charsets()->encodingForName(descriptiveName);
return NodeHelper::fixEncoding(encoding);
}
QStringList NodeHelper::supportedEncodings(bool usAscii)
{
QStringList encodingNames = KCharsets::charsets()->availableEncodingNames();
QStringList encodings;
QMap<QString, bool> mimeNames;
QStringList::ConstIterator constEnd(encodingNames.constEnd());
for (QStringList::ConstIterator it = encodingNames.constBegin();
it != constEnd; ++it) {
QTextCodec *codec = KCharsets::charsets()->codecForName(*it);
QString mimeName = (codec) ? QString::fromLatin1(codec->name()).toLower() : (*it);
if (!mimeNames.contains(mimeName)) {
encodings.append(KCharsets::charsets()->descriptionForEncoding(*it));
mimeNames.insert(mimeName, true);
}
}
encodings.sort();
if (usAscii) {
encodings.prepend(KCharsets::charsets()->descriptionForEncoding(QStringLiteral("us-ascii")));
}
return encodings;
}
QString NodeHelper::fromAsString(KMime::Content *node) const
{
if (auto topLevel = dynamic_cast<KMime::Message *>(node->topLevel())) {
return topLevel->from()->asUnicodeString();
} else {
auto realNode = std::find_if(mExtraContents.cbegin(), mExtraContents.cend(),
[node](const QList<KMime::Content *> &nodes) {
return nodes.contains(node);
});
if (realNode != mExtraContents.cend()) {
return fromAsString(realNode.key());
}
}
return QString();
}
void NodeHelper::attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content)
{
qCDebug(MIMETREEPARSER_LOG) << "mExtraContents added for" << topLevelNode << " extra content: " << content;
mExtraContents[topLevelNode].append(content);
}
void NodeHelper::cleanExtraContent(KMime::Content *topLevelNode)
{
qCDebug(MIMETREEPARSER_LOG) << "remove all extraContents for" << topLevelNode;
mExtraContents[topLevelNode].clear();
}
QList< KMime::Content * > NodeHelper::extraContents(KMime::Content *topLevelnode) const
{
return mExtraContents.value(topLevelnode);
}
void NodeHelper::mergeExtraNodes(KMime::Content *node)
{
if (!node) {
return;
}
const QList<KMime::Content * > extraNodes = extraContents(node);
for (KMime::Content *extra : extraNodes) {
if (node->bodyIsMessage()) {
qCWarning(MIMETREEPARSER_LOG) << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
<<node->encodedContent() << "\n====== with =======\n" << extra << extra->encodedContent();
continue;
}
KMime::Content *c = new KMime::Content(node);
c->setContent(extra->encodedContent());
c->parse();
node->addContent(c);
}
Q_FOREACH (KMime::Content *child, node->contents()) {
mergeExtraNodes(child);
}
}
void NodeHelper::cleanFromExtraNodes(KMime::Content *node)
{
if (!node) {
return;
}
const QList<KMime::Content * > extraNodes = extraContents(node);
for (KMime::Content *extra : extraNodes) {
QByteArray s = extra->encodedContent();
const auto children = node->contents();
for (KMime::Content *c : children) {
if (c->encodedContent() == s) {
node->removeContent(c);
}
}
}
Q_FOREACH (KMime::Content *child, node->contents()) {
cleanFromExtraNodes(child);
}
}
KMime::Message *NodeHelper::messageWithExtraContent(KMime::Content *topLevelNode)
{
/*The merge is done in several steps:
1) merge the extra nodes into topLevelNode
2) copy the modified (merged) node tree into a new node tree
3) restore the original node tree in topLevelNode by removing the extra nodes from it
The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
*/
if (!topLevelNode) {
return nullptr;
}
mergeExtraNodes(topLevelNode);
KMime::Message *m = new KMime::Message;
m->setContent(topLevelNode->encodedContent());
m->parse();
cleanFromExtraNodes(topLevelNode);
// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITH EXTRA: " << m->encodedContent();
// qCDebug(MIMETREEPARSER_LOG) << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
return m;
}
KMime::Content *NodeHelper::decryptedNodeForContent(KMime::Content *content) const
{
const QList<KMime::Content *> xc = extraContents(content);
if (!xc.empty()) {
if (xc.size() == 1) {
return xc.front();
} else {
qCWarning(MIMETREEPARSER_LOG) << "WTF, encrypted node has multiple extra contents?";
}
}
return nullptr;
}
bool NodeHelper::unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel)
{
bool returnValue = false;
if (node) {
KMime::Content *curNode = node;
KMime::Content *decryptedNode = nullptr;
const QByteArray type = node->contentType(false) ? QByteArray(node->contentType()->mediaType()).toLower() : "text";
const QByteArray subType = node->contentType(false) ? node->contentType()->subType().toLower() : "plain";
const bool isMultipart = node->contentType(false) && node->contentType()->isMultipart();
bool isSignature = false;
qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
if (isMultipart) {
if (subType == "signed") {
isSignature = true;
} else if (subType == "encrypted") {
decryptedNode = decryptedNodeForContent(curNode);
}
} else if (type == "application") {
if (subType == "octet-stream") {
decryptedNode = decryptedNodeForContent(curNode);
} else if (subType == "pkcs7-signature") {
isSignature = true;
} else if (subType == "pkcs7-mime") {
// note: subtype pkcs7-mime can also be signed
// and we do NOT want to remove the signature!
if (encryptionState(curNode) != KMMsgNotEncrypted) {
decryptedNode = decryptedNodeForContent(curNode);
}
}
}
if (decryptedNode) {
qCDebug(MIMETREEPARSER_LOG) << "Current node has an associated decrypted node, adding a modified header "
"and then processing the children.";
Q_ASSERT(addHeaders);
KMime::Content headers;
headers.setHead(curNode->head());
headers.parse();
if (decryptedNode->contentType(false)) {
headers.contentType()->from7BitString(decryptedNode->contentType()->as7BitString(false));
} else {
headers.removeHeader<KMime::Headers::ContentType>();
}
if (decryptedNode->contentTransferEncoding(false)) {
headers.contentTransferEncoding()->from7BitString(decryptedNode->contentTransferEncoding()->as7BitString(false));
} else {
headers.removeHeader<KMime::Headers::ContentTransferEncoding>();
}
if (decryptedNode->contentDisposition(false)) {
headers.contentDisposition()->from7BitString(decryptedNode->contentDisposition()->as7BitString(false));
} else {
headers.removeHeader<KMime::Headers::ContentDisposition>();
}
if (decryptedNode->contentDescription(false)) {
headers.contentDescription()->from7BitString(decryptedNode->contentDescription()->as7BitString(false));
} else {
headers.removeHeader<KMime::Headers::ContentDescription>();
}
headers.assemble();
resultingData += headers.head() + '\n';
unencryptedMessage_helper(decryptedNode, resultingData, false, recursionLevel + 1);
returnValue = true;
} else if (isSignature) {
qCDebug(MIMETREEPARSER_LOG) << "Current node is a signature, adding it as-is.";
// We can't change the nodes under the signature, as that would invalidate it. Add the signature
// and its child as-is
if (addHeaders) {
resultingData += curNode->head() + '\n';
}
resultingData += curNode->encodedBody();
returnValue = false;
} else if (isMultipart) {
qCDebug(MIMETREEPARSER_LOG) << "Current node is a multipart node, adding its header and then processing all children.";
// Normal multipart node, add the header and all of its children
bool somethingChanged = false;
if (addHeaders) {
resultingData += curNode->head() + '\n';
}
const QByteArray boundary = curNode->contentType()->boundary();
foreach (KMime::Content *child, curNode->contents()) {
resultingData += "\n--" + boundary + '\n';
const bool changed = unencryptedMessage_helper(child, resultingData, true, recursionLevel + 1);
if (changed) {
somethingChanged = true;
}
}
resultingData += "\n--" + boundary + "--\n\n";
returnValue = somethingChanged;
} else if (curNode->bodyIsMessage()) {
qCDebug(MIMETREEPARSER_LOG) << "Current node is a message, adding the header and then processing the child.";
if (addHeaders) {
resultingData += curNode->head() + '\n';
}
returnValue = unencryptedMessage_helper(curNode->bodyAsMessage().data(), resultingData, true, recursionLevel + 1);
} else {
qCDebug(MIMETREEPARSER_LOG) << "Current node is an ordinary leaf node, adding it as-is.";
if (addHeaders) {
resultingData += curNode->head() + '\n';
}
resultingData += curNode->body();
returnValue = false;
}
}
qCDebug(MIMETREEPARSER_LOG) << "(" << recursionLevel << ") done.";
return returnValue;
}
KMime::Message::Ptr NodeHelper::unencryptedMessage(const KMime::Message::Ptr &originalMessage)
{
QByteArray resultingData;
const bool messageChanged = unencryptedMessage_helper(originalMessage.data(), resultingData, true);
if (messageChanged) {
#if 0
qCDebug(MIMETREEPARSER_LOG) << "Resulting data is:" << resultingData;
QFile bla("stripped.mbox");
bla.open(QIODevice::WriteOnly);
bla.write(resultingData);
bla.close();
#endif
KMime::Message::Ptr newMessage(new KMime::Message);
newMessage->setContent(resultingData);
newMessage->parse();
return newMessage;
} else {
return KMime::Message::Ptr();
}
}
QVector<KMime::Content *> NodeHelper::attachmentsOfExtraContents() const
{
QVector<KMime::Content *> result;
for (auto it = mExtraContents.begin(), end = mExtraContents.end(); it != end; ++it) {
foreach (auto content, it.value()) {
if (KMime::isAttachment(content)) {
result.push_back(content);
} else {
result += content->attachments();
}
}
}
return result;
}
}
diff --git a/mimetreeparser/src/nodehelper.h b/mimetreeparser/src/nodehelper.h
index 23a7ce65..3327e3ef 100644
--- a/mimetreeparser/src/nodehelper.h
+++ b/mimetreeparser/src/nodehelper.h
@@ -1,267 +1,267 @@
/*
Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
*/
#ifndef MIMETREEPARSER_NODEHELPER_H
#define MIMETREEPARSER_NODEHELPER_H
#include "mimetreeparser_export.h"
#include "mimetreeparser/partmetadata.h"
#include "mimetreeparser/enums.h"
#include <KMime/Message>
#include <QList>
#include <QMap>
#include <QSet>
class QUrl;
class QTextCodec;
namespace MimeTreeParser {
class AttachmentTemporaryFilesDirs;
namespace Interface {
class BodyPartMemento;
}
}
namespace MimeTreeParser {
/**
* @author Andras Mantia <andras@kdab.net>
*/
class MIMETREEPARSER_EXPORT NodeHelper : public QObject
{
Q_OBJECT
public:
NodeHelper();
~NodeHelper();
void setNodeProcessed(KMime::Content *node, bool recurse);
void setNodeUnprocessed(KMime::Content *node, bool recurse);
bool nodeProcessed(KMime::Content *node) const;
void clear();
void forceCleanTempFiles();
void setEncryptionState(const KMime::Content *node, const KMMsgEncryptionState state);
KMMsgEncryptionState encryptionState(const KMime::Content *node) const;
void setSignatureState(const KMime::Content *node, const KMMsgSignatureState state);
KMMsgSignatureState signatureState(const KMime::Content *node) const;
KMMsgSignatureState overallSignatureState(KMime::Content *node) const;
KMMsgEncryptionState overallEncryptionState(KMime::Content *node) const;
void setPartMetaData(KMime::Content *node, const PartMetaData &metaData);
PartMetaData partMetaData(KMime::Content *node);
/**
* Set the 'Content-Type' by mime-magic from the contents of the body.
* If autoDecode is true the decoded body will be used for mime type
* determination (this does not change the body itself).
*/
static void magicSetType(KMime::Content *node, bool autoDecode = true);
bool hasMailHeader(const char *header, const KMime::Message *message) const;
KMime::Headers::Base *mailHeaderAsBase(const char *header, const KMime::Message *message) const;
KMime::Headers::Generics::AddressList *mailHeaderAsAddressList(const char *header, KMime::Message *message) const;
QDateTime dateHeader(KMime::Message *message) const;
/** Attach an extra node to an existing node */
void attachExtraContent(KMime::Content *topLevelNode, KMime::Content *content);
void cleanExtraContent(KMime::Content *topLevelNode);
/** Get the extra nodes attached to the @param topLevelNode and all sub-nodes of @param topLevelNode */
QList<KMime::Content *> extraContents(KMime::Content *topLevelNode) const;
/** Return a modified message (node tree) starting from @param topLevelNode that has the original nodes and the extra nodes.
The caller has the responsibility to delete the new message.
*/
KMime::Message *messageWithExtraContent(KMime::Content *topLevelNode);
/** Get a QTextCodec suitable for this message part */
const QTextCodec *codec(KMime::Content *node);
/** Set the charset the user selected for the message to display */
void setOverrideCodec(KMime::Content *node, const QTextCodec *codec);
Interface::BodyPartMemento *bodyPartMemento(KMime::Content *node, const QByteArray &which) const;
void setBodyPartMemento(KMime::Content *node, const QByteArray &which, Interface::BodyPartMemento *memento);
// A flag to remember if the node was embedded. This is useful for attachment nodes, the reader
// needs to know if they were displayed inline or not.
bool isNodeDisplayedEmbedded(KMime::Content *node) const;
void setNodeDisplayedEmbedded(KMime::Content *node, bool displayedEmbedded);
// Same as above, but this time determines if the node was hidden or not
bool isNodeDisplayedHidden(KMime::Content *node) const;
void setNodeDisplayedHidden(KMime::Content *node, bool displayedHidden);
/**
* Writes the given message part to a temporary file and returns the
* name of this file or QString() if writing failed.
*/
QString writeNodeToTempFile(KMime::Content *node);
QString writeFileToTempFile(KMime::Content *node, const QString &filename);
/**
* Returns the temporary file path and name where this node was saved, or an empty url
* if it wasn't saved yet with writeNodeToTempFile()
*/
QUrl tempFileUrlFromNode(const KMime::Content *node);
/**
* Creates a temporary dir for saving attachments, etc.
* Will be automatically deleted when another message is viewed.
* @param param Optional part of the directory name.
*/
QString createTempDir(const QString &param = QString());
/**
* Cleanup the attachment temp files
*/
void removeTempFiles();
/**
* Add a file to the list of managed temporary files
*/
void addTempFile(const QString &file);
// Get a href in the form attachment:<nodeId>?place=<place>, used by ObjectTreeParser and
// UrlHandlerManager.
QString asHREF(const KMime::Content *node, const QString &place) const;
KMime::Content *fromHREF(const KMime::Message::Ptr &mMessage, const QUrl &href) const;
/**
* @return true if this node is a child or an encapsulated message
*/
static bool isInEncapsulatedMessage(KMime::Content *node);
/**
* Returns the charset for the given node. If no charset is specified
* for the node, the defaultCharset() is returned.
*/
static QByteArray charset(KMime::Content *node);
/**
* Return a QTextCodec for the specified charset.
* This function is a bit more tolerant, than QTextCodec::codecForName
*/
static const QTextCodec *codecForName(const QByteArray &_str);
/**
* Returns a usable filename for a node, that can be the filename from the
* content disposition header, or if that one is empty, the name from the
* content type header.
*/
static QString fileName(const KMime::Content *node);
/**
* Fixes an encoding received by a KDE function and returns the proper,
- * MIME-compilant encoding name instead.
+ * MIME-compliant encoding name instead.
* @see encodingForName
*/
static QString fixEncoding(const QString &encoding); //TODO(Andras) move to a utility class?
/**
* Drop-in replacement for KCharsets::encodingForName(). The problem with
* the KCharsets function is that it returns "human-readable" encoding names
* like "ISO 8859-15" instead of valid encoding names like "ISO-8859-15".
* This function fixes this by replacing whitespace with a hyphen.
*/
static QString encodingForName(const QString &descriptiveName); //TODO(Andras) move to a utility class?
/**
* Return a list of the supported encodings
* @param usAscii if true, US-Ascii encoding will be prepended to the list.
*/
static QStringList supportedEncodings(bool usAscii); //TODO(Andras) move to a utility class?
QString fromAsString(KMime::Content *node) const;
KMime::Content *decryptedNodeForContent(KMime::Content *content) const;
/**
* This function returns the unencrypted message that is based on @p originalMessage.
* All encrypted MIME parts are removed and replaced by their decrypted plain-text versions.
* Encrypted parts that are within signed parts are not replaced, since that would invalidate
* the signature.
*
* This only works if the message was run through ObjectTreeParser::parseObjectTree() with the
- * currrent NodeHelper before, because parseObjectTree() actually decrypts the message and stores
+ * current NodeHelper before, because parseObjectTree() actually decrypts the message and stores
* the decrypted nodes by calling attachExtraContent().
*
* @return the unencrypted message or an invalid pointer if the original message didn't contain
* a part that needed to be modified.
*/
KMime::Message::Ptr unencryptedMessage(const KMime::Message::Ptr &originalMessage);
/**
* Returns a list of attachments of attached extra content nodes.
* This is mainly useful is order to get attachments of encrypted messages.
* Note that this does not include attachments from the primary node tree.
* @see KMime::Content::attachments().
*/
QVector<KMime::Content *> attachmentsOfExtraContents() const;
Q_SIGNALS:
void update(MimeTreeParser::UpdateMode);
private:
Q_DISABLE_COPY(NodeHelper)
bool unencryptedMessage_helper(KMime::Content *node, QByteArray &resultingData, bool addHeaders, int recursionLevel = 1);
void mergeExtraNodes(KMime::Content *node);
void cleanFromExtraNodes(KMime::Content *node);
/** Creates a persistent index string that bridges the gap between the
permanent nodes and the temporary ones.
Used internally for robust indexing.
**/
QString persistentIndex(const KMime::Content *node) const;
/** Translates the persistentIndex into a node back
node: any node of the actually message to what the persistentIndex is interpreded
**/
KMime::Content *contentFromIndex(KMime::Content *node, const QString &persistentIndex) const;
private:
QList<KMime::Content *> mProcessedNodes;
QList<KMime::Content *> mNodesUnderProcess;
QMap<const KMime::Content *, KMMsgEncryptionState> mEncryptionState;
QMap<const KMime::Content *, KMMsgSignatureState> mSignatureState;
QSet<KMime::Content *> mDisplayEmbeddedNodes;
QSet<KMime::Content *> mDisplayHiddenNodes;
QTextCodec *mLocalCodec = nullptr;
QMap<KMime::Content *, const QTextCodec *> mOverrideCodecs;
QMap<QString, QMap<QByteArray, Interface::BodyPartMemento *> > mBodyPartMementoMap;
QMap<KMime::Content *, PartMetaData> mPartMetaDatas;
QMap<KMime::Message::Content *, QList<KMime::Content *> > mExtraContents;
AttachmentTemporaryFilesDirs *mAttachmentFilesDir = nullptr;
friend class NodeHelperTest;
};
}
#endif
diff --git a/mimetreeparser/src/objecttreeparser.cpp b/mimetreeparser/src/objecttreeparser.cpp
index f7ff5ffc..05ce9d9e 100644
--- a/mimetreeparser/src/objecttreeparser.cpp
+++ b/mimetreeparser/src/objecttreeparser.cpp
@@ -1,341 +1,330 @@
/*
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>
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.
+ 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.
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
#include "objecttreeparser.h"
#include "bodypartformatterfactory.h"
#include "nodehelper.h"
#include "messagepart.h"
#include "partnodebodypart.h"
#include "mimetreeparser_debug.h"
#include "bodyformatter/utils.h"
#include "interfaces/bodypartformatter.h"
#include "utils/util.h"
#include <KMime/Headers>
#include <KMime/Message>
// KDE includes
// Qt includes
#include <QByteArray>
#include <QTextCodec>
using namespace MimeTreeParser;
ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser *topLevelParser)
: mSource(topLevelParser->mSource)
, mNodeHelper(topLevelParser->mNodeHelper)
, mTopLevelContent(topLevelParser->mTopLevelContent)
, mHasPendingAsyncJobs(false)
, mAllowAsync(topLevelParser->mAllowAsync)
{
init();
}
ObjectTreeParser::ObjectTreeParser(Interface::ObjectTreeSource *source, MimeTreeParser::NodeHelper *nodeHelper)
: mSource(source)
, mNodeHelper(nodeHelper)
, mTopLevelContent(nullptr)
, mHasPendingAsyncJobs(false)
, mAllowAsync(false)
{
init();
}
void ObjectTreeParser::init()
{
Q_ASSERT(mSource);
if (!mNodeHelper) {
mNodeHelper = new NodeHelper();
mDeleteNodeHelper = true;
} else {
mDeleteNodeHelper = false;
}
}
ObjectTreeParser::ObjectTreeParser(const ObjectTreeParser &other)
: mSource(other.mSource)
, mNodeHelper(other.nodeHelper())
, mTopLevelContent(other.mTopLevelContent)
, mHasPendingAsyncJobs(other.hasPendingAsyncJobs())
, mAllowAsync(other.allowAsync())
, mDeleteNodeHelper(false)
{
}
ObjectTreeParser::~ObjectTreeParser()
{
if (mDeleteNodeHelper) {
delete mNodeHelper;
mNodeHelper = nullptr;
}
}
void ObjectTreeParser::setAllowAsync(bool allow)
{
Q_ASSERT(!mHasPendingAsyncJobs);
mAllowAsync = allow;
}
bool ObjectTreeParser::allowAsync() const
{
return mAllowAsync;
}
bool ObjectTreeParser::hasPendingAsyncJobs() const
{
return mHasPendingAsyncJobs;
}
QString ObjectTreeParser::plainTextContent() const
{
return mPlainTextContent;
}
QString ObjectTreeParser::htmlContent() const
{
return mHtmlContent;
}
-void ObjectTreeParser::copyContentFrom(const ObjectTreeParser *other)
-{
- mPlainTextContent += other->plainTextContent();
- mHtmlContent += other->htmlContent();
- if (!other->plainTextContentCharset().isEmpty()) {
- mPlainTextContentCharset = other->plainTextContentCharset();
- }
- if (!other->htmlContentCharset().isEmpty()) {
- mHtmlContentCharset = other->htmlContentCharset();
- }
-}
-
//-----------------------------------------------------------------------------
void ObjectTreeParser::parseObjectTree(KMime::Content *node, bool parseOnlySingleNode)
{
mTopLevelContent = node;
mParsedPart = parseObjectTreeInternal(node, parseOnlySingleNode);
if (mParsedPart) {
mParsedPart->fix();
if (auto mp = toplevelTextNode(mParsedPart)) {
if (auto _mp = mp.dynamicCast<TextMessagePart>()) {
extractNodeInfos(_mp->content(), true);
} else if (auto _mp = mp.dynamicCast<AlternativeMessagePart>()) {
if (_mp->childParts().contains(Util::MultipartPlain)) {
extractNodeInfos(_mp->childParts()[Util::MultipartPlain]->content(), true);
}
}
setPlainTextContent(mp->text());
}
mSource->render(mParsedPart, parseOnlySingleNode);
}
}
MessagePartPtr ObjectTreeParser::parsedPart() const
{
return mParsedPart;
}
MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, ProcessResult &processResult, const QByteArray &mimeType)
{
const auto formatters = mSource->bodyPartFormatterFactory()->formattersForType(QString::fromUtf8(mimeType));
Q_ASSERT(!formatters.empty());
for (auto formatter : formatters) {
PartNodeBodyPart part(this, &processResult, mTopLevelContent, node, mNodeHelper);
mNodeHelper->setNodeDisplayedEmbedded(node, true);
const MessagePart::Ptr result = formatter->process(part);
if (!result) {
continue;
}
result->setAttachmentContent(node);
return result;
}
Q_UNREACHABLE();
return {};
}
MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart)
{
if (!node) {
return MessagePart::Ptr();
}
// reset pending async jobs state (we'll rediscover pending jobs as we go)
mHasPendingAsyncJobs = false;
// reset "processed" flags for...
if (onlyOneMimePart) {
// ... this node and all descendants
mNodeHelper->setNodeUnprocessed(node, false);
if (!node->contents().isEmpty()) {
mNodeHelper->setNodeUnprocessed(node, true);
}
} else if (!node->parent()) {
// ...this node and all it's siblings and descendants
mNodeHelper->setNodeUnprocessed(node, true);
}
const bool isRoot = node->isTopLevel();
auto parsedPart = MessagePart::Ptr(new MessagePartList(this));
parsedPart->setIsRoot(isRoot);
KMime::Content *parent = node->parent();
auto contents = parent ? parent->contents() : KMime::Content::List();
if (contents.isEmpty()) {
contents.append(node);
}
int i = contents.indexOf(node);
if (i < 0) {
return parsedPart;
} else {
for (; i < contents.size(); ++i) {
node = contents.at(i);
if (mNodeHelper->nodeProcessed(node)) {
continue;
}
ProcessResult processResult(mNodeHelper);
QByteArray mimeType("text/plain");
if (node->contentType(false) && !node->contentType()->mimeType().isEmpty()) {
mimeType = node->contentType()->mimeType();
}
// unfortunately there's many emails where we can't trust the attachment mimetype
// so try to see if we can find something better
if (mimeType == "application/octet-stream") {
NodeHelper::magicSetType(node);
mimeType = node->contentType()->mimeType();
}
const auto mp = processType(node, processResult, mimeType);
Q_ASSERT(mp);
parsedPart->appendSubPart(mp);
mNodeHelper->setNodeProcessed(node, false);
// adjust signed/encrypted flags if inline PGP was found
processResult.adjustCryptoStatesOfNode(node);
if (onlyOneMimePart) {
break;
}
}
}
return parsedPart;
}
KMMsgSignatureState ProcessResult::inlineSignatureState() const
{
return mInlineSignatureState;
}
void ProcessResult::setInlineSignatureState(KMMsgSignatureState state)
{
mInlineSignatureState = state;
}
KMMsgEncryptionState ProcessResult::inlineEncryptionState() const
{
return mInlineEncryptionState;
}
void ProcessResult::setInlineEncryptionState(KMMsgEncryptionState state)
{
mInlineEncryptionState = state;
}
bool ProcessResult::neverDisplayInline() const
{
return mNeverDisplayInline;
}
void ProcessResult::setNeverDisplayInline(bool display)
{
mNeverDisplayInline = display;
}
void ProcessResult::adjustCryptoStatesOfNode(const KMime::Content *node) const
{
if ((inlineSignatureState() != KMMsgNotSigned)
|| (inlineEncryptionState() != KMMsgNotEncrypted)) {
mNodeHelper->setSignatureState(node, inlineSignatureState());
mNodeHelper->setEncryptionState(node, inlineEncryptionState());
}
}
void ObjectTreeParser::extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart)
{
if (isFirstTextPart) {
mPlainTextContent += curNode->decodedText();
mPlainTextContentCharset += NodeHelper::charset(curNode);
}
}
void ObjectTreeParser::setPlainTextContent(const QString &plainTextContent)
{
mPlainTextContent = plainTextContent;
}
const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const
{
Q_ASSERT(node);
if (mSource->overrideCodec()) {
return mSource->overrideCodec();
}
return mNodeHelper->codec(node);
}
QByteArray ObjectTreeParser::plainTextContentCharset() const
{
return mPlainTextContentCharset;
}
QByteArray ObjectTreeParser::htmlContentCharset() const
{
return mHtmlContentCharset;
}
MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const
{
return mNodeHelper;
}
diff --git a/mimetreeparser/src/objecttreeparser.h b/mimetreeparser/src/objecttreeparser.h
index bbc6c87e..5081a1a4 100644
--- a/mimetreeparser/src/objecttreeparser.h
+++ b/mimetreeparser/src/objecttreeparser.h
@@ -1,372 +1,370 @@
/*
objecttreeparser.h
This file is part of KMail, the KDE mail client.
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
Copyright (C) 2002-2003, 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
Copyright (c) 2009 Andras Mantia <andras@kdab.net>
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.
+ 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.
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.
*/
#ifndef MIMETREEPARSER_OBJECTTREEPARSER_H
#define MIMETREEPARSER_OBJECTTREEPARSER_H
#include "mimetreeparser_export.h"
#include "mimetreeparser/nodehelper.h"
#include "mimetreeparser/objecttreesource.h"
#include <gpgme++/verificationresult.h>
class QString;
namespace KMime {
class Content;
}
namespace MimeTreeParser {
class PartMetaData;
class ViewerPrivate;
class NodeHelper;
class MessagePart;
class MimeMessagePart;
typedef QSharedPointer<MessagePart> MessagePartPtr;
typedef QSharedPointer<MimeMessagePart> MimeMessagePartPtr;
class MIMETREEPARSER_EXPORT ProcessResult
{
public:
- explicit ProcessResult(NodeHelper *nodeHelper, KMMsgSignatureState inlineSignatureState = KMMsgNotSigned, KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted,
- bool neverDisplayInline = false)
+ explicit ProcessResult(NodeHelper *nodeHelper, KMMsgSignatureState inlineSignatureState = KMMsgNotSigned, KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted, bool neverDisplayInline = false)
: mInlineSignatureState(inlineSignatureState)
, mInlineEncryptionState(inlineEncryptionState)
, mNeverDisplayInline(neverDisplayInline)
, mNodeHelper(nodeHelper)
{
}
KMMsgSignatureState inlineSignatureState() const;
void setInlineSignatureState(KMMsgSignatureState state);
KMMsgEncryptionState inlineEncryptionState() const;
void setInlineEncryptionState(KMMsgEncryptionState state);
bool neverDisplayInline() const;
void setNeverDisplayInline(bool display);
void adjustCryptoStatesOfNode(const KMime::Content *node) const;
private:
KMMsgSignatureState mInlineSignatureState;
KMMsgEncryptionState mInlineEncryptionState;
bool mNeverDisplayInline : 1;
NodeHelper *mNodeHelper;
};
/**
\brief Parses messages and generates HTML display code out of them
\par Introduction
First, have a look at the documentation in Mainpage.dox and at the documentation of ViewerPrivate
to understand the broader picture.
Just a note on the terminology: 'Node' refers to a MIME part here, which in KMime is a
KMime::Content.
\par Basics
The ObjectTreeParser basically has two modes: Generating the HTML code for the Viewer, or only
extracting the plainTextContent() for situations where only the message text is needed, for example
when inline forwarding a message. The mode depends on the Interface::ObjectTreeSource passed to the
constructor: If Interface::ObjectTreeSource::htmlWriter() is not 0, then the HTML code generation mode is
used.
Basically, all the ObjectTreeParser does is going through the tree of MIME parts and operating on
those nodes. Operating here means creating the HTML code for the node or extracting the textual
content from it. This process is started with parseObjectTree(), where we loop over the subnodes
of the current root node. For each of those subnodes, we try to find a BodyPartFormatter that can
handle the type of the node. This can either be an internal function, such as
processMultiPartAlternativeSubtype() or processTextHtmlSubtype(), or it can be an external plugin.
More on external plugins later. When no matching formatter is found, defaultHandling() is called
for that node.
\par Multipart Nodes
Those nodes that are of type multipart have subnodes. If one of those children needs to be
processed normally, the processMultipartXXX() functions call stdChildHandling() for the node that
should be handled normally. stdChildHandling() creates its own ObjectTreeParser, which is a clone
of the current ObjectTreeParser, and processes the node. stdChildHandling() is not called for all
children of the multipart node, for example processMultiPartAlternativeSubtype() only calls it on
-one of the children, as the other one doesn't need to be displayed. Similary,
+one of the children, as the other one doesn't need to be displayed. Similarly,
processMultiPartSignedSubtype() doesn't call stdChildHandling() for the signature node, only for the
signed node.
\par Processed and Unprocessed Nodes
When a BodyPartFormatter has finished processing a node, it is processed. Nodes are set to being
not processed at the beginning of parseObjectTree(). The processed state of a node is saved in a
list in NodeHelper, see NodeHelper::setNodeProcessed(), NodeHelper::nodeProcessed() and the other
related helper functions.
It is the responsibility of the BodyPartFormatter to correctly call setNodeProcessed() and the
related functions. This is important so that processing the same node twice can be prevented. The
check that prevents duplicate processing is in parseObjectTree().
An example where duplicate processing would happen if we didn't check for it is in stdChildHandling(),
which is for example called from processMultiPartAlternativeSubtype(). Let's say the setting is to
prefer HTML over plain text. In this case, processMultiPartAlternativeSubtype() would call
stdChildHandling() on the HTML node, which would create a new ObjectTreeParser and call
parseObjectTree() on it. parseObjectTree() processes the node and all its siblings, and one of the
siblings is the plain text node, which shouldn't be processed! Therefore
processMultiPartAlternativeSubtype() sets the plain text node as been processed already.
\par Plain Text Output
Various nodes have plain text that should be displayed. This plain text is usually processed though
writeBodyString() first. That method checks if the provided text is an inline PGP text and decrypts
it if necessary. It also pushes the text through quotedHTML(), which does a number of things like
coloring quoted lines or detecting links and creating real link tags for them.
\par Modifying the Message
The ObjectTreeParser does not only parse its message, in some circumstances it also modifies it
before displaying. This is for example the case when displaying a decrypted message: The original
message only contains a binary blob of crypto data, and processMultiPartEncryptedSubtype() decrypts
that blob. After decryption, the current node is replaced with the decrypted node, which happens
in insertAndParseNewChildNode().
\par Crypto Operations
For signature and decryption handling, there are functions which help with generating the HTML code
for the signature header and footer. These are writeDeferredDecryptionBlock(), writeSigstatFooter()
and writeSigstatHeader(). As the name writeDeferredDecryptionBlock() suggests, a setting can cause
the message to not be decrypted unless the user clicks a link. Whether the message should be
decrypted or not can be controlled by Interface::ObjectTreeSource::decryptMessage(). When the user clicks the
decryption link, the URLHandler for 'kmail:' URLs sets that variable to true and triggers an update
of the Viewer, which will cause parseObjectTree() to be called again.
\par Async Crypto Operations
The above case describes decryption the message in place. However, decryption and also verifying of
-the signature can take a long time, so synchronous decryption and verifing would cause the Viewer to
+the signature can take a long time, so synchronous decryption and verifying would cause the Viewer to
block. Therefore it is possible to run these operations in async mode, see allowAsync().
In the first run of the async mode, all the ObjectTreeParser does is starting the decrypt or the
verify job, and informing the user that the operation is in progress with
writeDecryptionInProgressBlock() or with writeSigstatHeader(). Then, it creates and associates a
BodyPartMemento with the current node, for example a VerifyDetachedBodyPartMemento. Each node can
have multiple mementos associated with it, which are differeniated by name.
NodeHelper::setBodyPartMemento() and NodeHelper::bodyPartMemento() provide means to store and
retrieve these mementos. A memento is basically a thin wrapper around the crypto job, it stores the
job pointer, the job input data and the job result. Mementos can be used for any async situation,
not just for crypto jobs, but I'll describe crypto jobs here.
So in the first run of decrypting or verifying a message, the BodyPartFormatter only starts the
crypto job, creates the BodyPartMemento and writes the HTML code that tells the user that the
operation is in progress. parseObjectTree() thus finishes without waiting for anything, and the
message is displayed.
At some point, the crypto jobs then finish, which will cause slotResult() of the BodyPartMemento
to be called. slotResult() then saves the result to some member variable and calls
BodyPartMemento::notify(), which in the end will trigger an update of the Viewer. That update
will, in ViewerPrivate::parseMsg(), create a new ObjectTreeParser and call parseObjectTree() on it.
This is where the second run begins.
The functions that deal with decrypting of verifying, like processMultiPartSignedSubtype() or
processMultiPartEncryptedSubtype() will look if they find a BodyPartMemento that is associated with
the current node. Now it finds that memento, since it was created in the first run. It checks if the
memento's job has finished, and if so, the result can be written out (either the decrypted data or
the verified signature).
When dealing with encrypted nodes, new nodes are created with the decrypted data. It is important to
note that the original MIME tree is never modified, and remains the same as the original one. The method
createAndParseTempNode is called with the newly decrypted data, and it generates a new temporary node to
store the decrypted data. When these nodes are created, it is important to keep track of them as otherwise
some mementos that are added to the newly created temporary nodes will be constantly regenerated. As the
regeneration triggers a viewer update when complete, it results in an infinite refresh loop. The function
NodeHelper::linkAsPermanentDecrypted will create a link between the newly created node and the original parent.
Conversely, the function NodeHelper::attachExtraContent will create a link in the other direction, from the parent
node to the newly created temporary node.
When generating some mementos for nodes that may be temporary nodes (for example, contact photo mementos), the
function NodeHelper::setBodyPartMementoForPermanentParent is used. This will save the given body part memento for
the closest found permanent parent node, rather than the transient node itself. Then when checking for the existence
of a certain memento in a node, NodeHelper::findPermanentParentBodyPartMemento will check to see if any parent of the
given temporary node is a permanent (encrypted) node that has been used to generate the asked-for node.
To conclude: For async operations, parseObjectTree() is called twice: The first call starts the
crypto operation and creates the BodyPartMemento, the second calls sees that the BodyPartMemento is
there and can use its result for writing out the HTML.
\par PartMetaData and ProcessResult
For crypto operations, the class PartMetaData is used a lot, mainly to pass around info about the
crypto state of a node. A PartMetaData can also be associated with a node by using
NodeHelper::setPartMetaData(). The only user of that however is MessageAnalyzer::processPart() of
the Nepomuk E-Mail Feeder, which also uses the ObjectTreeParser to analyze the message.
You'll notice that a ProcessResult is passed to each formatter. The formatter is supposed to modify
the ProcessResult to tell the callers something about the state of the nodes that were processed.
One example for its use is to tell the caller about the crypto state of the node.
\par BodyPartFormatter Plugins
As mentioned way earlier, BodyPartFormatter can either be plugins or be internal. bodypartformatter.cpp
contains some trickery so that the processXXX() methods of the ObjectTreeParser are called from
a BodyPartFormatter associated with them, see the CREATE_BODY_PART_FORMATTER macro.
The BodyPartFormatter code is work in progress, it was supposed to be refactored, but that has not
yet happened at the time of writing. Therefore the code can seem a bit chaotic.
External plugins are loaded with loadPlugins() in bodypartformatterfactory.cpp. External plugins
can only use the classes in the interfaces/ directory, they include BodyPart, BodyPartMemento,
BodyPartFormatterPlugin, BodyPartFormatter, BodyPartURLHandler and URLHandler. Therefore
external plugins have powerful capabilities, which are needed for example in the iCal formatter or
in the vCard formatter.
\par Special HTML tags
As also mentioned in the documentation of ViewerPrivate, the ObjectTreeParser writes out special
links that are only understood by the viewer, for example 'kmail:' URLs or 'attachment:' URLs.
Also, some special HTML tags are created, which the Viewer later uses for post-processing. For
example a div with the id 'attachmentInjectionPoint', or a div with the id 'attachmentDiv', which
is used to mark an attachment in the body with a yellow border when the user clicks the attachment
in the header. Finally, parseObjectTree() creates an anchor with the id 'att%1', which is used in
the Viewer to scroll to the attachment.
*/
class MIMETREEPARSER_EXPORT ObjectTreeParser
{
/**
* @internal
* Copies the context of @p other, but not it's rawDecryptedBody, plainTextContent or htmlContent.
*/
ObjectTreeParser(const ObjectTreeParser &other);
public:
explicit ObjectTreeParser(Interface::ObjectTreeSource *source, NodeHelper *nodeHelper = nullptr);
explicit ObjectTreeParser(const ObjectTreeParser *topLevelParser);
virtual ~ObjectTreeParser();
void setAllowAsync(bool allow);
bool allowAsync() const;
bool hasPendingAsyncJobs() const;
/**
* The text of the message, ie. what would appear in the
* composer's text editor if this was edited or replied to.
* This is usually the content of the first text/plain MIME part.
*/
QString plainTextContent() const;
/**
* Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part.
*
- * Not to be consfused with the HTML code that the message viewer widget displays, that HTML
+ * Not to be confused with the HTML code that the message viewer widget displays, that HTML
* is written out by htmlWriter() and a totally different pair of shoes.
*/
QString htmlContent() const;
/**
* The original charset of MIME part the plain text was extracted from.
*
* If there were more than one text/plain MIME parts in the mail, the this is the charset
* of the last MIME part processed.
*/
QByteArray plainTextContentCharset() const;
QByteArray htmlContentCharset() const;
NodeHelper *nodeHelper() const;
/** Parse beginning at a given node and recursively parsing
the children of that node and it's next sibling. */
void parseObjectTree(KMime::Content *node, bool parseOnlySingleNode = false);
MessagePartPtr parsedPart() const;
private:
void extractNodeInfos(KMime::Content *curNode, bool isFirstTextPart);
void setPlainTextContent(const QString &plainTextContent);
/**
* Does the actual work for parseObjectTree. Unlike parseObjectTree(), this does not change the
* top-level content.
*/
MessagePartPtr parseObjectTreeInternal(KMime::Content *node, bool mOnlyOneMimePart);
MessagePartPtr processType(KMime::Content *node, MimeTreeParser::ProcessResult &processResult, const QByteArray &mimeType);
private:
/** ctor helper */
void init();
const QTextCodec *codecFor(KMime::Content *node) const;
- void copyContentFrom(const ObjectTreeParser *other);
-
private:
Interface::ObjectTreeSource *mSource;
NodeHelper *mNodeHelper;
QByteArray mPlainTextContentCharset;
QByteArray mHtmlContentCharset;
QString mPlainTextContent;
QString mHtmlContent;
KMime::Content *mTopLevelContent;
MessagePartPtr mParsedPart;
/// Show only one mime part means that the user has selected some node in the message structure
/// viewer that is not the root, which means the user wants to only see the selected node and its
/// children. If that is the case, this variable is set to true.
/// The code needs to behave differently if this is set. For example, it should not process the
/// siblings. Also, consider inline images: Normally, those nodes are completely hidden, as the
- /// HTML node embedds them. However, when showing only the node of the image, one has to show them,
+ /// HTML node embeds them. However, when showing only the node of the image, one has to show them,
/// as their is no HTML node in which they are displayed. There are many more cases where this
/// variable needs to be obeyed.
/// This variable is set to false again when processing the children in stdChildHandling(), as
/// the children can be completely displayed again.
bool mShowOnlyOneMimePart;
bool mHasPendingAsyncJobs;
bool mAllowAsync;
// DataUrl Icons cache
QString mCollapseIcon;
QString mExpandIcon;
bool mDeleteNodeHelper;
friend class PartNodeBodyPart;
friend class MessagePart;
friend class EncryptedMessagePart;
friend class SignedMessagePart;
friend class TextMessagePart;
friend class HtmlMessagePart;
friend class MultiPartSignedBodyPartFormatter;
friend class ApplicationPkcs7MimeBodyPartFormatter;
};
}
#endif // MIMETREEPARSER_OBJECTTREEPARSER_H
diff --git a/mimetreeparser/src/partmetadata.h b/mimetreeparser/src/partmetadata.h
index 29d72bbf..ae6565e8 100644
--- a/mimetreeparser/src/partmetadata.h
+++ b/mimetreeparser/src/partmetadata.h
@@ -1,66 +1,67 @@
/* -*- c++ -*-
partmetadata.h
KMail, the KDE mail client.
Copyright (c) 2002-2003 Karl-Heinz Zimmer <khz@kde.org>
Copyright (c) 2003 Marc Mutz <mutz@kde.org>
This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License,
- version 2.0, as published by the Free Software Foundation.
+ 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.
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, US
*/
#ifndef MIMETREEPARSER_PARTMETADATA_H
#define MIMETREEPARSER_PARTMETADATA_H
#include "mimetreeparser_export.h"
#include <gpgme++/verificationresult.h>
#include <gpgme++/context.h>
#include <QStringList>
#include <QDateTime>
namespace MimeTreeParser {
class PartMetaData
{
public:
PartMetaData()
: sigSummary(GpgME::Signature::None)
, isSigned(false)
, isGoodSignature(false)
, isEncrypted(false)
, isDecryptable(false)
, inProgress(false)
, technicalProblem(false)
, isEncapsulatedRfc822Message(false)
{
}
GpgME::Signature::Summary sigSummary;
QString signClass;
QString signer;
QStringList signerMailAddresses;
QByteArray keyId;
GpgME::Signature::Validity keyTrust;
QString status; // to be used for unknown plug-ins
int status_code; // to be used for i18n of OpenPGP and S/MIME CryptPlugs
QString errorText;
QDateTime creationTime;
QString decryptionError;
QString auditLog;
GpgME::Error auditLogError;
bool isSigned : 1;
bool isGoodSignature : 1;
bool isEncrypted : 1;
bool isDecryptable : 1;
bool inProgress : 1;
bool technicalProblem : 1;
bool isEncapsulatedRfc822Message : 1;
};
}
#endif // MIMETREEPARSER_PARTMETADATA_H
diff --git a/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.cpp b/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.cpp
index 1ae0fb2d..dae899d3 100644
--- a/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.cpp
+++ b/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.cpp
@@ -1,105 +1,105 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#include "attachmenttemporaryfilesdirs.h"
#include <QDir>
#include <QFile>
#include <QTimer>
using namespace MimeTreeParser;
class MimeTreeParser::AttachmentTemporaryFilesDirsPrivate
{
public:
AttachmentTemporaryFilesDirsPrivate()
{
}
QStringList mTempFiles;
QStringList mTempDirs;
int mDelayRemoveAll = 10000;
};
AttachmentTemporaryFilesDirs::AttachmentTemporaryFilesDirs(QObject *parent)
: QObject(parent)
, d(new AttachmentTemporaryFilesDirsPrivate)
{
}
AttachmentTemporaryFilesDirs::~AttachmentTemporaryFilesDirs()
{
delete d;
}
void AttachmentTemporaryFilesDirs::setDelayRemoveAllInMs(int ms)
{
d->mDelayRemoveAll = (ms < 0) ? 0 : ms;
}
void AttachmentTemporaryFilesDirs::removeTempFiles()
{
QTimer::singleShot(d->mDelayRemoveAll, this, &AttachmentTemporaryFilesDirs::slotRemoveTempFiles);
}
void AttachmentTemporaryFilesDirs::forceCleanTempFiles()
{
QStringList::ConstIterator end = d->mTempFiles.constEnd();
for (QStringList::ConstIterator it = d->mTempFiles.constBegin(); it != end; ++it) {
QFile::remove(*it);
}
d->mTempFiles.clear();
end = d->mTempDirs.constEnd();
for (QStringList::ConstIterator it = d->mTempDirs.constBegin(); it != end; ++it) {
QDir(*it).rmdir(*it);
}
d->mTempDirs.clear();
}
void AttachmentTemporaryFilesDirs::slotRemoveTempFiles()
{
forceCleanTempFiles();
//Delete it after cleaning
deleteLater();
}
void AttachmentTemporaryFilesDirs::addTempFile(const QString &file)
{
if (!d->mTempFiles.contains(file)) {
d->mTempFiles.append(file);
}
}
void AttachmentTemporaryFilesDirs::addTempDir(const QString &dir)
{
if (!d->mTempDirs.contains(dir)) {
d->mTempDirs.append(dir);
}
}
QStringList AttachmentTemporaryFilesDirs::temporaryFiles() const
{
return d->mTempFiles;
}
QStringList AttachmentTemporaryFilesDirs::temporaryDirs() const
{
return d->mTempDirs;
}
diff --git a/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.h b/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.h
index c4a1524d..75882809 100644
--- a/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.h
+++ b/mimetreeparser/src/temporaryfile/attachmenttemporaryfilesdirs.h
@@ -1,56 +1,56 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@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.
*/
#ifndef ATTACHMENTTEMPORARYFILESDIRS_H
#define ATTACHMENTTEMPORARYFILESDIRS_H
#include <QObject>
#include <QStringList>
#include "mimetreeparser_export.h"
namespace MimeTreeParser {
class AttachmentTemporaryFilesDirsPrivate;
class MIMETREEPARSER_EXPORT AttachmentTemporaryFilesDirs : public QObject
{
Q_OBJECT
public:
explicit AttachmentTemporaryFilesDirs(QObject *parent = nullptr);
~AttachmentTemporaryFilesDirs();
void addTempFile(const QString &file);
void addTempDir(const QString &dir);
QStringList temporaryFiles() const;
void removeTempFiles();
void forceCleanTempFiles();
QStringList temporaryDirs() const;
void setDelayRemoveAllInMs(int ms);
private Q_SLOTS:
void slotRemoveTempFiles();
private:
AttachmentTemporaryFilesDirsPrivate *const d;
};
}
#endif // ATTACHMENTTEMPORARYFILESDIRS_H
diff --git a/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.cpp b/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.cpp
index 6e7b57be..3915f68f 100644
--- a/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.cpp
+++ b/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.cpp
@@ -1,154 +1,154 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#include "attachmenttemporaryfilesdirstest.h"
#include "../attachmenttemporaryfilesdirs.h"
#include <QTemporaryDir>
-#include <qtest.h>
+#include <QTest>
#include <QDebug>
using namespace MimeTreeParser;
AttachmentTemporaryFilesDirsTest::AttachmentTemporaryFilesDirsTest(QObject *parent)
: QObject(parent)
{
}
AttachmentTemporaryFilesDirsTest::~AttachmentTemporaryFilesDirsTest()
{
}
void AttachmentTemporaryFilesDirsTest::shouldHaveDefaultValue()
{
AttachmentTemporaryFilesDirs attachmentDir;
QVERIFY(attachmentDir.temporaryFiles().isEmpty());
QVERIFY(attachmentDir.temporaryDirs().isEmpty());
}
void AttachmentTemporaryFilesDirsTest::shouldAddTemporaryFiles()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempFile(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryFiles().count(), 1);
attachmentDir.addTempFile(QStringLiteral("foo1"));
QCOMPARE(attachmentDir.temporaryFiles().count(), 2);
}
void AttachmentTemporaryFilesDirsTest::shouldAddTemporaryDirs()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempDir(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryDirs().count(), 1);
attachmentDir.addTempDir(QStringLiteral("foo1"));
QCOMPARE(attachmentDir.temporaryDirs().count(), 2);
}
void AttachmentTemporaryFilesDirsTest::shouldNotAddSameFiles()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempFile(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryFiles().count(), 1);
attachmentDir.addTempFile(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryFiles().count(), 1);
}
void AttachmentTemporaryFilesDirsTest::shouldNotAddSameDirs()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempDir(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryDirs().count(), 1);
attachmentDir.addTempDir(QStringLiteral("foo"));
QCOMPARE(attachmentDir.temporaryDirs().count(), 1);
}
void AttachmentTemporaryFilesDirsTest::shouldForceRemoveTemporaryDirs()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempDir(QStringLiteral("foo"));
attachmentDir.addTempDir(QStringLiteral("foo1"));
QCOMPARE(attachmentDir.temporaryDirs().count(), 2);
attachmentDir.forceCleanTempFiles();
QCOMPARE(attachmentDir.temporaryDirs().count(), 0);
QCOMPARE(attachmentDir.temporaryFiles().count(), 0);
}
void AttachmentTemporaryFilesDirsTest::shouldForceRemoveTemporaryFiles()
{
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempFile(QStringLiteral("foo"));
attachmentDir.addTempFile(QStringLiteral("foo2"));
QCOMPARE(attachmentDir.temporaryFiles().count(), 2);
attachmentDir.forceCleanTempFiles();
QCOMPARE(attachmentDir.temporaryFiles().count(), 0);
QCOMPARE(attachmentDir.temporaryDirs().count(), 0);
}
void AttachmentTemporaryFilesDirsTest::shouldCreateDeleteTemporaryFiles()
{
QTemporaryDir tmpDir;
QVERIFY(tmpDir.isValid());
QFile file(tmpDir.path() + QStringLiteral("/foo"));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Can open file";
return;
}
tmpDir.setAutoRemove(false);
file.close();
QVERIFY(file.exists());
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempDir(tmpDir.path());
attachmentDir.addTempFile(file.fileName());
QVERIFY(!attachmentDir.temporaryFiles().isEmpty());
QCOMPARE(attachmentDir.temporaryFiles().first(), file.fileName());
const QString path = tmpDir.path();
attachmentDir.forceCleanTempFiles();
QCOMPARE(attachmentDir.temporaryFiles().count(), 0);
QCOMPARE(attachmentDir.temporaryDirs().count(), 0);
QVERIFY(!QDir(path).exists());
}
void AttachmentTemporaryFilesDirsTest::shouldRemoveTemporaryFilesAfterTime()
{
QTemporaryDir tmpDir;
QVERIFY(tmpDir.isValid());
QFile file(tmpDir.path() + QStringLiteral("/foo"));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "Can open file";
return;
}
tmpDir.setAutoRemove(false);
file.close();
QVERIFY(file.exists());
AttachmentTemporaryFilesDirs attachmentDir;
attachmentDir.addTempDir(tmpDir.path());
attachmentDir.addTempFile(file.fileName());
QVERIFY(!attachmentDir.temporaryFiles().isEmpty());
QCOMPARE(attachmentDir.temporaryFiles().first(), file.fileName());
attachmentDir.setDelayRemoveAllInMs(500);
QTest::qSleep(1000);
attachmentDir.removeTempFiles();
const QString path = tmpDir.path();
attachmentDir.forceCleanTempFiles();
QCOMPARE(attachmentDir.temporaryFiles().count(), 0);
QCOMPARE(attachmentDir.temporaryDirs().count(), 0);
QVERIFY(!QDir(path).exists());
}
QTEST_GUILESS_MAIN(AttachmentTemporaryFilesDirsTest)
diff --git a/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.h b/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.h
index d4f257a7..37e7e523 100644
--- a/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.h
+++ b/mimetreeparser/src/temporaryfile/autotests/attachmenttemporaryfilesdirstest.h
@@ -1,44 +1,44 @@
/*
- Copyright (c) 2014-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2014-2019 Montel Laurent <montel@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.
*/
#ifndef ATTACHMENTTEMPORARYFILESDIRSTEST_H
#define ATTACHMENTTEMPORARYFILESDIRSTEST_H
#include <QObject>
class AttachmentTemporaryFilesDirsTest : public QObject
{
Q_OBJECT
public:
explicit AttachmentTemporaryFilesDirsTest(QObject *parent = nullptr);
~AttachmentTemporaryFilesDirsTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAddTemporaryFiles();
void shouldAddTemporaryDirs();
void shouldNotAddSameFiles();
void shouldNotAddSameDirs();
void shouldForceRemoveTemporaryDirs();
void shouldForceRemoveTemporaryFiles();
void shouldCreateDeleteTemporaryFiles();
void shouldRemoveTemporaryFilesAfterTime();
};
#endif // ATTACHMENTTEMPORARYFILESDIRSTEST_H
diff --git a/mimetreeparser/src/utils/util.cpp b/mimetreeparser/src/utils/util.cpp
index 290d9632..db0e3620 100644
--- a/mimetreeparser/src/utils/util.cpp
+++ b/mimetreeparser/src/utils/util.cpp
@@ -1,155 +1,154 @@
/*
Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
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.
*/
#include "util.h"
#include "mimetreeparser_debug.h"
#include "nodehelper.h"
#include <KMime/Content>
#include <QMimeDatabase>
#include <QString>
using namespace MimeTreeParser;
using namespace MimeTreeParser::Util;
bool MimeTreeParser::Util::isTypeBlacklisted(KMime::Content *node)
{
const QByteArray mediaTypeLower = node->contentType()->mediaType().toLower();
bool typeBlacklisted = mediaTypeLower == QByteArrayLiteral("multipart");
if (!typeBlacklisted) {
typeBlacklisted = KMime::isCryptoPart(node);
}
typeBlacklisted = typeBlacklisted || node == node->topLevel();
const bool firstTextChildOfEncapsulatedMsg
= mediaTypeLower == "text"
&& node->contentType()->subType().toLower() == "plain"
&& node->parent() && node->parent()->contentType()->mediaType().toLower() == "message";
return typeBlacklisted || firstTextChildOfEncapsulatedMsg;
}
QString MimeTreeParser::Util::labelForContent(KMime::Content *node)
{
const QString name = node->contentType()->name();
QString label = name.isEmpty() ? NodeHelper::fileName(node) : name;
if (label.isEmpty()) {
label = node->contentDescription()->asUnicodeString();
}
return label;
}
QMimeType MimeTreeParser::Util::mimetype(const QString &name)
{
QMimeDatabase db;
// consider the filename if mimetype cannot be found by content-type
const auto mimeTypes = db.mimeTypesForFileName(name);
for (const auto &mt : mimeTypes) {
if (mt.name() != QLatin1String("application/octet-stream")) {
return mt;
}
}
// consider the attachment's contents if neither the Content-Type header
// nor the filename give us a clue
return db.mimeTypeForFile(name);
}
QString MimeTreeParser::Util::iconNameForMimetype(const QString &mimeType, const QString &fallbackFileName1, const QString &fallbackFileName2)
{
QString fileName;
QString tMimeType = mimeType;
// convert non-registered types to registered types
if (mimeType == QLatin1String("application/x-vnd.kolab.contact")) {
tMimeType = QStringLiteral("text/x-vcard");
} else if (mimeType == QLatin1String("application/x-vnd.kolab.event")) {
tMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.event");
} else if (mimeType == QLatin1String("application/x-vnd.kolab.task")) {
tMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.todo");
} else if (mimeType == QLatin1String("application/x-vnd.kolab.journal")) {
tMimeType = QStringLiteral("application/x-vnd.akonadi.calendar.journal");
} else if (mimeType == QLatin1String("application/x-vnd.kolab.note")) {
tMimeType = QStringLiteral("application/x-vnd.akonadi.note");
} else if (mimeType == QLatin1String("image/jpg")) {
tMimeType = QStringLiteral("image/jpeg");
} else if (mimeType == QLatin1String("application/x-pkcs7-signature")) {
tMimeType = QStringLiteral("application/pkcs7-signature");
}
QMimeDatabase mimeDb;
auto mime = mimeDb.mimeTypeForName(tMimeType);
if (mime.isValid()) {
fileName = mime.iconName();
} else {
fileName = QStringLiteral("unknown");
if (!tMimeType.isEmpty()) {
qCWarning(MIMETREEPARSER_LOG) << "unknown mimetype" << tMimeType;
}
}
//WorkAround for #199083
if (fileName == QLatin1String("text-vcard")) {
fileName = QStringLiteral("text-x-vcard");
}
if (fileName.isEmpty()) {
fileName = fallbackFileName1;
if (fileName.isEmpty()) {
fileName = fallbackFileName2;
}
if (!fileName.isEmpty()) {
fileName = mimeDb.mimeTypeForFile(QLatin1String("/tmp/") + fileName).iconName();
}
}
return fileName;
}
QString MimeTreeParser::Util::iconNameForContent(KMime::Content *node)
{
if (!node) {
return QString();
}
QByteArray mimeType = node->contentType()->mimeType();
if (mimeType.isNull() || mimeType == "application/octet-stream") {
const QString mime = MimeTreeParser::Util::mimetype(node->contentDisposition()->filename()).name();
mimeType = mime.toLatin1();
}
mimeType = mimeType.toLower();
return MimeTreeParser::Util::iconNameForMimetype(QLatin1String(mimeType), node->contentDisposition()->filename(),
- node->contentType()->name());
+ node->contentType()->name());
}
QString MimeTreeParser::Util::htmlModeToString(HtmlMode mode)
{
switch (mode) {
case Normal: ///< A normal plaintext message, non-multipart
return QStringLiteral("Normal PlainText Message, non-multipart");
case Html: ///< A HTML message, non-multipart
return QStringLiteral("A HTML message, non-multipart");
case MultipartPlain: ///< A multipart/alternative message, the plain text part is currently displayed
return QStringLiteral("A multipart/alternative message, the plain text part is currently displayed");
- case MultipartHtml: ///< A multipart/altervative message, the HTML part is currently displayed
+ case MultipartHtml: ///< A multipart/alternative message, the HTML part is currently displayed
return QStringLiteral("A multipart/alternative message, the HTML part is currently displayed");
- case MultipartIcal: ///< A multipart/altervative message, the ICal part is currently displayed
+ case MultipartIcal: ///< A multipart/alternative message, the ICal part is currently displayed
return QStringLiteral("A multipart/alternative message, the ICal part is currently displayed");
-
}
return {};
}
diff --git a/mimetreeparser/src/utils/util.h b/mimetreeparser/src/utils/util.h
index 2f462cfe..d493b15f 100644
--- a/mimetreeparser/src/utils/util.h
+++ b/mimetreeparser/src/utils/util.h
@@ -1,64 +1,64 @@
/*
Copyright (c) 2016 Sandro Knauß <sknauss@kde.org>
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.
*/
#ifndef MIMETREEPARSER_UTILS_UTIL_H
#define MIMETREEPARSER_UTILS_UTIL_H
#include "mimetreeparser_export.h"
#include <QString>
class QMimeType;
namespace KMime {
class Content;
}
namespace MimeTreeParser {
/**
* The Util namespace contains a collection of helper functions use in
* various places.
*/
namespace Util {
/**
* Describes the type of the displayed message. This depends on the MIME structure
* of the mail and on whether HTML mode is enabled (which is decided by htmlMail())
*/
enum HtmlMode {
Normal, ///< A normal plaintext message, non-multipart
Html, ///< A HTML message, non-multipart
MultipartPlain, ///< A multipart/alternative message, the plain text part is currently displayed
- MultipartHtml, ///< A multipart/altervative message, the HTML part is currently displayed
- MultipartIcal ///< A multipart/altervative message, the ICal part is currently displayed
+ MultipartHtml, ///< A multipart/alternative message, the HTML part is currently displayed
+ MultipartIcal ///< A multipart/alternative message, the ICal part is currently displayed
};
MIMETREEPARSER_EXPORT QString htmlModeToString(Util::HtmlMode mode);
MIMETREEPARSER_EXPORT bool isTypeBlacklisted(KMime::Content *node);
MIMETREEPARSER_EXPORT QString labelForContent(KMime::Content *node);
MIMETREEPARSER_EXPORT QMimeType mimetype(const QString &name);
MIMETREEPARSER_EXPORT QString iconNameForMimetype(const QString &mimeType, const QString &fallbackFileName1 = QString(), const QString &fallbackFileName2 = QString());
MIMETREEPARSER_EXPORT QString iconNameForContent(KMime::Content *node);
}
}
#endif
diff --git a/templateparser/autotests/customtemplatesmenutest.cpp b/templateparser/autotests/customtemplatesmenutest.cpp
index c193c0a4..f6f48fd3 100644
--- a/templateparser/autotests/customtemplatesmenutest.cpp
+++ b/templateparser/autotests/customtemplatesmenutest.cpp
@@ -1,42 +1,42 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "customtemplatesmenutest.h"
#include "customtemplatesmenu.h"
#include <kactioncollection.h>
-#include <qtest.h>
+#include <QTest>
CustomTemplatesMenuTest::CustomTemplatesMenuTest(QObject *parent)
: QObject(parent)
{
}
CustomTemplatesMenuTest::~CustomTemplatesMenuTest()
{
}
void CustomTemplatesMenuTest::shouldHaveDefaultValue()
{
KActionCollection *collection = new KActionCollection(this);
TemplateParser::CustomTemplatesMenu templateMenu(nullptr, collection);
QVERIFY(templateMenu.replyActionMenu());
QVERIFY(templateMenu.replyAllActionMenu());
QVERIFY(templateMenu.forwardActionMenu());
}
QTEST_MAIN(CustomTemplatesMenuTest)
diff --git a/templateparser/autotests/customtemplatesmenutest.h b/templateparser/autotests/customtemplatesmenutest.h
index 525ea5ad..60a2125c 100644
--- a/templateparser/autotests/customtemplatesmenutest.h
+++ b/templateparser/autotests/customtemplatesmenutest.h
@@ -1,34 +1,34 @@
/*
- Copyright (c) 2015-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2015-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef CUSTOMTEMPLATESMENUTEST_H
#define CUSTOMTEMPLATESMENUTEST_H
#include <QObject>
class CustomTemplatesMenuTest : public QObject
{
Q_OBJECT
public:
explicit CustomTemplatesMenuTest(QObject *parent = nullptr);
~CustomTemplatesMenuTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // CUSTOMTEMPLATESMENUTEST_H
diff --git a/templateparser/autotests/data/404698-gpg-attachments.mbox b/templateparser/autotests/data/404698-gpg-attachments.mbox
new file mode 100644
index 00000000..83c2d169
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-attachments.mbox
@@ -0,0 +1,72 @@
+Subject: Testcase 'reply-mix-crlf' (PGP/MIME HTML)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+--BOUNDARY
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+
+
+--BOUNDARY
+Content-Type: text/plain; name="text1.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf7BxmM0vO8nG37hKqoqOHb35JqprJM+sqF7JFmrsuWe6V2
+PAyyE2wdtq+AhvXjVnggxYLwU+DEFpBTmWr1rsanyV8hWXRbecfN/9gN/4/N9y7Z
+XSx2OeE/uA5z8Kz5vrv/ywMqcVHjB5MQPTcLC2Zlg8MVltpriy6mdAkON4I3t7kl
+j9uwQRY7HeKvsib63HWnYAOV/fYPXXor/lioeYIll08uuCiTh3Z9fEhXQI/az5Ft
+e/xa70xGqviux+OvhoNUSZspzl7vK7e/NTBlC+LF1zVXUXT8prrd+ZFNwKvtn0Hl
+W4KfNqTM9TJB8vpE5FWnH6+B365ZvxZopZ5F/9szp9JGAUCNdX5WujBreg7nTLui
+UrnDNwOvjvsE/gsoO3n3jARK+Tu8PfUl8V1bHiCeGJz/mkA9uGJ/IApcT4rYsoHB
+nVQjW1NJ6A==
+=zrF/
+-----END PGP MESSAGE-----
+
+--BOUNDARY
+Content-Type: text/plain; name="text2.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MCqjMjAB80hAMAHfa7bdk/6L4DJQBQn+zHRv6oYzzYFC
+8l79DDIE2uorQNFj1ZBw5+pi7+/2QmAANnG2ug5W0HRphg2WPXTUswy5H+mg08PM
+MXRsP9lX5pAXEbLZVp61tvOQHnO/ltBhHHBwRaIq2tiirUUhy5erqLwlkSyN8xHM
+Bh0u/dIJw7ewMk0l3BtF/GuP7l6PtUxT7P0Vwit4h1FV1bc9mSFmBNN16dvixJ4l
+jK0mYEqT97SNZpg0MPOxx8E3xuJptzea4qmACv5zx4gYHlZRM0ZlKNqffmRauWOe
+pDCjZv2F1IUJOg28NzZhKCBVhmhBmP1VmLNYFKGAsNJHAV+3uN2YYWzbhoOJAE0N
+UxLI0EQN4y7OkAnGiRH45HygLxAjTk6dPiP5OD9OhUnSqofAjajlmqzfAAVMxY1a
+epnRKPsnCZU=
+=dqBN
+-----END PGP MESSAGE-----
+
+
+--BOUNDARY
+Content-Type: text/plain; name="text3.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MNDSEBVsF78knI+uirDbLSLrHicrXExTocmXr2DZOggI
+zMYCAHyg7ohINA40/8ZuR0bC9h6qCZjjhR+VFe2edRFshXlbuzykjpXNYcSv61Sm
+9TAVpgAExzS5VhAxYIJ6+zWJR8+hgv63oREZPWlJ23utBDAMkEeY7cga3wn1HZMZ
+g4XQZ94a8s9s/I+s3dLOdHGdxw+hmSnxjMhI6TMcZV/Kvr1MkkW10N0h0+hiuq2O
+4owEztpm4See8fCkRfhr0TO+a8ElCtIXjVwqeB0tQh0fU3QaaNiDXYawoFMQXG8N
+nwCP92glfOeAvJn9KuLwO3ee+WKwcrJhsFRMmjziDdJGAUvptVDNrk2P/0fzo/Xl
+ypmw8zhir6ch+4C2+5yFCtVSmC+3Y7+NQ4YE4AR/z5rGvA1lxclulU1DSGkhFTbJ
+XEVyg8o23A==
+=Bs3d
+-----END PGP MESSAGE-----
+
+--BOUNDARY
+
+whoo three encrypted parts inside.
+
+--BOUNDRY--
diff --git a/templateparser/autotests/data/404698-gpg-attachments.mbox.forwarded.mbox b/templateparser/autotests/data/404698-gpg-attachments.mbox.forwarded.mbox
new file mode 100644
index 00000000..1b3a24a0
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-attachments.mbox.forwarded.mbox
@@ -0,0 +1,66 @@
+Subject: Testcase 'reply-mix-crlf' (PGP/MIME HTML)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+--BOUNDARY
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+
+
+--BOUNDARY
+Content-Type: text/plain; name="text1.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf7BxmM0vO8nG37hKqoqOHb35JqprJM+sqF7JFmrsuWe6V2
+PAyyE2wdtq+AhvXjVnggxYLwU+DEFpBTmWr1rsanyV8hWXRbecfN/9gN/4/N9y7Z
+XSx2OeE/uA5z8Kz5vrv/ywMqcVHjB5MQPTcLC2Zlg8MVltpriy6mdAkON4I3t7kl
+j9uwQRY7HeKvsib63HWnYAOV/fYPXXor/lioeYIll08uuCiTh3Z9fEhXQI/az5Ft
+e/xa70xGqviux+OvhoNUSZspzl7vK7e/NTBlC+LF1zVXUXT8prrd+ZFNwKvtn0Hl
+W4KfNqTM9TJB8vpE5FWnH6+B365ZvxZopZ5F/9szp9JGAUCNdX5WujBreg7nTLui
+UrnDNwOvjvsE/gsoO3n3jARK+Tu8PfUl8V1bHiCeGJz/mkA9uGJ/IApcT4rYsoHB
+nVQjW1NJ6A==
+=zrF/
+-----END PGP MESSAGE-----
+
+--BOUNDARY
+Content-Type: text/plain; name="text2.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MCqjMjAB80hAMAHfa7bdk/6L4DJQBQn+zHRv6oYzzYFC
+8l79DDIE2uorQNFj1ZBw5+pi7+/2QmAANnG2ug5W0HRphg2WPXTUswy5H+mg08PM
+MXRsP9lX5pAXEbLZVp61tvOQHnO/ltBhHHBwRaIq2tiirUUhy5erqLwlkSyN8xHM
+Bh0u/dIJw7ewMk0l3BtF/GuP7l6PtUxT7P0Vwit4h1FV1bc9mSFmBNN16dvixJ4l
+jK0mYEqT97SNZpg0MPOxx8E3xuJptzea4qmACv5zx4gYHlZRM0ZlKNqffmRauWOe
+pDCjZv2F1IUJOg28NzZhKCBVhmhBmP1VmLNYFKGAsNJHAV+3uN2YYWzbhoOJAE0N
+UxLI0EQN4y7OkAnGiRH45HygLxAjTk6dPiP5OD9OhUnSqofAjajlmqzfAAVMxY1a
+epnRKPsnCZU=
+=dqBN
+-----END PGP MESSAGE-----
+
+
+--BOUNDARY
+Content-Type: text/plain; name="text3.txt"
+Content-Disposition: inline
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MNDSEBVsF78knI+uirDbLSLrHicrXExTocmXr2DZOggI
+zMYCAHyg7ohINA40/8ZuR0bC9h6qCZjjhR+VFe2edRFshXlbuzykjpXNYcSv61Sm
+9TAVpgAExzS5VhAxYIJ6+zWJR8+hgv63oREZPWlJ23utBDAMkEeY7cga3wn1HZMZ
+g4XQZ94a8s9s/I+s3dLOdHGdxw+hmSnxjMhI6TMcZV/Kvr1MkkW10N0h0+hiuq2O
+4owEztpm4See8fCkRfhr0TO+a8ElCtIXjVwqeB0tQh0fU3QaaNiDXYawoFMQXG8N
+nwCP92glfOeAvJn9KuLwO3ee+WKwcrJhsFRMmjziDdJGAUvptVDNrk2P/0fzo/Xl
+ypmw8zhir6ch+4C2+5yFCtVSmC+3Y7+NQ4YE4AR/z5rGvA1lxclulU1DSGkhFTbJ
+XEVyg8o23A==
+=Bs3d
+-----END PGP MESSAGE-----
diff --git a/templateparser/autotests/data/404698-gpg-attachments.mbox.html.reply b/templateparser/autotests/data/404698-gpg-attachments.mbox.html.reply
new file mode 100644
index 00000000..dadcd3ce
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-attachments.mbox.html.reply
@@ -0,0 +1 @@
+Please reply to this message<br>.<br>.<br>.<br><br>
diff --git a/templateparser/autotests/data/404698-gpg-attachments.mbox.plain.reply b/templateparser/autotests/data/404698-gpg-attachments.mbox.plain.reply
new file mode 100644
index 00000000..0b58a5bd
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-attachments.mbox.plain.reply
@@ -0,0 +1,5 @@
+Please reply to this message
+.
+.
+.
+
diff --git a/templateparser/autotests/data/404698-gpg-html.mbox b/templateparser/autotests/data/404698-gpg-html.mbox
new file mode 100644
index 00000000..c1a53c26
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-html.mbox
@@ -0,0 +1,131 @@
+Subject: Testcase 'reply-mix-crlf' (PGP/MIME HTML)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+--BOUNDARY
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+
+--BOUNDARY
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="DELIMITER"
+
+--DELIMITER
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--DELIMITER
+Content-Type: application/octet-stream; name="encrypted.asc"
+Content-Disposition: inline; filename="encrypted.asc"
+
+-----BEGIN PGP MESSAGE-----
+
+hQELAwzOQ1qnzNo7AQf3Zp/i09urv4DVCcbCu75LQqVtkanqYIaVF+CgUX2aeReD
+VIf6Q58I5lX0+ZighYMjoqFyLoIg8KfazaiCZ7prwTOjTUxgVIaEUt46E/zaIU64
+y0M09xrD0ZXvdCb2DPbzBI2YGCUOg+WRN0Bjv90DXgqXLDzdHqJ4w/iFTF3sXTW0
+nxAeOqNWwr4vSOG2AfTbIHpAspl2woqnoKds0dxA8DgGD9ffGx1/VL9aoNPuhdGf
+GWko1NfHnwgTaLEBr5Yr5uaDfPvtMfIdtw6HVc5xZfmlPfES9NeL0aHmHe9htuFf
+fyEHzFHJDxqny5/PqULxcxBpKJH2jQuGWtzswOj/0sD4ASnTPJ/AmGqI2pWT1KIS
+m1mSw7VBTK/f47EeQhdWIbIVt6+oqfjum+aAkwEEOGcUHNcNwclKYlHT1hUesNr5
+mj0XpKVUz1WBRI0O5Vedt7xNJOUIhBSOeiDrZ3dhn8kD12aJvP0NUx88jeR87U3k
+F5UvDfsWyjqN4GMzjVV3vLsGShR+8C7I2+DPebyoLUfkqeEUUJOwYg4YL+gjAVHR
+VKNvQIQWC5PFMm5LasDF0UdfTtKI+kWYgGTWVKQawr3nBJmly2b2By9biUh2Z4Ly
+pUHqKpHEKW5O+VLd0dgzYV9x5Wq7zkKKX44B55W050bh3u9iztfZvghd16Cy7OYB
+9h20ZMKFffkZCorrhiN6mHr1YkPXI30ZwC1xkQ6YuUlwIpE5gJr4D14+/zFcJsU0
+grdLbDzFApcwrGrxjiFGMb8tbS6XYyB0uVh0NEX7YGUHYtZnE6JLR0XVYjvSUFGs
+plvgdpXbohA3J9T8Grd1M5O7D+SmrXHrZGj8oJW2DST6QmbphlkVBf0LrNK+sqSU
+nKxX/pzNqA4Sh/lLJOQ7eON4or2u9JpiP5Zr++Wl99xWBel5iZC9s/8=
+=mtFc
+-----END PGP MESSAGE-----
+
+
+--DELIMITER--
+
+--BOUNDARY
+
+--BOUNDARY
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="DELIMITER"
+
+--DELIMITER
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--DELIMITER
+Content-Type: application/octet-stream; name="encrypted.asc"
+Content-Disposition: inline; filename="encrypted.asc"
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf9EgAO1PAwK3tquVubNpsRpK6kfvjAVSDXNiUtuMN5G7KG
+aqnVl5PmfdLdEbGLjcHyFMsZ8k2alSyG0lFWdXQUqfYE5TSo3en5Zqyc+kK/5pDI
+GZ6V5E5I0JPVIeY5lSFIVvSBXCBncK7jdRAtN3WfDL50UQYJX8fAOGc6iiMKDEb8
+jKwJbS0qwjHDwaKKwseE/yuYO3YFUWsijsnxwUXhU0iq3Yv7JIhXKldHFPe+jCOy
+v2OQ2064I9mCNi6LjQPcqyXVTESB5/6Sdl1L9/ETjep5Gzlhp8WJTZNIzWJhqmPe
+6hebCCJowQ6CxiqVInh46fPfSGw/YuFGRlyWZCSVa9LA+wGb+KaR0oDwHjBsUkhh
+VhdNS7kRtx3kec9KaJQHCDFt0aUaMgnFdnNDS3AEQ+5PbplyKYT5PjA1TkhHGXmb
+VQ07Xyab3zGPE5DUsUYt/c+bfN3EmaOc8JAZBOJzYw0nL9fSt5E1feo3Ntwse5S1
+yMMK7ALfv6W4/ItWf1KTDCo/A2QiAzZO9Gy8kFOx3nXYeh5BN2Efe5tmdtxgypQP
++b3TJbl98elyS6jiV4xB+gooVTrK3DIJbZi8xcHnBqtg3bSVML5pDJBM1MCcS3cP
+eVgJ5JkE1qqiHuk4NFm0KIKMWyjgXUp8XJ7pOpHtu5COEyz3TwpOjmcYUMjQnyQs
+/c9DD/oTZZq4y7VTFab9W0iBS0aadHzSR3TQdneIap7MOay79+fZQEqK8zel4qkN
+ngaxDyBYA9wy3gsigbb5lBEqX9M8o7VLpz6Hn3CXqxpgaPvE+GFN+r5Os1/bcuSK
+EfJTTtPuqx3MjVH9y+wW3fThb5xSoDNgb3Z93HVMGZzcKh9Kt47ZBhxMfb/4N7lE
+Hh3qvh6KIJNnVlhm+JqqlVbAUEAg6bFH+CI1RtsCPnrmHjT6ysqeNKciAvVB
+=VRca
+-----END PGP MESSAGE-----
+
+--DELIMITER--
diff --git a/templateparser/autotests/data/404698-gpg-html.mbox.html.reply b/templateparser/autotests/data/404698-gpg-html.mbox.html.reply
new file mode 100644
index 00000000..8fd2007f
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-html.mbox.html.reply
@@ -0,0 +1 @@
+Please reply to this message<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>
diff --git a/templateparser/autotests/data/404698-gpg-html.mbox.plain.reply b/templateparser/autotests/data/404698-gpg-html.mbox.plain.reply
new file mode 100644
index 00000000..580c8fde
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-html.mbox.plain.reply
@@ -0,0 +1,51 @@
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
diff --git a/templateparser/autotests/data/404698-gpg-inline.mbox b/templateparser/autotests/data/404698-gpg-inline.mbox
new file mode 100644
index 00000000..e42eccd7
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-inline.mbox
@@ -0,0 +1,51 @@
+Subject: Testcase 'reply-decrytion-oracle' (PGP INLINE)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf7BxmM0vO8nG37hKqoqOHb35JqprJM+sqF7JFmrsuWe6V2
+PAyyE2wdtq+AhvXjVnggxYLwU+DEFpBTmWr1rsanyV8hWXRbecfN/9gN/4/N9y7Z
+XSx2OeE/uA5z8Kz5vrv/ywMqcVHjB5MQPTcLC2Zlg8MVltpriy6mdAkON4I3t7kl
+j9uwQRY7HeKvsib63HWnYAOV/fYPXXor/lioeYIll08uuCiTh3Z9fEhXQI/az5Ft
+e/xa70xGqviux+OvhoNUSZspzl7vK7e/NTBlC+LF1zVXUXT8prrd+ZFNwKvtn0Hl
+W4KfNqTM9TJB8vpE5FWnH6+B365ZvxZopZ5F/9szp9JGAUCNdX5WujBreg7nTLui
+UrnDNwOvjvsE/gsoO3n3jARK+Tu8PfUl8V1bHiCeGJz/mkA9uGJ/IApcT4rYsoHB
+nVQjW1NJ6A==
+=zrF/
+-----END PGP MESSAGE-----
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MCqjMjAB80hAMAHfa7bdk/6L4DJQBQn+zHRv6oYzzYFC
+8l79DDIE2uorQNFj1ZBw5+pi7+/2QmAANnG2ug5W0HRphg2WPXTUswy5H+mg08PM
+MXRsP9lX5pAXEbLZVp61tvOQHnO/ltBhHHBwRaIq2tiirUUhy5erqLwlkSyN8xHM
+Bh0u/dIJw7ewMk0l3BtF/GuP7l6PtUxT7P0Vwit4h1FV1bc9mSFmBNN16dvixJ4l
+jK0mYEqT97SNZpg0MPOxx8E3xuJptzea4qmACv5zx4gYHlZRM0ZlKNqffmRauWOe
+pDCjZv2F1IUJOg28NzZhKCBVhmhBmP1VmLNYFKGAsNJHAV+3uN2YYWzbhoOJAE0N
+UxLI0EQN4y7OkAnGiRH45HygLxAjTk6dPiP5OD9OhUnSqofAjajlmqzfAAVMxY1a
+epnRKPsnCZU=
+=dqBN
+-----END PGP MESSAGE-----
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf+MNDSEBVsF78knI+uirDbLSLrHicrXExTocmXr2DZOggI
+zMYCAHyg7ohINA40/8ZuR0bC9h6qCZjjhR+VFe2edRFshXlbuzykjpXNYcSv61Sm
+9TAVpgAExzS5VhAxYIJ6+zWJR8+hgv63oREZPWlJ23utBDAMkEeY7cga3wn1HZMZ
+g4XQZ94a8s9s/I+s3dLOdHGdxw+hmSnxjMhI6TMcZV/Kvr1MkkW10N0h0+hiuq2O
+4owEztpm4See8fCkRfhr0TO+a8ElCtIXjVwqeB0tQh0fU3QaaNiDXYawoFMQXG8N
+nwCP92glfOeAvJn9KuLwO3ee+WKwcrJhsFRMmjziDdJGAUvptVDNrk2P/0fzo/Xl
+ypmw8zhir6ch+4C2+5yFCtVSmC+3Y7+NQ4YE4AR/z5rGvA1lxclulU1DSGkhFTbJ
+XEVyg8o23A==
+=Bs3d
+-----END PGP MESSAGE-----
+
+whoo three encrypted parts inside.
diff --git a/templateparser/autotests/data/404698-gpg-inline.mbox.html.reply b/templateparser/autotests/data/404698-gpg-inline.mbox.html.reply
new file mode 100644
index 00000000..dadcd3ce
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-inline.mbox.html.reply
@@ -0,0 +1 @@
+Please reply to this message<br>.<br>.<br>.<br><br>
diff --git a/templateparser/autotests/data/404698-gpg-inline.mbox.plain.reply b/templateparser/autotests/data/404698-gpg-inline.mbox.plain.reply
new file mode 100644
index 00000000..0b58a5bd
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg-inline.mbox.plain.reply
@@ -0,0 +1,5 @@
+Please reply to this message
+.
+.
+.
+
diff --git a/templateparser/autotests/data/404698-gpg.mbox b/templateparser/autotests/data/404698-gpg.mbox
new file mode 100644
index 00000000..ed9c0f98
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg.mbox
@@ -0,0 +1,120 @@
+Subject: Testcase 'reply-mix-crlf' (PGP/MIME)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+--BOUNDARY
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+
+--BOUNDARY
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="DELIMITER"
+
+--DELIMITER
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--DELIMITER
+Content-Type: application/octet-stream; name="encrypted.asc"
+Content-Disposition: inline; filename="encrypted.asc"
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1
+
+hIwDGJlthTT7oq0BA/9NtLLXbiIJVS6pOynwEeSznrQK7kYVla8RM43//JECCkGJ
+azEaSBznabBv6epaFmQtVHLMXlCbZnMmW9loyqPBfMoAms6kKKBdG/jqhus89iXE
++seXngC233Va/gZMb2DxOqIokVNfj9tpR7xQ8wS/jHTDiLNc1GOQC7ku42z2bNLA
+IQFRD/qbBFz89hU4wP4cYoAysOnEDojFrsrnCidTHJOJrndM6PPUtH/jQCyfr/EG
+2tSpJwYKvmT6ly3yqaGLBtRPIxiv+dMe+7yw0t40qbjvvaTGavErEBJEKX5eWbTN
+/sjajHpUHqs6SIiMheH9dr+WfzFONtVbPEgGRmOERhlgTl/nLo86AZpjJroIGKJJ
+tTHCcoQGAWG+N7wrCE1RxR0kkMs4nRozj0TLu6ZyXMs+H063MewTPNxNAiQT1Nbi
+udKWmfLBlxn06p+JDzUKxj8PFwObdbxTvACzbAvBY1aHMQ==
+=mLl3
+-----END PGP MESSAGE-----
+
+
+--DELIMITER--
+
+--BOUNDARY
+
+--BOUNDARY
+Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="DELIMITER"
+
+--DELIMITER
+Content-Type: application/pgp-encrypted
+
+Version: 1
+
+--DELIMITER
+Content-Type: application/octet-stream; name="encrypted.asc"
+Content-Disposition: inline; filename="encrypted.asc"
+
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v2.0.15 (GNU/Linux)
+
+hQEMAwzOQ1qnzNo7AQgAtWfDWWI2JUGuptpackiIxpWViEEpGAeruETubiIPwxNb
+DNmXrMDhbm/zIbPntIGWJDgUMfABZCUgmlJLWhsceDTt+tXnWGha2VYrN2/WsF6/
+Pqs/TavTvMIJQHDaIH5yDDCaMoq/mGSbcu7go2H8Sw7aBEYlM8jGlqc1HziXnZ1q
+3vDiA+4qWfvbNoSRo1kb9Pcq997yg6WqZXH2hJ7cp+hIQ4uTP1/+qgYHMvfPlzQk
+XcDguGbIer88ELhuR5622unGBAB4dqp+5w6n9c6rrCH81qhV4W0nqSEvj1tBj78S
+ZTi6VBAo5eS0e3iOJqMpwUZz6hQUpJw2wnNRGvLgI9KZAag0HkgPdMeANowg7vpE
+L4nU7B0ybhswA2Y7QT/wwCDZu9N1JGeBmy0dgy4sA38Ki27rn2/lIaP0j14JycwM
+RTJ1uwI+ZuQiwXlyYtdFZJWe8nraWARch0oKqhaR7aSsxGWo63eiGEQhkQCBFBb3
+Vg0nNCZRBauEqIESEW5EV2zrJqdfNYcz+f9IP125dnQEKgLZ6FxTt3+v
+=mhNl
+-----END PGP MESSAGE-----
+
+--DELIMITER--
diff --git a/templateparser/autotests/data/404698-gpg.mbox.html.reply b/templateparser/autotests/data/404698-gpg.mbox.html.reply
new file mode 100644
index 00000000..8fd2007f
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg.mbox.html.reply
@@ -0,0 +1 @@
+Please reply to this message<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>
diff --git a/templateparser/autotests/data/404698-gpg.mbox.plain.reply b/templateparser/autotests/data/404698-gpg.mbox.plain.reply
new file mode 100644
index 00000000..580c8fde
--- /dev/null
+++ b/templateparser/autotests/data/404698-gpg.mbox.plain.reply
@@ -0,0 +1,51 @@
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
diff --git a/templateparser/autotests/data/404698-smime.mbox b/templateparser/autotests/data/404698-smime.mbox
new file mode 100644
index 00000000..7cd5a106
--- /dev/null
+++ b/templateparser/autotests/data/404698-smime.mbox
@@ -0,0 +1,88 @@
+Subject: Testcase 'reply-mix-crlf' (S/MIME)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="BOUNDARY"
+
+--BOUNDARY
+Content-Type: text/plain
+
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+
+--BOUNDARY
+Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxgfwwgfkCAQAwYjBVMQswCQYDVQQGEwJVUzENMAsGA1UECgwE
+S0RBQjEWMBQGA1UEAwwNdW5pdHRlc3QgY2VydDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
+LmNvbQIJANNFIDoYY4XJMA0GCSqGSIb3DQEBAQUABIGAJwmmaOeidXUHSQGOf2OBIsPYafVqdORe
+y54pEXbXiAfSVUWgI4a9CsiWwcDX8vlaX9ZLLr+L2VmOfr6Yc5214yxzausZVvnUFjy6LUXotuEX
+tSar4EW7XI9DjaZc1l985naMsTx9JUa5GyQ9J6PGqhosAKpKMGgKkFAHaOwE1/IwgAYJKoZIhvcN
+AQcBMBQGCCqGSIb3DQMHBAieDfmz3WGbN6CABHgEpsLrNn0PAZTDUfNomDypvSCl5bQH+9cKm80m
+upMV2r8RBiXS7OaP4SpCxq18afDTTPatvboHIoEX92taTbq8soiAgEs6raSGtEYZNvFL0IYqm7MA
+o5HCOmjiEcInyPf14lL3HnPk10FaP3hh58qTHUh4LPYtL7UECOZELYnUfUVhAAAAAAAAAAAAAA==
+
+--BOUNDARY
+Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
+Content-Transfer-Encoding: base64
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxgfwwgfkCAQAwYjBVMQswCQYDVQQGEwJVUzENMAsGA1UECgwE
+S0RBQjEWMBQGA1UEAwwNdW5pdHRlc3QgY2VydDEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
+LmNvbQIJANNFIDoYY4XJMA0GCSqGSIb3DQEBAQUABIGAJwmmaOeidXUHSQGOf2OBIsPYafVqdORe
+y54pEXbXiAfSVUWgI4a9CsiWwcDX8vlaX9ZLLr+L2VmOfr6Yc5214yxzausZVvnUFjy6LUXotuEX
+tSar4EW7XI9DjaZc1l985naMsTx9JUa5GyQ9J6PGqhosAKpKMGgKkFAHaOwE1/IwgAYJKoZIhvcN
+AQcBMBQGCCqGSIb3DQMHBAieDfmz3WGbN6CABHgEpsLrNn0PAZTDUfNomDypvSCl5bQH+9cKm80m
+upMV2r8RBiXS7OaP4SpCxq18afDTTPatvboHIoEX92taTbq8soiAgEs6raSGtEYZNvFL0IYqm7MA
+o5HCOmjiEcInyPf14lL3HnPk10FaP3hh58qTHUh4LPYtL7UECOZELYnUfUVhAAAAAAAAAAAAAA==
+
+--BOUNDARY--
diff --git a/templateparser/autotests/data/404698-smime.mbox.html.reply b/templateparser/autotests/data/404698-smime.mbox.html.reply
new file mode 100644
index 00000000..8fd2007f
--- /dev/null
+++ b/templateparser/autotests/data/404698-smime.mbox.html.reply
@@ -0,0 +1 @@
+Please reply to this message<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>.<br>
diff --git a/templateparser/autotests/data/404698-smime.mbox.plain.reply b/templateparser/autotests/data/404698-smime.mbox.plain.reply
new file mode 100644
index 00000000..580c8fde
--- /dev/null
+++ b/templateparser/autotests/data/404698-smime.mbox.plain.reply
@@ -0,0 +1,51 @@
+Please reply to this message
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
+.
diff --git a/templateparser/autotests/data/html-attachment1.mbox.forwarded.mbox b/templateparser/autotests/data/html-attachment1.mbox.forwarded.mbox
new file mode 100644
index 00000000..b4df51a9
--- /dev/null
+++ b/templateparser/autotests/data/html-attachment1.mbox.forwarded.mbox
@@ -0,0 +1,83 @@
+Return-path: <sender@example.com>
+Envelope-to: gunter@ohrner.net
+Delivery-date: Mon, 12 Sep 2016 13:38:39 +0200
+Received: from mail.from example.com ([178.251.88.150])
+ by luggage.ohrner.net with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:256)
+ (Exim 4.80)
+ (envelope-from <sender@example.com>)
+ id 1bjPYi-0000oA-Re
+ for gunter@ohrner.net; Mon, 12 Sep 2016 13:38:39 +0200
+Received: from example.com.local (172.17.124.205) by from example.com.local
+ (172.17.124.1) with Microsoft SMTP Server id 14.3.266.1; Mon, 12 Sep 2016
+ 13:37:53 +0200
+From: <sender@example.com>
+To: <receiver@example.com>
+Message-ID: <8614416.18.1473680273823.JavaMail.sender@example.com>
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="----=_Part_16_9312243.1473680273820"
+Date: Mon, 12 Sep 2016 13:37:53 +0200
+X-TM-AS-Product-Ver: SMEX-11.1.0.1278-8.000.1202-22570.004
+X-TM-AS-Result: No--23.664000-5.000000-31
+X-TM-AS-User-Approved-Sender: No
+X-TM-AS-User-Blocked-Sender: No
+Subject: A Subject Line
+
+------=_Part_16_9312243.1473680273820
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+<html><head></head><body><p><span style="font-family:Arial;">A Body Text</span></p></body></html>
+------=_Part_16_9312243.1473680273820
+Content-Type: application/pdf; name="Attachment1.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820
+Content-Type: application/pdf; name="Attachment2.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820
+Content-Type: application/pdf; name="Attachment3.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment
+
+JVBERi0xLjEKJeLjz9MKMSAwIG9iaiAKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDIgMCBSCj4+
+CmVuZG9iaiAKMiAwIG9iaiAKPDwKL0tpZHMgWzMgMCBSXQovVHlwZSAvUGFnZXMKL01lZGlhQm94
+IFswIDAgMzAwIDE0NF0KL0NvdW50IDEKPj4KZW5kb2JqIAozIDAgb2JqIAo8PAovUmVzb3VyY2Vz
+IAo8PAovRm9udCAKPDwKL0YxIAo8PAovU3VidHlwZSAvVHlwZTEKL1R5cGUgL0ZvbnQKL0Jhc2VG
+b250IC9UaW1lcy1Sb21hbgo+Pgo+Pgo+PgovQ29udGVudHMgNCAwIFIKL1BhcmVudCAyIDAgUgov
+VHlwZSAvUGFnZQovTWVkaWFCb3ggWzAgMCAzMDAgMTQ0XQo+PgplbmRvYmogCjQgMCBvYmogCjw8
+Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNTIKPj4Kc3RyZWFtCnicU1BwCuFSAAJ9N0MF
+QwuFkDQwzwAIQ1LATA2P1JycfIXw/KKcFE2FkCygoGsIACsbDAQKZW5kc3RyZWFtIAplbmRvYmog
+eHJlZgowIDUKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDE1IDAwMDAwIG4gCjAwMDAwMDAw
+NjYgMDAwMDAgbiAKMDAwMDAwMDE0OSAwMDAwMCBuIAowMDAwMDAwMzMxIDAwMDAwIG4gCnRyYWls
+ZXIKCjw8Ci9Sb290IDEgMCBSCi9TaXplIDUKPj4Kc3RhcnR4cmVmCjQ1NgolJUVPRgo=
+
+------=_Part_16_9312243.1473680273820--
+
diff --git a/templateparser/autotests/data/html-attachment1.mbox.html.reply b/templateparser/autotests/data/html-attachment1.mbox.html.reply
new file mode 100644
index 00000000..71b8c1c0
--- /dev/null
+++ b/templateparser/autotests/data/html-attachment1.mbox.html.reply
@@ -0,0 +1 @@
+<p><span style="font-family:Arial;">A Body Text</span></p>
diff --git a/templateparser/autotests/data/html-attachment2.mbox.forwarded.mbox b/templateparser/autotests/data/html-attachment2.mbox.forwarded.mbox
new file mode 100644
index 00000000..498cf3a9
--- /dev/null
+++ b/templateparser/autotests/data/html-attachment2.mbox.forwarded.mbox
@@ -0,0 +1,28 @@
+From maillists@whattf.com Tue Jul 05 19:20:58 2011
+Return-path: <maillists@whattf.com>
+Envelope-to: maillists@whattf.com
+Date: Mon, 24 Oct 2016 11:53:00 +0100
+To: maillists@whattf.com
+From: maillists@whattf.com
+MIME-Version: 1.0
+Subject: Test
+Content-Type: multipart/related; boundary="----=_Part_20324_1054669183.1477306380301"
+MIME-Version: 1.0
+
+------=_Part_20324_1054669183.1477306380301
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: 7bit
+
+<html><head></head><body>HTML Text</body></html>
+------=_Part_20324_1054669183.1477306380301
+Content-Type: image/png
+Content-Disposition: attachment; filename="image1.png"
+
+Image1
+------=_Part_20324_1054669183.1477306380301
+Content-Type: image/png
+Content-Disposition: attachment; filename="image2.png"
+
+Image2
+------=_Part_20324_1054669183.1477306380301--
+
diff --git a/templateparser/autotests/data/html-attachment2.mbox.html.reply b/templateparser/autotests/data/html-attachment2.mbox.html.reply
new file mode 100644
index 00000000..292a0196
--- /dev/null
+++ b/templateparser/autotests/data/html-attachment2.mbox.html.reply
@@ -0,0 +1 @@
+HTML Text
diff --git a/templateparser/autotests/data/openpgp-encrypted.mbox b/templateparser/autotests/data/openpgp-encrypted.mbox
new file mode 100644
index 00000000..54fe6530
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-encrypted.mbox
@@ -0,0 +1,35 @@
+From test@kolab.org Wed, 08 Sep 2010 17:02:52 +0200
+From: OpenPGP Test <test@kolab.org>
+To: test@kolab.org
+Subject: OpenPGP encrypted
+Date: Wed, 08 Sep 2010 17:02:52 +0200
+User-Agent: KMail/4.6 pre (Linux/2.6.34-rc2-2-default; KDE/4.5.60; x86_64; ; )
+MIME-Version: 1.0
+Content-Type: multipart/encrypted; boundary="nextPart1357031.ppLHckZtsp"; protocol="application/pgp-encrypted"
+Content-Transfer-Encoding: 7Bit
+
+
+--nextPart1357031.ppLHckZtsp
+Content-Type: application/pgp-encrypted
+Content-Disposition: attachment
+
+Version: 1
+--nextPart1357031.ppLHckZtsp
+Content-Type: application/octet-stream
+Content-Disposition: inline; filename="msg.asc"
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf/dJ5Pn6aE02/ImggNDDJBfvRU7vWF3OeKaGNrZd9sWp3Q
+x8Tas0r+Yfaoo5cNqEJ7LXaRA9UGJ3BtDEtzLl/xrY++QyewrRtLlfSLXFSifZOA
+9eBYmL4cj4Fbp4HR8iUBC2p64FzARQBe4hCyEoIIQPK7+3XqgsFdCIqmnEt1+Nys
+mKXN4VSukAcTzjR8JasPKZ+Qayx9+DBu7wJJNzjlhnrIv4AMYOTetLwL7f5/c2j+
+zX/2/9ptWwcBDxMl6WKTpPF4Zf57MgeV3TI4O5FHXfhjldUh6JiSt3i+t4ev9F8A
+s1pYZ15qDRwNwshZ7fujJvd1lk7ZDZdAQyKXgHNo0NKOATJ6IN0S5yvFi9FhyHM6
+sGzHXVqBgJspKqOuuoSPuX0RBvAskNPa4yERla0725n/F9AHsbiw4olQvIbKD70+
+gPd1k+MR6OfsGb+m5IIMBSjaKDLtgO9H0JLIq2U7Qf3YU+VsvvPH+PhTnUuz5/Ea
+0W1dTWWU0MRh0Z8uKM9KJHjMjkNzBvO4T8uTfRwBPA==
+=G9lS
+-----END PGP MESSAGE-----
+
+--nextPart1357031.ppLHckZtsp--
diff --git a/templateparser/autotests/data/openpgp-encrypted.mbox.html.reply b/templateparser/autotests/data/openpgp-encrypted.mbox.html.reply
new file mode 100644
index 00000000..57839070
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-encrypted.mbox.html.reply
@@ -0,0 +1 @@
+encrypted text<br>
diff --git a/templateparser/autotests/data/openpgp-encrypted.mbox.plain.reply b/templateparser/autotests/data/openpgp-encrypted.mbox.plain.reply
new file mode 100644
index 00000000..965dc9a5
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-encrypted.mbox.plain.reply
@@ -0,0 +1 @@
+encrypted text
diff --git a/templateparser/autotests/data/openpgp-inline-space.mbox b/templateparser/autotests/data/openpgp-inline-space.mbox
new file mode 100644
index 00000000..eaff262b
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-inline-space.mbox
@@ -0,0 +1,31 @@
+Subject: Testcase 'reply-decrytion-oracle' (PGP INLINE)
+To: brucewayne45@web.de
+From: brucewayne45@web.de
+MIME-Version: 1.0
+Content-Type: text/plain
+
+
+
+
+
+
+
+
+
+
+
+
+-----BEGIN PGP MESSAGE-----
+
+hQEMAwzOQ1qnzNo7AQf7BxmM0vO8nG37hKqoqOHb35JqprJM+sqF7JFmrsuWe6V2
+PAyyE2wdtq+AhvXjVnggxYLwU+DEFpBTmWr1rsanyV8hWXRbecfN/9gN/4/N9y7Z
+XSx2OeE/uA5z8Kz5vrv/ywMqcVHjB5MQPTcLC2Zlg8MVltpriy6mdAkON4I3t7kl
+j9uwQRY7HeKvsib63HWnYAOV/fYPXXor/lioeYIll08uuCiTh3Z9fEhXQI/az5Ft
+e/xa70xGqviux+OvhoNUSZspzl7vK7e/NTBlC+LF1zVXUXT8prrd+ZFNwKvtn0Hl
+W4KfNqTM9TJB8vpE5FWnH6+B365ZvxZopZ5F/9szp9JGAUCNdX5WujBreg7nTLui
+UrnDNwOvjvsE/gsoO3n3jARK+Tu8PfUl8V1bHiCeGJz/mkA9uGJ/IApcT4rYsoHB
+nVQjW1NJ6A==
+=zrF/
+-----END PGP MESSAGE-----
+
+whoo three encrypted parts inside.
diff --git a/templateparser/autotests/data/openpgp-inline-space.mbox.html.reply b/templateparser/autotests/data/openpgp-inline-space.mbox.html.reply
new file mode 100644
index 00000000..b78a7780
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-inline-space.mbox.html.reply
@@ -0,0 +1 @@
+first part<br>
diff --git a/templateparser/autotests/data/openpgp-inline-space.mbox.plain.reply b/templateparser/autotests/data/openpgp-inline-space.mbox.plain.reply
new file mode 100644
index 00000000..a20785d2
--- /dev/null
+++ b/templateparser/autotests/data/openpgp-inline-space.mbox.plain.reply
@@ -0,0 +1 @@
+first part
diff --git a/templateparser/autotests/templateextracthtmlelementwebengineviewtest.cpp b/templateparser/autotests/templateextracthtmlelementwebengineviewtest.cpp
index b150a77f..74ca7fbe 100644
--- a/templateparser/autotests/templateextracthtmlelementwebengineviewtest.cpp
+++ b/templateparser/autotests/templateextracthtmlelementwebengineviewtest.cpp
@@ -1,65 +1,65 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateextracthtmlelementwebengineviewtest.h"
#include "templateextracthtmlelementwebengineview.h"
#include <QTest>
#include <QSignalSpy>
TemplateExtractHtmlElementWebEngineViewTest::TemplateExtractHtmlElementWebEngineViewTest(QObject *parent)
: QObject(parent)
{
}
void TemplateExtractHtmlElementWebEngineViewTest::shouldHaveDefaultValue()
{
TemplateParser::TemplateExtractHtmlElementWebEngineView w;
QVERIFY(w.bodyElement().isEmpty());
QVERIFY(w.headerElement().isEmpty());
QVERIFY(w.htmlElement().isEmpty());
}
void TemplateExtractHtmlElementWebEngineViewTest::shouldExtractHtml_data()
{
QTest::addColumn<QString>("html");
QTest::addColumn<QString>("body");
QTest::addColumn<QString>("header");
QTest::newRow("test1") << QStringLiteral("<html><head></head><body>HTML Text</body></html>") << QStringLiteral("HTML Text") << QString();
QTest::newRow("empty") << QString() << QString() << QString();
}
void TemplateExtractHtmlElementWebEngineViewTest::shouldExtractHtml()
{
QFETCH(QString, html);
QFETCH(QString, body);
QFETCH(QString, header);
TemplateParser::TemplateExtractHtmlElementWebEngineView w;
QVERIFY(w.htmlElement().isEmpty());
QSignalSpy spy(&w, &TemplateParser::TemplateExtractHtmlElementWebEngineView::loadContentDone);
w.setHtmlContent(html);
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
const bool result = spy.at(0).at(0).toBool();
QVERIFY(result);
QCOMPARE(w.htmlElement(), html);
QCOMPARE(w.headerElement(), header);
QCOMPARE(w.bodyElement(), body);
}
QTEST_MAIN(TemplateExtractHtmlElementWebEngineViewTest)
diff --git a/templateparser/autotests/templateextracthtmlelementwebengineviewtest.h b/templateparser/autotests/templateextracthtmlelementwebengineviewtest.h
index 841c1c17..9a30ab3f 100644
--- a/templateparser/autotests/templateextracthtmlelementwebengineviewtest.h
+++ b/templateparser/autotests/templateextracthtmlelementwebengineviewtest.h
@@ -1,37 +1,37 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H
#define TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H
#include <QObject>
class TemplateExtractHtmlElementWebEngineViewTest : public QObject
{
Q_OBJECT
public:
explicit TemplateExtractHtmlElementWebEngineViewTest(QObject *parent = nullptr);
~TemplateExtractHtmlElementWebEngineViewTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldExtractHtml_data();
void shouldExtractHtml();
};
#endif // TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEWTEST_H
diff --git a/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.cpp b/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.cpp
index 61be01db..244696f2 100644
--- a/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.cpp
+++ b/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.cpp
@@ -1,61 +1,61 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparseremailaddressrequesterinterfacewidgettest.h"
#include "../src/templateparseremailaddressrequesterinterfacewidget.h"
#include <TemplateParser/TemplateParserEmailAddressRequesterBase>
#include <QHBoxLayout>
#include <QTest>
TemplateParserEmailAddressRequesterInterfaceWidgetTest::TemplateParserEmailAddressRequesterInterfaceWidgetTest(QObject *parent)
: QObject(parent)
{
}
void TemplateParserEmailAddressRequesterInterfaceWidgetTest::shouldHaveDefaultValues()
{
TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget w;
QHBoxLayout *mainLayout = w.findChild<QHBoxLayout *>(QStringLiteral("mainlayout"));
QVERIFY(mainLayout);
QCOMPARE(mainLayout->margin(), 0);
TemplateParser::TemplateParserEmailAddressRequesterBase *mTemplateParserEmailBase
= w.findChild<TemplateParser::TemplateParserEmailAddressRequesterBase *>(QStringLiteral("templateparseremailbase"));
QVERIFY(mTemplateParserEmailBase);
QVERIFY(mTemplateParserEmailBase->text().isEmpty());
}
void TemplateParserEmailAddressRequesterInterfaceWidgetTest::shouldAssignValues()
{
TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget w;
TemplateParser::TemplateParserEmailAddressRequesterBase *mTemplateParserEmailBase
= w.findChild<TemplateParser::TemplateParserEmailAddressRequesterBase *>(QStringLiteral("templateparseremailbase"));
const QString value{
QStringLiteral("foo")
};
w.setText(value);
QCOMPARE(w.text(), value);
QCOMPARE(mTemplateParserEmailBase->text(), value);
w.clear();
QVERIFY(w.text().isEmpty());
QVERIFY(mTemplateParserEmailBase->text().isEmpty());
}
QTEST_MAIN(TemplateParserEmailAddressRequesterInterfaceWidgetTest)
diff --git a/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.h b/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.h
index a929f9a7..77a808f1 100644
--- a/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.h
+++ b/templateparser/autotests/templateparseremailaddressrequesterinterfacewidgettest.h
@@ -1,37 +1,37 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGETTEST_H
#define TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGETTEST_H
#include <QObject>
class TemplateParserEmailAddressRequesterInterfaceWidgetTest : public QObject
{
Q_OBJECT
public:
explicit TemplateParserEmailAddressRequesterInterfaceWidgetTest(QObject *parent = nullptr);
~TemplateParserEmailAddressRequesterInterfaceWidgetTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValues();
void shouldAssignValues();
};
#endif // TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGETTEST_H
diff --git a/templateparser/autotests/templateparseremailaddressrequesterlineedittest.cpp b/templateparser/autotests/templateparseremailaddressrequesterlineedittest.cpp
index f458cad5..e9d67734 100644
--- a/templateparser/autotests/templateparseremailaddressrequesterlineedittest.cpp
+++ b/templateparser/autotests/templateparseremailaddressrequesterlineedittest.cpp
@@ -1,83 +1,83 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparseremailaddressrequesterlineedittest.h"
#include "templateparseremailaddressrequesterlineedit.h"
#include <QTest>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QSignalSpy>
TemplateParserEmailAddressRequesterLineEditTest::TemplateParserEmailAddressRequesterLineEditTest(QObject *parent)
: QObject(parent)
{
}
void TemplateParserEmailAddressRequesterLineEditTest::shouldHaveDefaultValue()
{
TemplateParser::TemplateParserEmailAddressRequesterLineEdit w;
QHBoxLayout *mainLayout = w.findChild<QHBoxLayout *>(QStringLiteral("mainlayout"));
QVERIFY(mainLayout);
QCOMPARE(mainLayout->margin(), 0);
QLineEdit *mLineEdit = w.findChild<QLineEdit *>(QStringLiteral("lineedit"));
QVERIFY(mLineEdit);
QVERIFY(mLineEdit->text().isEmpty());
}
void TemplateParserEmailAddressRequesterLineEditTest::shouldAssignValue()
{
TemplateParser::TemplateParserEmailAddressRequesterLineEdit w;
QLineEdit *mLineEdit = w.findChild<QLineEdit *>(QStringLiteral("lineedit"));
const QString str{
QStringLiteral("foo")
};
w.setText(str);
QCOMPARE(w.text(), str);
QCOMPARE(mLineEdit->text(), str);
}
void TemplateParserEmailAddressRequesterLineEditTest::shouldClearValue()
{
TemplateParser::TemplateParserEmailAddressRequesterLineEdit w;
QLineEdit *mLineEdit = w.findChild<QLineEdit *>(QStringLiteral("lineedit"));
const QString str{
QStringLiteral("foo")
};
w.setText(str);
QCOMPARE(w.text(), str);
w.clear();
QVERIFY(w.text().isEmpty());
QVERIFY(mLineEdit->text().isEmpty());
}
void TemplateParserEmailAddressRequesterLineEditTest::shouldEmitSignal()
{
TemplateParser::TemplateParserEmailAddressRequesterLineEdit w;
QSignalSpy spy(&w, &TemplateParser::TemplateParserEmailAddressRequesterBase::textChanged);
w.setText(QStringLiteral("foo"));
QCOMPARE(spy.count(), 1);
w.clear();
QCOMPARE(spy.count(), 2);
w.setText(QStringLiteral("foo"));
QCOMPARE(spy.count(), 3);
}
QTEST_MAIN(TemplateParserEmailAddressRequesterLineEditTest)
diff --git a/templateparser/autotests/templateparseremailaddressrequesterlineedittest.h b/templateparser/autotests/templateparseremailaddressrequesterlineedittest.h
index d9ee5052..1f208508 100644
--- a/templateparser/autotests/templateparseremailaddressrequesterlineedittest.h
+++ b/templateparser/autotests/templateparseremailaddressrequesterlineedittest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDITTEST_H
#define TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDITTEST_H
#include <QObject>
class TemplateParserEmailAddressRequesterLineEditTest : public QObject
{
Q_OBJECT
public:
explicit TemplateParserEmailAddressRequesterLineEditTest(QObject *parent = nullptr);
~TemplateParserEmailAddressRequesterLineEditTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAssignValue();
void shouldClearValue();
void shouldEmitSignal();
};
#endif // TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDITTEST_H
diff --git a/templateparser/autotests/templateparserextracthtmlinfotest.cpp b/templateparser/autotests/templateparserextracthtmlinfotest.cpp
index 9d310780..816f4789 100644
--- a/templateparser/autotests/templateparserextracthtmlinfotest.cpp
+++ b/templateparser/autotests/templateparserextracthtmlinfotest.cpp
@@ -1,66 +1,66 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparserextracthtmlinfotest.h"
#include "templateparserextracthtmlinfo.h"
#include <QTest>
#include <QSignalSpy>
TemplateParserExtractHtmlInfoTest::TemplateParserExtractHtmlInfoTest(QObject *parent)
: QObject(parent)
{
}
void TemplateParserExtractHtmlInfoTest::initTestCase()
{
qRegisterMetaType<TemplateParserExtractHtmlInfoResult>();
}
void TemplateParserExtractHtmlInfoTest::shouldReturnNullResult()
{
TemplateParser::TemplateParserExtractHtmlInfo *info = new TemplateParser::TemplateParserExtractHtmlInfo;
QSignalSpy spy(info, &TemplateParser::TemplateParserExtractHtmlInfo::finished);
info->start();
QCOMPARE(spy.count(), 1);
TemplateParserExtractHtmlInfoResult result = spy.at(0).at(0).value<TemplateParserExtractHtmlInfoResult>();
QVERIFY(result.mBodyElement.isEmpty());
QVERIFY(result.mHeaderElement.isEmpty());
QVERIFY(result.mHtmlElement.isEmpty());
QVERIFY(result.mPlainText.isEmpty());
QVERIFY(result.mTemplate.isEmpty());
}
void TemplateParserExtractHtmlInfoTest::shouldReturnNullButWithTemplate()
{
TemplateParser::TemplateParserExtractHtmlInfo *info = new TemplateParser::TemplateParserExtractHtmlInfo;
const QString templateStr = QStringLiteral("foo");
info->setTemplate(templateStr);
QSignalSpy spy(info, &TemplateParser::TemplateParserExtractHtmlInfo::finished);
info->start();
QCOMPARE(spy.count(), 1);
TemplateParserExtractHtmlInfoResult result = spy.at(0).at(0).value<TemplateParserExtractHtmlInfoResult>();
QVERIFY(result.mBodyElement.isEmpty());
QVERIFY(result.mHeaderElement.isEmpty());
QVERIFY(result.mHtmlElement.isEmpty());
QVERIFY(result.mPlainText.isEmpty());
QCOMPARE(result.mTemplate, templateStr);
}
QTEST_MAIN(TemplateParserExtractHtmlInfoTest)
diff --git a/templateparser/autotests/templateparserextracthtmlinfotest.h b/templateparser/autotests/templateparserextracthtmlinfotest.h
index 0e108218..8da5b863 100644
--- a/templateparser/autotests/templateparserextracthtmlinfotest.h
+++ b/templateparser/autotests/templateparserextracthtmlinfotest.h
@@ -1,37 +1,37 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREXTRACTHTMLINFOTEST_H
#define TEMPLATEPARSEREXTRACTHTMLINFOTEST_H
#include <QObject>
class TemplateParserExtractHtmlInfoTest : public QObject
{
Q_OBJECT
public:
explicit TemplateParserExtractHtmlInfoTest(QObject *parent = nullptr);
~TemplateParserExtractHtmlInfoTest() = default;
private Q_SLOTS:
void initTestCase();
void shouldReturnNullResult();
void shouldReturnNullButWithTemplate();
};
#endif // TEMPLATEPARSEREXTRACTHTMLINFOTEST_H
diff --git a/templateparser/autotests/templateparserjobtest.cpp b/templateparser/autotests/templateparserjobtest.cpp
index fb074427..a607ce9e 100644
--- a/templateparser/autotests/templateparserjobtest.cpp
+++ b/templateparser/autotests/templateparserjobtest.cpp
@@ -1,346 +1,565 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparserjobtest.h"
#define private public
#include "templateparserjob_p.h"
#include "templateparserjob.h"
#undef protected
#include <MimeTreeParser/ObjectTreeParser>
#include <KIdentityManagement/IdentityManager>
#include <KIdentityManagement/Identity>
#include <QTest>
#include <QDir>
#include <QLocale>
#include <QSignalSpy>
#include <QStandardPaths>
using namespace MimeTreeParser;
TemplateParserJobTest::TemplateParserJobTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
TemplateParserJobTest::~TemplateParserJobTest()
{
// Workaround QTestLib not flushing deleteLater()s on exit, which leads to WebEngine asserts (view not deleted)
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
void TemplateParserJobTest::test_convertedHtml_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
- const auto l = dir.entryList(QStringList(QLatin1String("plain*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ const auto l = dir.entryList(QStringList(QStringLiteral("plain*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
foreach (const QString &file, l) {
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << QString(dir.path() + QLatin1Char('/') + file + QLatin1String(".html"));
}
}
void TemplateParserJobTest::test_convertedHtml()
{
QFETCH(QString, mailFileName);
QFETCH(QString, referenceFileName);
// load input mail
QFile mailFile(mailFileName);
QVERIFY(mailFile.open(QIODevice::ReadOnly));
const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
QVERIFY(!mailData.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
- msg->setContent(mailData);
- msg->parse();
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
+ QCOMPARE(origMsg->subject()->as7BitString(false).constData(), "Plain Message Test");
+ QCOMPARE(origMsg->contents().size(), 0);
// load expected result
QFile referenceFile(referenceFileName);
QVERIFY(referenceFile.open(QIODevice::ReadOnly));
const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
const QString referenceData = QString::fromLatin1(referenceRawData);
QVERIFY(!referenceData.isEmpty());
- QCOMPARE(msg->subject()->as7BitString(false).constData(), "Plain Message Test");
- QCOMPARE(msg->contents().size(), 0);
TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::NewMessage);
KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
parser->setIdentityManager(identMan);
- parser->d->mOtp->parseObjectTree(msg.data());
+
+ parser->d->mOrigMsg = origMsg;
+
+ QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
+ parser->processWithTemplate(QString());
+ QVERIFY(spy.wait());
QVERIFY(parser->d->mOtp->htmlContent().isEmpty());
QVERIFY(!parser->d->mOtp->plainTextContent().isEmpty());
+ const QString convertedHtmlContent = parser->htmlMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+ QVERIFY(!convertedHtmlContent.isEmpty());
+
+ QCOMPARE(convertedHtmlContent, referenceData);
+}
+
+void TemplateParserJobTest::test_replyHtml_data()
+{
+ QTest::addColumn<QString>("mailFileName");
+ QTest::addColumn<QString>("referenceFileName");
+
+ QDir dir(QStringLiteral(MAIL_DATA_DIR));
+ const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ foreach (const QString &file, l) {
+ const QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".html.reply");
+ if (!QFile::exists(expectedFile)) {
+ continue;
+ }
+ QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile;
+ }
+}
+
+void TemplateParserJobTest::test_replyHtml()
+{
+ QFETCH(QString, mailFileName);
+ QFETCH(QString, referenceFileName);
+
+ // load input mail
+ QFile mailFile(mailFileName);
+ QVERIFY(mailFile.open(QIODevice::ReadOnly));
+ const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
+ QVERIFY(!mailData.isEmpty());
+ KMime::Message::Ptr msg(new KMime::Message);
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
+
+ // load expected result
+ QFile referenceFile(referenceFileName);
+ QVERIFY(referenceFile.open(QIODevice::ReadOnly));
+ const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
+ const QString referenceData = QString::fromLatin1(referenceRawData);
+ QVERIFY(!referenceData.isEmpty());
+
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::NewMessage);
+ KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
+ parser->setIdentityManager(identMan);
+
+ parser->d->mOrigMsg = origMsg;
+
QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
parser->processWithTemplate(QString());
QVERIFY(spy.wait());
+ QVERIFY(parser->d->mOtp->htmlContent().isEmpty());
+ QVERIFY(!parser->d->mOtp->plainTextContent().isEmpty());
- const QString convertedHtmlContent = parser->htmlMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+ QString convertedHtmlContent = parser->htmlMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
QVERIFY(!convertedHtmlContent.isEmpty());
+ // referenceData is read from a file and most text editors add a \n at the end of the last line
+ if (!convertedHtmlContent.endsWith(QStringLiteral("\n"))) {
+ convertedHtmlContent += QStringLiteral("\n");
+ }
+
QCOMPARE(convertedHtmlContent, referenceData);
}
+
void TemplateParserJobTest::test_replyPlain_data()
{
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("referenceFileName");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
foreach (const QString &file, l) {
const QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".plain.reply");
if (!QFile::exists(expectedFile)) {
continue;
}
QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile;
}
}
void TemplateParserJobTest::test_replyPlain()
{
QFETCH(QString, mailFileName);
QFETCH(QString, referenceFileName);
// load input mail
QFile mailFile(mailFileName);
QVERIFY(mailFile.open(QIODevice::ReadOnly));
const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
QVERIFY(!mailData.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
- msg->setContent(mailData);
- msg->parse();
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
// load expected result
QFile referenceFile(referenceFileName);
QVERIFY(referenceFile.open(QIODevice::ReadOnly));
const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
const QString referenceData = QString::fromLatin1(referenceRawData);
-// QVERIFY(!referenceData.isEmpty());
-
-// QCOMPARE(msg->subject()->as7BitString(false).constData(), "Plain Message Test");
-// QCOMPARE(msg->contents().size(), 0);
+ QVERIFY(!referenceData.isEmpty());
TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply);
- //KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
- //parser->setIdentityManager(identMan);
+ parser->d->mOrigMsg = origMsg;
+
+ QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
+ parser->processWithTemplate(QString());
+ QVERIFY(spy.wait());
+
+ const QString convertedPlainTextContent = parser->plainMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+
+ QCOMPARE(convertedPlainTextContent, referenceData);
+}
+
+void TemplateParserJobTest::test_forwardPlain_data()
+{
+ QTest::addColumn<QString>("mailFileName");
+ QTest::addColumn<QString>("referenceFileName");
+
+ QDir dir(QStringLiteral(MAIL_DATA_DIR));
+ const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ foreach (const QString &file, l) {
+ const QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".plain.reply");
+ if (!QFile::exists(expectedFile)) {
+ continue;
+ }
+ QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile;
+ }
+}
+
+void TemplateParserJobTest::test_forwardPlain()
+{
+ QFETCH(QString, mailFileName);
+ QFETCH(QString, referenceFileName);
+
+ // load input mail
+ QFile mailFile(mailFileName);
+ QVERIFY(mailFile.open(QIODevice::ReadOnly));
+ const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
+ QVERIFY(!mailData.isEmpty());
+ KMime::Message::Ptr msg(new KMime::Message);
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
+
+ // load expected result
+ QFile referenceFile(referenceFileName);
+ QVERIFY(referenceFile.open(QIODevice::ReadOnly));
+ const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
+ const QString referenceData = QString::fromLatin1(referenceRawData);
+ QVERIFY(!referenceData.isEmpty());
+
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Forward);
+ parser->d->mOrigMsg = origMsg;
+
+ QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
+ parser->processWithTemplate(QString());
+ QVERIFY(spy.wait());
+
+ const QString convertedPlainTextContent = parser->plainMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+
+ QCOMPARE(convertedPlainTextContent, referenceData);
+}
+
+void TemplateParserJobTest::test_forwardHtml_data()
+{
+ QTest::addColumn<QString>("mailFileName");
+ QTest::addColumn<QString>("referenceFileName");
+
+ QDir dir(QStringLiteral(MAIL_DATA_DIR));
+ const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ foreach (const QString &file, l) {
+ const QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".html.reply");
+ if (!QFile::exists(expectedFile)) {
+ continue;
+ }
+ QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile;
+ }
+}
+
+void TemplateParserJobTest::test_forwardHtml()
+{
+ QFETCH(QString, mailFileName);
+ QFETCH(QString, referenceFileName);
+
+ // load input mail
+ QFile mailFile(mailFileName);
+ QVERIFY(mailFile.open(QIODevice::ReadOnly));
+ const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
+ QVERIFY(!mailData.isEmpty());
+ KMime::Message::Ptr msg(new KMime::Message);
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
+
+ // load expected result
+ QFile referenceFile(referenceFileName);
+ QVERIFY(referenceFile.open(QIODevice::ReadOnly));
+ const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
+ const QString referenceData = QString::fromLatin1(referenceRawData);
+ QVERIFY(!referenceData.isEmpty());
+
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Forward);
+ parser->d->mOrigMsg = origMsg;
+
+ QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
+ parser->processWithTemplate(QString());
+ QVERIFY(spy.wait());
+
+ QString convertedHtmlContent = parser->htmlMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+ // referenceData is read from a file and most text editors add a \n at the end of the last line
+ if (!convertedHtmlContent.endsWith(QStringLiteral("\n"))) {
+ convertedHtmlContent += QStringLiteral("\n");
+ }
+
+ QCOMPARE(convertedHtmlContent, referenceData);
+}
+
+void TemplateParserJobTest::test_forwardedAttachments_data()
+{
+ QTest::addColumn<QString>("mailFileName");
+ QTest::addColumn<QString>("referenceFileName");
+
+ QDir dir(QStringLiteral(MAIL_DATA_DIR));
+ const auto l = dir.entryList(QStringList(QStringLiteral("*.mbox")), QDir::Files | QDir::Readable | QDir::NoSymLinks);
+ foreach (const QString &file, l) {
+ if (!QFile::exists(dir.path() + QLatin1Char('/') + file + QStringLiteral(".html.reply"))) {
+ continue;
+ }
+ QString expectedFile = dir.path() + QLatin1Char('/') + file + QStringLiteral(".forwarded.mbox");
+ QTest::newRow(file.toLatin1().constData()) << QString(dir.path() + QLatin1Char('/') + file) << expectedFile;
+ }
+}
+
+void TemplateParserJobTest::test_forwardedAttachments()
+{
+ QFETCH(QString, mailFileName);
+ QFETCH(QString, referenceFileName);
+
+ // load input mail
+ QFile mailFile(mailFileName);
+ QVERIFY(mailFile.open(QIODevice::ReadOnly));
+ const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
+ QVERIFY(!mailData.isEmpty());
+ KMime::Message::Ptr msg(new KMime::Message);
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
- parser->d->mOtp->parseObjectTree(msg.data());
- parser->d->mOrigMsg = msg;
- //QVERIFY(parser->mOtp->htmlContent().isEmpty());
- //QVERIFY(!parser->mOtp->plainTextContent().isEmpty());
+ bool referenceExists = QFile::exists(referenceFileName);
-// QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
-// parser->processWithTemplate(QString());
-// QVERIFY(spy.wait());
+ TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Forward);
+ parser->d->mOrigMsg = origMsg;
-// QBENCHMARK {
-// const QString convertedHtmlContent = parser->plainMessageText(false, TemplateParser::TemplateParserJob::NoSelectionAllowed);
+ QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
+ parser->processWithTemplate(QString());
+ QVERIFY(spy.wait());
+
+ if (referenceExists) {
+ QFile referenceFile(referenceFileName);
+ QVERIFY(referenceFile.open(QIODevice::ReadOnly));
+ const QByteArray referenceRawData = KMime::CRLFtoLF(referenceFile.readAll());
+ const KMime::Message::Ptr referenceMsg(new KMime::Message);
+ referenceMsg->setContent(referenceRawData);
+ referenceMsg->parse();
+
+ QCOMPARE(msg->contents().size(), referenceMsg->contents().size());
+ for (int i=1; i < msg->contents().size(); i++) {
+ QCOMPARE(msg->contents()[i]->encodedContent(), referenceMsg->contents()[i]->encodedContent());
+ }
+ } else {
+ QCOMPARE(msg->contents().size(), 0);
+ }
-// QCOMPARE(convertedHtmlContent, referenceData);
-// }
}
void TemplateParserJobTest::test_processWithTemplatesForBody_data()
{
QTest::addColumn<QString>("command");
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("expected");
QTest::addColumn<QString>("selection");
- QTest::newRow("%OTEXT") << "%OTEXT" << "Original text.\nLine two." << "Original text.\nLine two." << "";
- QTest::newRow("%OTEXT") << "%OTEXT" << "-----BEGIN PGP MESSAGE-----\nVersion: GnuPG v1.4.12 (GNU/Linux)\n"
+ QTest::newRow("%OTEXT-plain") << "%OTEXT" << "Original text.\nLine two." << "Original text.\nLine two." << "";
+ QTest::newRow("%OTEXT-encrypted") << "%OTEXT" << "-----BEGIN PGP MESSAGE-----\nVersion: GnuPG v1.4.12 (GNU/Linux)\n"
"\n"
"hQEMAwzOQ1qnzNo7AQgA1345CrnOBTGf2eo4ABR6wkOdasI9SELRBKA1fNkFcq+Z\n"
"Qg0gWB5RLapU+VFRc5hK1zPOZ1dY6j3+uPHO4RhjfUgfiZ8T7oaWav15yP+07u21\n"
"EI9W9sk+eQU9GZSOayURucmZa/mbBz9hrsmePpORxD+C3uNTYa6ePTFlQP6wEZOI\n"
"7E53DrtJnF0EzIsCBIVep6CyuYfuSSwQ5gMgyPzfBqiGHNw96w2i/eayErc6lquL\n"
"JPFhIcMMq8w9Yo9+vXCAbkns6dtBAzlnAzuV86VFUZ/MnHTlCNk2yHyGLP6BS6hG\n"
"kFEUmgdHrGRizdz1sjo1tSmOLu+Gyjlv1Ir/Sqr8etJQAeTq3heKslAfhtotAMMt\n"
"R3tk228Su13Q3CAP/rktAyuGMDFtH8klW09zFdsZBDu8svE6d9e2nZ541NGspFVI\n"
"6XTZHUMMdlgnTBcu3aPc0ow=\n"
"=0xtc\n"
"-----END PGP MESSAGE-----" << "Crypted line.\nCrypted line two." << "";
QTest::newRow("%QUOTE") << "%QUOTE" << "Quoted text.\nLine two." << "> Quoted text.\n> Line two." << "";
}
void TemplateParserJobTest::test_processWithTemplatesForBody()
{
QFETCH(QString, command);
QFETCH(QString, text);
QFETCH(QString, expected);
QFETCH(QString, selection);
KMime::Message::Ptr msg(new KMime::Message());
- msg->setBody(text.toLocal8Bit());
- msg->parse();
+ KMime::Message::Ptr origMsg(new KMime::Message());
+ origMsg->setBody(text.toLocal8Bit());
+ origMsg->parse();
TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply);
parser->setSelection(selection);
KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
parser->setIdentityManager(identMan);
parser->setAllowDecryption(true);
- parser->d->mOrigMsg = msg;
+ parser->d->mOrigMsg = origMsg;
QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
parser->processWithTemplate(command);
QVERIFY(spy.wait());
identMan->deleteLater();
QCOMPARE(QString::fromLatin1(msg->encodedBody()), expected);
QCOMPARE(spy.count(), 1);
}
void TemplateParserJobTest::test_processWithTemplatesForContent_data()
{
QTest::addColumn<QString>("command");
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("expectedBody");
QTest::addColumn<bool>("hasDictionary");
qputenv("TZ", "Europe/Paris");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
const QString file = QStringLiteral("plain-message.mbox");
const QString fileName = QString(dir.path() + QLatin1Char('/') + file);
QTest::newRow("%OTIME") << "%OTIME" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::ShortFormat) << false;
QTest::newRow("%OTIMELONG") << "%OTIMELONG" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) << false;
QTest::newRow("%OTIMELONGEN") << "%OTIMELONGEN" << fileName << QLocale(QLocale::C).toString(QTime(8, 0, 27), QLocale::LongFormat) << false;
QTest::newRow("%ODATE") << "%ODATE" << fileName << QLocale().toString(QDate(2011, 8, 7), QLocale::LongFormat) << false;
QTest::newRow("%ODATESHORT") << "%ODATESHORT" << fileName << QLocale().toString(QDate(2011, 8, 7), QLocale::ShortFormat) << false;
QTest::newRow("%ODATEEN") << "%ODATEEN" << fileName << QLocale::c().toString(QDate(2011, 8, 7), QLocale::LongFormat) << false;
QTest::newRow("%OFULLSUBJ") << "%OFULLSUBJ" << fileName << "Plain Message Test" << false;
QTest::newRow("%OFULLSUBJECT") << "%OFULLSUBJECT" << fileName << "Plain Message Test" << false;
QTest::newRow("%OFROMFNAME") << "%OFROMFNAME" << fileName << "Sudhendu" << false;
QTest::newRow("%OFROMLNAME") << "%OFROMLNAME" << fileName << "Kumar" << false;
QTest::newRow("%OFROMNAME") << "%OFROMNAME" << fileName << "Sudhendu Kumar" << false;
QTest::newRow("%OFROMADDR") << "%OFROMADDR" << fileName << "Sudhendu Kumar <dontspamme@yoohoo.com>" << false;
QTest::newRow("%OTOADDR") << "%OTOADDR" << fileName << "kde <foo@yoohoo.org>" << false;
QTest::newRow("%OTOFNAME") << "%OTOFNAME" << fileName << "kde" << false;
QTest::newRow("%OTONAME") << "%OTONAME" << fileName << "kde" << false;
QTest::newRow("%OTOLNAME") << "%OTOLNAME" << fileName << "" << false;
QTest::newRow("%OTOLIST") << "%OTOLIST" << fileName << "kde <foo@yoohoo.org>" << false;
QTest::newRow("%ODOW") << "%ODOW" << fileName << QLocale().dayName(7, QLocale::LongFormat) << false;
QTest::newRow("%BLANK") << "%BLANK" << fileName << "" << false;
QTest::newRow("%NOP") << "%NOP" << fileName << "" << false;
QTest::newRow("%DICTIONARYLANGUAGE=\"en\"") << "%DICTIONARYLANGUAGE=\"en\"" << fileName << "" << true;
QTest::newRow("%DICTIONARYLANGUAGE=\"\"") << "%DICTIONARYLANGUAGE=\"\"" << fileName << "" << false;
QTest::newRow("%OTIMELONG %OFULLSUBJECT") << "%OTIMELONG %OFULLSUBJECT" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) + QStringLiteral(" Plain Message Test") << false;
QTest::newRow("%OTIMELONG\n%OFULLSUBJECT") << "%OTIMELONG\n%OFULLSUBJECT" << fileName << QLocale().toString(QTime(8, 0, 27), QLocale::LongFormat) + QStringLiteral("\nPlain Message Test") << false;
QTest::newRow("%REM=\"sdfsfsdsdfsdf\"") << "%REM=\"sdfsfsdsdfsdf\"" << fileName << "" << false;
QTest::newRow("%CLEAR") << "%CLEAR" << fileName << "" << false;
QTest::newRow("FOO foo") << "FOO foo" << fileName << "FOO foo" << false;
const QString insertFileName = QString(dir.path() + QLatin1Char('/') + QLatin1String("insert-file.txt"));
QString insertFileNameCommand = QStringLiteral("%INSERT=\"%1\"").arg(insertFileName);
QTest::newRow("%INSERT") << insertFileNameCommand << fileName << "test insert file!\n" << false;
insertFileNameCommand = QStringLiteral("%PUT=\"%1\"").arg(insertFileName);
QTest::newRow("%PUT") << insertFileNameCommand << fileName << "test insert file!\n" << false;
- QTest::newRow("%MSGID") << "%MSGID" << fileName << "<20150@foo.kde.org>" << false;
+ QTest::newRow("%OMSGID") << "%OMSGID" << fileName << "<20150@foo.kde.org>" << false;
QTest::newRow("%SYSTEM") << "%SYSTEM=\"echo foo\"" << fileName << "foo\n" << false;
QTest::newRow("%DEBUG") << "%DEBUG" << fileName << "" << false;
QTest::newRow("%DEBUGOFF") << "%DEBUGOFF" << fileName << "" << false;
QTest::newRow("%HEADER=\"Reply-To\"") << "%HEADER=\"Reply-To\"" << fileName << "bla@yoohoo.org" << false;
//Header doesn't exist => don't add value
QTest::newRow("%OHEADER=\"SSS\"") << "%HEADER=\"SSS\"" << fileName << "" << false;
QTest::newRow("%OHEADER=\"To\"") << "%OHEADER=\"To\"" << fileName << "kde <foo@yoohoo.org>" << false;
//Unknown command
QTest::newRow("unknown command") << "%GGGGG" << fileName << "%GGGGG" << false;
//Test bug 308444
const QString file2 = QStringLiteral("plain-message-timezone.mbox");
const QString fileName2 = QString(dir.path() + QLatin1Char('/') + file2);
QTest::newRow("bug308444-%OTIMELONG") << "%OTIMELONG" << fileName2 << QLocale::system().toString(QTime(20, 31, 25), QLocale::LongFormat) << false;
}
void TemplateParserJobTest::test_processWithTemplatesForContent()
{
QFETCH(QString, command);
QFETCH(QString, mailFileName);
QFETCH(QString, expectedBody);
QFETCH(bool, hasDictionary);
QFile mailFile(mailFileName);
QVERIFY(mailFile.open(QIODevice::ReadOnly));
const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
QVERIFY(!mailData.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
- msg->setContent(mailData);
- msg->parse();
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply);
KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
parser->setIdentityManager(identMan);
parser->setAllowDecryption(false);
- parser->d->mOrigMsg = msg;
+ parser->d->mOrigMsg = origMsg;
QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
parser->processWithTemplate(command);
QVERIFY(spy.wait());
QCOMPARE(msg->hasHeader("X-KMail-Dictionary"), hasDictionary);
identMan->deleteLater();
QCOMPARE(QString::fromUtf8(msg->encodedBody()), expectedBody);
QCOMPARE(spy.count(), 1);
}
void TemplateParserJobTest::test_processWithTemplatesForContentOtherTimeZone_data()
{
QTest::addColumn<QString>("command");
QTest::addColumn<QString>("mailFileName");
QTest::addColumn<QString>("expectedBody");
QTest::addColumn<bool>("hasDictionary");
qputenv("TZ", "America/New_York");
QDir dir(QStringLiteral(MAIL_DATA_DIR));
//Test bug 308444
const QString file2 = QStringLiteral("plain-message-timezone.mbox");
const QString fileName2 = QString(dir.path() + QLatin1Char('/') + file2);
QTest::newRow("bug308444-%OTIMELONG") << "%OTIMELONG" << fileName2 << QLocale::system().toString(QTime(14, 31, 25), QLocale::LongFormat) << false;
}
void TemplateParserJobTest::test_processWithTemplatesForContentOtherTimeZone()
{
QFETCH(QString, command);
QFETCH(QString, mailFileName);
QFETCH(QString, expectedBody);
QFETCH(bool, hasDictionary);
QFile mailFile(mailFileName);
QVERIFY(mailFile.open(QIODevice::ReadOnly));
const QByteArray mailData = KMime::CRLFtoLF(mailFile.readAll());
QVERIFY(!mailData.isEmpty());
KMime::Message::Ptr msg(new KMime::Message);
- msg->setContent(mailData);
- msg->parse();
+ KMime::Message::Ptr origMsg(new KMime::Message);
+ origMsg->setContent(mailData);
+ origMsg->parse();
TemplateParser::TemplateParserJob *parser = new TemplateParser::TemplateParserJob(msg, TemplateParser::TemplateParserJob::Reply);
KIdentityManagement::IdentityManager *identMan = new KIdentityManagement::IdentityManager;
parser->setIdentityManager(identMan);
parser->setAllowDecryption(false);
- parser->d->mOrigMsg = msg;
+ parser->d->mOrigMsg = origMsg;
QSignalSpy spy(parser, &TemplateParser::TemplateParserJob::parsingDone);
parser->processWithTemplate(command);
QVERIFY(spy.wait());
QCOMPARE(msg->hasHeader("X-KMail-Dictionary"), hasDictionary);
identMan->deleteLater();
QCOMPARE(QString::fromUtf8(msg->encodedBody()), expectedBody);
QCOMPARE(spy.count(), 1);
}
QTEST_MAIN(TemplateParserJobTest)
diff --git a/templateparser/autotests/templateparserjobtest.h b/templateparser/autotests/templateparserjobtest.h
index 37a99610..f7771e8a 100644
--- a/templateparser/autotests/templateparserjobtest.h
+++ b/templateparser/autotests/templateparserjobtest.h
@@ -1,55 +1,67 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSERJOBTEST_H
#define TEMPLATEPARSERJOBTEST_H
#include <QObject>
class TemplateParserJobTest : public QObject
{
Q_OBJECT
public:
explicit TemplateParserJobTest(QObject *parent = nullptr);
~TemplateParserJobTest();
private Q_SLOTS:
/**
* checks whether text/plain only mails are converted to a valid HTML
*/
void test_convertedHtml();
void test_convertedHtml_data();
+ void test_replyHtml();
+ void test_replyHtml_data();
+
void test_replyPlain();
void test_replyPlain_data();
+ void test_forwardPlain();
+ void test_forwardPlain_data();
+
+ void test_forwardHtml();
+ void test_forwardHtml_data();
+
+ void test_forwardedAttachments();
+ void test_forwardedAttachments_data();
+
/**
* Tests whether templates are returning required body or not
*/
void test_processWithTemplatesForBody();
void test_processWithTemplatesForBody_data();
void test_processWithTemplatesForContent();
void test_processWithTemplatesForContent_data();
void test_processWithTemplatesForContentOtherTimeZone();
void test_processWithTemplatesForContentOtherTimeZone_data();
};
#endif // TEMPLATEPARSERJOBTEST_H
diff --git a/templateparser/autotests/templatesinsertcommandactiontest.cpp b/templateparser/autotests/templatesinsertcommandactiontest.cpp
index bc0a477a..2e3211d7 100644
--- a/templateparser/autotests/templatesinsertcommandactiontest.cpp
+++ b/templateparser/autotests/templatesinsertcommandactiontest.cpp
@@ -1,40 +1,39 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "templatesinsertcommandactiontest.h"
#include "templatesinsertcommandaction.h"
#include "templatescommandmenu.h"
#include <QMenu>
#include <QTest>
QTEST_MAIN(TemplatesInsertCommandActionTest)
TemplatesInsertCommandActionTest::TemplatesInsertCommandActionTest(QObject *parent)
: QObject(parent)
{
-
}
void TemplatesInsertCommandActionTest::shouldHaveDefaultValue()
{
TemplateParser::TemplatesInsertCommandAction act;
QVERIFY(act.menu());
QVERIFY(!act.menu()->isEmpty());
TemplateParser::TemplatesCommandMenu *menu = act.findChild<TemplateParser::TemplatesCommandMenu *>(QStringLiteral("templatescommandmenu"));
QVERIFY(menu);
}
diff --git a/templateparser/autotests/templatesinsertcommandactiontest.h b/templateparser/autotests/templatesinsertcommandactiontest.h
index b4927e28..417da4f2 100644
--- a/templateparser/autotests/templatesinsertcommandactiontest.h
+++ b/templateparser/autotests/templatesinsertcommandactiontest.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATESINSERTCOMMANDACTIONTEST_H
#define TEMPLATESINSERTCOMMANDACTIONTEST_H
#include <QObject>
class TemplatesInsertCommandActionTest : public QObject
{
Q_OBJECT
public:
explicit TemplatesInsertCommandActionTest(QObject *parent = nullptr);
~TemplatesInsertCommandActionTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // TEMPLATESINSERTCOMMANDACTIONTEST_H
diff --git a/templateparser/autotests/templatesinsertcommandpushbuttontest.cpp b/templateparser/autotests/templatesinsertcommandpushbuttontest.cpp
index 3aef640c..74bdc302 100644
--- a/templateparser/autotests/templatesinsertcommandpushbuttontest.cpp
+++ b/templateparser/autotests/templatesinsertcommandpushbuttontest.cpp
@@ -1,43 +1,42 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "templatesinsertcommandpushbuttontest.h"
#include "templatesinsertcommandpushbutton.h"
#include "templatescommandmenu.h"
#include <QTest>
#include <QMenu>
QTEST_MAIN(TemplatesInsertCommandPushButtonTest)
TemplatesInsertCommandPushButtonTest::TemplatesInsertCommandPushButtonTest(QObject *parent)
: QObject(parent)
{
-
}
void TemplatesInsertCommandPushButtonTest::shouldHaveDefaultValue()
{
TemplateParser::TemplatesInsertCommandPushButton act(nullptr);
QVERIFY(act.menu());
QVERIFY(!act.menu()->isEmpty());
QCOMPARE(act.type(), TemplateParser::TemplatesCommandMenu::Default);
TemplateParser::TemplatesCommandMenu *menu = act.findChild<TemplateParser::TemplatesCommandMenu *>(QStringLiteral("templatescommandmenu"));
QVERIFY(menu);
}
diff --git a/templateparser/autotests/templatesinsertcommandpushbuttontest.h b/templateparser/autotests/templatesinsertcommandpushbuttontest.h
index 51be8d30..98a06d26 100644
--- a/templateparser/autotests/templatesinsertcommandpushbuttontest.h
+++ b/templateparser/autotests/templatesinsertcommandpushbuttontest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATESINSERTCOMMANDPUSHBUTTONTEST_H
#define TEMPLATESINSERTCOMMANDPUSHBUTTONTEST_H
#include <QObject>
class TemplatesInsertCommandPushButtonTest : public QObject
{
Q_OBJECT
public:
explicit TemplatesInsertCommandPushButtonTest(QObject *parent = nullptr);
~TemplatesInsertCommandPushButtonTest() = default;
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // TEMPLATESINSERTCOMMANDPUSHBUTTONTEST_H
diff --git a/templateparser/autotests/templatewebengineviewtest.cpp b/templateparser/autotests/templatewebengineviewtest.cpp
index 8eb466e9..e82b1c1b 100644
--- a/templateparser/autotests/templatewebengineviewtest.cpp
+++ b/templateparser/autotests/templatewebengineviewtest.cpp
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templatewebengineviewtest.h"
#include "templatewebengineview.h"
#include <QTest>
#include <QSignalSpy>
TemplateWebEngineViewTest::TemplateWebEngineViewTest(QObject *parent)
: QObject(parent)
{
}
TemplateWebEngineViewTest::~TemplateWebEngineViewTest()
{
}
void TemplateWebEngineViewTest::shouldHaveDefaultValue()
{
TemplateParser::TemplateWebEngineView w;
QVERIFY(w.plainText().isEmpty());
}
void TemplateWebEngineViewTest::shouldExtractHtml()
{
TemplateParser::TemplateWebEngineView w;
QVERIFY(w.plainText().isEmpty());
QSignalSpy spy(&w, &TemplateParser::TemplateWebEngineView::loadContentDone);
w.setHtmlContent(QStringLiteral("<html><head></head><body>HTML Text</body></html>"));
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
const bool result = spy.at(0).at(0).toBool();
QVERIFY(result);
QCOMPARE(w.plainText(), QStringLiteral("HTML Text"));
}
QTEST_MAIN(TemplateWebEngineViewTest)
diff --git a/templateparser/autotests/templatewebengineviewtest.h b/templateparser/autotests/templatewebengineviewtest.h
index 6675e195..699ddde5 100644
--- a/templateparser/autotests/templatewebengineviewtest.h
+++ b/templateparser/autotests/templatewebengineviewtest.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEWEBENGINEVIEWTEST_H
#define TEMPLATEWEBENGINEVIEWTEST_H
#include <QObject>
class TemplateWebEngineViewTest : public QObject
{
Q_OBJECT
public:
explicit TemplateWebEngineViewTest(QObject *parent = nullptr);
~TemplateWebEngineViewTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldExtractHtml();
};
#endif // TEMPLATEWEBENGINEVIEWTEST_H
diff --git a/templateparser/metainfo.yaml b/templateparser/metainfo.yaml
index f07ef1ee..44820d89 100644
--- a/templateparser/metainfo.yaml
+++ b/templateparser/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer: mlaurent
description: TemplateParser Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: TemplateParser
cmake: "KF5::TemplateParser"
- cmakename: KF5TemplateParser
+cmakename: KF5TemplateParser
diff --git a/templateparser/src/customtemplates.cpp b/templateparser/src/customtemplates.cpp
index 7fc3c26d..f96bf28e 100644
--- a/templateparser/src/customtemplates.cpp
+++ b/templateparser/src/customtemplates.cpp
@@ -1,574 +1,574 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
- * Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2011-2019 Laurent Montel <montel@kde.org>
*
* 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.
*/
#include "customtemplates.h"
#include "templateparser/templatesinsertcommandpushbutton.h"
#include "customtemplates_kfg.h"
#include "globalsettings_templateparser.h"
#include "ui_customtemplates_base.h"
#include "kpimtextedit/plaintexteditor.h"
#include "templateparseremailaddressrequesterinterfacewidget.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <QIcon>
#include <QWhatsThis>
using namespace TemplateParser;
CustomTemplates::CustomTemplates(const QList<KActionCollection *> &actionCollection, QWidget *parent)
: QWidget(parent)
, mBlockChangeSignal(false)
{
mUi = new Ui_CustomTemplatesBase;
mUi->setupUi(this);
mUi->mAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
mUi->mAdd->setEnabled(false);
mUi->mRemove->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
mUi->mDuplicate->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
mUi->mList->setColumnWidth(0, 100);
mUi->mList->header()->setStretchLastSection(true);
mUi->mList->setItemDelegate(new CustomTemplateItemDelegate(this));
mUi->mList->header()->setSectionsMovable(false);
mUi->mEditFrame->setEnabled(false);
mUi->mName->setTrapReturnKey(true);
connect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged,
this, &CustomTemplates::slotTextChanged);
connect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
connect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
connect(mUi->mName, &KLineEdit::textChanged, this, &CustomTemplates::slotNameChanged);
connect(mUi->mName, &KLineEdit::returnPressed, this, &CustomTemplates::slotAddClicked);
- connect(mUi->mInsertCommand, QOverload<const QString &, int>::of(&TemplateParser::TemplatesInsertCommandPushButton::insertCommand), this, &CustomTemplates::slotInsertCommand);
+ connect(mUi->mInsertCommand, qOverload<const QString &, int>(&TemplateParser::TemplatesInsertCommandPushButton::insertCommand), this, &CustomTemplates::slotInsertCommand);
connect(mUi->mAdd, &QPushButton::clicked, this, &CustomTemplates::slotAddClicked);
connect(mUi->mRemove, &QPushButton::clicked, this, &CustomTemplates::slotRemoveClicked);
connect(mUi->mDuplicate, &QPushButton::clicked, this, &CustomTemplates::slotDuplicateClicked);
connect(mUi->mList, &QTreeWidget::currentItemChanged, this, &CustomTemplates::slotListSelectionChanged);
connect(mUi->mList, &QTreeWidget::itemChanged, this, &CustomTemplates::slotItemChanged);
- connect(mUi->mType, QOverload<int>::of(&KComboBox::activated), this, &CustomTemplates::slotTypeActivated);
+ connect(mUi->mType, qOverload<int>(&KComboBox::activated), this, &CustomTemplates::slotTypeActivated);
connect(mUi->mKeySequenceWidget, &KKeySequenceWidget::keySequenceChanged, this, &CustomTemplates::slotShortcutChanged);
mUi->mKeySequenceWidget->setCheckActionCollections(actionCollection);
mReplyPix = QIcon::fromTheme(QStringLiteral("mail-reply-sender"));
mReplyAllPix = QIcon::fromTheme(QStringLiteral("mail-reply-all"));
mForwardPix = QIcon::fromTheme(QStringLiteral("mail-forward"));
mUi->mType->clear();
mUi->mType->addItem(QPixmap(), i18nc("Message->", "Universal"));
mUi->mType->addItem(mReplyPix, i18nc("Message->", "Reply"));
mUi->mType->addItem(mReplyAllPix, i18nc("Message->", "Reply to All"));
mUi->mType->addItem(mForwardPix, i18nc("Message->", "Forward"));
mUi->mHelp->setText(i18n("<a href=\"whatsthis\">How does this work?</a>"));
connect(mUi->mHelp, &QLabel::linkActivated,
this, &CustomTemplates::slotHelpLinkClicked);
mUi->mHelp->setContextMenuPolicy(Qt::NoContextMenu);
slotNameChanged(mUi->mName->text());
}
void CustomTemplates::slotHelpLinkClicked(const QString &)
{
const QString help
= i18n("<qt>"
"<p>Here you can add, edit, and delete custom message "
"templates to use when you compose a reply or forwarding message. "
"Create the custom template by typing the name into the input box "
"and press the '+' button. Also, you can bind a keyboard "
"combination to the template for faster operations.</p>"
"<p>Message templates support substitution commands, "
"by simply typing them or selecting them from the "
"<i>Insert command</i> menu.</p>"
"<p>There are four types of custom templates: used to "
"<i>Reply</i>, <i>Reply to All</i>, <i>Forward</i>, and "
"<i>Universal</i> which can be used for all kinds of operations. "
"You cannot bind a keyboard shortcut to <i>Universal</i> templates.</p>"
"</qt>");
QWhatsThis::showText(QCursor::pos(), help);
}
CustomTemplates::~CustomTemplates()
{
disconnect(mUi->mEdit->editor(), &QPlainTextEdit::textChanged,
this, &CustomTemplates::slotTextChanged);
disconnect(mUi->mToEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
disconnect(mUi->mCCEdit, &TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget::textChanged, this, &CustomTemplates::slotTextChanged);
delete mUi;
mUi = nullptr;
}
void CustomTemplates::slotNameChanged(const QString &text)
{
mUi->mAdd->setEnabled(!text.trimmed().isEmpty());
}
QString CustomTemplates::indexToType(int index)
{
QString typeStr;
switch (index) {
case TUniversal:
typeStr = i18nc("Message->", "Universal");
break;
/* case TNewMessage:
typeStr = i18n( "New Message" );
break; */
case TReply:
typeStr = i18nc("Message->", "Reply");
break;
case TReplyAll:
typeStr = i18nc("Message->", "Reply to All");
break;
case TForward:
typeStr = i18nc("Message->", "Forward");
break;
default:
typeStr = i18nc("Message->", "Unknown");
break;
}
return typeStr;
}
void CustomTemplates::slotTextChanged()
{
QTreeWidgetItem *item = mUi->mList->currentItem();
if (item) {
CustomTemplateItem *vitem = static_cast<CustomTemplateItem *>(item);
vitem->setContent(mUi->mEdit->toPlainText());
if (!mBlockChangeSignal) {
vitem->setTo(mUi->mToEdit->text());
vitem->setCc(mUi->mCCEdit->text());
}
}
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
void CustomTemplates::iconFromType(CustomTemplates::Type type, CustomTemplateItem *item)
{
switch (type) {
case TReply:
item->setIcon(0, mReplyPix);
break;
case TReplyAll:
item->setIcon(0, mReplyAllPix);
break;
case TForward:
item->setIcon(0, mForwardPix);
break;
default:
item->setIcon(0, QPixmap());
break;
}
}
void CustomTemplates::load()
{
const QStringList list = TemplateParserSettings::self()->customTemplates();
mUi->mList->clear();
QStringList::const_iterator end(list.constEnd());
for (QStringList::const_iterator it = list.constBegin(); it != end; ++it) {
CTemplates t(*it);
QKeySequence shortcut(t.shortcut());
CustomTemplates::Type type = static_cast<Type>(t.type());
CustomTemplateItem *item = new CustomTemplateItem(mUi->mList, *it, t.content(),
shortcut, type, t.to(), t.cC());
item->setText(1, *it);
item->setText(0, indexToType(type));
iconFromType(type, item);
}
const bool enabled = mUi->mList->topLevelItemCount() > 0 && mUi->mList->currentItem();
mUi->mRemove->setEnabled(enabled);
mUi->mDuplicate->setEnabled(enabled);
}
void CustomTemplates::save()
{
// Before saving the new templates, delete the old ones. That needs to be done before
// saving, since otherwise a new template with the new name wouldn't get saved.
KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("customtemplatesrc"), KConfig::NoGlobals);
for (const QString &item : qAsConst(mItemsToDelete)) {
CTemplates t(item);
const QString configGroup = t.currentGroup();
config->deleteGroup(configGroup);
}
QStringList list;
QTreeWidgetItemIterator lit(mUi->mList);
while (*lit) {
CustomTemplateItem *it = static_cast<CustomTemplateItem *>(*lit);
const QString name = it->text(1);
list.append(name);
CTemplates t(name);
QString content = it->content();
if (content.trimmed().isEmpty()) {
content = QStringLiteral("%BLANK");
}
t.setContent(content);
t.setShortcut(it->shortcut().toString());
t.setType(it->customType());
t.setTo(it->to());
t.setCC(it->cc());
t.save();
++lit;
}
TemplateParserSettings::self()->setCustomTemplates(list);
TemplateParserSettings::self()->save();
Q_EMIT templatesUpdated();
}
void CustomTemplates::slotInsertCommand(const QString &cmd, int adjustCursor)
{
QTextCursor cursor = mUi->mEdit->editor()->textCursor();
cursor.insertText(cmd);
cursor.setPosition(cursor.position() + adjustCursor);
mUi->mEdit->editor()->setTextCursor(cursor);
mUi->mEdit->editor()->setFocus();
}
bool CustomTemplates::nameAlreadyExists(const QString &str, QTreeWidgetItem *item)
{
QTreeWidgetItemIterator lit(mUi->mList);
while (*lit) {
const QString name = (*lit)->text(1);
if ((name == str) && ((*lit) != item)) {
KMessageBox::error(
this,
i18n("A template with same name already exists."),
i18n("Cannot create template"));
return true;
}
++lit;
}
return false;
}
void CustomTemplates::slotAddClicked()
{
const QString str = mUi->mName->text();
if (!str.isEmpty()) {
if (nameAlreadyExists(str)) {
return;
}
QKeySequence nullShortcut;
CustomTemplateItem *item
= new CustomTemplateItem(mUi->mList, str, QString(), nullShortcut, TUniversal,
QString(), QString());
item->setText(0, indexToType(TUniversal));
item->setText(1, str);
mUi->mList->setCurrentItem(item);
mUi->mRemove->setEnabled(true);
mUi->mDuplicate->setEnabled(true);
mUi->mName->clear();
mUi->mKeySequenceWidget->setEnabled(false);
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
}
QString CustomTemplates::createUniqueName(const QString &name) const
{
QString uniqueName = name;
int counter = 0;
bool found = true;
while (found) {
found = false;
QTreeWidgetItemIterator lit(mUi->mList);
while (*lit) {
const QString itemName = (*lit)->text(1);
if (!itemName.compare(uniqueName)) {
found = true;
++counter;
uniqueName = name;
uniqueName += QLatin1String(" (") + QString::number(counter) + QLatin1String(")");
break;
}
lit++;
}
}
return uniqueName;
}
void CustomTemplates::slotDuplicateClicked()
{
QTreeWidgetItem *currentItem = mUi->mList->currentItem();
if (!currentItem) {
return;
}
CustomTemplateItem *origItem = static_cast<CustomTemplateItem *>(currentItem);
const QString templateName = createUniqueName(origItem->text(1));
QKeySequence nullShortcut;
CustomTemplates::Type type = origItem->customType();
CustomTemplateItem *item
= new CustomTemplateItem(mUi->mList, templateName, origItem->content(), nullShortcut, type,
origItem->to(), origItem->cc());
item->setText(0, indexToType(type));
item->setText(1, templateName);
iconFromType(type, item);
mUi->mList->setCurrentItem(item);
mUi->mRemove->setEnabled(true);
mUi->mDuplicate->setEnabled(true);
mUi->mName->clear();
mUi->mKeySequenceWidget->setEnabled(type != TUniversal);
Q_EMIT changed();
}
void CustomTemplates::slotRemoveClicked()
{
QTreeWidgetItem *item = mUi->mList->currentItem();
if (!item) {
return;
}
const QString templateName = item->text(1);
if (KMessageBox::warningContinueCancel(
this,
i18nc("@info", "Do you really want to remove template \"%1\"?", templateName),
i18nc("@title:window", "Remove Template?"),
KStandardGuiItem::remove(),
KStandardGuiItem::cancel()) == KMessageBox::Continue) {
mItemsToDelete.append(templateName);
delete mUi->mList->takeTopLevelItem(mUi->mList->indexOfTopLevelItem(item));
mUi->mRemove->setEnabled(mUi->mList->topLevelItemCount() > 0);
mUi->mDuplicate->setEnabled(mUi->mList->topLevelItemCount() > 0);
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
}
void CustomTemplates::slotListSelectionChanged()
{
QTreeWidgetItem *item = mUi->mList->currentItem();
if (item) {
mUi->mEditFrame->setEnabled(true);
mUi->mRemove->setEnabled(true);
mUi->mDuplicate->setEnabled(true);
CustomTemplateItem *vitem = static_cast<CustomTemplateItem *>(item);
mBlockChangeSignal = true;
mUi->mEdit->setPlainText(vitem->content());
mUi->mKeySequenceWidget->setKeySequence(vitem->shortcut(),
KKeySequenceWidget::NoValidate);
CustomTemplates::Type type = vitem->customType();
mUi->mType->setCurrentIndex(mUi->mType->findText(indexToType(type)));
mUi->mToEdit->setText(vitem->to());
mUi->mCCEdit->setText(vitem->cc());
mBlockChangeSignal = false;
// I think the logic (originally 'vitem->mType==TUniversal') was inverted here:
// a key shortcut is only allowed for a specific type of template and not for
// a universal, as otherwise we won't know what sort of action to do when the
// key sequence is activated!
// This agrees with KMMainWidget::updateCustomTemplateMenus() -- marten
mUi->mKeySequenceWidget->setEnabled(type != TUniversal);
} else {
mUi->mEditFrame->setEnabled(false);
mUi->mEdit->editor()->clear();
// see above
mUi->mKeySequenceWidget->clearKeySequence();
mUi->mType->setCurrentIndex(0);
mUi->mToEdit->clear();
mUi->mCCEdit->clear();
}
}
void CustomTemplates::slotTypeActivated(int index)
{
QTreeWidgetItem *item = mUi->mList->currentItem();
if (item) {
CustomTemplateItem *vitem = static_cast<CustomTemplateItem *>(item);
CustomTemplates::Type customtype = static_cast<Type>(index);
vitem->setCustomType(customtype);
vitem->setText(0, indexToType(customtype));
iconFromType(customtype, vitem);
// see slotListSelectionChanged() above
mUi->mKeySequenceWidget->setEnabled(customtype != TUniversal);
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
}
void CustomTemplates::slotShortcutChanged(const QKeySequence &newSeq)
{
QTreeWidgetItem *item = mUi->mList->currentItem();
if (item) {
CustomTemplateItem *vitem = static_cast<CustomTemplateItem *>(item);
vitem->setShortcut(newSeq);
mUi->mKeySequenceWidget->applyStealShortcut();
}
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
void CustomTemplates::slotItemChanged(QTreeWidgetItem *item, int column)
{
if (item) {
CustomTemplateItem *vitem = static_cast<CustomTemplateItem *>(item);
if (column == 1) {
const QString newName = vitem->text(1).trimmed();
if (!newName.isEmpty()) {
const QString oldName = vitem->oldName();
if (nameAlreadyExists(newName, item)) {
vitem->setText(1, oldName);
return;
}
if (newName != oldName) {
mItemsToDelete.append(oldName);
vitem->setOldName(newName);
if (!mBlockChangeSignal) {
Q_EMIT changed();
}
}
}
}
}
}
CustomTemplateItemDelegate::CustomTemplateItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
CustomTemplateItemDelegate::~CustomTemplateItemDelegate()
{
}
void CustomTemplateItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
KLineEdit *lineEdit = static_cast<KLineEdit *>(editor);
const QString text = lineEdit->text();
if (!text.isEmpty()) {
model->setData(index, text, Qt::EditRole);
}
}
QWidget *CustomTemplateItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() == 1) {
return QStyledItemDelegate::createEditor(parent, option, index);
}
return nullptr;
}
CustomTemplateItem::CustomTemplateItem(QTreeWidget *parent, const QString &name, const QString &content, const QKeySequence &shortcut, CustomTemplates::Type type, const QString &to, const QString &cc)
: QTreeWidgetItem(parent)
, mName(name)
, mContent(content)
, mShortcut(shortcut)
, mType(type)
, mTo(to)
, mCC(cc)
{
setFlags(flags() | Qt::ItemIsEditable);
}
CustomTemplateItem::~CustomTemplateItem()
{
}
void CustomTemplateItem::setCustomType(CustomTemplates::Type type)
{
mType = type;
}
CustomTemplates::Type CustomTemplateItem::customType() const
{
return mType;
}
QString CustomTemplateItem::to() const
{
return mTo;
}
QString CustomTemplateItem::cc() const
{
return mCC;
}
QString CustomTemplateItem::content() const
{
return mContent;
}
void CustomTemplateItem::setContent(const QString &content)
{
mContent = content;
}
void CustomTemplateItem::setTo(const QString &to)
{
mTo = to;
}
void CustomTemplateItem::setCc(const QString &cc)
{
mCC = cc;
}
QKeySequence CustomTemplateItem::shortcut() const
{
return mShortcut;
}
void CustomTemplateItem::setShortcut(const QKeySequence &shortcut)
{
mShortcut = shortcut;
}
QString CustomTemplateItem::oldName() const
{
return mName;
}
void CustomTemplateItem::setOldName(const QString &name)
{
mName = name;
}
diff --git a/templateparser/src/customtemplates.h b/templateparser/src/customtemplates.h
index a45d785b..d7f97b98 100644
--- a/templateparser/src/customtemplates.h
+++ b/templateparser/src/customtemplates.h
@@ -1,133 +1,133 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
- * Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2011-2019 Laurent Montel <montel@kde.org>
*
* 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.
*/
#ifndef TEMPLATEPARSER_CUSTOMTEMPLATES_H
#define TEMPLATEPARSER_CUSTOMTEMPLATES_H
#include "templateparser_export.h"
#include <QStyledItemDelegate>
#include <QTreeWidgetItem>
#include <QWidget>
class KActionCollection;
class Ui_CustomTemplatesBase;
namespace TemplateParser {
class CustomTemplateItem;
class TEMPLATEPARSER_EXPORT CustomTemplates : public QWidget
{
Q_OBJECT
public:
enum Type {
TUniversal,
TReply,
TReplyAll,
TForward
};
public:
explicit CustomTemplates(const QList<KActionCollection *> &actionCollection, QWidget *parent = nullptr);
~CustomTemplates();
void load();
void save();
Q_SIGNALS:
void changed();
void templatesUpdated();
private Q_SLOTS:
void slotInsertCommand(const QString &cmd, int adjustCursor = 0);
void slotTextChanged();
void slotAddClicked();
void slotRemoveClicked();
void slotListSelectionChanged();
void slotTypeActivated(int index);
void slotShortcutChanged(const QKeySequence &newSeq);
void slotItemChanged(QTreeWidgetItem *item, int column);
void slotHelpLinkClicked(const QString &);
void slotNameChanged(const QString &text);
void slotDuplicateClicked();
private:
bool nameAlreadyExists(const QString &str, QTreeWidgetItem *item = nullptr);
QString indexToType(int index);
QString createUniqueName(const QString &name) const;
void iconFromType(CustomTemplates::Type type, CustomTemplateItem *item);
/// These templates will be deleted when we're saving.
QStringList mItemsToDelete;
QIcon mReplyPix;
QIcon mReplyAllPix;
QIcon mForwardPix;
/// Whether or not to Q_EMIT the changed() signal. This is useful to disable when loading
/// templates, which changes the UI without user action
bool mBlockChangeSignal;
Ui_CustomTemplatesBase *mUi = nullptr;
};
class CustomTemplateItem : public QTreeWidgetItem
{
public:
explicit CustomTemplateItem(QTreeWidget *parent, const QString &name, const QString &content, const QKeySequence &shortcut, CustomTemplates::Type type, const QString &to, const QString &cc);
~CustomTemplateItem();
void setCustomType(CustomTemplates::Type type);
CustomTemplates::Type customType() const;
QString to() const;
QString cc() const;
void setTo(const QString &);
void setCc(const QString &);
QString content() const;
void setContent(const QString &);
QKeySequence shortcut() const;
void setShortcut(const QKeySequence &);
QString oldName() const;
void setOldName(const QString &);
private:
QString mName, mContent;
QKeySequence mShortcut;
CustomTemplates::Type mType;
QString mTo, mCC;
};
class CustomTemplateItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit CustomTemplateItemDelegate(QObject *parent = nullptr);
~CustomTemplateItemDelegate() override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
};
}
#endif
diff --git a/templateparser/src/customtemplatesmenu.cpp b/templateparser/src/customtemplatesmenu.cpp
index f5b6eea6..a03773a0 100644
--- a/templateparser/src/customtemplatesmenu.cpp
+++ b/templateparser/src/customtemplatesmenu.cpp
@@ -1,216 +1,228 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@ubiz.ru>
*
* 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.
*/
#include "customtemplatesmenu.h"
#include "customtemplates.h"
#include "customtemplates_kfg.h"
#include "globalsettings_templateparser.h"
#include <KActionCollection>
#include <KActionMenu>
#include <QIcon>
#include <KLocalizedString>
#include <QMenu>
using namespace TemplateParser;
class TemplateParser::CustomTemplatesMenuPrivate
{
public:
CustomTemplatesMenuPrivate()
{
}
~CustomTemplatesMenuPrivate()
{
delete mCustomReplyActionMenu;
delete mCustomReplyAllActionMenu;
delete mCustomForwardActionMenu;
}
KActionCollection *mOwnerActionCollection = nullptr;
QStringList mCustomTemplates;
QList<QAction *> mCustomTemplateActions;
// Custom template actions menu
KActionMenu *mCustomReplyActionMenu = nullptr;
KActionMenu *mCustomReplyAllActionMenu = nullptr;
KActionMenu *mCustomForwardActionMenu = nullptr;
};
CustomTemplatesMenu::CustomTemplatesMenu(QWidget *owner, KActionCollection *ac)
: d(new TemplateParser::CustomTemplatesMenuPrivate)
{
d->mOwnerActionCollection = ac;
d->mCustomForwardActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-forward-custom")),
i18n("With Custom Template"), owner);
d->mOwnerActionCollection->addAction(QStringLiteral("custom_forward"), d->mCustomForwardActionMenu);
d->mCustomReplyActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-custom")),
i18n("Reply With Custom Template"), owner);
d->mOwnerActionCollection->addAction(QStringLiteral("custom_reply"), d->mCustomReplyActionMenu);
d->mCustomReplyAllActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("mail-reply-all-custom")),
i18n("Reply to All With Custom Template"), owner);
d->mOwnerActionCollection->addAction(QStringLiteral("custom_reply_all"), d->mCustomReplyAllActionMenu);
update();
}
CustomTemplatesMenu::~CustomTemplatesMenu()
{
clear();
delete d;
}
KActionMenu *CustomTemplatesMenu::replyActionMenu() const
{
return d->mCustomReplyActionMenu;
}
KActionMenu *CustomTemplatesMenu::replyAllActionMenu() const
{
return d->mCustomReplyAllActionMenu;
}
KActionMenu *CustomTemplatesMenu::forwardActionMenu() const
{
return d->mCustomForwardActionMenu;
}
void CustomTemplatesMenu::clear()
{
qDeleteAll(d->mCustomTemplateActions);
d->mCustomTemplateActions.clear();
d->mCustomReplyActionMenu->menu()->clear();
d->mCustomReplyAllActionMenu->menu()->clear();
d->mCustomForwardActionMenu->menu()->clear();
d->mCustomTemplates.clear();
}
void CustomTemplatesMenu::update()
{
clear();
const QStringList list = TemplateParserSettings::self()->customTemplates();
QStringList::const_iterator it = list.constBegin();
QStringList::const_iterator end = list.constEnd();
int idx = 0;
int replyc = 0;
int replyallc = 0;
int forwardc = 0;
for (; it != end; ++it) {
CTemplates t(*it);
d->mCustomTemplates.append(*it);
QString nameAction(*it);
nameAction.replace(QLatin1Char('&'), QStringLiteral("&&"));
const QString nameActionName = nameAction.replace(QLatin1Char(' '), QLatin1Char('_'));
QAction *action = nullptr;
switch (t.type()) {
case CustomTemplates::TReply:
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut());
d->mOwnerActionCollection->addAction(nameActionName, action);
- connect(action, &QAction::triggered, this, [this, idx] { slotReplySelected(idx); });
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotReplySelected(idx);
+ });
d->mCustomReplyActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++replyc;
break;
case CustomTemplates::TReplyAll:
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut());
d->mOwnerActionCollection->addAction(nameActionName, action);
- connect(action, &QAction::triggered, this, [this, idx] { slotReplyAllSelected(idx);});
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotReplyAllSelected(idx);
+ });
d->mCustomReplyAllActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++replyallc;
break;
case CustomTemplates::TForward:
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
d->mOwnerActionCollection->addAction(nameActionName, action);
d->mOwnerActionCollection->setDefaultShortcut(action, t.shortcut());
- connect(action, &QAction::triggered, this, [this, idx] { slotForwardSelected(idx); });
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotForwardSelected(idx);
+ });
d->mCustomForwardActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++forwardc;
break;
case CustomTemplates::TUniversal:
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
d->mOwnerActionCollection->addAction(nameActionName, action);
- connect(action, &QAction::triggered, this, [this, idx] { slotReplySelected(idx); });
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotReplySelected(idx);
+ });
d->mCustomReplyActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++replyc;
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
- connect(action, &QAction::triggered, this, [this, idx] { slotReplyAllSelected(idx);});
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotReplyAllSelected(idx);
+ });
d->mCustomReplyAllActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++replyallc;
action = new QAction(nameAction, d->mOwnerActionCollection); //krazy:exclude=tipsandthis
- connect(action, &QAction::triggered, this, [this, idx] { slotForwardSelected(idx); });
+ connect(action, &QAction::triggered, this, [this, idx] {
+ slotForwardSelected(idx);
+ });
d->mCustomForwardActionMenu->addAction(action);
d->mCustomTemplateActions.append(action);
++forwardc;
break;
}
++idx;
}
if (!replyc) {
QAction *noAction
= d->mCustomReplyActionMenu->menu()->addAction(i18n("(no custom templates)"));
noAction->setEnabled(false);
d->mCustomReplyActionMenu->setEnabled(false);
}
if (!replyallc) {
QAction *noAction
= d->mCustomReplyAllActionMenu->menu()->addAction(i18n("(no custom templates)"));
noAction->setEnabled(false);
d->mCustomReplyAllActionMenu->setEnabled(false);
}
if (!forwardc) {
QAction *noAction
= d->mCustomForwardActionMenu->menu()->addAction(i18n("(no custom templates)"));
noAction->setEnabled(false);
d->mCustomForwardActionMenu->setEnabled(false);
}
}
void CustomTemplatesMenu::slotReplySelected(int idx)
{
Q_EMIT replyTemplateSelected(d->mCustomTemplates.at(idx));
}
void CustomTemplatesMenu::slotReplyAllSelected(int idx)
{
Q_EMIT replyAllTemplateSelected(d->mCustomTemplates.at(idx));
}
void CustomTemplatesMenu::slotForwardSelected(int idx)
{
Q_EMIT forwardTemplateSelected(d->mCustomTemplates.at(idx));
}
diff --git a/templateparser/src/customtemplatesmenu.h b/templateparser/src/customtemplatesmenu.h
index 0e1e35d3..34602f83 100644
--- a/templateparser/src/customtemplatesmenu.h
+++ b/templateparser/src/customtemplatesmenu.h
@@ -1,62 +1,62 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@ubiz.ru>
*
* 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.
*/
#ifndef TEMPLATEPARSER_CUSTOMTEMPLATESMENU_H
#define TEMPLATEPARSER_CUSTOMTEMPLATESMENU_H
#include "templateparser_export.h"
#include <QObject>
class KActionCollection;
class KActionMenu;
namespace TemplateParser {
class CustomTemplatesMenuPrivate;
class TEMPLATEPARSER_EXPORT CustomTemplatesMenu : public QObject
{
Q_OBJECT
public:
- CustomTemplatesMenu(QWidget *parent, KActionCollection *ac);
+ explicit CustomTemplatesMenu(QWidget *parent, KActionCollection *ac);
~CustomTemplatesMenu();
KActionMenu *replyActionMenu() const;
KActionMenu *replyAllActionMenu() const;
KActionMenu *forwardActionMenu() const;
public Q_SLOTS:
void update();
Q_SIGNALS:
void replyTemplateSelected(const QString &tmpl);
void replyAllTemplateSelected(const QString &tmpl);
void forwardTemplateSelected(const QString &tmpl);
private Q_SLOTS:
void slotReplySelected(int idx);
void slotReplyAllSelected(int idx);
void slotForwardSelected(int idx);
private:
void clear();
CustomTemplatesMenuPrivate *const d;
};
}
#endif
diff --git a/templateparser/src/templateconvertcommandjob.cpp b/templateparser/src/templateconvertcommandjob.cpp
index 1a9a7a1b..e924add6 100644
--- a/templateparser/src/templateconvertcommandjob.cpp
+++ b/templateparser/src/templateconvertcommandjob.cpp
@@ -1,58 +1,56 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#include "templateconvertcommandjob.h"
using namespace TemplateParser;
TemplateConvertCommandJob::TemplateConvertCommandJob(QObject *parent)
: QObject(parent)
{
-
}
TemplateConvertCommandJob::~TemplateConvertCommandJob()
{
-
}
QString TemplateConvertCommandJob::convertText()
{
return {};
}
QString TemplateConvertCommandJob::currentText() const
{
return mCurrentText;
}
void TemplateConvertCommandJob::setCurrentText(const QString &currentText)
{
mCurrentText = currentText;
}
KMime::Message::Ptr TemplateConvertCommandJob::originalMessage() const
{
return mOriginalMessage;
}
void TemplateConvertCommandJob::setOriginalMessage(const KMime::Message::Ptr &originalMessage)
{
mOriginalMessage = originalMessage;
}
diff --git a/templateparser/src/templateconvertcommandjob.h b/templateparser/src/templateconvertcommandjob.h
index 6ab0724b..aac36af9 100644
--- a/templateparser/src/templateconvertcommandjob.h
+++ b/templateparser/src/templateconvertcommandjob.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATECONVERTCOMMANDJOB_H
#define TEMPLATECONVERTCOMMANDJOB_H
#include <QObject>
#include "templateparser_export.h"
#include <KMime/Message>
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplateConvertCommandJob : public QObject
{
Q_OBJECT
public:
explicit TemplateConvertCommandJob(QObject *parent = nullptr);
~TemplateConvertCommandJob();
QString convertText();
QString currentText() const;
void setCurrentText(const QString &currentText);
KMime::Message::Ptr originalMessage() const;
void setOriginalMessage(const KMime::Message::Ptr &originalMessage);
private:
QString mCurrentText;
KMime::Message::Ptr mOriginalMessage;
};
}
#endif // TEMPLATECONVERTCOMMANDJOB_H
diff --git a/templateparser/src/templateextracthtmlelementwebengineview.cpp b/templateparser/src/templateextracthtmlelementwebengineview.cpp
index e335b17a..c57b217d 100644
--- a/templateparser/src/templateextracthtmlelementwebengineview.cpp
+++ b/templateparser/src/templateextracthtmlelementwebengineview.cpp
@@ -1,119 +1,119 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateextracthtmlelementwebengineview.h"
#include "templateparser_debug.h"
#include "templatewebenginepage.h"
#include <QWebEngineScript>
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFun)(Arg);
void operator()(Arg result)
{
(receiver->*memberFun)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
return wrapper;
}
using namespace TemplateParser;
TemplateExtractHtmlElementWebEngineView::TemplateExtractHtmlElementWebEngineView(QWidget *parent)
: QWebEngineView(parent)
{
mPage = new TemplateWebEnginePage(this);
setPage(mPage);
connect(mPage, &TemplateWebEnginePage::loadFinished, this, &TemplateExtractHtmlElementWebEngineView::slotLoadFinished);
}
TemplateExtractHtmlElementWebEngineView::~TemplateExtractHtmlElementWebEngineView()
{
}
void TemplateExtractHtmlElementWebEngineView::clear()
{
mBodyElement.clear();
mHeaderElement.clear();
mHtmlElement.clear();
}
void TemplateExtractHtmlElementWebEngineView::setHtmlContent(const QString &html)
{
clear();
mHtmlElement = html;
setHtml(html);
}
QString extractHeaderBodyScript()
{
const QString source = QStringLiteral("(function() {"
"var res = {"
" body: document.getElementsByTagName('body')[0].innerHTML,"
" header: document.getElementsByTagName('head')[0].innerHTML"
"};"
"return res;"
"})()");
return source;
}
void TemplateExtractHtmlElementWebEngineView::slotLoadFinished(bool success)
{
if (success) {
mPage->runJavaScript(extractHeaderBodyScript(),
(QWebEngineScript::UserWorld + 2),
invoke(this, &TemplateExtractHtmlElementWebEngineView::handleHtmlInfo));
} else {
Q_EMIT loadContentDone(false);
}
}
void TemplateExtractHtmlElementWebEngineView::handleHtmlInfo(const QVariant &result)
{
if (result.isValid()) {
const QVariantMap map = result.toMap();
mBodyElement = map.value(QStringLiteral("body")).toString();
mHeaderElement = map.value(QStringLiteral("header")).toString();
Q_EMIT loadContentDone(true);
} else {
qCWarning(TEMPLATEPARSER_LOG) << "Impossible to get value";
Q_EMIT loadContentDone(false);
}
}
QString TemplateExtractHtmlElementWebEngineView::htmlElement() const
{
return mHtmlElement;
}
QString TemplateExtractHtmlElementWebEngineView::headerElement() const
{
return mHeaderElement;
}
QString TemplateExtractHtmlElementWebEngineView::bodyElement() const
{
return mBodyElement;
}
diff --git a/templateparser/src/templateextracthtmlelementwebengineview.h b/templateparser/src/templateextracthtmlelementwebengineview.h
index 33202f2a..853d3221 100644
--- a/templateparser/src/templateextracthtmlelementwebengineview.h
+++ b/templateparser/src/templateextracthtmlelementwebengineview.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H
#define TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H
#include <QWebEngineView>
#include "templateparser_private_export.h"
namespace TemplateParser {
class TemplateWebEnginePage;
class TEMPLATEPARSER_TESTS_EXPORT TemplateExtractHtmlElementWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
explicit TemplateExtractHtmlElementWebEngineView(QWidget *parent = nullptr);
~TemplateExtractHtmlElementWebEngineView();
QString bodyElement() const;
QString headerElement() const;
QString htmlElement() const;
void setHtmlContent(const QString &html);
Q_SIGNALS:
void loadContentDone(bool success);
private:
void clear();
void slotLoadFinished(bool success);
void handleHtmlInfo(const QVariant &result);
QString mBodyElement;
QString mHeaderElement;
QString mHtmlElement;
TemplateWebEnginePage *mPage = nullptr;
};
}
#endif // TEMPLATEEXTRACTHTMLELEMENTWEBENGINEVIEW_H
diff --git a/templateparser/src/templateparser_private_export.h b/templateparser/src/templateparser_private_export.h
index 87c8ed45..3fae31f4 100644
--- a/templateparser/src/templateparser_private_export.h
+++ b/templateparser/src/templateparser_private_export.h
@@ -1,34 +1,34 @@
/* This file is part of the KDE project
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSERPRIVATE_EXPORT_H
#define TEMPLATEPARSERPRIVATE_EXPORT_H
#include "templateparser_export.h"
/* Classes which are exported only for unit tests */
#ifdef BUILD_TESTING
# ifndef TEMPLATEPARSER_TESTS_EXPORT
# define TEMPLATEPARSER_TESTS_EXPORT TEMPLATEPARSER_EXPORT
# endif
#else /* not compiling tests */
# define TEMPLATEPARSER_TESTS_EXPORT
#endif
#endif
diff --git a/templateparser/src/templateparseremailaddressrequesterbase.cpp b/templateparser/src/templateparseremailaddressrequesterbase.cpp
index 358b35c7..b6fbb27c 100644
--- a/templateparser/src/templateparseremailaddressrequesterbase.cpp
+++ b/templateparser/src/templateparseremailaddressrequesterbase.cpp
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparseremailaddressrequesterbase.h"
using namespace TemplateParser;
TemplateParserEmailAddressRequesterBase::TemplateParserEmailAddressRequesterBase(QWidget *parent)
: QWidget(parent)
{
}
TemplateParserEmailAddressRequesterBase::~TemplateParserEmailAddressRequesterBase()
{
}
QString TemplateParserEmailAddressRequesterBase::text() const
{
return {};
}
void TemplateParserEmailAddressRequesterBase::setText(const QString &str)
{
Q_UNUSED(str);
}
void TemplateParserEmailAddressRequesterBase::clear()
{
}
diff --git a/templateparser/src/templateparseremailaddressrequesterbase.h b/templateparser/src/templateparseremailaddressrequesterbase.h
index 482f14f8..660787e1 100644
--- a/templateparser/src/templateparseremailaddressrequesterbase.h
+++ b/templateparser/src/templateparseremailaddressrequesterbase.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREMAILADDRESSREQUESTERBASE_H
#define TEMPLATEPARSEREMAILADDRESSREQUESTERBASE_H
#include <QWidget>
#include "templateparser_export.h"
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplateParserEmailAddressRequesterBase : public QWidget
{
Q_OBJECT
public:
explicit TemplateParserEmailAddressRequesterBase(QWidget *parent = nullptr);
~TemplateParserEmailAddressRequesterBase();
virtual QString text() const;
virtual void setText(const QString &str);
virtual void clear();
Q_SIGNALS:
void textChanged();
};
}
#endif // TEMPLATEPARSEREMAILADDRESSREQUESTERBASE_H
diff --git a/templateparser/src/templateparseremailaddressrequesterinterfacewidget.cpp b/templateparser/src/templateparseremailaddressrequesterinterfacewidget.cpp
index 3f9cf07f..a6359ad8 100644
--- a/templateparser/src/templateparseremailaddressrequesterinterfacewidget.cpp
+++ b/templateparser/src/templateparseremailaddressrequesterinterfacewidget.cpp
@@ -1,67 +1,67 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparseremailaddressrequesterinterfacewidget.h"
#include <QHBoxLayout>
#include <TemplateParser/TemplateParserEmailAddressRequesterBase>
#include "templateparseremailaddressrequesterlineedit.h"
#include <KPluginLoader>
#include <KPluginFactory>
using namespace TemplateParser;
TemplateParserEmailAddressRequesterInterfaceWidget::TemplateParserEmailAddressRequesterInterfaceWidget(QWidget *parent)
: QWidget(parent)
, mTemplateParserEmailBase(nullptr)
{
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setObjectName(QStringLiteral("mainlayout"));
- mainLayout->setMargin(0);
+ mainLayout->setContentsMargins(0, 0, 0, 0);
initializeEmailWidget();
mainLayout->addWidget(mTemplateParserEmailBase);
}
void TemplateParserEmailAddressRequesterInterfaceWidget::initializeEmailWidget()
{
KPluginLoader loader(QStringLiteral("templateparser/templateparseraddressrequesterplugin"));
KPluginFactory *factory = loader.factory();
if (factory) {
mTemplateParserEmailBase = factory->create<TemplateParser::TemplateParserEmailAddressRequesterBase>(this);
} else {
mTemplateParserEmailBase = new TemplateParser::TemplateParserEmailAddressRequesterLineEdit(this);
}
mTemplateParserEmailBase->setObjectName(QStringLiteral("templateparseremailbase"));
connect(mTemplateParserEmailBase, &TemplateParserEmailAddressRequesterBase::textChanged,
this, &TemplateParserEmailAddressRequesterInterfaceWidget::textChanged);
}
QString TemplateParserEmailAddressRequesterInterfaceWidget::text() const
{
return mTemplateParserEmailBase->text();
}
void TemplateParserEmailAddressRequesterInterfaceWidget::setText(const QString &str)
{
mTemplateParserEmailBase->setText(str);
}
void TemplateParserEmailAddressRequesterInterfaceWidget::clear()
{
mTemplateParserEmailBase->clear();
}
diff --git a/templateparser/src/templateparseremailaddressrequesterinterfacewidget.h b/templateparser/src/templateparseremailaddressrequesterinterfacewidget.h
index 4d39c124..cd00852b 100644
--- a/templateparser/src/templateparseremailaddressrequesterinterfacewidget.h
+++ b/templateparser/src/templateparseremailaddressrequesterinterfacewidget.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGET_H
#define TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGET_H
#include <QWidget>
#include "templateparser_private_export.h"
namespace TemplateParser {
class TemplateParserEmailAddressRequesterBase;
class TEMPLATEPARSER_TESTS_EXPORT TemplateParserEmailAddressRequesterInterfaceWidget : public QWidget
{
Q_OBJECT
public:
explicit TemplateParserEmailAddressRequesterInterfaceWidget(QWidget *parent = nullptr);
~TemplateParserEmailAddressRequesterInterfaceWidget() = default;
QString text() const;
void setText(const QString &str);
void clear();
Q_SIGNALS:
void textChanged();
private:
void initializeEmailWidget();
TemplateParser::TemplateParserEmailAddressRequesterBase *mTemplateParserEmailBase = nullptr;
};
}
#endif // TEMPLATEPARSEREMAILADDRESSREQUESTERINTERFACEWIDGET_H
diff --git a/templateparser/src/templateparseremailaddressrequesterlineedit.cpp b/templateparser/src/templateparseremailaddressrequesterlineedit.cpp
index b5da8a41..73efcec6 100644
--- a/templateparser/src/templateparseremailaddressrequesterlineedit.cpp
+++ b/templateparser/src/templateparseremailaddressrequesterlineedit.cpp
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparseremailaddressrequesterlineedit.h"
#include <QHBoxLayout>
#include <QLineEdit>
using namespace TemplateParser;
TemplateParserEmailAddressRequesterLineEdit::TemplateParserEmailAddressRequesterLineEdit(QWidget *parent)
: TemplateParser::TemplateParserEmailAddressRequesterBase(parent)
{
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setObjectName(QStringLiteral("mainlayout"));
- mainLayout->setMargin(0);
+ mainLayout->setContentsMargins(0, 0, 0, 0);
mLineEdit = new QLineEdit(this);
mLineEdit->setObjectName(QStringLiteral("lineedit"));
mainLayout->addWidget(mLineEdit);
connect(mLineEdit, &QLineEdit::textChanged, this, &TemplateParserEmailAddressRequesterLineEdit::textChanged);
}
TemplateParserEmailAddressRequesterLineEdit::~TemplateParserEmailAddressRequesterLineEdit()
{
disconnect(mLineEdit, &QLineEdit::textChanged, this, &TemplateParserEmailAddressRequesterLineEdit::textChanged);
}
QString TemplateParserEmailAddressRequesterLineEdit::text() const
{
return mLineEdit->text();
}
void TemplateParserEmailAddressRequesterLineEdit::setText(const QString &str)
{
mLineEdit->setText(str);
}
void TemplateParserEmailAddressRequesterLineEdit::clear()
{
mLineEdit->clear();
}
diff --git a/templateparser/src/templateparseremailaddressrequesterlineedit.h b/templateparser/src/templateparseremailaddressrequesterlineedit.h
index 23b7dbc0..6439c3d3 100644
--- a/templateparser/src/templateparseremailaddressrequesterlineedit.h
+++ b/templateparser/src/templateparseremailaddressrequesterlineedit.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDIT_H
#define TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDIT_H
#include <TemplateParser/TemplateParserEmailAddressRequesterBase>
#include "templateparser_private_export.h"
class QLineEdit;
namespace TemplateParser {
class TEMPLATEPARSER_TESTS_EXPORT TemplateParserEmailAddressRequesterLineEdit : public TemplateParser::TemplateParserEmailAddressRequesterBase
{
Q_OBJECT
public:
explicit TemplateParserEmailAddressRequesterLineEdit(QWidget *parent = nullptr);
~TemplateParserEmailAddressRequesterLineEdit() override;
QString text() const override;
void setText(const QString &str) override;
void clear() override;
private:
QLineEdit *mLineEdit = nullptr;
};
}
#endif // TEMPLATEPARSEREMAILADDRESSREQUESTERLINEEDIT_H
diff --git a/templateparser/src/templateparserextracthtmlinfo.cpp b/templateparser/src/templateparserextracthtmlinfo.cpp
index 86529798..f7478f38 100644
--- a/templateparser/src/templateparserextracthtmlinfo.cpp
+++ b/templateparser/src/templateparserextracthtmlinfo.cpp
@@ -1,97 +1,99 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparserextracthtmlinfo.h"
#include "templateextracthtmlelementwebengineview.h"
#include "templatewebengineview.h"
#include "templateparser_debug.h"
using namespace TemplateParser;
TemplateParserExtractHtmlInfo::TemplateParserExtractHtmlInfo(QObject *parent)
: QObject(parent)
- , mTemplateWebEngineView(nullptr)
- , mExtractHtmlElementWebEngineView(nullptr)
{
}
TemplateParserExtractHtmlInfo::~TemplateParserExtractHtmlInfo()
{
- delete mTemplateWebEngineView;
- delete mExtractHtmlElementWebEngineView;
+ if (mTemplateWebEngineView) {
+ mTemplateWebEngineView->deleteLater();
+ }
+ if (mExtractHtmlElementWebEngineView) {
+ mExtractHtmlElementWebEngineView->deleteLater();
+ }
}
void TemplateParserExtractHtmlInfo::setHtmlForExtractingTextPlain(const QString &html)
{
mHtmlForExtractingTextPlain = html;
}
void TemplateParserExtractHtmlInfo::setHtmlForExtractionHeaderAndBody(const QString &html)
{
mHtmlForExtractionHeaderAndBody = html;
}
void TemplateParserExtractHtmlInfo::setTemplate(const QString &str)
{
mTemplateStr = str;
}
void TemplateParserExtractHtmlInfo::start()
{
mResult.clear();
mResult.mTemplate = mTemplateStr;
if (!mHtmlForExtractingTextPlain.isEmpty()) {
mTemplateWebEngineView = new TemplateWebEngineView;
connect(mTemplateWebEngineView, &TemplateWebEngineView::loadContentDone, this, &TemplateParserExtractHtmlInfo::slotExtractToPlainTextFinished);
mTemplateWebEngineView->setHtmlContent(mHtmlForExtractingTextPlain);
} else {
qCDebug(TEMPLATEPARSER_LOG) << "html string is empty for extracting to plainText";
slotExtractToPlainTextFinished(false);
}
}
void TemplateParserExtractHtmlInfo::slotExtractToPlainTextFinished(bool success)
{
if (success) {
mResult.mPlainText = mTemplateWebEngineView->plainText();
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Impossible to extract plaintext";
}
if (!mHtmlForExtractionHeaderAndBody.isEmpty()) {
mExtractHtmlElementWebEngineView = new TemplateExtractHtmlElementWebEngineView;
connect(mExtractHtmlElementWebEngineView, &TemplateExtractHtmlElementWebEngineView::loadContentDone, this, &TemplateParserExtractHtmlInfo::slotExtractHtmlElementFinished);
mExtractHtmlElementWebEngineView->setHtmlContent(mHtmlForExtractionHeaderAndBody);
} else {
qCDebug(TEMPLATEPARSER_LOG) << "html string is empty for extracting to header and body";
slotExtractHtmlElementFinished(false);
}
}
void TemplateParserExtractHtmlInfo::slotExtractHtmlElementFinished(bool success)
{
if (success) {
mResult.mBodyElement = mExtractHtmlElementWebEngineView->bodyElement();
mResult.mHeaderElement = mExtractHtmlElementWebEngineView->headerElement();
mResult.mHtmlElement = mExtractHtmlElementWebEngineView->htmlElement();
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Impossible to extract html element";
}
Q_EMIT finished(mResult);
deleteLater();
}
diff --git a/templateparser/src/templateparserextracthtmlinfo.h b/templateparser/src/templateparserextracthtmlinfo.h
index 6a7edc89..faeb2259 100644
--- a/templateparser/src/templateparserextracthtmlinfo.h
+++ b/templateparser/src/templateparserextracthtmlinfo.h
@@ -1,61 +1,61 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREXTRACTHTMLINFO_H
#define TEMPLATEPARSEREXTRACTHTMLINFO_H
#include <QObject>
#include "templateparser_private_export.h"
#include "templateparserextracthtmlinforesult.h"
namespace TemplateParser {
class TemplateWebEngineView;
class TemplateExtractHtmlElementWebEngineView;
class TEMPLATEPARSER_TESTS_EXPORT TemplateParserExtractHtmlInfo : public QObject
{
Q_OBJECT
public:
explicit TemplateParserExtractHtmlInfo(QObject *parent = nullptr);
~TemplateParserExtractHtmlInfo();
void setHtmlForExtractingTextPlain(const QString &html);
void setHtmlForExtractionHeaderAndBody(const QString &html);
void setTemplate(const QString &str);
void start();
Q_SIGNALS:
void finished(const TemplateParserExtractHtmlInfoResult &result);
private:
void slotExtractHtmlElementFinished(bool success);
void slotExtractToPlainTextFinished(bool success);
TemplateParserExtractHtmlInfoResult mResult;
QString mHtmlForExtractingTextPlain;
QString mHtmlForExtractionHeaderAndBody;
QString mTemplateStr;
TemplateWebEngineView *mTemplateWebEngineView = nullptr;
TemplateExtractHtmlElementWebEngineView *mExtractHtmlElementWebEngineView = nullptr;
};
}
#endif // TEMPLATEPARSEREXTRACTHTMLINFO_H
diff --git a/templateparser/src/templateparserextracthtmlinforesult.cpp b/templateparser/src/templateparserextracthtmlinforesult.cpp
index 106dabf7..e946a6b5 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.cpp
+++ b/templateparser/src/templateparserextracthtmlinforesult.cpp
@@ -1,29 +1,29 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparserextracthtmlinforesult.h"
void TemplateParserExtractHtmlInfoResult::clear()
{
mPlainText.clear();
mBodyElement.clear();
mHeaderElement.clear();
mHtmlElement.clear();
mTemplate.clear();
}
diff --git a/templateparser/src/templateparserextracthtmlinforesult.h b/templateparser/src/templateparserextracthtmlinforesult.h
index 1026c0db..8670fd84 100644
--- a/templateparser/src/templateparserextracthtmlinforesult.h
+++ b/templateparser/src/templateparserextracthtmlinforesult.h
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSEREXTRACTHTMLINFORESULT_H
#define TEMPLATEPARSEREXTRACTHTMLINFORESULT_H
#include "templateparser_export.h"
#include <QObject>
struct TEMPLATEPARSER_EXPORT TemplateParserExtractHtmlInfoResult {
void clear();
QString mBodyElement;
QString mHeaderElement;
QString mHtmlElement;
QString mPlainText;
QString mTemplate;
};
Q_DECLARE_METATYPE(TemplateParserExtractHtmlInfoResult)
#endif // TEMPLATEPARSEREXTRACTHTMLINFORESULT_H
diff --git a/templateparser/src/templateparserjob.cpp b/templateparser/src/templateparserjob.cpp
index 517c264d..a93799d5 100644
--- a/templateparser/src/templateparserjob.cpp
+++ b/templateparser/src/templateparserjob.cpp
@@ -1,1611 +1,1645 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templateparserjob.h"
#include "templateparserjob_p.h"
#include "templateparserextracthtmlinfo.h"
#include "globalsettings_templateparser.h"
#include "customtemplates_kfg.h"
#include "templatesconfiguration_kfg.h"
#include "templatesconfiguration.h"
#include <MessageCore/ImageCollector>
#include <MessageCore/StringUtil>
#include <MimeTreeParser/ObjectTreeParser>
+#include <MimeTreeParser/MessagePart>
#include <MimeTreeParser/SimpleObjectTreeSource>
#include <KIdentityManagement/Identity>
#include <KIdentityManagement/IdentityManager>
#include <KCharsets>
#include <KLocalizedString>
#include <KMessageBox>
#include <KProcess>
#include <KShell>
#include "templateparser_debug.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextCodec>
#include <QLocale>
namespace {
Q_DECL_CONSTEXPR inline int pipeTimeout()
{
return 15 * 1000;
}
static QTextCodec *selectCharset(const QStringList &charsets, const QString &text)
{
for (const QString &name : charsets) {
// We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because
// the former knows us-ascii is latin1.
bool ok = true;
QTextCodec *codec = nullptr;
if (name == QLatin1String("locale")) {
codec = QTextCodec::codecForLocale();
} else {
codec = KCharsets::charsets()->codecForName(name, ok);
}
if (!ok || !codec) {
qCWarning(TEMPLATEPARSER_LOG) << "Could not get text codec for charset" << name;
continue;
}
if (codec->canEncode(text)) {
// Special check for us-ascii (needed because us-ascii is not exactly latin1).
if (name == QLatin1String("us-ascii") && !KMime::isUsAscii(text)) {
continue;
}
qCDebug(TEMPLATEPARSER_LOG) << "Chosen charset" << name << codec->name();
return codec;
}
}
qCDebug(TEMPLATEPARSER_LOG) << "No appropriate charset found.";
return KCharsets::charsets()->codecForName(QStringLiteral("utf-8"));
}
}
using namespace TemplateParser;
TemplateParserJobPrivate::TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode)
: mMsg(amsg)
, mMode(amode)
{
mEmptySource = new MimeTreeParser::SimpleObjectTreeSource;
mEmptySource->setDecryptMessage(mAllowDecryption);
mOtp = new MimeTreeParser::ObjectTreeParser(mEmptySource);
mOtp->setAllowAsync(false);
}
TemplateParserJobPrivate::~TemplateParserJobPrivate()
{
delete mEmptySource;
delete mOtp;
}
void TemplateParserJobPrivate::setAllowDecryption(const bool allowDecryption)
{
mAllowDecryption = allowDecryption;
mEmptySource->setDecryptMessage(mAllowDecryption);
}
-
-TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode)
- : d(new TemplateParserJobPrivate(amsg, amode))
+TemplateParserJob::TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent)
+ : QObject(parent)
+ , d(new TemplateParserJobPrivate(amsg, amode))
{
-
}
TemplateParserJob::~TemplateParserJob() = default;
void TemplateParserJob::setSelection(const QString &selection)
{
d->mSelection = selection;
}
void TemplateParserJob::setAllowDecryption(const bool allowDecryption)
{
d->setAllowDecryption(allowDecryption);
}
bool TemplateParserJob::shouldStripSignature() const
{
// Only strip the signature when replying, it should be preserved when forwarding
return (d->mMode == Reply || d->mMode == ReplyAll) && TemplateParserSettings::self()->stripSignature();
}
void TemplateParserJob::setIdentityManager(KIdentityManagement::IdentityManager *ident)
{
d->m_identityManager = ident;
}
void TemplateParserJob::setCharsets(const QStringList &charsets)
{
d->mCharsets = charsets;
}
int TemplateParserJob::parseQuotes(const QString &prefix, const QString &str, QString &quote)
{
int pos = prefix.length();
int len;
const int str_len = str.length();
// Also allow the german lower double-quote sign as quote separator, not only
// the standard ASCII quote ("). This fixes bug 166728.
const QList< QChar > quoteChars = {QLatin1Char('"'), 0x201C};
QChar prev(QChar::Null);
pos++;
len = pos;
while (pos < str_len) {
const QChar c = str[pos];
pos++;
len++;
if (!prev.isNull()) {
quote.append(c);
prev = QChar::Null;
} else {
if (c == QLatin1Char('\\')) {
prev = c;
} else if (quoteChars.contains(c)) {
break;
} else {
quote.append(c);
}
}
}
return len;
}
QString TemplateParserJob::getFirstName(const QString &str)
{
// simple logic:
// if there is ',' in name, than format is 'Last, First'
// else format is 'First Last'
// last resort -- return 'name' from 'name@domain'
int sep_pos;
QString res;
if ((sep_pos = str.indexOf(QLatin1Char('@'))) > 0) {
int i;
for (i = (sep_pos - 1); i >= 0; --i) {
QChar c = str[i];
if (c.isLetterOrNumber()) {
res.prepend(c);
} else {
break;
}
}
} else if ((sep_pos = str.indexOf(QLatin1Char(','))) > 0) {
int i;
bool begin = false;
const int strLength(str.length());
for (i = sep_pos; i < strLength; ++i) {
QChar c = str[i];
if (c.isLetterOrNumber()) {
begin = true;
res.append(c);
} else if (begin) {
break;
}
}
} else {
int i;
const int strLength(str.length());
for (i = 0; i < strLength; ++i) {
QChar c = str[i];
if (c.isLetterOrNumber()) {
res.append(c);
} else {
break;
}
}
}
return res;
}
QString TemplateParserJob::getLastName(const QString &str)
{
// simple logic:
// if there is ',' in name, than format is 'Last, First'
// else format is 'First Last'
int sep_pos;
QString res;
if ((sep_pos = str.indexOf(QLatin1Char(','))) > 0) {
int i;
for (i = sep_pos; i >= 0; --i) {
QChar c = str[i];
if (c.isLetterOrNumber()) {
res.prepend(c);
} else {
break;
}
}
} else {
if ((sep_pos = str.indexOf(QLatin1Char(' '))) > 0) {
bool begin = false;
const int strLength(str.length());
for (int i = sep_pos; i < strLength; ++i) {
QChar c = str[i];
if (c.isLetterOrNumber()) {
begin = true;
res.append(c);
} else if (begin) {
break;
}
}
}
}
return res;
}
void TemplateParserJob::process(const KMime::Message::Ptr &aorig_msg, qint64 afolder)
{
if (aorig_msg == nullptr) {
qCDebug(TEMPLATEPARSER_LOG) << "aorig_msg == 0!";
Q_EMIT parsingDone(d->mForceCursorPosition);
+ deleteLater();
return;
}
d->mOrigMsg = aorig_msg;
d->mFolder = afolder;
const QString tmpl = findTemplate();
if (tmpl.isEmpty()) {
Q_EMIT parsingDone(d->mForceCursorPosition);
+ deleteLater();
return;
}
processWithTemplate(tmpl);
}
void TemplateParserJob::process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
{
d->mForceCursorPosition = false;
d->mOrigMsg = aorig_msg;
d->mFolder = afolder;
const QString tmpl = findCustomTemplate(tmplName);
processWithTemplate(tmpl);
}
void TemplateParserJob::processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder)
{
d->mIdentity = uoid;
process(aorig_msg, afolder);
}
+MimeTreeParser::MessagePart::Ptr toplevelTextNode(MimeTreeParser::MessagePart::Ptr messageTree)
+{
+ foreach (const auto &mp, messageTree->subParts()) {
+ auto text = mp.dynamicCast<MimeTreeParser::TextMessagePart>();
+ const auto attach = mp.dynamicCast<MimeTreeParser::AttachmentMessagePart>();
+ if (text && !attach) {
+ // TextMessagePart can have several subparts cause of PGP inline, we search for the first part with content
+ foreach (const auto &sub, mp->subParts()) {
+ if (!sub->text().trimmed().isEmpty()) {
+ return sub;
+ }
+ }
+ return text;
+ } else if (const auto html = mp.dynamicCast<MimeTreeParser::HtmlMessagePart>()) {
+ return html;
+ } else if (const auto alternative = mp.dynamicCast<MimeTreeParser::AlternativeMessagePart>()) {
+ return alternative;
+ } else {
+ auto ret = toplevelTextNode(mp);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+ return MimeTreeParser::MessagePart::Ptr();
+}
+
void TemplateParserJob::processWithTemplate(const QString &tmpl)
{
d->mOtp->parseObjectTree(d->mOrigMsg.data());
- TemplateParserExtractHtmlInfo *job = new TemplateParserExtractHtmlInfo(this);
- connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone);
+ const auto mp = toplevelTextNode(d->mOtp->parsedPart());
- QString plainText = d->mOtp->plainTextContent();
- if (plainText.isEmpty()) { //HTML-only mails
- plainText = d->mOtp->htmlContent();
- }
-
- job->setHtmlForExtractingTextPlain(plainText);
- job->setTemplate(tmpl);
-
- QString htmlElement = d->mOtp->htmlContent();
+ QString plainText = mp->plaintextContent();
+ QString htmlElement;
- if (htmlElement.isEmpty()) { //plain mails only
- QString htmlReplace =d->mOtp->plainTextContent().toHtmlEscaped();
+ if (mp->isHtml()) {
+ htmlElement = d->mOtp->htmlContent();
+ if (plainText.isEmpty()) { //HTML-only mails
+ plainText = htmlElement;
+ }
+ } else { //plain mails only
+ QString htmlReplace = plainText.toHtmlEscaped();
htmlReplace = htmlReplace.replace(QLatin1Char('\n'), QStringLiteral("<br />"));
htmlElement = QStringLiteral("<html><head></head><body>%1</body></html>\n").arg(htmlReplace);
}
+ TemplateParserExtractHtmlInfo *job = new TemplateParserExtractHtmlInfo(this);
+ connect(job, &TemplateParserExtractHtmlInfo::finished, this, &TemplateParserJob::slotExtractInfoDone);
+
+ job->setHtmlForExtractingTextPlain(plainText);
+ job->setTemplate(tmpl);
+
job->setHtmlForExtractionHeaderAndBody(htmlElement);
job->start();
}
void TemplateParserJob::slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result)
{
d->mExtractHtmlInfoResult = result;
const QString tmpl = d->mExtractHtmlInfoResult.mTemplate;
const int tmpl_len = tmpl.length();
QString plainBody, htmlBody;
bool dnl = false;
auto definedLocale = QLocale();
for (int i = 0; i < tmpl_len; ++i) {
QChar c = tmpl[i];
// qCDebug(TEMPLATEPARSER_LOG) << "Next char: " << c;
if (c == QLatin1Char('%')) {
const QString cmd = tmpl.mid(i + 1);
if (cmd.startsWith(QLatin1Char('-'))) {
// dnl
qCDebug(TEMPLATEPARSER_LOG) << "Command: -";
dnl = true;
i += 1;
} else if (cmd.startsWith(QLatin1String("REM="))) {
// comments
qCDebug(TEMPLATEPARSER_LOG) << "Command: REM=";
QString q;
const int len = parseQuotes(QStringLiteral("REM="), cmd, q);
i += len;
} else if (cmd.startsWith(QLatin1String("LANGUAGE="))) {
QString q;
const int len = parseQuotes(QStringLiteral("LANGUAGE="), cmd, q);
i += len;
if (!q.isEmpty()) {
definedLocale = QLocale(q);
}
} else if (cmd.startsWith(QLatin1String("DICTIONARYLANGUAGE="))) {
QString q;
const int len = parseQuotes(QStringLiteral("DICTIONARYLANGUAGE="), cmd, q);
i += len;
if (!q.isEmpty()) {
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Dictionary");
header->fromUnicodeString(q, "utf-8");
d->mMsg->setHeader(header);
}
} else if (cmd.startsWith(QLatin1String("INSERT=")) || cmd.startsWith(QLatin1String("PUT="))) {
QString q;
int len = 0;
if (cmd.startsWith(QLatin1String("INSERT="))) {
// insert content of specified file as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: INSERT=";
len = parseQuotes(QStringLiteral("INSERT="), cmd, q);
} else {
// insert content of specified file as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: PUT=";
len = parseQuotes(QStringLiteral("PUT="), cmd, q);
}
i += len;
QString path = KShell::tildeExpand(q);
QFileInfo finfo(path);
if (finfo.isRelative()) {
path = QDir::homePath() + QLatin1Char('/') + q;
}
QFile file(path);
if (file.open(QIODevice::ReadOnly)) {
const QByteArray content = file.readAll();
const QString str = QString::fromLocal8Bit(content.constData(), content.size());
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (d->mDebug) {
KMessageBox::error(
nullptr,
i18nc("@info",
"Cannot insert content from file %1: %2", path, file.errorString()));
}
} else if (cmd.startsWith(QLatin1String("SYSTEM="))) {
// insert content of specified file as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: SYSTEM=";
QString q;
const int len = parseQuotes(QStringLiteral("SYSTEM="), cmd, q);
i += len;
const QString pipe_cmd = q;
const QString str = pipe(pipe_cmd, QString());
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("QUOTEPIPE="))) {
// pipe message body through command and insert it as quotation
qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTEPIPE=";
QString q;
const int len = parseQuotes(QStringLiteral("QUOTEPIPE="), cmd, q);
i += len;
const QString pipe_cmd = q;
if (d->mOrigMsg) {
const QString plainStr
= pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
QString plainQuote = quotedPlainText(plainStr);
if (plainQuote.endsWith(QLatin1Char('\n'))) {
plainQuote.chop(1);
}
plainBody.append(plainQuote);
const QString htmlStr
= pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
const QString htmlQuote = quotedHtmlText(htmlStr);
htmlBody.append(htmlQuote);
}
} else if (cmd.startsWith(QLatin1String("QUOTE"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: QUOTE";
i += strlen("QUOTE");
if (d->mOrigMsg) {
QString plainQuote
= quotedPlainText(plainMessageText(shouldStripSignature(), SelectionAllowed));
if (plainQuote.endsWith(QLatin1Char('\n'))) {
plainQuote.chop(1);
}
plainBody.append(plainQuote);
const QString htmlQuote
= quotedHtmlText(htmlMessageText(shouldStripSignature(), SelectionAllowed));
htmlBody.append(htmlQuote);
}
} else if (cmd.startsWith(QLatin1String("FORCEDPLAIN"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDPLAIN";
d->mQuotes = ReplyAsPlain;
i += strlen("FORCEDPLAIN");
} else if (cmd.startsWith(QLatin1String("FORCEDHTML"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FORCEDHTML";
d->mQuotes = ReplyAsHtml;
i += strlen("FORCEDHTML");
} else if (cmd.startsWith(QLatin1String("QHEADERS"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: QHEADERS";
i += strlen("QHEADERS");
if (d->mOrigMsg) {
QString plainQuote
= quotedPlainText(QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg)));
if (plainQuote.endsWith(QLatin1Char('\n'))) {
plainQuote.chop(1);
}
plainBody.append(plainQuote);
const QString htmlQuote
= quotedHtmlText(QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg)));
const QString str = plainToHtml(htmlQuote);
htmlBody.append(str);
}
} else if (cmd.startsWith(QLatin1String("HEADERS"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADERS";
i += strlen("HEADERS");
if (d->mOrigMsg) {
const QString str = QString::fromLatin1(MessageCore::StringUtil::headerAsSendableString(d->mOrigMsg));
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("TEXTPIPE="))) {
// pipe message body through command and insert it as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXTPIPE=";
QString q;
const int len = parseQuotes(QStringLiteral("TEXTPIPE="), cmd, q);
i += len;
const QString pipe_cmd = q;
if (d->mOrigMsg) {
const QString plainStr
= pipe(pipe_cmd, plainMessageText(shouldStripSignature(), NoSelectionAllowed));
plainBody.append(plainStr);
const QString htmlStr
= pipe(pipe_cmd, htmlMessageText(shouldStripSignature(), NoSelectionAllowed));
htmlBody.append(htmlStr);
}
} else if (cmd.startsWith(QLatin1String("MSGPIPE="))) {
// pipe full message through command and insert result as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGPIPE=";
QString q;
const int len = parseQuotes(QStringLiteral("MSGPIPE="), cmd, q);
i += len;
if (d->mOrigMsg) {
QString pipe_cmd = q;
const QString str = pipe(pipe_cmd, QString::fromLatin1(d->mOrigMsg->encodedContent()));
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("BODYPIPE="))) {
// pipe message body generated so far through command and insert result as is
qCDebug(TEMPLATEPARSER_LOG) << "Command: BODYPIPE=";
QString q;
const int len = parseQuotes(QStringLiteral("BODYPIPE="), cmd, q);
i += len;
const QString pipe_cmd = q;
const QString plainStr = pipe(pipe_cmd, plainBody);
plainBody.append(plainStr);
const QString htmlStr = pipe(pipe_cmd, htmlBody);
const QString body = plainToHtml(htmlStr);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("CLEARPIPE="))) {
// pipe message body generated so far through command and
// insert result as is replacing current body
qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEARPIPE=";
QString q;
const int len = parseQuotes(QStringLiteral("CLEARPIPE="), cmd, q);
i += len;
const QString pipe_cmd = q;
const QString plainStr = pipe(pipe_cmd, plainBody);
plainBody = plainStr;
const QString htmlStr = pipe(pipe_cmd, htmlBody);
htmlBody = htmlStr;
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos");
header->fromUnicodeString(QString::number(0), "utf-8");
d->mMsg->setHeader(header);
} else if (cmd.startsWith(QLatin1String("TEXT"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TEXT";
i += strlen("TEXT");
if (d->mOrigMsg) {
const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
plainBody.append(plainStr);
const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
htmlBody.append(htmlStr);
}
} else if (cmd.startsWith(QLatin1String("OTEXTSIZE"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXTSIZE";
i += strlen("OTEXTSIZE");
if (d->mOrigMsg) {
const QString str = QStringLiteral("%1").arg(d->mOrigMsg->body().length());
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTEXT"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTEXT";
i += strlen("OTEXT");
if (d->mOrigMsg) {
const QString plainStr = plainMessageText(shouldStripSignature(), NoSelectionAllowed);
plainBody.append(plainStr);
const QString htmlStr = htmlMessageText(shouldStripSignature(), NoSelectionAllowed);
htmlBody.append(htmlStr);
}
} else if (cmd.startsWith(QLatin1String("OADDRESSEESADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OADDRESSEESADDR";
i += strlen("OADDRESSEESADDR");
if (d->mOrigMsg) {
const QString to = d->mOrigMsg->to()->asUnicodeString();
const QString cc = d->mOrigMsg->cc()->asUnicodeString();
if (!to.isEmpty()) {
const QString toLine = i18nc("@item:intext email To", "To:") + QLatin1Char(' ') + to;
plainBody.append(toLine);
const QString body = plainToHtml(toLine);
htmlBody.append(body);
}
if (!to.isEmpty() && !cc.isEmpty()) {
plainBody.append(QLatin1Char('\n'));
const QString str = plainToHtml(QString(QLatin1Char('\n')));
htmlBody.append(str);
}
if (!cc.isEmpty()) {
const QString ccLine = i18nc("@item:intext email CC", "CC:") + QLatin1Char(' ') + cc;
plainBody.append(ccLine);
const QString str = plainToHtml(ccLine);
htmlBody.append(str);
}
}
} else if (cmd.startsWith(QLatin1String("CCADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: CCADDR";
i += strlen("CCADDR");
const QString str = d->mMsg->cc()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("CCNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: CCNAME";
i += strlen("CCNAME");
const QString str = d->mMsg->cc()->displayString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("CCFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: CCFNAME";
i += strlen("CCFNAME");
const QString str = d->mMsg->cc()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("CCLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: CCLNAME";
i += strlen("CCLNAME");
const QString str = d->mMsg->cc()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TOADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TOADDR";
i += strlen("TOADDR");
const QString str = d->mMsg->to()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TONAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TONAME";
i += strlen("TONAME");
const QString str = (d->mMsg->to()->displayString());
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TOFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TOFNAME";
i += strlen("TOFNAME");
const QString str = d->mMsg->to()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TOLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLNAME";
i += strlen("TOLNAME");
const QString str = d->mMsg->to()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TOLIST"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TOLIST";
i += strlen("TOLIST");
const QString str = d->mMsg->to()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("FROMADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMADDR";
i += strlen("FROMADDR");
const QString str = d->mMsg->from()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("FROMNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMNAME";
i += strlen("FROMNAME");
const QString str = d->mMsg->from()->displayString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("FROMFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMFNAME";
i += strlen("FROMFNAME");
const QString str = d->mMsg->from()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("FROMLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FROMLNAME";
i += strlen("FROMLNAME");
const QString str = d->mMsg->from()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("FULLSUBJECT")) || cmd.startsWith(QLatin1String("FULLSUBJ"))) {
if (cmd.startsWith(QLatin1String("FULLSUBJ"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJ";
i += strlen("FULLSUBJ");
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Command: FULLSUBJECT";
i += strlen("FULLSUBJECT");
}
const QString str = d->mMsg->subject()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("MSGID"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: MSGID";
i += strlen("MSGID");
const QString str = d->mMsg->messageID()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("OHEADER="))) {
// insert specified content of header from original message
qCDebug(TEMPLATEPARSER_LOG) << "Command: OHEADER=";
QString q;
const int len = parseQuotes(QStringLiteral("OHEADER="), cmd, q);
i += len;
if (d->mOrigMsg) {
const QString hdr = q;
QString str;
if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
str = hrdMsgOrigin->asUnicodeString();
}
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("HEADER="))) {
// insert specified content of header from current message
qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER=";
QString q;
const int len = parseQuotes(QStringLiteral("HEADER="), cmd, q);
i += len;
const QString hdr = q;
QString str;
if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
str = hrdMsgOrigin->asUnicodeString();
}
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("HEADER( "))) {
// insert specified content of header from current message
qCDebug(TEMPLATEPARSER_LOG) << "Command: HEADER(";
QRegExp re = QRegExp(QLatin1String("^HEADER\\((.+)\\)"));
re.setMinimal(true);
int res = re.indexIn(cmd);
if (res != 0) {
// something wrong
i += strlen("HEADER( ");
} else {
i += re.matchedLength();
const QString hdr = re.cap(1);
QString str;
if (auto hrdMsgOrigin = d->mOrigMsg->headerByType(hdr.toLocal8Bit().constData())) {
str = hrdMsgOrigin->asUnicodeString();
}
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OCCADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCADDR";
i += strlen("OCCADDR");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->cc()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OCCNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCNAME";
i += strlen("OCCNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->cc()->displayString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OCCFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCFNAME";
i += strlen("OCCFNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->cc()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OCCLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OCCLNAME";
i += strlen("OCCLNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->cc()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTOADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOADDR";
i += strlen("OTOADDR");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTONAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTONAME";
i += strlen("OTONAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->displayString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTOFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOFNAME";
i += strlen("OTOFNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTOLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLNAME";
i += strlen("OTOLNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTOLIST"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTOLIST";
i += strlen("OTOLIST");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTO"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTO";
i += strlen("OTO");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->to()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OFROMADDR"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMADDR";
i += strlen("OFROMADDR");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->from()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OFROMNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMNAME";
i += strlen("OFROMNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->from()->displayString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OFROMFNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMFNAME";
i += strlen("OFROMFNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->from()->displayString();
plainBody.append(getFirstName(str));
const QString body = plainToHtml(getFirstName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OFROMLNAME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFROMLNAME";
i += strlen("OFROMLNAME");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->from()->displayString();
plainBody.append(getLastName(str));
const QString body = plainToHtml(getLastName(str));
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OFULLSUBJECT")) || cmd.startsWith(QLatin1String("OFULLSUBJ"))) {
if (cmd.startsWith(QLatin1String("OFULLSUBJECT"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJECT";
i += strlen("OFULLSUBJECT");
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OFULLSUBJ";
i += strlen("OFULLSUBJ");
}
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->subject()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OMSGID"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OMSGID";
i += strlen("OMSGID");
if (d->mOrigMsg) {
const QString str = d->mOrigMsg->messageID()->asUnicodeString();
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("DATEEN"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: DATEEN";
i += strlen("DATEEN");
const QDateTime date = QDateTime::currentDateTime();
QLocale locale(QLocale::C);
const QString str = locale.toString(date.date(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("DATESHORT"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: DATESHORT";
i += strlen("DATESHORT");
const QDateTime date = QDateTime::currentDateTime();
const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("DATE"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: DATE";
i += strlen("DATE");
const QDateTime date = QDateTime::currentDateTime();
const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("DOW"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: DOW";
i += strlen("DOW");
const QDateTime date = QDateTime::currentDateTime();
const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TIMELONGEN"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONGEN";
i += strlen("TIMELONGEN");
const QDateTime date = QDateTime::currentDateTime();
QLocale locale(QLocale::C);
const QString str = locale.toString(date.time(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TIMELONG"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TIMELONG";
i += strlen("TIMELONG");
const QDateTime date = QDateTime::currentDateTime();
const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("TIME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: TIME";
i += strlen("TIME");
const QDateTime date = QDateTime::currentDateTime();
const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
} else if (cmd.startsWith(QLatin1String("ODATEEN"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATEEN";
i += strlen("ODATEEN");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = QLocale::c().toString(date.date(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("ODATESHORT"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATESHORT";
i += strlen("ODATESHORT");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = definedLocale.toString(date.date(), QLocale::ShortFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("ODATE"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: ODATE";
i += strlen("ODATE");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = definedLocale.toString(date.date(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("ODOW"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: ODOW";
i += strlen("ODOW");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = definedLocale.dayName(date.date().dayOfWeek(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTIMELONGEN"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONGEN";
i += strlen("OTIMELONGEN");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
QLocale locale(QLocale::C);
const QString str = locale.toString(date.time(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTIMELONG"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIMELONG";
i += strlen("OTIMELONG");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = definedLocale.toString(date.time(), QLocale::LongFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("OTIME"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: OTIME";
i += strlen("OTIME");
if (d->mOrigMsg) {
const QDateTime date = d->mOrigMsg->date()->dateTime().toLocalTime();
const QString str = definedLocale.toString(date.time(), QLocale::ShortFormat);
plainBody.append(str);
const QString body = plainToHtml(str);
htmlBody.append(body);
}
} else if (cmd.startsWith(QLatin1String("BLANK"))) {
// do nothing
qCDebug(TEMPLATEPARSER_LOG) << "Command: BLANK";
i += strlen("BLANK");
} else if (cmd.startsWith(QLatin1String("NOP"))) {
// do nothing
qCDebug(TEMPLATEPARSER_LOG) << "Command: NOP";
i += strlen("NOP");
} else if (cmd.startsWith(QLatin1String("CLEAR"))) {
// clear body buffer; not too useful yet
qCDebug(TEMPLATEPARSER_LOG) << "Command: CLEAR";
i += strlen("CLEAR");
plainBody.clear();
htmlBody.clear();
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos");
header->fromUnicodeString(QString::number(0), "utf-8");
d->mMsg->setHeader(header);
} else if (cmd.startsWith(QLatin1String("DEBUGOFF"))) {
// turn off debug
qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUGOFF";
i += strlen("DEBUGOFF");
d->mDebug = false;
} else if (cmd.startsWith(QLatin1String("DEBUG"))) {
// turn on debug
qCDebug(TEMPLATEPARSER_LOG) << "Command: DEBUG";
i += strlen("DEBUG");
d->mDebug = true;
} else if (cmd.startsWith(QLatin1String("CURSOR"))) {
// turn on debug
qCDebug(TEMPLATEPARSER_LOG) << "Command: CURSOR";
int oldI = i;
i += strlen("CURSOR");
KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-CursorPos");
header->fromUnicodeString(QString::number(plainBody.length()), "utf-8");
/* if template is:
* FOOBAR
* %CURSOR
*
* Make sure there is an empty line for the cursor otherwise it will be placed at the end of FOOBAR
*/
if (oldI > 0 && tmpl[ oldI - 1 ] == QLatin1Char('\n') && i == tmpl_len - 1) {
plainBody.append(QLatin1Char('\n'));
}
d->mMsg->setHeader(header);
d->mForceCursorPosition = true;
//FIXME HTML part for header remaining
} else if (cmd.startsWith(QLatin1String("SIGNATURE"))) {
qCDebug(TEMPLATEPARSER_LOG) << "Command: SIGNATURE";
i += strlen("SIGNATURE");
plainBody.append(getPlainSignature());
htmlBody.append(getHtmlSignature());
} else {
// wrong command, do nothing
plainBody.append(c);
htmlBody.append(c);
}
} else if (dnl && (c == QLatin1Char('\n') || c == QLatin1Char('\r'))) {
// skip
if ((tmpl.size() > i + 1)
&& ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r'))
|| (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) {
// skip one more
i += 1;
}
dnl = false;
} else {
plainBody.append(c);
if (c == QLatin1Char('\n') || c == QLatin1Char('\r')) {
htmlBody.append(QLatin1String("<br />"));
htmlBody.append(c);
if (tmpl.size() > i + 1
&& ((c == QLatin1Char('\n') && tmpl[i + 1] == QLatin1Char('\r'))
|| (c == QLatin1Char('\r') && tmpl[i + 1] == QLatin1Char('\n')))) {
htmlBody.append(tmpl[i + 1]);
plainBody.append(tmpl[i + 1]);
i += 1;
}
} else {
htmlBody.append(c);
}
}
}
// Clear the HTML body if FORCEDPLAIN has set ReplyAsPlain, OR if,
// there is no use of FORCED command but a configure setting has ReplyUsingHtml disabled,
// OR the original mail has no HTML part.
const KMime::Content *content = d->mOrigMsg->mainBodyPart("text/html");
if (d->mQuotes == ReplyAsPlain
|| (d->mQuotes != ReplyAsHtml && !TemplateParserSettings::self()->replyUsingHtml())
|| (!content || !content->hasContent())) {
htmlBody.clear();
} else {
makeValidHtml(htmlBody);
}
if (d->mMode == NewMessage && plainBody.isEmpty() && !d->mExtractHtmlInfoResult.mPlainText.isEmpty()) {
plainBody = d->mExtractHtmlInfoResult.mPlainText;
}
/*
if (d->mMode == NewMessage && htmlBody.isEmpty() && !d->mExtractHtmlInfoResult.mHtmlElement.isEmpty()) {
htmlBody = d->mExtractHtmlInfoResult.mHtmlElement;
}
*/
addProcessedBodyToMessage(plainBody, htmlBody);
Q_EMIT parsingDone(d->mForceCursorPosition);
+ deleteLater();
}
QString TemplateParserJob::getPlainSignature() const
{
const KIdentityManagement::Identity &identity
= d->m_identityManager->identityForUoid(d->mIdentity);
if (identity.isNull()) {
return QString();
}
KIdentityManagement::Signature signature
= const_cast<KIdentityManagement::Identity &>(identity).signature();
if (signature.type() == KIdentityManagement::Signature::Inlined
&& signature.isInlinedHtml()) {
return signature.toPlainText();
} else {
return signature.rawText();
}
}
// TODO If %SIGNATURE command is on, then override it with signature from
// "KMail configure->General->identity->signature".
// There should be no two signatures.
QString TemplateParserJob::getHtmlSignature() const
{
const KIdentityManagement::Identity &identity
= d->m_identityManager->identityForUoid(d->mIdentity);
if (identity.isNull()) {
return QString();
}
KIdentityManagement::Signature signature
= const_cast<KIdentityManagement::Identity &>(identity).signature();
if (!signature.isInlinedHtml()) {
signature = signature.rawText().toHtmlEscaped();
return signature.rawText().replace(QLatin1Char('\n'), QStringLiteral("<br />"));
}
return signature.rawText();
}
void TemplateParserJob::addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const
{
MessageCore::ImageCollector ic;
ic.collectImagesFrom(d->mOrigMsg.data());
// Now, delete the old content and set the new content, which
// is either only the new text or the new text with some attachments.
const auto parts = d->mMsg->contents();
for (KMime::Content *content : parts) {
d->mMsg->removeContent(content, true /*delete*/);
}
// Set To and CC from the template
if (!d->mTo.isEmpty()) {
d->mMsg->to()->fromUnicodeString(d->mMsg->to()->asUnicodeString() + QLatin1Char(',') + d->mTo, "utf-8");
}
if (!d->mCC.isEmpty()) {
d->mMsg->cc()->fromUnicodeString(d->mMsg->cc()->asUnicodeString() + QLatin1Char(',') + d->mCC, "utf-8");
}
d->mMsg->contentType()->clear(); // to get rid of old boundary
//const QByteArray boundary = KMime::multiPartBoundary();
KMime::Content *const mainTextPart
= htmlBody.isEmpty()
? createPlainPartContent(plainBody)
: createMultipartAlternativeContent(plainBody, htmlBody);
mainTextPart->assemble();
KMime::Content *textPart = mainTextPart;
if (!ic.images().empty()) {
textPart = createMultipartRelated(ic, mainTextPart);
textPart->assemble();
}
// If we have some attachments, create a multipart/mixed mail and
// add the normal body as well as the attachments
KMime::Content *mainPart = textPart;
if (d->mMode == Forward) {
auto attachments = d->mOrigMsg->attachments();
- attachments +=d->mOtp->nodeHelper()->attachmentsOfExtraContents();
+ attachments += d->mOtp->nodeHelper()->attachmentsOfExtraContents();
if (!attachments.isEmpty()) {
mainPart = createMultipartMixed(attachments, textPart);
mainPart->assemble();
}
}
d->mMsg->setBody(mainPart->encodedBody());
d->mMsg->setHeader(mainPart->contentType());
d->mMsg->setHeader(mainPart->contentTransferEncoding());
d->mMsg->assemble();
d->mMsg->parse();
}
KMime::Content *TemplateParserJob::createMultipartMixed(const QVector<KMime::Content *> &attachments, KMime::Content *textPart) const
{
KMime::Content *mixedPart = new KMime::Content(d->mMsg.data());
const QByteArray boundary = KMime::multiPartBoundary();
mixedPart->contentType()->setMimeType("multipart/mixed");
mixedPart->contentType()->setBoundary(boundary);
mixedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
mixedPart->addContent(textPart);
int attachmentNumber = 1;
for (KMime::Content *attachment : attachments) {
mixedPart->addContent(attachment);
// If the content type has no name or filename parameter, add one, since otherwise the name
// would be empty in the attachment view of the composer, which looks confusing
if (attachment->contentType(false)) {
if (!attachment->contentType()->hasParameter(QStringLiteral("name"))
&& !attachment->contentType()->hasParameter(QStringLiteral("filename"))) {
attachment->contentType()->setParameter(
QStringLiteral("name"), i18nc("@item:intext", "Attachment %1", attachmentNumber));
}
}
++attachmentNumber;
}
return mixedPart;
}
KMime::Content *TemplateParserJob::createMultipartRelated(const MessageCore::ImageCollector &ic, KMime::Content *mainTextPart) const
{
KMime::Content *relatedPart = new KMime::Content(d->mMsg.data());
const QByteArray boundary = KMime::multiPartBoundary();
relatedPart->contentType()->setMimeType("multipart/related");
relatedPart->contentType()->setBoundary(boundary);
relatedPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
relatedPart->addContent(mainTextPart);
for (KMime::Content *image : ic.images()) {
qCWarning(TEMPLATEPARSER_LOG) << "Adding" << image->contentID() << "as an embedded image";
relatedPart->addContent(image);
}
return relatedPart;
}
KMime::Content *TemplateParserJob::createPlainPartContent(const QString &plainBody) const
{
KMime::Content *textPart = new KMime::Content(d->mMsg.data());
textPart->contentType()->setMimeType("text/plain");
QTextCodec *charset = selectCharset(d->mCharsets, plainBody);
textPart->contentType()->setCharset(charset->name());
textPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
textPart->fromUnicodeString(plainBody);
return textPart;
}
KMime::Content *TemplateParserJob::createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody) const
{
KMime::Content *multipartAlternative = new KMime::Content(d->mMsg.data());
multipartAlternative->contentType()->setMimeType("multipart/alternative");
const QByteArray boundary = KMime::multiPartBoundary();
multipartAlternative->contentType()->setBoundary(boundary);
KMime::Content *textPart = createPlainPartContent(plainBody);
multipartAlternative->addContent(textPart);
KMime::Content *htmlPart = new KMime::Content(d->mMsg.data());
htmlPart->contentType()->setMimeType("text/html");
QTextCodec *charset = selectCharset(d->mCharsets, htmlBody);
htmlPart->contentType()->setCharset(charset->name());
htmlPart->contentTransferEncoding()->setEncoding(KMime::Headers::CE8Bit);
htmlPart->fromUnicodeString(htmlBody);
multipartAlternative->addContent(htmlPart);
return multipartAlternative;
}
QString TemplateParserJob::findCustomTemplate(const QString &tmplName)
{
CTemplates t(tmplName);
d->mTo = t.to();
d->mCC = t.cC();
const QString content = t.content();
if (!content.isEmpty()) {
return content;
} else {
return findTemplate();
}
}
QString TemplateParserJob::findTemplate()
{
// qCDebug(TEMPLATEPARSER_LOG) << "Trying to find template for mode" << mode;
QString tmpl;
qCDebug(TEMPLATEPARSER_LOG) << "Folder identify found:" << d->mFolder;
if (d->mFolder >= 0) { // only if a folder was found
QString fid = QString::number(d->mFolder);
Templates fconf(fid);
if (fconf.useCustomTemplates()) { // does folder use custom templates?
switch (d->mMode) {
case NewMessage:
tmpl = fconf.templateNewMessage();
break;
case Reply:
tmpl = fconf.templateReply();
break;
case ReplyAll:
tmpl = fconf.templateReplyAll();
break;
case Forward:
tmpl = fconf.templateForward();
break;
default:
qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
return QString();
}
d->mQuoteString = fconf.quoteString();
if (!tmpl.isEmpty()) {
return tmpl; // use folder-specific template
}
}
}
if (!d->mIdentity) { // find identity message belongs to
d->mIdentity = identityUoid(d->mMsg);
if (!d->mIdentity && d->mOrigMsg) {
d->mIdentity = identityUoid(d->mOrigMsg);
}
d->mIdentity = d->m_identityManager->identityForUoidOrDefault(d->mIdentity).uoid();
if (!d->mIdentity) {
qCDebug(TEMPLATEPARSER_LOG) << "Oops! No identity for message";
}
}
qCDebug(TEMPLATEPARSER_LOG) << "Identity found:" << d->mIdentity;
QString iid;
if (d->mIdentity) {
iid = TemplatesConfiguration::configIdString(d->mIdentity); // templates ID for that identity
} else {
iid = QStringLiteral("IDENTITY_NO_IDENTITY"); // templates ID for no identity
}
Templates iconf(iid);
if (iconf.useCustomTemplates()) { // does identity use custom templates?
switch (d->mMode) {
case NewMessage:
tmpl = iconf.templateNewMessage();
break;
case Reply:
tmpl = iconf.templateReply();
break;
case ReplyAll:
tmpl = iconf.templateReplyAll();
break;
case Forward:
tmpl = iconf.templateForward();
break;
default:
qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
return QString();
}
d->mQuoteString = iconf.quoteString();
if (!tmpl.isEmpty()) {
return tmpl; // use identity-specific template
}
}
switch (d->mMode) { // use the global template
case NewMessage:
tmpl = TemplateParserSettings::self()->templateNewMessage();
break;
case Reply:
tmpl = TemplateParserSettings::self()->templateReply();
break;
case ReplyAll:
tmpl = TemplateParserSettings::self()->templateReplyAll();
break;
case Forward:
tmpl = TemplateParserSettings::self()->templateForward();
break;
default:
qCDebug(TEMPLATEPARSER_LOG) << "Unknown message mode:" << d->mMode;
return QString();
}
d->mQuoteString = TemplateParserSettings::self()->quoteString();
return tmpl;
}
QString TemplateParserJob::pipe(const QString &cmd, const QString &buf)
{
KProcess process;
bool success;
process.setOutputChannelMode(KProcess::SeparateChannels);
process.setShellCommand(cmd);
process.start();
if (process.waitForStarted(pipeTimeout())) {
bool finished = false;
if (!buf.isEmpty()) {
process.write(buf.toLatin1());
}
if (buf.isEmpty() || process.waitForBytesWritten(pipeTimeout())) {
if (!buf.isEmpty()) {
process.closeWriteChannel();
}
if (process.waitForFinished(pipeTimeout())) {
success = (process.exitStatus() == QProcess::NormalExit);
finished = true;
} else {
finished = false;
success = false;
}
} else {
success = false;
finished = false;
}
// The process has started, but did not finish in time. Kill it.
if (!finished) {
process.kill();
}
} else {
success = false;
}
if (!success && d->mDebug) {
KMessageBox::error(
nullptr,
xi18nc("@info",
"Pipe command <command>%1</command> failed.", cmd));
}
if (success) {
return QString::fromLatin1(process.readAllStandardOutput());
} else {
return QString();
}
}
void TemplateParserJob::setWordWrap(bool wrap, int wrapColWidth)
{
d->mWrap = wrap;
d->mColWrap = wrapColWidth;
}
QString TemplateParserJob::plainMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) const
{
if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) {
return d->mSelection;
}
if (!d->mOrigMsg) {
return QString();
}
- QString result =d->mOtp->plainTextContent();
+ const auto mp = toplevelTextNode(d->mOtp->parsedPart());
+ QString result = mp->plaintextContent();
if (result.isEmpty()) {
result = d->mExtractHtmlInfoResult.mPlainText;
}
if (aStripSignature) {
result = MessageCore::StringUtil::stripSignature(result);
}
return result;
}
QString TemplateParserJob::htmlMessageText(bool aStripSignature, AllowSelection isSelectionAllowed)
{
if (!d->mSelection.isEmpty() && (isSelectionAllowed == SelectionAllowed)) {
//TODO implement d->mSelection for HTML
return d->mSelection;
}
d->mHeadElement = d->mExtractHtmlInfoResult.mHeaderElement;
const QString bodyElement = d->mExtractHtmlInfoResult.mBodyElement;
if (!bodyElement.isEmpty()) {
if (aStripSignature) {
//FIXME strip signature works partially for HTML mails
return MessageCore::StringUtil::stripSignature(bodyElement);
}
return bodyElement;
}
if (aStripSignature) {
//FIXME strip signature works partially for HTML mails
return MessageCore::StringUtil::stripSignature(d->mExtractHtmlInfoResult.mHtmlElement);
}
return d->mExtractHtmlInfoResult.mHtmlElement;
}
QString TemplateParserJob::quotedPlainText(const QString &selection) const
{
QString content = selection;
// Remove blank lines at the beginning:
const int firstNonWS = content.indexOf(QRegExp(QLatin1String("\\S")));
const int lineStart = content.lastIndexOf(QLatin1Char('\n'), firstNonWS);
if (lineStart >= 0) {
- content.remove(0, static_cast<unsigned int>(lineStart));
+ content.remove(0, lineStart);
}
const QString indentStr
= MessageCore::StringUtil::formatQuotePrefix(d->mQuoteString, d->mOrigMsg->from()->displayString());
if (TemplateParserSettings::self()->smartQuote() && d->mWrap) {
content = MessageCore::StringUtil::smartQuote(content, d->mColWrap - indentStr.length());
}
content.replace(QLatin1Char('\n'), QLatin1Char('\n') + indentStr);
content.prepend(indentStr);
content += QLatin1Char('\n');
return content;
}
QString TemplateParserJob::quotedHtmlText(const QString &selection) const
{
QString content = selection;
//TODO 1) look for all the variations of <br> and remove the blank lines
//2) implement vertical bar for quoted HTML mail.
//3) After vertical bar is implemented, If a user wants to edit quoted message,
// then the <blockquote> tags below should open and close as when required.
//Add blockquote tag, so that quoted message can be differentiated from normal message
content = QLatin1String("<blockquote>") + content + QLatin1String("</blockquote>");
return content;
}
uint TemplateParserJob::identityUoid(const KMime::Message::Ptr &msg) const
{
QString idString;
if (auto hrd = msg->headerByType("X-KMail-Identity")) {
idString = hrd->asUnicodeString().trimmed();
}
bool ok = false;
- int id = idString.toUInt(&ok);
+ unsigned int id = idString.toUInt(&ok);
if (!ok || id == 0) {
id = d->m_identityManager->identityForAddress(
msg->to()->asUnicodeString() + QLatin1String(", ") + msg->cc()->asUnicodeString()).uoid();
}
return id;
}
bool TemplateParserJob::isHtmlSignature() const
{
const KIdentityManagement::Identity &identity
= d->m_identityManager->identityForUoid(d->mIdentity);
if (identity.isNull()) {
return false;
}
const KIdentityManagement::Signature signature
= const_cast<KIdentityManagement::Identity &>(identity).signature();
return signature.isInlinedHtml();
}
QString TemplateParserJob::plainToHtml(const QString &body)
{
QString str = body;
str = str.toHtmlEscaped();
str.replace(QLatin1Char('\n'), QStringLiteral("<br />\n"));
return str;
}
//TODO implement this function using a DOM tree parser
void TemplateParserJob::makeValidHtml(QString &body)
{
QRegExp regEx;
regEx.setMinimal(true);
regEx.setPattern(QStringLiteral("<html.*>"));
if (!body.isEmpty() && !body.contains(regEx)) {
regEx.setPattern(QStringLiteral("<body.*>"));
if (!body.contains(regEx)) {
body = QLatin1String("<body>") + body + QLatin1String("<br/></body>");
}
regEx.setPattern(QStringLiteral("<head.*>"));
if (!body.contains(regEx)) {
body = QLatin1String("<head>") + d->mHeadElement + QLatin1String("</head>") + body;
}
body = QLatin1String("<html>") + body + QLatin1String("</html>");
}
}
diff --git a/templateparser/src/templateparserjob.h b/templateparser/src/templateparserjob.h
index e2b9105f..0427b7ee 100644
--- a/templateparser/src/templateparserjob.h
+++ b/templateparser/src/templateparserjob.h
@@ -1,371 +1,371 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSERJOB_H
#define TEMPLATEPARSERJOB_H
#include "templateparser_export.h"
#include <KMime/Message>
#include <QObject>
#include <memory>
namespace KIdentityManagement {
class IdentityManager;
}
namespace MessageCore {
class ImageCollector;
}
-class TemplateParserExtractHtmlInfoResult;
+struct TemplateParserExtractHtmlInfoResult;
namespace TemplateParser {
class TemplateParserJobPrivate;
/**
* \brief The TemplateParser transforms a message with a given template.
*
* \par Introduction
* The TemplateParser transforms a message with a given template.
* A template contains text and commands, such as %QUOTE or %ODATE, which will be
* replaced with the real values in process().
*
* \par Basics
* The message given in the templateparser constructor amsg, is the message that
* is being transformed.
* aorig_msg is the original message to which actions are performed.
* The message text in amsg will be replaced by the processed text of the template,
* but other properties, such as the attachments or the subject, are preserved.
*
* There are two different kind of commands: Those that work on the message that is
* to be transformed and those that work on an 'original message'.
* Those that work on the message that is to be transformed have no special prefix, e.g.
* '%DATE'. Those that work on the original message have an 'O' prefix, for example
* '%ODATE'.
* This means that the %DATE command will take the date of the message passed in the
* constructor, the message which is to be transformed, whereas the %ODATE command will
* take the date of the message that is being passed in process(), the original message.
*
* \par The process()
* The process() function takes aorig_msg as parameter. This aorig_msg is the original
* message from which various commands in templates with prefix 'O' extract the data and adds
* to the processed message.
* This function finds the template and passes them to processWithTemplate(), where templates
* are processed and its value is added to the processed message.
*
* \par Reply To/Forward Plain Text Mails
* Plain Text mails are the mails with only text part and no html part. While creating
* reply/forward to mails, processWithTemplate() processes all the commands and then
* appends its values to plainBody and htmlBody. This function then on the
* basis of whether the user wants to use plain mails or HTML mails, clears the htmlBody,
* or just passes both the plainBody and htmlBody unaltered.
*
* \par Reply To/Forward HTML Mails
* By HTML mails here, we mean multipart/alternative mails. As mentioned above, all
* commands in the TemplateParser appends text, i.e. plain text to plainBody
* and html text to htmlBody in the function processWithTemplate().
* This function also takes a decision of clearing the htmlBody on the basis of fact
* whether the user wants to reply/forward using plain mails or multipart/alternative
* mails.
*
* \par When "TO" and when "NOT TO" make multipart/alternative Mails
* User is the master to decide when to and when not to make multipart/alternative mails.
* <b>For user who <u>don't prefer</u> using HTML mails</b>
* There is a TemplateParserSettings::self()->replyUsingHtml() (in GUI as Settings->Configure KMail->
* Composer->General->"Reply using HTML if present"), which when not true (checkbox disabled
* in UI), will clear the htmlBody.
* An another option within the standard templates, %FORCEDPLAIN command raises the flag,
* ReplyAsPlain. This flag when raised in processWithTemplate() takes care that the
* processed message will contain text/plain part by clearing the htmlBody.
*
* Once the htmlBody is cleared, plainBody and an empty htmlBody is passed to
* addProcessedBodyToMessage(). Here since the htmlBody is empty, text/plain messages are
* assembled and thus user is not dealing with any kind of HTML part.
*
* <b>For user who <u>do prefer</u> using HTML mails</b>
* The setting discussed above as "Reply using HTML if present" (when checked to true),
* passes the htmlBody to addProcessedBodyToMessage() without doing any changes.
* An another option %FORCEDHTML within standard templates command raises the flag ReplyAsHtml.
* This flag when raised in processWithTemplate() takes care that the htmlBody is passed to
* addProcessedBodyToMessage() unaltered.
*
* Since htmlBody received by addProcessedBodyToMessage() is not empty, multipart/alternative
* messages are assembled.
*
* @NOTE Resolving conflict between TemplateParserSettings "replyUsingHtml" and FORCEDXXXX command.
* The conflict is resolved by simply giving preference to the commands over TemplateParserSettings.
*
* \par Make plain part
* mMsg is the reply message in which the message text will be replaced by the
* processed value from templates.
*
* In case of no attachments, the message will be a single-part message.
* A KMime::Content containing the plainBody from processWithTemplate() is
* created. Then the encodedBody(), contentType (text/plain) of this
* KMime::Content is set in the body and the header of mMsg. The addContent()
* method can be used for adding sub-content to content object in case of
* attachments. The addContent() method is not used for adding content of the
* above mentioned single-part, as addContent() will convert single-part to
* multipart-mixed before adding it to mMsg.
*
* \par Make multipart/alternative mails
* First of all a KMime::Content (content) is created with a content-type of
* multipart/alternative. Then in the same way as plain-part is created in above
* paragraph, a KMime::Content (sub-content) containing the plainBody is created
* and added as child to the content. Then a new KMime::Content (sub-content)
* with htmlBody as the body is created. The content-type is set as text/html.
* This new sub-content is then added to the parent content. Now, since the
* parent content (multipart/alternative) has two sub-content (text/plain and
* text/html) to it, it is added to the reply message (mMsg).
*
* TODO: What is the usecase of the commands that work on the message to be transformed?
* In general you only use the commands that work on the original message...
*/
class TEMPLATEPARSER_EXPORT TemplateParserJob : public QObject
{
Q_OBJECT
public:
enum Mode {
NewMessage,
Reply,
ReplyAll,
Forward
};
enum AllowSelection {
SelectionAllowed,
NoSelectionAllowed
};
enum Quotes {
ReplyAsOriginalMessage,
ReplyAsPlain,
ReplyAsHtml
};
public:
- explicit TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode);
+ explicit TemplateParserJob(const KMime::Message::Ptr &amsg, const Mode amode, QObject *parent = nullptr);
~TemplateParserJob();
/**
* Sets the selection. If this is set, only the selection will be added to
* commands such as %QUOTE. Otherwise, the whole message is quoted.
* If this is not called at all, the whole message is quoted as well.
* Call this before calling process().
*/
void setSelection(const QString &selection);
/**
* Sets whether the template parser is allowed to decrypt the original
* message when needing its message text, for example for the %QUOTE command.
* If true, it will tell the ObjectTreeParser it uses internally to decrypt the
* message, and that will possibly show a password request dialog to the user.
*
* The default is false.
*/
void setAllowDecryption(const bool allowDecryption);
/**
* Tell template parser whether or not to wrap words, and at what column
* to wrap at.
*
* Default is true, wrapping at 80chars.
*/
void setWordWrap(bool wrap, int wrapColWidth = 80);
/**
* Set the identity manager to be used when creating the template.
*/
void setIdentityManager(KIdentityManagement::IdentityManager *ident);
/**
* Sets the list of charsets to try to use to encode the resulting text.
* They are tried in order until one matches, or utf-8 as a fallback.
*/
void setCharsets(const QStringList &charsets);
void process(const KMime::Message::Ptr &aorig_msg, qint64 afolder = -1);
void process(const QString &tmplName, const KMime::Message::Ptr &aorig_msg, qint64 afolder = -1);
void processWithIdentity(uint uoid, const KMime::Message::Ptr &aorig_msg, qint64 afolder = -1);
void processWithTemplate(const QString &tmpl);
Q_SIGNALS:
void parsingDone(bool cursorPositionWasSet);
private:
void slotExtractInfoDone(const TemplateParserExtractHtmlInfoResult &result);
/// This finds the template to use. Either the one from the folder, identity or
/// finally the global template.
/// This also reads the To and CC address of the template
/// @return the contents of the template
QString findTemplate();
/// Finds the template with the given name.
/// This also reads the To and CC address of the template
/// @return the contents of the template
QString findCustomTemplate(const QString &tmpl);
QString pipe(const QString &cmd, const QString &buf);
QString getFirstName(const QString &str);
QString getLastName(const QString &str);
/**
* Called by processWithTemplate(). This adds the completely processed body to
* the message.
*
* This function creates plain text message or multipart/alternative message,
* depending on whether the processed body has @p htmlBody or not.
*
* In append mode, this will simply append the text to the body.
*
* Otherwise, the content of the old message is deleted and replaced with @p plainBody
* and @p htmlBody.
* Attachments of the original message are also added back to the new message.
*/
void addProcessedBodyToMessage(const QString &plainBody, const QString &htmlBody) const;
/**
* Determines whether the signature should be stripped when getting the text
* of the original message, e.g. for commands such as %QUOTE
*/
bool shouldStripSignature() const;
static int parseQuotes(const QString &prefix, const QString &str, QString &quote);
/**
* Return the text signature used the by current identity.
*/
QString getPlainSignature() const;
/**
* Return the HTML signature used the by current identity.
*/
QString getHtmlSignature() const;
/**
* Returns message body indented by the
* given indentation string. This is suitable for including the message
* in another message of for replies, forwards.
*
* No attachments are handled if includeAttach is false.
* The signature is stripped if aStripSignature is true and
* smart quoting is turned on. Signed or encrypted texts
* get converted to plain text when allowDecryption is true.
*/
QString quotedPlainText(const QString &selection = QString()) const;
/**
* Returns HTML message body.
* This is suitable for including the message
* in another message of for replies, forwards.
*
* No attachments are handled if includeAttach is false.
* The signature is stripped if aStripSignature is true and
* smart quoting is turned on. Signed or encrypted texts
* get converted to plain text when allowDecryption is true.
*/
QString quotedHtmlText(const QString &selection) const;
/**
* This function return the plain text part from the OTP.
* For HTML only mails. It returns the converted plain text
* from the OTP.
* @param allowSelectionOnly takes care that if a reply/forward
* is made to a selected part of message, then the selection is
* returned as it is without going through th OTP
* @param aStripSignature strips the signature out of the message
*
*/
QString plainMessageText(bool aStripSignature, AllowSelection isSelectionAllowed) const;
/**
* Returns the HTML content of the message as plain text
*/
QString htmlMessageText(bool aStripSignature, AllowSelection isSelectionAllowed);
/** @return the UOID of the identity for this message.
* Searches the "x-kmail-identity" header and if that fails,
* searches with KIdentityManagement::IdentityManager::identityForAddress()
*/
uint identityUoid(const KMime::Message::Ptr &msg) const;
/**
* Returns KMime content of the plain text part of the message after setting
* its mime type, charset and CTE.
* This function is called by:-
* 1) TemplateParser::addProcessedBodyToMessage(), which uses this content
* to simply create the text/plain message
*
* 2) TemplateParser::createMultipartAlternativeContent() which adds this content
* to create the multipart/alternative message.
*/
KMime::Content *createPlainPartContent(const QString &plainBody) const;
/**
* Returns KMime content of the multipart/alternative part of the message
* after setting the mime type, charset and CTE of its respective text/plain
* part and text/html part.
*/
KMime::Content *createMultipartAlternativeContent(const QString &plainBody, const QString &htmlBody) const;
/**
* Returns a multipart/mixed KMime::Content that has textPart and all
* attachments as children.
* @param attachments the list of attachments to add
* @param textPart a KMime::Content that is to be added as a child.
* @since 4.8
*/
KMime::Content *createMultipartMixed(const QVector<KMime::Content *> &attachments, KMime::Content *textPart) const;
/**
* Returnsa multipart/related KMime::Content that has mainTextPart and all
* embedded images as children.
* @param ac a reference to an MessageCore::ImageCollector that has
* collected all attachments.
* @param mainTextPart a KMime::Content that is to be added as a child.
* @since 4.8
*/
KMime::Content *createMultipartRelated(const MessageCore::ImageCollector &ic, KMime::Content *mainTextPart) const;
/**
* Checks if the signature is HTML or not.
*/
bool isHtmlSignature() const;
/**
- * Does the necessary conversions like escaping charecters, changing "\n" to
+ * Does the necessary conversions like escaping characters, changing "\n" to
* breakline tag before appending text to htmlBody.
*/
static QString plainToHtml(const QString &body);
/**
* Make a HTML content valid by adding missing html/head/body tag.
*/
void makeValidHtml(QString &body);
std::unique_ptr<TemplateParserJobPrivate> d;
};
}
#endif // TEMPLATEPARSERJOB_H
diff --git a/templateparser/src/templateparserjob_p.h b/templateparser/src/templateparserjob_p.h
index 3edb5670..79fa7bde 100644
--- a/templateparser/src/templateparserjob_p.h
+++ b/templateparser/src/templateparserjob_p.h
@@ -1,75 +1,73 @@
/*
Copyright (C) 2017 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.
*/
#ifndef TEMPLATEPARSERJOB_P_H
#define TEMPLATEPARSERJOB_P_H
#include "templateparserjob.h"
#include "templateparserextracthtmlinforesult.h"
#include <KMime/Message>
namespace MimeTreeParser {
class ObjectTreeParser;
class SimpleObjectTreeSource;
}
namespace KIdentityManagement {
class IdentityManager;
}
namespace MessageCore {
class ImageCollector;
}
-
-namespace TemplateParser
-{
+namespace TemplateParser {
class TemplateParserJobPrivate
{
public:
TemplateParserJobPrivate(const KMime::Message::Ptr &amsg, const TemplateParserJob::Mode amode);
~TemplateParserJobPrivate();
void setAllowDecryption(const bool allowDecryption);
KMime::Message::Ptr mMsg; // Msg to write to
KMime::Message::Ptr mOrigMsg; // Msg to read from
KIdentityManagement::IdentityManager *m_identityManager = nullptr;
MimeTreeParser::ObjectTreeParser *mOtp = nullptr;
MimeTreeParser::SimpleObjectTreeSource *mEmptySource = nullptr;
QString mSelection;
QString mQuoteString = QStringLiteral("> ");
QString mTo;
QString mCC;
QString mHeadElement;
QStringList mCharsets;
TemplateParserJob::Quotes mQuotes = TemplateParserJob::ReplyAsOriginalMessage;
TemplateParserJob::Mode mMode;
TemplateParserExtractHtmlInfoResult mExtractHtmlInfoResult;
qint64 mFolder = -1; //Used to find a template
uint mIdentity = 0;
int mColWrap = 80;
bool mForceCursorPosition = false;
bool mAllowDecryption = true;
bool mDebug = false;
bool mWrap = true;
};
}
#endif
diff --git a/templateparser/src/templatescommandmenu.cpp b/templateparser/src/templatescommandmenu.cpp
index de4052b6..16041d73 100644
--- a/templateparser/src/templatescommandmenu.cpp
+++ b/templateparser/src/templatescommandmenu.cpp
@@ -1,739 +1,737 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-
#include "templatescommandmenu.h"
#include <QAction>
#include <KActionMenu>
#include "templateparser_debug.h"
#include <KLocalizedString>
#undef I18N_NOOP
#define I18N_NOOP(t) nullptr, t
#undef I18N_NOOP2
#define I18N_NOOP2(c, t) c, t
using namespace TemplateParser;
struct InsertCommand {
const char *context;
const char *name;
const TemplatesCommandMenu::Command command;
QString getLocalizedDisplayName() const
{
return i18nc(context, name);
}
};
static const InsertCommand originalCommands[] = {
{
I18N_NOOP("Quoted Message Text"),
TemplatesCommandMenu::CQuote
},
{
I18N_NOOP("Message Text as Is"),
TemplatesCommandMenu::CText
},
{
I18N_NOOP("Message Id"),
TemplatesCommandMenu::COMsgId
},
{
I18N_NOOP("Date"),
TemplatesCommandMenu::CODate
},
{
I18N_NOOP("Date in Short Format"),
TemplatesCommandMenu::CODateShort
},
{
I18N_NOOP("Date in C Locale"),
TemplatesCommandMenu::CODateEn
},
{
I18N_NOOP("Day of Week"),
TemplatesCommandMenu::CODow
},
{
I18N_NOOP("Time"),
TemplatesCommandMenu::COTime
},
{
I18N_NOOP("Time in Long Format"),
TemplatesCommandMenu::COTimeLong
},
{
I18N_NOOP("Time in C Locale"),
TemplatesCommandMenu::COTimeLongEn
},
{
I18N_NOOP("To Field Address"),
TemplatesCommandMenu::COToAddr
},
{
I18N_NOOP("To Field Name"),
TemplatesCommandMenu::COToName
},
{
I18N_NOOP("To Field First Name"),
TemplatesCommandMenu::COToFName
},
{
I18N_NOOP("To Field Last Name"),
TemplatesCommandMenu::COToLName
},
{
I18N_NOOP("CC Field Address"),
TemplatesCommandMenu::COCCAddr
},
{
I18N_NOOP("CC Field Name"),
TemplatesCommandMenu::COCCName
},
{
I18N_NOOP("CC Field First Name"),
TemplatesCommandMenu::COCCFName
},
{
I18N_NOOP("CC Field Last Name"),
TemplatesCommandMenu::COCCLName
},
{
I18N_NOOP("From Field Address"),
TemplatesCommandMenu::COFromAddr
},
{
I18N_NOOP("From Field Name"),
TemplatesCommandMenu::COFromName
},
{
I18N_NOOP("From Field First Name"),
TemplatesCommandMenu::COFromFName
},
{
I18N_NOOP("From Field Last Name"),
TemplatesCommandMenu::COFromLName
},
{
I18N_NOOP("Addresses of all recipients"),
TemplatesCommandMenu::COAddresseesAddr
},
{
I18N_NOOP2("Template value for subject of the message", "Subject"),
TemplatesCommandMenu::COFullSubject
},
{
I18N_NOOP("Quoted Headers"),
TemplatesCommandMenu::CQHeaders
},
{
I18N_NOOP("Headers as Is"),
TemplatesCommandMenu::CHeaders
},
{
I18N_NOOP("Header Content"),
TemplatesCommandMenu::COHeader
},
{
I18N_NOOP("Reply as Quoted Plain Text"),
TemplatesCommandMenu::CQuotePlain
},
{
I18N_NOOP("Reply as Quoted HTML Text"),
TemplatesCommandMenu::CQuoteHtml
}
};
static const int originalCommandsCount
= sizeof(originalCommands) / sizeof(*originalCommands);
static const InsertCommand currentCommands[] = {
{
I18N_NOOP("Date"),
TemplatesCommandMenu::CDate
},
{
I18N_NOOP("Date in Short Format"),
TemplatesCommandMenu::CDateShort
},
{
I18N_NOOP("Date in C Locale"),
TemplatesCommandMenu::CDateEn
},
{
I18N_NOOP("Day of Week"),
TemplatesCommandMenu::CDow
},
{
I18N_NOOP("Time"),
TemplatesCommandMenu::CTime
},
{
I18N_NOOP("Time in Long Format"),
TemplatesCommandMenu::CTimeLong
},
{
I18N_NOOP("Time in C Locale"),
TemplatesCommandMenu::CTimeLongEn
},
{
I18N_NOOP("To Field Address"),
TemplatesCommandMenu::CToAddr
},
{
I18N_NOOP("To Field Name"),
TemplatesCommandMenu::CToName
},
{
I18N_NOOP("To Field First Name"),
TemplatesCommandMenu::CToFName
},
{
I18N_NOOP("To Field Last Name"),
TemplatesCommandMenu::CToLName
},
{
I18N_NOOP("CC Field Address"),
TemplatesCommandMenu::CCCAddr
},
{
I18N_NOOP("CC Field Name"),
TemplatesCommandMenu::CCCName
},
{
I18N_NOOP("CC Field First Name"),
TemplatesCommandMenu::CCCFName
},
{
I18N_NOOP("CC Field Last Name"),
TemplatesCommandMenu::CCCLName
},
{
I18N_NOOP("From Field Address"),
TemplatesCommandMenu::CFromAddr
},
{
I18N_NOOP("From field Name"),
TemplatesCommandMenu::CFromName
},
{
I18N_NOOP("From Field First Name"),
TemplatesCommandMenu::CFromFName
},
{
I18N_NOOP("From Field Last Name"),
TemplatesCommandMenu::CFromLName
},
{
I18N_NOOP2("Template subject command.", "Subject"),
TemplatesCommandMenu::CFullSubject
},
{
I18N_NOOP("Header Content"),
TemplatesCommandMenu::CHeader
}
};
static const int currentCommandsCount = sizeof(currentCommands) / sizeof(*currentCommands);
static const InsertCommand extCommands[] = {
{
I18N_NOOP("Pipe Original Message Body and Insert Result as Quoted Text"),
TemplatesCommandMenu::CQuotePipe
},
{
I18N_NOOP("Pipe Original Message Body and Insert Result as Is"),
TemplatesCommandMenu::CTextPipe
},
{
I18N_NOOP("Pipe Original Message with Headers and Insert Result as Is"),
TemplatesCommandMenu::CMsgPipe
},
{
I18N_NOOP("Pipe Current Message Body and Insert Result as Is"),
TemplatesCommandMenu::CBodyPipe
},
{
I18N_NOOP("Pipe Current Message Body and Replace with Result"),
TemplatesCommandMenu::CClearPipe
}
};
static const int extCommandsCount
= sizeof(extCommands) / sizeof(*extCommands);
static const InsertCommand miscCommands[] = {
{
I18N_NOOP2("Inserts user signature, also known as footer, into message", "Signature"),
TemplatesCommandMenu::CSignature
},
{
I18N_NOOP("Insert File Content"),
TemplatesCommandMenu::CInsert
},
{
I18N_NOOP2("All characters, up to and including the next newline, "
"are discarded without performing any macro expansion",
"Discard to Next Line"),
TemplatesCommandMenu::CDnl
},
{
I18N_NOOP("Template Comment"),
TemplatesCommandMenu::CRem
},
{
I18N_NOOP("No Operation"),
TemplatesCommandMenu::CNop
},
{
I18N_NOOP("Clear Generated Message"),
TemplatesCommandMenu::CClear
},
{
I18N_NOOP("Cursor position"),
TemplatesCommandMenu::CCursor
},
{
I18N_NOOP("Blank text"),
TemplatesCommandMenu::CBlank
},
{
I18N_NOOP("Dictionary Language"),
TemplatesCommandMenu::CDictionaryLanguage
},
{
I18N_NOOP("Language"),
TemplatesCommandMenu::CLanguage
},
//TODO add support for custom variable. %CUSTOM="???" ?
};
static const int miscCommandsCount = sizeof(miscCommands) / sizeof(*miscCommands);
-
static const InsertCommand debugCommands[] = {
{
I18N_NOOP("Turn Debug On"),
TemplatesCommandMenu::CDebug
},
{
I18N_NOOP("Turn Debug Off"),
TemplatesCommandMenu::CDebugOff
}
};
static const int debugCommandsCount = sizeof(debugCommands) / sizeof(*debugCommands);
void TemplatesCommandMenu::fillMenuFromActionMap(const QMap< QString, TemplatesCommandMenu::Command > &map, KActionMenu *menu)
{
QMap< QString, TemplatesCommandMenu::Command >::const_iterator it = map.constBegin();
QMap< QString, TemplatesCommandMenu::Command >::const_iterator end = map.constEnd();
while (it != end) {
QAction *action = new QAction(it.key(), menu); //krazy:exclude=tipsandthis
const TemplatesCommandMenu::Command cmd = it.value();
- connect(action, &QAction::triggered, this, [this, cmd]{ slotInsertCommand(cmd); });
+ connect(action, &QAction::triggered, this, [this, cmd] {
+ slotInsertCommand(cmd);
+ });
menu->addAction(action);
++it;
}
}
TemplatesCommandMenu::TemplatesCommandMenu(QObject *parent)
: QObject(parent)
{
}
TemplatesCommandMenu::~TemplatesCommandMenu()
{
-
}
QMenu *TemplatesCommandMenu::menu() const
{
return mMenu->menu();
}
void TemplatesCommandMenu::fillMenu()
{
mMenu = new KActionMenu(i18n("Insert Command"), this);
}
void TemplatesCommandMenu::fillSubMenus()
{
if (mWasInitialized) {
return;
}
mWasInitialized = true;
QMap< QString, Command > commandMap;
KActionMenu *menu = nullptr;
// ******************************************************
if (mType & ReplyForwardMessage) {
menu = new KActionMenu(i18n("Original Message"), mMenu);
mMenu->addAction(menu);
// Map sorts commands
for (int i = 0; i < originalCommandsCount; ++i) {
commandMap.insert(originalCommands[i].getLocalizedDisplayName(), originalCommands[i].command);
}
fillMenuFromActionMap(commandMap, menu);
commandMap.clear();
}
// ******************************************************
if (mType & CurrentMessage) {
menu = new KActionMenu(i18n("Current Message"), mMenu);
mMenu->addAction(menu);
for (int i = 0; i < currentCommandsCount; ++i) {
commandMap.insert(currentCommands[i].getLocalizedDisplayName(), currentCommands[i].command);
}
fillMenuFromActionMap(commandMap, menu);
commandMap.clear();
}
// ******************************************************
if (mType & External) {
menu = new KActionMenu(i18n("Process with External Programs"), mMenu);
mMenu->addAction(menu);
for (int i = 0; i < extCommandsCount; ++i) {
commandMap.insert(extCommands[i].getLocalizedDisplayName(), extCommands[i].command);
}
fillMenuFromActionMap(commandMap, menu);
commandMap.clear();
}
// ******************************************************
if (mType & Misc) {
menu = new KActionMenu(i18nc("Miscellaneous template commands menu", "Miscellaneous"), mMenu);
mMenu->addAction(menu);
for (int i = 0; i < miscCommandsCount; ++i) {
commandMap.insert(miscCommands[i].getLocalizedDisplayName(), miscCommands[i].command);
}
fillMenuFromActionMap(commandMap, menu);
commandMap.clear();
}
// ******************************************************
if (!qEnvironmentVariableIsEmpty("KDEPIM_DEBUGGING")) {
if (mType & Debug) {
menu = new KActionMenu(i18nc("Debug template commands menu", "Debug"), mMenu);
mMenu->addAction(menu);
for (int i = 0; i < debugCommandsCount; ++i) {
commandMap.insert(debugCommands[i].getLocalizedDisplayName(), debugCommands[i].command);
}
fillMenuFromActionMap(commandMap, menu);
}
}
-
}
TemplatesCommandMenu::MenuTypes TemplatesCommandMenu::type() const
{
return mType;
}
void TemplatesCommandMenu::setType(MenuTypes type)
{
mType = type;
delete mMenu;
mWasInitialized = false;
fillMenu();
fillSubMenus();
}
void TemplatesCommandMenu::slotInsertCommand(TemplatesCommandMenu::Command cmd)
{
Q_EMIT insertCommand(cmd);
switch (cmd) {
case TemplatesCommandMenu::CBlank:
Q_EMIT insertCommand(QStringLiteral("%BLANK"));
return;
case TemplatesCommandMenu::CQuote:
Q_EMIT insertCommand(QStringLiteral("%QUOTE"));
return;
case TemplatesCommandMenu::CText:
Q_EMIT insertCommand(QStringLiteral("%TEXT"));
return;
case TemplatesCommandMenu::COMsgId:
Q_EMIT insertCommand(QStringLiteral("%OMSGID"));
return;
case TemplatesCommandMenu::CODate:
Q_EMIT insertCommand(QStringLiteral("%ODATE"));
return;
case TemplatesCommandMenu::CODateShort:
Q_EMIT insertCommand(QStringLiteral("%ODATESHORT"));
return;
case TemplatesCommandMenu::CODateEn:
Q_EMIT insertCommand(QStringLiteral("%ODATEEN"));
return;
case TemplatesCommandMenu::CODow:
Q_EMIT insertCommand(QStringLiteral("%ODOW"));
return;
case TemplatesCommandMenu::COTime:
Q_EMIT insertCommand(QStringLiteral("%OTIME"));
return;
case TemplatesCommandMenu::COTimeLong:
Q_EMIT insertCommand(QStringLiteral("%OTIMELONG"));
return;
case TemplatesCommandMenu::COTimeLongEn:
Q_EMIT insertCommand(QStringLiteral("%OTIMELONGEN"));
return;
case TemplatesCommandMenu::COToAddr:
Q_EMIT insertCommand(QStringLiteral("%OTOADDR"));
return;
case TemplatesCommandMenu::COToName:
Q_EMIT insertCommand(QStringLiteral("%OTONAME"));
return;
case TemplatesCommandMenu::COToFName:
Q_EMIT insertCommand(QStringLiteral("%OTOFNAME"));
return;
case TemplatesCommandMenu::COToLName:
Q_EMIT insertCommand(QStringLiteral("%OTOLNAME"));
return;
case TemplatesCommandMenu::COCCAddr:
Q_EMIT insertCommand(QStringLiteral("%OCCADDR"));
return;
case TemplatesCommandMenu::COCCName:
Q_EMIT insertCommand(QStringLiteral("%OCCNAME"));
return;
case TemplatesCommandMenu::COCCFName:
Q_EMIT insertCommand(QStringLiteral("%OCCFNAME"));
return;
case TemplatesCommandMenu::COCCLName:
Q_EMIT insertCommand(QStringLiteral("%OCCLNAME"));
return;
case TemplatesCommandMenu::COFromAddr:
Q_EMIT insertCommand(QStringLiteral("%OFROMADDR"));
return;
case TemplatesCommandMenu::COFromName:
Q_EMIT insertCommand(QStringLiteral("%OFROMNAME"));
return;
case TemplatesCommandMenu::COFromFName:
Q_EMIT insertCommand(QStringLiteral("%OFROMFNAME"));
return;
case TemplatesCommandMenu::COFromLName:
Q_EMIT insertCommand(QStringLiteral("%OFROMLNAME"));
return;
case TemplatesCommandMenu::COFullSubject:
Q_EMIT insertCommand(QStringLiteral("%OFULLSUBJECT"));
return;
case TemplatesCommandMenu::CQHeaders:
Q_EMIT insertCommand(QStringLiteral("%QHEADERS"));
return;
case TemplatesCommandMenu::CHeaders:
Q_EMIT insertCommand(QStringLiteral("%HEADERS"));
return;
case TemplatesCommandMenu::COHeader:
Q_EMIT insertCommand(QStringLiteral("%OHEADER=\"\""), -1);
return;
case TemplatesCommandMenu::CMsgId:
Q_EMIT insertCommand(QStringLiteral("%MSGID"));
return;
case TemplatesCommandMenu::CDate:
Q_EMIT insertCommand(QStringLiteral("%DATE"));
return;
case TemplatesCommandMenu::CDateShort:
Q_EMIT insertCommand(QStringLiteral("%DATESHORT"));
return;
case TemplatesCommandMenu::CDateEn:
Q_EMIT insertCommand(QStringLiteral("%DATEEN"));
return;
case TemplatesCommandMenu::CDow:
Q_EMIT insertCommand(QStringLiteral("%DOW"));
return;
case TemplatesCommandMenu::CTime:
Q_EMIT insertCommand(QStringLiteral("%TIME"));
return;
case TemplatesCommandMenu::CTimeLong:
Q_EMIT insertCommand(QStringLiteral("%TIMELONG"));
return;
case TemplatesCommandMenu::CTimeLongEn:
Q_EMIT insertCommand(QStringLiteral("%TIMELONGEN"));
return;
case TemplatesCommandMenu::COAddresseesAddr:
Q_EMIT insertCommand(QStringLiteral("%OADDRESSEESADDR"));
return;
case TemplatesCommandMenu::CToAddr:
Q_EMIT insertCommand(QStringLiteral("%TOADDR"));
return;
case TemplatesCommandMenu::CToName:
Q_EMIT insertCommand(QStringLiteral("%TONAME"));
return;
case TemplatesCommandMenu::CToFName:
Q_EMIT insertCommand(QStringLiteral("%TOFNAME"));
return;
case TemplatesCommandMenu::CToLName:
Q_EMIT insertCommand(QStringLiteral("%TOLNAME"));
return;
case TemplatesCommandMenu::CCCAddr:
Q_EMIT insertCommand(QStringLiteral("%CCADDR"));
return;
case TemplatesCommandMenu::CCCName:
Q_EMIT insertCommand(QStringLiteral("%CCNAME"));
return;
case TemplatesCommandMenu::CCCFName:
Q_EMIT insertCommand(QStringLiteral("%CCFNAME"));
return;
case TemplatesCommandMenu::CCCLName:
Q_EMIT insertCommand(QStringLiteral("%CCLNAME"));
return;
case TemplatesCommandMenu::CFromAddr:
Q_EMIT insertCommand(QStringLiteral("%FROMADDR"));
return;
case TemplatesCommandMenu::CFromName:
Q_EMIT insertCommand(QStringLiteral("%FROMNAME"));
return;
case TemplatesCommandMenu::CFromFName:
Q_EMIT insertCommand(QStringLiteral("%FROMFNAME"));
return;
case TemplatesCommandMenu::CFromLName:
Q_EMIT insertCommand(QStringLiteral("%FROMLNAME"));
return;
case TemplatesCommandMenu::CFullSubject:
Q_EMIT insertCommand(QStringLiteral("%FULLSUBJECT"));
return;
case TemplatesCommandMenu::CHeader:
Q_EMIT insertCommand(QStringLiteral("%HEADER=\"\""), -1);
return;
case TemplatesCommandMenu::CSystem:
Q_EMIT insertCommand(QStringLiteral("%SYSTEM=\"\""), -1);
return;
case TemplatesCommandMenu::CQuotePipe:
Q_EMIT insertCommand(QStringLiteral("%QUOTEPIPE=\"\""), -1);
return;
case TemplatesCommandMenu::CTextPipe:
Q_EMIT insertCommand(QStringLiteral("%TEXTPIPE=\"\""), -1);
return;
case TemplatesCommandMenu::CMsgPipe:
Q_EMIT insertCommand(QStringLiteral("%MSGPIPE=\"\""), -1);
return;
case TemplatesCommandMenu::CBodyPipe:
Q_EMIT insertCommand(QStringLiteral("%BODYPIPE=\"\""), -1);
return;
case TemplatesCommandMenu::CClearPipe:
Q_EMIT insertCommand(QStringLiteral("%CLEARPIPE=\"\""), -1);
return;
case TemplatesCommandMenu::CCursor:
Q_EMIT insertCommand(QStringLiteral("%CURSOR"));
return;
case TemplatesCommandMenu::CSignature:
Q_EMIT insertCommand(QStringLiteral("%SIGNATURE"));
return;
case TemplatesCommandMenu::CInsert:
Q_EMIT insertCommand(QStringLiteral("%INSERT=\"\""), -1);
return;
case TemplatesCommandMenu::CDnl:
Q_EMIT insertCommand(QStringLiteral("%-"));
return;
case TemplatesCommandMenu::CRem:
Q_EMIT insertCommand(QStringLiteral("%REM=\"\""), -1);
return;
case TemplatesCommandMenu::CNop:
Q_EMIT insertCommand(QStringLiteral("%NOP"));
return;
case TemplatesCommandMenu::CClear:
Q_EMIT insertCommand(QStringLiteral("%CLEAR"));
return;
case TemplatesCommandMenu::CDebug:
Q_EMIT insertCommand(QStringLiteral("%DEBUG"));
return;
case TemplatesCommandMenu::CDebugOff:
Q_EMIT insertCommand(QStringLiteral("%DEBUGOFF"));
return;
case TemplatesCommandMenu::CQuotePlain:
Q_EMIT insertCommand(QStringLiteral("%FORCEDPLAIN"));
return;
case TemplatesCommandMenu::CQuoteHtml:
Q_EMIT insertCommand(QStringLiteral("%FORCEDHTML"));
return;
case TemplatesCommandMenu::CDictionaryLanguage:
Q_EMIT insertCommand(QStringLiteral("%DICTIONARYLANGUAGE=\"\""), -1);
return;
case TemplatesCommandMenu::CLanguage:
Q_EMIT insertCommand(QStringLiteral("%LANGUAGE=\"\""), -1);
return;
}
qCDebug(TEMPLATEPARSER_LOG) << "Unknown template command index:" << cmd;
}
diff --git a/templateparser/src/templatescommandmenu.h b/templateparser/src/templatescommandmenu.h
index a6c6b473..28a87aad 100644
--- a/templateparser/src/templatescommandmenu.h
+++ b/templateparser/src/templatescommandmenu.h
@@ -1,143 +1,143 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATESCOMMANDMENU_H
#define TEMPLATESCOMMANDMENU_H
#include <QMap>
#include <QObject>
#include "templateparser_export.h"
class KActionMenu;
class QMenu;
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplatesCommandMenu : public QObject
{
Q_OBJECT
public:
explicit TemplatesCommandMenu(QObject *parent = nullptr);
~TemplatesCommandMenu();
enum MenuType {
ReplyForwardMessage = 1,
CurrentMessage = 2,
External = 4,
Misc = 8,
Debug = 16,
Default = ReplyForwardMessage | CurrentMessage | External | Misc | Debug
};
Q_FLAG(MenuType)
Q_DECLARE_FLAGS(MenuTypes, MenuType)
//TODO: apidox for all these enums
enum Command {
CDnl = 1,
CRem,
CInsert,
CSystem,
CQuotePipe,
CQuote,
CQHeaders,
CHeaders,
CTextPipe,
CMsgPipe,
CBodyPipe,
CClearPipe,
CText,
CToAddr,
CToName,
CFromAddr,
CFromName,
CFullSubject,
CMsgId,
COHeader,
CHeader,
COToAddr,
COToName,
COFromAddr,
COFromName,
COFullSubject,
COMsgId,
CDateEn,
CDateShort,
CDate,
CDow,
CTimeLongEn,
CTimeLong,
CTime,
CODateEn,
CODateShort,
CODate,
CODow,
COTimeLongEn,
COTimeLong,
COTime,
CBlank,
CNop,
CClear,
CDebug,
CDebugOff,
CToFName,
CToLName,
CFromFName,
CFromLName,
COToFName,
COToLName,
COFromFName,
COFromLName,
CCursor,
CCCAddr,
CCCName,
CCCFName,
CCCLName,
COCCAddr,
COCCName,
COCCFName,
COCCLName,
COAddresseesAddr,
CSignature,
CQuotePlain,
CQuoteHtml,
CDictionaryLanguage,
CLanguage
};
QMenu *menu() const;
void fillMenu();
void fillSubMenus();
MenuTypes type() const;
void setType(MenuTypes type);
Q_SIGNALS:
void insertCommand(TemplatesCommandMenu::Command cmd);
void insertCommand(const QString &cmd, int adjustCursor = 0);
public Q_SLOTS:
void slotInsertCommand(TemplatesCommandMenu::Command cmd);
protected:
KActionMenu *mMenu = nullptr;
private:
void fillMenuFromActionMap(const QMap<QString, TemplatesCommandMenu::Command> &map, KActionMenu *menu);
MenuTypes mType = Default;
bool mWasInitialized = false;
};
}
#endif // TEMPLATESCOMMANDMENU_H
diff --git a/templateparser/src/templatesconfiguration.cpp b/templateparser/src/templatesconfiguration.cpp
index a4f9847f..22d0b57e 100644
--- a/templateparser/src/templatesconfiguration.cpp
+++ b/templateparser/src/templatesconfiguration.cpp
@@ -1,414 +1,414 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
- * Copyright (C) 2012-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2012-2019 Laurent Montel <montel@kde.org>
*
* 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.
*/
#include "templatesconfiguration.h"
#include "globalsettings_templateparser.h"
#include "templatesconfiguration_kfg.h"
#include "kpimtextedit/plaintexteditor.h"
#include <KMessageBox>
#include <KLocalizedString>
#include "templateparser_debug.h"
#include <QWhatsThis>
using namespace TemplateParser;
class TemplateParser::TemplatesConfigurationPrivate
{
public:
TemplatesConfigurationPrivate()
{
}
QString mHelpString;
};
TemplatesConfiguration::TemplatesConfiguration(QWidget *parent, const QString &name)
: QWidget(parent)
, d(new TemplateParser::TemplatesConfigurationPrivate)
{
setupUi(this);
setObjectName(name);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizeHint();
connect(textEdit_new->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
connect(textEdit_reply->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
connect(textEdit_reply_all->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
connect(textEdit_forward->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
connect(lineEdit_quote, &QLineEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
- connect(mInsertCommand, QOverload<const QString &, int>::of(&TemplateParser::TemplatesInsertCommandPushButton::insertCommand),
+ connect(mInsertCommand, qOverload<const QString &, int>(&TemplateParser::TemplatesInsertCommandPushButton::insertCommand),
this, &TemplatesConfiguration::slotInsertCommand);
d->mHelpString
= i18n("<p>Here you can create and manage templates to use when "
"composing new messages, replies or forwarded messages.</p>"
"<p>The message templates support substitution commands, "
"either simply type them or select them from "
"the <i>Insert command</i> menu.</p>");
const QString templateConfigurationName(name);
if (templateConfigurationName == QLatin1String("folder-templates")) {
d->mHelpString
+= i18n("<p>Templates specified here are folder-specific. "
"They override both global templates and per-identity "
"templates.</p>");
} else if (templateConfigurationName == QLatin1String("identity-templates")) {
d->mHelpString
+= i18n("<p>Templates specified here are identity-specific. "
"They override global templates, but can be overridden by "
"per-folder templates if they are specified.</p>");
} else {
d->mHelpString
+= i18n("<p>These are global (default) templates. They can be overridden "
"by per-identity templates or per-folder templates "
"if they are specified.</p>");
}
mHelp->setText(i18n("<a href=\"whatsthis\">How does this work?</a>"));
connect(mHelp, &QLabel::linkActivated,
this, &TemplatesConfiguration::slotHelpLinkClicked);
mHelp->setContextMenuPolicy(Qt::NoContextMenu);
}
TemplatesConfiguration::~TemplatesConfiguration()
{
disconnect(textEdit_new->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
disconnect(textEdit_reply->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
disconnect(textEdit_reply_all->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
disconnect(textEdit_forward->editor(), &QPlainTextEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
disconnect(lineEdit_quote, &QLineEdit::textChanged,
this, &TemplatesConfiguration::slotTextChanged);
delete d;
}
void TemplatesConfiguration::slotHelpLinkClicked(const QString &)
{
QWhatsThis::showText(QCursor::pos(), d->mHelpString);
}
void TemplatesConfiguration::slotTextChanged()
{
Q_EMIT changed();
}
void TemplatesConfiguration::resetToDefault()
{
const int choice
= KMessageBox::questionYesNoCancel(
- nullptr,
- i18n("Do you want to reset current template or all templates to default?"),
- i18n("Reset to default"),
- KGuiItem(i18n("Reset Current Template")),
- KGuiItem(i18n("Reset All Templates")),
- KStandardGuiItem::cancel());
+ nullptr,
+ i18n("Do you want to reset current template or all templates to default?"),
+ i18n("Reset to default"),
+ KGuiItem(i18n("Reset Current Template")),
+ KGuiItem(i18n("Reset All Templates")),
+ KStandardGuiItem::cancel());
if (choice == KMessageBox::Cancel) {
return;
} else if (choice == KMessageBox::Yes) {
const int toolboxCurrentIndex(toolBox1->currentIndex());
if (toolBox1->widget(toolboxCurrentIndex) == page_new) {
textEdit_new->setPlainText(DefaultTemplates::defaultNewMessage());
} else if (toolBox1->widget(toolboxCurrentIndex) == page_reply) {
textEdit_reply->setPlainText(DefaultTemplates::defaultReply());
} else if (toolBox1->widget(toolboxCurrentIndex) == page_reply_all) {
textEdit_reply_all->setPlainText(DefaultTemplates::defaultReplyAll());
} else if (toolBox1->widget(toolboxCurrentIndex) == page_forward) {
textEdit_forward->setPlainText(DefaultTemplates::defaultForward());
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Unknown current page in TemplatesConfiguration!";
}
} else {
textEdit_new->setPlainText(DefaultTemplates::defaultNewMessage());
textEdit_reply->setPlainText(DefaultTemplates::defaultReply());
textEdit_reply_all->setPlainText(DefaultTemplates::defaultReplyAll());
textEdit_forward->setPlainText(DefaultTemplates::defaultForward());
}
lineEdit_quote->setText(DefaultTemplates::defaultQuoteString());
}
QLabel *TemplatesConfiguration::helpLabel() const
{
return mHelp;
}
void TemplatesConfiguration::loadFromGlobal()
{
QString str;
str = TemplateParserSettings::self()->templateNewMessage();
if (str.isEmpty()) {
textEdit_new->setPlainText(DefaultTemplates::defaultNewMessage());
} else {
textEdit_new->setPlainText(str);
}
str = TemplateParserSettings::self()->templateReply();
if (str.isEmpty()) {
textEdit_reply->setPlainText(DefaultTemplates::defaultReply());
} else {
textEdit_reply->setPlainText(str);
}
str = TemplateParserSettings::self()->templateReplyAll();
if (str.isEmpty()) {
textEdit_reply_all->setPlainText(DefaultTemplates::defaultReplyAll());
} else {
textEdit_reply_all->setPlainText(str);
}
str = TemplateParserSettings::self()->templateForward();
if (str.isEmpty()) {
textEdit_forward->setPlainText(DefaultTemplates::defaultForward());
} else {
textEdit_forward->setPlainText(str);
}
str = TemplateParserSettings::self()->quoteString();
if (str.isEmpty()) {
lineEdit_quote->setText(DefaultTemplates::defaultQuoteString());
} else {
lineEdit_quote->setText(str);
}
}
void TemplatesConfiguration::saveToGlobal()
{
TemplateParserSettings::self()->setTemplateNewMessage(strOrBlank(textEdit_new->toPlainText()));
TemplateParserSettings::self()->setTemplateReply(strOrBlank(textEdit_reply->toPlainText()));
TemplateParserSettings::self()->setTemplateReplyAll(strOrBlank(textEdit_reply_all->toPlainText()));
TemplateParserSettings::self()->setTemplateForward(strOrBlank(textEdit_forward->toPlainText()));
TemplateParserSettings::self()->setQuoteString(lineEdit_quote->text());
TemplateParserSettings::self()->save();
}
void TemplatesConfiguration::loadFromIdentity(uint id)
{
Templates t(configIdString(id));
QString str;
str = t.templateNewMessage();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateNewMessage();
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultNewMessage();
}
textEdit_new->setPlainText(str);
str = t.templateReply();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateReply();
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultReply();
}
textEdit_reply->setPlainText(str);
str = t.templateReplyAll();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateReplyAll();
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultReplyAll();
}
textEdit_reply_all->setPlainText(str);
str = t.templateForward();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateForward();
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultForward();
}
textEdit_forward->setPlainText(str);
str = t.quoteString();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->quoteString();
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultQuoteString();
}
lineEdit_quote->setText(str);
}
void TemplatesConfiguration::saveToIdentity(uint id)
{
Templates t(configIdString(id));
t.setTemplateNewMessage(strOrBlank(textEdit_new->toPlainText()));
t.setTemplateReply(strOrBlank(textEdit_reply->toPlainText()));
t.setTemplateReplyAll(strOrBlank(textEdit_reply_all->toPlainText()));
t.setTemplateForward(strOrBlank(textEdit_forward->toPlainText()));
t.setQuoteString(lineEdit_quote->text());
t.save();
}
void TemplatesConfiguration::loadFromFolder(const QString &id, uint identity)
{
Templates t(id);
Templates *tid = nullptr;
if (identity) {
tid = new Templates(configIdString(identity));
}
QString str;
str = t.templateNewMessage();
if (str.isEmpty() && tid) {
str = tid->templateNewMessage();
}
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateNewMessage();
if (str.isEmpty()) {
str = DefaultTemplates::defaultNewMessage();
}
}
textEdit_new->setPlainText(str);
str = t.templateReply();
if (str.isEmpty() && tid) {
str = tid->templateReply();
}
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateReply();
if (str.isEmpty()) {
str = DefaultTemplates::defaultReply();
}
}
textEdit_reply->setPlainText(str);
str = t.templateReplyAll();
if (str.isEmpty() && tid) {
str = tid->templateReplyAll();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateReplyAll();
}
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultReplyAll();
}
textEdit_reply_all->setPlainText(str);
str = t.templateForward();
if (str.isEmpty() && tid) {
str = tid->templateForward();
if (str.isEmpty()) {
str = TemplateParserSettings::self()->templateForward();
}
}
if (str.isEmpty()) {
str = DefaultTemplates::defaultForward();
}
textEdit_forward->setPlainText(str);
str = t.quoteString();
if (str.isEmpty() && tid) {
str = tid->quoteString();
}
if (str.isEmpty()) {
str = TemplateParserSettings::self()->quoteString();
if (str.isEmpty()) {
str = DefaultTemplates::defaultQuoteString();
}
}
lineEdit_quote->setText(str);
delete(tid);
}
void TemplatesConfiguration::saveToFolder(const QString &id)
{
Templates t(id);
t.setTemplateNewMessage(strOrBlank(textEdit_new->toPlainText()));
t.setTemplateReply(strOrBlank(textEdit_reply->toPlainText()));
t.setTemplateReplyAll(strOrBlank(textEdit_reply_all->toPlainText()));
t.setTemplateForward(strOrBlank(textEdit_forward->toPlainText()));
t.setQuoteString(lineEdit_quote->text());
t.save();
}
QPlainTextEdit *TemplatesConfiguration::currentTextEdit() const
{
QPlainTextEdit *edit = nullptr;
const int toolboxCurrentIndex(toolBox1->currentIndex());
if (toolBox1->widget(toolboxCurrentIndex) == page_new) {
edit = textEdit_new->editor();
} else if (toolBox1->widget(toolboxCurrentIndex) == page_reply) {
edit = textEdit_reply->editor();
} else if (toolBox1->widget(toolboxCurrentIndex) == page_reply_all) {
edit = textEdit_reply_all->editor();
} else if (toolBox1->widget(toolboxCurrentIndex) == page_forward) {
edit = textEdit_forward->editor();
} else {
qCDebug(TEMPLATEPARSER_LOG) << "Unknown current page in TemplatesConfiguration!";
edit = nullptr;
}
return edit;
}
void TemplatesConfiguration::slotInsertCommand(const QString &cmd, int adjustCursor)
{
QPlainTextEdit *edit = currentTextEdit();
if (!edit) {
return;
}
// qCDebug(TEMPLATEPARSER_LOG) << "Insert command:" << cmd;
const QString editText(edit->toPlainText());
if ((editText.contains(QLatin1String("%FORCEDPLAIN")) && (cmd == QLatin1String("%FORCEDHTML")))
|| (editText.contains(QLatin1String("%FORCEDHTML")) && (cmd == QLatin1String("%FORCEDPLAIN")))) {
KMessageBox::error(
this,
i18n("Use of \"Reply using plain text\" and \"Reply using HTML text\" in pairs"
" is not correct. Use only one of the aforementioned commands with \" Reply as"
" Quoted Message command\" as per your need\n"
"(a)Reply using plain text for quotes to be strictly in plain text\n"
"(b)Reply using HTML text for quotes being in HTML format if present"));
} else {
QTextCursor cursor = edit->textCursor();
cursor.insertText(cmd);
cursor.setPosition(cursor.position() + adjustCursor);
edit->setTextCursor(cursor);
edit->setFocus();
}
}
QString TemplatesConfiguration::strOrBlank(const QString &str)
{
if (str.trimmed().isEmpty()) {
return QStringLiteral("%BLANK");
}
return str;
}
QString TemplatesConfiguration::configIdString(uint id)
{
return QStringLiteral("IDENTITY_%1").arg(id);
}
diff --git a/templateparser/src/templatesinsertcommandaction.cpp b/templateparser/src/templatesinsertcommandaction.cpp
index a28a390c..7caab356 100644
--- a/templateparser/src/templatesinsertcommandaction.cpp
+++ b/templateparser/src/templatesinsertcommandaction.cpp
@@ -1,50 +1,47 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-
-
#include "templatesinsertcommandaction.h"
using namespace TemplateParser;
TemplatesInsertCommandAction::TemplatesInsertCommandAction(QObject *parent)
: QWidgetAction(parent)
{
mMenuCommand = new TemplatesCommandMenu(this);
mMenuCommand->fillMenu();
mMenuCommand->fillSubMenus();
mMenuCommand->setObjectName(QStringLiteral("templatescommandmenu"));
- connect(mMenuCommand, QOverload<const QString &, int>::of(&TemplatesCommandMenu::insertCommand), this, &TemplatesInsertCommandAction::insertCommand);
+ connect(mMenuCommand, qOverload<const QString &, int>(&TemplatesCommandMenu::insertCommand), this, &TemplatesInsertCommandAction::insertCommand);
setMenu(mMenuCommand->menu());
}
TemplatesInsertCommandAction::~TemplatesInsertCommandAction()
{
-
}
TemplatesCommandMenu::MenuTypes TemplatesInsertCommandAction::type() const
{
return mMenuCommand->type();
}
void TemplatesInsertCommandAction::setType(TemplatesCommandMenu::MenuTypes type)
{
mMenuCommand->setType(type);
setMenu(mMenuCommand->menu());
}
diff --git a/templateparser/src/templatesinsertcommandaction.h b/templateparser/src/templatesinsertcommandaction.h
index 868292d4..9b5d71c8 100644
--- a/templateparser/src/templatesinsertcommandaction.h
+++ b/templateparser/src/templatesinsertcommandaction.h
@@ -1,47 +1,45 @@
/*
- Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2018-2019 Laurent Montel <montel@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.
*/
-
#ifndef TEMPLATESINSERTCOMMANDACTION_H
#define TEMPLATESINSERTCOMMANDACTION_H
#include <QWidgetAction>
#include <TemplateParser/TemplatesCommandMenu>
#include "templateparser_export.h"
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplatesInsertCommandAction : public QWidgetAction
{
Q_OBJECT
public:
explicit TemplatesInsertCommandAction(QObject *parent = nullptr);
~TemplatesInsertCommandAction();
TemplatesCommandMenu::MenuTypes type() const;
void setType(TemplatesCommandMenu::MenuTypes type);
Q_SIGNALS:
void insertCommand(const QString &cmd, int adjustCursor);
private:
TemplatesCommandMenu *mMenuCommand = nullptr;
};
-
}
#endif // TEMPLATESINSERTCOMMANDACTION_H
diff --git a/templateparser/src/templatesinsertcommandpushbutton.cpp b/templateparser/src/templatesinsertcommandpushbutton.cpp
index 5009de1b..d669434f 100644
--- a/templateparser/src/templatesinsertcommandpushbutton.cpp
+++ b/templateparser/src/templatesinsertcommandpushbutton.cpp
@@ -1,68 +1,65 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
- * Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2018-2019 Laurent Montel <montel@kde.org>
*
* 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.
*/
#include "templatesinsertcommandpushbutton.h"
#include <QAction>
#include <KActionMenu>
#include "templateparser_debug.h"
#include <KLocalizedString>
-
using namespace TemplateParser;
-
TemplatesInsertCommandPushButton::TemplatesInsertCommandPushButton(QWidget *parent, const QString &name)
: QPushButton(parent)
{
setObjectName(name);
setText(i18n("&Insert Command"));
mMenuCommand = new TemplatesCommandMenu(this);
mMenuCommand->setObjectName(QStringLiteral("templatescommandmenu"));
mMenuCommand->fillMenu();
mMenuCommand->fillSubMenus();
setMenu(mMenuCommand->menu());
- connect(mMenuCommand, QOverload<const QString &, int>::of(&TemplatesCommandMenu::insertCommand), this, &TemplatesInsertCommandPushButton::insertCommand);
+ connect(mMenuCommand, qOverload<const QString &, int>(&TemplatesCommandMenu::insertCommand), this, &TemplatesInsertCommandPushButton::insertCommand);
setToolTip(
i18nc("@info:tooltip",
"Select a command to insert into the template"));
setWhatsThis(
i18nc("@info:whatsthis",
"Traverse this menu to find a command to insert into the current template "
"being edited. The command will be inserted at the cursor location, "
"so you want to move your cursor to the desired insertion point first."));
}
TemplatesInsertCommandPushButton::~TemplatesInsertCommandPushButton()
{
}
TemplatesCommandMenu::MenuTypes TemplatesInsertCommandPushButton::type() const
{
return mMenuCommand->type();
}
void TemplatesInsertCommandPushButton::setType(TemplatesCommandMenu::MenuTypes type)
{
mMenuCommand->setType(type);
setMenu(mMenuCommand->menu());
}
-
diff --git a/templateparser/src/templatesinsertcommandpushbutton.h b/templateparser/src/templatesinsertcommandpushbutton.h
index ae1720ef..b85f540d 100644
--- a/templateparser/src/templatesinsertcommandpushbutton.h
+++ b/templateparser/src/templatesinsertcommandpushbutton.h
@@ -1,48 +1,48 @@
/*
* Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
- * Copyright (C) 2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2018-2019 Laurent Montel <montel@kde.org>
*
* 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.
*/
#ifndef TEMPLATEPARSER_TEMPLATESINSERTCOMMANDPUSHBUTTON_H
#define TEMPLATEPARSER_TEMPLATESINSERTCOMMANDPUSHBUTTON_H
#include <QPushButton>
#include "templateparser_export.h"
#include <TemplateParser/TemplatesCommandMenu>
class KActionMenu;
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplatesInsertCommandPushButton : public QPushButton
{
Q_OBJECT
public:
explicit TemplatesInsertCommandPushButton(QWidget *parent, const QString &name = QString());
~TemplatesInsertCommandPushButton();
TemplatesCommandMenu::MenuTypes type() const;
void setType(TemplatesCommandMenu::MenuTypes type);
Q_SIGNALS:
void insertCommand(const QString &cmd, int adjustCursor = 0);
private:
TemplatesCommandMenu *mMenuCommand = nullptr;
};
}
#endif
diff --git a/templateparser/src/templatestextedit.cpp b/templateparser/src/templatestextedit.cpp
index d916665f..0d3ce9fb 100644
--- a/templateparser/src/templatestextedit.cpp
+++ b/templateparser/src/templatestextedit.cpp
@@ -1,31 +1,31 @@
-/* Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2011-2019 Laurent Montel <montel@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.
*/
#include "templatestextedit.h"
#include "templatestexteditor.h"
using namespace TemplateParser;
TemplatesTextEdit::TemplatesTextEdit(QWidget *parent)
: KPIMTextEdit::PlainTextEditorWidget(new TemplatesTextEditor, parent)
{
}
TemplatesTextEdit::~TemplatesTextEdit()
{
}
diff --git a/templateparser/src/templatestextedit.h b/templateparser/src/templatestextedit.h
index 3f90c9c3..19457454 100644
--- a/templateparser/src/templatestextedit.h
+++ b/templateparser/src/templatestextedit.h
@@ -1,36 +1,36 @@
-/* Copyright (C) 2011-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2011-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEPARSER_TEMPLATESTEXTEDIT_H
#define TEMPLATEPARSER_TEMPLATESTEXTEDIT_H
#include "templateparser_export.h"
#include "kpimtextedit/plaintexteditorwidget.h"
namespace TemplateParser {
class TEMPLATEPARSER_EXPORT TemplatesTextEdit : public KPIMTextEdit::PlainTextEditorWidget
{
Q_OBJECT
public:
explicit TemplatesTextEdit(QWidget *parent = nullptr);
~TemplatesTextEdit();
};
}
#endif
diff --git a/templateparser/src/templatestexteditor.cpp b/templateparser/src/templatestexteditor.cpp
index cc9ff8da..5e68fe1a 100644
--- a/templateparser/src/templatestexteditor.cpp
+++ b/templateparser/src/templatestexteditor.cpp
@@ -1,114 +1,115 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "templatestexteditor.h"
#include "templatesutil_p.h"
#include <KPIMTextEdit/TextEditorCompleter>
#include <KPIMTextEdit/PlainTextSyntaxSpellCheckingHighlighter>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Theme>
#include <QKeyEvent>
#include <QAbstractItemView>
#include <QFontDatabase>
#include <QCompleter>
using namespace TemplateParser;
TemplatesTextEditor::TemplatesTextEditor(QWidget *parent)
: KPIMTextEdit::PlainTextEditor(parent)
{
setFocus();
const QFont f = QFontDatabase::systemFont(QFontDatabase::FixedFont);
setFont(f);
QStringList excludeKeyWord;
const QStringList lst = TemplateParser::Util::keywords();
for (QString str : lst) {
excludeKeyWord << str.remove(QLatin1Char('%'));
excludeKeyWord << str.replace(QLatin1String("\\("), QLatin1String("("));
}
addIgnoreWords(excludeKeyWord);
setWordWrapMode(QTextOption::NoWrap);
initCompleter();
createHighlighter();
}
TemplatesTextEditor::~TemplatesTextEditor()
{
}
void TemplatesTextEditor::updateHighLighter()
{
KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter *hlighter = dynamic_cast<KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter *>(highlighter());
if (hlighter) {
hlighter->toggleSpellHighlighting(checkSpellingEnabled());
}
}
void TemplatesTextEditor::clearDecorator()
{
//Nothing
}
void TemplatesTextEditor::createHighlighter()
{
KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter *highlighter = new KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter(this);
highlighter->toggleSpellHighlighting(checkSpellingEnabled());
highlighter->setCurrentLanguage(spellCheckingLanguage());
highlighter->setDefinition(mSyntaxRepo.definitionForName(QStringLiteral("KMail Template")));
highlighter->setTheme((palette().color(QPalette::Base).lightness() < 128)
? mSyntaxRepo.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme)
: mSyntaxRepo.defaultTheme(KSyntaxHighlighting::Repository::LightTheme));
setHighlighter(highlighter);
}
void TemplatesTextEditor::initCompleter()
{
QStringList listWord;
QStringList excludeKeyWord;
const QStringList lst = TemplateParser::Util::keywords();
excludeKeyWord.reserve(lst.count());
for (QString str : lst) {
excludeKeyWord << str.replace(QLatin1String("\\("), QLatin1String("("));
}
listWord << excludeKeyWord;
listWord << Util::keywordsWithArgs();
mTextEditorCompleter = new KPIMTextEdit::TextEditorCompleter(this, this);
mTextEditorCompleter->setCompleterStringList(listWord);
mTextEditorCompleter->setExcludeOfCharacters(QStringLiteral("~!@#$^&*()+{}|\"<>,./;'[]\\-= "));
}
void TemplatesTextEditor::keyPressEvent(QKeyEvent *e)
{
if (mTextEditorCompleter->completer()->popup()->isVisible()) {
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
KPIMTextEdit::PlainTextEditor::keyPressEvent(e);
mTextEditorCompleter->completeText();
}
diff --git a/templateparser/src/templatestexteditor.h b/templateparser/src/templatestexteditor.h
index 33bbef9b..5c891dd9 100644
--- a/templateparser/src/templatestexteditor.h
+++ b/templateparser/src/templatestexteditor.h
@@ -1,50 +1,51 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#ifndef TEMPLATESTEXTEDITOR_H
#define TEMPLATESTEXTEDITOR_H
#include "kpimtextedit/plaintexteditor.h"
#include <KSyntaxHighlighting/Repository>
class QKeyEvent;
namespace KPIMTextEdit {
class TextEditorCompleter;
}
namespace TemplateParser {
class TemplatesTextEditor : public KPIMTextEdit::PlainTextEditor
{
Q_OBJECT
public:
explicit TemplatesTextEditor(QWidget *parent = nullptr);
~TemplatesTextEditor() override;
protected:
void initCompleter();
void keyPressEvent(QKeyEvent *e) override;
void updateHighLighter() override;
void clearDecorator() override;
void createHighlighter() override;
private:
KPIMTextEdit::TextEditorCompleter *mTextEditorCompleter = nullptr;
KSyntaxHighlighting::Repository mSyntaxRepo;
};
}
#endif // TEMPLATESTEXTEDITOR_H
diff --git a/templateparser/src/templatesutil.cpp b/templateparser/src/templatesutil.cpp
index dd91f5df..77b3e650 100644
--- a/templateparser/src/templatesutil.cpp
+++ b/templateparser/src/templatesutil.cpp
@@ -1,124 +1,125 @@
/*
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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
*/
#include "templatesutil.h"
#include "templatesutil_p.h"
#include <KConfigGroup>
#include <KSharedConfig>
#include <QStringList>
using namespace TemplateParser;
void TemplateParser::Util::deleteTemplate(const QString &id)
{
KSharedConfig::Ptr config
= KSharedConfig::openConfig(QStringLiteral("templatesconfigurationrc"), KConfig::NoGlobals);
const QString key = QStringLiteral("Templates #%1").arg(id);
if (config->hasGroup(key)) {
KConfigGroup group = config->group(key);
group.deleteGroup();
group.sync();
}
}
QStringList TemplateParser::Util::keywordsWithArgs()
{
const QStringList keywordsWithArgs = QStringList()
<< QStringLiteral("%REM=\"\"%-")
<< QStringLiteral("%INSERT=\"\"")
<< QStringLiteral("%SYSTEM=\"\"")
<< QStringLiteral("%QUOTEPIPE=\"\"")
<< QStringLiteral("%MSGPIPE=\"\"")
<< QStringLiteral("%BODYPIPE=\"\"")
<< QStringLiteral("%CLEARPIPE=\"\"")
<< QStringLiteral("%TEXTPIPE=\"\"")
<< QStringLiteral("%OHEADER=\"\"")
<< QStringLiteral("%HEADER=\"\"")
<< QStringLiteral("%DICTIONARYLANGUAGE=\"\"")
<< QStringLiteral("%LANGUAGE=\"\"");
return keywordsWithArgs;
}
QStringList TemplateParser::Util::keywords()
{
const QStringList keywords = QStringList()
<< QStringLiteral("%QUOTE")
<< QStringLiteral("%FORCEDPLAIN")
<< QStringLiteral("%FORCEDHTML")
<< QStringLiteral("%QHEADERS")
<< QStringLiteral("%HEADERS")
<< QStringLiteral("%TEXT")
<< QStringLiteral("%OTEXTSIZE")
<< QStringLiteral("%OTEXT")
<< QStringLiteral("%OADDRESSEESADDR")
<< QStringLiteral("%CCADDR")
<< QStringLiteral("%CCNAME")
<< QStringLiteral("%CCFNAME")
<< QStringLiteral("%CCLNAME")
<< QStringLiteral("%TOADDR")
<< QStringLiteral("%TONAME")
<< QStringLiteral("%TOFNAME")
<< QStringLiteral("%TOLNAME")
<< QStringLiteral("%TOLIST")
<< QStringLiteral("%FROMADDR")
<< QStringLiteral("%FROMNAME")
<< QStringLiteral("%FROMFNAME")
<< QStringLiteral("%FROMLNAME")
<< QStringLiteral("%FULLSUBJECT")
<< QStringLiteral("%MSGID")
<< QStringLiteral("%HEADER\\( ")
<< QStringLiteral("%OCCADDR")
<< QStringLiteral("%OCCNAME")
<< QStringLiteral("%OCCFNAME")
<< QStringLiteral("%OCCLNAME")
<< QStringLiteral("%OTOADDR")
<< QStringLiteral("%OTONAME")
<< QStringLiteral("%OTOFNAME")
<< QStringLiteral("%OTOLNAME")
<< QStringLiteral("%OTOLIST")
<< QStringLiteral("%OTO")
<< QStringLiteral("%OFROMADDR")
<< QStringLiteral("%OFROMNAME")
<< QStringLiteral("%OFROMFNAME")
<< QStringLiteral("%OFROMLNAME")
<< QStringLiteral("%OFULLSUBJECT")
<< QStringLiteral("%OFULLSUBJ")
<< QStringLiteral("%OMSGID")
<< QStringLiteral("%DATEEN")
<< QStringLiteral("%DATESHORT")
<< QStringLiteral("%DATE")
<< QStringLiteral("%DOW")
<< QStringLiteral("%TIMELONGEN")
<< QStringLiteral("%TIMELONG")
<< QStringLiteral("%TIME")
<< QStringLiteral("%ODATEEN")
<< QStringLiteral("%ODATESHORT")
<< QStringLiteral("%ODATE")
<< QStringLiteral("%ODOW")
<< QStringLiteral("%OTIMELONGEN")
<< QStringLiteral("%OTIMELONG")
<< QStringLiteral("%OTIME")
<< QStringLiteral("%BLANK")
<< QStringLiteral("%NOP")
<< QStringLiteral("%CLEAR")
<< QStringLiteral("%DEBUGOFF")
<< QStringLiteral("%DEBUG")
<< QStringLiteral("%CURSOR")
<< QStringLiteral("%SIGNATURE");
return keywords;
}
diff --git a/templateparser/src/templatesutil.h b/templateparser/src/templatesutil.h
index e7c3da3a..78babbee 100644
--- a/templateparser/src/templatesutil.h
+++ b/templateparser/src/templatesutil.h
@@ -1,30 +1,31 @@
/*
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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 progam is distributed in the hope that it will be useful, but
+ 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
*/
#ifndef TEMPLATEPARSER_TEMPLATESUTIL_H
#define TEMPLATEPARSER_TEMPLATESUTIL_H
#include "templateparser_export.h"
class QString;
namespace TemplateParser {
namespace Util {
TEMPLATEPARSER_EXPORT void deleteTemplate(const QString &id);
}
}
#endif
diff --git a/templateparser/src/templatesutil_p.h b/templateparser/src/templatesutil_p.h
index b2471f66..f72c7ba2 100644
--- a/templateparser/src/templatesutil_p.h
+++ b/templateparser/src/templatesutil_p.h
@@ -1,32 +1,33 @@
/*
- Copyright (c) 2011-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2011-2019 Montel Laurent <montel@kde.org>
This program 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.
+ 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 progam is distributed in the hope that it will be useful, but
+ 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
*/
#ifndef TEMPLATEPARSER_TEMPLATESUTIL_P_H
#define TEMPLATEPARSER_TEMPLATESUTIL_P_H
#include "templateparser_export.h"
class QString;
class QStringList;
namespace TemplateParser {
namespace Util {
QStringList keywords();
QStringList keywordsWithArgs();
}
}
#endif
diff --git a/templateparser/src/templatewebenginepage.cpp b/templateparser/src/templatewebenginepage.cpp
index 2f2fae5a..c780348c 100644
--- a/templateparser/src/templatewebenginepage.cpp
+++ b/templateparser/src/templatewebenginepage.cpp
@@ -1,54 +1,58 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templatewebenginepage.h"
#include <QWebEngineSettings>
#include <QWebEngineProfile>
+#include <QtWebEngineWidgets>
using namespace TemplateParser;
TemplateWebEnginePage::TemplateWebEnginePage(QObject *parent)
: QWebEnginePage(parent)
{
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
settings()->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
settings()->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, false);
settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
settings()->setAttribute(QWebEngineSettings::XSSAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, false);
settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false);
settings()->setAttribute(QWebEngineSettings::HyperlinkAuditingEnabled, false);
settings()->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, false);
settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
settings()->setAttribute(QWebEngineSettings::WebGLEnabled, false);
settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false);
settings()->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, false);
settings()->setAttribute(QWebEngineSettings::WebGLEnabled, false);
settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, false);
+#if QTWEBENGINEWIDGETS_VERSION >= QT_VERSION_CHECK(5, 13, 0)
+ settings()->setAttribute(QWebEngineSettings::PdfViewerEnabled, false);
+#endif
profile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
profile()->setHttpCacheType(QWebEngineProfile::MemoryHttpCache);
}
TemplateWebEnginePage::~TemplateWebEnginePage()
{
}
diff --git a/templateparser/src/templatewebenginepage.h b/templateparser/src/templatewebenginepage.h
index 94e9320b..025c6a3b 100644
--- a/templateparser/src/templatewebenginepage.h
+++ b/templateparser/src/templatewebenginepage.h
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEWEBENGINEPAGE_H
#define TEMPLATEWEBENGINEPAGE_H
#include <QWebEnginePage>
namespace TemplateParser {
class TemplateWebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
explicit TemplateWebEnginePage(QObject *parent = nullptr);
~TemplateWebEnginePage();
};
}
#endif // TEMPLATEWEBENGINEPAGE_H
diff --git a/templateparser/src/templatewebengineview.cpp b/templateparser/src/templatewebengineview.cpp
index ec7c27e6..f831b694 100644
--- a/templateparser/src/templatewebengineview.cpp
+++ b/templateparser/src/templatewebengineview.cpp
@@ -1,80 +1,80 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "templatewebengineview.h"
#include "templatewebenginepage.h"
#include "templateparser_debug.h"
using namespace TemplateParser;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFun)(Arg);
void operator()(Arg result)
{
(receiver->*memberFun)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
return wrapper;
}
TemplateWebEngineView::TemplateWebEngineView(QWidget *parent)
: QWebEngineView(parent)
{
mPage = new TemplateWebEnginePage(this);
setPage(mPage);
connect(mPage, &TemplateWebEnginePage::loadFinished, this, &TemplateWebEngineView::slotLoadFinished);
}
TemplateWebEngineView::~TemplateWebEngineView()
{
}
void TemplateWebEngineView::setHtmlContent(const QString &html)
{
mExtractedPlainText.clear();
setHtml(html);
}
void TemplateWebEngineView::slotLoadFinished(bool ok)
{
if (ok) {
mPage->toPlainText(invoke(this, &TemplateWebEngineView::setPlainText));
} else {
qCWarning(TEMPLATEPARSER_LOG) << "Loading page failed";
Q_EMIT loadContentDone(false);
}
}
void TemplateWebEngineView::setPlainText(const QString &plainText)
{
mExtractedPlainText = plainText;
Q_EMIT loadContentDone(true);
}
QString TemplateWebEngineView::plainText() const
{
return mExtractedPlainText;
}
diff --git a/templateparser/src/templatewebengineview.h b/templateparser/src/templatewebengineview.h
index cced867c..fc77eab5 100644
--- a/templateparser/src/templatewebengineview.h
+++ b/templateparser/src/templatewebengineview.h
@@ -1,50 +1,50 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TEMPLATEWEBENGINEVIEW_H
#define TEMPLATEWEBENGINEVIEW_H
#include <QWebEngineView>
#include "templateparser_export.h"
namespace TemplateParser {
class TemplateWebEnginePage;
class TEMPLATEPARSER_EXPORT TemplateWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
explicit TemplateWebEngineView(QWidget *parent = nullptr);
~TemplateWebEngineView();
void setHtmlContent(const QString &html);
QString plainText() const;
Q_SIGNALS:
void loadContentDone(bool success);
private:
void slotLoadFinished(bool ok);
void setPlainText(const QString &plainText);
QString mExtractedPlainText;
TemplateWebEnginePage *mPage = nullptr;
};
}
#endif // TEMPLATEWEBENGINEVIEW_H
diff --git a/templateparser/tests/templateconfigurewidget_gui.cpp b/templateparser/tests/templateconfigurewidget_gui.cpp
index 947987af..c3ffd4d7 100644
--- a/templateparser/tests/templateconfigurewidget_gui.cpp
+++ b/templateparser/tests/templateconfigurewidget_gui.cpp
@@ -1,52 +1,52 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "templateconfigurewidget_gui.h"
#include "templatesconfiguration.h"
#include <QApplication>
#include <QCommandLineParser>
#include <QStandardPaths>
TemplateConfigureTestWidget::TemplateConfigureTestWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
lay->addWidget(new TemplateParser::TemplatesConfiguration(this));
}
TemplateConfigureTestWidget::~TemplateConfigureTestWidget()
{
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
TemplateConfigureTestWidget *w = new TemplateConfigureTestWidget();
w->resize(800, 600);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/templateparser/tests/templateconfigurewidget_gui.h b/templateparser/tests/templateconfigurewidget_gui.h
index 70e5f2fa..47db8644 100644
--- a/templateparser/tests/templateconfigurewidget_gui.h
+++ b/templateparser/tests/templateconfigurewidget_gui.h
@@ -1,31 +1,31 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef TEST_TEMPLATECONFIGUREWIDGET_GUI_H
#define TEST_TEMPLATECONFIGUREWIDGET_GUI_H
#include <QWidget>
class TemplateConfigureTestWidget : public QWidget
{
Q_OBJECT
public:
explicit TemplateConfigureTestWidget(QWidget *parent = nullptr);
~TemplateConfigureTestWidget();
};
#endif
diff --git a/templateparser/tests/templateeditor_gui.cpp b/templateparser/tests/templateeditor_gui.cpp
index 17f7bcc3..a45f0d57 100644
--- a/templateparser/tests/templateeditor_gui.cpp
+++ b/templateparser/tests/templateeditor_gui.cpp
@@ -1,54 +1,54 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "templateeditor_gui.h"
#include "templateparser/templatestextedit.h"
#include <QHBoxLayout>
#include <QApplication>
#include <QCommandLineParser>
#include <QStandardPaths>
TemplateEditorTestWidget::TemplateEditorTestWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
lay->addWidget(new TemplateParser::TemplatesTextEdit(this));
}
TemplateEditorTestWidget::~TemplateEditorTestWidget()
{
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
TemplateEditorTestWidget *w = new TemplateEditorTestWidget();
w->resize(800, 600);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/templateparser/tests/templateeditor_gui.h b/templateparser/tests/templateeditor_gui.h
index de14d20c..e84fb586 100644
--- a/templateparser/tests/templateeditor_gui.h
+++ b/templateparser/tests/templateeditor_gui.h
@@ -1,31 +1,31 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef TEST_TEMPLATEEDITOR_GUI_H
#define TEST_TEMPLATEEDITOR_GUI_H
#include <QWidget>
class TemplateEditorTestWidget : public QWidget
{
Q_OBJECT
public:
explicit TemplateEditorTestWidget(QWidget *parent = nullptr);
~TemplateEditorTestWidget();
};
#endif
diff --git a/templateparser/tests/templateparseremailrequester.cpp b/templateparser/tests/templateparseremailrequester.cpp
index 8f9fead0..5035f157 100644
--- a/templateparser/tests/templateparseremailrequester.cpp
+++ b/templateparser/tests/templateparseremailrequester.cpp
@@ -1,56 +1,56 @@
/*
- Copyright (c) 2013-2018 Montel Laurent <montel@kde.org>
+ Copyright (c) 2013-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#include "templateparseremailrequester.h"
#include "templateparser/templatestextedit.h"
#include "templateparseremailaddressrequesterinterfacewidget.h"
#include <QHBoxLayout>
#include <QApplication>
#include <QCommandLineParser>
#include <QStandardPaths>
TemplateParserEmailRequesterTestWidget::TemplateParserEmailRequesterTestWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget *w = new TemplateParser::TemplateParserEmailAddressRequesterInterfaceWidget(this);
lay->addWidget(w);
}
TemplateParserEmailRequesterTestWidget::~TemplateParserEmailRequesterTestWidget()
{
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
QCommandLineParser parser;
parser.addVersionOption();
parser.addHelpOption();
parser.process(app);
TemplateParserEmailRequesterTestWidget *w = new TemplateParserEmailRequesterTestWidget();
w->resize(800, 600);
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/templateparser/tests/templateparseremailrequester.h b/templateparser/tests/templateparseremailrequester.h
index 49e68eaf..d339b420 100644
--- a/templateparser/tests/templateparseremailrequester.h
+++ b/templateparser/tests/templateparseremailrequester.h
@@ -1,31 +1,31 @@
/*
- Copyright (C) 2017-2018 Montel Laurent <montel@kde.org>
+ Copyright (C) 2017-2019 Montel Laurent <montel@kde.org>
This program 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.
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
*/
#ifndef TEST_TEMPLATEPARSEREMAILREQUESTER_GUI_H
#define TEST_TEMPLATEPARSEREMAILREQUESTER_GUI_H
#include <QWidget>
class TemplateParserEmailRequesterTestWidget : public QWidget
{
Q_OBJECT
public:
explicit TemplateParserEmailRequesterTestWidget(QWidget *parent = nullptr);
~TemplateParserEmailRequesterTestWidget();
};
#endif
diff --git a/webengineviewer/metainfo.yaml b/webengineviewer/metainfo.yaml
index 4a529195..29860524 100644
--- a/webengineviewer/metainfo.yaml
+++ b/webengineviewer/metainfo.yaml
@@ -1,14 +1,14 @@
maintainer: mlaurent
description: WebEngineViewer Library
tier: 3
type: functional
platforms:
- name: All
portingAid: false
deprecated: false
release: false
libraries:
- qmake: WebEngineViewer
cmake: "KF5::WebEngineViewer"
- cmakename: KF5WebEngineViewer
+cmakename: KF5WebEngineViewer
diff --git a/webengineviewer/src/CMakeLists.txt b/webengineviewer/src/CMakeLists.txt
index d7b9a13c..faf097b3 100644
--- a/webengineviewer/src/CMakeLists.txt
+++ b/webengineviewer/src/CMakeLists.txt
@@ -1,225 +1,223 @@
add_definitions(-DTRANSLATION_DOMAIN=\"libwebengineviewer\")
include_directories(${CMAKE_BINARY_DIR}/webengineviewer/src ${CMAKE_BINARY_DIR}/webengineviewer)
if(BUILD_TESTING)
add_subdirectory(tests)
add_subdirectory(autotests)
add_subdirectory(webengineaccesskey/autotests)
add_subdirectory(findbar/autotests)
add_subdirectory(checkphishingurl/autotests/)
add_subdirectory(checkphishingurl/tests/)
endif()
set(libwebengineviewer_webengine_SRCS
networkmanager/interceptormanager.cpp
webhittestresult.cpp
webhittest.cpp
webenginepage.cpp
webenginescript.cpp
webengineview.cpp
webenginemanagescript.cpp
webengineexporthtmlpagejob.cpp
webenginenavigationrequestinterceptor.cpp
)
set(libwebengineviewer_checkphishingurl_SRCS
checkphishingurl/checkphishingurljob.cpp
checkphishingurl/checkphishingurlcache.cpp
checkphishingurl/createphishingurldatabasejob.cpp
checkphishingurl/localdatabasemanager.cpp
checkphishingurl/checkphishingurlutil.cpp
checkphishingurl/searchfullhashjob.cpp
checkphishingurl/localdatabasefile.cpp
checkphishingurl/createdatabasefilejob.cpp
checkphishingurl/updatedatabaseinfo.cpp
checkphishingurl/riceencodingdecoder.cpp
checkphishingurl/urlhashing.cpp
checkphishingurl/hashcachemanager.cpp
checkphishingurl/backoffmodemanager.cpp
checkphishingurl/downloadlocaldatabasethread.cpp
)
set(libwebengineviewer_interceptor_SRCS
urlinterceptor/networkurlinterceptor.cpp
urlinterceptor/networkpluginurlinterceptorinterface.cpp
urlinterceptor/networkurlinterceptorpluginmanager.cpp
urlinterceptor/networkpluginurlinterceptor.cpp
urlinterceptor/networkurlinterceptormanager.cpp
urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
)
set(libwebengineviewer_accesskey_SRCS
webengineaccesskey/webengineaccesskey.cpp
webengineaccesskey/webengineaccesskeyanchor.cpp
webengineaccesskey/webengineaccesskeyutils.cpp
)
set(libwebengineviewer_findbar_SRCS
findbar/findbarbase.cpp
findbar/findbarwebengineview.cpp
)
set(libwebengineviewer_widgets_SRCS
widgets/zoomactionmenu.cpp
)
set(libwebengineviewer_SRCS
${libwebengineviewer_checkphishingurl_SRCS}
${libwebengineviewer_interceptor_SRCS}
${libwebengineviewer_webengine_SRCS}
${libwebengineviewer_findbar_SRCS}
${libwebengineviewer_widgets_SRCS}
${libwebengineviewer_print_SRCS}
${libwebengineviewer_accesskey_SRCS}
)
-qt5_add_resources(libwebengineviewer_webengine_SRCS jquery.qrc)
-
ecm_qt_declare_logging_category(libwebengineviewer_webengine_SRCS HEADER webengineviewer_debug.h IDENTIFIER WEBENGINEVIEWER_LOG CATEGORY_NAME org.kde.pim.webengineviewer)
add_library(KF5WebEngineViewer ${libwebengineviewer_SRCS} ${libwebengineviewer_webengine_SRCS})
generate_export_header(KF5WebEngineViewer BASE_NAME webengineviewer)
add_library(KF5::WebEngineViewer ALIAS KF5WebEngineViewer)
target_include_directories(KF5WebEngineViewer INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/WebEngineViewer/;${KDE_INSTALL_INCLUDEDIR_KF5}/webengineviewer>")
target_link_libraries(KF5WebEngineViewer
PUBLIC
Qt5::WebEngineWidgets
KF5::PimCommon
PRIVATE
KF5::CoreAddons
KF5::XmlGui
KF5::Completion
KF5::I18n
KF5::WidgetsAddons
KF5::ConfigCore
Qt5::PrintSupport
)
set_target_properties(KF5WebEngineViewer PROPERTIES
VERSION ${WEBENGINEVIEWER_VERSION_STRING}
SOVERSION ${WEBENGINEVIEWER_SOVERSION}
EXPORT_NAME WebEngineViewer
)
install(TARGETS
KF5WebEngineViewer
EXPORT KF5WebEngineViewerTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK}
)
ecm_generate_headers(WebEngineViewer_Camelfindbar_HEADERS
HEADER_NAMES
FindBarBase
FindBarWebEngineView
REQUIRED_HEADERS WebEngineViewer_findbar_HEADERS
PREFIX WebEngineViewer
RELATIVE findbar
)
ecm_generate_headers(WebEngineViewer_Camelcasewebengine_accesskey_HEADERS
HEADER_NAMES
WebEngineAccessKey
REQUIRED_HEADERS WebEngineViewer_webengine_accesskey_HEADERS
PREFIX WebEngineViewer
RELATIVE webengineaccesskey
)
ecm_generate_headers(WebEngineViewer_Camelcasewebengine_urlinterceptor_HEADERS
HEADER_NAMES
NetworkUrlInterceptorPluginManager
NetworkUrlInterceptor
NetworkPluginUrlInterceptorInterface
NetworkPluginUrlInterceptor
NetworkPluginUrlInterceptorConfigureWidget
REQUIRED_HEADERS WebEngineViewer_webengine_urlinterceptor_HEADERS
PREFIX WebEngineViewer
RELATIVE urlinterceptor
)
ecm_generate_headers(WebEngineViewer_Camelcasewebengine_manager_HEADERS
HEADER_NAMES
InterceptorManager
REQUIRED_HEADERS WebEngineViewer_webengine_manager_HEADERS
PREFIX WebEngineViewer
RELATIVE networkmanager
)
ecm_generate_headers(WebEngineViewer_Camelcasewebengine_checkurl_HEADERS
HEADER_NAMES
CheckPhishingUrlJob
CheckPhishingUrlCache
CreatePhishingUrlDataBaseJob
LocalDataBaseManager
CheckPhishingUrlUtil
SearchFullHashJob
UpdateDataBaseInfo
HashCacheManager
REQUIRED_HEADERS WebEngineViewer_webengine_checkurl_HEADERS
PREFIX WebEngineViewer
RELATIVE checkphishingurl
)
ecm_generate_headers(WebEngineViewer_Camelcasewebengine_misc_HEADERS
HEADER_NAMES
WebHitTestResult
WebEnginePage
WebEngineView
WebHitTest
WebEngineScript
WebEngineManageScript
WebEngineExportHtmlPageJob
REQUIRED_HEADERS WebEngineViewer_webengine_misc_HEADERS
PREFIX WebEngineViewer
RELATIVE
)
ecm_generate_headers(WebEngineViewer_Camelcasewidgets_HEADERS
HEADER_NAMES
ZoomActionMenu
REQUIRED_HEADERS WebEngineViewer_widgets_HEADERS
PREFIX WebEngineViewer
RELATIVE widgets
)
ecm_generate_pri_file(BASE_NAME WebEngineViewer
LIB_NAME KF5WebEngineViewer
DEPS "WebEngineWidgets PimCommon" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/WebEngineViewer
)
install(FILES
${WebEngineViewer_Camelcasewebengine_checkurl_HEADERS}
${WebEngineViewer_Camelcasewebengine_accesskey_HEADERS}
${WebEngineViewer_Camelcasewebengine_urlinterceptor_HEADERS}
${WebEngineViewer_Camelcasewebengine_manager_HEADERS}
${WebEngineViewer_Camelcasewebengine_misc_HEADERS}
${WebEngineViewer_Camelfindbar_HEADERS}
${WebEngineViewer_Camelcasewidgets_HEADERS}
${WebEngineViewer_Camelprint_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/WebEngineViewer
COMPONENT Devel
)
install(FILES
${WebEngineViewer_webengine_checkurl_HEADERS}
${WebEngineViewer_webengine_accesskey_HEADERS}
${WebEngineViewer_findbar_HEADERS}
${WebEngineViewer_webengine_urlinterceptor_HEADERS}
${WebEngineViewer_webengine_manager_HEADERS}
${WebEngineViewer_webengine_misc_HEADERS}
${WebEngineViewer_widgets_HEADERS}
${WebEngineViewer_print_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/webengineviewer_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/webengineviewer
COMPONENT Devel
)
install(FILES
${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
diff --git a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp
index 36016eb8..2d7806da 100644
--- a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp
+++ b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.cpp
@@ -1,45 +1,45 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineexporthtmlpagejobtest.h"
#include "../webengineexporthtmlpagejob.h"
#include <QSignalSpy>
#include <QTest>
WebEngineExportHtmlPageJobTest::WebEngineExportHtmlPageJobTest(QObject *parent)
: QObject(parent)
{
}
WebEngineExportHtmlPageJobTest::~WebEngineExportHtmlPageJobTest()
{
}
void WebEngineExportHtmlPageJobTest::shouldHaveDefaultValue()
{
WebEngineViewer::WebEngineExportHtmlPageJob job;
- QSignalSpy spyFailed(&job, SIGNAL(failed()));
- QSignalSpy spySuccess(&job, SIGNAL(success(QString)));
+ QSignalSpy spyFailed(&job, &WebEngineViewer::WebEngineExportHtmlPageJob::failed);
+ QSignalSpy spySuccess(&job, &WebEngineViewer::WebEngineExportHtmlPageJob::success);
QVERIFY(!job.engineView());
job.start();
QCOMPARE(spyFailed.count(), 1);
QCOMPARE(spySuccess.count(), 0);
}
QTEST_MAIN(WebEngineExportHtmlPageJobTest)
diff --git a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.h b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.h
index 00286e98..6ab68ff7 100644
--- a/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.h
+++ b/webengineviewer/src/autotests/webengineexporthtmlpagejobtest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEEXPORTHTMLPAGEJOBTEST_H
#define WEBENGINEEXPORTHTMLPAGEJOBTEST_H
#include <QObject>
class WebEngineExportHtmlPageJobTest : public QObject
{
Q_OBJECT
public:
explicit WebEngineExportHtmlPageJobTest(QObject *parent = nullptr);
~WebEngineExportHtmlPageJobTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
};
#endif // WEBENGINEEXPORTHTMLPAGEJOBTEST_H
diff --git a/webengineviewer/src/autotests/webhittestresulttest.cpp b/webengineviewer/src/autotests/webhittestresulttest.cpp
index 83bacd45..fb9ce2c1 100644
--- a/webengineviewer/src/autotests/webhittestresulttest.cpp
+++ b/webengineviewer/src/autotests/webhittestresulttest.cpp
@@ -1,173 +1,173 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webhittestresulttest.h"
#include "../webhittestresult.h"
#include <QTest>
WebHitTestResultTest::WebHitTestResultTest(QObject *parent)
: QObject(parent)
{
}
WebHitTestResultTest::~WebHitTestResultTest()
{
}
void WebHitTestResultTest::shouldHaveDefaultValues()
{
WebEngineViewer::WebHitTestResult result;
QVERIFY(result.alternateText().isEmpty());
QVERIFY(result.boundingRect().isNull());
QVERIFY(!result.imageUrl().isValid());
QVERIFY(!result.isContentEditable());
QVERIFY(!result.isContentSelected());
QVERIFY(result.isNull());
QVERIFY(result.linkTitle().isEmpty());
QVERIFY(!result.linkUrl().isValid());
QVERIFY(!result.mediaUrl().isValid());
QVERIFY(!result.mediaPaused());
QVERIFY(!result.mediaMuted());
QVERIFY(result.pos().isNull());
QVERIFY(result.tagName().isEmpty());
QVERIFY(!result.pageUrl().isValid());
}
void WebHitTestResultTest::shouldAssignPosAndUrl()
{
QPoint pos(5, 5);
QUrl url(QStringLiteral("http://www.kde.org"));
WebEngineViewer::WebHitTestResult result(pos, url, QVariant());
QVERIFY(result.alternateText().isEmpty());
QVERIFY(result.boundingRect().isNull());
QVERIFY(!result.imageUrl().isValid());
QVERIFY(!result.isContentEditable());
QVERIFY(!result.isContentSelected());
QVERIFY(result.isNull());
QVERIFY(result.linkTitle().isEmpty());
QVERIFY(!result.mediaUrl().isValid());
QVERIFY(!result.mediaPaused());
QVERIFY(!result.mediaMuted());
QVERIFY(result.tagName().isEmpty());
QVERIFY(!result.linkUrl().isValid());
QCOMPARE(result.pageUrl(), url);
QCOMPARE(result.pos(), pos);
}
void WebHitTestResultTest::shouldAssignFromQVariant()
{
QPoint pos(5, 5);
QUrl pageUrl(QStringLiteral("http://www.kde.org"));
QVariantMap map;
QString alternateText = QStringLiteral("FOO");
map.insert(QStringLiteral("alternateText"), alternateText);
bool contentEditable = true;
map.insert(QStringLiteral("contentEditable"), contentEditable);
bool contentSelected = true;
map.insert(QStringLiteral("contentSelected"), contentSelected);
QString linkTitle = QStringLiteral("GGGG");
map.insert(QStringLiteral("linkTitle"), linkTitle);
QUrl imageUrl(QStringLiteral("https://www.foo.net"));
map.insert(QStringLiteral("imageUrl"), imageUrl);
QUrl linkUrl(QStringLiteral("https://www.linux.org"));
map.insert(QStringLiteral("linkUrl"), linkUrl);
QUrl mediaUrl(QStringLiteral("https://www.media.org"));
map.insert(QStringLiteral("mediaUrl"), mediaUrl);
bool mediaPaused = true;
map.insert(QStringLiteral("mediaPaused"), mediaPaused);
bool mediaMuted = true;
map.insert(QStringLiteral("mediaMuted"), mediaMuted);
QString tagName = QStringLiteral("HHHHHH");
map.insert(QStringLiteral("tagName"), tagName);
QRect boundingRect(5, 7, 9, 11);
QVariantList lstRect;
lstRect << boundingRect.left() << boundingRect.top() << boundingRect.width() << boundingRect.height();
map.insert(QStringLiteral("boundingRect"), lstRect);
WebEngineViewer::WebHitTestResult result(pos, pageUrl, map);
QCOMPARE(result.alternateText(), alternateText);
QCOMPARE(result.boundingRect(), boundingRect);
QCOMPARE(result.imageUrl(), imageUrl);
QCOMPARE(result.isContentEditable(), contentEditable);
QCOMPARE(result.isContentSelected(), contentSelected);
QCOMPARE(result.isNull(), false);
QCOMPARE(result.linkTitle(), linkTitle);
QCOMPARE(result.linkUrl(), linkUrl);
QCOMPARE(result.mediaUrl(), mediaUrl);
QCOMPARE(result.mediaPaused(), mediaPaused);
QCOMPARE(result.mediaMuted(), mediaMuted);
QCOMPARE(result.pos(), pos);
QCOMPARE(result.tagName(), tagName);
QCOMPARE(result.pageUrl(), pageUrl);
}
void WebHitTestResultTest::shouldCopyWebHitTestResult()
{
QPoint pos(5, 5);
QUrl pageUrl(QStringLiteral("http://www.kde.org"));
QVariantMap map;
QString alternateText = QStringLiteral("FOO");
map.insert(QStringLiteral("alternateText"), alternateText);
bool contentEditable = true;
map.insert(QStringLiteral("contentEditable"), contentEditable);
bool contentSelected = true;
map.insert(QStringLiteral("contentSelected"), contentSelected);
QString linkTitle = QStringLiteral("GGGG");
map.insert(QStringLiteral("linkTitle"), linkTitle);
QUrl imageUrl(QStringLiteral("https://www.foo.net"));
map.insert(QStringLiteral("imageUrl"), imageUrl);
QUrl linkUrl(QStringLiteral("https://www.linux.org"));
map.insert(QStringLiteral("linkUrl"), linkUrl);
QUrl mediaUrl(QStringLiteral("https://www.media.org"));
map.insert(QStringLiteral("mediaUrl"), mediaUrl);
bool mediaPaused = true;
map.insert(QStringLiteral("mediaPaused"), mediaPaused);
bool mediaMuted = true;
map.insert(QStringLiteral("mediaMuted"), mediaMuted);
QString tagName = QStringLiteral("HHHHHH");
map.insert(QStringLiteral("tagName"), tagName);
QRect boundingRect(5, 7, 9, 11);
QVariantList lstRect;
lstRect << boundingRect.left() << boundingRect.top() << boundingRect.width() << boundingRect.height();
map.insert(QStringLiteral("boundingRect"), lstRect);
const WebEngineViewer::WebHitTestResult result1(pos, pageUrl, map);
WebEngineViewer::WebHitTestResult result = result1;
QCOMPARE(result.alternateText(), alternateText);
QCOMPARE(result.boundingRect(), boundingRect);
QCOMPARE(result.imageUrl(), imageUrl);
QCOMPARE(result.isContentEditable(), contentEditable);
QCOMPARE(result.isContentSelected(), contentSelected);
QCOMPARE(result.isNull(), false);
QCOMPARE(result.linkTitle(), linkTitle);
QCOMPARE(result.linkUrl(), linkUrl);
QCOMPARE(result.mediaUrl(), mediaUrl);
QCOMPARE(result.mediaPaused(), mediaPaused);
QCOMPARE(result.mediaMuted(), mediaMuted);
QCOMPARE(result.pos(), pos);
QCOMPARE(result.tagName(), tagName);
QCOMPARE(result.pageUrl(), pageUrl);
}
QTEST_MAIN(WebHitTestResultTest)
diff --git a/webengineviewer/src/autotests/webhittestresulttest.h b/webengineviewer/src/autotests/webhittestresulttest.h
index 070f1a96..3a067d78 100644
--- a/webengineviewer/src/autotests/webhittestresulttest.h
+++ b/webengineviewer/src/autotests/webhittestresulttest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBHITTESTRESULTTEST_H
#define WEBHITTESTRESULTTEST_H
#include <QObject>
class WebHitTestResultTest : public QObject
{
Q_OBJECT
public:
explicit WebHitTestResultTest(QObject *parent = nullptr);
~WebHitTestResultTest();
private Q_SLOTS:
void shouldHaveDefaultValues();
void shouldAssignPosAndUrl();
void shouldAssignFromQVariant();
void shouldCopyWebHitTestResult();
};
#endif // WEBHITTESTRESULTTEST_H
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.cpp b/webengineviewer/src/autotests/zoomactionmenutest.cpp
index a6774e3e..d02fe85f 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.cpp
+++ b/webengineviewer/src/autotests/zoomactionmenutest.cpp
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "zoomactionmenutest.h"
#include "../widgets/zoomactionmenu.h"
#include <QTest>
#include <KActionCollection>
ZoomActionMenuTest::ZoomActionMenuTest(QObject *parent)
: QObject(parent)
{
}
ZoomActionMenuTest::~ZoomActionMenuTest()
{
}
void ZoomActionMenuTest::shouldHaveDefaultValue()
{
WebEngineViewer::ZoomActionMenu menu(this);
menu.setActionCollection(new KActionCollection(this));
menu.createZoomActions();
QVERIFY(menu.zoomInAction());
QVERIFY(menu.zoomOutAction());
QVERIFY(menu.zoomResetAction());
}
void ZoomActionMenuTest::shouldAssignZoomFactor()
{
WebEngineViewer::ZoomActionMenu menu(this);
menu.setActionCollection(new KActionCollection(this));
menu.createZoomActions();
const qreal initialValue = 50;
menu.setZoomFactor(initialValue);
QCOMPARE(menu.zoomFactor(), initialValue);
}
QTEST_MAIN(ZoomActionMenuTest)
diff --git a/webengineviewer/src/autotests/zoomactionmenutest.h b/webengineviewer/src/autotests/zoomactionmenutest.h
index 000e7ca0..c7d0772d 100644
--- a/webengineviewer/src/autotests/zoomactionmenutest.h
+++ b/webengineviewer/src/autotests/zoomactionmenutest.h
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef ZOOMACTIONMENUTEST_H
#define ZOOMACTIONMENUTEST_H
#include <QObject>
class ZoomActionMenuTest : public QObject
{
Q_OBJECT
public:
explicit ZoomActionMenuTest(QObject *parent = nullptr);
~ZoomActionMenuTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldAssignZoomFactor();
};
#endif // ZOOMACTIONMENUTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.cpp b/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.cpp
index d185bc45..b1e16bc7 100644
--- a/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.cpp
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "backoffmodemanagertest.h"
#include "../backoffmodemanager.h"
#include <QTest>
#include <QStandardPaths>
BackOffModeManagerTest::BackOffModeManagerTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
BackOffModeManagerTest::~BackOffModeManagerTest()
{
}
void BackOffModeManagerTest::shouldHaveDefaultValue()
{
WebEngineViewer::BackOffModeManager manager;
QVERIFY(!manager.isInBackOffMode());
QCOMPARE(manager.numberOfHttpFailed(), 0);
}
void BackOffModeManagerTest::shouldStartBackOffMode()
{
WebEngineViewer::BackOffModeManager manager;
manager.startOffMode();
QVERIFY(manager.isInBackOffMode());
}
void BackOffModeManagerTest::shouldIncreaseBackOff()
{
WebEngineViewer::BackOffModeManager manager;
for (int i = 0; i < 5; ++i) {
manager.startOffMode();
}
QCOMPARE(manager.numberOfHttpFailed(), 5);
}
QTEST_MAIN(BackOffModeManagerTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.h b/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.h
index 08e7b9c9..27a9f664 100644
--- a/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/backoffmodemanagertest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef BACKOFFMODEMANAGERTEST_H
#define BACKOFFMODEMANAGERTEST_H
#include <QObject>
class BackOffModeManagerTest : public QObject
{
Q_OBJECT
public:
explicit BackOffModeManagerTest(QObject *parent = nullptr);
~BackOffModeManagerTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldStartBackOffMode();
void shouldIncreaseBackOff();
};
#endif // BACKOFFMODEMANAGERTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.cpp b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.cpp
index 0789886f..32e27360 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.cpp
@@ -1,128 +1,128 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurlcachetest.h"
#include "../checkphishingurlcache.h"
#include "../checkphishingurlutil.h"
#include <QTest>
#include <QStandardPaths>
#include <KConfig>
#include <kconfiggroup.h>
CheckPhishingUrlCacheTest::CheckPhishingUrlCacheTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
CheckPhishingUrlCacheTest::~CheckPhishingUrlCacheTest()
{
}
void CheckPhishingUrlCacheTest::shouldNotBeAMalware()
{
WebEngineViewer::CheckPhishingUrlCache cache;
QCOMPARE(cache.urlStatus(QUrl(QStringLiteral("http://www.kde.org"))), WebEngineViewer::CheckPhishingUrlCache::Unknown);
}
void CheckPhishingUrlCacheTest::shouldAddValue_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<bool>("validurl");
QTest::addColumn<uint>("seconds");
QTest::addColumn<WebEngineViewer::CheckPhishingUrlCache::UrlStatus>("status");
uint currentValue = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
QTest::newRow("valid") << QUrl(QStringLiteral("http://www.kde.org")) << true << currentValue << WebEngineViewer::CheckPhishingUrlCache::UrlOk;
QTest::newRow("malware1validcache") << QUrl(QStringLiteral("http://www.kde.org")) << false << (currentValue + 2000) << WebEngineViewer::CheckPhishingUrlCache::MalWare;
QTest::newRow("malware1invalidcache") << QUrl(QStringLiteral("http://www.kde.org")) << false << (currentValue - 2000) << WebEngineViewer::CheckPhishingUrlCache::Unknown;
}
void CheckPhishingUrlCacheTest::shouldAddValue()
{
QFETCH(QUrl, url);
QFETCH(bool, validurl);
QFETCH(uint, seconds);
QFETCH(WebEngineViewer::CheckPhishingUrlCache::UrlStatus, status);
WebEngineViewer::CheckPhishingUrlCache cache;
cache.addCheckingUrlResult(url, validurl, seconds);
QCOMPARE(cache.urlStatus(url), status);
cache.clearCache();
}
void CheckPhishingUrlCacheTest::shouldStoreValues()
{
WebEngineViewer::CheckPhishingUrlCache cache;
QUrl url = QUrl(QStringLiteral("http://www.kde.org"));
uint currentValue = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
cache.addCheckingUrlResult(url, false, currentValue + 2000);
//Add malware
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("MalwareUrl"));
QList<QUrl> listMalware = grp.readEntry("Url", QList<QUrl>());
QList<double> listMalwareCachedTime = grp.readEntry("CachedTime", QList<double>());
QCOMPARE(listMalware.count(), 1);
QCOMPARE(listMalwareCachedTime.count(), 1);
//Clear cache
cache.clearCache();
KConfig phishingurlKConfig2(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
grp = phishingurlKConfig2.group(QStringLiteral("MalwareUrl"));
listMalware = grp.readEntry("Url", QList<QUrl>());
listMalwareCachedTime = grp.readEntry("CachedTime", QList<double>());
QCOMPARE(listMalware.count(), 0);
QCOMPARE(listMalwareCachedTime.count(), 0);
//Add correct url
cache.clearCache();
cache.addCheckingUrlResult(url, true, 0);
KConfig phishingurlKConfig3(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
grp = phishingurlKConfig3.group(QStringLiteral("MalwareUrl"));
listMalware = grp.readEntry("Url", QList<QUrl>());
listMalwareCachedTime = grp.readEntry("CachedTime", QList<double>());
QCOMPARE(listMalware.count(), 0);
QCOMPARE(listMalwareCachedTime.count(), 0);
cache.clearCache();
//Load another instance => data were saved
cache.addCheckingUrlResult(url, false, currentValue + 2000);
WebEngineViewer::CheckPhishingUrlCache cache2;
KConfig phishingurlKConfig4(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
grp = phishingurlKConfig4.group(QStringLiteral("MalwareUrl"));
listMalware = grp.readEntry("Url", QList<QUrl>());
listMalwareCachedTime = grp.readEntry("CachedTime", QList<double>());
QCOMPARE(listMalware.count(), 1);
QCOMPARE(listMalwareCachedTime.count(), 1);
//Need to clear to avoid problem when we relaunch this autotest
cache2.clearCache();
cache.clearCache();
}
QTEST_MAIN(CheckPhishingUrlCacheTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.h b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.h
index e18dbefd..cf84c3e2 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlcachetest.h
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLCACHETEST_H
#define CHECKPHISHINGURLCACHETEST_H
#include <QObject>
class CheckPhishingUrlCacheTest : public QObject
{
Q_OBJECT
public:
explicit CheckPhishingUrlCacheTest(QObject *parent = nullptr);
~CheckPhishingUrlCacheTest();
private Q_SLOTS:
void shouldNotBeAMalware();
void shouldAddValue_data();
void shouldAddValue();
void shouldStoreValues();
};
#endif // CHECKPHISHINGURLCACHETEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp
index 115575ac..54b06ee4 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.cpp
@@ -1,101 +1,101 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurljobtest.h"
#include "../checkphishingurljob.h"
#include "../checkphishingurlutil.h"
#include <QSignalSpy>
#include <QTest>
CheckPhishingUrlJobTest::CheckPhishingUrlJobTest(QObject *parent)
: QObject(parent)
{
}
CheckPhishingUrlJobTest::~CheckPhishingUrlJobTest()
{
}
void CheckPhishingUrlJobTest::shouldNotBeAbleToStartWithEmptyUrl()
{
WebEngineViewer::CheckPhishingUrlJob job;
QVERIFY(!job.canStart());
}
void CheckPhishingUrlJobTest::shouldCreateRequest_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QString>("request");
QTest::addColumn<bool>("canStart");
QTest::newRow("no url") << QUrl() << QString() << false;
QTest::newRow("value") << QUrl(QStringLiteral("http://www.kde.org")) << QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"threatInfo\":{\"platformTypes\":[\"WINDOWS\"],\"threatEntries\":[{\"url\":\"http://www.kde.org\"}],\"threatEntryTypes\":[\"URL\"],\"threatTypes\":[\"MALWARE\"]}}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps()) << true;
}
void CheckPhishingUrlJobTest::shouldCreateRequest()
{
QFETCH(QUrl, url);
QFETCH(QString, request);
QFETCH(bool, canStart);
WebEngineViewer::CheckPhishingUrlJob job;
job.setUrl(url);
QCOMPARE(job.canStart(), canStart);
if (canStart) {
QCOMPARE(job.jsonRequest(), request.toLatin1());
}
}
void CheckPhishingUrlJobTest::shouldParseResult_data()
{
QTest::addColumn<QByteArray>("input");
QTest::addColumn<QUrl>("checkedUrl");
QTest::addColumn<uint>("verifyCacheAfterThisTime");
QTest::addColumn<WebEngineViewer::CheckPhishingUrlUtil::UrlStatus>("urlStatus");
uint val = 0;
QTest::newRow("empty") << QByteArray() << QUrl() << val << WebEngineViewer::CheckPhishingUrlUtil::Unknown;
QTest::newRow("empty1") << QByteArray() << QUrl(QStringLiteral("http://www.kde.org")) << val << WebEngineViewer::CheckPhishingUrlUtil::Unknown;
QTest::newRow("urlOk") << QByteArrayLiteral("{}") << QUrl(QStringLiteral("http://www.kde.org")) << val << WebEngineViewer::CheckPhishingUrlUtil::Ok;
val = WebEngineViewer::CheckPhishingUrlUtil::refreshingCacheAfterThisTime(WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(QStringLiteral("300s")));
QTest::newRow("malware") << QByteArrayLiteral(
"{\"matches\":[{\"threatType\":\"MALWARE\",\"platformType\":\"WINDOWS\",\"threat\":{\"url\":\"http://malware.testing.google.test/testing/malware/\"},\"cacheDuration\":\"300s\",\"threatEntryType\":\"URL\"}]}")
<< QUrl(QStringLiteral("http://malware.testing.google.test/testing/malware/")) << val << WebEngineViewer::CheckPhishingUrlUtil::MalWare;
}
void CheckPhishingUrlJobTest::shouldParseResult()
{
QFETCH(QByteArray, input);
QFETCH(QUrl, checkedUrl);
QFETCH(uint, verifyCacheAfterThisTime);
QFETCH(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus, urlStatus);
WebEngineViewer::CheckPhishingUrlJob job;
job.setUrl(checkedUrl);
- QSignalSpy spy1(&job, SIGNAL(result(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus,QUrl,uint)));
+ QSignalSpy spy1(&job, &WebEngineViewer::CheckPhishingUrlJob::result);
job.parse(input);
QCOMPARE(spy1.count(), 1);
QCOMPARE(spy1.at(0).at(0).value<WebEngineViewer::CheckPhishingUrlUtil::UrlStatus>(), urlStatus);
QCOMPARE(spy1.at(0).at(1).toUrl(), checkedUrl);
QCOMPARE(spy1.at(0).at(2).value<uint>(), verifyCacheAfterThisTime);
}
QTEST_MAIN(CheckPhishingUrlJobTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.h b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.h
index 91a5529c..b329f786 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurljobtest.h
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLJOBTEST_H
#define CHECKPHISHINGURLJOBTEST_H
#include <QObject>
class CheckPhishingUrlJobTest : public QObject
{
Q_OBJECT
public:
explicit CheckPhishingUrlJobTest(QObject *parent = nullptr);
~CheckPhishingUrlJobTest();
private Q_SLOTS:
void shouldNotBeAbleToStartWithEmptyUrl();
void shouldCreateRequest_data();
void shouldCreateRequest();
void shouldParseResult_data();
void shouldParseResult();
};
#endif // CHECKPHISHINGURLJOBTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.cpp b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.cpp
index 41f777ba..c6df4135 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.cpp
@@ -1,95 +1,95 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurlutiltest.h"
#include "../checkphishingurlutil.h"
#include <QTest>
CheckPhishingUrlUtilTest::CheckPhishingUrlUtilTest(QObject *parent)
: QObject(parent)
{
}
CheckPhishingUrlUtilTest::~CheckPhishingUrlUtilTest()
{
}
void CheckPhishingUrlUtilTest::shouldConvertToSecond_data()
{
QTest::addColumn<QString>("value");
QTest::addColumn<double>("result");
QTest::newRow("empty") << QString() << (double)-1;
QTest::newRow("test1") << QStringLiteral("459.123s") << 459.123;
QTest::newRow("test2") << QStringLiteral("459s") << 459.;
}
void CheckPhishingUrlUtilTest::shouldConvertToSecond()
{
QFETCH(QString, value);
QFETCH(double, result);
QCOMPARE(WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(value), result);
}
void CheckPhishingUrlUtilTest::shouldCacheIsStillValid_data()
{
QTest::addColumn<double>("second");
QTest::addColumn<bool>("valid");
uint currentTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
QTest::newRow("valid") << (currentTime + 2000.) << true;
QTest::newRow("invalid") << (currentTime - 2000.) << false;
}
void CheckPhishingUrlUtilTest::shouldCacheIsStillValid()
{
QFETCH(double, second);
QFETCH(bool, valid);
QCOMPARE(WebEngineViewer::CheckPhishingUrlUtil::cachedValueStillValid(second), valid);
}
void CheckPhishingUrlUtilTest::shouldGenerateBackModeDelay_data()
{
QTest::addColumn<int>("numberFailed");
QTest::addColumn<int>("minuteMin");
QTest::addColumn<int>("minuteMax");
QTest::newRow("onefailed") << 1 << 15 << 30;
QTest::newRow("twofailed") << 2 << 30 << 60;
QTest::newRow("3failed") << 3 << 60 << 120;
QTest::newRow("4failed") << 4 << 120 << 240;
QTest::newRow("5failed") << 5 << 240 << 480;
QTest::newRow("6failed") << 6 << 480 << 960;
QTest::newRow("7failed") << 7 << 960 << 1440;
QTest::newRow("8failed") << 8 << 1440 << 1440;
QTest::newRow("9failed") << 9 << 1440 << 1440;
QTest::newRow("10failed") << 10 << 1440 << 1440;
}
void CheckPhishingUrlUtilTest::shouldGenerateBackModeDelay()
{
QFETCH(int, numberFailed);
QFETCH(int, minuteMin);
QFETCH(int, minuteMax);
int result = WebEngineViewer::CheckPhishingUrlUtil::generateRandomSecondValue(numberFailed);
result /= 60; //minutes
QVERIFY(result >= minuteMin);
QVERIFY(result <= minuteMax);
}
QTEST_MAIN(CheckPhishingUrlUtilTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.h b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.h
index eb5f6989..ee998a4c 100644
--- a/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/checkphishingurlutiltest.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLUTILTEST_H
#define CHECKPHISHINGURLUTILTEST_H
#include <QObject>
class CheckPhishingUrlUtilTest : public QObject
{
Q_OBJECT
public:
explicit CheckPhishingUrlUtilTest(QObject *parent = nullptr);
~CheckPhishingUrlUtilTest();
private Q_SLOTS:
void shouldConvertToSecond_data();
void shouldConvertToSecond();
void shouldCacheIsStillValid_data();
void shouldCacheIsStillValid();
void shouldGenerateBackModeDelay_data();
void shouldGenerateBackModeDelay();
};
#endif // CHECKPHISHINGURLUTILTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp
index 104f762b..15f525bb 100644
--- a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.cpp
@@ -1,441 +1,441 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createdatabasefilejobtest.h"
#include "../createdatabasefilejob.h"
#include "../createphishingurldatabasejob.h"
#include "../localdatabasefile.h"
#include <QStandardPaths>
#include <QSignalSpy>
#include <QTest>
#include <QDebug>
Q_DECLARE_METATYPE(QVector<WebEngineViewer::Addition>)
QByteArray readJsonFile(const QString &jsonFile)
{
QFile file(QLatin1String(CHECKPHISHINGURL_DATA_DIR) + QLatin1Char('/') + jsonFile);
file.open(QIODevice::ReadOnly);
Q_ASSERT(file.isOpen());
const QByteArray data = file.readAll();
Q_ASSERT(!data.isEmpty());
return data;
}
CreateDatabaseFileJobTest::CreateDatabaseFileJobTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl"));
}
CreateDatabaseFileJobTest::~CreateDatabaseFileJobTest()
{
}
void CreateDatabaseFileJobTest::initTestCase()
{
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult>();
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType>();
qRegisterMetaType<WebEngineViewer::UpdateDataBaseInfo>();
}
void CreateDatabaseFileJobTest::shouldHaveDefaultValue()
{
WebEngineViewer::CreateDatabaseFileJob job;
QVERIFY(!job.canStart());
}
void CreateDatabaseFileJobTest::shouldCreateFile_data()
{
QTest::addColumn<QString>("filename");
QTest::addColumn<quint64>("numberOfElement");
QTest::addColumn<bool>("success");
QTest::newRow("correctdatabase") << QStringLiteral("current.json") << static_cast<quint64>(580600) << true;
QTest::newRow("correctdatabase2") << QStringLiteral("newdatabase2.json") << static_cast<quint64>(579416) << true;
QTest::newRow("incorrectdatabase") << QStringLiteral("incorrectdatabase2.json") << static_cast<quint64>(0) << false;
}
void CreateDatabaseFileJobTest::shouldCreateFile()
{
QFETCH(QString, filename);
QFETCH(quint64, numberOfElement);
QFETCH(bool, success);
const QByteArray ba = readJsonFile(filename);
WebEngineViewer::CreatePhishingUrlDataBaseJob job;
- QSignalSpy spy1(&job, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult)));
+ QSignalSpy spy1(&job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished);
job.parseResult(ba);
QCOMPARE(spy1.count(), 1);
const WebEngineViewer::UpdateDataBaseInfo info = spy1.at(0).at(0).value<WebEngineViewer::UpdateDataBaseInfo>();
WebEngineViewer::CreateDatabaseFileJob databasejob;
const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/test.db");
qDebug() << " new filename " << createDataBaseName;
databasejob.setFileName(createDataBaseName);
databasejob.setUpdateDataBaseInfo(info);
- QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob.start();
QCOMPARE(spy2.count(), 1);
bool successCreateDataBase = spy2.at(0).at(0).toBool();
QCOMPARE(successCreateDataBase, success);
WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName);
QVERIFY(newFile.isValid());
QCOMPARE(newFile.getUint16(0), static_cast<quint16>(1));
QCOMPARE(newFile.getUint16(2), static_cast<quint16>(0));
if (success) {
QCOMPARE(newFile.getUint64(4), numberOfElement);
}
}
void CreateDatabaseFileJobTest::shouldRemoveElementInDataBase_data()
{
QTest::addColumn<QList<quint32> >("listElementToRemove");
QTest::addColumn<QVector<WebEngineViewer::Addition> >("listElementToAdd");
QTest::addColumn<QByteArray>("newssha");
QTest::addColumn<bool>("success");
QVector<WebEngineViewer::Addition> lstAdditions;
QList<quint32> r = { 2, 3, 4};
QTest::newRow("correctdatabase") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true;
r = {3, 2, 4};
QTest::newRow("correctdatabaseotherorder") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true;
r = {4, 2, 3};
QTest::newRow("correctdatabaseotherorder2") << r << lstAdditions << QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=") << true;
// >>> import hashlib
// >>> m = hashlib.sha256()
// >>> m.update("111154321abcdabcdebbbbbcdef")
// >>> m.digest()
// '\x81\xdd9\xe3\xae\x94s\xfd\x16o\\\xcea \xb7\xbc\x1b+R\nN\x05o\xfe\xeeWY\x7f\x8a\xcb\xbeN'
// >>> import base64
// >>> encoded = base64.b64encode(m.digest())
// >>> encoded
// 'gd05466Uc/0Wb1zOYSC3vBsrUgpOBW/+7ldZf4rLvk4='
// >>>
r = {0, 2, 8};
QTest::newRow("correctdatabaseotherorder3") << r << lstAdditions << QByteArrayLiteral("gd05466Uc/0Wb1zOYSC3vBsrUgpOBW/+7ldZf4rLvk4=") << true;
r = {0, 2, 8};
WebEngineViewer::Addition c;
c.hashString = QByteArray("mnopqrst");
c.prefixSize = 4;
c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition b;
b.hashString = QByteArray("uvwx");
b.prefixSize = 4;
b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
lstAdditions << c << b;
// >>> import hashlib
// >>> m = hashlib.sha256()
// >>> m.update("111154321abcdabcdebbbbbcdefmnopqrstuvwx")
// >>> m.digest()
// '\n\xae\xe2\xe0!\x8f\xa4\x05N\x89,\xdcJ*\xbe\x85\xa1Q\xc3\x9c\xc8}j\x83*s\xd5L&\xbe\xfbh'
// >>> import base64
// >>> encoded = base64.b64encode(m.digest())
// >>> encoded
// 'Cq7i4CGPpAVOiSzcSiq+haFRw5zIfWqDKnPVTCa++2g='
//m.update("111154321abcdabcdebbbbbcdefmnopqrstuvwx");
QTest::newRow("correctdatabaseotherorderwithadditions") << r << lstAdditions << QByteArrayLiteral("Cq7i4CGPpAVOiSzcSiq+haFRw5zIfWqDKnPVTCa++2g=") << true;
}
void CreateDatabaseFileJobTest::shouldRemoveElementInDataBase()
{
QFETCH(QList<quint32>, listElementToRemove);
QFETCH(QVector<WebEngineViewer::Addition>, listElementToAdd);
QFETCH(QByteArray, newssha);
QFETCH(bool, success);
// Proof of checksum validity using python:
// >>> import hashlib
// >>> m = hashlib.sha256()
// >>> m.update("----11112222254321abcdabcdebbbbbcdefefgh")
// >>> m.digest()
// "\xbc\xb3\xedk\xe3x\xd1(\xa9\xedz7]"
// "x\x18\xbdn]\xa5\xa8R\xf7\xab\xcf\xc1\xa3\xa3\xc5Z,\xa6o"
WebEngineViewer::CreateDatabaseFileJob databasejob;
const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/correctBinary.db");
qDebug() << " new filename " << createDataBaseName;
databasejob.setFileName(createDataBaseName);
WebEngineViewer::UpdateDataBaseInfo info;
WebEngineViewer::Addition a;
a.hashString = QByteArray("----1111bbbb");
a.prefixSize = 4;
a.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition b;
b.hashString = QByteArray("abcdefgh");
b.prefixSize = 4;
b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition c;
c.hashString = QByteArray("54321abcde");
c.prefixSize = 5;
c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition d;
d.hashString = QByteArray("22222bcdef");
d.prefixSize = 5;
d.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
QVector<WebEngineViewer::Addition> lst;
lst << a << b << c << d;
info.additionList = lst;
info.minimumWaitDuration = QStringLiteral("593.440s");
info.threatType = QStringLiteral("MALWARE");
info.threatEntryType = QStringLiteral("URL");
info.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate;
info.platformType = QStringLiteral("WINDOWS");
info.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
info.sha256 = QByteArrayLiteral("vLPta+N40Sip7Xo3XXgYvW5dpahS96vPwaOjxVospm8=");
databasejob.setUpdateDataBaseInfo(info);
- QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob.start();
QCOMPARE(spy2.count(), 1);
bool successCreateDataBase = spy2.at(0).at(0).toBool();
QVERIFY(successCreateDataBase);
WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName);
QVERIFY(newFile.isValid());
QCOMPARE(newFile.getUint16(0), static_cast<quint16>(1));
QCOMPARE(newFile.getUint16(2), static_cast<quint16>(0));
QCOMPARE(newFile.getUint64(4), static_cast<quint64>(9));
int index = 4 + sizeof(quint64);
QList<QByteArray> storageData;
storageData << QByteArrayLiteral("----");
storageData << QByteArrayLiteral("1111");
storageData << QByteArrayLiteral("22222");
storageData << QByteArrayLiteral("54321");
storageData << QByteArrayLiteral("abcd");
storageData << QByteArrayLiteral("abcde");
storageData << QByteArrayLiteral("bbbb");
storageData << QByteArrayLiteral("bcdef");
storageData << QByteArrayLiteral("efgh");
for (int i = 0; i < 9; ++i) {
quint64 value = newFile.getUint64(index);
//qDebug() << "char "<< newFile.getCharStar(value);
QCOMPARE(storageData.at(i), QByteArray(newFile.getCharStar(value)));
index += sizeof(quint64);
}
const QVector<WebEngineViewer::Addition> lstInfo = newFile.extractAllInfo();
QCOMPARE(lstInfo.count(), 9);
for (int i = 0; i < 9; i++) {
QCOMPARE(lstInfo.at(i).hashString, storageData.at(i));
QCOMPARE(lstInfo.at(i).prefixSize, lstInfo.at(i).hashString.size());
}
//Before
//storageData << QByteArrayLiteral("----");
//storageData << QByteArrayLiteral("1111");
//storageData << QByteArrayLiteral("22222");
//storageData << QByteArrayLiteral("54321");
//storageData << QByteArrayLiteral("abcd");
//storageData << QByteArrayLiteral("abcde");
//storageData << QByteArrayLiteral("bbbb");
//storageData << QByteArrayLiteral("bcdef");
//storageData << QByteArrayLiteral("efgh");
//TODO remove items.
WebEngineViewer::UpdateDataBaseInfo updateinfo;
// we will remove QByteArrayLiteral("22222"); QByteArrayLiteral("54321"); QByteArrayLiteral("abcd");
WebEngineViewer::Removal r;
r.indexes = listElementToRemove;
r.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
// Proof of checksum validity using python:
// >>> import hashlib
// >>> m = hashlib.sha256()
// >>> m.update("----1111abcdebbbbbcdefefgh")
// >>> m.digest()
// '\xc99\xf2\x8c\x08\x08\x15\xe4\xba\n\xff\x9b\xe0\x82G\x9e\x06\x1bv\xfay\xbb=[\xc7\xd5xz^B\xc9\xe1'
// >>> import base64
// >>> encoded = base64.b64encode(m.digest())
// >>> encoded
// 'yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE='
QVector<WebEngineViewer::Removal> lstRemovals;
lstRemovals << r;
updateinfo.additionList = listElementToAdd;
updateinfo.removalList = lstRemovals;
updateinfo.minimumWaitDuration = QStringLiteral("593.440s");
updateinfo.threatType = QStringLiteral("MALWARE");
updateinfo.threatEntryType = QStringLiteral("URL");
updateinfo.responseType = WebEngineViewer::UpdateDataBaseInfo::PartialUpdate;
updateinfo.platformType = QStringLiteral("WINDOWS");
updateinfo.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
updateinfo.sha256 = /*QByteArrayLiteral("yTnyjAgIFeS6Cv+b4IJHngYbdvp5uz1bx9V4el5CyeE=")*/ newssha;
WebEngineViewer::CreateDatabaseFileJob updateDatabasejob;
qDebug() << " new filename " << createDataBaseName;
updateDatabasejob.setFileName(createDataBaseName);
updateDatabasejob.setUpdateDataBaseInfo(updateinfo);
- QSignalSpy spy3(&updateDatabasejob, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy3(&updateDatabasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
updateDatabasejob.start();
QCOMPARE(spy3.count(), 1);
successCreateDataBase = spy3.at(0).at(0).toBool();
QCOMPARE(successCreateDataBase, success);
}
void CreateDatabaseFileJobTest::shouldCreateCorrectBinaryFile()
{
WebEngineViewer::CreateDatabaseFileJob databasejob;
const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/correctBinary.db");
qDebug() << " new filename " << createDataBaseName;
databasejob.setFileName(createDataBaseName);
WebEngineViewer::UpdateDataBaseInfo info;
WebEngineViewer::Addition a;
a.hashString = QByteArray("----1111bbbb");
a.prefixSize = 4;
a.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition b;
b.hashString = QByteArray("abcdefgh");
b.prefixSize = 4;
b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition c;
c.hashString = QByteArray("54321abcde");
c.prefixSize = 5;
c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition d;
d.hashString = QByteArray("22222bcdef");
d.prefixSize = 5;
d.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
QVector<WebEngineViewer::Addition> lst;
lst << a << b << c << d;
info.additionList = lst;
info.minimumWaitDuration = QStringLiteral("593.440s");
info.threatType = QStringLiteral("MALWARE");
info.threatEntryType = QStringLiteral("URL");
info.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate;
info.platformType = QStringLiteral("WINDOWS");
info.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
info.sha256 = QByteArrayLiteral("vLPta+N40Sip7Xo3XXgYvW5dpahS96vPwaOjxVospm8=");
databasejob.setUpdateDataBaseInfo(info);
- QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob.start();
QCOMPARE(spy2.count(), 1);
bool successCreateDataBase = spy2.at(0).at(0).toBool();
QVERIFY(successCreateDataBase);
WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName);
QVERIFY(newFile.isValid());
QCOMPARE(newFile.getUint16(0), static_cast<quint16>(1));
QCOMPARE(newFile.getUint16(2), static_cast<quint16>(0));
QCOMPARE(newFile.getUint64(4), static_cast<quint64>(9));
int index = 4 + sizeof(quint64);
QList<QByteArray> storageData;
storageData << QByteArrayLiteral("----");
storageData << QByteArrayLiteral("1111");
storageData << QByteArrayLiteral("22222");
storageData << QByteArrayLiteral("54321");
storageData << QByteArrayLiteral("abcd");
storageData << QByteArrayLiteral("abcde");
storageData << QByteArrayLiteral("bbbb");
storageData << QByteArrayLiteral("bcdef");
storageData << QByteArrayLiteral("efgh");
for (int i = 0; i < 9; ++i) {
quint64 value = newFile.getUint64(index);
//qDebug() << "char "<< newFile.getCharStar(value);
QCOMPARE(storageData.at(i), QByteArray(newFile.getCharStar(value)));
index += sizeof(quint64);
}
const QVector<WebEngineViewer::Addition> lstInfo = newFile.extractAllInfo();
QCOMPARE(lstInfo.count(), 9);
for (int i = 0; i < 9; i++) {
QCOMPARE(lstInfo.at(i).hashString, storageData.at(i));
QCOMPARE(lstInfo.at(i).prefixSize, lstInfo.at(i).hashString.size());
}
}
void CreateDatabaseFileJobTest::shouldUpdateDataBase()
{
QString firstFilename = QStringLiteral("newdatabase2.json");
const QByteArray ba = readJsonFile(firstFilename);
WebEngineViewer::CreatePhishingUrlDataBaseJob job;
- QSignalSpy spy1(&job, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult)));
+ QSignalSpy spy1(&job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished);
job.parseResult(ba);
QCOMPARE(spy1.count(), 1);
const WebEngineViewer::UpdateDataBaseInfo info = spy1.at(0).at(0).value<WebEngineViewer::UpdateDataBaseInfo>();
WebEngineViewer::CreateDatabaseFileJob databasejob;
const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/update.db");
//qDebug() << " new filename " << createDataBaseName;
databasejob.setFileName(createDataBaseName);
databasejob.setUpdateDataBaseInfo(info);
- QSignalSpy spy2(&databasejob, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob.start();
QCOMPARE(spy2.count(), 1);
bool successCreateDataBase = spy2.at(0).at(0).toBool();
QVERIFY(successCreateDataBase);
WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName);
QVERIFY(newFile.isValid());
QCOMPARE(newFile.getUint16(0), static_cast<quint16>(1));
QCOMPARE(newFile.getUint16(2), static_cast<quint16>(0));
QCOMPARE(newFile.getUint64(4), static_cast<quint64>(579416));
newFile.close();
QString updateFilename = QStringLiteral("partial_download3.json");
const QByteArray baUpdate = readJsonFile(updateFilename);
WebEngineViewer::CreatePhishingUrlDataBaseJob jobUpdate;
- QSignalSpy spy3(&jobUpdate, SIGNAL(finished(WebEngineViewer::UpdateDataBaseInfo,WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult)));
+ QSignalSpy spy3(&jobUpdate, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished);
jobUpdate.parseResult(baUpdate);
QCOMPARE(spy3.count(), 1);
const WebEngineViewer::UpdateDataBaseInfo infoUpdate = spy3.at(0).at(0).value<WebEngineViewer::UpdateDataBaseInfo>();
QCOMPARE(infoUpdate.responseType, WebEngineViewer::UpdateDataBaseInfo::PartialUpdate);
WebEngineViewer::CreateDatabaseFileJob databasejob2;
databasejob2.setFileName(createDataBaseName);
databasejob2.setUpdateDataBaseInfo(infoUpdate);
- QSignalSpy spy4(&databasejob2, SIGNAL(finished(bool,QString,QString)));
+ QSignalSpy spy4(&databasejob2, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob2.start();
QCOMPARE(spy4.count(), 1);
successCreateDataBase = spy4.at(0).at(0).toBool();
QEXPECT_FAIL("successCreateDataBase", "Expected a success but not", Continue);
//QVERIFY(successCreateDataBase);
}
QTEST_MAIN(CreateDatabaseFileJobTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.h b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.h
index 2c972b2b..807ddb6e 100644
--- a/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/createdatabasefilejobtest.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CREATEDATABASEFILEJOBTEST_H
#define CREATEDATABASEFILEJOBTEST_H
#include <QObject>
class CreateDatabaseFileJobTest : public QObject
{
Q_OBJECT
public:
explicit CreateDatabaseFileJobTest(QObject *parent = nullptr);
~CreateDatabaseFileJobTest();
private Q_SLOTS:
void initTestCase();
void shouldHaveDefaultValue();
void shouldCreateFile();
void shouldCreateFile_data();
void shouldCreateCorrectBinaryFile();
void shouldUpdateDataBase();
void shouldRemoveElementInDataBase();
void shouldRemoveElementInDataBase_data();
};
#endif // CREATEDATABASEFILEJOBTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.cpp
index 5818b967..54934cf8 100644
--- a/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.cpp
@@ -1,324 +1,324 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createphishingurldatabasejobtest.h"
#include "../createphishingurldatabasejob.h"
#include "../updatedatabaseinfo.h"
#include "../checkphishingurlutil.h"
#include <QSignalSpy>
#include <QTest>
extern WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_CreatePhishingUrlDataBaseJob;
QByteArray readJsonFile(const QString &jsonFile)
{
QFile file(QLatin1String(CHECKPHISHINGURL_DATA_DIR) + QLatin1Char('/') + jsonFile);
file.open(QIODevice::ReadOnly);
Q_ASSERT(file.isOpen());
const QByteArray data = file.readAll();
//Q_ASSERT(!data.isEmpty());
return data;
}
QByteArray createHash(const QByteArray &ba)
{
QByteArray b = QCryptographicHash::hash(ba, QCryptographicHash::Sha256);
return b.toBase64();
}
CreatePhishingUrlDataBaseJobTest::CreatePhishingUrlDataBaseJobTest(QObject *parent)
: QObject(parent)
{
webengineview_useCompactJson_CreatePhishingUrlDataBaseJob = true;
}
CreatePhishingUrlDataBaseJobTest::~CreatePhishingUrlDataBaseJobTest()
{
}
void CreatePhishingUrlDataBaseJobTest::initTestCase()
{
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult>();
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType>();
qRegisterMetaType<WebEngineViewer::UpdateDataBaseInfo>();
}
void CreatePhishingUrlDataBaseJobTest::shouldClearUpdateDataBaseInfo()
{
WebEngineViewer::UpdateDataBaseInfo info;
WebEngineViewer::UpdateDataBaseInfo info2;
QCOMPARE(info, info2);
info.clear();
QCOMPARE(info, info2);
WebEngineViewer::UpdateDataBaseInfo value;
QVector<WebEngineViewer::Addition> additionList;
WebEngineViewer::Addition tmp;
tmp.prefixSize = 4;
tmp.hashString = QByteArrayLiteral("rnGLoQ==");
additionList.append(tmp);
QVector<WebEngineViewer::Removal> removalList;
WebEngineViewer::Removal tmpRemoval;
tmpRemoval.indexes = QList<quint32>() << 0 << 2 << 4;
removalList.append(tmpRemoval);
value.minimumWaitDuration = QStringLiteral("593.440s");
value.threatType = QStringLiteral("MALWARE");
value.threatEntryType = QStringLiteral("URL");
value.responseType = WebEngineViewer::UpdateDataBaseInfo::PartialUpdate;
value.platformType = QStringLiteral("WINDOWS");
value.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
value.sha256 = QByteArrayLiteral("YSgoRtsRlgHDqDA3LAhM1gegEpEzs1TjzU33vqsR8iM=");
value.additionList = additionList;
value.removalList = removalList;
info = value;
QCOMPARE(info, value);
info2 = info;
QCOMPARE(info, info2);
info2.clear();
info.clear();
QCOMPARE(info, info2);
WebEngineViewer::UpdateDataBaseInfo defaultValue;
QCOMPARE(info, defaultValue);
QCOMPARE(info2, defaultValue);
}
void CreatePhishingUrlDataBaseJobTest::shouldCreateRequest_data()
{
QTest::addColumn<QString>("databasestate");
QTest::addColumn<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadType>("downloadtype");
QTest::addColumn<WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType>("contraintsCompressionType");
QTest::addColumn<QString>("request");
QTest::newRow("fulldownload") << QString() << WebEngineViewer::CreatePhishingUrlDataBaseJob::FullDataBase << WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RAW\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("fulldownloadwithdatabasestate") << QStringLiteral("foo") << WebEngineViewer::CreatePhishingUrlDataBaseJob::FullDataBase
<< WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RAW\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("partialdownloadwithdatabasestate") << QStringLiteral("foo") << WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase
<< WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RAW\"]},\"platformType\":\"WINDOWS\",\"state\":\"foo\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("partialdownloadwithoutdatabasestate") << QString() << WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase << WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RAW\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("fulldownload-rice") << QString() << WebEngineViewer::CreatePhishingUrlDataBaseJob::FullDataBase << WebEngineViewer::CreatePhishingUrlDataBaseJob::RiceCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RICE\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("fulldownloadwithdatabasestate-rice") << QStringLiteral("foo") << WebEngineViewer::CreatePhishingUrlDataBaseJob::FullDataBase
<< WebEngineViewer::CreatePhishingUrlDataBaseJob::RiceCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RICE\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("partialdownloadwithdatabasestate-rice") << QStringLiteral("foo") << WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase
<< WebEngineViewer::CreatePhishingUrlDataBaseJob::RiceCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RICE\"]},\"platformType\":\"WINDOWS\",\"state\":\"foo\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
QTest::newRow("partialdownloadwithoutdatabasestate-rice") << QString() << WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase
<< WebEngineViewer::CreatePhishingUrlDataBaseJob::RiceCompression
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"listUpdateRequests\":[{\"constraints\":{\"supportedCompressions\":[\"RICE\"]},\"platformType\":\"WINDOWS\",\"state\":\"\",\"threatEntryType\":\"URL\",\"threatType\":\"MALWARE\"}]}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps());
}
void CreatePhishingUrlDataBaseJobTest::shouldCreateRequest()
{
QFETCH(QString, databasestate);
QFETCH(WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadType, downloadtype);
QFETCH(WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType, contraintsCompressionType);
QFETCH(QString, request);
WebEngineViewer::CreatePhishingUrlDataBaseJob job;
job.setDataBaseState(databasestate);
job.setDataBaseDownloadNeeded(downloadtype);
job.setContraintsCompressionType(contraintsCompressionType);
QCOMPARE(job.jsonRequest(), request.toLatin1());
}
void CreatePhishingUrlDataBaseJobTest::checkRiceDeltaEncoding_data()
{
QTest::addColumn<QByteArray>("encodingData");
QTest::addColumn<QByteArray>("firstValue");
QTest::addColumn<int>("numberEntries");
QTest::addColumn<int>("riceParameter");
QTest::addColumn<bool>("valid");
QTest::newRow("valid") << QByteArrayLiteral("ff") << QByteArrayLiteral("AAAA") << 15 << 18 << true;
QTest::newRow("valid1") << QByteArrayLiteral("ff") << QByteArrayLiteral("AAAA") << 0 << 0 << true;
QTest::newRow("invalid") << QByteArrayLiteral("ff") << QByteArrayLiteral("AAAA") << 99 << 200 << false;
QTest::newRow("invalid1") << QByteArray() << QByteArrayLiteral("AAAA") << 15 << 18 << false;
QTest::newRow("invalid2") << QByteArrayLiteral("AAAA") << QByteArray() << 15 << 18 << false;
QTest::newRow("invalid3") << QByteArray() << QByteArray() << 15 << 18 << false;
}
void CreatePhishingUrlDataBaseJobTest::checkRiceDeltaEncoding()
{
QFETCH(QByteArray, encodingData);
QFETCH(QByteArray, firstValue);
QFETCH(int, numberEntries);
QFETCH(int, riceParameter);
QFETCH(bool, valid);
WebEngineViewer::RiceDeltaEncoding a;
a.encodingData = encodingData;
a.firstValue = firstValue;
a.numberEntries = numberEntries;
a.riceParameter = riceParameter;
QCOMPARE(a.isValid(), valid);
WebEngineViewer::RiceDeltaEncoding b;
b = a;
QCOMPARE(b.isValid(), valid);
QCOMPARE(a, b);
}
void CreatePhishingUrlDataBaseJobTest::checkAdditionElements_data()
{
QTest::addColumn<QByteArray>("hashString");
QTest::addColumn<int>("prefixSize");
QTest::addColumn<WebEngineViewer::UpdateDataBaseInfo::CompressionType>("compression");
QTest::addColumn<bool>("isValid");
QTest::newRow("invalid") << QByteArray() << 4 << WebEngineViewer::UpdateDataBaseInfo::RawCompression << false;
QTest::newRow("notcorrectsize") << QByteArrayLiteral("IL5HqwT2c6bltw==") << 2 << WebEngineViewer::UpdateDataBaseInfo::RawCompression << false;
QTest::newRow("valid") << QByteArrayLiteral("IL5HqwT2c6bltw=") << 5 << WebEngineViewer::UpdateDataBaseInfo::RawCompression << true;
QTest::newRow("invalid1") << QByteArrayLiteral("foossso") << 4 << WebEngineViewer::UpdateDataBaseInfo::RawCompression << false;
//QByteArray b = createHash(QByteArrayLiteral("abcde"));
//QTest::newRow("valid1") << b << 5 << true;
}
void CreatePhishingUrlDataBaseJobTest::checkAdditionElements()
{
QFETCH(QByteArray, hashString);
QFETCH(int, prefixSize);
QFETCH(WebEngineViewer::UpdateDataBaseInfo::CompressionType, compression);
QFETCH(bool, isValid);
WebEngineViewer::Addition a;
a.hashString = hashString;
a.prefixSize = prefixSize;
a.compressionType = compression;
QCOMPARE(a.isValid(), isValid);
}
void CreatePhishingUrlDataBaseJobTest::checkRemovalElements_data()
{
QTest::addColumn<QList<quint32> >("lst");
QTest::addColumn<WebEngineViewer::UpdateDataBaseInfo::CompressionType>("compression");
QTest::addColumn<bool>("isValid");
QList<quint32> lst;
QTest::newRow("invalid") << lst << WebEngineViewer::UpdateDataBaseInfo::RawCompression << false;
lst << 1 << 2 << 3;
QTest::newRow("valid") << lst << WebEngineViewer::UpdateDataBaseInfo::RawCompression << true;
}
void CreatePhishingUrlDataBaseJobTest::checkRemovalElements()
{
QFETCH(QList<quint32>, lst);
QFETCH(WebEngineViewer::UpdateDataBaseInfo::CompressionType, compression);
QFETCH(bool, isValid);
WebEngineViewer::Removal a;
a.indexes = lst;
a.compressionType = compression;
QCOMPARE(a.isValid(), isValid);
}
void CreatePhishingUrlDataBaseJobTest::shouldParseResult_data()
{
QTest::addColumn<QString>("filename");
QTest::addColumn<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult>("parseResult");
QTest::addColumn<WebEngineViewer::UpdateDataBaseInfo>("parseInfo");
QTest::newRow("emptydocument") << QStringLiteral("empty.json") << WebEngineViewer::CreatePhishingUrlDataBaseJob::InvalidData << WebEngineViewer::UpdateDataBaseInfo();
QTest::newRow("emptydocument2") << QStringLiteral("empty2.json") << WebEngineViewer::CreatePhishingUrlDataBaseJob::InvalidData << WebEngineViewer::UpdateDataBaseInfo();
WebEngineViewer::UpdateDataBaseInfo value;
QVector<WebEngineViewer::Addition> additionList;
WebEngineViewer::Addition tmp;
tmp.prefixSize = 4;
QByteArray hash = QByteArrayLiteral("rnGLoQ==");
hash = QByteArray::fromBase64(hash);
tmp.hashString = hash;
tmp.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
additionList.append(tmp);
QVector<WebEngineViewer::Removal> removalList;
WebEngineViewer::Removal tmpRemoval;
tmpRemoval.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
tmpRemoval.indexes = QList<quint32>() << 0 << 2 << 4;
removalList.append(tmpRemoval);
value.minimumWaitDuration = QStringLiteral("593.440s");
value.threatType = QStringLiteral("MALWARE");
value.threatEntryType = QStringLiteral("URL");
value.responseType = WebEngineViewer::UpdateDataBaseInfo::PartialUpdate;
value.platformType = QStringLiteral("WINDOWS");
value.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
value.sha256 = QByteArrayLiteral("YSgoRtsRlgHDqDA3LAhM1gegEpEzs1TjzU33vqsR8iM=");
value.additionList = additionList;
value.removalList = removalList;
QTest::newRow("test1") << QStringLiteral("test1.json") << WebEngineViewer::CreatePhishingUrlDataBaseJob::ValidData << value;
value.clear();
QVector<WebEngineViewer::Addition> additionList2;
QByteArray hash1 = QByteArrayLiteral("AAAaxAAAG3QAACdhAAA");
hash1 = QByteArray::fromBase64(hash1);
tmp.hashString = hash1;
tmp.prefixSize = 4;
tmp.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
additionList2.append(tmp);
QByteArray hash2 = QByteArrayLiteral("IL5HqwT2c6bltw==");
hash2 = QByteArray::fromBase64(hash2);
tmp.hashString = hash2;
tmp.prefixSize = 5;
tmp.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
additionList2.append(tmp);
value.minimumWaitDuration = QStringLiteral("1786.932s");
value.threatType = QStringLiteral("MALWARE");
value.threatEntryType = QStringLiteral("URL");
value.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate;
value.platformType = QStringLiteral("WINDOWS");
value.newClientState = QStringLiteral("Cg0IARAGGAEiAzAwMTABELmwARoCGAUmgN3G");
value.sha256 = QByteArrayLiteral("ANcYWR8Umuoir+uNs1AhfxqW0iXEPDkxN6Pp2QF8dSs=");
value.additionList = additionList2;
QTest::newRow("test2") << QStringLiteral("test2.json") << WebEngineViewer::CreatePhishingUrlDataBaseJob::ValidData << value;
}
void CreatePhishingUrlDataBaseJobTest::shouldParseResult()
{
QFETCH(QString, filename);
QFETCH(WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult, parseResult);
QFETCH(WebEngineViewer::UpdateDataBaseInfo, parseInfo);
const QByteArray ba = readJsonFile(filename);
WebEngineViewer::CreatePhishingUrlDataBaseJob job;
QSignalSpy spy1(&job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::finished);
job.parseResult(ba);
QCOMPARE(spy1.count(), 1);
QCOMPARE(spy1.at(0).at(1).value<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult>(), parseResult);
QEXPECT_FAIL("test2", "Need to Investigate it", Continue);
QCOMPARE(spy1.at(0).at(0).value<WebEngineViewer::UpdateDataBaseInfo>(), parseInfo);
}
QTEST_MAIN(CreatePhishingUrlDataBaseJobTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.h b/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.h
index 1cfdaef5..b4426a08 100644
--- a/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/createphishingurldatabasejobtest.h
@@ -1,51 +1,51 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CREATEPHISHINGURLDATABASEJOBTEST_H
#define CREATEPHISHINGURLDATABASEJOBTEST_H
#include <QObject>
class CreatePhishingUrlDataBaseJobTest : public QObject
{
Q_OBJECT
public:
explicit CreatePhishingUrlDataBaseJobTest(QObject *parent = nullptr);
~CreatePhishingUrlDataBaseJobTest();
private Q_SLOTS:
void shouldCreateRequest_data();
void shouldCreateRequest();
void checkAdditionElements();
void checkAdditionElements_data();
void checkRemovalElements();
void checkRemovalElements_data();
void shouldParseResult_data();
void shouldParseResult();
void initTestCase();
void shouldClearUpdateDataBaseInfo();
void checkRiceDeltaEncoding_data();
void checkRiceDeltaEncoding();
};
#endif // CREATEPHISHINGURLDATABASEJOBTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp b/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
index 3a383811..24b67a7f 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.cpp
@@ -1,34 +1,34 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "downloadlocaldatabasethreadtest.h"
#include "../downloadlocaldatabasethread.h"
#include <QTest>
DownloadLocalDatabaseThreadTest::DownloadLocalDatabaseThreadTest(QObject *parent)
: QObject(parent)
{
}
DownloadLocalDatabaseThreadTest::~DownloadLocalDatabaseThreadTest()
{
}
QTEST_MAIN(DownloadLocalDatabaseThreadTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.h b/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.h
index 93cd7cc6..78ff471e 100644
--- a/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/downloadlocaldatabasethreadtest.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef DOWNLOADLOCALDATABASETHREADTEST_H
#define DOWNLOADLOCALDATABASETHREADTEST_H
#include <QObject>
class DownloadLocalDatabaseThreadTest : public QObject
{
Q_OBJECT
public:
explicit DownloadLocalDatabaseThreadTest(QObject *parent = nullptr);
~DownloadLocalDatabaseThreadTest();
};
#endif // DOWNLOADLOCALDATABASETHREADTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.cpp b/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.cpp
index b3a2a655..d23c78d8 100644
--- a/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.cpp
@@ -1,65 +1,65 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "hashcachemanagertest.h"
#include "../hashcachemanager.h"
#include <QStandardPaths>
#include <QTest>
HashCacheManagerTest::HashCacheManagerTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
HashCacheManagerTest::~HashCacheManagerTest()
{
}
void HashCacheManagerTest::shouldBeUnknowByDefault()
{
WebEngineViewer::HashCacheManager cache;
QCOMPARE(cache.hashStatus(QByteArrayLiteral("foo")), WebEngineViewer::HashCacheManager::Unknown);
}
void HashCacheManagerTest::shouldAddValue_data()
{
QTest::addColumn<QByteArray>("hash");
QTest::addColumn<uint>("seconds");
QTest::addColumn<WebEngineViewer::HashCacheManager::UrlStatus>("status");
uint currentValue = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
QTest::newRow("valid") << QByteArrayLiteral("foo") << (currentValue + 2000) << WebEngineViewer::HashCacheManager::UrlOk;
QTest::newRow("malware1validcache") << QByteArrayLiteral("bla") << (currentValue + 2000) << WebEngineViewer::HashCacheManager::MalWare;
QTest::newRow("malware1invalidcache") << QByteArrayLiteral("blu") << (currentValue - 2000) << WebEngineViewer::HashCacheManager::Unknown;
}
void HashCacheManagerTest::shouldAddValue()
{
QFETCH(QByteArray, hash);
QFETCH(uint, seconds);
QFETCH(WebEngineViewer::HashCacheManager::UrlStatus, status);
WebEngineViewer::HashCacheManager cache;
cache.addHashStatus(hash, status, seconds);
QCOMPARE(cache.hashStatus(hash), status);
cache.clearCache();
}
QTEST_MAIN(HashCacheManagerTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.h b/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.h
index beea05a6..55624993 100644
--- a/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/hashcachemanagertest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef HASHCACHEMANAGERTEST_H
#define HASHCACHEMANAGERTEST_H
#include <QObject>
class HashCacheManagerTest : public QObject
{
Q_OBJECT
public:
explicit HashCacheManagerTest(QObject *parent = nullptr);
~HashCacheManagerTest();
private Q_SLOTS:
void shouldBeUnknowByDefault();
void shouldAddValue_data();
void shouldAddValue();
};
#endif // HASHCACHEMANAGERTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.cpp b/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.cpp
index 3265b5d9..25bba3f2 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.cpp
@@ -1,124 +1,124 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "localdatabasefiletest.h"
#include "../localdatabasefile.h"
#include "../createdatabasefilejob.h"
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTest>
LocalDataBaseFileTest::LocalDataBaseFileTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl"));
}
LocalDataBaseFileTest::~LocalDataBaseFileTest()
{
}
void LocalDataBaseFileTest::shouldBeInvalidWithUnExistingFile()
{
WebEngineViewer::LocalDataBaseFile f(QStringLiteral("foo"));
QVERIFY(!f.isValid());
QVERIFY(!f.fileExists());
}
void LocalDataBaseFileTest::shouldCheckHashBinaryFile_data()
{
QTest::addColumn<QByteArray>("hash");
QTest::addColumn<QByteArray>("resultHash");
QTest::addColumn<bool>("found");
QTest::newRow("nohash") << QByteArrayLiteral("foo") << QByteArrayLiteral("efgh") << true;
QTest::newRow("foundhash") << QByteArrayLiteral("1111") << QByteArrayLiteral("1111") << true;
QTest::newRow("foundhash1") << QByteArrayLiteral("11111") << QByteArrayLiteral("1111") << true;
QTest::newRow("foundhash2") << QByteArrayLiteral("HGsse") << QByteArrayLiteral("54321") << true;
QTest::newRow("foundhash3") << QByteArrayLiteral("1112") << QByteArrayLiteral("1111") << true;
}
void LocalDataBaseFileTest::shouldCheckHashBinaryFile()
{
QFETCH(QByteArray, hash);
QFETCH(QByteArray, resultHash);
QFETCH(bool, found);
WebEngineViewer::CreateDatabaseFileJob databasejob;
const QString createDataBaseName = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl") + QStringLiteral("/correctBinary.db");
qDebug() << " new filename " << createDataBaseName;
databasejob.setFileName(createDataBaseName);
WebEngineViewer::UpdateDataBaseInfo info;
WebEngineViewer::Addition a;
a.hashString = QByteArray("----1111bbbb");
a.prefixSize = 4;
a.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition b;
b.hashString = QByteArray("abcdefgh");
b.prefixSize = 4;
b.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition c;
c.hashString = QByteArray("54321abcde");
c.prefixSize = 5;
c.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
WebEngineViewer::Addition d;
d.hashString = QByteArray("22222bcdef");
d.prefixSize = 5;
d.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
QVector<WebEngineViewer::Addition> lst;
lst << a << b << c << d;
info.additionList = lst;
info.minimumWaitDuration = QStringLiteral("593.440s");
info.threatType = QStringLiteral("MALWARE");
info.threatEntryType = QStringLiteral("URL");
info.responseType = WebEngineViewer::UpdateDataBaseInfo::FullUpdate;
info.platformType = QStringLiteral("WINDOWS");
info.newClientState = QStringLiteral("ChAIBRADGAEiAzAwMSiAEDABEAFGpqhd");
info.sha256 = QByteArrayLiteral("vLPta+N40Sip7Xo3XXgYvW5dpahS96vPwaOjxVospm8=");
databasejob.setUpdateDataBaseInfo(info);
QSignalSpy spy2(&databasejob, &WebEngineViewer::CreateDatabaseFileJob::finished);
databasejob.start();
QCOMPARE(spy2.count(), 1);
bool successCreateDataBase = spy2.at(0).at(0).toBool();
QVERIFY(successCreateDataBase);
WebEngineViewer::LocalDataBaseFile newFile(createDataBaseName);
QVERIFY(newFile.isValid());
QCOMPARE(newFile.getUint16(0), static_cast<quint16>(1));
QCOMPARE(newFile.getUint16(2), static_cast<quint16>(0));
quint64 number = newFile.getUint64(4);
QCOMPARE(number, static_cast<quint64>(9));
int index = 4 + sizeof(quint64);
QCOMPARE(index, 12);
const QByteArray val = newFile.searchHash(hash);
qDebug() << "result : " << val;
QCOMPARE(!val.isEmpty(), found);
if (!val.isEmpty()) {
QCOMPARE(resultHash, val);
}
}
QTEST_MAIN(LocalDataBaseFileTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.h b/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.h
index a8756c5b..94bea659 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/localdatabasefiletest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOCALDATABASEFILETEST_H
#define LOCALDATABASEFILETEST_H
#include <QObject>
class LocalDataBaseFileTest : public QObject
{
Q_OBJECT
public:
explicit LocalDataBaseFileTest(QObject *parent = nullptr);
~LocalDataBaseFileTest();
private Q_SLOTS:
void shouldBeInvalidWithUnExistingFile();
void shouldCheckHashBinaryFile_data();
void shouldCheckHashBinaryFile();
};
#endif // LOCALDATABASEFILETEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.cpp b/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.cpp
index 7aedbae7..d1a8a728 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.cpp
@@ -1,72 +1,72 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "localdatabasemanagertest.h"
#include "../localdatabasemanager.h"
#include "../localdatabasemanager_p.h"
#include <QTest>
class TestLocalDatabaseManagerPrivate : public WebEngineViewer::LocalDataBaseManagerPrivate
{
public:
TestLocalDatabaseManagerPrivate()
: WebEngineViewer::LocalDataBaseManagerPrivate()
{
}
protected:
void downloadDataBase(const QString &clientState) override
{
// don't actually download anything
}
};
class TestLocalDataBaseManager : public WebEngineViewer::LocalDataBaseManager
{
public:
TestLocalDataBaseManager(QObject *parent)
: WebEngineViewer::LocalDataBaseManager(new TestLocalDatabaseManagerPrivate, parent)
{
}
~TestLocalDataBaseManager()
{
delete d;
}
void setDownloadInfoSendByServer(const QString &data)
{
mDownloadInfoSendByServer = data;
}
private:
QString mDownloadInfoSendByServer;
};
LocalDataBaseManagerTest::LocalDataBaseManagerTest(QObject *parent)
: QObject(parent)
{
}
LocalDataBaseManagerTest::~LocalDataBaseManagerTest()
{
}
QTEST_MAIN(LocalDataBaseManagerTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h b/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
index 0bf0ff2b..44393943 100644
--- a/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/localdatabasemanagertest.h
@@ -1,33 +1,33 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOCALDATABASEMANAGERTEST_H
#define LOCALDATABASEMANAGERTEST_H
#include <QObject>
class LocalDataBaseManagerTest : public QObject
{
Q_OBJECT
public:
explicit LocalDataBaseManagerTest(QObject *parent = nullptr);
~LocalDataBaseManagerTest();
};
#endif // LOCALDATABASEMANAGERTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.cpp b/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.cpp
index e30c12d8..ea677b44 100644
--- a/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.cpp
@@ -1,112 +1,112 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "riceencodingdecodertest.h"
#include "../riceencodingdecoder.h"
#include <QTest>
RiceEncodingDecoderTest::RiceEncodingDecoderTest(QObject *parent)
: QObject(parent)
{
}
RiceEncodingDecoderTest::~RiceEncodingDecoderTest()
{
}
void RiceEncodingDecoderTest::shouldDecodeRiceIndices_data()
{
QTest::addColumn<QByteArray>("firstValue");
QTest::addColumn<int>("riceParameter");
QTest::addColumn<int>("numberEntries");
QTest::addColumn<QByteArray>("encodingData");
QTest::addColumn<QList<quint32> >("result");
QTest::newRow("empty") << QByteArray() << 0 << 0 << QByteArray() << QList<quint32>();
QList<quint32> result;
result << 3;
QTest::newRow("zero value") << QByteArray("3") << 2 << 0 << QByteArray() << result;
result.clear();
result << 5 << 20 << 29;
QTest::newRow("test1") << QByteArray("5") << 2 << 2 << QByteArrayLiteral("\xf7\x2") << result;
result.clear();
QTest::newRow("failedempty") << QByteArray("3") << 5 << 1 << QByteArray() << result;
result.clear();
//TODO fixme
//QTest::newRow("failednegativeentries") << QByteArray("3") << 5 << -1 << QByteArray() << result;
result.clear();
//QTest::newRow("failednonpositive") << QByteArray("3") << 0 << 1 << QByteArrayLiteral("a") << result;
result.clear();
QTest::newRow("failednonpositive1") << QByteArray("3") << -1 << 1 << QByteArrayLiteral("a") << result;
}
void RiceEncodingDecoderTest::shouldDecodeRiceIndices()
{
QFETCH(QByteArray, firstValue);
QFETCH(int, riceParameter);
QFETCH(int, numberEntries);
QFETCH(QByteArray, encodingData);
QFETCH(QList<quint32>, result);
WebEngineViewer::RiceEncodingDecoder decoding;
WebEngineViewer::RiceDeltaEncoding deltaEncoding;
deltaEncoding.encodingData = encodingData;
deltaEncoding.firstValue = firstValue;
deltaEncoding.numberEntries = numberEntries;
deltaEncoding.riceParameter = riceParameter;
QList<quint32> list = decoding.decodeRiceIndiceDelta(deltaEncoding);
QCOMPARE(list.count(), result.count());
QCOMPARE(list, result);
}
void RiceEncodingDecoderTest::shouldDecodeRiceHashes_data()
{
QTest::addColumn<QByteArray>("firstValue");
QTest::addColumn<int>("riceParameter");
QTest::addColumn<int>("numberEntries");
QTest::addColumn<QByteArray>("encodingData");
QTest::addColumn<QList<quint32> >("result");
QList<quint32> r;
QTest::newRow("empty") << QByteArray() << 0 << 0 << QByteArray() << r;
r.clear();
r = {5, 0xad934c0cu, 0x6ff67f56u, 0x81316fceu};
QTest::newRow("test1") << QByteArrayLiteral("5") << 28 << 3 << QByteArrayLiteral("\xbf\xa8\x3f\xfb\xf\xf\x5e\x27\xe6\xc3\x1d\xc6\x38") << r;
}
void RiceEncodingDecoderTest::shouldDecodeRiceHashes()
{
QFETCH(QByteArray, firstValue);
QFETCH(int, riceParameter);
QFETCH(int, numberEntries);
QFETCH(QByteArray, encodingData);
QFETCH(QList<quint32>, result);
WebEngineViewer::RiceEncodingDecoder decoding;
WebEngineViewer::RiceDeltaEncoding deltaEncoding;
deltaEncoding.encodingData = encodingData;
deltaEncoding.firstValue = firstValue;
deltaEncoding.numberEntries = numberEntries;
deltaEncoding.riceParameter = riceParameter;
const QList<quint32> hash = decoding.decodeRiceHashesDelta(deltaEncoding);
qDebug() << " hash " << hash << " 0xad934c0cu" << 0xad934c0cu;
QCOMPARE(hash, result);
}
QTEST_MAIN(RiceEncodingDecoderTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.h b/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.h
index fe62b104..26e26c1b 100644
--- a/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/riceencodingdecodertest.h
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef RICEENCODINGDECODERTEST_H
#define RICEENCODINGDECODERTEST_H
#include <QObject>
class RiceEncodingDecoderTest : public QObject
{
Q_OBJECT
public:
explicit RiceEncodingDecoderTest(QObject *parent = nullptr);
~RiceEncodingDecoderTest();
private Q_SLOTS:
void shouldDecodeRiceIndices_data();
void shouldDecodeRiceIndices();
void shouldDecodeRiceHashes_data();
void shouldDecodeRiceHashes();
};
#endif // RICEENCODINGDECODERTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.cpp b/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.cpp
index 46b19093..b84bf762 100644
--- a/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.cpp
@@ -1,90 +1,90 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "searchfullhashjobtest.h"
#include "../searchfullhashjob.h"
#include "../checkphishingurlutil.h"
#include <QTest>
SearchFullHashJobTest::SearchFullHashJobTest(QObject *parent)
: QObject(parent)
{
}
SearchFullHashJobTest::~SearchFullHashJobTest()
{
}
void SearchFullHashJobTest::shouldNotBeAbleToStartWithEmptyUrl()
{
WebEngineViewer::SearchFullHashJob job;
QVERIFY(!job.canStart());
}
void SearchFullHashJobTest::shouldCreateRequest_data()
{
QTest::addColumn<QHash<QByteArray, QByteArray> >("hash");
QTest::addColumn<QStringList>("databaseHash");
QTest::addColumn<QString>("request");
QTest::addColumn<QUrl>("url");
QTest::addColumn<bool>("canStart");
QTest::newRow("no hash") << QHash<QByteArray, QByteArray>() << QStringList() << QString() << QUrl() << false;
QTest::newRow("database hash but not hash and not url") << QHash<QByteArray, QByteArray>() << QStringList{
QStringLiteral("boo")
} << QString() << QUrl() << false;
QHash<QByteArray, QByteArray> hashs;
hashs.insert(QByteArrayLiteral("bla"), QByteArrayLiteral("bla"));
QTest::newRow("database hash but hash and not url") << hashs << QStringList{
QStringLiteral("boo")
} << QString() << QUrl() << false;
QTest::newRow("database hash and hash") << hashs << QStringList{
QStringLiteral("boo")
}
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"clientStates\":[\"boo\"],\"threatInfo\":{\"platformTypes\":[\"WINDOWS\"],\"threatEntries\":[{\"hash\":\"bla\"}],\"threatEntryTypes\":[\"URL\"],\"threatTypes\":[\"MALWARE\"]}}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps())
<< QUrl(QStringLiteral("http://www.kde.org")) << true;
QTest::newRow("multi database hash and hash") << hashs << (QStringList() << QStringLiteral("boo") << QStringLiteral("bli"))
<< QStringLiteral(
"{\"client\":{\"clientId\":\"KDE\",\"clientVersion\":\"%1\"},\"clientStates\":[\"boo\",\"bli\"],\"threatInfo\":{\"platformTypes\":[\"WINDOWS\"],\"threatEntries\":[{\"hash\":\"bla\"}],\"threatEntryTypes\":[\"URL\"],\"threatTypes\":[\"MALWARE\"]}}")
.arg(WebEngineViewer::CheckPhishingUrlUtil::versionApps())
<< QUrl(QStringLiteral("http://www.kde.org")) << true;
}
void SearchFullHashJobTest::shouldCreateRequest()
{
typedef QHash<QByteArray, QByteArray> hashdef;
QFETCH(hashdef, hash);
QFETCH(QStringList, databaseHash);
QFETCH(QString, request);
QFETCH(QUrl, url);
QFETCH(bool, canStart);
WebEngineViewer::SearchFullHashJob job;
job.setDatabaseState(databaseHash);
job.setSearchFullHashForUrl(url);
if (!hash.isEmpty()) {
job.setSearchHashs(hash);
}
QCOMPARE(job.canStart(), canStart);
if (canStart) {
QCOMPARE(job.jsonRequest(), request.toLatin1());
}
}
QTEST_MAIN(SearchFullHashJobTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.h b/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.h
index cc85723a..b745092c 100644
--- a/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/searchfullhashjobtest.h
@@ -1,38 +1,38 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef SEARCHFULLHASHJOBTEST_H
#define SEARCHFULLHASHJOBTEST_H
#include <QObject>
class SearchFullHashJobTest : public QObject
{
Q_OBJECT
public:
explicit SearchFullHashJobTest(QObject *parent = nullptr);
~SearchFullHashJobTest();
private Q_SLOTS:
void shouldNotBeAbleToStartWithEmptyUrl();
void shouldCreateRequest_data();
void shouldCreateRequest();
};
#endif // SEARCHFULLHASHJOBTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.cpp b/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.cpp
index 89df7375..d8467239 100644
--- a/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.cpp
@@ -1,185 +1,185 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "urlhashingtest.h"
#include "../urlhashing.h"
#include <QUrl>
#include <QTest>
UrlHashingTest::UrlHashingTest(QObject *parent)
: QObject(parent)
{
}
UrlHashingTest::~UrlHashingTest()
{
}
void UrlHashingTest::shouldCanonicalizeUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
#if 0
Canonicalize("http://host/%25%32%35") = "http://host/%25";
Canonicalize("http://host/%25%32%35%25%32%35") = "http://host/%25%25";
Canonicalize("http://host/%2525252525252525") = "http://host/%25";
Canonicalize("http://host/asdf%25%32%35asd") = "http://host/asdf%25asd";
Canonicalize("http://host/%%%25%32%35asd%%") = "http://host/%25%25%25asd%25%25";
Canonicalize("http://www.google.com/") = "http://www.google.com/";
Canonicalize("http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") = "http://168.188.99.26/.secure/www.ebay.com/";
Canonicalize("http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/")
= "http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/";
Canonicalize("http://host%23.com/%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A22%252833%252944_55%252B") = "http://host%23.com/~a!b@c%23d$e%25f^00&11*22(33)44_55+";
Canonicalize("http://3279880203/blah") = "http://195.127.0.11/blah";
Canonicalize("http://www.google.com/blah/..") = "http://www.google.com/";
Canonicalize("www.google.com/") = "http://www.google.com/";
Canonicalize("www.google.com") = "http://www.google.com/";
Canonicalize("http://www.evil.com/blah#frag") = "http://www.evil.com/blah";
Canonicalize("http://www.GOOgle.com/") = "http://www.google.com/";
Canonicalize("http://www.google.com.../") = "http://www.google.com/";
Canonicalize("http://www.google.com/foo\tbar\rbaz\n2") = "http://www.google.com/foobarbaz2";
Canonicalize("http://www.google.com/q?") = "http://www.google.com/q?";
Canonicalize("http://www.google.com/q?r?") = "http://www.google.com/q?r?";
Canonicalize("http://www.google.com/q?r?s") = "http://www.google.com/q?r?s";
Canonicalize("http://evil.com/foo#bar#baz") = "http://evil.com/foo";
Canonicalize("http://evil.com/foo;") = "http://evil.com/foo;";
Canonicalize("http://evil.com/foo?bar;") = "http://evil.com/foo?bar;";
Canonicalize("http://\x01\x80.com/") = "http://%01%80.com/";
Canonicalize("http://notrailingslash.com") = "http://notrailingslash.com/";
Canonicalize("http://www.gotaport.com:1234/") = "http://www.gotaport.com/";
Canonicalize(" http://www.google.com/ ") = "http://www.google.com/";
Canonicalize("http:// leadingspace.com/") = "http://%20leadingspace.com/";
Canonicalize("http://%20leadingspace.com/") = "http://%20leadingspace.com/";
Canonicalize("%20leadingspace.com/") = "http://%20leadingspace.com/";
Canonicalize("https://www.securesite.com/") = "https://www.securesite.com/";
Canonicalize("http://host.com/ab%23cd") = "http://host.com/ab%23cd";
Canonicalize("http://host.com//twoslashes?more//slashes") = "http://host.com/twoslashes?more//slashes";
#endif
QTest::newRow("empty") << QString() << QString();
QTest::newRow("http://host/%25%32%35") << QStringLiteral("http://host/%25%32%35") << QStringLiteral("http://host/%25");
QTest::newRow("http://host/%25%32%35%25%32%35") << QStringLiteral("http://host/%25%32%35%25%32%35") << QStringLiteral("http://host/%25%25");
QTest::newRow("http://host/%2525252525252525") << QStringLiteral("http://host/%2525252525252525") << QStringLiteral("http://host/%25");
QTest::newRow("http://host/asdf%25%32%35asd") << QStringLiteral("http://host/asdf%25%32%35asd") << QStringLiteral("http://host/asdf%25asd");
QTest::newRow("http://host/%%%25%32%35asd%%") << QStringLiteral("http://host/%%%25%32%35asd%%") << QStringLiteral("http://host/%25%25%25asd%25%25");
QTest::newRow("http://www.google.com/") << QStringLiteral("http://www.google.com/") << QStringLiteral("http://www.google.com/");
QTest::newRow("http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") << QStringLiteral(
"http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") << QStringLiteral("http://168.188.99.26/.secure/www.ebay.com/");
QTest::newRow("test8") << QStringLiteral("http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/") << QStringLiteral(
"http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/");
QTest::newRow("test9") << QStringLiteral("http://host%23.com/%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A22%252833%252944_55%252B") << QStringLiteral(
"http://host%23.com/~a!b@c%23d$e%25f^00&11*22(33)44_55+");
QTest::newRow("http://3279880203/blah") << QStringLiteral("http://3279880203/blah") << QStringLiteral("http://195.127.0.11/blah");
QTest::newRow("http://www.google.com/blah/..") << QStringLiteral("http://www.google.com/blah/..") << QStringLiteral("http://www.google.com/");
QTest::newRow("www.google.com/") << QStringLiteral("www.google.com/") << QStringLiteral("http://www.google.com/");
QTest::newRow("www.google.com") << QStringLiteral("www.google.com") << QStringLiteral("http://www.google.com/");
QTest::newRow("http://www.evil.com/blah#frag") << QStringLiteral("http://www.evil.com/blah#frag") << QStringLiteral("http://www.evil.com/blah");
QTest::newRow("http://www.GOOgle.com/") << QStringLiteral("http://www.GOOgle.com/") << QStringLiteral("http://www.google.com/");
QTest::newRow("http://www.google.com.../") << QStringLiteral("http://www.google.com.../") << QStringLiteral("http://www.google.com/");
QTest::newRow("http://www.google.com/foo\tbar\rbaz\n2") << QStringLiteral("http://www.google.com/foo\tbar\rbaz\n2") << QStringLiteral("http://www.google.com/foobarbaz2");
QTest::newRow("http://www.google.com/q?") << QStringLiteral("http://www.google.com/q?") << QStringLiteral("http://www.google.com/q?");
QTest::newRow("http://www.google.com/q?r?") << QStringLiteral("http://www.google.com/q?r?") << QStringLiteral("http://www.google.com/q?r?");
QTest::newRow("http://www.google.com/q?r?s") << QStringLiteral("http://www.google.com/q?r?s") << QStringLiteral("http://www.google.com/q?r?s");
QTest::newRow("http://evil.com/foo#bar#baz") << QStringLiteral("http://evil.com/foo#bar#baz") << QStringLiteral("http://evil.com/foo");
QTest::newRow("http://evil.com/foo;") << QStringLiteral("http://evil.com/foo;") << QStringLiteral("http://evil.com/foo;");
QTest::newRow("http://evil.com/foo?bar;") << QStringLiteral("http://evil.com/foo?bar;") << QStringLiteral("http://evil.com/foo?bar;");
QTest::newRow("http://\x01\x80.com/") << QStringLiteral("http://\x01\x80.com/") << QStringLiteral("http://%01%80.com/");
QTest::newRow("http://notrailingslash.com") << QStringLiteral("http://notrailingslash.com") << QStringLiteral("http://notrailingslash.com/");
QTest::newRow("http://www.gotaport.com:1234/") << QStringLiteral("http://www.gotaport.com:1234/") << QStringLiteral("http://www.gotaport.com/");
QTest::newRow(" http://www.google.com/ ") << QStringLiteral(" http://www.google.com/ ") << QStringLiteral("http://www.google.com/");
QTest::newRow("http:// leadingspace.com/") << QStringLiteral("http:// leadingspace.com/") << QStringLiteral("http://%20leadingspace.com/");
QTest::newRow("http://%20leadingspace.com/") << QStringLiteral("http://%20leadingspace.com/") << QStringLiteral("http://%20leadingspace.com/");
QTest::newRow("%20leadingspace.com/") << QStringLiteral("%20leadingspace.com/") << QStringLiteral("http://%20leadingspace.com/");
QTest::newRow("https://www.securesite.com/") << QStringLiteral("https://www.securesite.com/") << QStringLiteral("https://www.securesite.com/");
QTest::newRow("http://host.com/ab%23cd") << QStringLiteral("http://host.com/ab%23cd") << QStringLiteral("http://host.com/ab%23cd");
QTest::newRow("http://host.com//twoslashes?more//slashes") << QStringLiteral("http://host.com//twoslashes?more//slashes") << QStringLiteral("http://host.com/twoslashes?more//slashes");
}
void UrlHashingTest::shouldCanonicalizeUrl()
{
QFETCH(QString, input);
QFETCH(QString, output);
input = input.trimmed();
QEXPECT_FAIL("http://host/%2525252525252525", "Not supported yet", Continue);
QEXPECT_FAIL("http://\x01\x80.com/", "Not supported yet", Continue);
QEXPECT_FAIL("%20leadingspace.com/", "Not supported yet", Continue);
QEXPECT_FAIL("http://%20leadingspace.com/", "Not supported yet", Continue);
QEXPECT_FAIL("http://www.google.com.../", "Not supported yet", Continue);
QEXPECT_FAIL("http://http/host%23.com/%7Ea%21b%40c%23d%24e%f%5E00%2611%2A22%2833%2944_55%2B", "Not supported yet", Continue);
QEXPECT_FAIL("http://host/%%%25%32%35asd%%", "Not supported yet", Continue);
QEXPECT_FAIL("http://host/%25252525252525", "Not supported yet", Continue);
QEXPECT_FAIL("http:// leadingspace.com/", "Not supported yet", Continue);
QEXPECT_FAIL("test9", "Not supported yet", Continue);
QCOMPARE(WebEngineViewer::UrlHashing::canonicalizeUrl(QUrl::fromUserInput(input)), output);
}
void UrlHashingTest::shouldGenerateHostPath_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QStringList>("hosts");
QTest::addColumn<QStringList>("paths");
QTest::newRow("empty") << QString() << QStringList() << QStringList();
QStringList hosts;
QStringList paths;
hosts << QStringLiteral("b.c") << QStringLiteral("a.b.c");
paths << QStringLiteral("/") << QStringLiteral("/1/") << QStringLiteral("/1/2.html") << QStringLiteral("/1/2.html?param=1");
QTest::newRow("http://a.b.c/1/2.html?param=1") << QStringLiteral("http://a.b.c/1/2.html?param=1") << hosts << paths;
hosts.clear();
paths.clear();
hosts << QStringLiteral("f.g") << QStringLiteral("e.f.g") << QStringLiteral("d.e.f.g") << QStringLiteral("c.d.e.f.g") << QStringLiteral("a.b.c.d.e.f.g");
paths << QStringLiteral("/") << QStringLiteral("/1.html");
QTest::newRow("http://a.b.c.d.e.f.g/1.html") << QStringLiteral("http://a.b.c.d.e.f.g/1.html") << hosts << paths;
hosts.clear();
paths.clear();
hosts << QStringLiteral("a.b");
paths << QStringLiteral("/") << QStringLiteral("/saw-cgi/") << QStringLiteral("/saw-cgi/eBayISAPI.dll/");
QTest::newRow("http://a.b/saw-cgi/eBayISAPI.dll/") << QStringLiteral("http://a.b/saw-cgi/eBayISAPI.dll/") << hosts << paths;
}
void UrlHashingTest::shouldGenerateHostPath()
{
QFETCH(QString, input);
QFETCH(QStringList, hosts);
QFETCH(QStringList, paths);
QString result = WebEngineViewer::UrlHashing::canonicalizeUrl(QUrl::fromUserInput(input));
QUrl url(result);
QCOMPARE(WebEngineViewer::UrlHashing::generateHostsToCheck(url.host()), hosts);
QCOMPARE(WebEngineViewer::UrlHashing::generatePathsToCheck(url.path(), url.query()), paths);
}
void UrlHashingTest::shouldGenerateHashList_data()
{
QTest::addColumn<QUrl>("input");
QTest::addColumn<int>("numberItems");
QTest::newRow("http://a.b/saw-cgi/eBayISAPI.dll/") << QUrl(QStringLiteral("http://a.b/saw-cgi/eBayISAPI.dll/")) << 3;
}
void UrlHashingTest::shouldGenerateHashList()
{
QFETCH(QUrl, input);
QFETCH(int, numberItems);
WebEngineViewer::UrlHashing hashing(input);
QCOMPARE(hashing.hashList().count(), numberItems);
}
QTEST_MAIN(UrlHashingTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.h b/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.h
index 9aee5bd1..61645115 100644
--- a/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/urlhashingtest.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef URLHASHINGTEST_H
#define URLHASHINGTEST_H
#include <QObject>
class UrlHashingTest : public QObject
{
Q_OBJECT
public:
explicit UrlHashingTest(QObject *parent = nullptr);
~UrlHashingTest();
private Q_SLOTS:
void shouldCanonicalizeUrl();
void shouldCanonicalizeUrl_data();
void shouldGenerateHostPath_data();
void shouldGenerateHostPath();
void shouldGenerateHashList_data();
void shouldGenerateHashList();
};
#endif // URLHASHINGTEST_H
diff --git a/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.cpp b/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.cpp
index 3e52d347..685748ee 100644
--- a/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.cpp
+++ b/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.cpp
@@ -1,138 +1,138 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "verifydatabaseupdatetest.h"
#include "../updatedatabaseinfo.h"
#include <QCryptographicHash>
#include <QTest>
Q_DECLARE_METATYPE(QList<WebEngineViewer::Addition>)
VerifyDataBaseUpdateTest::VerifyDataBaseUpdateTest(QObject *parent)
: QObject(parent)
{
}
VerifyDataBaseUpdateTest::~VerifyDataBaseUpdateTest()
{
}
void VerifyDataBaseUpdateTest::shouldVerifyCheckSums_data()
{
QTest::addColumn<QList<WebEngineViewer::Addition> >("additionList");
QTest::addColumn<int>("numberOfItems");
QTest::addColumn<QByteArray>("calculateCheckSums");
WebEngineViewer::Addition a;
a.hashString = QByteArray("----1111bbbb");
a.prefixSize = 4;
WebEngineViewer::Addition b;
b.hashString = QByteArray("abcdefgh");
b.prefixSize = 4;
WebEngineViewer::Addition c;
c.hashString = QByteArray("54321abcde");
c.prefixSize = 5;
WebEngineViewer::Addition d;
d.hashString = QByteArray("22222bcdef");
d.prefixSize = 5;
QList<WebEngineViewer::Addition> lst;
lst << a << b << c << d;
QByteArray calculateCheckSums = QByteArrayLiteral("\xBC\xB3\xEDk\xE3x\xD1(\xA9\xEDz7]x\x18\xBDn]"
"\xA5\xA8R\xF7\xAB\xCF\xC1\xA3\xA3\xC5Z,\xA6o");
QTest::newRow("checksum1") << lst << 9 << calculateCheckSums;
lst.clear();
WebEngineViewer::Addition a1;
a1.hashString = QByteArray("----1111bbbbabcdefgh");
a1.prefixSize = 4;
WebEngineViewer::Addition c1;
c1.hashString = QByteArray("54321abcde22222bcdef");
c1.prefixSize = 5;
lst << a1 << c1;
QTest::newRow("checksum2") << lst << 9 << calculateCheckSums;
lst.clear();
WebEngineViewer::Addition c2;
c2.hashString = QByteArray("54321abcde22222bcdef");
c2.prefixSize = 5;
WebEngineViewer::Addition a2;
a2.hashString = QByteArray("----1111bbbbabcdefgh");
a2.prefixSize = 4;
lst << c2 << a2;
QTest::newRow("checksum3") << lst << 9 << calculateCheckSums;
}
void VerifyDataBaseUpdateTest::shouldVerifyCheckSums()
{
QFETCH(QList<WebEngineViewer::Addition>, additionList);
QFETCH(int, numberOfItems);
QFETCH(QByteArray, calculateCheckSums);
// Proof of checksum validity using python:
// >>> import hashlib
// >>> m = hashlib.sha256()
// >>> m.update("----11112222254321abcdabcdebbbbbcdefefgh")
// >>> m.digest()
// "\xbc\xb3\xedk\xe3x\xd1(\xa9\xedz7]"
// "x\x18\xbdn]\xa5\xa8R\xf7\xab\xcf\xc1\xa3\xa3\xc5Z,\xa6o"
QList<WebEngineViewer::Addition> itemToStore;
for (const WebEngineViewer::Addition &add : additionList) {
const QByteArray uncompressed = add.hashString;
for (int i = 0; i < uncompressed.size();) {
const QByteArray m = uncompressed.mid(i, add.prefixSize);
i += add.prefixSize;
WebEngineViewer::Addition tmp;
tmp.hashString = m;
tmp.prefixSize = add.prefixSize;
itemToStore << tmp;
if (m.size() != add.prefixSize) {
qDebug() << "hashstring: " << m << " hash size: " << m.size();
}
}
}
QCOMPARE(itemToStore.count(), numberOfItems);
QByteArray newSsha256;
std::sort(itemToStore.begin(), itemToStore.end(), WebEngineViewer::Addition::lessThan);
- Q_FOREACH (const WebEngineViewer::Addition &add, itemToStore) {
+ for (const WebEngineViewer::Addition &add : qAsConst(itemToStore)) {
QByteArray ba = add.hashString;
newSsha256 += ba;
}
QCOMPARE(newSsha256, QByteArrayLiteral("----11112222254321abcdabcdebbbbbcdefefgh"));
const QByteArray newSsha256Value = QCryptographicHash::hash(newSsha256, QCryptographicHash::Sha256);
QCOMPARE(newSsha256Value, calculateCheckSums);
QCOMPARE(newSsha256Value.toBase64(), calculateCheckSums.toBase64());
}
QTEST_MAIN(VerifyDataBaseUpdateTest)
diff --git a/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.h b/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.h
index 6ace5e64..5a0826bb 100644
--- a/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.h
+++ b/webengineviewer/src/checkphishingurl/autotests/verifydatabaseupdatetest.h
@@ -1,37 +1,37 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef VERIFYDATABASEUPDATETEST_H
#define VERIFYDATABASEUPDATETEST_H
#include <QObject>
class VerifyDataBaseUpdateTest : public QObject
{
Q_OBJECT
public:
explicit VerifyDataBaseUpdateTest(QObject *parent = nullptr);
~VerifyDataBaseUpdateTest();
private Q_SLOTS:
void shouldVerifyCheckSums();
void shouldVerifyCheckSums_data();
};
#endif // VERIFYDATABASEUPDATETEST_H
diff --git a/webengineviewer/src/checkphishingurl/backoffmodemanager.cpp b/webengineviewer/src/checkphishingurl/backoffmodemanager.cpp
index 93062dec..521e1ac9 100644
--- a/webengineviewer/src/checkphishingurl/backoffmodemanager.cpp
+++ b/webengineviewer/src/checkphishingurl/backoffmodemanager.cpp
@@ -1,166 +1,166 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "backoffmodemanager.h"
#include "checkphishingurlutil.h"
#include "webengineviewer_debug.h"
#include <KConfigGroup>
#include <KConfig>
#include <QTimer>
using namespace WebEngineViewer;
Q_GLOBAL_STATIC(BackOffModeManager, s_backOffModeManager)
class WebEngineViewer::BackOffModeManagerPrivate
{
public:
BackOffModeManagerPrivate(BackOffModeManager *qq)
: q(qq)
{
mTimer = new QTimer(q);
mTimer->setSingleShot(true);
q->connect(mTimer, &QTimer::timeout, q, &BackOffModeManager::slotTimerFinished);
load();
}
void save();
void load();
int calculateBackModeTime() const;
void startOffMode();
void exitBackOffMode();
void updateTimer(int seconds);
void slotTimerFinished();
BackOffModeManager *q;
QTimer *mTimer = nullptr;
int mNumberOfHttpFailed = 0;
bool isInOffMode = false;
};
void BackOffModeManagerPrivate::save()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("BackOffMode"));
grp.writeEntry("Enabled", isInOffMode);
if (isInOffMode) {
const int calculateTimeInSeconds = calculateBackModeTime();
const qint64 delay = QDateTime::currentDateTime().addSecs(calculateTimeInSeconds).toSecsSinceEpoch();
grp.writeEntry("Delay", delay);
updateTimer(calculateTimeInSeconds);
} else {
grp.deleteEntry("Delay");
}
grp.sync();
}
void BackOffModeManagerPrivate::slotTimerFinished()
{
qCDebug(WEBENGINEVIEWER_LOG) << "Existing from BlackOffMode";
exitBackOffMode();
save();
}
void BackOffModeManagerPrivate::updateTimer(int seconds)
{
if (mTimer->isActive()) {
mTimer->stop();
}
mTimer->setInterval(seconds * 1000);
mTimer->start();
}
void BackOffModeManagerPrivate::load()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("BackOffMode"));
isInOffMode = grp.readEntry("Enabled", false);
if (isInOffMode) {
const qint64 delay = grp.readEntry("Delay", 0);
const qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
if (delay > now) {
const qint64 diff = (delay - now);
updateTimer(diff);
} else {
//Disable mode.
isInOffMode = false;
}
}
}
int BackOffModeManagerPrivate::calculateBackModeTime() const
{
return WebEngineViewer::CheckPhishingUrlUtil::generateRandomSecondValue(mNumberOfHttpFailed);
}
void BackOffModeManagerPrivate::startOffMode()
{
mNumberOfHttpFailed++;
if (isInOffMode) {
qCWarning(WEBENGINEVIEWER_LOG) << "New failed" << mNumberOfHttpFailed;
} else {
qCWarning(WEBENGINEVIEWER_LOG) << "starting back of mode";
isInOffMode = true;
}
save();
}
void BackOffModeManagerPrivate::exitBackOffMode()
{
isInOffMode = false;
mNumberOfHttpFailed = 0;
}
BackOffModeManager::BackOffModeManager(QObject *parent)
: QObject(parent)
, d(new BackOffModeManagerPrivate(this))
{
}
BackOffModeManager::~BackOffModeManager()
{
delete d;
}
BackOffModeManager *BackOffModeManager::self()
{
return s_backOffModeManager;
}
bool BackOffModeManager::isInBackOffMode() const
{
return d->isInOffMode;
}
void BackOffModeManager::startOffMode()
{
d->startOffMode();
}
int BackOffModeManager::numberOfHttpFailed() const
{
return d->mNumberOfHttpFailed;
}
void BackOffModeManager::slotTimerFinished()
{
d->slotTimerFinished();
}
diff --git a/webengineviewer/src/checkphishingurl/backoffmodemanager.h b/webengineviewer/src/checkphishingurl/backoffmodemanager.h
index a1c5917b..010bad0c 100644
--- a/webengineviewer/src/checkphishingurl/backoffmodemanager.h
+++ b/webengineviewer/src/checkphishingurl/backoffmodemanager.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef BACKOFFMODEMANAGER_H
#define BACKOFFMODEMANAGER_H
#include <QObject>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class BackOffModeManagerPrivate;
//https://developers.google.com/safe-browsing/v4/request-frequency
class WEBENGINEVIEWER_EXPORT BackOffModeManager : public QObject
{
public:
explicit BackOffModeManager(QObject *parent = nullptr);
~BackOffModeManager();
static BackOffModeManager *self();
bool isInBackOffMode() const;
void startOffMode();
int numberOfHttpFailed() const;
public Q_SLOTS:
void slotTimerFinished();
private:
BackOffModeManagerPrivate *const d;
};
}
#endif // BACKOFFMODEMANAGER_H
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurlcache.cpp b/webengineviewer/src/checkphishingurl/checkphishingurlcache.cpp
index d49db142..738e9992 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurlcache.cpp
+++ b/webengineviewer/src/checkphishingurl/checkphishingurlcache.cpp
@@ -1,177 +1,177 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurlcache.h"
#include "webengineviewer_debug.h"
#include "checkphishingurlutil.h"
#include <KConfig>
#include <KConfigGroup>
#include <QMap>
using namespace WebEngineViewer;
struct UrlCacheInfo {
UrlCacheInfo()
{
}
bool isMalWare() const;
bool isValid() const;
CheckPhishingUrlCache::UrlStatus status = CheckPhishingUrlCache::Unknown;
uint verifyCacheAfterThisTime = 0;
};
bool UrlCacheInfo::isMalWare() const
{
return status == CheckPhishingUrlCache::MalWare;
}
bool UrlCacheInfo::isValid() const
{
return status != CheckPhishingUrlCache::Unknown;
}
class WebEngineViewer::CheckPhishingUrlCachePrivate
{
public:
CheckPhishingUrlCachePrivate()
{
load();
}
CheckPhishingUrlCache::UrlStatus urlStatus(const QUrl &url);
void addCheckPhishingUrlResult(const QUrl &url, bool correctUrl, uint verifyCacheAfterThisTime);
void clearCache();
void load();
void save();
private:
QMap<QUrl, UrlCacheInfo> mCacheCheckedUrl;
};
void CheckPhishingUrlCachePrivate::clearCache()
{
mCacheCheckedUrl.clear();
save();
}
void CheckPhishingUrlCachePrivate::load()
{
mCacheCheckedUrl.clear();
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("MalwareUrl"));
const QList<QUrl> listMalware = grp.readEntry("Url", QList<QUrl>());
const QList<double> listMalwareCachedTime = grp.readEntry("CachedTime", QList<double>());
if (listMalware.count() != listMalwareCachedTime.count()) {
qCWarning(WEBENGINEVIEWER_LOG) << "CheckPhishingUrlCachePrivate invalid number of data stored";
} else {
UrlCacheInfo info;
const int numberOfMalware = listMalware.count();
for (int i = 0; i < numberOfMalware; ++i) {
info.status = WebEngineViewer::CheckPhishingUrlCache::MalWare;
info.verifyCacheAfterThisTime = listMalwareCachedTime.at(i);
if (WebEngineViewer::CheckPhishingUrlUtil::cachedValueStillValid(info.verifyCacheAfterThisTime)) {
mCacheCheckedUrl.insert(listMalware.at(i), info);
}
}
}
}
void CheckPhishingUrlCachePrivate::save()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("MalwareUrl"));
QList<QUrl> listMalware;
QList<double> listMalwareCachedTime;
QMap<QUrl, UrlCacheInfo>::const_iterator i = mCacheCheckedUrl.constBegin();
const QMap<QUrl, UrlCacheInfo>::const_iterator end = mCacheCheckedUrl.constEnd();
while (i != end) {
const UrlCacheInfo info = i.value();
if (info.isMalWare() && WebEngineViewer::CheckPhishingUrlUtil::cachedValueStillValid(info.verifyCacheAfterThisTime)) {
listMalware.append(i.key());
listMalwareCachedTime.append(info.verifyCacheAfterThisTime);
}
++i;
}
grp.writeEntry("Url", listMalware);
grp.writeEntry("CachedTime", listMalwareCachedTime);
grp.sync();
}
CheckPhishingUrlCache::UrlStatus CheckPhishingUrlCachePrivate::urlStatus(const QUrl &url)
{
const UrlCacheInfo info = mCacheCheckedUrl.value(url, UrlCacheInfo());
if (info.isValid()) {
if (info.verifyCacheAfterThisTime > 0) {
if (CheckPhishingUrlUtil::cachedValueStillValid(info.verifyCacheAfterThisTime)) {
return info.status;
} else {
return CheckPhishingUrlCache::Unknown;
}
} else {
return info.status;
}
} else {
return CheckPhishingUrlCache::Unknown;
}
}
void CheckPhishingUrlCachePrivate::addCheckPhishingUrlResult(const QUrl &url, bool correctUrl, uint verifyCacheAfterThisTime)
{
UrlCacheInfo info;
info.status = correctUrl ? CheckPhishingUrlCache::UrlOk : CheckPhishingUrlCache::MalWare;
info.verifyCacheAfterThisTime = correctUrl ? 0 : verifyCacheAfterThisTime;
mCacheCheckedUrl.insert(url, info);
if (info.status == CheckPhishingUrlCache::MalWare) {
save();
}
}
CheckPhishingUrlCache::CheckPhishingUrlCache(QObject *parent)
: QObject(parent)
, d(new CheckPhishingUrlCachePrivate)
{
}
CheckPhishingUrlCache::~CheckPhishingUrlCache()
{
delete d;
}
void CheckPhishingUrlCache::addCheckingUrlResult(const QUrl &url, bool correctUrl, uint verifyCacheAfterThisTime)
{
d->addCheckPhishingUrlResult(url, correctUrl, verifyCacheAfterThisTime);
}
CheckPhishingUrlCache::UrlStatus CheckPhishingUrlCache::urlStatus(const QUrl &url)
{
return d->urlStatus(url);
}
void CheckPhishingUrlCache::clearCache()
{
d->clearCache();
}
CheckPhishingUrlCache *CheckPhishingUrlCache::self()
{
static CheckPhishingUrlCache s_self;
return &s_self;
}
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurlcache.h b/webengineviewer/src/checkphishingurl/checkphishingurlcache.h
index bfcb65b0..fa696643 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurlcache.h
+++ b/webengineviewer/src/checkphishingurl/checkphishingurlcache.h
@@ -1,68 +1,68 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLCACHE_H
#define CHECKPHISHINGURLCACHE_H
#include "webengineviewer_export.h"
#include <QObject>
#include <QUrl>
namespace WebEngineViewer {
class CheckPhishingUrlCachePrivate;
class WEBENGINEVIEWER_EXPORT CheckPhishingUrlCache : public QObject
{
Q_OBJECT
public:
static CheckPhishingUrlCache *self();
enum UrlStatus {
UrlOk = 0,
MalWare = 1,
Unknown = 2
};
explicit CheckPhishingUrlCache(QObject *parent = nullptr);
~CheckPhishingUrlCache();
/**
* @brief addCheckingUrlResult cache url. If @p correctUrl is true we store as UrlOk otherwise MalWare
* @param url
* @param correctUrl
*/
void addCheckingUrlResult(const QUrl &url, bool correctUrl, uint cacheDuration = 0);
/**
- * @brief urlStatus Return the status of cached Url. When we didn't stored it it returns Unknown
+ * @brief urlStatus Return the status of cached Url. When we didn't stored it, it returns Unknown
* @param url
* @return the status of url
*/
CheckPhishingUrlCache::UrlStatus urlStatus(const QUrl &url);
/**
* @brief clearCache clear the cache and save result in config file.
*/
void clearCache();
private:
CheckPhishingUrlCachePrivate *const d;
};
}
Q_DECLARE_METATYPE(WebEngineViewer::CheckPhishingUrlCache::UrlStatus)
#endif // CHECKPHISHINGURLCACHE_H
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp b/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp
index f229aeed..53f042b2 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp
+++ b/webengineviewer/src/checkphishingurl/checkphishingurljob.cpp
@@ -1,183 +1,183 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurljob.h"
#include "checkphishingurlutil.h"
#include <QNetworkAccessManager>
#include <PimCommon/NetworkManager>
#include <QJsonDocument>
#include <webengineviewer_debug.h>
#include <QNetworkConfigurationManager>
#include <QUrlQuery>
using namespace WebEngineViewer;
WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson = true;
class WebEngineViewer::CheckPhishingUrlJobPrivate
{
public:
CheckPhishingUrlJobPrivate()
: mNetworkAccessManager(nullptr)
{
}
QUrl mUrl;
QNetworkAccessManager *mNetworkAccessManager = nullptr;
};
CheckPhishingUrlJob::CheckPhishingUrlJob(QObject *parent)
: QObject(parent)
, d(new WebEngineViewer::CheckPhishingUrlJobPrivate)
{
d->mNetworkAccessManager = new QNetworkAccessManager(this);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &CheckPhishingUrlJob::slotCheckUrlFinished);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &CheckPhishingUrlJob::slotSslErrors);
}
CheckPhishingUrlJob::~CheckPhishingUrlJob()
{
delete d;
}
void CheckPhishingUrlJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
{
qCDebug(WEBENGINEVIEWER_LOG) << " void CheckPhishingUrlJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)" << error.count();
reply->ignoreSslErrors(error);
}
void CheckPhishingUrlJob::parse(const QByteArray &replyStr)
{
QJsonDocument document = QJsonDocument::fromJson(replyStr);
if (document.isNull()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
} else {
const QVariantMap answer = document.toVariant().toMap();
if (answer.isEmpty()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl);
return;
} else {
const QVariantList info = answer.value(QStringLiteral("matches")).toList();
if (info.count() == 1) {
const QVariantMap map = info.at(0).toMap();
const QString threatTypeStr = map[QStringLiteral("threatType")].toString();
const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString();
uint verifyCacheAfterThisTime = 0;
if (!cacheDuration.isEmpty()) {
double cacheDurationValue = WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(cacheDuration);
if (cacheDurationValue > 0) {
verifyCacheAfterThisTime = WebEngineViewer::CheckPhishingUrlUtil::refreshingCacheAfterThisTime(cacheDurationValue);
}
}
if (threatTypeStr == QStringLiteral("MALWARE")) {
const QVariantMap urlMap = map[QStringLiteral("threat")].toMap();
if (urlMap.count() == 1) {
if (urlMap[QStringLiteral("url")].toString() == d->mUrl.toString()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl, verifyCacheAfterThisTime);
return;
}
}
} else {
qWarning() << " CheckPhishingUrlJob::parse threatTypeStr : " << threatTypeStr;
}
}
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
}
}
}
void CheckPhishingUrlJob::slotCheckUrlFinished(QNetworkReply *reply)
{
parse(reply->readAll());
reply->deleteLater();
deleteLater();
}
void CheckPhishingUrlJob::setUrl(const QUrl &url)
{
d->mUrl = url;
}
QByteArray CheckPhishingUrlJob::jsonRequest() const
{
QVariantMap clientMap;
QVariantMap map;
clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
map.insert(QStringLiteral("client"), clientMap);
QVariantMap threatMap;
const QVariantList platformList = { QStringLiteral("WINDOWS") };
threatMap.insert(QStringLiteral("platformTypes"), platformList);
const QVariantList threatTypesList = { QStringLiteral("MALWARE") };
threatMap.insert(QStringLiteral("threatTypes"), threatTypesList);
const QVariantList threatEntryTypesList = { QStringLiteral("URL") };
threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList);
QVariantList threatEntriesList;
QVariantMap urlMap;
urlMap.insert(QStringLiteral("url"), d->mUrl.toString());
threatEntriesList.append(urlMap);
threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList);
map.insert(QStringLiteral("threatInfo"), threatMap);
const QJsonDocument postData = QJsonDocument::fromVariant(map);
const QByteArray baPostData = postData.toJson(webengineview_useCompactJson ? QJsonDocument::Compact : QJsonDocument::Indented);
return baPostData;
}
void CheckPhishingUrlJob::start()
{
if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl);
deleteLater();
} else if (canStart()) {
QUrlQuery query;
query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/threatMatches:find"));
safeUrl.setQuery(query);
QNetworkRequest request(safeUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
const QByteArray baPostData = jsonRequest();
qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
Q_EMIT debugJson(baPostData);
//curl -H "Content-Type: application/json" -X POST -d '{"client":{"clientId":"KDE","clientVersion":"5.4.0"},"threatInfo":{"platformTypes":["WINDOWS"],"threatEntries":[{"url":"http://www.kde.org"}],"threatEntryTypes":["URL"],"threatTypes":["MALWARE"]}}' https://safebrowsing.googleapis.com/v4/threatMatches:find?key=AIzaSyBS62pXATjabbH2RM_jO2EzDg1mTMHlnyo
QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
- connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CheckPhishingUrlJob::slotError);
+ connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &CheckPhishingUrlJob::slotError);
} else {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl);
deleteLater();
}
}
void CheckPhishingUrlJob::slotError(QNetworkReply::NetworkError error)
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString();
reply->deleteLater();
deleteLater();
}
bool CheckPhishingUrlJob::canStart() const
{
return d->mUrl.isValid();
}
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurljob.h b/webengineviewer/src/checkphishingurl/checkphishingurljob.h
index 349208a3..6d6c5591 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurljob.h
+++ b/webengineviewer/src/checkphishingurl/checkphishingurljob.h
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLJOB_H
#define CHECKPHISHINGURLJOB_H
#include <QObject>
#include <QUrl>
#include <QNetworkReply>
#include "webengineviewer_export.h"
#include "checkphishingurlutil.h"
namespace WebEngineViewer {
class CheckPhishingUrlJobPrivate;
/* https://developers.google.com/safe-browsing/v4/lookup-api */
/* example http://malware.testing.google.test/testing/malware/ */
class WEBENGINEVIEWER_EXPORT CheckPhishingUrlJob : public QObject
{
Q_OBJECT
public:
explicit CheckPhishingUrlJob(QObject *parent = nullptr);
~CheckPhishingUrlJob();
void setUrl(const QUrl &url);
void start();
bool canStart() const;
QByteArray jsonRequest() const;
void parse(const QByteArray &replyStr);
Q_SIGNALS:
void result(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status, const QUrl &url, uint verifyCacheAfterThisTime = 0);
void debugJson(const QByteArray &ba);
private Q_SLOTS:
void slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
void slotError(QNetworkReply::NetworkError error);
void slotCheckUrlFinished(QNetworkReply *reply);
private:
CheckPhishingUrlJobPrivate *const d;
};
}
#endif // CHECKPHISHINGURLJOB_H
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurlutil.cpp b/webengineviewer/src/checkphishingurl/checkphishingurlutil.cpp
index b5593c25..41d52c03 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurlutil.cpp
+++ b/webengineviewer/src/checkphishingurl/checkphishingurlutil.cpp
@@ -1,101 +1,101 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurlutil.h"
#include "webengineviewer_version.h"
#include <QDateTime>
#include <QtMath>
using namespace WebEngineViewer;
QString CheckPhishingUrlUtil::apiKey()
{
return QStringLiteral("AIzaSyBS62pXATjabbH2RM_jO2EzDg1mTMHlnyo");
}
QString CheckPhishingUrlUtil::versionApps()
{
return QStringLiteral(WEBENGINEVIEWER_VERSION_STRING);
}
QString CheckPhishingUrlUtil::databaseFileName()
{
return QStringLiteral("malware.db");
}
quint16 CheckPhishingUrlUtil::majorVersion()
{
return 1;
}
quint16 CheckPhishingUrlUtil::minorVersion()
{
return 0;
}
QString CheckPhishingUrlUtil::configFileName()
{
return QStringLiteral("phishingurlrc");
}
double CheckPhishingUrlUtil::convertToSecond(const QString &str)
{
QString minimumDuration = str;
if (!minimumDuration.isEmpty()) {
if (minimumDuration.endsWith(QLatin1Char('s'))) {
minimumDuration = minimumDuration.left(minimumDuration.length() - 1);
}
bool ok;
double val = minimumDuration.toDouble(&ok);
if (ok) {
return val;
}
}
return -1;
}
uint CheckPhishingUrlUtil::refreshingCacheAfterThisTime(double seconds)
{
if (seconds > 0) {
return QDateTime::currentDateTime().addMSecs(seconds * 1000).toSecsSinceEpoch();
} else {
return 0;
}
}
bool CheckPhishingUrlUtil::cachedValueStillValid(uint seconds)
{
return QDateTime::currentDateTimeUtc().toSecsSinceEpoch() < seconds;
}
int CheckPhishingUrlUtil::generateRandomSecondValue(int numberOfFailed)
{
//Random between 0-1
float r = static_cast<float>(rand()) / static_cast<float>(RAND_MAX) + 1;
const int numberOfSecondByDay = 24 * 60 * 60;
//MIN(((2^(n-1))*15 minutes) * (RAND + 1), 24 hours)
int seconds = 0;
if (numberOfFailed >= 1 && numberOfFailed < 9) {
seconds = static_cast<int>(qMin(static_cast<int>(qPow(2, numberOfFailed - 1)) * (15 * 60) * r, static_cast<float>(numberOfSecondByDay)));
} else if (numberOfFailed >= 9) {
seconds = numberOfSecondByDay;
}
return seconds;
}
diff --git a/webengineviewer/src/checkphishingurl/checkphishingurlutil.h b/webengineviewer/src/checkphishingurl/checkphishingurlutil.h
index 08a9067e..338a8fdf 100644
--- a/webengineviewer/src/checkphishingurl/checkphishingurlutil.h
+++ b/webengineviewer/src/checkphishingurl/checkphishingurlutil.h
@@ -1,49 +1,49 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLUTIL_H
#define CHECKPHISHINGURLUTIL_H
#include <QString>
#include <QObject>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
namespace CheckPhishingUrlUtil {
enum UrlStatus {
Ok = 0,
MalWare = 1,
BrokenNetwork = 2,
InvalidUrl = 3,
Unknown = 4
};
QString apiKey();
QString versionApps();
QString databaseFileName();
WEBENGINEVIEWER_EXPORT QString configFileName();
WEBENGINEVIEWER_EXPORT quint16 minorVersion();
WEBENGINEVIEWER_EXPORT quint16 majorVersion();
WEBENGINEVIEWER_EXPORT double convertToSecond(const QString &str);
WEBENGINEVIEWER_EXPORT uint refreshingCacheAfterThisTime(double seconds);
WEBENGINEVIEWER_EXPORT bool cachedValueStillValid(uint seconds);
WEBENGINEVIEWER_EXPORT int generateRandomSecondValue(int numberOfFailed);
}
}
Q_DECLARE_METATYPE(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus)
#endif // CHECKPHISHINGURLUTIL_H
diff --git a/webengineviewer/src/checkphishingurl/createdatabasefilejob.cpp b/webengineviewer/src/checkphishingurl/createdatabasefilejob.cpp
index 43967ca5..a9faeef7 100644
--- a/webengineviewer/src/checkphishingurl/createdatabasefilejob.cpp
+++ b/webengineviewer/src/checkphishingurl/createdatabasefilejob.cpp
@@ -1,248 +1,248 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createdatabasefilejob.h"
#include "checkphishingurlutil.h"
#include "webengineviewer_debug.h"
#include "localdatabasefile.h"
#include "riceencodingdecoder.h"
#include <QCryptographicHash>
#include <QFile>
using namespace WebEngineViewer;
class WebEngineViewer::CreateDatabaseFileJobPrivate
{
public:
CreateDatabaseFileJobPrivate(CreateDatabaseFileJob *qq)
: q(qq)
{
}
void createFileFromFullUpdate(const QVector<Addition> &additionList);
void removeElementFromDataBase(const QVector<Removal> &removalList, QVector<Addition> &oldDataBaseAddition);
void createBinaryFile();
void generateFile(bool fullUpdate);
WebEngineViewer::UpdateDataBaseInfo mInfoDataBase;
QString mFileName;
QFile mFile;
CreateDatabaseFileJob *q;
};
void CreateDatabaseFileJobPrivate::createFileFromFullUpdate(const QVector<Addition> &additionList)
{
//1 add version number
const quint16 major = WebEngineViewer::CheckPhishingUrlUtil::majorVersion();
const quint16 minor = WebEngineViewer::CheckPhishingUrlUtil::minorVersion();
qint64 hashStartPosition = mFile.write(reinterpret_cast<const char *>(&major), sizeof(major));
hashStartPosition += mFile.write(reinterpret_cast<const char *>(&minor), sizeof(minor));
//2 add number of items
QList<Addition> itemToStore;
for (const Addition &add : additionList) {
switch (add.compressionType) {
case UpdateDataBaseInfo::RawCompression:
{
//qCWarning(WEBENGINEVIEWER_LOG) << " add.size" << add.prefixSize;
const QByteArray uncompressed = add.hashString;
for (int i = 0; i < uncompressed.size();) {
const QByteArray m = uncompressed.mid(i, add.prefixSize);
i += add.prefixSize;
Addition tmp;
tmp.hashString = m;
tmp.prefixSize = add.prefixSize;
itemToStore << tmp;
//We store index as 8 octets.
hashStartPosition += 8;
if (m.size() != add.prefixSize) {
qCWarning(WEBENGINEVIEWER_LOG) << "hash string: " << m << " hash string size: " << m.size();
}
}
break;
}
case UpdateDataBaseInfo::RiceCompression:
{
//TODO
qCWarning(WEBENGINEVIEWER_LOG) << "Rice compression still not implemented";
const QList<quint32> listRice = WebEngineViewer::RiceEncodingDecoder::decodeRiceHashesDelta(add.riceDeltaEncoding);
qDebug() << " listRice" << listRice;
break;
}
case UpdateDataBaseInfo::UnknownCompression:
- qCWarning(WEBENGINEVIEWER_LOG) << "Unknow compression type in addition element";
+ qCWarning(WEBENGINEVIEWER_LOG) << "Unknown compression type in addition element";
break;
}
}
const quint64 numberOfElement = itemToStore.count();
hashStartPosition += mFile.write(reinterpret_cast<const char *>(&numberOfElement), sizeof(numberOfElement));
//3 add index of items
//Order it first
std::sort(itemToStore.begin(), itemToStore.end(), Addition::lessThan);
quint64 tmpPos = hashStartPosition;
for (const Addition &add : qAsConst(itemToStore)) {
mFile.write(reinterpret_cast<const char *>(&tmpPos), sizeof(tmpPos));
tmpPos += add.prefixSize + 1; //We add +1 as we store '\0'
}
//4 add items
QByteArray newSsha256;
for (const Addition &add : qAsConst(itemToStore)) {
const QByteArray storedBa = add.hashString + '\0';
mFile.write(reinterpret_cast<const char *>(storedBa.constData()), storedBa.size());
newSsha256 += add.hashString;
}
mFile.close();
//Verify hash with sha256
const QByteArray newSsha256Value = QCryptographicHash::hash(newSsha256, QCryptographicHash::Sha256);
const bool checkSumCorrect = (mInfoDataBase.sha256 == newSsha256Value.toBase64());
if (!checkSumCorrect) {
qCWarning(WEBENGINEVIEWER_LOG) << " newSsha256Value different from sha256 : " << newSsha256Value.toBase64() << " from server " << mInfoDataBase.sha256;
}
Q_EMIT q->finished(checkSumCorrect, mInfoDataBase.newClientState, mInfoDataBase.minimumWaitDuration);
}
void CreateDatabaseFileJobPrivate::generateFile(bool fullUpdate)
{
qCDebug(WEBENGINEVIEWER_LOG) << " void CreateDatabaseFileJobPrivate::generateFile(bool fullUpdate)" << fullUpdate;
mFile.setFileName(mFileName);
if (fullUpdate) {
if (mFile.exists() && !mFile.remove()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Impossible to remove database file " << mFileName;
Q_EMIT q->finished(false, QString(), QString());
return;
}
if (!mFile.open(QIODevice::WriteOnly)) {
qCWarning(WEBENGINEVIEWER_LOG) << "Impossible to open database file " << mFileName;
Q_EMIT q->finished(false, QString(), QString());
return;
}
createFileFromFullUpdate(mInfoDataBase.additionList);
} else {
WebEngineViewer::LocalDataBaseFile localeFile(mFileName);
if (!localeFile.fileExists()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Impossible to create partial update as file doesn't exist";
Q_EMIT q->finished(false, QString(), QString());
return;
}
//Read Element from database.
QVector<Addition> oldDataBaseAddition = localeFile.extractAllInfo();
removeElementFromDataBase(mInfoDataBase.removalList, oldDataBaseAddition);
QVector<Addition> additionList = mInfoDataBase.additionList; // Add value found in database
oldDataBaseAddition += additionList;
//Close file
localeFile.close();
if (!mFile.remove()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Impossible to remove database file " << mFileName;
Q_EMIT q->finished(false, QString(), QString());
return;
}
if (!mFile.open(QIODevice::WriteOnly)) {
qCWarning(WEBENGINEVIEWER_LOG) << "Impossible to open database file " << mFileName;
Q_EMIT q->finished(false, QString(), QString());
return;
}
createFileFromFullUpdate(oldDataBaseAddition);
}
}
void CreateDatabaseFileJobPrivate::removeElementFromDataBase(const QVector<Removal> &removalList, QVector<Addition> &oldDataBaseAddition)
{
QList<quint32> indexToRemove;
for (const Removal &removeItem : removalList) {
switch (removeItem.compressionType) {
case UpdateDataBaseInfo::RawCompression:
for (int id : qAsConst(removeItem.indexes)) {
indexToRemove << id;
}
break;
case UpdateDataBaseInfo::RiceCompression:
indexToRemove = WebEngineViewer::RiceEncodingDecoder::decodeRiceIndiceDelta(removeItem.riceDeltaEncoding);
break;
case UpdateDataBaseInfo::UnknownCompression:
qCWarning(WEBENGINEVIEWER_LOG) << " Unknown compression type defined in removal elements. It's a bug!";
break;
}
}
std::sort(indexToRemove.begin(), indexToRemove.end());
for (int i = (indexToRemove.count() - 1); i >= 0; --i) {
oldDataBaseAddition.remove(indexToRemove.at(i));
}
}
void CreateDatabaseFileJobPrivate::createBinaryFile()
{
switch (mInfoDataBase.responseType) {
case UpdateDataBaseInfo::Unknown:
- qCWarning(WEBENGINEVIEWER_LOG) << " Reponse Type of database info is \"unknow\". It's a bug!";
+ qCWarning(WEBENGINEVIEWER_LOG) << " Response Type of database info is \"unknown\". It's a bug!";
break;
case UpdateDataBaseInfo::FullUpdate:
case UpdateDataBaseInfo::PartialUpdate:
generateFile((mInfoDataBase.responseType == UpdateDataBaseInfo::FullUpdate));
break;
}
q->deleteLater();
}
CreateDatabaseFileJob::CreateDatabaseFileJob(QObject *parent)
: QObject(parent)
, d(new WebEngineViewer::CreateDatabaseFileJobPrivate(this))
{
}
CreateDatabaseFileJob::~CreateDatabaseFileJob()
{
delete d;
}
bool CreateDatabaseFileJob::canStart() const
{
return !d->mFileName.isEmpty() && d->mInfoDataBase.isValid();
}
void CreateDatabaseFileJob::setUpdateDataBaseInfo(const UpdateDataBaseInfo &infoDataBase)
{
d->mInfoDataBase = infoDataBase;
}
void CreateDatabaseFileJob::start()
{
if (!canStart()) {
Q_EMIT finished(false, QString(), QString());
deleteLater();
} else {
d->createBinaryFile();
}
}
void CreateDatabaseFileJob::setFileName(const QString &filename)
{
d->mFileName = filename;
}
diff --git a/webengineviewer/src/checkphishingurl/createdatabasefilejob.h b/webengineviewer/src/checkphishingurl/createdatabasefilejob.h
index c068fc9e..208e107b 100644
--- a/webengineviewer/src/checkphishingurl/createdatabasefilejob.h
+++ b/webengineviewer/src/checkphishingurl/createdatabasefilejob.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CREATEDATABASEFILEJOB_H
#define CREATEDATABASEFILEJOB_H
#include <QObject>
#include "webengineviewer_export.h"
#include "updatedatabaseinfo.h"
namespace WebEngineViewer {
class CreateDatabaseFileJobPrivate;
class WEBENGINEVIEWER_EXPORT CreateDatabaseFileJob : public QObject
{
Q_OBJECT
public:
explicit CreateDatabaseFileJob(QObject *parent = nullptr);
~CreateDatabaseFileJob();
void start();
void setFileName(const QString &filename);
bool canStart() const;
void setUpdateDataBaseInfo(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase);
Q_SIGNALS:
void finished(bool success, const QString &newStateDatabase, const QString &minimumWaitDurationStr);
private:
CreateDatabaseFileJobPrivate *const d;
};
}
#endif // CREATEDATABASEFILEJOB_H
diff --git a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp
index 6c880811..679117c8 100644
--- a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp
+++ b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.cpp
@@ -1,412 +1,412 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createphishingurldatabasejob.h"
#include "checkphishingurlutil.h"
#include "updatedatabaseinfo.h"
#include "webengineviewer_debug.h"
#include <PimCommon/NetworkManager>
#include <QNetworkConfigurationManager>
#include <QJsonDocument>
#include <QUrlQuery>
using namespace WebEngineViewer;
WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_CreatePhishingUrlDataBaseJob = true;
class WebEngineViewer::CreatePhishingUrlDataBaseJobPrivate
{
public:
CreatePhishingUrlDataBaseJobPrivate()
: mContraintsCompressionType(CreatePhishingUrlDataBaseJob::RawCompression)
, mDataBaseDownloadNeeded(CreatePhishingUrlDataBaseJob::FullDataBase)
, mNetworkAccessManager(nullptr)
{
}
UpdateDataBaseInfo::CompressionType parseCompressionType(const QString &str);
RiceDeltaEncoding parseRiceDeltaEncoding(const QMap<QString, QVariant> &map);
QVector<Removal> parseRemovals(const QVariantList &lst);
QVector<Addition> parseAdditions(const QVariantList &lst);
QString mDataBaseState;
CreatePhishingUrlDataBaseJob::ContraintsCompressionType mContraintsCompressionType;
CreatePhishingUrlDataBaseJob::DataBaseDownloadType mDataBaseDownloadNeeded;
QNetworkAccessManager *mNetworkAccessManager = nullptr;
};
CreatePhishingUrlDataBaseJob::CreatePhishingUrlDataBaseJob(QObject *parent)
: QObject(parent)
, d(new CreatePhishingUrlDataBaseJobPrivate)
{
d->mNetworkAccessManager = new QNetworkAccessManager(this);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &CreatePhishingUrlDataBaseJob::slotDownloadDataBaseFinished);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &CreatePhishingUrlDataBaseJob::slotSslErrors);
}
CreatePhishingUrlDataBaseJob::~CreatePhishingUrlDataBaseJob()
{
delete d;
}
void CreatePhishingUrlDataBaseJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
{
qCDebug(WEBENGINEVIEWER_LOG) << " void CreatePhishingUrlDataBaseJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)" << error.count();
reply->ignoreSslErrors(error);
}
void CreatePhishingUrlDataBaseJob::start()
{
if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) {
Q_EMIT finished(UpdateDataBaseInfo(), BrokenNetwork);
deleteLater();
} else {
QUrlQuery query;
query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch"));
safeUrl.setQuery(query);
//qCDebug(WEBENGINEVIEWER_LOG) << " safeUrl" << safeUrl;
QNetworkRequest request(safeUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
const QByteArray baPostData = jsonRequest();
Q_EMIT debugJson(baPostData);
qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
//curl -H "Content-Type: application/json" -X POST -d '{"client":{"clientId":"KDE","clientVersion":"5.4.0"},"threatInfo":{"platformTypes":["WINDOWS"],"threatEntries":[{"url":"http://www.kde.org"}],"threatEntryTypes":["URL"],"threatTypes":["MALWARE"]}}' https://safebrowsing.googleapis.com/v4/threatMatches:find?key=AIzaSyBS62pXATjabbH2RM_jO2EzDg1mTMHlnyo
QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
- connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CreatePhishingUrlDataBaseJob::slotError);
+ connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &CreatePhishingUrlDataBaseJob::slotError);
}
}
void CreatePhishingUrlDataBaseJob::setDataBaseState(const QString &value)
{
d->mDataBaseState = value;
}
void CreatePhishingUrlDataBaseJob::slotError(QNetworkReply::NetworkError error)
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
qWarning() << " error " << error << " error string : " << reply->errorString();
reply->deleteLater();
deleteLater();
}
QByteArray CreatePhishingUrlDataBaseJob::jsonRequest() const
{
#if 0
{
"client" : {
"clientId" : "yourcompanyname",
"clientVersion" : "1.5.2"
},
"listUpdateRequests" : [{
"threatType" : "MALWARE",
"platformType" : "WINDOWS",
"threatEntryType" : "URL",
"state" : "Gg4IBBADIgYQgBAiAQEoAQ==",
"constraints" : {
"maxUpdateEntries" : 2048,
"maxDatabaseEntries" : 4096,
"region" : "US",
"supportedCompressions" : ["RAW"]
}
}]
}
#endif
QVariantMap clientMap;
QVariantMap map;
clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
map.insert(QStringLiteral("client"), clientMap);
QVariantList listUpdateRequests;
QVariantMap threatMap;
threatMap.insert(QStringLiteral("platformType"), QStringLiteral("WINDOWS"));
threatMap.insert(QStringLiteral("threatType"), QStringLiteral("MALWARE"));
threatMap.insert(QStringLiteral("threatEntryType"), QStringLiteral("URL"));
//Contrainsts
QVariantMap contraintsMap;
QVariantList contraintsCompressionList;
QString compressionStr;
switch (d->mContraintsCompressionType) {
case RiceCompression:
compressionStr = QStringLiteral("RICE");
break;
case RawCompression:
compressionStr = QStringLiteral("RAW");
break;
}
contraintsCompressionList.append(compressionStr);
contraintsMap.insert(QStringLiteral("supportedCompressions"), contraintsCompressionList);
threatMap.insert(QStringLiteral("constraints"), contraintsMap);
//Define state when we want to define update database. Empty is full.
switch (d->mDataBaseDownloadNeeded) {
case FullDataBase:
qCDebug(WEBENGINEVIEWER_LOG) << " full update";
threatMap.insert(QStringLiteral("state"), QString());
break;
case UpdateDataBase:
qCDebug(WEBENGINEVIEWER_LOG) << " update database";
if (d->mDataBaseState.isEmpty()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Partial Download asked but database set is empty";
}
threatMap.insert(QStringLiteral("state"), d->mDataBaseState);
break;
}
listUpdateRequests.append(threatMap);
map.insert(QStringLiteral("listUpdateRequests"), listUpdateRequests);
const QJsonDocument postData = QJsonDocument::fromVariant(map);
const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_CreatePhishingUrlDataBaseJob ? QJsonDocument::Compact : QJsonDocument::Indented);
return baPostData;
}
void CreatePhishingUrlDataBaseJob::setDataBaseDownloadNeeded(CreatePhishingUrlDataBaseJob::DataBaseDownloadType type)
{
d->mDataBaseDownloadNeeded = type;
}
void CreatePhishingUrlDataBaseJob::slotDownloadDataBaseFinished(QNetworkReply *reply)
{
const QByteArray returnValue(reply->readAll());
Q_EMIT debugJsonResult(returnValue);
parseResult(returnValue);
reply->deleteLater();
}
RiceDeltaEncoding CreatePhishingUrlDataBaseJobPrivate::parseRiceDeltaEncoding(const QMap<QString, QVariant> &map)
{
RiceDeltaEncoding riceDeltaEncodingTmp;
QMap<QString, QVariant>::const_iterator riceHashesIt = map.cbegin();
const QMap<QString, QVariant>::const_iterator riceHashesItEnd = map.cend();
for (; riceHashesIt != riceHashesItEnd; ++riceHashesIt) {
const QString key = riceHashesIt.key();
if (key == QLatin1String("firstValue")) {
riceDeltaEncodingTmp.firstValue = riceHashesIt.value().toByteArray();
} else if (key == QLatin1String("riceParameter")) {
riceDeltaEncodingTmp.riceParameter = riceHashesIt.value().toInt();
} else if (key == QLatin1String("numEntries")) {
riceDeltaEncodingTmp.numberEntries = riceHashesIt.value().toInt();
} else if (key == QLatin1String("encodedData")) {
riceDeltaEncodingTmp.encodingData = riceHashesIt.value().toByteArray();
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRiceDeltaEncoding unknown riceDeltaEncoding key " << key;
}
}
return riceDeltaEncodingTmp;
}
QVector<Addition> CreatePhishingUrlDataBaseJobPrivate::parseAdditions(const QVariantList &lst)
{
QVector<Addition> additionList;
for (const QVariant &v : lst) {
if (v.canConvert<QVariantMap>()) {
QMapIterator<QString, QVariant> mapIt(v.toMap());
Addition tmp;
while (mapIt.hasNext()) {
mapIt.next();
const QString keyStr = mapIt.key();
if (keyStr == QLatin1String("compressionType")) {
tmp.compressionType = parseCompressionType(mapIt.value().toString());
} else if (keyStr == QLatin1String("riceHashes")) {
RiceDeltaEncoding riceDeltaEncodingTmp = parseRiceDeltaEncoding(mapIt.value().toMap());
if (riceDeltaEncodingTmp.isValid()) {
tmp.riceDeltaEncoding = riceDeltaEncodingTmp;
}
} else if (keyStr == QLatin1String("rawHashes")) {
QMapIterator<QString, QVariant> rawHashesIt(mapIt.value().toMap());
while (rawHashesIt.hasNext()) {
rawHashesIt.next();
const QString key = rawHashesIt.key();
if (key == QLatin1String("rawHashes")) {
tmp.hashString = QByteArray::fromBase64(rawHashesIt.value().toByteArray());
} else if (key == QLatin1String("prefixSize")) {
tmp.prefixSize = rawHashesIt.value().toInt();
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions unknown rawHashes key " << key;
}
}
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions unknown mapIt.key() " << keyStr;
}
}
if (tmp.isValid()) {
additionList.append(tmp);
}
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseAdditions not parsing type " << v.typeName();
}
}
return additionList;
}
UpdateDataBaseInfo::CompressionType CreatePhishingUrlDataBaseJobPrivate::parseCompressionType(const QString &str)
{
UpdateDataBaseInfo::CompressionType type(UpdateDataBaseInfo::UnknownCompression);
if (str == QLatin1String("COMPRESSION_TYPE_UNSPECIFIED")) {
type = UpdateDataBaseInfo::UnknownCompression;
} else if (str == QLatin1String("RICE")) {
type = UpdateDataBaseInfo::RiceCompression;
} else if (str == QLatin1String("RAW")) {
type = UpdateDataBaseInfo::RawCompression;
} else {
qCWarning(WEBENGINEVIEWER_LOG) << "CreatePhishingUrlDataBaseJob::parseCompressionType unknown compression type " << str;
}
return type;
}
QVector<Removal> CreatePhishingUrlDataBaseJobPrivate::parseRemovals(const QVariantList &lst)
{
QVector<Removal> removalList;
for (const QVariant &v : lst) {
if (v.canConvert<QVariantMap>()) {
Removal tmp;
QMapIterator<QString, QVariant> mapIt(v.toMap());
while (mapIt.hasNext()) {
mapIt.next();
const QString keyStr = mapIt.key();
if (keyStr == QLatin1String("compressionType")) {
tmp.compressionType = parseCompressionType(mapIt.value().toString());
} else if (keyStr == QLatin1String("riceIndices")) {
RiceDeltaEncoding riceDeltaEncodingTmp = parseRiceDeltaEncoding(mapIt.value().toMap());
if (riceDeltaEncodingTmp.isValid()) {
tmp.riceDeltaEncoding = riceDeltaEncodingTmp;
}
} else if (keyStr == QLatin1String("rawIndices")) {
const QVariantMap map = mapIt.value().toMap();
QMapIterator<QString, QVariant> rawIndicesIt(map);
while (rawIndicesIt.hasNext()) {
rawIndicesIt.next();
if (rawIndicesIt.key() == QStringLiteral("indices")) {
const QVariantList lst = rawIndicesIt.value().toList();
QList<quint32> indexList;
indexList.reserve(lst.count());
for (const QVariant &var : lst) {
indexList.append(var.toUInt());
}
tmp.indexes = indexList;
} else {
qCDebug(WEBENGINEVIEWER_LOG) << "rawIndicesIt.key() unknown " << rawIndicesIt.key();
}
}
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRemovals unknown mapIt.key() " << keyStr;
}
}
if (tmp.isValid()) {
removalList.append(tmp);
}
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " CreatePhishingUrlDataBaseJob::parseRemovals not parsing type " << v.typeName();
}
}
return removalList;
}
void CreatePhishingUrlDataBaseJob::parseResult(const QByteArray &value)
{
UpdateDataBaseInfo databaseInfo;
QJsonDocument document = QJsonDocument::fromJson(value);
if (document.isNull()) {
Q_EMIT finished(databaseInfo, InvalidData);
} else {
const QVariantMap answer = document.toVariant().toMap();
if (answer.isEmpty()) {
Q_EMIT finished(databaseInfo, InvalidData);
} else {
QMapIterator<QString, QVariant> i(answer);
while (i.hasNext()) {
i.next();
if (i.key() == QLatin1String("listUpdateResponses")) {
const QVariantList info = i.value().toList();
if (info.count() == 1) {
const QVariant infoVar = info.at(0);
if (infoVar.canConvert<QVariantMap>()) {
QMapIterator<QString, QVariant> mapIt(infoVar.toMap());
while (mapIt.hasNext()) {
mapIt.next();
const QString mapKey = mapIt.key();
if (mapKey == QLatin1String("additions")) {
const QVariantList lst = mapIt.value().toList();
const QVector<Addition> addList = d->parseAdditions(lst);
if (!addList.isEmpty()) {
databaseInfo.additionList.append(addList);
}
} else if (mapKey == QLatin1String("removals")) {
const QVariantList lst = mapIt.value().toList();
const QVector<Removal> removeList = d->parseRemovals(lst);
if (!removeList.isEmpty()) {
databaseInfo.removalList.append(removeList);
}
} else if (mapKey == QLatin1String("checksum")) {
QMapIterator<QString, QVariant> mapCheckSum(mapIt.value().toMap());
while (mapCheckSum.hasNext()) {
mapCheckSum.next();
if (mapCheckSum.key() == QLatin1String("sha256")) {
databaseInfo.sha256 = mapCheckSum.value().toByteArray();
} else {
qCDebug(WEBENGINEVIEWER_LOG) << "Invalid checksum key" << mapCheckSum.key();
}
}
} else if (mapKey == QLatin1String("newClientState")) {
databaseInfo.newClientState = mapIt.value().toString();
} else if (mapKey == QLatin1String("platformType")) {
databaseInfo.platformType = mapIt.value().toString();
} else if (mapKey == QLatin1String("responseType")) {
const QString str = mapIt.value().toString();
if (str == QLatin1String("FULL_UPDATE")) {
databaseInfo.responseType = UpdateDataBaseInfo::FullUpdate;
} else if (str == QLatin1String("PARTIAL_UPDATE")) {
databaseInfo.responseType = UpdateDataBaseInfo::PartialUpdate;
} else {
- qCDebug(WEBENGINEVIEWER_LOG) << " unknow responsetype " << str;
+ qCDebug(WEBENGINEVIEWER_LOG) << " unknown responsetype " << str;
databaseInfo.responseType = UpdateDataBaseInfo::Unknown;
}
} else if (mapKey == QLatin1String("threatEntryType")) {
databaseInfo.threatEntryType = mapIt.value().toString();
} else if (mapKey == QLatin1String("threatType")) {
databaseInfo.threatType = mapIt.value().toString();
} else {
- qCDebug(WEBENGINEVIEWER_LOG) << " unknow key " << mapKey;
+ qCDebug(WEBENGINEVIEWER_LOG) << " unknown key " << mapKey;
}
}
}
}
} else if (i.key() == QLatin1String("minimumWaitDuration")) {
databaseInfo.minimumWaitDuration = i.value().toString();
} else {
qCDebug(WEBENGINEVIEWER_LOG) << " map key unknown " << i.key();
}
}
Q_EMIT finished(databaseInfo, ValidData);
}
}
deleteLater();
}
void CreatePhishingUrlDataBaseJob::setContraintsCompressionType(CreatePhishingUrlDataBaseJob::ContraintsCompressionType type)
{
d->mContraintsCompressionType = type;
}
diff --git a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.h b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.h
index f205d5de..a6b1d2b7 100644
--- a/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.h
+++ b/webengineviewer/src/checkphishingurl/createphishingurldatabasejob.h
@@ -1,84 +1,84 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CREATEPHISHINGURLDATABASEJOB_H
#define CREATEPHISHINGURLDATABASEJOB_H
#include <QObject>
#include <QSslError>
#include <QNetworkReply>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
struct UpdateDataBaseInfo;
class CreatePhishingUrlDataBaseJobPrivate;
/* https://developers.google.com/safe-browsing/v4/update-api */
class WEBENGINEVIEWER_EXPORT CreatePhishingUrlDataBaseJob : public QObject
{
Q_OBJECT
public:
enum DataBaseDownloadType {
FullDataBase = 0,
UpdateDataBase = 1
};
enum DataBaseDownloadResult {
InvalidData = 0,
ValidData = 1,
UnknownError = 2,
BrokenNetwork = 3
};
enum ContraintsCompressionType {
RawCompression = 0,
RiceCompression = 1
};
explicit CreatePhishingUrlDataBaseJob(QObject *parent = nullptr);
~CreatePhishingUrlDataBaseJob();
void start();
void setDataBaseState(const QString &value);
void setDataBaseDownloadNeeded(WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadType type);
QByteArray jsonRequest() const;
void parseResult(const QByteArray &value);
void setContraintsCompressionType(CreatePhishingUrlDataBaseJob::ContraintsCompressionType type);
Q_SIGNALS:
void finished(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase, WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult status);
void debugJsonResult(const QByteArray &ba);
void debugJson(const QByteArray &ba);
private:
void slotDownloadDataBaseFinished(QNetworkReply *reply);
void slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
void slotError(QNetworkReply::NetworkError error);
CreatePhishingUrlDataBaseJobPrivate *const d;
};
}
Q_DECLARE_METATYPE(WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadType)
Q_DECLARE_METATYPE(WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult)
Q_DECLARE_METATYPE(WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType)
#endif // CREATEPHISHINGURLDATABASEJOB_H
diff --git a/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.cpp b/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.cpp
index 63f9b169..78b70ba5 100644
--- a/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.cpp
+++ b/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.cpp
@@ -1,117 +1,117 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createdatabasefilejob.h"
#include "webengineviewer_debug.h"
#include "downloadlocaldatabasethread.h"
#include <WebEngineViewer/CreatePhishingUrlDataBaseJob>
using namespace WebEngineViewer;
DownloadLocalDatabaseThread::DownloadLocalDatabaseThread(QObject *parent)
: QThread(parent)
{
qCDebug(WEBENGINEVIEWER_LOG) << "DownloadLocalDatabaseThread::DownloadLocalDatabaseThread " << this;
}
DownloadLocalDatabaseThread::~DownloadLocalDatabaseThread()
{
qCDebug(WEBENGINEVIEWER_LOG) << "DownloadLocalDatabaseThread::~DownloadLocalDatabaseThread " << this;
}
void DownloadLocalDatabaseThread::setDataBaseState(const QString &value)
{
mCurrentDataBaseState = value;
}
void DownloadLocalDatabaseThread::run()
{
if (mDatabaseFullPath.isEmpty()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Database full path is empty";
Q_EMIT createDataBaseFailed();
deleteLater();
return;
}
WebEngineViewer::CreatePhishingUrlDataBaseJob *job = new WebEngineViewer::CreatePhishingUrlDataBaseJob;
job->moveToThread(this);
job->setDataBaseDownloadNeeded(mCurrentDataBaseState.isEmpty() ? WebEngineViewer::CreatePhishingUrlDataBaseJob::FullDataBase : WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase);
job->setDataBaseState(mCurrentDataBaseState);
connect(job, &CreatePhishingUrlDataBaseJob::finished, this, &DownloadLocalDatabaseThread::slotDownloadDataBaseFinished);
job->start();
exec();
}
void DownloadLocalDatabaseThread::slotDownloadDataBaseFinished(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase, WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult status)
{
bool dataBaseOk = false;
switch (status) {
case CreatePhishingUrlDataBaseJob::InvalidData:
qCWarning(WEBENGINEVIEWER_LOG) << "Invalid Data.";
dataBaseOk = false;
break;
case CreatePhishingUrlDataBaseJob::ValidData:
qCWarning(WEBENGINEVIEWER_LOG) << "Valid Data.";
dataBaseOk = true;
break;
case CreatePhishingUrlDataBaseJob::UnknownError:
qCWarning(WEBENGINEVIEWER_LOG) << "Unknown data.";
dataBaseOk = false;
break;
case CreatePhishingUrlDataBaseJob::BrokenNetwork:
qCWarning(WEBENGINEVIEWER_LOG) << "Broken Networks.";
dataBaseOk = false;
break;
}
if (dataBaseOk) {
if (mCurrentDataBaseState == infoDataBase.newClientState) {
qCDebug(WEBENGINEVIEWER_LOG) << "No update necessary ";
} else {
switch (infoDataBase.responseType) {
case WebEngineViewer::UpdateDataBaseInfo::FullUpdate:
case WebEngineViewer::UpdateDataBaseInfo::PartialUpdate:
installNewDataBase(infoDataBase);
break;
case WebEngineViewer::UpdateDataBaseInfo::Unknown:
break;
}
}
} else {
Q_EMIT createDataBaseFailed();
quit();
}
}
void DownloadLocalDatabaseThread::setDatabaseFullPath(const QString &databaseFullPath)
{
mDatabaseFullPath = databaseFullPath;
}
void DownloadLocalDatabaseThread::installNewDataBase(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase)
{
WebEngineViewer::CreateDatabaseFileJob *job = new WebEngineViewer::CreateDatabaseFileJob;
job->setFileName(mDatabaseFullPath);
job->setUpdateDataBaseInfo(infoDataBase);
connect(job, &CreateDatabaseFileJob::finished, this, &DownloadLocalDatabaseThread::slotCreateDataBaseFileNameFinished);
job->start();
}
void DownloadLocalDatabaseThread::slotCreateDataBaseFileNameFinished(bool success, const QString &newClientState, const QString &minimumWaitDurationStr)
{
Q_EMIT createDataBaseFinished(success, newClientState, minimumWaitDurationStr);
quit();
}
diff --git a/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.h b/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.h
index 11beb83e..6ca1230f 100644
--- a/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.h
+++ b/webengineviewer/src/checkphishingurl/downloadlocaldatabasethread.h
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef DOWNLOADLOCALDATABASETHREAD_H
#define DOWNLOADLOCALDATABASETHREAD_H
#include <QThread>
#include "webengineviewer_private_export.h"
#include <WebEngineViewer/CreatePhishingUrlDataBaseJob>
namespace WebEngineViewer {
class WEBENGINEVIEWER_TESTS_EXPORT DownloadLocalDatabaseThread : public QThread
{
Q_OBJECT
public:
explicit DownloadLocalDatabaseThread(QObject *parent = nullptr);
~DownloadLocalDatabaseThread() override;
void setDataBaseState(const QString &value);
void setDatabaseFullPath(const QString &databaseFullPath);
Q_SIGNALS:
void createDataBaseFinished(bool success, const QString &newClientState, const QString &minimumWaitDurationStr);
void createDataBaseFailed();
protected:
void run() override;
private:
void installNewDataBase(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase);
void slotDownloadDataBaseFinished(const WebEngineViewer::UpdateDataBaseInfo &infoDataBase, WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult status);
void slotCreateDataBaseFileNameFinished(bool success, const QString &newClientState, const QString &minimumWaitDurationStr);
QString mCurrentDataBaseState;
QString mDatabaseFullPath;
};
}
#endif // DOWNLOADLOCALDATABASETHREAD_H
diff --git a/webengineviewer/src/checkphishingurl/hashcachemanager.cpp b/webengineviewer/src/checkphishingurl/hashcachemanager.cpp
index 87f2a9af..bff02922 100644
--- a/webengineviewer/src/checkphishingurl/hashcachemanager.cpp
+++ b/webengineviewer/src/checkphishingurl/hashcachemanager.cpp
@@ -1,211 +1,211 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "hashcachemanager.h"
#include "webengineviewer_debug.h"
#include "checkphishingurlutil.h"
#include <KConfig>
#include <KConfigGroup>
using namespace WebEngineViewer;
struct HashCacheInfo {
HashCacheInfo()
{
}
bool isValid() const;
HashCacheManager::UrlStatus status = HashCacheManager::Unknown;
uint verifyCacheAfterThisTime = 0;
};
bool HashCacheInfo::isValid() const
{
return status != HashCacheManager::Unknown;
}
class WebEngineViewer::HashCacheManagerPrivate
{
public:
HashCacheManagerPrivate()
{
load();
}
void clearCache();
void save();
void load();
void addHashStatus(const QByteArray &hash, HashCacheManager::UrlStatus status, uint cacheDuration);
HashCacheManager::UrlStatus hashStatus(const QByteArray &hash);
private:
void clear();
QMap<QByteArray, HashCacheInfo> mHashList;
};
void HashCacheManagerPrivate::clear()
{
mHashList.clear();
}
void HashCacheManagerPrivate::clearCache()
{
clear();
save();
}
void HashCacheManagerPrivate::save()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("Hash"));
QList<QByteArray> lstMalware;
QList<double> lstMalwareDuration;
QList<QByteArray> lstOk;
QList<double> lstOkDuration;
QMapIterator<QByteArray, HashCacheInfo> i(mHashList);
while (i.hasNext()) {
i.next();
switch (i.value().status) {
case HashCacheManager::UrlOk:
lstOk << i.key();
lstOkDuration << i.value().verifyCacheAfterThisTime;
break;
case HashCacheManager::MalWare:
lstMalware << i.key();
lstMalwareDuration << i.value().verifyCacheAfterThisTime;
break;
case HashCacheManager::Unknown:
break;
}
}
grp.writeEntry("malware", lstMalware);
grp.writeEntry("malwareCacheDuration", lstMalwareDuration);
grp.writeEntry("safe", lstOk);
grp.writeEntry("safeCacheDuration", lstOkDuration);
grp.sync();
}
void HashCacheManagerPrivate::load()
{
clear();
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("Hash"));
QList<QByteArray> lstMalware = grp.readEntry("malware", QList<QByteArray>());
QList<double> lstMalwareDuration = grp.readEntry("malwareCacheDuration", QList<double>());
QList<QByteArray> lstOk = grp.readEntry("safe", QList<QByteArray>());
QList<double> lstOkDuration = grp.readEntry("safeCacheDuration", QList<double>());
if (lstMalware.count() != lstMalwareDuration.count()) {
qCWarning(WEBENGINEVIEWER_LOG) << "unsafe url: HashCacheManagerPrivate invalid number of data stored";
return;
}
if (lstOk.count() != lstOkDuration.count()) {
qCWarning(WEBENGINEVIEWER_LOG) << "safe url: HashCacheManagerPrivate invalid number of data stored";
return;
}
const int nb(lstOk.count());
for (int i = 0; i < nb; ++i) {
HashCacheInfo info;
info.status = HashCacheManager::UrlOk;
info.verifyCacheAfterThisTime = lstOkDuration.at(i);
if (info.isValid()) {
mHashList.insert(lstOk.at(i), info);
}
}
const int nb2(lstMalware.count());
for (int i = 0; i < nb2; ++i) {
HashCacheInfo info;
info.status = HashCacheManager::MalWare;
info.verifyCacheAfterThisTime = lstMalwareDuration.at(i);
if (info.isValid()) {
mHashList.insert(lstMalware.at(i), info);
}
}
}
HashCacheManager::UrlStatus HashCacheManagerPrivate::hashStatus(const QByteArray &hash)
{
const HashCacheInfo info = mHashList.value(hash, HashCacheInfo());
if (info.isValid()) {
if (CheckPhishingUrlUtil::cachedValueStillValid(info.verifyCacheAfterThisTime)) {
return info.status;
} else {
return HashCacheManager::Unknown;
}
} else {
return HashCacheManager::Unknown;
}
}
void HashCacheManagerPrivate::addHashStatus(const QByteArray &hash, HashCacheManager::UrlStatus status, uint cacheDuration)
{
HashCacheInfo info;
info.status = status;
info.verifyCacheAfterThisTime = cacheDuration;
switch (status) {
case HashCacheManager::UrlOk:
mHashList.insert(hash, info);
break;
case HashCacheManager::MalWare:
mHashList.insert(hash, info);
break;
case HashCacheManager::Unknown:
- qCWarning(WEBENGINEVIEWER_LOG()) << "HashCacheManagerPrivate::addHashStatus unknow status detected!";
+ qCWarning(WEBENGINEVIEWER_LOG()) << "HashCacheManagerPrivate::addHashStatus unknown status detected!";
return;
}
save();
}
HashCacheManager *HashCacheManager::self()
{
static HashCacheManager s_self;
return &s_self;
}
HashCacheManager::HashCacheManager(QObject *parent)
: QObject(parent)
, d(new HashCacheManagerPrivate)
{
}
HashCacheManager::~HashCacheManager()
{
delete d;
}
void HashCacheManager::clearCache()
{
d->clearCache();
}
void HashCacheManager::addHashStatus(const QByteArray &hash, HashCacheManager::UrlStatus status, uint cacheDuration)
{
d->addHashStatus(hash, status, cacheDuration);
}
HashCacheManager::UrlStatus HashCacheManager::hashStatus(const QByteArray &hash)
{
return d->hashStatus(hash);
}
diff --git a/webengineviewer/src/checkphishingurl/hashcachemanager.h b/webengineviewer/src/checkphishingurl/hashcachemanager.h
index 9d4ec8f3..ffd9cd6c 100644
--- a/webengineviewer/src/checkphishingurl/hashcachemanager.h
+++ b/webengineviewer/src/checkphishingurl/hashcachemanager.h
@@ -1,55 +1,55 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef HASHCACHEMANAGER_H
#define HASHCACHEMANAGER_H
#include <QObject>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class HashCacheManagerPrivate;
//https://developers.google.com/safe-browsing/v4/caching
class WEBENGINEVIEWER_EXPORT HashCacheManager : public QObject
{
Q_OBJECT
public:
enum UrlStatus {
UrlOk = 0,
MalWare = 1,
Unknown = 2
};
explicit HashCacheManager(QObject *parent = nullptr);
~HashCacheManager();
static HashCacheManager *self();
void clearCache();
void addHashStatus(const QByteArray &hash, HashCacheManager::UrlStatus status, uint cacheDuration);
HashCacheManager::UrlStatus hashStatus(const QByteArray &hash);
private:
HashCacheManagerPrivate *const d;
};
}
Q_DECLARE_METATYPE(WebEngineViewer::HashCacheManager::UrlStatus)
#endif // HASHCACHEMANAGER_H
diff --git a/webengineviewer/src/checkphishingurl/localdatabasefile.cpp b/webengineviewer/src/checkphishingurl/localdatabasefile.cpp
index ca89dd48..46a6b1f5 100644
--- a/webengineviewer/src/checkphishingurl/localdatabasefile.cpp
+++ b/webengineviewer/src/checkphishingurl/localdatabasefile.cpp
@@ -1,219 +1,219 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "localdatabasefile.h"
#include "checkphishingurlutil.h"
#include "updatedatabaseinfo.h"
#include "webengineviewer_debug.h"
#include <QElapsedTimer>
#include <QFileInfo>
#include <QDateTime>
using namespace WebEngineViewer;
WEBENGINEVIEWER_EXPORT int webengineview_LocalDataBaseFile = 1000 * 60 * 60;
class WebEngineViewer::LocalDataBaseFilePrivate
{
public:
LocalDataBaseFilePrivate(const QString &filename, LocalDataBaseFile *qq)
: mFile(filename)
, q(qq)
{
}
bool load();
bool reload();
void close();
QElapsedTimer mLastCheck;
QFile mFile;
uchar *mData = nullptr;
QDateTime mMtime;
LocalDataBaseFile *q;
bool mValid = false;
};
bool LocalDataBaseFilePrivate::load()
{
if (!mFile.open(QIODevice::ReadOnly)) {
return false;
}
mData = mFile.map(0, mFile.size());
if (mData) {
const int major = q->getUint16(0);
const int minor = q->getUint16(2);
//Check version in binary file. => version value == 1.0 for the moment see CheckPhishingUrlUtil
mValid = (major == WebEngineViewer::CheckPhishingUrlUtil::majorVersion()
&& minor == WebEngineViewer::CheckPhishingUrlUtil::minorVersion());
}
mMtime = QFileInfo(mFile).lastModified();
return mValid;
}
void LocalDataBaseFilePrivate::close()
{
mValid = false;
if (mFile.isOpen()) {
mFile.close();
}
mData = nullptr;
}
bool LocalDataBaseFilePrivate::reload()
{
qCDebug(WEBENGINEVIEWER_LOG) << "Reload File" << mFile.fileName();
close();
return load();
}
LocalDataBaseFile::LocalDataBaseFile(const QString &filename)
: d(new WebEngineViewer::LocalDataBaseFilePrivate(filename, this))
{
d->load();
}
LocalDataBaseFile::~LocalDataBaseFile()
{
delete d;
}
void LocalDataBaseFile::close()
{
d->close();
}
bool LocalDataBaseFile::isValid() const
{
return d->mValid;
}
quint16 LocalDataBaseFile::getUint16(int offset) const
{
return *reinterpret_cast<quint16 *>(d->mData + offset);
}
quint32 LocalDataBaseFile::getUint32(int offset) const
{
return *reinterpret_cast<quint32 *>(d->mData + offset);
}
quint64 LocalDataBaseFile::getUint64(int offset) const
{
return *reinterpret_cast<quint64 *>(d->mData + offset);
}
const char *LocalDataBaseFile::getCharStar(int offset) const
{
return reinterpret_cast<const char *>(d->mData + offset);
}
bool LocalDataBaseFile::reload()
{
return d->reload();
}
QByteArray LocalDataBaseFile::searchHash(const QByteArray &hashToSearch)
{
/* database file has:
* - one getUint16 => major
* - one getUint16 => minor
* - one getUint64 => number of item
* => 12
*/
int posListOffset = 12;
const int numHash = getUint64(4);
int begin = 0;
int end = numHash - 1;
QByteArray previousValue;
if (end > 0) {
QByteArray currentValue;
do {
previousValue = currentValue;
const int medium = (begin + end) / 2;
const int off = posListOffset + 8 * medium;
const int hashOffset = getUint64(off);
const char *hashCharStar = getCharStar(hashOffset);
const int cmp = qstrcmp(hashCharStar, hashToSearch.constData());
currentValue = QByteArray(hashCharStar);
qCWarning(WEBENGINEVIEWER_LOG) << "search " << hashToSearch.toBase64() << " begin " << begin << " end " << end << " hashCharStar" << currentValue.toBase64();
if (end == begin) {
return currentValue;
}
if (cmp < 0) {
begin = medium + 1;
} else if (cmp > 0) {
end = medium - 1;
} else {
return currentValue;
}
} while (begin <= end);
}
return previousValue;
}
bool LocalDataBaseFile::shouldCheck() const
{
// 1 hours
if (d->mLastCheck.isValid() && d->mLastCheck.elapsed() < webengineview_LocalDataBaseFile) {
return false;
}
d->mLastCheck.start();
return true;
}
bool LocalDataBaseFile::checkFileChanged()
{
bool somethingChanged = false;
QFileInfo fileInfo(d->mFile);
if (!fileInfo.exists() || fileInfo.lastModified() > d->mMtime) {
// But the user could use rm -rf :-)
reload(); // will mark itself as invalid on failure
somethingChanged = true;
}
if (somethingChanged) {
return isValid();
}
return somethingChanged;
}
QVector<WebEngineViewer::Addition> LocalDataBaseFile::extractAllInfo() const
{
QVector<WebEngineViewer::Addition> lst;
quint64 numberOfElement = getUint64(4);
lst.reserve(numberOfElement);
int index = 12; // quint16 major + quint16 minor + quint64 number of element
for (quint64 i = 0; i < numberOfElement; ++i) {
const quint64 value = getUint64(index);
Addition tmp;
tmp.hashString = QByteArray(getCharStar(value));
tmp.prefixSize = tmp.hashString.size();
tmp.compressionType = WebEngineViewer::UpdateDataBaseInfo::RawCompression;
index += 8; //next index based on quint64
lst.append(tmp);
}
return lst;
}
bool LocalDataBaseFile::fileExists() const
{
QFileInfo fileInfo(d->mFile);
return fileInfo.exists();
}
diff --git a/webengineviewer/src/checkphishingurl/localdatabasefile.h b/webengineviewer/src/checkphishingurl/localdatabasefile.h
index 3a8c9dfa..a7a308f2 100644
--- a/webengineviewer/src/checkphishingurl/localdatabasefile.h
+++ b/webengineviewer/src/checkphishingurl/localdatabasefile.h
@@ -1,67 +1,67 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOCALDATABASEFILE_H
#define LOCALDATABASEFILE_H
#include "webengineviewer_private_export.h"
#include <QString>
namespace WebEngineViewer {
class LocalDataBaseFilePrivate;
struct Addition;
class WEBENGINEVIEWER_TESTS_EXPORT LocalDataBaseFile
{
public:
/*
* binary file:
* index 0 => quint16 => major version
* index 2 => quint16 => minor version
* index 4 => quint64 => number of element
*
* After : index of item in binary file
*
* value
*/
explicit LocalDataBaseFile(const QString &filename);
~LocalDataBaseFile();
void close();
bool fileExists() const;
bool reload();
bool isValid() const;
quint16 getUint16(int offset) const;
quint32 getUint32(int offset) const;
quint64 getUint64(int offset) const;
const char *getCharStar(int offset) const;
QByteArray searchHash(const QByteArray &hashToSearch);
bool shouldCheck() const;
bool checkFileChanged();
QVector<WebEngineViewer::Addition> extractAllInfo() const;
private:
LocalDataBaseFilePrivate *const d;
};
}
#endif // LOCALDATABASEFILE_H
diff --git a/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp b/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp
index 0ec0491c..7fb4bdbb 100644
--- a/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp
+++ b/webengineviewer/src/checkphishingurl/localdatabasemanager.cpp
@@ -1,101 +1,101 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "localdatabasemanager.h"
#include "localdatabasemanager_p.h"
#include "webengineviewer_debug.h"
#include "createphishingurldatabasejob.h"
#include "createdatabasefilejob.h"
#include "checkphishingurlutil.h"
#include "urlhashing.h"
#include "backoffmodemanager.h"
#include <KSharedConfig>
#include <QCryptographicHash>
using namespace WebEngineViewer;
Q_GLOBAL_STATIC(LocalDataBaseManagerPrivate, s_localDataBaseManager)
LocalDataBaseManager::LocalDataBaseManager(LocalDataBaseManagerPrivate *impl, QObject *parent)
: QObject(parent)
, d(impl)
{
qRegisterMetaType<WebEngineViewer::UpdateDataBaseInfo>();
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::DataBaseDownloadResult>();
qRegisterMetaType<WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType>();
}
LocalDataBaseManager::LocalDataBaseManager(QObject *parent)
: LocalDataBaseManager(s_localDataBaseManager, parent)
{
}
LocalDataBaseManager::~LocalDataBaseManager()
{
}
void LocalDataBaseManager::initialize()
{
d->initialize();
}
void LocalDataBaseManager::checkUrl(const QUrl &url)
{
if (d->mDataBaseOk) {
//TODO fixme short hash! we don't need to use it.
WebEngineViewer::UrlHashing urlHashing(url);
QHash<QByteArray, QByteArray> hashList = urlHashing.hashList();
QHash<QByteArray, QByteArray> conflictHashs;
QHashIterator<QByteArray, QByteArray> i(hashList);
while (i.hasNext()) {
i.next();
const QByteArray ba = i.value();
QByteArray result = d->mFile.searchHash(ba);
if (ba.contains(result)) {
conflictHashs.insert(i.key().toBase64(), ba.toBase64());
}
}
if (conflictHashs.isEmpty()) {
Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Ok);
} else {
qCWarning(WEBENGINEVIEWER_LOG) << " We need to Check Server Database";
if (d->mNewClientState.isEmpty()) {
qCWarning(WEBENGINEVIEWER_LOG) << "Database client state is unknown";
Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Unknown);
} else {
WebEngineViewer::SearchFullHashJob *job = new WebEngineViewer::SearchFullHashJob(this);
job->setDatabaseState(QStringList() << d->mNewClientState);
job->setSearchHashs(conflictHashs);
job->setSearchFullHashForUrl(url);
connect(job, &SearchFullHashJob::result, this, [this](CheckPhishingUrlUtil::UrlStatus status, const QUrl &url) {
Q_EMIT checkUrlFinished(url, status);
});
job->start();
}
}
} else {
qCWarning(WEBENGINEVIEWER_LOG) << "Database not ok";
Q_EMIT checkUrlFinished(url, WebEngineViewer::CheckPhishingUrlUtil::Unknown);
}
if (d->mFile.checkFileChanged()) {
d->mFile.reload();
}
}
diff --git a/webengineviewer/src/checkphishingurl/localdatabasemanager.h b/webengineviewer/src/checkphishingurl/localdatabasemanager.h
index 2ae26ae8..fa2088b1 100644
--- a/webengineviewer/src/checkphishingurl/localdatabasemanager.h
+++ b/webengineviewer/src/checkphishingurl/localdatabasemanager.h
@@ -1,52 +1,52 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOCALDATABASEMANAGER_H
#define LOCALDATABASEMANAGER_H
#include <QObject>
#include "webengineviewer_export.h"
#include "createphishingurldatabasejob.h"
#include "searchfullhashjob.h"
#include <QUrl>
namespace WebEngineViewer {
class WebEngineView;
class LocalDataBaseManagerPrivate;
class WEBENGINEVIEWER_EXPORT LocalDataBaseManager : public QObject
{
Q_OBJECT
public:
explicit LocalDataBaseManager(QObject *parent = nullptr);
~LocalDataBaseManager();
void checkUrl(const QUrl &url);
void initialize();
Q_SIGNALS:
void checkUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status);
protected:
explicit LocalDataBaseManager(LocalDataBaseManagerPrivate *impl, QObject *parent = nullptr);
LocalDataBaseManagerPrivate *const d;
};
}
#endif // LOCALDATABASEMANAGER_H
diff --git a/webengineviewer/src/checkphishingurl/localdatabasemanager_p.h b/webengineviewer/src/checkphishingurl/localdatabasemanager_p.h
index 7ddf3a24..8ff98ffb 100644
--- a/webengineviewer/src/checkphishingurl/localdatabasemanager_p.h
+++ b/webengineviewer/src/checkphishingurl/localdatabasemanager_p.h
@@ -1,151 +1,151 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef LOCALDATABASEMANAGER_P_H
#define LOCALDATABASEMANAGER_P_H
#include "localdatabasefile.h"
#include "webengineviewer_debug.h"
#include "downloadlocaldatabasethread.h"
#include <KConfigGroup>
#include <KConfig>
#include <QDir>
#include <QStandardPaths>
#include <QPointer>
namespace {
inline QString localDataBasePath()
{
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/phishingurl/");
}
inline QString databaseFullPath()
{
return localDataBasePath() + QLatin1Char('/') + WebEngineViewer::CheckPhishingUrlUtil::databaseFileName();
}
}
namespace WebEngineViewer {
class LocalDataBaseManagerPrivate
{
public:
LocalDataBaseManagerPrivate()
: mFile(databaseFullPath())
, mSecondToStartRefreshing(0)
, mDataBaseOk(false)
, mDownloadProgress(false)
{
QDir().mkpath(localDataBasePath());
readConfig();
}
virtual ~LocalDataBaseManagerPrivate()
{
if (downloadLocalDatabaseThread) {
downloadLocalDatabaseThread->quit();
downloadLocalDatabaseThread->wait();
delete downloadLocalDatabaseThread;
}
saveConfig();
}
void initialize()
{
if (mDownloadProgress) {
return;
}
if (!mDataBaseOk) {
qCDebug(WEBENGINEVIEWER_LOG) << "Start to create database";
if (!QFileInfo::exists(databaseFullPath())) {
downloadDataBase(QString());
} else {
const uint now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
//qDebug() << " now "<< now << " d->mSecondToStartRefreshing "<<d->mSecondToStartRefreshing << " now > d->mSecondToStartRefreshing" << (now > d->mSecondToStartRefreshing);
if ((mSecondToStartRefreshing != 0) && (mSecondToStartRefreshing > now)) {
qCDebug(WEBENGINEVIEWER_LOG) << " It's not necessary to check database now";
mDataBaseOk = true;
} else {
//Perhaps don't download for each start of kmail
downloadDataBase(mNewClientState);
}
}
}
}
virtual void downloadDataBase(const QString &clientState)
{
mDownloadProgress = true;
downloadLocalDatabaseThread = new WebEngineViewer::DownloadLocalDatabaseThread;
downloadLocalDatabaseThread->setDatabaseFullPath(databaseFullPath());
downloadLocalDatabaseThread->setDataBaseState(clientState);
QObject::connect(downloadLocalDatabaseThread.data(), &DownloadLocalDatabaseThread::createDataBaseFailed,
[this]() {
mDataBaseOk = false;
mDownloadProgress = false;
});
QObject::connect(downloadLocalDatabaseThread.data(), &DownloadLocalDatabaseThread::createDataBaseFinished,
[this](bool success, const QString &newClientState, const QString &minWaitDurationStr) {
mDataBaseOk = success;
mDownloadProgress = false;
mNewClientState = success ? newClientState : QString();
mMinimumWaitDuration = minWaitDurationStr;
saveConfig();
//if !success => redownload full!
if (!success) {
qCWarning(WEBENGINEVIEWER_LOG) << "We need to redownload full database";
downloadDataBase(QString()); // download full database
}
});
QObject::connect(downloadLocalDatabaseThread.data(), &DownloadLocalDatabaseThread::finished,
downloadLocalDatabaseThread.data(), &DownloadLocalDatabaseThread::deleteLater);
downloadLocalDatabaseThread->start();
}
void readConfig()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("General"));
mNewClientState = grp.readEntry(QStringLiteral("DataBaseState"));
mMinimumWaitDuration = grp.readEntry(QStringLiteral("RefreshDataBase"));
if (!mMinimumWaitDuration.isEmpty()) {
mSecondToStartRefreshing = WebEngineViewer::CheckPhishingUrlUtil::refreshingCacheAfterThisTime(WebEngineViewer::CheckPhishingUrlUtil::convertToSecond(mMinimumWaitDuration));
}
}
void saveConfig()
{
KConfig phishingurlKConfig(WebEngineViewer::CheckPhishingUrlUtil::configFileName());
KConfigGroup grp = phishingurlKConfig.group(QStringLiteral("General"));
grp.writeEntry(QStringLiteral("DataBaseState"), mNewClientState);
grp.writeEntry(QStringLiteral("RefreshDataBase"), mMinimumWaitDuration);
}
LocalDataBaseFile mFile;
QString mNewClientState;
QString mMinimumWaitDuration;
uint mSecondToStartRefreshing;
bool mDataBaseOk;
bool mDownloadProgress;
QPointer<WebEngineViewer::DownloadLocalDatabaseThread> downloadLocalDatabaseThread;
};
}
#endif
diff --git a/webengineviewer/src/checkphishingurl/riceencodingdecoder.cpp b/webengineviewer/src/checkphishingurl/riceencodingdecoder.cpp
index 1728c7a7..a0122819 100644
--- a/webengineviewer/src/checkphishingurl/riceencodingdecoder.cpp
+++ b/webengineviewer/src/checkphishingurl/riceencodingdecoder.cpp
@@ -1,242 +1,242 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@kde.org>
Code based in v4_rice.cc
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "riceencodingdecoder.h"
#include "webengineviewer_debug.h"
#include <netinet/in.h>
namespace {
const int kBitsPerByte = 8;
const unsigned int kMaxBitIndex = kBitsPerByte * sizeof(uint32_t);
}
using namespace WebEngineViewer;
RiceEncodingDecoder::RiceEncodingDecoder()
{
}
RiceEncodingDecoder::~RiceEncodingDecoder()
{
}
QList<quint32> RiceEncodingDecoder::decodeRiceIndiceDelta(const RiceDeltaEncoding &riceDeltaEncoding)
{
bool ok;
QList<quint32> list;
if (riceDeltaEncoding.firstValue.isEmpty()) {
return list;
}
quint64 firstValue = riceDeltaEncoding.firstValue.toInt(&ok);
if (!ok) {
qCWarning(WEBENGINEVIEWER_LOG) << "First value is not a int value " << riceDeltaEncoding.firstValue;
return QList<quint32>();
}
list.reserve(riceDeltaEncoding.numberEntries + 1);
list << firstValue;
if (riceDeltaEncoding.numberEntries == 0) {
return list;
}
RiceDecoder decoder(riceDeltaEncoding.riceParameter, riceDeltaEncoding.numberEntries, riceDeltaEncoding.encodingData);
int lastValue(firstValue);
while (decoder.hasOtherEntries()) {
quint32 offset;
bool result = decoder.nextValue(&offset);
if (!result) {
return QList<quint32>();
}
lastValue += offset;
#if 0
if (!last_value.IsValid()) {
return false;
}
#endif
list << lastValue;
}
return list;
}
QList<quint32> RiceEncodingDecoder::decodeRiceHashesDelta(const RiceDeltaEncoding &riceDeltaEncoding)
{
QList<quint32> list;
bool ok = false;
quint64 firstValue = riceDeltaEncoding.firstValue.toInt(&ok);
if (!ok) {
qCWarning(WEBENGINEVIEWER_LOG) << "First value is not a int value " << riceDeltaEncoding.firstValue;
return list;
}
list.reserve(riceDeltaEncoding.numberEntries + 1);
RiceDecoder decoder(riceDeltaEncoding.riceParameter, riceDeltaEncoding.numberEntries, riceDeltaEncoding.encodingData);
int lastValue(firstValue);
list << htonl(lastValue);
while (decoder.hasOtherEntries()) {
quint32 offset;
bool result = decoder.nextValue(&offset);
if (!result) {
return QList<quint32>();
}
lastValue += offset;
#if 0
if (!last_value) {
return false;
}
#endif
// This flipping is done so that the decoded uint32 is interpreted
- // correcly as a string of 4 bytes.
+ // correctly as a string of 4 bytes.
list << htonl(lastValue);
}
// Flipping the bytes, as done above, destroys the sort order. Sort the
// values back.
std::sort(list.begin(), list.end());
// This flipping is done so that when the vector is interpreted as a string,
// the bytes are in the correct order.
QList<quint32> newList;
newList.reserve(list.count());
const int listCount(list.count());
for (int i = 0; i < listCount; ++i) {
newList << ntohl(list.at(i));
}
return newList;
}
RiceDecoder::RiceDecoder(int riceParameter, int numberEntries, const QByteArray &encodingData)
: mEncodingData(encodingData)
, mRiceParameter(riceParameter)
, mNumberEntries(numberEntries)
, mCurrentWord(0)
{
mDataByteIndex = 0;
mCurrentWordBitIndex = kMaxBitIndex;
}
RiceDecoder::~RiceDecoder()
{
}
bool RiceDecoder::hasOtherEntries() const
{
return mNumberEntries > 0;
}
bool RiceDecoder::nextValue(uint32_t *value)
{
if (!hasOtherEntries()) {
return false;
}
bool result;
uint32_t q = 0;
uint32_t bit;
do {
result = nextBits(1, &bit);
if (!result) {
return false;
}
q += bit;
} while (bit);
uint32_t r = 0;
result = nextBits(mRiceParameter, &r);
if (!result) {
return false;
}
*value = (q << mRiceParameter) + r;
mNumberEntries--;
return true;
}
bool RiceDecoder::nextBits(unsigned int numRequestedBits, uint32_t *x)
{
if (numRequestedBits > kMaxBitIndex) {
return false;
}
if (mCurrentWordBitIndex == kMaxBitIndex) {
bool result = nextWord(&mCurrentWord);
if (!result) {
return false;
}
}
unsigned int num_bits_left_in_current_word = kMaxBitIndex - mCurrentWordBitIndex;
if (num_bits_left_in_current_word >= numRequestedBits) {
// All the bits that we need are in |mCurrentWord|.
*x = bitsFromCurrentWord(numRequestedBits);
} else {
// |mCurrentWord| contains fewer bits than we need so read the remaining
// bits from |mCurrentWord| into |lower|, and then call nextBits on the
// remaining number of bits, which will read in a new word into
// |mCurrentWord|.
uint32_t lower = bitsFromCurrentWord(num_bits_left_in_current_word);
unsigned int num_bits_from_next_word
= numRequestedBits - num_bits_left_in_current_word;
uint32_t upper;
bool result = nextBits(num_bits_from_next_word, &upper);
if (!result) {
return false;
}
*x = (upper << num_bits_left_in_current_word) | lower;
}
return true;
}
bool RiceDecoder::nextWord(uint32_t *word)
{
if (mDataByteIndex >= mEncodingData.size()) {
return false;
}
const size_t mask = 0xFF;
*word = (mEncodingData[mDataByteIndex] & mask);
mDataByteIndex++;
mCurrentWordBitIndex = 0;
if (mDataByteIndex < mEncodingData.size()) {
*word |= ((mEncodingData[mDataByteIndex] & mask) << 8);
mDataByteIndex++;
if (mDataByteIndex < mEncodingData.size()) {
*word |= ((mEncodingData[mDataByteIndex] & mask) << 16);
mDataByteIndex++;
if (mDataByteIndex < mEncodingData.size()) {
*word |= ((mEncodingData[mDataByteIndex] & mask) << 24);
mDataByteIndex++;
}
}
}
return true;
}
uint32_t RiceDecoder::bitsFromCurrentWord(unsigned int numRequestedBits)
{
uint32_t mask = 0xFFFFFFFF >> (kMaxBitIndex - numRequestedBits);
uint32_t x = mCurrentWord & mask;
mCurrentWord = mCurrentWord >> numRequestedBits;
mCurrentWordBitIndex += numRequestedBits;
return x;
}
diff --git a/webengineviewer/src/checkphishingurl/riceencodingdecoder.h b/webengineviewer/src/checkphishingurl/riceencodingdecoder.h
index 78ce00a8..8863a3b1 100644
--- a/webengineviewer/src/checkphishingurl/riceencodingdecoder.h
+++ b/webengineviewer/src/checkphishingurl/riceencodingdecoder.h
@@ -1,68 +1,68 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef RICEENCODINGDECODER_H
#define RICEENCODINGDECODER_H
#include "webengineviewer_private_export.h"
#include "updatedatabaseinfo.h"
namespace WebEngineViewer {
//https://developers.google.com/safe-browsing/v4/compression
class RiceDecoder
{
public:
RiceDecoder(int riceParameter, int numberEntries, const QByteArray &encodingData);
~RiceDecoder();
bool hasOtherEntries() const;
bool nextValue(uint32_t *value);
bool nextBits(unsigned int num_requested_bits, uint32_t *x);
uint32_t bitsFromCurrentWord(unsigned int num_requested_bits);
bool nextWord(uint32_t *word);
private:
QByteArray mEncodingData;
int mRiceParameter;
int mNumberEntries;
// Represents how many total bytes have we read from |data_| into
// |mCurrentWord|.
int mDataByteIndex;
// Represents the number of bits that we have read from |mCurrentWord|. When
// this becomes 32, which is the size of |mCurrentWord|, a new
// |mCurrentWord| needs to be read from |data_|.
unsigned int mCurrentWordBitIndex;
// The 32-bit value read from |data_|. All bit reading operations operate on
// |mCurrentWord|.
uint32_t mCurrentWord;
};
class WEBENGINEVIEWER_TESTS_EXPORT RiceEncodingDecoder
{
public:
RiceEncodingDecoder();
~RiceEncodingDecoder();
static QList<quint32> decodeRiceIndiceDelta(const WebEngineViewer::RiceDeltaEncoding &riceDeltaEncoding);
static QList<quint32> decodeRiceHashesDelta(const WebEngineViewer::RiceDeltaEncoding &riceDeltaEncoding);
};
}
#endif // RICEENCODINGDECODER_H
diff --git a/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp b/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp
index d04d66c1..fca6b60e 100644
--- a/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp
+++ b/webengineviewer/src/checkphishingurl/searchfullhashjob.cpp
@@ -1,290 +1,290 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "searchfullhashjob.h"
#include "checkphishingurlutil.h"
#include <QNetworkAccessManager>
#include <QNetworkConfigurationManager>
#include <PimCommon/NetworkManager>
#include <QJsonDocument>
#include <QUrlQuery>
#include <webengineviewer_debug.h>
using namespace WebEngineViewer;
WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob = true;
class WebEngineViewer::SearchFullHashJobPrivate
{
public:
SearchFullHashJobPrivate()
{
}
bool foundExactHash(const QList<QByteArray> &listLongHash);
QHash<QByteArray, QByteArray> mHashs;
QUrl mUrl;
QStringList mDatabaseHashes;
QNetworkAccessManager *mNetworkAccessManager = nullptr;
};
SearchFullHashJob::SearchFullHashJob(QObject *parent)
: QObject(parent)
, d(new SearchFullHashJobPrivate)
{
d->mNetworkAccessManager = new QNetworkAccessManager(this);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &SearchFullHashJob::slotCheckUrlFinished);
connect(d->mNetworkAccessManager, &QNetworkAccessManager::sslErrors, this, &SearchFullHashJob::slotSslErrors);
}
SearchFullHashJob::~SearchFullHashJob()
{
delete d;
}
void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)
{
qCDebug(WEBENGINEVIEWER_LOG) << " void SearchFullHashJob::slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error)" << error.count();
reply->ignoreSslErrors(error);
}
void SearchFullHashJob::parse(const QByteArray &replyStr)
{
/*
{
"matches": [{
"threatType": "MALWARE",
"platformType": "WINDOWS",
"threatEntryType": "URL",
"threat": {
"hash": "WwuJdQx48jP-4lxr4y2Sj82AWoxUVcIRDSk1PC9Rf-4="
},
"threatEntryMetadata": {
"entries": [{
"key": "bWFsd2FyZV90aHJlYXRfdHlwZQ==", // base64-encoded "malware_threat_type"
"value": "TEFORElORw==" // base64-encoded "LANDING"
}]
},
"cacheDuration": "300.000s"
}, {
"threatType": "SOCIAL_ENGINEERING",
"platformType": "WINDOWS",
"threatEntryType": "URL",
"threat": {
"hash": "771MOrRPMn6xPKlCrXx_CrR-wmCk0LgFFoSgGy7zUiA="
},
"threatEntryMetadata": {
"entries": []
},
"cacheDuration": "300.000s"
}],
"minimumWaitDuration": "300.000s",
"negativeCacheDuration": "300.000s"
}
*/
QJsonDocument document = QJsonDocument::fromJson(replyStr);
if (document.isNull()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
} else {
const QVariantMap answer = document.toVariant().toMap();
if (answer.isEmpty()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Ok, d->mUrl);
return;
} else {
const QVariantList info = answer.value(QStringLiteral("matches")).toList();
//TODO
//const QString minimumWaitDuration = answer.value(QStringLiteral("minimumWaitDuration")).toString();
//const QString negativeCacheDuration = answer.value(QStringLiteral("negativeCacheDuration")).toString();
//Implement multi match ?
if (info.count() == 1) {
const QVariantMap map = info.at(0).toMap();
const QString threatTypeStr = map[QStringLiteral("threatType")].toString();
//const QString cacheDuration = map[QStringLiteral("cacheDuration")].toString();
if (threatTypeStr == QStringLiteral("MALWARE")) {
const QVariantMap urlMap = map[QStringLiteral("threat")].toMap();
QList<QByteArray> hashList;
QMap<QString, QVariant>::const_iterator urlMapIt = urlMap.cbegin();
const QMap<QString, QVariant>::const_iterator urlMapItEnd = urlMap.cend();
for (; urlMapIt != urlMapItEnd; ++urlMapIt) {
const QByteArray hashStr = urlMapIt.value().toByteArray();
hashList << hashStr;
}
if (d->foundExactHash(hashList)) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::MalWare, d->mUrl);
} else {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
}
const QVariantMap threatEntryMetadataMap = map[QStringLiteral("threatEntryMetadata")].toMap();
if (!threatEntryMetadataMap.isEmpty()) {
//TODO
}
} else {
qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse threatTypeStr : " << threatTypeStr;
}
} else {
qCWarning(WEBENGINEVIEWER_LOG) << " SearchFullHashJob::parse matches multi element : " << info.count();
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::Unknown, d->mUrl);
}
}
}
deleteLater();
}
bool SearchFullHashJobPrivate::foundExactHash(const QList<QByteArray> &listLongHash)
{
const QList<QByteArray> lstLongHash = mHashs.keys();
for (const QByteArray &ba : lstLongHash) {
if (listLongHash.contains(ba)) {
return true;
}
}
return false;
}
void SearchFullHashJob::slotCheckUrlFinished(QNetworkReply *reply)
{
parse(reply->readAll());
reply->deleteLater();
}
void SearchFullHashJob::setSearchHashs(const QHash<QByteArray, QByteArray> &hash)
{
d->mHashs = hash;
}
QByteArray SearchFullHashJob::jsonRequest() const
{
/*
{
"client": {
"clientId": "yourcompanyname",
"clientVersion": "1.5.2"
},
"clientStates": [
"ChAIARABGAEiAzAwMSiAEDABEAE=",
"ChAIAhABGAEiAzAwMSiAEDABEOgH"
],
"threatInfo": {
"threatTypes": ["MALWARE", "SOCIAL_ENGINEERING"],
"platformTypes": ["WINDOWS"],
"threatEntryTypes": ["URL"],
"threatEntries": [
{"hash": "WwuJdQ=="},
{"hash": "771MOg=="},
{"hash": "5eOrwQ=="}
]
}
}
*/
QVariantMap clientMap;
QVariantMap map;
clientMap.insert(QStringLiteral("clientId"), QStringLiteral("KDE"));
clientMap.insert(QStringLiteral("clientVersion"), CheckPhishingUrlUtil::versionApps());
map.insert(QStringLiteral("client"), clientMap);
//clientStates We can support multi database.
QVariantList clientStatesList;
for (const QString &str : qAsConst(d->mDatabaseHashes)) {
if (!str.isEmpty()) {
clientStatesList.append(str);
}
}
map.insert(QStringLiteral("clientStates"), clientStatesList);
QVariantMap threatMap;
QVariantList platformList;
platformList.append(QLatin1String("WINDOWS"));
threatMap.insert(QStringLiteral("platformTypes"), platformList);
const QVariantList threatTypesList = { QStringLiteral("MALWARE") };
threatMap.insert(QStringLiteral("threatTypes"), threatTypesList);
const QVariantList threatEntryTypesList = { QStringLiteral("URL") };
threatMap.insert(QStringLiteral("threatEntryTypes"), threatEntryTypesList);
QVariantList threatEntriesList;
QVariantMap hashUrlMap;
QHashIterator<QByteArray, QByteArray> i(d->mHashs);
while (i.hasNext()) {
i.next();
hashUrlMap.insert(QStringLiteral("hash"), i.value());
}
threatEntriesList.append(hashUrlMap);
threatMap.insert(QStringLiteral("threatEntries"), threatEntriesList);
map.insert(QStringLiteral("threatInfo"), threatMap);
const QJsonDocument postData = QJsonDocument::fromVariant(map);
const QByteArray baPostData = postData.toJson(webengineview_useCompactJson_SearchFullHashJob ? QJsonDocument::Compact : QJsonDocument::Indented);
return baPostData;
}
void SearchFullHashJob::start()
{
if (!PimCommon::NetworkManager::self()->networkConfigureManager()->isOnline()) {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork, d->mUrl);
deleteLater();
} else if (canStart()) {
QUrlQuery query;
query.addQueryItem(QStringLiteral("key"), WebEngineViewer::CheckPhishingUrlUtil::apiKey());
QUrl safeUrl = QUrl(QStringLiteral("https://safebrowsing.googleapis.com/v4/fullHashes:find"));
safeUrl.setQuery(query);
QNetworkRequest request(safeUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
const QByteArray baPostData = jsonRequest();
//qCDebug(WEBENGINEVIEWER_LOG) << " postData.toJson()" << baPostData;
Q_EMIT debugJson(baPostData);
QNetworkReply *reply = d->mNetworkAccessManager->post(request, baPostData);
- connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SearchFullHashJob::slotError);
+ connect(reply, qOverload<QNetworkReply::NetworkError>(&QNetworkReply::error), this, &SearchFullHashJob::slotError);
} else {
Q_EMIT result(WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl, d->mUrl);
deleteLater();
}
}
void SearchFullHashJob::slotError(QNetworkReply::NetworkError error)
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
qCWarning(WEBENGINEVIEWER_LOG) << " error " << error << " error string : " << reply->errorString();
reply->deleteLater();
deleteLater();
}
bool SearchFullHashJob::canStart() const
{
return !d->mHashs.isEmpty() && !d->mDatabaseHashes.isEmpty() && !d->mUrl.isEmpty();
}
void SearchFullHashJob::setDatabaseState(const QStringList &hash)
{
d->mDatabaseHashes = hash;
}
void SearchFullHashJob::setSearchFullHashForUrl(const QUrl &url)
{
d->mUrl = url;
}
diff --git a/webengineviewer/src/checkphishingurl/searchfullhashjob.h b/webengineviewer/src/checkphishingurl/searchfullhashjob.h
index 8217335e..7a4e4360 100644
--- a/webengineviewer/src/checkphishingurl/searchfullhashjob.h
+++ b/webengineviewer/src/checkphishingurl/searchfullhashjob.h
@@ -1,62 +1,62 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef SEARCHFULLHASHJOB_H
#define SEARCHFULLHASHJOB_H
#include <QObject>
#include <QUrl>
#include <QNetworkReply>
#include "webengineviewer_export.h"
#include "checkphishingurlutil.h"
namespace WebEngineViewer {
class SearchFullHashJobPrivate;
/* https://developers.google.com/safe-browsing/v4/update-api */
class WEBENGINEVIEWER_EXPORT SearchFullHashJob : public QObject
{
Q_OBJECT
public:
explicit SearchFullHashJob(QObject *parent = nullptr);
~SearchFullHashJob();
void start();
bool canStart() const;
void setDatabaseState(const QStringList &hash);
void setSearchFullHashForUrl(const QUrl &url);
QByteArray jsonRequest() const;
void parse(const QByteArray &replyStr);
void setSearchHashs(const QHash<QByteArray, QByteArray> &hash);
Q_SIGNALS:
void result(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status, const QUrl &url);
void debugJson(const QByteArray &ba);
private Q_SLOTS:
void slotSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
void slotError(QNetworkReply::NetworkError error);
void slotCheckUrlFinished(QNetworkReply *reply);
private:
Q_DISABLE_COPY(SearchFullHashJob)
SearchFullHashJobPrivate *const d;
};
}
#endif // SEARCHFULLHASHJOB_H
diff --git a/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.cpp b/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.cpp
index 52033f9b..b014656e 100644
--- a/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.cpp
+++ b/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.cpp
@@ -1,116 +1,116 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "checkphishingurlgui.h"
#include <QApplication>
#include <QStandardPaths>
#include <QLineEdit>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QVBoxLayout>
extern WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson;
CheckPhishingUrlGui::CheckPhishingUrlGui(QWidget *parent)
: QWidget(parent)
{
webengineview_useCompactJson = false;
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *checkUrlLayout = new QHBoxLayout;
layout->addLayout(checkUrlLayout);
QLabel *lab = new QLabel(QStringLiteral("Url to Check:"), this);
checkUrlLayout->addWidget(lab);
mCheckUrlLineEdit = new QLineEdit(this);
checkUrlLayout->addWidget(mCheckUrlLineEdit);
QPushButton *button = new QPushButton(QStringLiteral("Check"), this);
checkUrlLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, &CheckPhishingUrlGui::slotCheckUrl);
connect(mCheckUrlLineEdit, &QLineEdit::returnPressed, this, &CheckPhishingUrlGui::slotCheckUrl);
mResult = new QPlainTextEdit(this);
mResult->setReadOnly(true);
layout->addWidget(mResult);
mJson = new QPlainTextEdit(this);
mJson->setReadOnly(true);
layout->addWidget(mJson);
}
CheckPhishingUrlGui::~CheckPhishingUrlGui()
{
}
void CheckPhishingUrlGui::slotCheckUrl()
{
const QString urlStr = mCheckUrlLineEdit->text().trimmed();
if (urlStr.isEmpty()) {
return;
}
mResult->clear();
WebEngineViewer::CheckPhishingUrlJob *job = new WebEngineViewer::CheckPhishingUrlJob(this);
connect(job, &WebEngineViewer::CheckPhishingUrlJob::result, this, &CheckPhishingUrlGui::slotGetResult);
connect(job, &WebEngineViewer::CheckPhishingUrlJob::debugJson, this, &CheckPhishingUrlGui::slotJSonDebug);
job->setUrl(QUrl::fromUserInput(urlStr));
job->start();
}
void CheckPhishingUrlGui::slotJSonDebug(const QByteArray &debug)
{
mJson->setPlainText(QString::fromLatin1(debug));
}
void CheckPhishingUrlGui::slotGetResult(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus result, const QUrl &url, uint verifyCacheAfterThisTime)
{
QString resultStr;
switch (result) {
case WebEngineViewer::CheckPhishingUrlUtil::Ok:
resultStr = QStringLiteral("Url ok");
break;
case WebEngineViewer::CheckPhishingUrlUtil::MalWare:
resultStr = QStringLiteral("Url MalWare");
break;
case WebEngineViewer::CheckPhishingUrlUtil::Unknown:
- resultStr = QStringLiteral("Url Unknow state");
+ resultStr = QStringLiteral("Url Unknown state");
break;
case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork:
resultStr = QStringLiteral("Broken Network");
break;
case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl:
resultStr = QStringLiteral("Invalid Url");
break;
}
const QString str = QStringLiteral("\nurl: %1, verifyCacheAfterThisTime: %2").arg(url.toDisplayString()).arg(verifyCacheAfterThisTime);
mResult->setPlainText(resultStr + str);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
CheckPhishingUrlGui *w = new CheckPhishingUrlGui;
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.h b/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.h
index d131f5f1..f75b9d98 100644
--- a/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.h
+++ b/webengineviewer/src/checkphishingurl/tests/checkphishingurlgui.h
@@ -1,43 +1,43 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CHECKPHISHINGURLGUI_H
#define CHECKPHISHINGURLGUI_H
#include <QWidget>
#include "../checkphishingurljob.h"
class QLineEdit;
class QPlainTextEdit;
class CheckPhishingUrlGui : public QWidget
{
Q_OBJECT
public:
explicit CheckPhishingUrlGui(QWidget *parent = nullptr);
~CheckPhishingUrlGui();
private Q_SLOTS:
void slotCheckUrl();
void slotGetResult(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus result, const QUrl &url, uint verifyCacheAfterThisTime);
void slotJSonDebug(const QByteArray &debug);
private:
QLineEdit *mCheckUrlLineEdit;
QPlainTextEdit *mJson;
QPlainTextEdit *mResult;
};
#endif // CHECKPHISHINGURLGUI_H
diff --git a/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.cpp b/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.cpp
index 1abab466..0994533f 100644
--- a/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.cpp
+++ b/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.cpp
@@ -1,150 +1,150 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "createphishingurldatabasegui.h"
#include <QApplication>
#include <QStandardPaths>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QInputDialog>
#include <QComboBox>
#include <QFileDialog>
extern WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_CreatePhishingUrlDataBaseJob;
CreatePhisingUrlDataBaseGui::CreatePhisingUrlDataBaseGui(QWidget *parent)
: QWidget(parent)
{
webengineview_useCompactJson_CreatePhishingUrlDataBaseJob = false;
QVBoxLayout *layout = new QVBoxLayout(this);
mCompressionType = new QComboBox(this);
mCompressionType->addItem(QStringLiteral("RAW"));
mCompressionType->addItem(QStringLiteral("RICE"));
layout->addWidget(mCompressionType);
mResult = new QPlainTextEdit(this);
mResult->setReadOnly(true);
layout->addWidget(mResult);
mJson = new QPlainTextEdit(this);
mJson->setReadOnly(true);
layout->addWidget(mJson);
QHBoxLayout *buttonLayout = new QHBoxLayout;
layout->addLayout(buttonLayout);
QPushButton *button = new QPushButton(QStringLiteral("DownLoad full database"), this);
connect(button, &QPushButton::clicked, this, &CreatePhisingUrlDataBaseGui::slotDownloadFullDatabase);
buttonLayout->addWidget(button);
QPushButton *button2 = new QPushButton(QStringLiteral("DownLoad partial database"), this);
connect(button2, &QPushButton::clicked, this, &CreatePhisingUrlDataBaseGui::slotDownloadPartialDatabase);
buttonLayout->addWidget(button2);
QPushButton *save = new QPushButton(QStringLiteral("Save result to disk"), this);
connect(save, &QPushButton::clicked, this, &CreatePhisingUrlDataBaseGui::slotSaveResultToDisk);
buttonLayout->addWidget(save);
}
CreatePhisingUrlDataBaseGui::~CreatePhisingUrlDataBaseGui()
{
}
void CreatePhisingUrlDataBaseGui::clear()
{
mJson->clear();
mResult->clear();
}
WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType CreatePhisingUrlDataBaseGui::compressionType()
{
WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType type = WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression;
if (mCompressionType->currentText() == QLatin1String("RICE")) {
type = WebEngineViewer::CreatePhishingUrlDataBaseJob::RiceCompression;
} else if (mCompressionType->currentText() == QLatin1String("RAW")) {
type = WebEngineViewer::CreatePhishingUrlDataBaseJob::RawCompression;
}
return type;
}
void CreatePhisingUrlDataBaseGui::slotDownloadPartialDatabase()
{
const QString newValue = QInputDialog::getText(this, QStringLiteral("Define database newClientState"), QStringLiteral("newClientState:"));
if (!newValue.isEmpty()) {
clear();
WebEngineViewer::CreatePhishingUrlDataBaseJob *job = new WebEngineViewer::CreatePhishingUrlDataBaseJob(this);
job->setContraintsCompressionType(compressionType());
job->setDataBaseDownloadNeeded(WebEngineViewer::CreatePhishingUrlDataBaseJob::UpdateDataBase);
job->setDataBaseState(newValue);
connect(job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::debugJsonResult, this, &CreatePhisingUrlDataBaseGui::slotResult);
connect(job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::debugJson, this, &CreatePhisingUrlDataBaseGui::slotDebugJSon);
job->start();
}
}
void CreatePhisingUrlDataBaseGui::slotDownloadFullDatabase()
{
clear();
WebEngineViewer::CreatePhishingUrlDataBaseJob *job = new WebEngineViewer::CreatePhishingUrlDataBaseJob(this);
job->setContraintsCompressionType(compressionType());
connect(job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::debugJsonResult, this, &CreatePhisingUrlDataBaseGui::slotResult);
connect(job, &WebEngineViewer::CreatePhishingUrlDataBaseJob::debugJson, this, &CreatePhisingUrlDataBaseGui::slotDebugJSon);
job->start();
}
void CreatePhisingUrlDataBaseGui::slotSaveResultToDisk()
{
if (!mResult->document()->isEmpty()) {
const QString filename = QFileDialog::getSaveFileName(this, QStringLiteral("save result to disk"));
QTextStream ds;
QFile file;
file.setFileName(filename);
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << " impossible to open file " << filename;
return;
}
ds.setDevice(&file);
ds << mResult->toPlainText();
file.close();
}
}
void CreatePhisingUrlDataBaseGui::slotDebugJSon(const QByteArray &data)
{
mJson->setPlainText(QString::fromLatin1(data));
}
void CreatePhisingUrlDataBaseGui::slotResult(const QByteArray &data)
{
mResult->setPlainText(QString::fromLatin1(data));
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
CreatePhisingUrlDataBaseGui *w = new CreatePhisingUrlDataBaseGui;
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.h b/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.h
index 07a27b1e..6772db2f 100644
--- a/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.h
+++ b/webengineviewer/src/checkphishingurl/tests/createphishingurldatabasegui.h
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef CREATEPHISHINGURLDATABASEGUI_H
#define CREATEPHISHINGURLDATABASEGUI_H
#include <QWidget>
#include "../createphishingurldatabasejob.h"
class QPlainTextEdit;
class QComboBox;
class CreatePhisingUrlDataBaseGui : public QWidget
{
Q_OBJECT
public:
explicit CreatePhisingUrlDataBaseGui(QWidget *parent = nullptr);
~CreatePhisingUrlDataBaseGui();
private Q_SLOTS:
void slotResult(const QByteArray &data);
void slotDownloadFullDatabase();
void slotDebugJSon(const QByteArray &data);
void slotDownloadPartialDatabase();
void slotSaveResultToDisk();
private:
WebEngineViewer::CreatePhishingUrlDataBaseJob::ContraintsCompressionType compressionType();
void clear();
QPlainTextEdit *mResult;
QPlainTextEdit *mJson;
QComboBox *mCompressionType;
};
#endif // CREATEPHISHINGURLDATABASEGUI_H
diff --git a/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.cpp b/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.cpp
index 338bf95c..cb57d5c6 100644
--- a/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.cpp
+++ b/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.cpp
@@ -1,63 +1,63 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "managelocaldatabasegui.h"
#include <QStandardPaths>
#include "../localdatabasemanager.h"
#include <QApplication>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QVBoxLayout>
ManageLocalDataBaseGui::ManageLocalDataBaseGui(QWidget *parent)
: QWidget(parent)
, mDbManager(new WebEngineViewer::LocalDataBaseManager(this))
{
QVBoxLayout *layout = new QVBoxLayout(this);
mResult = new QPlainTextEdit(this);
mResult->setReadOnly(true);
layout->addWidget(mResult);
QPushButton *button = new QPushButton(QStringLiteral("Create Database"), this);
connect(button, &QPushButton::clicked, this, &ManageLocalDataBaseGui::slotDownloadFullDatabase);
layout->addWidget(button);
}
ManageLocalDataBaseGui::~ManageLocalDataBaseGui()
{
}
void ManageLocalDataBaseGui::slotDownloadFullDatabase()
{
mDbManager->initialize();
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
ManageLocalDataBaseGui *w = new ManageLocalDataBaseGui;
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.h b/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.h
index 0271925c..4a72eac0 100644
--- a/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.h
+++ b/webengineviewer/src/checkphishingurl/tests/managelocaldatabasegui.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MANAGELOCALDATABASEGUI_H
#define MANAGELOCALDATABASEGUI_H
#include <QWidget>
class QPlainTextEdit;
namespace WebEngineViewer {
class LocalDataBaseManager;
}
class ManageLocalDataBaseGui : public QWidget
{
Q_OBJECT
public:
explicit ManageLocalDataBaseGui(QWidget *parent = nullptr);
~ManageLocalDataBaseGui();
private Q_SLOTS:
void slotDownloadFullDatabase();
private:
QPlainTextEdit *mResult;
WebEngineViewer::LocalDataBaseManager *mDbManager;
};
#endif // MANAGELOCALDATABASEGUI_H
diff --git a/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.cpp b/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.cpp
index 1ef999a2..8627ac20 100644
--- a/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.cpp
+++ b/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.cpp
@@ -1,134 +1,134 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "searchfullhashgui.h"
#include <QApplication>
#include <QStandardPaths>
#include <QLineEdit>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QVBoxLayout>
extern WEBENGINEVIEWER_EXPORT bool webengineview_useCompactJson_SearchFullHashJob;
SearchFullHashGui::SearchFullHashGui(QWidget *parent)
: QWidget(parent)
{
webengineview_useCompactJson_SearchFullHashJob = false;
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *checkHashLayout = new QHBoxLayout;
layout->addLayout(checkHashLayout);
QLabel *lab = new QLabel(QStringLiteral("Hash from Url to Check:"), this);
checkHashLayout->addWidget(lab);
mCheckHashLineEdit = new QLineEdit(this);
checkHashLayout->addWidget(mCheckHashLineEdit);
QHBoxLayout *databaseHashLayout = new QHBoxLayout;
layout->addLayout(databaseHashLayout);
lab = new QLabel(QStringLiteral("Database hash:"), this);
checkHashLayout->addWidget(lab);
mDataBaseHashLineEdit = new QLineEdit(this);
checkHashLayout->addWidget(mDataBaseHashLineEdit);
QPushButton *button = new QPushButton(QStringLiteral("Check"), this);
checkHashLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, &SearchFullHashGui::slotCheckUrl);
connect(mCheckHashLineEdit, &QLineEdit::returnPressed, this, &SearchFullHashGui::slotCheckUrl);
mResult = new QPlainTextEdit(this);
mResult->setReadOnly(true);
layout->addWidget(mResult);
mJson = new QPlainTextEdit(this);
mJson->setReadOnly(true);
layout->addWidget(mJson);
}
SearchFullHashGui::~SearchFullHashGui()
{
}
void SearchFullHashGui::slotCheckUrl()
{
const QString hashStr = mCheckHashLineEdit->text().trimmed();
if (hashStr.isEmpty()) {
return;
}
const QString databaseHashStr = mDataBaseHashLineEdit->text().trimmed();
if (databaseHashStr.isEmpty()) {
return;
}
mResult->clear();
WebEngineViewer::SearchFullHashJob *job = new WebEngineViewer::SearchFullHashJob(this);
connect(job, &WebEngineViewer::SearchFullHashJob::result, this, &SearchFullHashGui::slotGetResult);
connect(job, &WebEngineViewer::SearchFullHashJob::debugJson, this, &SearchFullHashGui::slotJSonDebug);
job->setDatabaseState(QStringList() << databaseHashStr);
QByteArray ba = hashStr.toLatin1();
QByteArray baShort = ba;
baShort.truncate(4);
QHash<QByteArray, QByteArray> lst;
lst.insert(ba, baShort);
job->setSearchHashs(lst);
job->start();
}
void SearchFullHashGui::slotJSonDebug(const QByteArray &debug)
{
mJson->setPlainText(QString::fromLatin1(debug));
}
void SearchFullHashGui::slotGetResult(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus result)
{
QString resultStr;
switch (result) {
case WebEngineViewer::CheckPhishingUrlUtil::Ok:
resultStr = QStringLiteral("Url ok");
break;
case WebEngineViewer::CheckPhishingUrlUtil::MalWare:
resultStr = QStringLiteral("Url MalWare");
break;
case WebEngineViewer::CheckPhishingUrlUtil::Unknown:
- resultStr = QStringLiteral("Url Unknow state");
+ resultStr = QStringLiteral("Url Unknown state");
break;
case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork:
resultStr = QStringLiteral("Broken Network");
break;
case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl:
resultStr = QStringLiteral("Invalid Url");
break;
}
mResult->setPlainText(resultStr);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
SearchFullHashGui *w = new SearchFullHashGui;
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.h b/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.h
index b1122269..95e1117c 100644
--- a/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.h
+++ b/webengineviewer/src/checkphishingurl/tests/searchfullhashgui.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef SEARCHFULLHASHGUI_H
#define SEARCHFULLHASHGUI_H
#include <QWidget>
#include "../searchfullhashjob.h"
class QLineEdit;
class QPlainTextEdit;
class SearchFullHashGui : public QWidget
{
Q_OBJECT
public:
explicit SearchFullHashGui(QWidget *parent = nullptr);
~SearchFullHashGui();
private Q_SLOTS:
void slotCheckUrl();
void slotGetResult(WebEngineViewer::CheckPhishingUrlUtil::UrlStatus result);
void slotJSonDebug(const QByteArray &debug);
private:
QLineEdit *mCheckHashLineEdit;
QLineEdit *mDataBaseHashLineEdit;
QPlainTextEdit *mJson;
QPlainTextEdit *mResult;
};
#endif // SEARCHFULLHASHGUI_H
diff --git a/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.cpp b/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.cpp
index 4593c4f9..ab3196c9 100644
--- a/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.cpp
+++ b/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.cpp
@@ -1,101 +1,101 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineviewwithsafebrowsingsupport.h"
#include "../localdatabasemanager.h"
#include <QApplication>
#include <QStandardPaths>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <webenginepage.h>
#include <webengineview.h>
WebEngineViewWithSafeBrowsingSupport::WebEngineViewWithSafeBrowsingSupport(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
QStandardPaths::setTestModeEnabled(true);
pageView = new WebEngineViewer::WebEngineView(this);
connect(pageView->phishingDatabase(), &WebEngineViewer::LocalDataBaseManager::checkUrlFinished,
this, &WebEngineViewWithSafeBrowsingSupport::slotCheckedUrlFinished);
layout->addWidget(pageView);
WebEngineViewer::WebEnginePage *mEnginePage = new WebEngineViewer::WebEnginePage(this);
pageView->setPage(mEnginePage);
//pageView->load(QUrl(QStringLiteral("http://www.kde.org")));
const QString urlPage = QLatin1String(CHECKPHISHINGURL_TEST_DATA_DIR) + QStringLiteral("/test-url.html");
qDebug() << " urlPage" << urlPage;
pageView->load(QUrl::fromLocalFile(urlPage));
connect(mEnginePage, &WebEngineViewer::WebEnginePage::urlClicked, this, &WebEngineViewWithSafeBrowsingSupport::slotUrlClicked);
mDebug = new QPlainTextEdit(this);
mDebug->setReadOnly(true);
layout->addWidget(mDebug);
}
WebEngineViewWithSafeBrowsingSupport::~WebEngineViewWithSafeBrowsingSupport()
{
}
void WebEngineViewWithSafeBrowsingSupport::slotUrlClicked(const QUrl &url)
{
qDebug() << " url clicked " << url;
pageView->phishingDatabase()->checkUrl(url);
}
void WebEngineViewWithSafeBrowsingSupport::slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status)
{
QString statusStr;
switch (status) {
case WebEngineViewer::CheckPhishingUrlUtil::Unknown:
statusStr = QStringLiteral("Unknown Status");
break;
case WebEngineViewer::CheckPhishingUrlUtil::Ok:
statusStr = QStringLiteral("Url Ok");
break;
case WebEngineViewer::CheckPhishingUrlUtil::MalWare:
statusStr = QStringLiteral("MalWare");
break;
case WebEngineViewer::CheckPhishingUrlUtil::InvalidUrl:
statusStr = QStringLiteral("Invalid Url");
break;
case WebEngineViewer::CheckPhishingUrlUtil::BrokenNetwork:
statusStr = QStringLiteral("Broken Network");
break;
}
qDebug() << " checked url: " << url << " result : " << statusStr;
mDebug->setPlainText(QStringLiteral("Url: %1 , Status %2").arg(url.toDisplayString(), statusStr));
if (status != WebEngineViewer::CheckPhishingUrlUtil::MalWare) {
pageView->load(url);
}
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QStandardPaths::setTestModeEnabled(true);
WebEngineViewWithSafeBrowsingSupport *w = new WebEngineViewWithSafeBrowsingSupport;
w->show();
app.exec();
delete w;
return 0;
}
diff --git a/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.h b/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.h
index f66d2774..04c1ae3a 100644
--- a/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.h
+++ b/webengineviewer/src/checkphishingurl/tests/webengineviewwithsafebrowsingsupport.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEVIEWWITHSAFEBROWSINGSUPPORT_H
#define WEBENGINEVIEWWITHSAFEBROWSINGSUPPORT_H
#include <QWidget>
#include "../searchfullhashjob.h"
#include <WebEngineViewer/LocalDataBaseManager>
class QPlainTextEdit;
namespace WebEngineViewer {
class WebEngineView;
}
class WebEngineViewWithSafeBrowsingSupport : public QWidget
{
Q_OBJECT
public:
explicit WebEngineViewWithSafeBrowsingSupport(QWidget *parent = nullptr);
~WebEngineViewWithSafeBrowsingSupport();
private Q_SLOTS:
void slotUrlClicked(const QUrl &url);
void slotCheckedUrlFinished(const QUrl &url, WebEngineViewer::CheckPhishingUrlUtil::UrlStatus status);
private:
QPlainTextEdit *mDebug = nullptr;
WebEngineViewer::WebEngineView *pageView = nullptr;
};
#endif // WEBENGINEVIEWWITHSAFEBROWSINGSUPPORT_H
diff --git a/webengineviewer/src/checkphishingurl/updatedatabaseinfo.cpp b/webengineviewer/src/checkphishingurl/updatedatabaseinfo.cpp
index 765262c0..acc328f8 100644
--- a/webengineviewer/src/checkphishingurl/updatedatabaseinfo.cpp
+++ b/webengineviewer/src/checkphishingurl/updatedatabaseinfo.cpp
@@ -1,182 +1,182 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "updatedatabaseinfo.h"
#include "webengineviewer_debug.h"
using namespace WebEngineViewer;
UpdateDataBaseInfo::UpdateDataBaseInfo()
: responseType(Unknown)
{
}
bool UpdateDataBaseInfo::isValid() const
{
return responseType != Unknown;
}
void UpdateDataBaseInfo::clear()
{
additionList.clear();
removalList.clear();
minimumWaitDuration.clear();
threatType.clear();
threatEntryType.clear();
responseType = UpdateDataBaseInfo::Unknown;
platformType.clear();
newClientState.clear();
sha256.clear();
}
bool UpdateDataBaseInfo::operator==(const UpdateDataBaseInfo &other) const
{
const bool val = (additionList == other.additionList)
&& (removalList == other.removalList)
&& (minimumWaitDuration == other.minimumWaitDuration)
&& (threatType == other.threatType)
&& (threatEntryType == other.threatEntryType)
&& (responseType == other.responseType)
&& (platformType == other.platformType)
&& (newClientState == other.newClientState)
&& (sha256 == other.sha256);
if (!val) {
qCWarning(WEBENGINEVIEWER_LOG) << " sha256 " << sha256 << " other.sha256 " << other.sha256;
qCWarning(WEBENGINEVIEWER_LOG) << " minimumWaitDuration " << minimumWaitDuration << " other.minimumWaitDuration " << other.minimumWaitDuration;
qCWarning(WEBENGINEVIEWER_LOG) << " threatType " << threatType << " other.threatType " << other.threatType;
qCWarning(WEBENGINEVIEWER_LOG) << " threatEntryType " << threatEntryType << " other.threatEntryType " << other.threatEntryType;
qCWarning(WEBENGINEVIEWER_LOG) << " responseType " << responseType << " other.responseType " << other.responseType;
qCWarning(WEBENGINEVIEWER_LOG) << " platformType " << platformType << " other.platformType " << other.platformType;
qCWarning(WEBENGINEVIEWER_LOG) << " newClientState " << newClientState << " other.newClientState " << other.newClientState;
qCWarning(WEBENGINEVIEWER_LOG) << " threatType " << threatType << " other.threatType " << other.threatType;
qCWarning(WEBENGINEVIEWER_LOG) << " removalList" << removalList.count() << " other.removalList " << other.removalList.count();
qCWarning(WEBENGINEVIEWER_LOG) << " additionList" << additionList.count() << " other.additionList " << other.additionList.count();
}
return val;
}
Removal::Removal()
: compressionType(UpdateDataBaseInfo::UnknownCompression)
{
}
bool Removal::operator==(const Removal &other) const
{
bool value = (indexes == other.indexes) && (compressionType == other.compressionType) && (riceDeltaEncoding == other.riceDeltaEncoding);
if (!value) {
qCWarning(WEBENGINEVIEWER_LOG) << " indexes " << indexes << " other.indexes " << other.indexes;
qCWarning(WEBENGINEVIEWER_LOG) << "compressionType " << compressionType << " other.compressionType " << other.compressionType;
}
return value;
}
bool Removal::isValid() const
{
bool valid = false;
switch (compressionType) {
case UpdateDataBaseInfo::UnknownCompression:
qCWarning(WEBENGINEVIEWER_LOG) << "Compression Type undefined";
valid = false;
break;
case UpdateDataBaseInfo::RiceCompression:
valid = riceDeltaEncoding.isValid();
break;
case UpdateDataBaseInfo::RawCompression:
valid = !indexes.isEmpty();
break;
}
return valid;
}
Addition::Addition()
: compressionType(UpdateDataBaseInfo::UnknownCompression)
, prefixSize(0)
{
}
bool Addition::isValid() const
{
bool valid = false;
switch (compressionType) {
case UpdateDataBaseInfo::UnknownCompression:
qCWarning(WEBENGINEVIEWER_LOG) << "Compression Type undefined";
valid = false;
break;
case UpdateDataBaseInfo::RiceCompression:
valid = riceDeltaEncoding.isValid();
break;
case UpdateDataBaseInfo::RawCompression:
const bool hasCorrectPrefixSize = (prefixSize >= 4) && (prefixSize <= 32);
if (!hasCorrectPrefixSize) {
qCWarning(WEBENGINEVIEWER_LOG) << "Prefix size is not correct";
valid = false;
} else if ((hashString.size() % static_cast<int>(prefixSize)) != 0) {
qDebug() << " hashString.size() " << hashString.size() << "prefixSize " << prefixSize;
qCWarning(WEBENGINEVIEWER_LOG) << "it's not a correct hash value";
valid = false;
} else {
valid = !hashString.isEmpty();
}
break;
}
return valid;
}
bool Addition::operator==(const Addition &other) const
{
bool value = (hashString == other.hashString)
&& (prefixSize == other.prefixSize)
&& (compressionType == other.compressionType)
&& (riceDeltaEncoding == other.riceDeltaEncoding);
if (!value) {
qCWarning(WEBENGINEVIEWER_LOG) << "hashString " << hashString << " other.hashString " << other.hashString;
qCWarning(WEBENGINEVIEWER_LOG) << "prefixSize " << prefixSize << " other.prefixSize " << other.prefixSize;
qCWarning(WEBENGINEVIEWER_LOG) << "compressionType " << compressionType << " other.compressionType " << other.compressionType;
}
return value;
}
bool Addition::lessThan(const Addition &s1, const Addition &s2)
{
return s1.hashString < s2.hashString;
}
RiceDeltaEncoding::RiceDeltaEncoding()
: riceParameter(0)
, numberEntries(0)
{
}
bool RiceDeltaEncoding::operator==(const RiceDeltaEncoding &other) const
{
return (firstValue == other.firstValue)
&& (encodingData == other.encodingData)
&& (riceParameter == other.riceParameter)
&& (numberEntries == other.numberEntries);
}
bool RiceDeltaEncoding::isValid() const
{
if (!firstValue.isEmpty()
&& !encodingData.isEmpty()
&& ((riceParameter >= 2 && riceParameter <= 28) || (riceParameter == 0 && numberEntries == 0))) {
return true;
}
return false;
}
diff --git a/webengineviewer/src/checkphishingurl/updatedatabaseinfo.h b/webengineviewer/src/checkphishingurl/updatedatabaseinfo.h
index 5f0a7a0a..8b7d0e03 100644
--- a/webengineviewer/src/checkphishingurl/updatedatabaseinfo.h
+++ b/webengineviewer/src/checkphishingurl/updatedatabaseinfo.h
@@ -1,97 +1,97 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef UPDATEDATABASEINFO_H
#define UPDATEDATABASEINFO_H
#include "webengineviewer_export.h"
#include <QList>
#include <QVector>
#include <QObject>
namespace WebEngineViewer {
struct Addition;
struct Removal;
struct WEBENGINEVIEWER_EXPORT UpdateDataBaseInfo
{
UpdateDataBaseInfo();
bool isValid() const;
enum ResponseType {
Unknown = 0,
FullUpdate = 1,
PartialUpdate = 2
};
enum CompressionType {
UnknownCompression = 0,
RiceCompression = 1,
RawCompression = 2
};
QVector<Addition> additionList;
QVector<Removal> removalList;
QString minimumWaitDuration;
QString threatType;
QString threatEntryType;
ResponseType responseType;
QString platformType;
QString newClientState;
QByteArray sha256;
void clear();
bool operator==(const UpdateDataBaseInfo &other) const;
};
struct WEBENGINEVIEWER_EXPORT RiceDeltaEncoding {
RiceDeltaEncoding();
bool operator==(const RiceDeltaEncoding &other) const;
bool isValid() const;
QByteArray firstValue;
QByteArray encodingData;
int riceParameter;
int numberEntries;
};
struct WEBENGINEVIEWER_EXPORT Addition {
Addition();
bool isValid() const;
bool operator==(const Addition &other) const;
static bool lessThan(const Addition &s1, const Addition &s2);
QByteArray hashString;
RiceDeltaEncoding riceDeltaEncoding;
UpdateDataBaseInfo::CompressionType compressionType;
int prefixSize;
};
struct WEBENGINEVIEWER_EXPORT Removal {
Removal();
bool operator==(const Removal &other) const;
bool isValid() const;
QList<quint32> indexes;
RiceDeltaEncoding riceDeltaEncoding;
UpdateDataBaseInfo::CompressionType compressionType;
};
}
Q_DECLARE_METATYPE(WebEngineViewer::UpdateDataBaseInfo)
Q_DECLARE_METATYPE(WebEngineViewer::UpdateDataBaseInfo::CompressionType)
Q_DECLARE_TYPEINFO(WebEngineViewer::Addition, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(WebEngineViewer::Removal, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(WebEngineViewer::RiceDeltaEncoding, Q_MOVABLE_TYPE);
#endif // UPDATEDATABASEINFO_H
diff --git a/webengineviewer/src/checkphishingurl/urlhashing.cpp b/webengineviewer/src/checkphishingurl/urlhashing.cpp
index adc3cc34..80ca8614 100644
--- a/webengineviewer/src/checkphishingurl/urlhashing.cpp
+++ b/webengineviewer/src/checkphishingurl/urlhashing.cpp
@@ -1,154 +1,154 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "urlhashing.h"
#include <QDebug>
#include <QCryptographicHash>
using namespace WebEngineViewer;
UrlHashing::UrlHashing(const QUrl &url)
: mUrl(url)
{
}
UrlHashing::~UrlHashing()
{
}
QString UrlHashing::canonicalizeUrl(QUrl url)
{
if (url.isEmpty()) {
return {};
}
QString path = url.path();
if (url.path().isEmpty()) {
url.setPath(QStringLiteral("/"));
} else {
// First, remove tab (0x09), CR (0x0d), and LF (0x0a) characters from the URL. Do not remove escape sequences for these characters (e.g. '%0a').
path.remove(QLatin1Char('\t'));
path.remove(QLatin1Char('\r'));
path.remove(QLatin1Char('\n'));
//In the URL, percent-escape all characters that are <= ASCII 32, >= 127, "#", or "%". The escapes should use uppercase hex characters.
//TODO
url.setPath(path);
}
// Remove all leading and trailing dots.
#if 0
QString hostname = url.host();
qDebug() << " hostname" << hostname;
while (!hostname.isEmpty() && hostname.at(0) == QLatin1Char('.')) {
hostname.remove(0, 1);
}
qDebug() << "111111 hostname" << hostname;
for (int i = hostname.length(); i >= 0; --i) {
if (hostname.at(i) == QLatin1Char('.')) {
hostname.remove(i);
} else {
break;
}
}
qDebug() << "2222222 hostname" << hostname;
mUrl.setHost(hostname);
#endif
QByteArray urlEncoded = url.toEncoded(QUrl::RemoveFragment | QUrl::NormalizePathSegments | QUrl::EncodeUnicode | QUrl::RemoveUserInfo | QUrl::RemovePort | QUrl::RemovePassword);
//qDebug() << "BEFORE urlEncoded" <<urlEncoded;
urlEncoded.replace(QByteArrayLiteral("%25"), QByteArrayLiteral("%"));
//qDebug() << "AFTER urlEncoded" <<urlEncoded;
return QString::fromLatin1(urlEncoded);
}
QStringList UrlHashing::generatePathsToCheck(const QString &str, const QString &query)
{
QStringList pathToCheck;
if (str.isEmpty()) {
return pathToCheck;
}
const int strLenght(str.length());
for (int i = 0; i < strLenght; ++i) {
//We check 5 element => 4 here and host if necessary
if (pathToCheck.count() == 4) {
break;
}
if (str.at(i) == QLatin1Char('/')) {
if (i == 0) {
pathToCheck << QStringLiteral("/");
} else {
pathToCheck << str.left(i + 1);
}
}
}
if (!pathToCheck.isEmpty() && pathToCheck.at(pathToCheck.count() - 1) != str) {
pathToCheck << str;
}
if (!query.isEmpty()) {
pathToCheck << str + QLatin1Char('?') + query;
}
return pathToCheck;
}
QStringList UrlHashing::generateHostsToCheck(const QString &str)
{
QStringList hostToCheck;
if (str.isEmpty()) {
return hostToCheck;
}
const int strLenght(str.length());
bool lastElement = true;
for (int i = (strLenght - 1); i > 0; --i) {
//We need to check just 5 element => 4 splits hosts + current host
if (hostToCheck.count() == 4) {
break;
}
if (str.at(i) == QLatin1Char('.')) {
if (lastElement) {
lastElement = false;
} else {
hostToCheck << str.right(strLenght - i - 1);
}
}
}
hostToCheck << str;
return hostToCheck;
}
QHash<QByteArray, QByteArray> UrlHashing::hashList() const
{
QHash<QByteArray, QByteArray> lst;
if (mUrl.isValid()) {
const QString result = WebEngineViewer::UrlHashing::canonicalizeUrl(mUrl);
const QUrl url(result);
const QStringList hosts = WebEngineViewer::UrlHashing::generateHostsToCheck(url.host());
const QStringList paths = WebEngineViewer::UrlHashing::generatePathsToCheck(url.path(), url.query());
for (const QString &host : hosts) {
for (const QString &path : paths) {
const QString str = host + path;
QByteArray ba = QCryptographicHash::hash(str.toLatin1(), QCryptographicHash::Sha256);
QByteArray baShort = ba;
baShort.truncate(4);
lst.insert(ba, baShort);
//qDebug() << " ba " << ba.toBase64();
}
}
}
return lst;
}
diff --git a/webengineviewer/src/checkphishingurl/urlhashing.h b/webengineviewer/src/checkphishingurl/urlhashing.h
index 87f9f83d..0c51acf4 100644
--- a/webengineviewer/src/checkphishingurl/urlhashing.h
+++ b/webengineviewer/src/checkphishingurl/urlhashing.h
@@ -1,46 +1,46 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef URLHASHING_H
#define URLHASHING_H
#include "webengineviewer_private_export.h"
#include <QUrl>
#include <QString>
namespace WebEngineViewer {
//https://developers.google.com/safe-browsing/v4/urls-hashing
class WEBENGINEVIEWER_TESTS_EXPORT UrlHashing
{
public:
explicit UrlHashing(const QUrl &url);
~UrlHashing();
static QString canonicalizeUrl(QUrl url);
static QStringList generatePathsToCheck(const QString &str, const QString &query);
static QStringList generateHostsToCheck(const QString &str);
/*long hash, short hash*/
QHash<QByteArray, QByteArray> hashList() const;
private:
QUrl mUrl;
};
}
#endif // URLHASHING_H
diff --git a/webengineviewer/src/data/jquery-ui.js b/webengineviewer/src/data/jquery-ui.js
deleted file mode 100644
index 046eeb7b..00000000
--- a/webengineviewer/src/data/jquery-ui.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/*!
- * jQuery UI 1.8.16
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI
- */
-(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.16",
-keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({propAttr:c.fn.prop||c.fn.attr,_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=
-this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,
-"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":
-"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,
-outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,
-"tabindex"),d=isNaN(b);return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&
-a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&
-c.ui.isOverAxis(b,e,i)}})}})(jQuery);
-;/*!
- * jQuery UI Widget 1.8.16
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Widget
- */
-(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)try{b(d).triggerHandler("remove")}catch(e){}k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){try{b(this).triggerHandler("remove")}catch(d){}});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=
-function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):
-d;if(e&&d.charAt(0)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=
-b.extend(true,{},this.options,this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+
-"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",
-c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
-;/*!
- * jQuery UI Mouse 1.8.16
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Mouse
- *
- * Depends:
- * jquery.ui.widget.js
- */
-(function(b){var d=false;b(document).mouseup(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
-this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"&&a.target.nodeName?b(a.target).closest(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=
-this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&&
-!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
-false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
-;/*
- * jQuery UI Sortable 1.8.16
- *
- * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * http://docs.jquery.com/UI/Sortables
- *
- * Depends:
- * jquery.ui.core.js
- * jquery.ui.mouse.js
- * jquery.ui.widget.js
- */
-(function(d){d.widget("ui.sortable",d.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:false,connectWith:false,containment:false,cursor:"auto",cursorAt:false,dropOnEmpty:true,forcePlaceholderSize:false,forceHelperSize:false,grid:false,handle:false,helper:"original",items:"> *",opacity:false,placeholder:false,revert:false,scroll:true,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1E3},_create:function(){var a=this.options;this.containerCache={};this.element.addClass("ui-sortable");
-this.refresh();this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):false;this.offset=this.element.offset();this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled").removeData("sortable").unbind(".sortable");this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData("sortable-item");return this},_setOption:function(a,b){if(a===
-"disabled"){this.options[a]=b;this.widget()[b?"addClass":"removeClass"]("ui-sortable-disabled")}else d.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(a,b){if(this.reverting)return false;if(this.options.disabled||this.options.type=="static")return false;this._refreshItems(a);var c=null,e=this;d(a.target).parents().each(function(){if(d.data(this,"sortable-item")==e){c=d(this);return false}});if(d.data(a.target,"sortable-item")==e)c=d(a.target);if(!c)return false;if(this.options.handle&&
-!b){var f=false;d(this.options.handle,c).find("*").andSelf().each(function(){if(this==a.target)f=true});if(!f)return false}this.currentItem=c;this._removeCurrentsFromItems();return true},_mouseStart:function(a,b,c){b=this.options;var e=this;this.currentContainer=this;this.refreshPositions();this.helper=this._createHelper(a);this._cacheHelperProportions();this._cacheMargins();this.scrollParent=this.helper.scrollParent();this.offset=this.currentItem.offset();this.offset={top:this.offset.top-this.margins.top,
-left:this.offset.left-this.margins.left};this.helper.css("position","absolute");this.cssPosition=this.helper.css("position");d.extend(this.offset,{click:{left:a.pageX-this.offset.left,top:a.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()});this.originalPosition=this._generatePosition(a);this.originalPageX=a.pageX;this.originalPageY=a.pageY;b.cursorAt&&this._adjustOffsetFromHelper(b.cursorAt);this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]};
-this.helper[0]!=this.currentItem[0]&&this.currentItem.hide();this._createPlaceholder();b.containment&&this._setContainment();if(b.cursor){if(d("body").css("cursor"))this._storedCursor=d("body").css("cursor");d("body").css("cursor",b.cursor)}if(b.opacity){if(this.helper.css("opacity"))this._storedOpacity=this.helper.css("opacity");this.helper.css("opacity",b.opacity)}if(b.zIndex){if(this.helper.css("zIndex"))this._storedZIndex=this.helper.css("zIndex");this.helper.css("zIndex",b.zIndex)}if(this.scrollParent[0]!=
-document&&this.scrollParent[0].tagName!="HTML")this.overflowOffset=this.scrollParent.offset();this._trigger("start",a,this._uiHash());this._preserveHelperProportions||this._cacheHelperProportions();if(!c)for(c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("activate",a,e._uiHash(this));if(d.ui.ddmanager)d.ui.ddmanager.current=this;d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,a);this.dragging=true;this.helper.addClass("ui-sortable-helper");this._mouseDrag(a);
-return true},_mouseDrag:function(a){this.position=this._generatePosition(a);this.positionAbs=this._convertPositionTo("absolute");if(!this.lastPositionAbs)this.lastPositionAbs=this.positionAbs;if(this.options.scroll){var b=this.options,c=false;if(this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"){if(this.overflowOffset.top+this.scrollParent[0].offsetHeight-a.pageY<b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop+b.scrollSpeed;else if(a.pageY-this.overflowOffset.top<
-b.scrollSensitivity)this.scrollParent[0].scrollTop=c=this.scrollParent[0].scrollTop-b.scrollSpeed;if(this.overflowOffset.left+this.scrollParent[0].offsetWidth-a.pageX<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft+b.scrollSpeed;else if(a.pageX-this.overflowOffset.left<b.scrollSensitivity)this.scrollParent[0].scrollLeft=c=this.scrollParent[0].scrollLeft-b.scrollSpeed}else{if(a.pageY-d(document).scrollTop()<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()-
-b.scrollSpeed);else if(d(window).height()-(a.pageY-d(document).scrollTop())<b.scrollSensitivity)c=d(document).scrollTop(d(document).scrollTop()+b.scrollSpeed);if(a.pageX-d(document).scrollLeft()<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()-b.scrollSpeed);else if(d(window).width()-(a.pageX-d(document).scrollLeft())<b.scrollSensitivity)c=d(document).scrollLeft(d(document).scrollLeft()+b.scrollSpeed)}c!==false&&d.ui.ddmanager&&!b.dropBehaviour&&d.ui.ddmanager.prepareOffsets(this,
-a)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(b=this.items.length-1;b>=0;b--){c=this.items[b];var e=c.item[0],f=this._intersectsWithPointer(c);if(f)if(e!=this.currentItem[0]&&this.placeholder[f==1?"next":"prev"]()[0]!=e&&!d.ui.contains(this.placeholder[0],e)&&(this.options.type=="semi-dynamic"?!d.ui.contains(this.element[0],
-e):true)){this.direction=f==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(c))this._rearrange(a,c);else break;this._trigger("change",a,this._uiHash());break}}this._contactContainers(a);d.ui.ddmanager&&d.ui.ddmanager.drag(this,a);this._trigger("sort",a,this._uiHash());this.lastPositionAbs=this.positionAbs;return false},_mouseStop:function(a,b){if(a){d.ui.ddmanager&&!this.options.dropBehaviour&&d.ui.ddmanager.drop(this,a);if(this.options.revert){var c=this;b=c.placeholder.offset();
-c.reverting=true;d(this.helper).animate({left:b.left-this.offset.parent.left-c.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:b.top-this.offset.parent.top-c.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){c._clear(a)})}else this._clear(a,b);return false}},cancel:function(){var a=this;if(this.dragging){this._mouseUp({target:null});this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):
-this.currentItem.show();for(var b=this.containers.length-1;b>=0;b--){this.containers[b]._trigger("deactivate",null,a._uiHash(this));if(this.containers[b].containerCache.over){this.containers[b]._trigger("out",null,a._uiHash(this));this.containers[b].containerCache.over=0}}}if(this.placeholder){this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove();d.extend(this,{helper:null,
-dragging:false,reverting:false,_noFinalSort:null});this.domPosition.prev?d(this.domPosition.prev).after(this.currentItem):d(this.domPosition.parent).prepend(this.currentItem)}return this},serialize:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};d(b).each(function(){var e=(d(a.item||this).attr(a.attribute||"id")||"").match(a.expression||/(.+)[-=_](.+)/);if(e)c.push((a.key||e[1]+"[]")+"="+(a.key&&a.expression?e[1]:e[2]))});!c.length&&a.key&&c.push(a.key+"=");return c.join("&")},
-toArray:function(a){var b=this._getItemsAsjQuery(a&&a.connected),c=[];a=a||{};b.each(function(){c.push(d(a.item||this).attr(a.attribute||"id")||"")});return c},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,e=this.positionAbs.top,f=e+this.helperProportions.height,g=a.left,h=g+a.width,i=a.top,k=i+a.height,j=this.offset.click.top,l=this.offset.click.left;j=e+j>i&&e+j<k&&b+l>g&&b+l<h;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||
-this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?j:g<b+this.helperProportions.width/2&&c-this.helperProportions.width/2<h&&i<e+this.helperProportions.height/2&&f-this.helperProportions.height/2<k},_intersectsWithPointer:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left,a.width);b=b&&a;a=this._getDragVerticalDirection();
-var c=this._getDragHorizontalDirection();if(!b)return false;return this.floating?c&&c=="right"||a=="down"?2:1:a&&(a=="down"?2:1)},_intersectsWithSides:function(a){var b=d.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,a.top+a.height/2,a.height);a=d.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,a.left+a.width/2,a.width);var c=this._getDragVerticalDirection(),e=this._getDragHorizontalDirection();return this.floating&&e?e=="right"&&a||e=="left"&&!a:c&&(c=="down"&&b||c=="up"&&!b)},
-_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a);this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(a){var b=[],c=[],e=this._connectWith();
-if(e&&a)for(a=e.length-1;a>=0;a--)for(var f=d(e[a]),g=f.length-1;g>=0;g--){var h=d.data(f[g],"sortable");if(h&&h!=this&&!h.options.disabled)c.push([d.isFunction(h.options.items)?h.options.items.call(h.element):d(h.options.items,h.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),h])}c.push([d.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):d(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),
-this]);for(a=c.length-1;a>=0;a--)c[a][0].each(function(){b.push(this)});return d(b)},_removeCurrentsFromItems:function(){for(var a=this.currentItem.find(":data(sortable-item)"),b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(a){this.items=[];this.containers=[this];var b=this.items,c=[[d.isFunction(this.options.items)?this.options.items.call(this.element[0],a,{item:this.currentItem}):d(this.options.items,this.element),
-this]],e=this._connectWith();if(e)for(var f=e.length-1;f>=0;f--)for(var g=d(e[f]),h=g.length-1;h>=0;h--){var i=d.data(g[h],"sortable");if(i&&i!=this&&!i.options.disabled){c.push([d.isFunction(i.options.items)?i.options.items.call(i.element[0],a,{item:this.currentItem}):d(i.options.items,i.element),i]);this.containers.push(i)}}for(f=c.length-1;f>=0;f--){a=c[f][1];e=c[f][0];h=0;for(g=e.length;h<g;h++){i=d(e[h]);i.data("sortable-item",a);b.push({item:i,instance:a,width:0,height:0,left:0,top:0})}}},refreshPositions:function(a){if(this.offsetParent&&
-this.helper)this.offset.parent=this._getParentOffset();for(var b=this.items.length-1;b>=0;b--){var c=this.items[b];if(!(c.instance!=this.currentContainer&&this.currentContainer&&c.item[0]!=this.currentItem[0])){var e=this.options.toleranceElement?d(this.options.toleranceElement,c.item):c.item;if(!a){c.width=e.outerWidth();c.height=e.outerHeight()}e=e.offset();c.left=e.left;c.top=e.top}}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(b=
-this.containers.length-1;b>=0;b--){e=this.containers[b].element.offset();this.containers[b].containerCache.left=e.left;this.containers[b].containerCache.top=e.top;this.containers[b].containerCache.width=this.containers[b].element.outerWidth();this.containers[b].containerCache.height=this.containers[b].element.outerHeight()}return this},_createPlaceholder:function(a){var b=a||this,c=b.options;if(!c.placeholder||c.placeholder.constructor==String){var e=c.placeholder;c.placeholder={element:function(){var f=
-d(document.createElement(b.currentItem[0].nodeName)).addClass(e||b.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];if(!e)f.style.visibility="hidden";return f},update:function(f,g){if(!(e&&!c.forcePlaceholderSize)){g.height()||g.height(b.currentItem.innerHeight()-parseInt(b.currentItem.css("paddingTop")||0,10)-parseInt(b.currentItem.css("paddingBottom")||0,10));g.width()||g.width(b.currentItem.innerWidth()-parseInt(b.currentItem.css("paddingLeft")||0,10)-parseInt(b.currentItem.css("paddingRight")||
-0,10))}}}}b.placeholder=d(c.placeholder.element.call(b.element,b.currentItem));b.currentItem.after(b.placeholder);c.placeholder.update(b,b.placeholder)},_contactContainers:function(a){for(var b=null,c=null,e=this.containers.length-1;e>=0;e--)if(!d.ui.contains(this.currentItem[0],this.containers[e].element[0]))if(this._intersectsWith(this.containers[e].containerCache)){if(!(b&&d.ui.contains(this.containers[e].element[0],b.element[0]))){b=this.containers[e];c=e}}else if(this.containers[e].containerCache.over){this.containers[e]._trigger("out",
-a,this._uiHash(this));this.containers[e].containerCache.over=0}if(b)if(this.containers.length===1){this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}else if(this.currentContainer!=this.containers[c]){b=1E4;e=null;for(var f=this.positionAbs[this.containers[c].floating?"left":"top"],g=this.items.length-1;g>=0;g--)if(d.ui.contains(this.containers[c].element[0],this.items[g].item[0])){var h=this.items[g][this.containers[c].floating?"left":"top"];if(Math.abs(h-
-f)<b){b=Math.abs(h-f);e=this.items[g]}}if(e||this.options.dropOnEmpty){this.currentContainer=this.containers[c];e?this._rearrange(a,e,null,true):this._rearrange(a,null,this.containers[c].element,true);this._trigger("change",a,this._uiHash());this.containers[c]._trigger("change",a,this._uiHash(this));this.options.placeholder.update(this.currentContainer,this.placeholder);this.containers[c]._trigger("over",a,this._uiHash(this));this.containers[c].containerCache.over=1}}},_createHelper:function(a){var b=
-this.options;a=d.isFunction(b.helper)?d(b.helper.apply(this.element[0],[a,this.currentItem])):b.helper=="clone"?this.currentItem.clone():this.currentItem;a.parents("body").length||d(b.appendTo!="parent"?b.appendTo:this.currentItem[0].parentNode)[0].appendChild(a[0]);if(a[0]==this.currentItem[0])this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")};if(a[0].style.width==
-""||b.forceHelperSize)a.width(this.currentItem.width());if(a[0].style.height==""||b.forceHelperSize)a.height(this.currentItem.height());return a},_adjustOffsetFromHelper:function(a){if(typeof a=="string")a=a.split(" ");if(d.isArray(a))a={left:+a[0],top:+a[1]||0};if("left"in a)this.offset.click.left=a.left+this.margins.left;if("right"in a)this.offset.click.left=this.helperProportions.width-a.right+this.margins.left;if("top"in a)this.offset.click.top=a.top+this.margins.top;if("bottom"in a)this.offset.click.top=
-this.helperProportions.height-a.bottom+this.margins.top},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var a=this.offsetParent.offset();if(this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0])){a.left+=this.scrollParent.scrollLeft();a.top+=this.scrollParent.scrollTop()}if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&d.browser.msie)a=
-{top:0,left:0};return{top:a.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:a.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}else return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),
-10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var a=this.options;if(a.containment=="parent")a.containment=this.helper[0].parentNode;if(a.containment=="document"||a.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,d(a.containment=="document"?
-document:window).width()-this.helperProportions.width-this.margins.left,(d(a.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(a.containment)){var b=d(a.containment)[0];a=d(a.containment).offset();var c=d(b).css("overflow")!="hidden";this.containment=[a.left+(parseInt(d(b).css("borderLeftWidth"),10)||0)+(parseInt(d(b).css("paddingLeft"),10)||0)-this.margins.left,a.top+(parseInt(d(b).css("borderTopWidth"),
-10)||0)+(parseInt(d(b).css("paddingTop"),10)||0)-this.margins.top,a.left+(c?Math.max(b.scrollWidth,b.offsetWidth):b.offsetWidth)-(parseInt(d(b).css("borderLeftWidth"),10)||0)-(parseInt(d(b).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,a.top+(c?Math.max(b.scrollHeight,b.offsetHeight):b.offsetHeight)-(parseInt(d(b).css("borderTopWidth"),10)||0)-(parseInt(d(b).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(a,b){if(!b)b=
-this.position;a=a=="absolute"?1:-1;var c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);return{top:b.top+this.offset.relative.top*a+this.offset.parent.top*a-(d.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:c.scrollTop())*a),left:b.left+this.offset.relative.left*a+this.offset.parent.left*a-(d.browser.safari&&
-this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())*a)}},_generatePosition:function(a){var b=this.options,c=this.cssPosition=="absolute"&&!(this.scrollParent[0]!=document&&d.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(c[0].tagName);if(this.cssPosition=="relative"&&!(this.scrollParent[0]!=document&&this.scrollParent[0]!=this.offsetParent[0]))this.offset.relative=this._getRelativeOffset();
-var f=a.pageX,g=a.pageY;if(this.originalPosition){if(this.containment){if(a.pageX-this.offset.click.left<this.containment[0])f=this.containment[0]+this.offset.click.left;if(a.pageY-this.offset.click.top<this.containment[1])g=this.containment[1]+this.offset.click.top;if(a.pageX-this.offset.click.left>this.containment[2])f=this.containment[2]+this.offset.click.left;if(a.pageY-this.offset.click.top>this.containment[3])g=this.containment[3]+this.offset.click.top}if(b.grid){g=this.originalPageY+Math.round((g-
-this.originalPageY)/b.grid[1])*b.grid[1];g=this.containment?!(g-this.offset.click.top<this.containment[1]||g-this.offset.click.top>this.containment[3])?g:!(g-this.offset.click.top<this.containment[1])?g-b.grid[1]:g+b.grid[1]:g;f=this.originalPageX+Math.round((f-this.originalPageX)/b.grid[0])*b.grid[0];f=this.containment?!(f-this.offset.click.left<this.containment[0]||f-this.offset.click.left>this.containment[2])?f:!(f-this.offset.click.left<this.containment[0])?f-b.grid[0]:f+b.grid[0]:f}}return{top:g-
-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:c.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(d.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:c.scrollLeft())}},_rearrange:function(a,b,c,e){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],
-this.direction=="down"?b.item[0]:b.item[0].nextSibling);this.counter=this.counter?++this.counter:1;var f=this,g=this.counter;window.setTimeout(function(){g==f.counter&&f.refreshPositions(!e)},0)},_clear:function(a,b){this.reverting=false;var c=[];!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem);this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var e in this._storedCSS)if(this._storedCSS[e]=="auto"||this._storedCSS[e]=="static")this._storedCSS[e]=
-"";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!b&&c.push(function(f){this._trigger("receive",f,this._uiHash(this.fromOutside))});if((this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!b)c.push(function(f){this._trigger("update",f,this._uiHash())});if(!d.ui.contains(this.element[0],this.currentItem[0])){b||c.push(function(f){this._trigger("remove",
-f,this._uiHash())});for(e=this.containers.length-1;e>=0;e--)if(d.ui.contains(this.containers[e].element[0],this.currentItem[0])&&!b){c.push(function(f){return function(g){f._trigger("receive",g,this._uiHash(this))}}.call(this,this.containers[e]));c.push(function(f){return function(g){f._trigger("update",g,this._uiHash(this))}}.call(this,this.containers[e]))}}for(e=this.containers.length-1;e>=0;e--){b||c.push(function(f){return function(g){f._trigger("deactivate",g,this._uiHash(this))}}.call(this,
-this.containers[e]));if(this.containers[e].containerCache.over){c.push(function(f){return function(g){f._trigger("out",g,this._uiHash(this))}}.call(this,this.containers[e]));this.containers[e].containerCache.over=0}}this._storedCursor&&d("body").css("cursor",this._storedCursor);this._storedOpacity&&this.helper.css("opacity",this._storedOpacity);if(this._storedZIndex)this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex);this.dragging=false;if(this.cancelHelperRemoval){if(!b){this._trigger("beforeStop",
-a,this._uiHash());for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}return false}b||this._trigger("beforeStop",a,this._uiHash());this.placeholder[0].parentNode.removeChild(this.placeholder[0]);this.helper[0]!=this.currentItem[0]&&this.helper.remove();this.helper=null;if(!b){for(e=0;e<c.length;e++)c[e].call(this,a);this._trigger("stop",a,this._uiHash())}this.fromOutside=false;return true},_trigger:function(){d.Widget.prototype._trigger.apply(this,arguments)===false&&this.cancel()},
-_uiHash:function(a){var b=a||this;return{helper:b.helper,placeholder:b.placeholder||d([]),position:b.position,originalPosition:b.originalPosition,offset:b.positionAbs,item:b.currentItem,sender:a?a.element:null}}});d.extend(d.ui.sortable,{version:"1.8.16"})})(jQuery);
-;
\ No newline at end of file
diff --git a/webengineviewer/src/data/jquery.js b/webengineviewer/src/data/jquery.js
deleted file mode 100644
index 462cde56..00000000
--- a/webengineviewer/src/data/jquery.js
+++ /dev/null
@@ -1,4376 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.3.2
- * http://jquery.com/
- *
- * Copyright (c) 2009 John Resig
- * Dual licensed under the MIT and GPL licenses.
- * http://docs.jquery.com/License
- *
- * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
- * Revision: 6246
- */
-(function(){
-
-var
- // Will speed up references to window, and allows munging its name.
- window = this,
- // Will speed up references to undefined, and allows munging its name.
- undefined,
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
- // Map over the $ in case of overwrite
- _$ = window.$,
-
- jQuery = window.jQuery = window.$ = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context );
- },
-
- // A simple way to check for HTML strings or ID strings
- // (both of which we optimize for)
- quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
- // Is it a simple selector
- isSimple = /^.[^:#\[\.,]*$/;
-
-jQuery.fn = jQuery.prototype = {
- init: function( selector, context ) {
- // Make sure that a selection was provided
- selector = selector || document;
-
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this[0] = selector;
- this.length = 1;
- this.context = selector;
- return this;
- }
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- // Are we dealing with HTML string or an ID?
- var match = quickExpr.exec( selector );
-
- // Verify a match, and that no context was specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] )
- selector = jQuery.clean( [ match[1] ], context );
-
- // HANDLE: $("#id")
- else {
- var elem = document.getElementById( match[3] );
-
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem && elem.id != match[3] )
- return jQuery().find( selector );
-
- // Otherwise, we inject the element directly into the jQuery object
- var ret = jQuery( elem || [] );
- ret.context = document;
- ret.selector = selector;
- return ret;
- }
-
- // HANDLE: $(expr, [context])
- // (which is just equivalent to: $(content).find(expr)
- } else
- return jQuery( context ).find( selector );
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) )
- return jQuery( document ).ready( selector );
-
- // Make sure that old selector state is passed along
- if ( selector.selector && selector.context ) {
- this.selector = selector.selector;
- this.context = selector.context;
- }
-
- return this.setArray(jQuery.isArray( selector ) ?
- selector :
- jQuery.makeArray(selector));
- },
-
- // Start with an empty selector
- selector: "",
-
- // The current version of jQuery being used
- jquery: "1.3.2",
-
- // The number of elements contained in the matched element set
- size: function() {
- return this.length;
- },
-
- // Get the Nth element in the matched element set OR
- // Get the whole matched element set as a clean array
- get: function( num ) {
- return num === undefined ?
-
- // Return a 'clean' array
- Array.prototype.slice.call( this ) :
-
- // Return just the object
- this[ num ];
- },
-
- // Take an array of elements and push it onto the stack
- // (returning the new matched element set)
- pushStack: function( elems, name, selector ) {
- // Build a new jQuery matched element set
- var ret = jQuery( elems );
-
- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
-
- ret.context = this.context;
-
- if ( name === "find" )
- ret.selector = this.selector + (this.selector ? " " : "") + selector;
- else if ( name )
- ret.selector = this.selector + "." + name + "(" + selector + ")";
-
- // Return the newly-formed element set
- return ret;
- },
-
- // Force the current matched set of elements to become
- // the specified array of elements (destroying the stack in the process)
- // You should use pushStack() in order to do this, but maintain the stack
- setArray: function( elems ) {
- // Resetting the length to 0, then using the native Array push
- // is a super-fast way to populate an object with array-like properties
- this.length = 0;
- Array.prototype.push.apply( this, elems );
-
- return this;
- },
-
- // Execute a callback for every element in the matched set.
- // (You can seed the arguments with an array of args, but this is
- // only used internally.)
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
- },
-
- // Determine the position of an element within
- // the matched set of elements
- index: function( elem ) {
- // Locate the position of the desired element
- return jQuery.inArray(
- // If it receives a jQuery object, the first element is used
- elem && elem.jquery ? elem[0] : elem
- , this );
- },
-
- attr: function( name, value, type ) {
- var options = name;
-
- // Look for the case where we're accessing a style value
- if ( typeof name === "string" )
- if ( value === undefined )
- return this[0] && jQuery[ type || "attr" ]( this[0], name );
-
- else {
- options = {};
- options[ name ] = value;
- }
-
- // Check to see if we're setting style values
- return this.each(function(i){
- // Set all the styles
- for ( name in options )
- jQuery.attr(
- type ?
- this.style :
- this,
- name, jQuery.prop( this, options[ name ], type, i, name )
- );
- });
- },
-
- css: function( key, value ) {
- // ignore negative width and height values
- if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
- value = undefined;
- return this.attr( key, value, "curCSS" );
- },
-
- text: function( text ) {
- if ( typeof text !== "object" && text != null )
- return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
-
- var ret = "";
-
- jQuery.each( text || this, function(){
- jQuery.each( this.childNodes, function(){
- if ( this.nodeType != 8 )
- ret += this.nodeType != 1 ?
- this.nodeValue :
- jQuery.fn.text( [ this ] );
- });
- });
-
- return ret;
- },
-
- wrapAll: function( html ) {
- if ( this[0] ) {
- // The elements to wrap the target around
- var wrap = jQuery( html, this[0].ownerDocument ).clone();
-
- if ( this[0].parentNode )
- wrap.insertBefore( this[0] );
-
- wrap.map(function(){
- var elem = this;
-
- while ( elem.firstChild )
- elem = elem.firstChild;
-
- return elem;
- }).append(this);
- }
-
- return this;
- },
-
- wrapInner: function( html ) {
- return this.each(function(){
- jQuery( this ).contents().wrapAll( html );
- });
- },
-
- wrap: function( html ) {
- return this.each(function(){
- jQuery( this ).wrapAll( html );
- });
- },
-
- append: function() {
- return this.domManip(arguments, true, function(elem){
- if (this.nodeType == 1)
- this.appendChild( elem );
- });
- },
-
- prepend: function() {
- return this.domManip(arguments, true, function(elem){
- if (this.nodeType == 1)
- this.insertBefore( elem, this.firstChild );
- });
- },
-
- before: function() {
- return this.domManip(arguments, false, function(elem){
- this.parentNode.insertBefore( elem, this );
- });
- },
-
- after: function() {
- return this.domManip(arguments, false, function(elem){
- this.parentNode.insertBefore( elem, this.nextSibling );
- });
- },
-
- end: function() {
- return this.prevObject || jQuery( [] );
- },
-
- // For internal use only.
- // Behaves like an Array's method, not like a jQuery method.
- push: [].push,
- sort: [].sort,
- splice: [].splice,
-
- find: function( selector ) {
- if ( this.length === 1 ) {
- var ret = this.pushStack( [], "find", selector );
- ret.length = 0;
- jQuery.find( selector, this[0], ret );
- return ret;
- } else {
- return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
- return jQuery.find( selector, elem );
- })), "find", selector );
- }
- },
-
- clone: function( events ) {
- // Do the clone
- var ret = this.map(function(){
- if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
- // IE copies events bound via attachEvent when
- // using cloneNode. Calling detachEvent on the
- // clone will also remove the events from the orignal
- // In order to get around this, we use innerHTML.
- // Unfortunately, this means some modifications to
- // attributes in IE that are actually only stored
- // as properties will not be copied (such as the
- // the name attribute on an input).
- var html = this.outerHTML;
- if ( !html ) {
- var div = this.ownerDocument.createElement("div");
- div.appendChild( this.cloneNode(true) );
- html = div.innerHTML;
- }
-
- return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
- } else
- return this.cloneNode(true);
- });
-
- // Copy the events from the original to the clone
- if ( events === true ) {
- var orig = this.find("*").andSelf(), i = 0;
-
- ret.find("*").andSelf().each(function(){
- if ( this.nodeName !== orig[i].nodeName )
- return;
-
- var events = jQuery.data( orig[i], "events" );
-
- for ( var type in events ) {
- for ( var handler in events[ type ] ) {
- jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
- }
- }
-
- i++;
- });
- }
-
- // Return the cloned set
- return ret;
- },
-
- filter: function( selector ) {
- return this.pushStack(
- jQuery.isFunction( selector ) &&
- jQuery.grep(this, function(elem, i){
- return selector.call( elem, i );
- }) ||
-
- jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
- return elem.nodeType === 1;
- }) ), "filter", selector );
- },
-
- closest: function( selector ) {
- var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
- closer = 0;
-
- return this.map(function(){
- var cur = this;
- while ( cur && cur.ownerDocument ) {
- if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
- jQuery.data(cur, "closest", closer);
- return cur;
- }
- cur = cur.parentNode;
- closer++;
- }
- });
- },
-
- not: function( selector ) {
- if ( typeof selector === "string" )
- // test special case where just one selector is passed in
- if ( isSimple.test( selector ) )
- return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
- else
- selector = jQuery.multiFilter( selector, this );
-
- var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
- return this.filter(function() {
- return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
- });
- },
-
- add: function( selector ) {
- return this.pushStack( jQuery.unique( jQuery.merge(
- this.get(),
- typeof selector === "string" ?
- jQuery( selector ) :
- jQuery.makeArray( selector )
- )));
- },
-
- is: function( selector ) {
- return !!selector && jQuery.multiFilter( selector, this ).length > 0;
- },
-
- hasClass: function( selector ) {
- return !!selector && this.is( "." + selector );
- },
-
- val: function( value ) {
- if ( value === undefined ) {
- var elem = this[0];
-
- if ( elem ) {
- if( jQuery.nodeName( elem, 'option' ) )
- return (elem.attributes.value || {}).specified ? elem.value : elem.text;
-
- // We need to handle select boxes special
- if ( jQuery.nodeName( elem, "select" ) ) {
- var index = elem.selectedIndex,
- values = [],
- options = elem.options,
- one = elem.type == "select-one";
-
- // Nothing was selected
- if ( index < 0 )
- return null;
-
- // Loop through all the selected options
- for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
- var option = options[ i ];
-
- if ( option.selected ) {
- // Get the specifc value for the option
- value = jQuery(option).val();
-
- // We don't need an array for one selects
- if ( one )
- return value;
-
- // Multi-Selects return an array
- values.push( value );
- }
- }
-
- return values;
- }
-
- // Everything else, we just grab the value
- return (elem.value || "").replace(/\r/g, "");
-
- }
-
- return undefined;
- }
-
- if ( typeof value === "number" )
- value += '';
-
- return this.each(function(){
- if ( this.nodeType != 1 )
- return;
-
- if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
- this.checked = (jQuery.inArray(this.value, value) >= 0 ||
- jQuery.inArray(this.name, value) >= 0);
-
- else if ( jQuery.nodeName( this, "select" ) ) {
- var values = jQuery.makeArray(value);
-
- jQuery( "option", this ).each(function(){
- this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
- jQuery.inArray( this.text, values ) >= 0);
- });
-
- if ( !values.length )
- this.selectedIndex = -1;
-
- } else
- this.value = value;
- });
- },
-
- html: function( value ) {
- return value === undefined ?
- (this[0] ?
- this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
- null) :
- this.empty().append( value );
- },
-
- replaceWith: function( value ) {
- return this.after( value ).remove();
- },
-
- eq: function( i ) {
- return this.slice( i, +i + 1 );
- },
-
- slice: function() {
- return this.pushStack( Array.prototype.slice.apply( this, arguments ),
- "slice", Array.prototype.slice.call(arguments).join(",") );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function(elem, i){
- return callback.call( elem, i, elem );
- }));
- },
-
- andSelf: function() {
- return this.add( this.prevObject );
- },
-
- domManip: function( args, table, callback ) {
- if ( this[0] ) {
- var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
- scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
- first = fragment.firstChild;
-
- if ( first )
- for ( var i = 0, l = this.length; i < l; i++ )
- callback.call( root(this[i], first), this.length > 1 || i > 0 ?
- fragment.cloneNode(true) : fragment );
-
- if ( scripts )
- jQuery.each( scripts, evalScript );
- }
-
- return this;
-
- function root( elem, cur ) {
- return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
- (elem.getElementsByTagName("tbody")[0] ||
- elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
- elem;
- }
- }
-};
-
-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
-function evalScript( i, elem ) {
- if ( elem.src )
- jQuery.ajax({
- url: elem.src,
- async: false,
- dataType: "script"
- });
-
- else
- jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
-
- if ( elem.parentNode )
- elem.parentNode.removeChild( elem );
-}
-
-function now(){
- return +new Date;
-}
-
-jQuery.extend = jQuery.fn.extend = function() {
- // copy reference to target object
- var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
-
- // Handle a deep copy situation
- if ( typeof target === "boolean" ) {
- deep = target;
- target = arguments[1] || {};
- // skip the boolean and the target
- i = 2;
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !jQuery.isFunction(target) )
- target = {};
-
- // extend jQuery itself if only one argument is passed
- if ( length == i ) {
- target = this;
- --i;
- }
-
- for ( ; i < length; i++ )
- // Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null )
- // Extend the base object
- for ( var name in options ) {
- var src = target[ name ], copy = options[ name ];
-
- // Prevent never-ending loop
- if ( target === copy )
- continue;
-
- // Recurse if we're merging object values
- if ( deep && copy && typeof copy === "object" && !copy.nodeType )
- target[ name ] = jQuery.extend( deep,
- // Never move original objects, clone them
- src || ( copy.length != null ? [ ] : { } )
- , copy );
-
- // Don't bring in undefined values
- else if ( copy !== undefined )
- target[ name ] = copy;
-
- }
-
- // Return the modified object
- return target;
-};
-
-// exclude the following css properties to add px
-var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
- // cache defaultView
- defaultView = document.defaultView || {},
- toString = Object.prototype.toString;
-
-jQuery.extend({
- noConflict: function( deep ) {
- window.$ = _$;
-
- if ( deep )
- window.jQuery = _jQuery;
-
- return jQuery;
- },
-
- // See test/unit/core.js for details concerning isFunction.
- // Since version 1.3, DOM methods and functions like alert
- // aren't supported. They return false on IE (#2968).
- isFunction: function( obj ) {
- return toString.call(obj) === "[object Function]";
- },
-
- isArray: function( obj ) {
- return toString.call(obj) === "[object Array]";
- },
-
- // check if an element is in a (or is an) XML document
- isXMLDoc: function( elem ) {
- return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
- !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
- },
-
- // Evalulates a script in a global context
- globalEval: function( data ) {
- if ( data && /\S/.test(data) ) {
- // Inspired by code by Andrea Giammarchi
- // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
- var head = document.getElementsByTagName("head")[0] || document.documentElement,
- script = document.createElement("script");
-
- script.type = "text/javascript";
- if ( jQuery.support.scriptEval )
- script.appendChild( document.createTextNode( data ) );
- else
- script.text = data;
-
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709).
- head.insertBefore( script, head.firstChild );
- head.removeChild( script );
- }
- },
-
- nodeName: function( elem, name ) {
- return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
- },
-
- // args is for internal usage only
- each: function( object, callback, args ) {
- var name, i = 0, length = object.length;
-
- if ( args ) {
- if ( length === undefined ) {
- for ( name in object )
- if ( callback.apply( object[ name ], args ) === false )
- break;
- } else
- for ( ; i < length; )
- if ( callback.apply( object[ i++ ], args ) === false )
- break;
-
- // A special, fast, case for the most common use of each
- } else {
- if ( length === undefined ) {
- for ( name in object )
- if ( callback.call( object[ name ], name, object[ name ] ) === false )
- break;
- } else
- for ( var value = object[0];
- i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
- }
-
- return object;
- },
-
- prop: function( elem, value, type, i, name ) {
- // Handle executable functions
- if ( jQuery.isFunction( value ) )
- value = value.call( elem, i );
-
- // Handle passing in a number to a CSS property
- return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
- value + "px" :
- value;
- },
-
- className: {
- // internal only, use addClass("class")
- add: function( elem, classNames ) {
- jQuery.each((classNames || "").split(/\s+/), function(i, className){
- if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
- elem.className += (elem.className ? " " : "") + className;
- });
- },
-
- // internal only, use removeClass("class")
- remove: function( elem, classNames ) {
- if (elem.nodeType == 1)
- elem.className = classNames !== undefined ?
- jQuery.grep(elem.className.split(/\s+/), function(className){
- return !jQuery.className.has( classNames, className );
- }).join(" ") :
- "";
- },
-
- // internal only, use hasClass("class")
- has: function( elem, className ) {
- return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
- }
- },
-
- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback ) {
- var old = {};
- // Remember the old values, and insert the new ones
- for ( var name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- callback.call( elem );
-
- // Revert the old values
- for ( var name in options )
- elem.style[ name ] = old[ name ];
- },
-
- css: function( elem, name, force, extra ) {
- if ( name == "width" || name == "height" ) {
- var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
-
- function getWH() {
- val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
-
- if ( extra === "border" )
- return;
-
- jQuery.each( which, function() {
- if ( !extra )
- val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
- if ( extra === "margin" )
- val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
- else
- val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
- });
- }
-
- if ( elem.offsetWidth !== 0 )
- getWH();
- else
- jQuery.swap( elem, props, getWH );
-
- return Math.max(0, Math.round(val));
- }
-
- return jQuery.curCSS( elem, name, force );
- },
-
- curCSS: function( elem, name, force ) {
- var ret, style = elem.style;
-
- // We need to handle opacity special in IE
- if ( name == "opacity" && !jQuery.support.opacity ) {
- ret = jQuery.attr( style, "opacity" );
-
- return ret == "" ?
- "1" :
- ret;
- }
-
- // Make sure we're using the right name for getting the float value
- if ( name.match( /float/i ) )
- name = styleFloat;
-
- if ( !force && style && style[ name ] )
- ret = style[ name ];
-
- else if ( defaultView.getComputedStyle ) {
-
- // Only "float" is needed here
- if ( name.match( /float/i ) )
- name = "float";
-
- name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
-
- var computedStyle = defaultView.getComputedStyle( elem, null );
-
- if ( computedStyle )
- ret = computedStyle.getPropertyValue( name );
-
- // We should always get a number back from opacity
- if ( name == "opacity" && ret == "" )
- ret = "1";
-
- } else if ( elem.currentStyle ) {
- var camelCase = name.replace(/\-(\w)/g, function(all, letter){
- return letter.toUpperCase();
- });
-
- ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
-
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
- // Remember the original values
- var left = style.left, rsLeft = elem.runtimeStyle.left;
-
- // Put in the new values to get a computed value out
- elem.runtimeStyle.left = elem.currentStyle.left;
- style.left = ret || 0;
- ret = style.pixelLeft + "px";
-
- // Revert the changed values
- style.left = left;
- elem.runtimeStyle.left = rsLeft;
- }
- }
-
- return ret;
- },
-
- clean: function( elems, context, fragment ) {
- context = context || document;
-
- // !context.createElement fails in IE with an error but returns typeof 'object'
- if ( typeof context.createElement === "undefined" )
- context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
-
- // If a single string is passed in and it's a single tag
- // just do a createElement and skip the rest
- if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
- var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
- if ( match )
- return [ context.createElement( match[1] ) ];
- }
-
- var ret = [], scripts = [], div = context.createElement("div");
-
- jQuery.each(elems, function(i, elem){
- if ( typeof elem === "number" )
- elem += '';
-
- if ( !elem )
- return;
-
- // Convert html string into DOM nodes
- if ( typeof elem === "string" ) {
- // Fix "XHTML"-style tags in all browsers
- elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
- return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
- all :
- front + "></" + tag + ">";
- });
-
- // Trim whitespace, otherwise indexOf won't work as expected
- var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
-
- var wrap =
- // option or optgroup
- !tags.indexOf("<opt") &&
- [ 1, "<select multiple='multiple'>", "</select>" ] ||
-
- !tags.indexOf("<leg") &&
- [ 1, "<fieldset>", "</fieldset>" ] ||
-
- tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
- [ 1, "<table>", "</table>" ] ||
-
- !tags.indexOf("<tr") &&
- [ 2, "<table><tbody>", "</tbody></table>" ] ||
-
- // <thead> matched above
- (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
- [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
-
- !tags.indexOf("<col") &&
- [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
-
- // IE can't serialize <link> and <script> tags normally
- !jQuery.support.htmlSerialize &&
- [ 1, "div<div>", "</div>" ] ||
-
- [ 0, "", "" ];
-
- // Go to html and back, then peel off extra wrappers
- div.innerHTML = wrap[1] + elem + wrap[2];
-
- // Move to the right depth
- while ( wrap[0]-- )
- div = div.lastChild;
-
- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
-
- // String was a <table>, *may* have spurious <tbody>
- var hasBody = /<tbody/i.test(elem),
- tbody = !tags.indexOf("<table") && !hasBody ?
- div.firstChild && div.firstChild.childNodes :
-
- // String was a bare <thead> or <tfoot>
- wrap[1] == "<table>" && !hasBody ?
- div.childNodes :
- [];
-
- for ( var j = tbody.length - 1; j >= 0 ; --j )
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
-
- }
-
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
- div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
-
- elem = jQuery.makeArray( div.childNodes );
- }
-
- if ( elem.nodeType )
- ret.push( elem );
- else
- ret = jQuery.merge( ret, elem );
-
- });
-
- if ( fragment ) {
- for ( var i = 0; ret[i]; i++ ) {
- if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
- scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
- } else {
- if ( ret[i].nodeType === 1 )
- ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
- fragment.appendChild( ret[i] );
- }
- }
-
- return scripts;
- }
-
- return ret;
- },
-
- attr: function( elem, name, value ) {
- // don't set attributes on text and comment nodes
- if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
- return undefined;
-
- var notxml = !jQuery.isXMLDoc( elem ),
- // Whether we are setting (or getting)
- set = value !== undefined;
-
- // Try to normalize/fix the name
- name = notxml && jQuery.props[ name ] || name;
-
- // Only do all the following if this is a node (faster for style)
- // IE elem.getAttribute passes even for style
- if ( elem.tagName ) {
-
- // These attributes require special treatment
- var special = /href|src|style/.test( name );
-
- // Safari mis-reports the default selected property of a hidden option
- // Accessing the parent's selectedIndex property fixes it
- if ( name == "selected" && elem.parentNode )
- elem.parentNode.selectedIndex;
-
- // If applicable, access the attribute via the DOM 0 way
- if ( name in elem && notxml && !special ) {
- if ( set ){
- // We can't allow the type property to be changed (since it causes problems in IE)
- if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
- throw "type property can't be changed";
-
- elem[ name ] = value;
- }
-
- // browsers index elements by id/name on forms, give priority to attributes.
- if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
- return elem.getAttributeNode( name ).nodeValue;
-
- // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
- // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- if ( name == "tabIndex" ) {
- var attributeNode = elem.getAttributeNode( "tabIndex" );
- return attributeNode && attributeNode.specified
- ? attributeNode.value
- : elem.nodeName.match(/(button|input|object|select|textarea)/i)
- ? 0
- : elem.nodeName.match(/^(a|area)$/i) && elem.href
- ? 0
- : undefined;
- }
-
- return elem[ name ];
- }
-
- if ( !jQuery.support.style && notxml && name == "style" )
- return jQuery.attr( elem.style, "cssText", value );
-
- if ( set )
- // convert the value to a string (all browsers do this but IE) see #1070
- elem.setAttribute( name, "" + value );
-
- var attr = !jQuery.support.hrefNormalized && notxml && special
- // Some attributes require a special call on IE
- ? elem.getAttribute( name, 2 )
- : elem.getAttribute( name );
-
- // Non-existent attributes return null, we normalize to undefined
- return attr === null ? undefined : attr;
- }
-
- // elem is actually elem.style ... set the style
-
- // IE uses filters for opacity
- if ( !jQuery.support.opacity && name == "opacity" ) {
- if ( set ) {
- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- elem.zoom = 1;
-
- // Set the alpha filter to set the opacity
- elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
- (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
- }
-
- return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
- (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
- "";
- }
-
- name = name.replace(/-([a-z])/ig, function(all, letter){
- return letter.toUpperCase();
- });
-
- if ( set )
- elem[ name ] = value;
-
- return elem[ name ];
- },
-
- trim: function( text ) {
- return (text || "").replace( /^\s+|\s+$/g, "" );
- },
-
- makeArray: function( array ) {
- var ret = [];
-
- if( array != null ){
- var i = array.length;
- // The window, strings (and functions) also have 'length'
- if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
- ret[0] = array;
- else
- while( i )
- ret[--i] = array[i];
- }
-
- return ret;
- },
-
- inArray: function( elem, array ) {
- for ( var i = 0, length = array.length; i < length; i++ )
- // Use === because on IE, window == document
- if ( array[ i ] === elem )
- return i;
-
- return -1;
- },
-
- merge: function( first, second ) {
- // We have to loop this way because IE & Opera overwrite the length
- // expando of getElementsByTagName
- var i = 0, elem, pos = first.length;
- // Also, we need to make sure that the correct elements are being returned
- // (IE returns comment nodes in a '*' query)
- if ( !jQuery.support.getAll ) {
- while ( (elem = second[ i++ ]) != null )
- if ( elem.nodeType != 8 )
- first[ pos++ ] = elem;
-
- } else
- while ( (elem = second[ i++ ]) != null )
- first[ pos++ ] = elem;
-
- return first;
- },
-
- unique: function( array ) {
- var ret = [], done = {};
-
- try {
-
- for ( var i = 0, length = array.length; i < length; i++ ) {
- var id = jQuery.data( array[ i ] );
-
- if ( !done[ id ] ) {
- done[ id ] = true;
- ret.push( array[ i ] );
- }
- }
-
- } catch( e ) {
- ret = array;
- }
-
- return ret;
- },
-
- grep: function( elems, callback, inv ) {
- var ret = [];
-
- // Go through the array, only saving the items
- // that pass the validator function
- for ( var i = 0, length = elems.length; i < length; i++ )
- if ( !inv != !callback( elems[ i ], i ) )
- ret.push( elems[ i ] );
-
- return ret;
- },
-
- map: function( elems, callback ) {
- var ret = [];
-
- // Go through the array, translating each of the items to their
- // new value (or values).
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- var value = callback( elems[ i ], i );
-
- if ( value != null )
- ret[ ret.length ] = value;
- }
-
- return ret.concat.apply( [], ret );
- }
-});
-
-// Use of jQuery.browser is deprecated.
-// It's included for backwards compatibility and plugins,
-// although they should work to migrate away.
-
-var userAgent = navigator.userAgent.toLowerCase();
-
-// Figure out what browser is being used
-jQuery.browser = {
- version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
- safari: /webkit/.test( userAgent ),
- opera: /opera/.test( userAgent ),
- msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
- mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
-};
-
-jQuery.each({
- parent: function(elem){return elem.parentNode;},
- parents: function(elem){return jQuery.dir(elem,"parentNode");},
- next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
- prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
- nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
- prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
- siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
- children: function(elem){return jQuery.sibling(elem.firstChild);},
- contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
-}, function(name, fn){
- jQuery.fn[ name ] = function( selector ) {
- var ret = jQuery.map( this, fn );
-
- if ( selector && typeof selector == "string" )
- ret = jQuery.multiFilter( selector, ret );
-
- return this.pushStack( jQuery.unique( ret ), name, selector );
- };
-});
-
-jQuery.each({
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function(name, original){
- jQuery.fn[ name ] = function( selector ) {
- var ret = [], insert = jQuery( selector );
-
- for ( var i = 0, l = insert.length; i < l; i++ ) {
- var elems = (i > 0 ? this.clone(true) : this).get();
- jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
- ret = ret.concat( elems );
- }
-
- return this.pushStack( ret, name, selector );
- };
-});
-
-jQuery.each({
- removeAttr: function( name ) {
- jQuery.attr( this, name, "" );
- if (this.nodeType == 1)
- this.removeAttribute( name );
- },
-
- addClass: function( classNames ) {
- jQuery.className.add( this, classNames );
- },
-
- removeClass: function( classNames ) {
- jQuery.className.remove( this, classNames );
- },
-
- toggleClass: function( classNames, state ) {
- if( typeof state !== "boolean" )
- state = !jQuery.className.has( this, classNames );
- jQuery.className[ state ? "add" : "remove" ]( this, classNames );
- },
-
- remove: function( selector ) {
- if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
- // Prevent memory leaks
- jQuery( "*", this ).add([this]).each(function(){
- jQuery.event.remove(this);
- jQuery.removeData(this);
- });
- if (this.parentNode)
- this.parentNode.removeChild( this );
- }
- },
-
- empty: function() {
- // Remove element nodes and prevent memory leaks
- jQuery(this).children().remove();
-
- // Remove any remaining nodes
- while ( this.firstChild )
- this.removeChild( this.firstChild );
- }
-}, function(name, fn){
- jQuery.fn[ name ] = function(){
- return this.each( fn, arguments );
- };
-});
-
-// Helper function used by the dimensions and offset modules
-function num(elem, prop) {
- return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
-}
-var expando = "jQuery" + now(), uuid = 0, windowData = {};
-
-jQuery.extend({
- cache: {},
-
- data: function( elem, name, data ) {
- elem = elem == window ?
- windowData :
- elem;
-
- var id = elem[ expando ];
-
- // Compute a unique ID for the element
- if ( !id )
- id = elem[ expando ] = ++uuid;
-
- // Only generate the data cache if we're
- // trying to access or manipulate it
- if ( name && !jQuery.cache[ id ] )
- jQuery.cache[ id ] = {};
-
- // Prevent overriding the named cache with undefined values
- if ( data !== undefined )
- jQuery.cache[ id ][ name ] = data;
-
- // Return the named cache data, or the ID for the element
- return name ?
- jQuery.cache[ id ][ name ] :
- id;
- },
-
- removeData: function( elem, name ) {
- elem = elem == window ?
- windowData :
- elem;
-
- var id = elem[ expando ];
-
- // If we want to remove a specific section of the element's data
- if ( name ) {
- if ( jQuery.cache[ id ] ) {
- // Remove the section of cache data
- delete jQuery.cache[ id ][ name ];
-
- // If we've removed all the data, remove the element's cache
- name = "";
-
- for ( name in jQuery.cache[ id ] )
- break;
-
- if ( !name )
- jQuery.removeData( elem );
- }
-
- // Otherwise, we want to remove all of the element's data
- } else {
- // Clean up the element expando
- try {
- delete elem[ expando ];
- } catch(e){
- // IE has trouble directly removing the expando
- // but it's ok with using removeAttribute
- if ( elem.removeAttribute )
- elem.removeAttribute( expando );
- }
-
- // Completely remove the data cache
- delete jQuery.cache[ id ];
- }
- },
- queue: function( elem, type, data ) {
- if ( elem ){
-
- type = (type || "fx") + "queue";
-
- var q = jQuery.data( elem, type );
-
- if ( !q || jQuery.isArray(data) )
- q = jQuery.data( elem, type, jQuery.makeArray(data) );
- else if( data )
- q.push( data );
-
- }
- return q;
- },
-
- dequeue: function( elem, type ){
- var queue = jQuery.queue( elem, type ),
- fn = queue.shift();
-
- if( !type || type === "fx" )
- fn = queue[0];
-
- if( fn !== undefined )
- fn.call(elem);
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ){
- var parts = key.split(".");
- parts[1] = parts[1] ? "." + parts[1] : "";
-
- if ( value === undefined ) {
- var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
-
- if ( data === undefined && this.length )
- data = jQuery.data( this[0], key );
-
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
- } else
- return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
- jQuery.data( this, key, value );
- });
- },
-
- removeData: function( key ){
- return this.each(function(){
- jQuery.removeData( this, key );
- });
- },
- queue: function(type, data){
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- }
-
- if ( data === undefined )
- return jQuery.queue( this[0], type );
-
- return this.each(function(){
- var queue = jQuery.queue( this, type, data );
-
- if( type == "fx" && queue.length == 1 )
- queue[0].call(this);
- });
- },
- dequeue: function(type){
- return this.each(function(){
- jQuery.dequeue( this, type );
- });
- }
-});/*!
- * Sizzle CSS Selector Engine - v0.9.3
- * Copyright 2009, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
- done = 0,
- toString = Object.prototype.toString;
-
-var Sizzle = function(selector, context, results, seed) {
- results = results || [];
- context = context || document;
-
- if ( context.nodeType !== 1 && context.nodeType !== 9 )
- return [];
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- var parts = [], m, set, checkSet, check, mode, extra, prune = true;
-
- // Reset the position of the chunker regexp (start from head)
- chunker.lastIndex = 0;
-
- while ( (m = chunker.exec(selector)) !== null ) {
- parts.push( m[1] );
-
- if ( m[2] ) {
- extra = RegExp.rightContext;
- break;
- }
- }
-
- if ( parts.length > 1 && origPOS.exec( selector ) ) {
- if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
- set = posProcess( parts[0] + parts[1], context );
- } else {
- set = Expr.relative[ parts[0] ] ?
- [ context ] :
- Sizzle( parts.shift(), context );
-
- while ( parts.length ) {
- selector = parts.shift();
-
- if ( Expr.relative[ selector ] )
- selector += parts.shift();
-
- set = posProcess( selector, set );
- }
- }
- } else {
- var ret = seed ?
- { expr: parts.pop(), set: makeArray(seed) } :
- Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
- set = Sizzle.filter( ret.expr, ret.set );
-
- if ( parts.length > 0 ) {
- checkSet = makeArray(set);
- } else {
- prune = false;
- }
-
- while ( parts.length ) {
- var cur = parts.pop(), pop = cur;
-
- if ( !Expr.relative[ cur ] ) {
- cur = "";
- } else {
- pop = parts.pop();
- }
-
- if ( pop == null ) {
- pop = context;
- }
-
- Expr.relative[ cur ]( checkSet, pop, isXML(context) );
- }
- }
-
- if ( !checkSet ) {
- checkSet = set;
- }
-
- if ( !checkSet ) {
- throw "Syntax error, unrecognized expression: " + (cur || selector);
- }
-
- if ( toString.call(checkSet) === "[object Array]" ) {
- if ( !prune ) {
- results.push.apply( results, checkSet );
- } else if ( context.nodeType === 1 ) {
- for ( var i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
- results.push( set[i] );
- }
- }
- } else {
- for ( var i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
- results.push( set[i] );
- }
- }
- }
- } else {
- makeArray( checkSet, results );
- }
-
- if ( extra ) {
- Sizzle( extra, context, results, seed );
-
- if ( sortOrder ) {
- hasDuplicate = false;
- results.sort(sortOrder);
-
- if ( hasDuplicate ) {
- for ( var i = 1; i < results.length; i++ ) {
- if ( results[i] === results[i-1] ) {
- results.splice(i--, 1);
- }
- }
- }
- }
- }
-
- return results;
-};
-
-Sizzle.matches = function(expr, set){
- return Sizzle(expr, null, null, set);
-};
-
-Sizzle.find = function(expr, context, isXML){
- var set, match;
-
- if ( !expr ) {
- return [];
- }
-
- for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
- var type = Expr.order[i], match;
-
- if ( (match = Expr.match[ type ].exec( expr )) ) {
- var left = RegExp.leftContext;
-
- if ( left.substr( left.length - 1 ) !== "\\" ) {
- match[1] = (match[1] || "").replace(/\\/g, "");
- set = Expr.find[ type ]( match, context, isXML );
- if ( set != null ) {
- expr = expr.replace( Expr.match[ type ], "" );
- break;
- }
- }
- }
- }
-
- if ( !set ) {
- set = context.getElementsByTagName("*");
- }
-
- return {set: set, expr: expr};
-};
-
-Sizzle.filter = function(expr, set, inplace, not){
- var old = expr, result = [], curLoop = set, match, anyFound,
- isXMLFilter = set && set[0] && isXML(set[0]);
-
- while ( expr && set.length ) {
- for ( var type in Expr.filter ) {
- if ( (match = Expr.match[ type ].exec( expr )) != null ) {
- var filter = Expr.filter[ type ], found, item;
- anyFound = false;
-
- if ( curLoop == result ) {
- result = [];
- }
-
- if ( Expr.preFilter[ type ] ) {
- match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
- if ( !match ) {
- anyFound = found = true;
- } else if ( match === true ) {
- continue;
- }
- }
-
- if ( match ) {
- for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
- if ( item ) {
- found = filter( item, match, i, curLoop );
- var pass = not ^ !!found;
-
- if ( inplace && found != null ) {
- if ( pass ) {
- anyFound = true;
- } else {
- curLoop[i] = false;
- }
- } else if ( pass ) {
- result.push( item );
- anyFound = true;
- }
- }
- }
- }
-
- if ( found !== undefined ) {
- if ( !inplace ) {
- curLoop = result;
- }
-
- expr = expr.replace( Expr.match[ type ], "" );
-
- if ( !anyFound ) {
- return [];
- }
-
- break;
- }
- }
- }
-
- // Improper expression
- if ( expr == old ) {
- if ( anyFound == null ) {
- throw "Syntax error, unrecognized expression: " + expr;
- } else {
- break;
- }
- }
-
- old = expr;
- }
-
- return curLoop;
-};
-
-var Expr = Sizzle.selectors = {
- order: [ "ID", "NAME", "TAG" ],
- match: {
- ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
- CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
- NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
- ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
- TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
- CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
- POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
- PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
- },
- attrMap: {
- "class": "className",
- "for": "htmlFor"
- },
- attrHandle: {
- href: function(elem){
- return elem.getAttribute("href");
- }
- },
- relative: {
- "+": function(checkSet, part, isXML){
- var isPartStr = typeof part === "string",
- isTag = isPartStr && !/\W/.test(part),
- isPartStrNotTag = isPartStr && !isTag;
-
- if ( isTag && !isXML ) {
- part = part.toUpperCase();
- }
-
- for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
- if ( (elem = checkSet[i]) ) {
- while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
- checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
- elem || false :
- elem === part;
- }
- }
-
- if ( isPartStrNotTag ) {
- Sizzle.filter( part, checkSet, true );
- }
- },
- ">": function(checkSet, part, isXML){
- var isPartStr = typeof part === "string";
-
- if ( isPartStr && !/\W/.test(part) ) {
- part = isXML ? part : part.toUpperCase();
-
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- var parent = elem.parentNode;
- checkSet[i] = parent.nodeName === part ? parent : false;
- }
- }
- } else {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- checkSet[i] = isPartStr ?
- elem.parentNode :
- elem.parentNode === part;
- }
- }
-
- if ( isPartStr ) {
- Sizzle.filter( part, checkSet, true );
- }
- }
- },
- "": function(checkSet, part, isXML){
- var doneName = done++, checkFn = dirCheck;
-
- if ( !part.match(/\W/) ) {
- var nodeCheck = part = isXML ? part : part.toUpperCase();
- checkFn = dirNodeCheck;
- }
-
- checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
- },
- "~": function(checkSet, part, isXML){
- var doneName = done++, checkFn = dirCheck;
-
- if ( typeof part === "string" && !part.match(/\W/) ) {
- var nodeCheck = part = isXML ? part : part.toUpperCase();
- checkFn = dirNodeCheck;
- }
-
- checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
- }
- },
- find: {
- ID: function(match, context, isXML){
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- return m ? [m] : [];
- }
- },
- NAME: function(match, context, isXML){
- if ( typeof context.getElementsByName !== "undefined" ) {
- var ret = [], results = context.getElementsByName(match[1]);
-
- for ( var i = 0, l = results.length; i < l; i++ ) {
- if ( results[i].getAttribute("name") === match[1] ) {
- ret.push( results[i] );
- }
- }
-
- return ret.length === 0 ? null : ret;
- }
- },
- TAG: function(match, context){
- return context.getElementsByTagName(match[1]);
- }
- },
- preFilter: {
- CLASS: function(match, curLoop, inplace, result, not, isXML){
- match = " " + match[1].replace(/\\/g, "") + " ";
-
- if ( isXML ) {
- return match;
- }
-
- for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
- if ( elem ) {
- if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
- if ( !inplace )
- result.push( elem );
- } else if ( inplace ) {
- curLoop[i] = false;
- }
- }
- }
-
- return false;
- },
- ID: function(match){
- return match[1].replace(/\\/g, "");
- },
- TAG: function(match, curLoop){
- for ( var i = 0; curLoop[i] === false; i++ ){}
- return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
- },
- CHILD: function(match){
- if ( match[1] == "nth" ) {
- // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
- var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
- match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
- !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
- // calculate the numbers (first)n+(last) including if they are negative
- match[2] = (test[1] + (test[2] || 1)) - 0;
- match[3] = test[3] - 0;
- }
-
- // TODO: Move to normal caching system
- match[0] = done++;
-
- return match;
- },
- ATTR: function(match, curLoop, inplace, result, not, isXML){
- var name = match[1].replace(/\\/g, "");
-
- if ( !isXML && Expr.attrMap[name] ) {
- match[1] = Expr.attrMap[name];
- }
-
- if ( match[2] === "~=" ) {
- match[4] = " " + match[4] + " ";
- }
-
- return match;
- },
- PSEUDO: function(match, curLoop, inplace, result, not){
- if ( match[1] === "not" ) {
- // If we're dealing with a complex expression, or a simple one
- if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
- match[3] = Sizzle(match[3], null, null, curLoop);
- } else {
- var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
- if ( !inplace ) {
- result.push.apply( result, ret );
- }
- return false;
- }
- } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
- return true;
- }
-
- return match;
- },
- POS: function(match){
- match.unshift( true );
- return match;
- }
- },
- filters: {
- enabled: function(elem){
- return elem.disabled === false && elem.type !== "hidden";
- },
- disabled: function(elem){
- return elem.disabled === true;
- },
- checked: function(elem){
- return elem.checked === true;
- },
- selected: function(elem){
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- elem.parentNode.selectedIndex;
- return elem.selected === true;
- },
- parent: function(elem){
- return !!elem.firstChild;
- },
- empty: function(elem){
- return !elem.firstChild;
- },
- has: function(elem, i, match){
- return !!Sizzle( match[3], elem ).length;
- },
- header: function(elem){
- return /h\d/i.test( elem.nodeName );
- },
- text: function(elem){
- return "text" === elem.type;
- },
- radio: function(elem){
- return "radio" === elem.type;
- },
- checkbox: function(elem){
- return "checkbox" === elem.type;
- },
- file: function(elem){
- return "file" === elem.type;
- },
- password: function(elem){
- return "password" === elem.type;
- },
- submit: function(elem){
- return "submit" === elem.type;
- },
- image: function(elem){
- return "image" === elem.type;
- },
- reset: function(elem){
- return "reset" === elem.type;
- },
- button: function(elem){
- return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
- },
- input: function(elem){
- return /input|select|textarea|button/i.test(elem.nodeName);
- }
- },
- setFilters: {
- first: function(elem, i){
- return i === 0;
- },
- last: function(elem, i, match, array){
- return i === array.length - 1;
- },
- even: function(elem, i){
- return i % 2 === 0;
- },
- odd: function(elem, i){
- return i % 2 === 1;
- },
- lt: function(elem, i, match){
- return i < match[3] - 0;
- },
- gt: function(elem, i, match){
- return i > match[3] - 0;
- },
- nth: function(elem, i, match){
- return match[3] - 0 == i;
- },
- eq: function(elem, i, match){
- return match[3] - 0 == i;
- }
- },
- filter: {
- PSEUDO: function(elem, match, i, array){
- var name = match[1], filter = Expr.filters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- } else if ( name === "contains" ) {
- return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
- } else if ( name === "not" ) {
- var not = match[3];
-
- for ( var i = 0, l = not.length; i < l; i++ ) {
- if ( not[i] === elem ) {
- return false;
- }
- }
-
- return true;
- }
- },
- CHILD: function(elem, match){
- var type = match[1], node = elem;
- switch (type) {
- case 'only':
- case 'first':
- while (node = node.previousSibling) {
- if ( node.nodeType === 1 ) return false;
- }
- if ( type == 'first') return true;
- node = elem;
- case 'last':
- while (node = node.nextSibling) {
- if ( node.nodeType === 1 ) return false;
- }
- return true;
- case 'nth':
- var first = match[2], last = match[3];
-
- if ( first == 1 && last == 0 ) {
- return true;
- }
-
- var doneName = match[0],
- parent = elem.parentNode;
-
- if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
- var count = 0;
- for ( node = parent.firstChild; node; node = node.nextSibling ) {
- if ( node.nodeType === 1 ) {
- node.nodeIndex = ++count;
- }
- }
- parent.sizcache = doneName;
- }
-
- var diff = elem.nodeIndex - last;
- if ( first == 0 ) {
- return diff == 0;
- } else {
- return ( diff % first == 0 && diff / first >= 0 );
- }
- }
- },
- ID: function(elem, match){
- return elem.nodeType === 1 && elem.getAttribute("id") === match;
- },
- TAG: function(elem, match){
- return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
- },
- CLASS: function(elem, match){
- return (" " + (elem.className || elem.getAttribute("class")) + " ")
- .indexOf( match ) > -1;
- },
- ATTR: function(elem, match){
- var name = match[1],
- result = Expr.attrHandle[ name ] ?
- Expr.attrHandle[ name ]( elem ) :
- elem[ name ] != null ?
- elem[ name ] :
- elem.getAttribute( name ),
- value = result + "",
- type = match[2],
- check = match[4];
-
- return result == null ?
- type === "!=" :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value != check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0, check.length + 1) === check + "-" :
- false;
- },
- POS: function(elem, match, i, array){
- var name = match[2], filter = Expr.setFilters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- }
- }
- }
-};
-
-var origPOS = Expr.match.POS;
-
-for ( var type in Expr.match ) {
- Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
-}
-
-var makeArray = function(array, results) {
- array = Array.prototype.slice.call( array );
-
- if ( results ) {
- results.push.apply( results, array );
- return results;
- }
-
- return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-try {
- Array.prototype.slice.call( document.documentElement.childNodes );
-
-// Provide a fallback method if it does not work
-} catch(e){
- makeArray = function(array, results) {
- var ret = results || [];
-
- if ( toString.call(array) === "[object Array]" ) {
- Array.prototype.push.apply( ret, array );
- } else {
- if ( typeof array.length === "number" ) {
- for ( var i = 0, l = array.length; i < l; i++ ) {
- ret.push( array[i] );
- }
- } else {
- for ( var i = 0; array[i]; i++ ) {
- ret.push( array[i] );
- }
- }
- }
-
- return ret;
- };
-}
-
-var sortOrder;
-
-if ( document.documentElement.compareDocumentPosition ) {
- sortOrder = function( a, b ) {
- var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-} else if ( "sourceIndex" in document.documentElement ) {
- sortOrder = function( a, b ) {
- var ret = a.sourceIndex - b.sourceIndex;
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-} else if ( document.createRange ) {
- sortOrder = function( a, b ) {
- var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
- aRange.selectNode(a);
- aRange.collapse(true);
- bRange.selectNode(b);
- bRange.collapse(true);
- var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
- if ( ret === 0 ) {
- hasDuplicate = true;
- }
- return ret;
- };
-}
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
- // We're going to inject a fake input element with a specified name
- var form = document.createElement("form"),
- id = "script" + (new Date).getTime();
- form.innerHTML = "<input name='" + id + "'/>";
-
- // Inject it into the root element, check its status, and remove it quickly
- var root = document.documentElement;
- root.insertBefore( form, root.firstChild );
-
- // The workaround has to do additional checks after a getElementById
- // Which slows things down for other browsers (hence the branching)
- if ( !!document.getElementById( id ) ) {
- Expr.find.ID = function(match, context, isXML){
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
- }
- };
-
- Expr.filter.ID = function(elem, match){
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
- return elem.nodeType === 1 && node && node.nodeValue === match;
- };
- }
-
- root.removeChild( form );
-})();
-
-(function(){
- // Check to see if the browser returns only elements
- // when doing getElementsByTagName("*")
-
- // Create a fake element
- var div = document.createElement("div");
- div.appendChild( document.createComment("") );
-
- // Make sure no comments are found
- if ( div.getElementsByTagName("*").length > 0 ) {
- Expr.find.TAG = function(match, context){
- var results = context.getElementsByTagName(match[1]);
-
- // Filter out possible comments
- if ( match[1] === "*" ) {
- var tmp = [];
-
- for ( var i = 0; results[i]; i++ ) {
- if ( results[i].nodeType === 1 ) {
- tmp.push( results[i] );
- }
- }
-
- results = tmp;
- }
-
- return results;
- };
- }
-
- // Check to see if an attribute returns normalized href attributes
- div.innerHTML = "<a href='#'></a>";
- if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
- div.firstChild.getAttribute("href") !== "#" ) {
- Expr.attrHandle.href = function(elem){
- return elem.getAttribute("href", 2);
- };
- }
-})();
-
-if ( document.querySelectorAll ) (function(){
- var oldSizzle = Sizzle, div = document.createElement("div");
- div.innerHTML = "<p class='TEST'></p>";
-
- // Safari can't handle uppercase or unicode characters when
- // in quirks mode.
- if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
- return;
- }
-
- Sizzle = function(query, context, extra, seed){
- context = context || document;
-
- // Only use querySelectorAll on non-XML documents
- // (ID selectors don't work in non-HTML documents)
- if ( !seed && context.nodeType === 9 && !isXML(context) ) {
- try {
- return makeArray( context.querySelectorAll(query), extra );
- } catch(e){}
- }
-
- return oldSizzle(query, context, extra, seed);
- };
-
- Sizzle.find = oldSizzle.find;
- Sizzle.filter = oldSizzle.filter;
- Sizzle.selectors = oldSizzle.selectors;
- Sizzle.matches = oldSizzle.matches;
-})();
-
-if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
- var div = document.createElement("div");
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
- // Opera can't find a second classname (in 9.6)
- if ( div.getElementsByClassName("e").length === 0 )
- return;
-
- // Safari caches class attributes, doesn't catch changes (in 3.2)
- div.lastChild.className = "e";
-
- if ( div.getElementsByClassName("e").length === 1 )
- return;
-
- Expr.order.splice(1, 0, "CLASS");
- Expr.find.CLASS = function(match, context, isXML) {
- if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
- return context.getElementsByClassName(match[1]);
- }
- };
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- var sibDir = dir == "previousSibling" && !isXML;
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- if ( sibDir && elem.nodeType === 1 ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- elem = elem[dir];
- var match = false;
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 && !isXML ){
- elem.sizcache = doneName;
- elem.sizset = i;
- }
-
- if ( elem.nodeName === cur ) {
- match = elem;
- break;
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- var sibDir = dir == "previousSibling" && !isXML;
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
- if ( elem ) {
- if ( sibDir && elem.nodeType === 1 ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- elem = elem[dir];
- var match = false;
-
- while ( elem ) {
- if ( elem.sizcache === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 ) {
- if ( !isXML ) {
- elem.sizcache = doneName;
- elem.sizset = i;
- }
- if ( typeof cur !== "string" ) {
- if ( elem === cur ) {
- match = true;
- break;
- }
-
- } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
- match = elem;
- break;
- }
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-var contains = document.compareDocumentPosition ? function(a, b){
- return a.compareDocumentPosition(b) & 16;
-} : function(a, b){
- return a !== b && (a.contains ? a.contains(b) : true);
-};
-
-var isXML = function(elem){
- return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
- !!elem.ownerDocument && isXML( elem.ownerDocument );
-};
-
-var posProcess = function(selector, context){
- var tmpSet = [], later = "", match,
- root = context.nodeType ? [context] : context;
-
- // Position selectors must be done after the filter
- // And so must :not(positional) so we move all PSEUDOs to the end
- while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
- later += match[0];
- selector = selector.replace( Expr.match.PSEUDO, "" );
- }
-
- selector = Expr.relative[selector] ? selector + "*" : selector;
-
- for ( var i = 0, l = root.length; i < l; i++ ) {
- Sizzle( selector, root[i], tmpSet );
- }
-
- return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-jQuery.find = Sizzle;
-jQuery.filter = Sizzle.filter;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.filters;
-
-Sizzle.selectors.filters.hidden = function(elem){
- return elem.offsetWidth === 0 || elem.offsetHeight === 0;
-};
-
-Sizzle.selectors.filters.visible = function(elem){
- return elem.offsetWidth > 0 || elem.offsetHeight > 0;
-};
-
-Sizzle.selectors.filters.animated = function(elem){
- return jQuery.grep(jQuery.timers, function(fn){
- return elem === fn.elem;
- }).length;
-};
-
-jQuery.multiFilter = function( expr, elems, not ) {
- if ( not ) {
- expr = ":not(" + expr + ")";
- }
-
- return Sizzle.matches(expr, elems);
-};
-
-jQuery.dir = function( elem, dir ){
- var matched = [], cur = elem[dir];
- while ( cur && cur != document ) {
- if ( cur.nodeType == 1 )
- matched.push( cur );
- cur = cur[dir];
- }
- return matched;
-};
-
-jQuery.nth = function(cur, result, dir, elem){
- result = result || 1;
- var num = 0;
-
- for ( ; cur; cur = cur[dir] )
- if ( cur.nodeType == 1 && ++num == result )
- break;
-
- return cur;
-};
-
-jQuery.sibling = function(n, elem){
- var r = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType == 1 && n != elem )
- r.push( n );
- }
-
- return r;
-};
-
-return;
-
-window.Sizzle = Sizzle;
-
-})();
-/*
- * A number of helper functions used for managing events.
- * Many of the ideas behind this code originated from
- * Dean Edwards' addEvent library.
- */
-jQuery.event = {
-
- // Bind an event to an element
- // Original by Dean Edwards
- add: function(elem, types, handler, data) {
- if ( elem.nodeType == 3 || elem.nodeType == 8 )
- return;
-
- // For whatever reason, IE has trouble passing the window object
- // around, causing it to be cloned in the process
- if ( elem.setInterval && elem != window )
- elem = window;
-
- // Make sure that the function being executed has a unique ID
- if ( !handler.guid )
- handler.guid = this.guid++;
-
- // if data is passed, bind to handler
- if ( data !== undefined ) {
- // Create temporary function pointer to original handler
- var fn = handler;
-
- // Create unique handler function, wrapped around original handler
- handler = this.proxy( fn );
-
- // Store data in unique handler
- handler.data = data;
- }
-
- // Init the element's event structure
- var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
- handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
- // Handle the second event of a trigger and when
- // an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
- jQuery.event.handle.apply(arguments.callee.elem, arguments) :
- undefined;
- });
- // Add elem as a property of the handle function
- // This is to prevent a memory leak with non-native
- // event in IE.
- handle.elem = elem;
-
- // Handle multiple events separated by a space
- // jQuery(...).bind("mouseover mouseout", fn);
- jQuery.each(types.split(/\s+/), function(index, type) {
- // Namespaced event handlers
- var namespaces = type.split(".");
- type = namespaces.shift();
- handler.type = namespaces.slice().sort().join(".");
-
- // Get the current list of functions bound to this event
- var handlers = events[type];
-
- if ( jQuery.event.specialAll[type] )
- jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
-
- // Init the event handler queue
- if (!handlers) {
- handlers = events[type] = {};
-
- // Check for a special event handler
- // Only use addEventListener/attachEvent if the special
- // events handler returns false
- if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
- // Bind the global event handler to the element
- if (elem.addEventListener)
- elem.addEventListener(type, handle, false);
- else if (elem.attachEvent)
- elem.attachEvent("on" + type, handle);
- }
- }
-
- // Add the function to the element's handler list
- handlers[handler.guid] = handler;
-
- // Keep track of which events have been used, for global triggering
- jQuery.event.global[type] = true;
- });
-
- // Nullify elem to prevent memory leaks in IE
- elem = null;
- },
-
- guid: 1,
- global: {},
-
- // Detach an event or set of events from an element
- remove: function(elem, types, handler) {
- // don't do events on text and comment nodes
- if ( elem.nodeType == 3 || elem.nodeType == 8 )
- return;
-
- var events = jQuery.data(elem, "events"), ret, index;
-
- if ( events ) {
- // Unbind all events for the element
- if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
- for ( var type in events )
- this.remove( elem, type + (types || "") );
- else {
- // types is actually an event object here
- if ( types.type ) {
- handler = types.handler;
- types = types.type;
- }
-
- // Handle multiple events seperated by a space
- // jQuery(...).unbind("mouseover mouseout", fn);
- jQuery.each(types.split(/\s+/), function(index, type){
- // Namespaced event handlers
- var namespaces = type.split(".");
- type = namespaces.shift();
- var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
-
- if ( events[type] ) {
- // remove the given handler for the given type
- if ( handler )
- delete events[type][handler.guid];
-
- // remove all handlers for the given type
- else
- for ( var handle in events[type] )
- // Handle the removal of namespaced events
- if ( namespace.test(events[type][handle].type) )
- delete events[type][handle];
-
- if ( jQuery.event.specialAll[type] )
- jQuery.event.specialAll[type].teardown.call(elem, namespaces);
-
- // remove generic event handler if no more handlers exist
- for ( ret in events[type] ) break;
- if ( !ret ) {
- if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
- if (elem.removeEventListener)
- elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
- else if (elem.detachEvent)
- elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
- }
- ret = null;
- delete events[type];
- }
- }
- });
- }
-
- // Remove the expando if it's no longer used
- for ( ret in events ) break;
- if ( !ret ) {
- var handle = jQuery.data( elem, "handle" );
- if ( handle ) handle.elem = null;
- jQuery.removeData( elem, "events" );
- jQuery.removeData( elem, "handle" );
- }
- }
- },
-
- // bubbling is internal
- trigger: function( event, data, elem, bubbling ) {
- // Event object or event type
- var type = event.type || event;
-
- if( !bubbling ){
- event = typeof event === "object" ?
- // jQuery.Event object
- event[expando] ? event :
- // Object literal
- jQuery.extend( jQuery.Event(type), event ) :
- // Just the event type (string)
- jQuery.Event(type);
-
- if ( type.indexOf("!") >= 0 ) {
- event.type = type = type.slice(0, -1);
- event.exclusive = true;
- }
-
- // Handle a global trigger
- if ( !elem ) {
- // Don't bubble custom events when global (to avoid too much overhead)
- event.stopPropagation();
- // Only trigger if we've ever bound an event for it
- if ( this.global[type] )
- jQuery.each( jQuery.cache, function(){
- if ( this.events && this.events[type] )
- jQuery.event.trigger( event, data, this.handle.elem );
- });
- }
-
- // Handle triggering a single element
-
- // don't do events on text and comment nodes
- if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
- return undefined;
-
- // Clean up in case it is reused
- event.result = undefined;
- event.target = elem;
-
- // Clone the incoming data, if any
- data = jQuery.makeArray(data);
- data.unshift( event );
- }
-
- event.currentTarget = elem;
-
- // Trigger the event, it is assumed that "handle" is a function
- var handle = jQuery.data(elem, "handle");
- if ( handle )
- handle.apply( elem, data );
-
- // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
- if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
- event.result = false;
-
- // Trigger the native events (except for clicks on links)
- if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
- this.triggered = true;
- try {
- elem[ type ]();
- // prevent IE from throwing an error for some hidden elements
- } catch (e) {}
- }
-
- this.triggered = false;
-
- if ( !event.isPropagationStopped() ) {
- var parent = elem.parentNode || elem.ownerDocument;
- if ( parent )
- jQuery.event.trigger(event, data, parent, true);
- }
- },
-
- handle: function(event) {
- // returned undefined or false
- var all, handlers;
-
- event = arguments[0] = jQuery.event.fix( event || window.event );
- event.currentTarget = this;
-
- // Namespaced event handlers
- var namespaces = event.type.split(".");
- event.type = namespaces.shift();
-
- // Cache this now, all = true means, any handler
- all = !namespaces.length && !event.exclusive;
-
- var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
-
- handlers = ( jQuery.data(this, "events") || {} )[event.type];
-
- for ( var j in handlers ) {
- var handler = handlers[j];
-
- // Filter the functions by class
- if ( all || namespace.test(handler.type) ) {
- // Pass in a reference to the handler function itself
- // So that we can later remove it
- event.handler = handler;
- event.data = handler.data;
-
- var ret = handler.apply(this, arguments);
-
- if( ret !== undefined ){
- event.result = ret;
- if ( ret === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
-
- if( event.isImmediatePropagationStopped() )
- break;
-
- }
- }
- },
-
- props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
-
- fix: function(event) {
- if ( event[expando] )
- return event;
-
- // store a copy of the original event object
- // and "clone" to set read-only properties
- var originalEvent = event;
- event = jQuery.Event( originalEvent );
-
- for ( var i = this.props.length, prop; i; ){
- prop = this.props[ --i ];
- event[ prop ] = originalEvent[ prop ];
- }
-
- // Fix target property, if necessary
- if ( !event.target )
- event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
-
- // check if target is a textnode (safari)
- if ( event.target.nodeType == 3 )
- event.target = event.target.parentNode;
-
- // Add relatedTarget, if necessary
- if ( !event.relatedTarget && event.fromElement )
- event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
-
- // Calculate pageX/Y if missing and clientX/Y available
- if ( event.pageX == null && event.clientX != null ) {
- var doc = document.documentElement, body = document.body;
- event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
- event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
- }
-
- // Add which for key events
- if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
- event.which = event.charCode || event.keyCode;
-
- // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
- if ( !event.metaKey && event.ctrlKey )
- event.metaKey = event.ctrlKey;
-
- // Add which for click: 1 == left; 2 == middle; 3 == right
- // Note: button is not normalized, so don't use it
- if ( !event.which && event.button )
- event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
-
- return event;
- },
-
- proxy: function( fn, proxy ){
- proxy = proxy || function(){ return fn.apply(this, arguments); };
- // Set the guid of unique handler to the same of original handler, so it can be removed
- proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
- // So proxy can be declared as an argument
- return proxy;
- },
-
- special: {
- ready: {
- // Make sure the ready event is setup
- setup: bindReady,
- teardown: function() {}
- }
- },
-
- specialAll: {
- live: {
- setup: function( selector, namespaces ){
- jQuery.event.add( this, namespaces[0], liveHandler );
- },
- teardown: function( namespaces ){
- if ( namespaces.length ) {
- var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
-
- jQuery.each( (jQuery.data(this, "events").live || {}), function(){
- if ( name.test(this.type) )
- remove++;
- });
-
- if ( remove < 1 )
- jQuery.event.remove( this, namespaces[0], liveHandler );
- }
- }
- }
- }
-};
-
-jQuery.Event = function( src ){
- // Allow instantiation without the 'new' keyword
- if( !this.preventDefault )
- return new jQuery.Event(src);
-
- // Event object
- if( src && src.type ){
- this.originalEvent = src;
- this.type = src.type;
- // Event type
- }else
- this.type = src;
-
- // timeStamp is buggy for some events on Firefox(#3843)
- // So we won't rely on the native value
- this.timeStamp = now();
-
- // Mark it as fixed
- this[expando] = true;
-};
-
-function returnFalse(){
- return false;
-}
-function returnTrue(){
- return true;
-}
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- preventDefault: function() {
- this.isDefaultPrevented = returnTrue;
-
- var e = this.originalEvent;
- if( !e )
- return;
- // if preventDefault exists run it on the original event
- if (e.preventDefault)
- e.preventDefault();
- // otherwise set the returnValue property of the original event to false (IE)
- e.returnValue = false;
- },
- stopPropagation: function() {
- this.isPropagationStopped = returnTrue;
-
- var e = this.originalEvent;
- if( !e )
- return;
- // if stopPropagation exists run it on the original event
- if (e.stopPropagation)
- e.stopPropagation();
- // otherwise set the cancelBubble property of the original event to true (IE)
- e.cancelBubble = true;
- },
- stopImmediatePropagation:function(){
- this.isImmediatePropagationStopped = returnTrue;
- this.stopPropagation();
- },
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse
-};
-// Checks if an event happened on an element within another element
-// Used in jQuery.event.special.mouseenter and mouseleave handlers
-var withinElement = function(event) {
- // Check if mouse(over|out) are still within the same parent element
- var parent = event.relatedTarget;
- // Traverse up the tree
- while ( parent && parent != this )
- try { parent = parent.parentNode; }
- catch(e) { parent = this; }
-
- if( parent != this ){
- // set the correct event type
- event.type = event.data;
- // handle event if we actually just moused on to a non sub-element
- jQuery.event.handle.apply( this, arguments );
- }
-};
-
-jQuery.each({
- mouseover: 'mouseenter',
- mouseout: 'mouseleave'
-}, function( orig, fix ){
- jQuery.event.special[ fix ] = {
- setup: function(){
- jQuery.event.add( this, orig, withinElement, fix );
- },
- teardown: function(){
- jQuery.event.remove( this, orig, withinElement );
- }
- };
-});
-
-jQuery.fn.extend({
- bind: function( type, data, fn ) {
- return type == "unload" ? this.one(type, data, fn) : this.each(function(){
- jQuery.event.add( this, type, fn || data, fn && data );
- });
- },
-
- one: function( type, data, fn ) {
- var one = jQuery.event.proxy( fn || data, function(event) {
- jQuery(this).unbind(event, one);
- return (fn || data).apply( this, arguments );
- });
- return this.each(function(){
- jQuery.event.add( this, type, one, fn && data);
- });
- },
-
- unbind: function( type, fn ) {
- return this.each(function(){
- jQuery.event.remove( this, type, fn );
- });
- },
-
- trigger: function( type, data ) {
- return this.each(function(){
- jQuery.event.trigger( type, data, this );
- });
- },
-
- triggerHandler: function( type, data ) {
- if( this[0] ){
- var event = jQuery.Event(type);
- event.preventDefault();
- event.stopPropagation();
- jQuery.event.trigger( event, data, this[0] );
- return event.result;
- }
- },
-
- toggle: function( fn ) {
- // Save reference to arguments for access in closure
- var args = arguments, i = 1;
-
- // link all the functions, so any of them can unbind this click handler
- while( i < args.length )
- jQuery.event.proxy( fn, args[i++] );
-
- return this.click( jQuery.event.proxy( fn, function(event) {
- // Figure out which function to execute
- this.lastToggle = ( this.lastToggle || 0 ) % i;
-
- // Make sure that clicks stop
- event.preventDefault();
-
- // and execute the function
- return args[ this.lastToggle++ ].apply( this, arguments ) || false;
- }));
- },
-
- hover: function(fnOver, fnOut) {
- return this.mouseenter(fnOver).mouseleave(fnOut);
- },
-
- ready: function(fn) {
- // Attach the listeners
- bindReady();
-
- // If the DOM is already ready
- if ( jQuery.isReady )
- // Execute the function immediately
- fn.call( document, jQuery );
-
- // Otherwise, remember the function for later
- else
- // Add the function to the wait list
- jQuery.readyList.push( fn );
-
- return this;
- },
-
- live: function( type, fn ){
- var proxy = jQuery.event.proxy( fn );
- proxy.guid += this.selector + type;
-
- jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
-
- return this;
- },
-
- die: function( type, fn ){
- jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
- return this;
- }
-});
-
-function liveHandler( event ){
- var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
- stop = true,
- elems = [];
-
- jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
- if ( check.test(fn.type) ) {
- var elem = jQuery(event.target).closest(fn.data)[0];
- if ( elem )
- elems.push({ elem: elem, fn: fn });
- }
- });
-
- elems.sort(function(a,b) {
- return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
- });
-
- jQuery.each(elems, function(){
- if ( this.fn.call(this.elem, event, this.fn.data) === false )
- return (stop = false);
- });
-
- return stop;
-}
-
-function liveConvert(type, selector){
- return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
-}
-
-jQuery.extend({
- isReady: false,
- readyList: [],
- // Handle when the DOM is ready
- ready: function() {
- // Make sure that the DOM is not already loaded
- if ( !jQuery.isReady ) {
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If there are functions bound, to execute
- if ( jQuery.readyList ) {
- // Execute all of them
- jQuery.each( jQuery.readyList, function(){
- this.call( document, jQuery );
- });
-
- // Reset the list of functions
- jQuery.readyList = null;
- }
-
- // Trigger any bound ready events
- jQuery(document).triggerHandler("ready");
- }
- }
-});
-
-var readyBound = false;
-
-function bindReady(){
- if ( readyBound ) return;
- readyBound = true;
-
- // Mozilla, Opera and webkit nightlies currently support this event
- if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", function(){
- document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
- jQuery.ready();
- }, false );
-
- // If IE event model is used
- } else if ( document.attachEvent ) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- document.attachEvent("onreadystatechange", function(){
- if ( document.readyState === "complete" ) {
- document.detachEvent( "onreadystatechange", arguments.callee );
- jQuery.ready();
- }
- });
-
- // If IE and not an iframe
- // continually check to see if the document is ready
- if ( document.documentElement.doScroll && window == window.top ) (function(){
- if ( jQuery.isReady ) return;
-
- try {
- // If IE is used, use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- document.documentElement.doScroll("left");
- } catch( error ) {
- setTimeout( arguments.callee, 0 );
- return;
- }
-
- // and execute any waiting functions
- jQuery.ready();
- })();
- }
-
- // A fallback to window.onload, that will always work
- jQuery.event.add( window, "load", jQuery.ready );
-}
-
-jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
- "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
- "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
-
- // Handle event binding
- jQuery.fn[name] = function(fn){
- return fn ? this.bind(name, fn) : this.trigger(name);
- };
-});
-
-// Prevent memory leaks in IE
-// And prevent errors on refresh with events like mouseover in other browsers
-// Window isn't included so as not to unbind existing unload events
-jQuery( window ).bind( 'unload', function(){
- for ( var id in jQuery.cache )
- // Skip the window
- if ( id != 1 && jQuery.cache[ id ].handle )
- jQuery.event.remove( jQuery.cache[ id ].handle.elem );
-});
-(function(){
-
- jQuery.support = {};
-
- var root = document.documentElement,
- script = document.createElement("script"),
- div = document.createElement("div"),
- id = "script" + (new Date).getTime();
-
- div.style.display = "none";
- div.innerHTML = ' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';
-
- var all = div.getElementsByTagName("*"),
- a = div.getElementsByTagName("a")[0];
-
- // Can't get basic test support
- if ( !all || !all.length || !a ) {
- return;
- }
-
- jQuery.support = {
- // IE strips leading whitespace when .innerHTML is used
- leadingWhitespace: div.firstChild.nodeType == 3,
-
- // Make sure that tbody elements aren't automatically inserted
- // IE will insert them into empty tables
- tbody: !div.getElementsByTagName("tbody").length,
-
- // Make sure that you can get all elements in an <object> element
- // IE 7 always returns no results
- objectAll: !!div.getElementsByTagName("object")[0]
- .getElementsByTagName("*").length,
-
- // Make sure that link elements get serialized correctly by innerHTML
- // This requires a wrapper element in IE
- htmlSerialize: !!div.getElementsByTagName("link").length,
-
- // Get the style information from getAttribute
- // (IE uses .cssText insted)
- style: /red/.test( a.getAttribute("style") ),
-
- // Make sure that URLs aren't manipulated
- // (IE normalizes it by default)
- hrefNormalized: a.getAttribute("href") === "/a",
-
- // Make sure that element opacity exists
- // (IE uses filter instead)
- opacity: a.style.opacity === "0.5",
-
- // Verify style float existence
- // (IE uses styleFloat instead of cssFloat)
- cssFloat: !!a.style.cssFloat,
-
- // Will be defined later
- scriptEval: false,
- noCloneEvent: true,
- boxModel: null
- };
-
- script.type = "text/javascript";
- try {
- script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
- } catch(e){}
-
- root.insertBefore( script, root.firstChild );
-
- // Make sure that the execution of code works by injecting a script
- // tag with appendChild/createTextNode
- // (IE doesn't support this, fails, and uses .text instead)
- if ( window[ id ] ) {
- jQuery.support.scriptEval = true;
- delete window[ id ];
- }
-
- root.removeChild( script );
-
- if ( div.attachEvent && div.fireEvent ) {
- div.attachEvent("onclick", function(){
- // Cloning a node shouldn't copy over any
- // bound event handlers (IE does this)
- jQuery.support.noCloneEvent = false;
- div.detachEvent("onclick", arguments.callee);
- });
- div.cloneNode(true).fireEvent("onclick");
- }
-
- // Figure out if the W3C box model works as expected
- // document.body must exist before we can do this
- jQuery(function(){
- var div = document.createElement("div");
- div.style.width = div.style.paddingLeft = "1px";
-
- document.body.appendChild( div );
- jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
- document.body.removeChild( div ).style.display = 'none';
- });
-})();
-
-var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";
-
-jQuery.props = {
- "for": "htmlFor",
- "class": "className",
- "float": styleFloat,
- cssFloat: styleFloat,
- styleFloat: styleFloat,
- readonly: "readOnly",
- maxlength: "maxLength",
- cellspacing: "cellSpacing",
- rowspan: "rowSpan",
- tabindex: "tabIndex"
-};
-jQuery.fn.extend({
- // Keep a copy of the old load
- _load: jQuery.fn.load,
-
- load: function( url, params, callback ) {
- if ( typeof url !== "string" )
- return this._load( url );
-
- var off = url.indexOf(" ");
- if ( off >= 0 ) {
- var selector = url.slice(off, url.length);
- url = url.slice(0, off);
- }
-
- // Default to a GET request
- var type = "GET";
-
- // If the second parameter was provided
- if ( params )
- // If it's a function
- if ( jQuery.isFunction( params ) ) {
- // We assume that it's the callback
- callback = params;
- params = null;
-
- // Otherwise, build a param string
- } else if( typeof params === "object" ) {
- params = jQuery.param( params );
- type = "POST";
- }
-
- var self = this;
-
- // Request the remote document
- jQuery.ajax({
- url: url,
- type: type,
- dataType: "html",
- data: params,
- complete: function(res, status){
- // If successful, inject the HTML into all the matched elements
- if ( status == "success" || status == "notmodified" )
- // See if a selector was specified
- self.html( selector ?
- // Create a dummy div to hold the results
- jQuery("<div/>")
- // inject the contents of the document in, removing the scripts
- // to avoid any 'Permission Denied' errors in IE
- .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
-
- // Locate the specified elements
- .find(selector) :
-
- // If not, just inject the full result
- res.responseText );
-
- if( callback )
- self.each( callback, [res.responseText, status, res] );
- }
- });
- return this;
- },
-
- serialize: function() {
- return jQuery.param(this.serializeArray());
- },
- serializeArray: function() {
- return this.map(function(){
- return this.elements ? jQuery.makeArray(this.elements) : this;
- })
- .filter(function(){
- return this.name && !this.disabled &&
- (this.checked || /select|textarea/i.test(this.nodeName) ||
- /text|hidden|password|search/i.test(this.type));
- })
- .map(function(i, elem){
- var val = jQuery(this).val();
- return val == null ? null :
- jQuery.isArray(val) ?
- jQuery.map( val, function(val, i){
- return {name: elem.name, value: val};
- }) :
- {name: elem.name, value: val};
- }).get();
- }
-});
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
- jQuery.fn[o] = function(f){
- return this.bind(o, f);
- };
-});
-
-var jsc = now();
-
-jQuery.extend({
-
- get: function( url, data, callback, type ) {
- // shift arguments if data argument was ommited
- if ( jQuery.isFunction( data ) ) {
- callback = data;
- data = null;
- }
-
- return jQuery.ajax({
- type: "GET",
- url: url,
- data: data,
- success: callback,
- dataType: type
- });
- },
-
- getScript: function( url, callback ) {
- return jQuery.get(url, null, callback, "script");
- },
-
- getJSON: function( url, data, callback ) {
- return jQuery.get(url, data, callback, "json");
- },
-
- post: function( url, data, callback, type ) {
- if ( jQuery.isFunction( data ) ) {
- callback = data;
- data = {};
- }
-
- return jQuery.ajax({
- type: "POST",
- url: url,
- data: data,
- success: callback,
- dataType: type
- });
- },
-
- ajaxSetup: function( settings ) {
- jQuery.extend( jQuery.ajaxSettings, settings );
- },
-
- ajaxSettings: {
- url: location.href,
- global: true,
- type: "GET",
- contentType: "application/x-www-form-urlencoded",
- processData: true,
- async: true,
- /*
- timeout: 0,
- data: null,
- username: null,
- password: null,
- */
- // Create the request object; Microsoft failed to properly
- // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
- // This function can be overriden by calling jQuery.ajaxSetup
- xhr:function(){
- return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
- },
- accepts: {
- xml: "application/xml, text/xml",
- html: "text/html",
- script: "text/javascript, application/javascript",
- json: "application/json, text/javascript",
- text: "text/plain",
- _default: "*/*"
- }
- },
-
- // Last-Modified header cache for next request
- lastModified: {},
-
- ajax: function( s ) {
- // Extend the settings, but re-extend 's' so that it can be
- // checked again later (in the test suite, specifically)
- s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
-
- var jsonp, jsre = /=\?(&|$)/g, status, data,
- type = s.type.toUpperCase();
-
- // convert data if not already a string
- if ( s.data && s.processData && typeof s.data !== "string" )
- s.data = jQuery.param(s.data);
-
- // Handle JSONP Parameter Callbacks
- if ( s.dataType == "jsonp" ) {
- if ( type == "GET" ) {
- if ( !s.url.match(jsre) )
- s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
- } else if ( !s.data || !s.data.match(jsre) )
- s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
- s.dataType = "json";
- }
-
- // Build temporary JSONP function
- if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
- jsonp = "jsonp" + jsc++;
-
- // Replace the =? sequence both in the query string and the data
- if ( s.data )
- s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
- s.url = s.url.replace(jsre, "=" + jsonp + "$1");
-
- // We need to make sure
- // that a JSONP style response is executed properly
- s.dataType = "script";
-
- // Handle JSONP-style loading
- window[ jsonp ] = function(tmp){
- data = tmp;
- success();
- complete();
- // Garbage collect
- window[ jsonp ] = undefined;
- try{ delete window[ jsonp ]; } catch(e){}
- if ( head )
- head.removeChild( script );
- };
- }
-
- if ( s.dataType == "script" && s.cache == null )
- s.cache = false;
-
- if ( s.cache === false && type == "GET" ) {
- var ts = now();
- // try replacing _= if it is there
- var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
- // if nothing was replaced, add timestamp to the end
- s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
- }
-
- // If data is available, append data to url for get requests
- if ( s.data && type == "GET" ) {
- s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
-
- // IE likes to send both get and post data, prevent this
- s.data = null;
- }
-
- // Watch for a new set of requests
- if ( s.global && ! jQuery.active++ )
- jQuery.event.trigger( "ajaxStart" );
-
- // Matches an absolute URL, and saves the domain
- var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );
-
- // If we're requesting a remote document
- // and trying to load JSON or Script with a GET
- if ( s.dataType == "script" && type == "GET" && parts
- && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){
-
- var head = document.getElementsByTagName("head")[0];
- var script = document.createElement("script");
- script.src = s.url;
- if (s.scriptCharset)
- script.charset = s.scriptCharset;
-
- // Handle Script loading
- if ( !jsonp ) {
- var done = false;
-
- // Attach handlers for all browsers
- script.onload = script.onreadystatechange = function(){
- if ( !done && (!this.readyState ||
- this.readyState == "loaded" || this.readyState == "complete") ) {
- done = true;
- success();
- complete();
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
- head.removeChild( script );
- }
- };
- }
-
- head.appendChild(script);
-
- // We handle everything using the script element injection
- return undefined;
- }
-
- var requestDone = false;
-
- // Create the request object
- var xhr = s.xhr();
-
- // Open the socket
- // Passing null username, generates a login popup on Opera (#2865)
- if( s.username )
- xhr.open(type, s.url, s.async, s.username, s.password);
- else
- xhr.open(type, s.url, s.async);
-
- // Need an extra try/catch for cross domain requests in Firefox 3
- try {
- // Set the correct header, if data is being sent
- if ( s.data )
- xhr.setRequestHeader("Content-Type", s.contentType);
-
- // Set the If-Modified-Since header, if ifModified mode.
- if ( s.ifModified )
- xhr.setRequestHeader("If-Modified-Since",
- jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
-
- // Set header so the called script knows that it's an XMLHttpRequest
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
-
- // Set the Accepts header for the server, depending on the dataType
- xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
- s.accepts[ s.dataType ] + ", */*" :
- s.accepts._default );
- } catch(e){}
-
- // Allow custom headers/mimetypes and early abort
- if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active )
- jQuery.event.trigger( "ajaxStop" );
- // close opended socket
- xhr.abort();
- return false;
- }
-
- if ( s.global )
- jQuery.event.trigger("ajaxSend", [xhr, s]);
-
- // Wait for a response to come back
- var onreadystatechange = function(isTimeout){
- // The request was aborted, clear the interval and decrement jQuery.active
- if (xhr.readyState == 0) {
- if (ival) {
- // clear poll interval
- clearInterval(ival);
- ival = null;
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active )
- jQuery.event.trigger( "ajaxStop" );
- }
- // The transfer is complete and the data is available, or the request timed out
- } else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
- requestDone = true;
-
- // clear poll interval
- if (ival) {
- clearInterval(ival);
- ival = null;
- }
-
- status = isTimeout == "timeout" ? "timeout" :
- !jQuery.httpSuccess( xhr ) ? "error" :
- s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
- "success";
-
- if ( status == "success" ) {
- // Watch for, and catch, XML document parse errors
- try {
- // process the data (runs the xml through httpData regardless of callback)
- data = jQuery.httpData( xhr, s.dataType, s );
- } catch(e) {
- status = "parsererror";
- }
- }
-
- // Make sure that the request was successful or notmodified
- if ( status == "success" ) {
- // Cache Last-Modified header, if ifModified mode.
- var modRes;
- try {
- modRes = xhr.getResponseHeader("Last-Modified");
- } catch(e) {} // swallow exception thrown by FF if header is not available
-
- if ( s.ifModified && modRes )
- jQuery.lastModified[s.url] = modRes;
-
- // JSONP handles its own success callback
- if ( !jsonp )
- success();
- } else
- jQuery.handleError(s, xhr, status);
-
- // Fire the complete handlers
- complete();
-
- if ( isTimeout )
- xhr.abort();
-
- // Stop memory leaks
- if ( s.async )
- xhr = null;
- }
- };
-
- if ( s.async ) {
- // don't attach the handler to the request, just poll it instead
- var ival = setInterval(onreadystatechange, 13);
-
- // Timeout checker
- if ( s.timeout > 0 )
- setTimeout(function(){
- // Check to see if the request is still happening
- if ( xhr && !requestDone )
- onreadystatechange( "timeout" );
- }, s.timeout);
- }
-
- // Send the data
- try {
- xhr.send(s.data);
- } catch(e) {
- jQuery.handleError(s, xhr, null, e);
- }
-
- // firefox 1.5 doesn't fire statechange for sync requests
- if ( !s.async )
- onreadystatechange();
-
- function success(){
- // If a local callback was specified, fire it and pass it the data
- if ( s.success )
- s.success( data, status );
-
- // Fire the global callback
- if ( s.global )
- jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
- }
-
- function complete(){
- // Process result
- if ( s.complete )
- s.complete(xhr, status);
-
- // The request was completed
- if ( s.global )
- jQuery.event.trigger( "ajaxComplete", [xhr, s] );
-
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active )
- jQuery.event.trigger( "ajaxStop" );
- }
-
- // return XMLHttpRequest to allow aborting the request etc.
- return xhr;
- },
-
- handleError: function( s, xhr, status, e ) {
- // If a local callback was specified, fire it
- if ( s.error ) s.error( xhr, status, e );
-
- // Fire the global callback
- if ( s.global )
- jQuery.event.trigger( "ajaxError", [xhr, s, e] );
- },
-
- // Counter for holding the number of active queries
- active: 0,
-
- // Determines if an XMLHttpRequest was successful or not
- httpSuccess: function( xhr ) {
- try {
- // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
- return !xhr.status && location.protocol == "file:" ||
- ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
- } catch(e){}
- return false;
- },
-
- // Determines if an XMLHttpRequest returns NotModified
- httpNotModified: function( xhr, url ) {
- try {
- var xhrRes = xhr.getResponseHeader("Last-Modified");
-
- // Firefox always returns 200. check Last-Modified date
- return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
- } catch(e){}
- return false;
- },
-
- httpData: function( xhr, type, s ) {
- var ct = xhr.getResponseHeader("content-type"),
- xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
- data = xml ? xhr.responseXML : xhr.responseText;
-
- if ( xml && data.documentElement.tagName == "parsererror" )
- throw "parsererror";
-
- // Allow a pre-filtering function to sanitize the response
- // s != null is checked to keep backwards compatibility
- if( s && s.dataFilter )
- data = s.dataFilter( data, type );
-
- // The filter can actually parse the response
- if( typeof data === "string" ){
-
- // If the type is "script", eval it in global context
- if ( type == "script" )
- jQuery.globalEval( data );
-
- // Get the JavaScript object, if JSON is used.
- if ( type == "json" )
- data = window["eval"]("(" + data + ")");
- }
-
- return data;
- },
-
- // Serialize an array of form elements or a set of
- // key/values into a query string
- param: function( a ) {
- var s = [ ];
-
- function add( key, value ){
- s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
- };
-
- // If an array was passed in, assume that it is an array
- // of form elements
- if ( jQuery.isArray(a) || a.jquery )
- // Serialize the form elements
- jQuery.each( a, function(){
- add( this.name, this.value );
- });
-
- // Otherwise, assume that it's an object of key/value pairs
- else
- // Serialize the key/values
- for ( var j in a )
- // If the value is an array then the key names need to be repeated
- if ( jQuery.isArray(a[j]) )
- jQuery.each( a[j], function(){
- add( j, this );
- });
- else
- add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
-
- // Return the resulting serialization
- return s.join("&").replace(/%20/g, "+");
- }
-
-});
-var elemdisplay = {},
- timerId,
- fxAttrs = [
- // height animations
- [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
- // width animations
- [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
- // opacity animations
- [ "opacity" ]
- ];
-
-function genFx( type, num ){
- var obj = {};
- jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
- obj[ this ] = type;
- });
- return obj;
-}
-
-jQuery.fn.extend({
- show: function(speed,callback){
- if ( speed ) {
- return this.animate( genFx("show", 3), speed, callback);
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ){
- var old = jQuery.data(this[i], "olddisplay");
-
- this[i].style.display = old || "";
-
- if ( jQuery.css(this[i], "display") === "none" ) {
- var tagName = this[i].tagName, display;
-
- if ( elemdisplay[ tagName ] ) {
- display = elemdisplay[ tagName ];
- } else {
- var elem = jQuery("<" + tagName + " />").appendTo("body");
-
- display = elem.css("display");
- if ( display === "none" )
- display = "block";
-
- elem.remove();
-
- elemdisplay[ tagName ] = display;
- }
-
- jQuery.data(this[i], "olddisplay", display);
- }
- }
-
- // Set the display of the elements in a second loop
- // to avoid the constant reflow
- for ( var i = 0, l = this.length; i < l; i++ ){
- this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
- }
-
- return this;
- }
- },
-
- hide: function(speed,callback){
- if ( speed ) {
- return this.animate( genFx("hide", 3), speed, callback);
- } else {
- for ( var i = 0, l = this.length; i < l; i++ ){
- var old = jQuery.data(this[i], "olddisplay");
- if ( !old && old !== "none" )
- jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
- }
-
- // Set the display of the elements in a second loop
- // to avoid the constant reflow
- for ( var i = 0, l = this.length; i < l; i++ ){
- this[i].style.display = "none";
- }
-
- return this;
- }
- },
-
- // Save the old toggle function
- _toggle: jQuery.fn.toggle,
-
- toggle: function( fn, fn2 ){
- var bool = typeof fn === "boolean";
-
- return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
- this._toggle.apply( this, arguments ) :
- fn == null || bool ?
- this.each(function(){
- var state = bool ? fn : jQuery(this).is(":hidden");
- jQuery(this)[ state ? "show" : "hide" ]();
- }) :
- this.animate(genFx("toggle", 3), fn, fn2);
- },
-
- fadeTo: function(speed,to,callback){
- return this.animate({opacity: to}, speed, callback);
- },
-
- animate: function( prop, speed, easing, callback ) {
- var optall = jQuery.speed(speed, easing, callback);
-
- return this[ optall.queue === false ? "each" : "queue" ](function(){
-
- var opt = jQuery.extend({}, optall), p,
- hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
- self = this;
-
- for ( p in prop ) {
- if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
- return opt.complete.call(this);
-
- if ( ( p == "height" || p == "width" ) && this.style ) {
- // Store display property
- opt.display = jQuery.css(this, "display");
-
- // Make sure that nothing sneaks out
- opt.overflow = this.style.overflow;
- }
- }
-
- if ( opt.overflow != null )
- this.style.overflow = "hidden";
-
- opt.curAnim = jQuery.extend({}, prop);
-
- jQuery.each( prop, function(name, val){
- var e = new jQuery.fx( self, opt, name );
-
- if ( /toggle|show|hide/.test(val) )
- e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
- else {
- var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
- start = e.cur(true) || 0;
-
- if ( parts ) {
- var end = parseFloat(parts[2]),
- unit = parts[3] || "px";
-
- // We need to compute starting value
- if ( unit != "px" ) {
- self.style[ name ] = (end || 1) + unit;
- start = ((end || 1) / e.cur(true)) * start;
- self.style[ name ] = start + unit;
- }
-
- // If a +=/-= token was provided, we're doing a relative animation
- if ( parts[1] )
- end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
-
- e.custom( start, end, unit );
- } else
- e.custom( start, val, "" );
- }
- });
-
- // For JS strict compliance
- return true;
- });
- },
-
- stop: function(clearQueue, gotoEnd){
- var timers = jQuery.timers;
-
- if (clearQueue)
- this.queue([]);
-
- this.each(function(){
- // go in reverse order so anything added to the queue during the loop is ignored
- for ( var i = timers.length - 1; i >= 0; i-- )
- if ( timers[i].elem == this ) {
- if (gotoEnd)
- // force the next step to be the last
- timers[i](true);
- timers.splice(i, 1);
- }
- });
-
- // start the next in the queue if the last step wasn't forced
- if (!gotoEnd)
- this.dequeue();
-
- return this;
- }
-
-});
-
-// Generate shortcuts for custom animations
-jQuery.each({
- slideDown: genFx("show", 1),
- slideUp: genFx("hide", 1),
- slideToggle: genFx("toggle", 1),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" }
-}, function( name, props ){
- jQuery.fn[ name ] = function( speed, callback ){
- return this.animate( props, speed, callback );
- };
-});
-
-jQuery.extend({
-
- speed: function(speed, easing, fn) {
- var opt = typeof speed === "object" ? speed : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
- };
-
- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
-
- // Queueing
- opt.old = opt.complete;
- opt.complete = function(){
- if ( opt.queue !== false )
- jQuery(this).dequeue();
- if ( jQuery.isFunction( opt.old ) )
- opt.old.call( this );
- };
-
- return opt;
- },
-
- easing: {
- linear: function( p, n, firstNum, diff ) {
- return firstNum + diff * p;
- },
- swing: function( p, n, firstNum, diff ) {
- return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
- }
- },
-
- timers: [],
-
- fx: function( elem, options, prop ){
- this.options = options;
- this.elem = elem;
- this.prop = prop;
-
- if ( !options.orig )
- options.orig = {};
- }
-
-});
-
-jQuery.fx.prototype = {
-
- // Simple function for setting a style value
- update: function(){
- if ( this.options.step )
- this.options.step.call( this.elem, this.now, this );
-
- (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
-
- // Set display property to block for height/width animations
- if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
- this.elem.style.display = "block";
- },
-
- // Get the current size
- cur: function(force){
- if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
- return this.elem[ this.prop ];
-
- var r = parseFloat(jQuery.css(this.elem, this.prop, force));
- return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
- },
-
- // Start an animation from one number to another
- custom: function(from, to, unit){
- this.startTime = now();
- this.start = from;
- this.end = to;
- this.unit = unit || this.unit || "px";
- this.now = this.start;
- this.pos = this.state = 0;
-
- var self = this;
- function t(gotoEnd){
- return self.step(gotoEnd);
- }
-
- t.elem = this.elem;
-
- if ( t() && jQuery.timers.push(t) && !timerId ) {
- timerId = setInterval(function(){
- var timers = jQuery.timers;
-
- for ( var i = 0; i < timers.length; i++ )
- if ( !timers[i]() )
- timers.splice(i--, 1);
-
- if ( !timers.length ) {
- clearInterval( timerId );
- timerId = undefined;
- }
- }, 13);
- }
- },
-
- // Simple 'show' function
- show: function(){
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
- this.options.show = true;
-
- // Begin the animation
- // Make sure that we start at a small width/height to avoid any
- // flash of content
- this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
-
- // Start by showing the element
- jQuery(this.elem).show();
- },
-
- // Simple 'hide' function
- hide: function(){
- // Remember where we started, so that we can go back to it later
- this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
- this.options.hide = true;
-
- // Begin the animation
- this.custom(this.cur(), 0);
- },
-
- // Each step of an animation
- step: function(gotoEnd){
- var t = now();
-
- if ( gotoEnd || t >= this.options.duration + this.startTime ) {
- this.now = this.end;
- this.pos = this.state = 1;
- this.update();
-
- this.options.curAnim[ this.prop ] = true;
-
- var done = true;
- for ( var i in this.options.curAnim )
- if ( this.options.curAnim[i] !== true )
- done = false;
-
- if ( done ) {
- if ( this.options.display != null ) {
- // Reset the overflow
- this.elem.style.overflow = this.options.overflow;
-
- // Reset the display
- this.elem.style.display = this.options.display;
- if ( jQuery.css(this.elem, "display") == "none" )
- this.elem.style.display = "block";
- }
-
- // Hide the element if the "hide" operation was done
- if ( this.options.hide )
- jQuery(this.elem).hide();
-
- // Reset the properties, if the item has been hidden or shown
- if ( this.options.hide || this.options.show )
- for ( var p in this.options.curAnim )
- jQuery.attr(this.elem.style, p, this.options.orig[p]);
-
- // Execute the complete function
- this.options.complete.call( this.elem );
- }
-
- return false;
- } else {
- var n = t - this.startTime;
- this.state = n / this.options.duration;
-
- // Perform the easing function, defaults to swing
- this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
- this.now = this.start + ((this.end - this.start) * this.pos);
-
- // Perform the next step of the animation
- this.update();
- }
-
- return true;
- }
-
-};
-
-jQuery.extend( jQuery.fx, {
- speeds:{
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
- },
- step: {
-
- opacity: function(fx){
- jQuery.attr(fx.elem.style, "opacity", fx.now);
- },
-
- _default: function(fx){
- if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
- fx.elem.style[ fx.prop ] = fx.now + fx.unit;
- else
- fx.elem[ fx.prop ] = fx.now;
- }
- }
-});
-if ( document.documentElement["getBoundingClientRect"] )
- jQuery.fn.offset = function() {
- if ( !this[0] ) return { top: 0, left: 0 };
- if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
- var box = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
- clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
- top = box.top + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop,
- left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
- return { top: top, left: left };
- };
-else
- jQuery.fn.offset = function() {
- if ( !this[0] ) return { top: 0, left: 0 };
- if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
- jQuery.offset.initialized || jQuery.offset.initialize();
-
- var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
- doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
- body = doc.body, defaultView = doc.defaultView,
- prevComputedStyle = defaultView.getComputedStyle(elem, null),
- top = elem.offsetTop, left = elem.offsetLeft;
-
- while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
- computedStyle = defaultView.getComputedStyle(elem, null);
- top -= elem.scrollTop, left -= elem.scrollLeft;
- if ( elem === offsetParent ) {
- top += elem.offsetTop, left += elem.offsetLeft;
- if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
- top += parseInt( computedStyle.borderTopWidth, 10) || 0,
- left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
- prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
- }
- if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
- top += parseInt( computedStyle.borderTopWidth, 10) || 0,
- left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
- prevComputedStyle = computedStyle;
- }
-
- if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
- top += body.offsetTop,
- left += body.offsetLeft;
-
- if ( prevComputedStyle.position === "fixed" )
- top += Math.max(docElem.scrollTop, body.scrollTop),
- left += Math.max(docElem.scrollLeft, body.scrollLeft);
-
- return { top: top, left: left };
- };
-
-jQuery.offset = {
- initialize: function() {
- if ( this.initialized ) return;
- var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
- html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
-
- rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
- for ( prop in rules ) container.style[prop] = rules[prop];
-
- container.innerHTML = html;
- body.insertBefore(container, body.firstChild);
- innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;
-
- this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
- this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
-
- innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
- this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
-
- body.style.marginTop = '1px';
- this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
- body.style.marginTop = bodyMarginTop;
-
- body.removeChild(container);
- this.initialized = true;
- },
-
- bodyOffset: function(body) {
- jQuery.offset.initialized || jQuery.offset.initialize();
- var top = body.offsetTop, left = body.offsetLeft;
- if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
- top += parseInt( jQuery.curCSS(body, 'marginTop', true), 10 ) || 0,
- left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
- return { top: top, left: left };
- }
-};
-
-
-jQuery.fn.extend({
- position: function() {
- var left = 0, top = 0, results;
-
- if ( this[0] ) {
- // Get *real* offsetParent
- var offsetParent = this.offsetParent(),
-
- // Get correct offsets
- offset = this.offset(),
- parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
-
- // Subtract element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- offset.top -= num( this, 'marginTop' );
- offset.left -= num( this, 'marginLeft' );
-
- // Add offsetParent borders
- parentOffset.top += num( offsetParent, 'borderTopWidth' );
- parentOffset.left += num( offsetParent, 'borderLeftWidth' );
-
- // Subtract the two offsets
- results = {
- top: offset.top - parentOffset.top,
- left: offset.left - parentOffset.left
- };
- }
-
- return results;
- },
-
- offsetParent: function() {
- var offsetParent = this[0].offsetParent || document.body;
- while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
- offsetParent = offsetParent.offsetParent;
- return jQuery(offsetParent);
- }
-});
-
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( ['Left', 'Top'], function(i, name) {
- var method = 'scroll' + name;
-
- jQuery.fn[ method ] = function(val) {
- if (!this[0]) return null;
-
- return val !== undefined ?
-
- // Set the scroll offset
- this.each(function() {
- this == window || this == document ?
- window.scrollTo(
- !i ? val : jQuery(window).scrollLeft(),
- i ? val : jQuery(window).scrollTop()
- ) :
- this[ method ] = val;
- }) :
-
- // Return the scroll offset
- this[0] == window || this[0] == document ?
- self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
- jQuery.boxModel && document.documentElement[ method ] ||
- document.body[ method ] :
- this[0][ method ];
- };
-});
-// Create innerHeight, innerWidth, outerHeight and outerWidth methods
-jQuery.each([ "Height", "Width" ], function(i, name){
-
- var tl = i ? "Left" : "Top", // top or left
- br = i ? "Right" : "Bottom", // bottom or right
- lower = name.toLowerCase();
-
- // innerHeight and innerWidth
- jQuery.fn["inner" + name] = function(){
- return this[0] ?
- jQuery.css( this[0], lower, false, "padding" ) :
- null;
- };
-
- // outerHeight and outerWidth
- jQuery.fn["outer" + name] = function(margin) {
- return this[0] ?
- jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
- null;
- };
-
- var type = name.toLowerCase();
-
- jQuery.fn[ type ] = function( size ) {
- // Get window width or height
- return this[0] == window ?
- // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
- document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
- document.body[ "client" + name ] :
-
- // Get document width or height
- this[0] == document ?
- // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
- Math.max(
- document.documentElement["client" + name],
- document.body["scroll" + name], document.documentElement["scroll" + name],
- document.body["offset" + name], document.documentElement["offset" + name]
- ) :
-
- // Get or set width or height on the element
- size === undefined ?
- // Get width or height on the element
- (this.length ? jQuery.css( this[0], type ) : null) :
-
- // Set the width or height on the element (default to pixels if value is unitless)
- this.css( type, typeof size === "string" ? size : size + "px" );
- };
-
-});
-})();
diff --git a/webengineviewer/src/data/jquery.min.js b/webengineviewer/src/data/jquery.min.js
deleted file mode 100644
index b1ae21d8..00000000
--- a/webengineviewer/src/data/jquery.min.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * jQuery JavaScript Library v1.3.2
- * http://jquery.com/
- *
- * Copyright (c) 2009 John Resig
- * Dual licensed under the MIT and GPL licenses.
- * http://docs.jquery.com/License
- *
- * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
- * Revision: 6246
- */
-(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G<E;G++){L.call(K(this[G],H),this.length>1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){if(G&&/\S/.test(G)){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+"></"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/<tbody/i.test(S),N=!O.indexOf("<table")&&!R?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(G){var J=[],L=o(G);for(var K=0,H=L.length;K<H;K++){var I=(K>0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
-/*
- * Sizzle CSS Selector Engine - v0.9.3
- * Copyright 2009, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML=' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})();
\ No newline at end of file
diff --git a/webengineviewer/src/findbar/autotests/findbarbasetest.cpp b/webengineviewer/src/findbar/autotests/findbarbasetest.cpp
index 0971421f..ae5b62fd 100644
--- a/webengineviewer/src/findbar/autotests/findbarbasetest.cpp
+++ b/webengineviewer/src/findbar/autotests/findbarbasetest.cpp
@@ -1,116 +1,116 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#include "findbarbasetest.h"
#include "../findbarbase.h"
#include <QLabel>
#include <QPushButton>
#include <QToolButton>
-#include <qtest.h>
+#include <QTest>
#include <QSignalSpy>
#include <PimCommon/LineEditWithCompleterNg>
FindBarBaseTest::FindBarBaseTest(QObject *parent)
: QObject(parent)
{
}
FindBarBaseTest::~FindBarBaseTest()
{
}
void FindBarBaseTest::shouldHaveDefaultValue()
{
WebEngineViewer::FindBarBase bar;
QLabel *status = bar.findChild<QLabel *>(QStringLiteral("status"));
QVERIFY(status);
QVERIFY(status->text().isEmpty());
QPushButton *previous = bar.findChild<QPushButton *>(QStringLiteral("findprevious"));
QVERIFY(previous);
QVERIFY(!previous->isEnabled());
QPushButton *next = bar.findChild<QPushButton *>(QStringLiteral("findnext"));
QVERIFY(next);
QVERIFY(!next->isEnabled());
QToolButton *close = bar.findChild<QToolButton *>(QStringLiteral("close"));
QVERIFY(close);
PimCommon::LineEditWithCompleterNg *lineedit = bar.findChild<PimCommon::LineEditWithCompleterNg *>(QStringLiteral("searchline"));
QVERIFY(lineedit);
QVERIFY(lineedit->text().isEmpty());
}
void FindBarBaseTest::shouldClearLineWhenClose()
{
WebEngineViewer::FindBarBase bar;
bar.show();
QApplication::setActiveWindow(&bar);
QSignalSpy spy(&bar, &WebEngineViewer::FindBarBase::hideFindBar);
QVERIFY(QTest::qWaitForWindowExposed(&bar));
QVERIFY(bar.isVisible());
bar.focusAndSetCursor();
PimCommon::LineEditWithCompleterNg *lineedit = bar.findChild<PimCommon::LineEditWithCompleterNg *>(QStringLiteral("searchline"));
lineedit->setText(QStringLiteral("FOO"));
QVERIFY(!lineedit->text().isEmpty());
QVERIFY(lineedit->hasFocus());
bar.closeBar();
QVERIFY(lineedit->text().isEmpty());
QVERIFY(!lineedit->hasFocus());
QCOMPARE(spy.count(), 1);
}
void FindBarBaseTest::shouldEnableDisableNextPreviousButton()
{
WebEngineViewer::FindBarBase bar;
bar.show();
QApplication::setActiveWindow(&bar);
QVERIFY(QTest::qWaitForWindowExposed(&bar));
QPushButton *previous = bar.findChild<QPushButton *>(QStringLiteral("findprevious"));
QPushButton *next = bar.findChild<QPushButton *>(QStringLiteral("findnext"));
bar.autoSearch(QStringLiteral("FOO"));
QVERIFY(next->isEnabled());
QVERIFY(previous->isEnabled());
bar.autoSearch(QString());
QVERIFY(!next->isEnabled());
QVERIFY(!previous->isEnabled());
}
void FindBarBaseTest::shouldClearAllWhenShowBar()
{
WebEngineViewer::FindBarBase bar;
bar.show();
QVERIFY(QTest::qWaitForWindowExposed(&bar));
QLabel *status = bar.findChild<QLabel *>(QStringLiteral("status"));
status->setText(QStringLiteral("FOO"));
bar.closeBar();
bar.show();
bar.focusAndSetCursor();
PimCommon::LineEditWithCompleterNg *lineedit = bar.findChild<PimCommon::LineEditWithCompleterNg *>(QStringLiteral("searchline"));
QVERIFY(lineedit->hasFocus());
QVERIFY(status->text().isEmpty());
}
QTEST_MAIN(FindBarBaseTest)
diff --git a/webengineviewer/src/findbar/autotests/findbarbasetest.h b/webengineviewer/src/findbar/autotests/findbarbasetest.h
index 55c309ad..2f32c059 100644
--- a/webengineviewer/src/findbar/autotests/findbarbasetest.h
+++ b/webengineviewer/src/findbar/autotests/findbarbasetest.h
@@ -1,39 +1,39 @@
/*
- Copyright (C) 2014-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2014-2019 Laurent Montel <montel@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.
*/
#ifndef FINDBARBASETEST_H
#define FINDBARBASETEST_H
#include <QObject>
class FindBarBaseTest : public QObject
{
Q_OBJECT
public:
explicit FindBarBaseTest(QObject *parent = nullptr);
~FindBarBaseTest();
private Q_SLOTS:
void shouldHaveDefaultValue();
void shouldClearLineWhenClose();
void shouldEnableDisableNextPreviousButton();
void shouldClearAllWhenShowBar();
};
#endif // FINDBARBASETEST_H
diff --git a/webengineviewer/src/findbar/findbarbase.cpp b/webengineviewer/src/findbar/findbarbase.cpp
index b030d28c..a7aaeb92 100644
--- a/webengineviewer/src/findbar/findbarbase.cpp
+++ b/webengineviewer/src/findbar/findbarbase.cpp
@@ -1,273 +1,273 @@
/* Copyright (C) 2010 Torgny Nyblom <nyblom@kde.org>
- * Copyright (C) 2010-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2010-2019 Laurent Montel <montel@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.
*/
#include "findbarbase.h"
#include <PimCommon/LineEditWithCompleterNg>
-#include <qicon.h>
+#include <QIcon>
#include <KLocalizedString>
#include <klineedit.h>
#include <KColorScheme>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include <QLabel>
#include <QMenu>
#include <QToolButton>
#include <QEvent>
#include <QKeyEvent>
using namespace WebEngineViewer;
FindBarBase::FindBarBase(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *lay = new QHBoxLayout(this);
- lay->setMargin(2);
+ lay->setContentsMargins(2, 2, 2, 2);
QToolButton *closeBtn = new QToolButton(this);
closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
closeBtn->setObjectName(QStringLiteral("close"));
closeBtn->setIconSize(QSize(16, 16));
closeBtn->setToolTip(i18n("Close"));
#ifndef QT_NO_ACCESSIBILITY
closeBtn->setAccessibleName(i18n("Close"));
#endif
closeBtn->setAutoRaise(true);
lay->addWidget(closeBtn);
QLabel *label = new QLabel(i18nc("Find text", "F&ind:"), this);
lay->addWidget(label);
mSearch = new PimCommon::LineEditWithCompleterNg(this);
mSearch->setObjectName(QStringLiteral("searchline"));
mSearch->setToolTip(i18n("Text to search for"));
mSearch->setClearButtonEnabled(true);
label->setBuddy(mSearch);
lay->addWidget(mSearch);
mFindNextBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("go-down-search")), i18nc("Find and go to the next search match", "Next"), this);
mFindNextBtn->setToolTip(i18n("Jump to next match"));
mFindNextBtn->setObjectName(QStringLiteral("findnext"));
lay->addWidget(mFindNextBtn);
mFindNextBtn->setEnabled(false);
mFindPrevBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("go-up-search")), i18nc("Find and go to the previous search match", "Previous"), this);
mFindPrevBtn->setToolTip(i18n("Jump to previous match"));
mFindPrevBtn->setObjectName(QStringLiteral("findprevious"));
lay->addWidget(mFindPrevBtn);
mFindPrevBtn->setEnabled(false);
QPushButton *optionsBtn = new QPushButton(this);
optionsBtn->setText(i18n("Options"));
optionsBtn->setToolTip(i18n("Modify search behavior"));
mOptionsMenu = new QMenu(optionsBtn);
mCaseSensitiveAct = mOptionsMenu->addAction(i18n("Case sensitive"));
mCaseSensitiveAct->setCheckable(true);
optionsBtn->setMenu(mOptionsMenu);
lay->addWidget(optionsBtn);
connect(closeBtn, &QToolButton::clicked, this, &FindBarBase::closeBar);
connect(mFindNextBtn, &QPushButton::clicked, this, &FindBarBase::findNext);
connect(mFindPrevBtn, &QPushButton::clicked, this, &FindBarBase::findPrev);
connect(mCaseSensitiveAct, &QAction::toggled, this, &FindBarBase::caseSensitivityChanged);
connect(mSearch, &QLineEdit::textChanged, this, &FindBarBase::autoSearch);
mStatus = new QLabel;
mStatus->setObjectName(QStringLiteral("status"));
mStatus->setTextFormat(Qt::PlainText);
QFontMetrics fm(mStatus->font());
mNotFoundString = i18n("Phrase not found");
mStatus->setFixedWidth(fm.boundingRect(mNotFoundString).width());
lay->addWidget(mStatus);
setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
hide();
}
FindBarBase::~FindBarBase()
{
}
QMenu *FindBarBase::optionsMenu()
{
return mOptionsMenu;
}
QString FindBarBase::text() const
{
return mSearch->text();
}
void FindBarBase::setText(const QString &text)
{
mSearch->setText(text);
}
void FindBarBase::focusAndSetCursor()
{
setFocus();
mStatus->clear();
mSearch->selectAll();
mSearch->setFocus();
}
void FindBarBase::slotClearSearch()
{
clearSelections();
}
void FindBarBase::autoSearch(const QString &str)
{
const bool isNotEmpty = (!str.isEmpty());
mFindPrevBtn->setEnabled(isNotEmpty);
mFindNextBtn->setEnabled(isNotEmpty);
if (isNotEmpty) {
QTimer::singleShot(0, this, [this]() {
slotSearchText();
});
} else {
clearSelections();
}
}
void FindBarBase::slotSearchText(bool backward, bool isAutoSearch)
{
searchText(backward, isAutoSearch);
}
void FindBarBase::setFoundMatch(bool match)
{
#ifndef QT_NO_STYLE_STYLESHEET
QString styleSheet;
if (!mSearch->text().isEmpty()) {
if (mNegativeBackground.isEmpty()) {
KStatefulBrush bgBrush(KColorScheme::View, KColorScheme::PositiveBackground);
mPositiveBackground = QStringLiteral("QLineEdit{ background-color:%1 }").arg(bgBrush.brush(mSearch).color().name());
bgBrush = KStatefulBrush(KColorScheme::View, KColorScheme::NegativeBackground);
mNegativeBackground = QStringLiteral("QLineEdit{ background-color:%1 }").arg(bgBrush.brush(mSearch).color().name());
}
if (match) {
styleSheet = mPositiveBackground;
mStatus->clear();
} else {
styleSheet = mNegativeBackground;
mStatus->setText(mNotFoundString);
}
}
mSearch->setStyleSheet(styleSheet);
#endif
}
void FindBarBase::searchText(bool backward, bool isAutoSearch)
{
Q_UNUSED(backward);
Q_UNUSED(isAutoSearch);
}
void FindBarBase::addToCompletion(const QString &text)
{
mSearch->addCompletionItem(text);
}
void FindBarBase::findNext()
{
searchText(false, false);
addToCompletion(mLastSearchStr);
}
void FindBarBase::findPrev()
{
searchText(true, false);
addToCompletion(mLastSearchStr);
}
void FindBarBase::caseSensitivityChanged(bool b)
{
updateSensitivity(b);
}
void FindBarBase::updateSensitivity(bool)
{
}
void FindBarBase::slotHighlightAllChanged(bool b)
{
updateHighLight(b);
}
void FindBarBase::updateHighLight(bool)
{
}
void FindBarBase::clearSelections()
{
setFoundMatch(false);
}
void FindBarBase::closeBar()
{
// Make sure that all old searches are cleared
mSearch->clear();
clearSelections();
mSearch->clearFocus();
Q_EMIT hideFindBar();
}
bool FindBarBase::event(QEvent *e)
{
// Close the bar when pressing Escape.
// Not using a QShortcut for this because it could conflict with
// window-global actions (e.g. Emil Sedgh binds Esc to "close tab").
// With a shortcut override we can catch this before it gets to kactions.
const bool shortCutOverride = (e->type() == QEvent::ShortcutOverride);
if (shortCutOverride || e->type() == QEvent::KeyPress) {
QKeyEvent *kev = static_cast<QKeyEvent * >(e);
if (kev->key() == Qt::Key_Escape) {
if (shortCutOverride) {
e->accept();
return true;
}
e->accept();
closeBar();
return true;
} else if (kev->key() == Qt::Key_Enter
|| kev->key() == Qt::Key_Return) {
e->accept();
if (shortCutOverride) {
return true;
}
if (mSearch->text().isEmpty()) {
return true;
}
if (kev->modifiers() & Qt::ShiftModifier) {
findPrev();
} else if (kev->modifiers() == Qt::NoModifier) {
findNext();
}
return true;
}
}
return QWidget::event(e);
}
diff --git a/webengineviewer/src/findbar/findbarbase.h b/webengineviewer/src/findbar/findbarbase.h
index 0b134418..6c8aae33 100644
--- a/webengineviewer/src/findbar/findbarbase.h
+++ b/webengineviewer/src/findbar/findbarbase.h
@@ -1,85 +1,85 @@
/* Copyright (C) 2010 Torgny Nyblom <nyblom@kde.org>
- * Copyright (C) 2010-2018 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2010-2019 Laurent Montel <montel@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.
*/
#ifndef FINDBARBASE_H
#define FINDBARBASE_H
#include <QWidget>
#include "webengineviewer_export.h"
class QAction;
class QPushButton;
class QMenu;
class QLabel;
namespace PimCommon {
class LineEditWithCompleterNg;
}
namespace WebEngineViewer {
class WEBENGINEVIEWER_EXPORT FindBarBase : public QWidget
{
Q_OBJECT
public:
explicit FindBarBase(QWidget *parent = nullptr);
~FindBarBase() override;
Q_REQUIRED_RESULT QString text() const;
void setText(const QString &text);
void focusAndSetCursor();
protected:
bool event(QEvent *e) override;
virtual void clearSelections();
virtual void updateHighLight(bool);
virtual void searchText(bool backward, bool isAutoSearch);
virtual void updateSensitivity(bool);
void setFoundMatch(bool match);
QMenu *optionsMenu();
public Q_SLOTS:
void findNext();
void findPrev();
void autoSearch(const QString &str);
void slotSearchText(bool backward = false, bool isAutoSearch = true);
void closeBar();
Q_SIGNALS:
void hideFindBar();
protected Q_SLOTS:
void caseSensitivityChanged(bool);
void slotHighlightAllChanged(bool);
void slotClearSearch();
protected:
QString mNotFoundString;
QString mPositiveBackground;
QString mNegativeBackground;
QString mLastSearchStr;
PimCommon::LineEditWithCompleterNg *mSearch = nullptr;
QAction *mCaseSensitiveAct = nullptr;
QPushButton *mFindPrevBtn = nullptr;
QPushButton *mFindNextBtn = nullptr;
QMenu *mOptionsMenu = nullptr;
QLabel *mStatus = nullptr;
void addToCompletion(const QString &text);
};
}
#endif /* FINDBARBASE_H */
diff --git a/webengineviewer/src/findbar/findbarwebengineview.cpp b/webengineviewer/src/findbar/findbarwebengineview.cpp
index edbcd976..4c94d936 100644
--- a/webengineviewer/src/findbar/findbarwebengineview.cpp
+++ b/webengineviewer/src/findbar/findbarwebengineview.cpp
@@ -1,86 +1,86 @@
-/* Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "findbarwebengineview.h"
#include <PimCommon/LineEditWithCompleterNg>
#include <QWebEngineView>
#include <QAction>
using namespace WebEngineViewer;
class WebEngineViewer::FindBarWebEngineViewPrivate
{
public:
FindBarWebEngineViewPrivate()
{
}
QWebEngineView *mView = nullptr;
};
FindBarWebEngineView::FindBarWebEngineView(QWebEngineView *view, QWidget *parent)
: FindBarBase(parent)
, d(new WebEngineViewer::FindBarWebEngineViewPrivate)
{
d->mView = view;
}
FindBarWebEngineView::~FindBarWebEngineView()
{
delete d;
}
void FindBarWebEngineView::searchText(bool backward, bool isAutoSearch)
{
QWebEnginePage::FindFlags searchOptions;
if (backward) {
searchOptions |= QWebEnginePage::FindBackward;
}
if (mCaseSensitiveAct->isChecked()) {
searchOptions |= QWebEnginePage::FindCaseSensitively;
}
const QString searchWord(mSearch->text());
if (!isAutoSearch && !mLastSearchStr.contains(searchWord, Qt::CaseSensitive)) {
clearSelections();
}
- d->mView->findText(QString()); //Clear an existing highligh
+ d->mView->findText(QString()); //Clear an existing highlight
mLastSearchStr = searchWord;
d->mView->findText(mLastSearchStr, searchOptions, [this](bool found) {
setFoundMatch(found);
});
}
void FindBarWebEngineView::updateSensitivity(bool sensitivity)
{
QWebEnginePage::FindFlags searchOptions;
if (sensitivity) {
searchOptions |= QWebEnginePage::FindCaseSensitively;
- d->mView->findText(QString()); //Clear an existing highligh
+ d->mView->findText(QString()); //Clear an existing highlight
}
d->mView->findText(QString(), searchOptions, [this](bool found) {
setFoundMatch(found);
});
}
void FindBarWebEngineView::clearSelections()
{
d->mView->findText(QString());
mLastSearchStr.clear();
FindBarBase::clearSelections();
}
diff --git a/webengineviewer/src/findbar/findbarwebengineview.h b/webengineviewer/src/findbar/findbarwebengineview.h
index df16fbb3..9f872e19 100644
--- a/webengineviewer/src/findbar/findbarwebengineview.h
+++ b/webengineviewer/src/findbar/findbarwebengineview.h
@@ -1,46 +1,46 @@
-/* Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+/* Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEVIEWER_FINDBAR_WEBENGINEVIEW_H
#define WEBENGINEVIEWER_FINDBAR_WEBENGINEVIEW_H
#include "findbarbase.h"
#include "webengineviewer_export.h"
class QWebEngineView;
namespace WebEngineViewer {
class FindBarWebEngineViewPrivate;
class WEBENGINEVIEWER_EXPORT FindBarWebEngineView : public FindBarBase
{
Q_OBJECT
public:
explicit FindBarWebEngineView(QWebEngineView *view, QWidget *parent = nullptr);
~FindBarWebEngineView() override;
private:
void clearSelections() override;
void searchText(bool backward, bool isAutoSearch) override;
void updateSensitivity(bool sensitivity) override;
private:
FindBarWebEngineViewPrivate *const d;
};
}
#endif
diff --git a/webengineviewer/src/jquery.qrc b/webengineviewer/src/jquery.qrc
deleted file mode 100644
index 8ea0a0a9..00000000
--- a/webengineviewer/src/jquery.qrc
+++ /dev/null
@@ -1,6 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>data/jquery.min.js</file>
- <file>data/jquery-ui.js</file>
- </qresource>
-</RCC>
diff --git a/webengineviewer/src/networkmanager/interceptormanager.cpp b/webengineviewer/src/networkmanager/interceptormanager.cpp
index e0448f80..84c1cb81 100644
--- a/webengineviewer/src/networkmanager/interceptormanager.cpp
+++ b/webengineviewer/src/networkmanager/interceptormanager.cpp
@@ -1,75 +1,79 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "interceptormanager.h"
#include "urlinterceptor/networkurlinterceptormanager.h"
#include "urlinterceptor/networkurlinterceptor.h"
#include <QWebEngineProfile>
#include <QWebEngineView>
#include <WebEngineViewer/NetworkPluginUrlInterceptorInterface>
using namespace WebEngineViewer;
class WebEngineViewer::NetworkAccessManagerWebEnginePrivate
{
public:
NetworkAccessManagerWebEnginePrivate()
{
}
WebEngineViewer::NetworkUrlInterceptorManager *mManager = nullptr;
WebEngineViewer::NetworkUrlInterceptor *mNetworkUrlInterceptor = nullptr;
};
InterceptorManager::InterceptorManager(QWebEngineView *webEngine, KActionCollection *ac, QObject *parent)
: QObject(parent)
, d(new NetworkAccessManagerWebEnginePrivate)
{
d->mManager = new WebEngineViewer::NetworkUrlInterceptorManager(webEngine, ac, this);
// Add interceptor.
d->mNetworkUrlInterceptor = new WebEngineViewer::NetworkUrlInterceptor(this);
for (WebEngineViewer::NetworkPluginUrlInterceptorInterface *interface : d->mManager->interfaceList()) {
d->mNetworkUrlInterceptor->addInterceptor(interface);
}
+#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
webEngine->page()->profile()->setRequestInterceptor(d->mNetworkUrlInterceptor);
+#else
+ webEngine->page()->profile()->setUrlRequestInterceptor(d->mNetworkUrlInterceptor);
+#endif
}
InterceptorManager::~InterceptorManager()
{
delete d;
}
void InterceptorManager::addInterceptor(WebEngineViewer::NetworkPluginUrlInterceptorInterface *interceptor)
{
d->mNetworkUrlInterceptor->addInterceptor(interceptor);
}
void InterceptorManager::removeInterceptor(WebEngineViewer::NetworkPluginUrlInterceptorInterface *interceptor)
{
d->mNetworkUrlInterceptor->removeInterceptor(interceptor);
}
QList<QAction *> InterceptorManager::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
{
QList<QAction *> actions;
for (WebEngineViewer::NetworkPluginUrlInterceptorInterface *interface : d->mManager->interfaceList()) {
actions.append(interface->interceptorUrlActions(result));
}
return actions;
}
diff --git a/webengineviewer/src/networkmanager/interceptormanager.h b/webengineviewer/src/networkmanager/interceptormanager.h
index c7496c7f..addf7c49 100644
--- a/webengineviewer/src/networkmanager/interceptormanager.h
+++ b/webengineviewer/src/networkmanager/interceptormanager.h
@@ -1,45 +1,45 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef INTERCEPTORMANAGER_H
#define INTERCEPTORMANAGER_H
#include "webengineviewer_export.h"
#include "webengineviewer/networkpluginurlinterceptor.h"
class KActionCollection;
class QWebEngineView;
class QAction;
namespace WebEngineViewer {
class WebHitTestResult;
class NetworkPluginUrlInterceptorInterface;
class NetworkAccessManagerWebEnginePrivate;
class WEBENGINEVIEWER_EXPORT InterceptorManager : public QObject
{
Q_OBJECT
public:
explicit InterceptorManager(QWebEngineView *webEngine, KActionCollection *ac, QObject *parent = nullptr);
~InterceptorManager();
void addInterceptor(WebEngineViewer::NetworkPluginUrlInterceptorInterface *interceptor);
Q_REQUIRED_RESULT QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
void removeInterceptor(WebEngineViewer::NetworkPluginUrlInterceptorInterface *interceptor);
private:
NetworkAccessManagerWebEnginePrivate *const d;
};
}
#endif // INTERCEPTORMANAGER_H
diff --git a/webengineviewer/src/tests/CMakeLists.txt b/webengineviewer/src/tests/CMakeLists.txt
index e2b5b445..be392872 100644
--- a/webengineviewer/src/tests/CMakeLists.txt
+++ b/webengineviewer/src/tests/CMakeLists.txt
@@ -1,66 +1,54 @@
set(webengine_test_SRCS
testwebengine.cpp
)
add_executable(webenginetest ${webengine_test_SRCS})
target_link_libraries(webenginetest
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets
)
#####
set(webenginescript_test_SRCS
testwebenginescript.cpp
)
add_executable(webenginescripttest ${webenginescript_test_SRCS})
target_link_libraries(webenginescripttest
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets
)
####
set(selectedtextwebengine_test_SRCS
testselectedtextwebengine.cpp
)
add_executable(selectedtextwebenginetest ${selectedtextwebengine_test_SRCS})
target_link_libraries(selectedtextwebenginetest
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets
)
-###
-set(jquerysupportwebengine_test_SRCS
- testjquerysupportwebengine.cpp
- )
-
-add_executable(jquerysupportwebenginetest ${jquerysupportwebengine_test_SRCS})
-
-target_link_libraries(jquerysupportwebenginetest
- Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets
- )
-
-##
set(testdndwebengine_test_SRCS
testdndwebengine.cpp
)
add_executable(testdndwebenginetest ${testdndwebengine_test_SRCS})
target_link_libraries(testdndwebenginetest
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets
)
#####
set(testselectionchangedwebengine_test_SRCS
testselectionchangedwebengine.cpp
)
add_executable(testselectionchangedwebengine ${testselectionchangedwebengine_test_SRCS})
target_link_libraries(testselectionchangedwebengine
Qt5::Widgets KF5::WebEngineViewer Qt5::WebEngine Qt5::WebEngineWidgets KF5::XmlGui KF5::IconThemes KF5::MessageViewer
)
diff --git a/webengineviewer/src/tests/testdndwebengine.cpp b/webengineviewer/src/tests/testdndwebengine.cpp
index 7ee40385..1d6be9d5 100644
--- a/webengineviewer/src/tests/testdndwebengine.cpp
+++ b/webengineviewer/src/tests/testdndwebengine.cpp
@@ -1,88 +1,88 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testdndwebengine.h"
#include <QApplication>
#include <QVBoxLayout>
#include <webengineview.h>
#include <QTextEdit>
#include <QDebug>
TestDndWebEngine::TestDndWebEngine(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *layout = new QHBoxLayout;
vbox->addLayout(layout);
pageView = new WebEngineViewer::WebEngineView(this);
pageView->load(QUrl(QStringLiteral("http://www.planetkde.org")));
layout->addWidget(pageView);
webEngineView = new WebEngineViewBase(this);
webEngineView->load(QUrl(QStringLiteral("http://www.kde.org")));
layout->addWidget(webEngineView);
QTextEdit *edit = new QTextEdit(this);
vbox->addWidget(edit);
}
TestDndWebEngine::~TestDndWebEngine()
{
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestDndWebEngine *testWebEngine = new TestDndWebEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
WebEngineViewBase::WebEngineViewBase(QWidget *parent)
: QWebEngineView(parent)
{
qDebug() << "WebEngineViewBase::WebEngineViewBase(QWidget *parent)" << this;
}
void WebEngineViewBase::dragEnterEvent(QDragEnterEvent *e)
{
qDebug() << " void WebEngineViewBase::dragEnterEvent(QDragEnterEvent *e)";
QWebEngineView::dragEnterEvent(e);
}
void WebEngineViewBase::dragLeaveEvent(QDragLeaveEvent *e)
{
qDebug() << " void WebEngineViewBase::dragLeaveEvent(QDragEnterEvent *e)";
QWebEngineView::dragLeaveEvent(e);
}
void WebEngineViewBase::dragMoveEvent(QDragMoveEvent *e)
{
qDebug() << "void WebEngineViewBase::dragMoveEvent(QDragMoveEvent *e)";
QWebEngineView::dragMoveEvent(e);
}
void WebEngineViewBase::dropEvent(QDropEvent *e)
{
qDebug() << " void WebEngineViewBase::dropEvent(QDropEvent *e)";
QWebEngineView::dropEvent(e);
}
diff --git a/webengineviewer/src/tests/testdndwebengine.h b/webengineviewer/src/tests/testdndwebengine.h
index 25371a21..cf9f3301 100644
--- a/webengineviewer/src/tests/testdndwebengine.h
+++ b/webengineviewer/src/tests/testdndwebengine.h
@@ -1,53 +1,53 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TestDndWebEngine_H
#define TestDndWebEngine_H
#include <QWebEngineView>
#include <QWidget>
namespace WebEngineViewer {
class WebEngineView;
}
class WebEngineViewBase : public QWebEngineView
{
Q_OBJECT
public:
explicit WebEngineViewBase(QWidget *parent = nullptr);
protected:
void dragEnterEvent(QDragEnterEvent *e) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
void dragMoveEvent(QDragMoveEvent *e) override;
void dropEvent(QDropEvent *e) override;
};
class TestDndWebEngine : public QWidget
{
Q_OBJECT
public:
explicit TestDndWebEngine(QWidget *parent = nullptr);
~TestDndWebEngine();
private:
WebEngineViewer::WebEngineView *pageView = nullptr;
WebEngineViewBase *webEngineView = nullptr;
};
#endif // TestDndWebEngine_H
diff --git a/webengineviewer/src/tests/testjquerysupportwebengine.cpp b/webengineviewer/src/tests/testjquerysupportwebengine.cpp
deleted file mode 100644
index 476ff83a..00000000
--- a/webengineviewer/src/tests/testjquerysupportwebengine.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- Copyright (C) 2016-2018 Laurent Montel <montel@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.
-*/
-
-#include "testjquerysupportwebengine.h"
-#include <QApplication>
-#include <WebEngineViewer/WebEnginePage>
-#include <webengineview.h>
-#include <QPushButton>
-#include <QTextEdit>
-#include <QVBoxLayout>
-#include <QPointer>
-#include <WebEngineViewer/WebEngineManageScript>
-
-template<typename Arg, typename R, typename C>
-struct InvokeWrapper {
- QPointer<R> receiver;
- void (C::*memberFunction)(Arg);
- void operator()(Arg result)
- {
- if (receiver) {
- (receiver->*memberFunction)(result);
- }
- }
-};
-
-template<typename Arg, typename R, typename C>
-
-InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
-{
- InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
- return wrapper;
-}
-
-TestJQuerySupportWebEngine::TestJQuerySupportWebEngine(QWidget *parent)
- : QWidget(parent)
-{
- QVBoxLayout *vboxLayout = new QVBoxLayout(this);
- pageView = new WebEngineViewer::WebEngineView(this);
- WebEngineViewer::WebEnginePage *page = new WebEngineViewer::WebEnginePage(this);
- pageView->setPage(page);
- vboxLayout->addWidget(pageView);
-
- pageView->initializeJQueryScript();
- mEditor = new QTextEdit(this);
- mEditor->setAcceptRichText(false);
- mEditor->setPlainText(QStringLiteral(
- "qt.jQuery('img').each( function () { qt.jQuery(this).css('-webkit-transition', '-webkit-transform 2s'); qt.jQuery(this).css('-webkit-transform', 'rotate(180deg)') } ); undefined"));
- vboxLayout->addWidget(mEditor);
- connect(page, &WebEngineViewer::WebEnginePage::showConsoleMessage, this, &TestJQuerySupportWebEngine::slotShowConsoleMessage);
-
- QPushButton *executeQuery = new QPushButton(QStringLiteral("Execute Query"), this);
- connect(executeQuery, &QPushButton::clicked, this, &TestJQuerySupportWebEngine::slotExecuteQuery);
- vboxLayout->addWidget(executeQuery);
-
- pageView->load(QUrl(QStringLiteral("http://www.kde.org")));
-}
-
-TestJQuerySupportWebEngine::~TestJQuerySupportWebEngine()
-{
-}
-
-void TestJQuerySupportWebEngine::slotShowConsoleMessage(const QString &message)
-{
- qDebug() << "TestJQuerySupportWebEngine::slotShowConsoleMessage :" << message;
-}
-
-void TestJQuerySupportWebEngine::handleResultScript(const QVariant &var)
-{
- qDebug() << " void TestJQuerySupportWebEngine::handleResultScript(const QVariant &var)" << var;
-}
-
-void TestJQuerySupportWebEngine::slotExecuteQuery()
-{
- const QString code = mEditor->toPlainText();
- if (!code.isEmpty()) {
- pageView->page()->runJavaScript(code, WebEngineViewer::WebEngineManageScript::scriptWordId(),
- invoke(this, &TestJQuerySupportWebEngine::handleResultScript));
- }
-}
-
-int main(int argc, char *argv[])
-{
- QApplication app(argc, argv);
- app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
- TestJQuerySupportWebEngine *testWebEngine = new TestJQuerySupportWebEngine;
- testWebEngine->show();
- const int ret = app.exec();
- return ret;
-}
diff --git a/webengineviewer/src/tests/testjquerysupportwebengine.h b/webengineviewer/src/tests/testjquerysupportwebengine.h
deleted file mode 100644
index 94110385..00000000
--- a/webengineviewer/src/tests/testjquerysupportwebengine.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- Copyright (C) 2016-2018 Laurent Montel <montel@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.
-*/
-
-#ifndef TestJQuerySupportWebEngine_H
-#define TestJQuerySupportWebEngine_H
-
-#include <QWidget>
-namespace WebEngineViewer {
-class WebEngineView;
-}
-class QTextEdit;
-class TestJQuerySupportWebEngine : public QWidget
-{
- Q_OBJECT
-public:
- explicit TestJQuerySupportWebEngine(QWidget *parent = nullptr);
- ~TestJQuerySupportWebEngine();
-
-private Q_SLOTS:
- void slotExecuteQuery();
-
-private:
- void handleResultScript(const QVariant &var);
- void slotShowConsoleMessage(const QString &message);
- WebEngineViewer::WebEngineView *pageView;
- QTextEdit *mEditor;
-};
-
-#endif
diff --git a/webengineviewer/src/tests/testselectedtextwebengine.cpp b/webengineviewer/src/tests/testselectedtextwebengine.cpp
index 478999e2..d889680e 100644
--- a/webengineviewer/src/tests/testselectedtextwebengine.cpp
+++ b/webengineviewer/src/tests/testselectedtextwebengine.cpp
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testselectedtextwebengine.h"
#include <QApplication>
#include <WebEngineViewer/WebEnginePage>
#include <webengineview.h>
#include <QPushButton>
#include <QMessageBox>
#include <QVBoxLayout>
TestSelectedTextWebEngine::TestSelectedTextWebEngine(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *hboxLayout = new QVBoxLayout(this);
pageView = new WebEngineViewer::WebEngineView(this);
hboxLayout->addWidget(pageView);
QPushButton *showSelectedText = new QPushButton(QStringLiteral("Show Selected Text"), this);
connect(showSelectedText, &QPushButton::clicked, this, &TestSelectedTextWebEngine::slotSlowSelectedText);
hboxLayout->addWidget(showSelectedText);
mEnginePage = new WebEngineViewer::WebEnginePage(this);
//pageView->setPage(mEnginePage);
pageView->load(QUrl(QStringLiteral("http://www.kde.org")));
}
TestSelectedTextWebEngine::~TestSelectedTextWebEngine()
{
}
void TestSelectedTextWebEngine::slotSlowSelectedText()
{
QMessageBox::information(this, QStringLiteral("selected text"), pageView->selectedText());
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestSelectedTextWebEngine *testWebEngine = new TestSelectedTextWebEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/webengineviewer/src/tests/testselectedtextwebengine.h b/webengineviewer/src/tests/testselectedtextwebengine.h
index 139b4a73..03beae59 100644
--- a/webengineviewer/src/tests/testselectedtextwebengine.h
+++ b/webengineviewer/src/tests/testselectedtextwebengine.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTSELECTEDTEXTWEBENGINE_H
#define TESTSELECTEDTEXTWEBENGINE_H
#include <QWidget>
namespace WebEngineViewer {
class WebEnginePage;
class WebEngineView;
}
class TestSelectedTextWebEngine : public QWidget
{
Q_OBJECT
public:
explicit TestSelectedTextWebEngine(QWidget *parent = nullptr);
~TestSelectedTextWebEngine();
private Q_SLOTS:
void slotSlowSelectedText();
private:
WebEngineViewer::WebEnginePage *mEnginePage;
WebEngineViewer::WebEngineView *pageView;
};
#endif
diff --git a/webengineviewer/src/tests/testselectionchangedwebengine.cpp b/webengineviewer/src/tests/testselectionchangedwebengine.cpp
index d96b512b..022fcfc9 100644
--- a/webengineviewer/src/tests/testselectionchangedwebengine.cpp
+++ b/webengineviewer/src/tests/testselectionchangedwebengine.cpp
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#include "testselectionchangedwebengine.h"
#include <QApplication>
#include <QVBoxLayout>
#include <webengineview.h>
#include <QTextEdit>
#include <QDebug>
TestSelectionChangedEngine::TestSelectionChangedEngine(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vbox = new QVBoxLayout(this);
QHBoxLayout *layout = new QHBoxLayout;
vbox->addLayout(layout);
pageView = new WebEngineViewer::WebEngineView(this);
pageView->load(QUrl(QStringLiteral("http://www.planetkde.org")));
connect(pageView, &WebEngineViewer::WebEngineView::selectionChanged, this, &TestSelectionChangedEngine::slotSelectionChanged);
layout->addWidget(pageView);
QTextEdit *edit = new QTextEdit(this);
vbox->addWidget(edit);
}
TestSelectionChangedEngine::~TestSelectionChangedEngine()
{
}
void TestSelectionChangedEngine::slotSelectionChanged()
{
qDebug() << " void TestSelectionChangedEngine::slotSelectionChanged()";
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestSelectionChangedEngine *testWebEngine = new TestSelectionChangedEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/webengineviewer/src/tests/testselectionchangedwebengine.h b/webengineviewer/src/tests/testselectionchangedwebengine.h
index f6afe2fa..510af754 100644
--- a/webengineviewer/src/tests/testselectionchangedwebengine.h
+++ b/webengineviewer/src/tests/testselectionchangedwebengine.h
@@ -1,41 +1,41 @@
/*
- Copyright (C) 2017-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2017-2019 Laurent Montel <montel@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.
*/
#ifndef TestSelectionChangedWebEngine_H
#define TestSelectionChangedWebEngine_H
#include <QWidget>
namespace WebEngineViewer {
class WebEngineView;
}
class TestSelectionChangedEngine : public QWidget
{
Q_OBJECT
public:
explicit TestSelectionChangedEngine(QWidget *parent = nullptr);
~TestSelectionChangedEngine();
private Q_SLOTS:
void slotSelectionChanged();
private:
WebEngineViewer::WebEngineView *pageView;
};
#endif // TestSelectionChangedWebEngine_H
diff --git a/webengineviewer/src/tests/testwebengine.cpp b/webengineviewer/src/tests/testwebengine.cpp
index 68c6a6d2..6d01aac7 100644
--- a/webengineviewer/src/tests/testwebengine.cpp
+++ b/webengineviewer/src/tests/testwebengine.cpp
@@ -1,83 +1,83 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testwebengine.h"
#include <QHBoxLayout>
#include <QApplication>
#include <QWebEngineView>
#include <WebEngineViewer/WebHitTestResult>
#include <WebEngineViewer/WebEnginePage>
#include <QDebug>
#include <QWebEngineSettings>
#include <QContextMenuEvent>
#include <webengineviewer/webhittest.h>
TestWebEngine::TestWebEngine(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *hboxLayout = new QHBoxLayout(this);
TestWebEngineView *pageView = new TestWebEngineView(this);
hboxLayout->addWidget(pageView);
mEnginePage = new WebEngineViewer::WebEnginePage(this);
pageView->setPage(mEnginePage);
pageView->load(QUrl(QStringLiteral("http://www.kde.org")));
}
TestWebEngine::~TestWebEngine()
{
}
TestWebEngineView::TestWebEngineView(QWidget *parent)
: QWebEngineView(parent)
{
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
}
void TestWebEngineView::slotHitTestFinished(const WebEngineViewer::WebHitTestResult &result)
{
qDebug() << " alternateText" << result.alternateText();
qDebug() << " boundingRect" << result.boundingRect();
qDebug() << " imageUrl" << result.imageUrl();
qDebug() << " isContentEditable" << result.isContentEditable();
qDebug() << " isContentSelected" << result.isContentSelected();
qDebug() << " isNull" << result.isNull();
qDebug() << " linkTitle" << result.linkTitle();
qDebug() << " linkUrl" << result.linkUrl();
qDebug() << " mediaUrl" << result.mediaUrl();
qDebug() << " mediaPaused" << result.mediaPaused();
qDebug() << " mediaMuted" << result.mediaMuted();
qDebug() << " pos" << result.pos();
qDebug() << " tagName" << result.tagName();
}
void TestWebEngineView::contextMenuEvent(QContextMenuEvent *e)
{
WebEngineViewer::WebHitTest *webHit = static_cast<WebEngineViewer::WebEnginePage *>(page())->hitTestContent(e->pos());
connect(webHit, &WebEngineViewer::WebHitTest::finished, this, &TestWebEngineView::slotHitTestFinished);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestWebEngine *testWebEngine = new TestWebEngine;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
diff --git a/webengineviewer/src/tests/testwebengine.h b/webengineviewer/src/tests/testwebengine.h
index 1fafb259..ccc610c4 100644
--- a/webengineviewer/src/tests/testwebengine.h
+++ b/webengineviewer/src/tests/testwebengine.h
@@ -1,51 +1,51 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTWEBENGINE_H
#define TESTWEBENGINE_H
#include <QWidget>
-#include <qwebengineview.h>
+#include <QWebEngineView>
namespace WebEngineViewer {
class WebEnginePage;
class WebHitTestResult;
}
class TestWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
explicit TestWebEngineView(QWidget *parent = nullptr);
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
private Q_SLOTS:
void slotHitTestFinished(const WebEngineViewer::WebHitTestResult &result);
};
class TestWebEngine : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngine(QWidget *parent = nullptr);
~TestWebEngine();
private:
WebEngineViewer::WebEnginePage *mEnginePage;
};
#endif // TESTWEBENGINE_H
diff --git a/webengineviewer/src/tests/testwebenginescript.cpp b/webengineviewer/src/tests/testwebenginescript.cpp
index 076bd1f4..bcc8b087 100644
--- a/webengineviewer/src/tests/testwebenginescript.cpp
+++ b/webengineviewer/src/tests/testwebenginescript.cpp
@@ -1,187 +1,187 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "testwebenginescript.h"
#include "webenginescript.h"
#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QTextEdit>
#include <QWebEngineSettings>
#include <QComboBox>
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
TestWebEngineScriptView::TestWebEngineScriptView(QWidget *parent)
: QWebEngineView(parent)
{
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
}
TestWebEngineScriptPage::TestWebEngineScriptPage(QObject *parent)
: QWebEnginePage(parent)
{
settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
}
void TestWebEngineScriptPage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID)
{
Q_UNUSED(level);
Q_UNUSED(lineNumber);
Q_UNUSED(sourceID);
qDebug() << "JAVASCRIPT MESSAGE : " << message;
//TODO improve it.
Q_EMIT showConsoleMessage(message);
}
TestWebEngineScript::TestWebEngineScript(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *vboxLayout = new QVBoxLayout(this);
mTestWebEngine = new TestWebEngineScriptView(this);
TestWebEngineScriptPage *page = new TestWebEngineScriptPage(this);
mTestWebEngine->setPage(page);
vboxLayout->addWidget(mTestWebEngine);
mTestWebEngine->load(QUrl(QStringLiteral("http://www.kde.org")));
mTestScriptWidget = new TestScriptWidget(this);
vboxLayout->addWidget(mTestScriptWidget);
connect(mTestScriptWidget, &TestScriptWidget::executeScript, this, &TestWebEngineScript::slotExecuteScript);
connect(page, &TestWebEngineScriptPage::showConsoleMessage, this, &TestWebEngineScript::slotShowConsoleMessage);
}
TestWebEngineScript::~TestWebEngineScript()
{
}
void TestWebEngineScript::slotShowConsoleMessage(const QString &msg)
{
Q_UNUSED(msg);
}
void TestWebEngineScript::handleScript(const QVariant &res)
{
qDebug() << " res" << res;
//TODO
//mTestScriptWidget->setResult();
}
void TestWebEngineScript::slotExecuteScript()
{
const QString script = mTestScriptWidget->script();
if (!script.isEmpty()) {
qDebug() << " script" << script;
mTestWebEngine->page()->runJavaScript(script, invoke(this, &TestWebEngineScript::handleScript));
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_UseHighDpiPixmaps, true);
TestWebEngineScript *testWebEngine = new TestWebEngineScript;
testWebEngine->show();
const int ret = app.exec();
return ret;
}
TestScriptWidget::TestScriptWidget(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout(this);
QVBoxLayout *vScriptLayout = new QVBoxLayout;
layout->addLayout(vScriptLayout);
mScriptCombo = new QComboBox;
vScriptLayout->addWidget(mScriptCombo);
fillScriptCombo(mScriptCombo);
- connect(mScriptCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &TestScriptWidget::slotCurrentIndexChanged);
+ connect(mScriptCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, &TestScriptWidget::slotCurrentIndexChanged);
mScriptEdit = new QTextEdit;
mScriptEdit->setAcceptRichText(false);
vScriptLayout->addWidget(mScriptEdit);
QVBoxLayout *vboxLayout = new QVBoxLayout;
layout->addLayout(vboxLayout);
mResultEdit = new QTextEdit;
mResultEdit->setReadOnly(true);
vboxLayout->addWidget(mResultEdit);
QPushButton *button = new QPushButton(QStringLiteral("Execute Script"), this);
connect(button, &QPushButton::clicked, this, &TestScriptWidget::executeScript);
vboxLayout->addWidget(button);
}
void TestScriptWidget::slotCurrentIndexChanged(int index)
{
if (index != -1) {
const QString str = mScriptCombo->currentData().toString();
mScriptEdit->setText(str);
}
}
void TestScriptWidget::fillScriptCombo(QComboBox *scriptCombo)
{
scriptCombo->addItem(QStringLiteral("find all images"), WebEngineViewer::WebEngineScript::findAllImages());
scriptCombo->addItem(QStringLiteral("find all scripts"), WebEngineViewer::WebEngineScript::findAllScripts());
scriptCombo->addItem(QStringLiteral("find all anchors"), WebEngineViewer::WebEngineScript::findAllAnchors());
scriptCombo->addItem(QStringLiteral("find all anchors and forms"), WebEngineViewer::WebEngineScript::findAllAnchorsAndForms());
scriptCombo->addItem(QStringLiteral("search element position"), WebEngineViewer::WebEngineScript::searchElementPosition(QStringLiteral("elements")));
scriptCombo->addItem(QStringLiteral("scroll to position"), WebEngineViewer::WebEngineScript::scrollToPosition(QPoint()));
scriptCombo->addItem(QStringLiteral("scroll down"), WebEngineViewer::WebEngineScript::scrollDown(0));
scriptCombo->addItem(QStringLiteral("scroll up"), WebEngineViewer::WebEngineScript::scrollUp(0));
scriptCombo->addItem(QStringLiteral("scroll percentage"), WebEngineViewer::WebEngineScript::scrollPercentage(0));
scriptCombo->addItem(QStringLiteral("Test is bottom"), WebEngineViewer::WebEngineScript::isScrolledToBottom());
#if 0
WebEngineViewer::WebEngineScript::setElementByIdVisible(const QString &elementStr, bool visibility);
WebEngineViewer::WebEngineScript::setStyleToElement(const QString &elementStr, const QString &style);
WebEngineViewer::WebEngineScript::scrollToRelativePosition(int pos);
WebEngineViewer::WebEngineScript::removeStyleToElement(const QString &element);
WebEngineViewer::WebEngineScript::replaceInnerHtml(const QString &field, const QString &html, bool doShow);
#endif
//TODO
}
void TestScriptWidget::setResult(const QString &res)
{
mResultEdit->setText(res);
}
QString TestScriptWidget::script() const
{
return mScriptEdit->toPlainText();
}
diff --git a/webengineviewer/src/tests/testwebenginescript.h b/webengineviewer/src/tests/testwebenginescript.h
index 658dc87d..acf4bbf1 100644
--- a/webengineviewer/src/tests/testwebenginescript.h
+++ b/webengineviewer/src/tests/testwebenginescript.h
@@ -1,81 +1,81 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef TESTWEBENGINESCRIPT_H
#define TESTWEBENGINESCRIPT_H
#include <QWebEngineView>
#include <QWidget>
class QTextEdit;
class QComboBox;
class TestScriptWidget : public QWidget
{
Q_OBJECT
public:
explicit TestScriptWidget(QWidget *parent = nullptr);
void setResult(const QString &res);
QString script() const;
Q_SIGNALS:
void executeScript();
private Q_SLOTS:
void slotCurrentIndexChanged(int);
private:
void fillScriptCombo(QComboBox *scriptCombo);
QTextEdit *mScriptEdit;
QTextEdit *mResultEdit;
QComboBox *mScriptCombo;
};
class TestWebEngineScriptPage : public QWebEnginePage
{
Q_OBJECT
public:
explicit TestWebEngineScriptPage(QObject *parent = nullptr);
protected:
void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) override;
Q_SIGNALS:
void showConsoleMessage(const QString &msg);
};
class TestWebEngineScriptView : public QWebEngineView
{
Q_OBJECT
public:
explicit TestWebEngineScriptView(QWidget *parent = nullptr);
};
class TestWebEngineScript : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngineScript(QWidget *parent = nullptr);
~TestWebEngineScript();
private Q_SLOTS:
void slotExecuteScript();
void handleScript(const QVariant &res);
void slotShowConsoleMessage(const QString &msg);
private:
TestScriptWidget *mTestScriptWidget;
TestWebEngineScriptView *mTestWebEngine;
};
#endif // TESTWEBENGINESCRIPT_H
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.cpp b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.cpp
index ab092a72..77fcb2a0 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.cpp
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.cpp
@@ -1,51 +1,51 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptor.h"
using namespace WebEngineViewer;
NetworkPluginUrlInterceptor::NetworkPluginUrlInterceptor(QObject *parent)
: QObject(parent)
{
}
NetworkPluginUrlInterceptor::~NetworkPluginUrlInterceptor()
{
}
bool NetworkPluginUrlInterceptor::hasConfigureDialog() const
{
return false;
}
void NetworkPluginUrlInterceptor::showConfigureDialog(QWidget *parent)
{
Q_UNUSED(parent);
}
void NetworkPluginUrlInterceptor::setIsEnabled(bool enabled)
{
mIsEnabled = enabled;
}
bool NetworkPluginUrlInterceptor::isEnabled() const
{
return mIsEnabled;
}
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.h b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.h
index 7d69f061..63bf4eed 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.h
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptor.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILNETWORKPLUGINURLINTERCEPTOR_H
#define MAILNETWORKPLUGINURLINTERCEPTOR_H
#include <QObject>
#include "webengineviewer_export.h"
class QWebEngineView;
namespace WebEngineViewer {
class NetworkPluginUrlInterceptorInterface;
class WEBENGINEVIEWER_EXPORT NetworkPluginUrlInterceptor : public QObject
{
Q_OBJECT
public:
explicit NetworkPluginUrlInterceptor(QObject *parent = nullptr);
~NetworkPluginUrlInterceptor();
virtual NetworkPluginUrlInterceptorInterface *createInterface(QWebEngineView *webEngine, QObject *parent = nullptr) = 0;
virtual bool hasConfigureDialog() const;
virtual void showConfigureDialog(QWidget *parent = nullptr);
void setIsEnabled(bool enabled);
Q_REQUIRED_RESULT bool isEnabled() const;
private:
bool mIsEnabled = false;
};
}
#endif // MAILNETWORKPLUGINURLINTERCEPTOR_H
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
index b5d6dc05..2d6fa522 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.cpp
@@ -1,36 +1,36 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptorconfigurewidget.h"
using namespace WebEngineViewer;
NetworkPluginUrlInterceptorConfigureWidget::NetworkPluginUrlInterceptorConfigureWidget(QWidget *parent)
: QWidget(parent)
{
}
NetworkPluginUrlInterceptorConfigureWidget::~NetworkPluginUrlInterceptorConfigureWidget()
{
}
QString NetworkPluginUrlInterceptorConfigureWidget::helpAnchor() const
{
return QString();
}
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.h b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.h
index d66faa30..3357cdeb 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.h
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorconfigurewidget.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef NETWORKPLUGINURLINTERCEPTORCONFIGUREWIDGET_H
#define NETWORKPLUGINURLINTERCEPTORCONFIGUREWIDGET_H
#include <QWidget>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class WEBENGINEVIEWER_EXPORT NetworkPluginUrlInterceptorConfigureWidget : public QWidget
{
Q_OBJECT
public:
explicit NetworkPluginUrlInterceptorConfigureWidget(QWidget *parent = nullptr);
~NetworkPluginUrlInterceptorConfigureWidget();
virtual void loadSettings() = 0;
virtual void saveSettings() = 0;
virtual void resetSettings() = 0;
virtual QString helpAnchor() const;
Q_SIGNALS:
void configureChanged();
};
}
#endif // NETWORKPLUGINURLINTERCEPTORCONFIGUREWIDGET_H
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.cpp b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.cpp
index 85c373f4..61172234 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.cpp
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.cpp
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptorinterface.h"
using namespace WebEngineViewer;
NetworkPluginUrlInterceptorInterface::NetworkPluginUrlInterceptorInterface(QObject *parent)
: QObject(parent)
{
}
NetworkPluginUrlInterceptorInterface::~NetworkPluginUrlInterceptorInterface()
{
}
void NetworkPluginUrlInterceptorInterface::createActions(KActionCollection *ac)
{
Q_UNUSED(ac);
}
QList<QAction *> NetworkPluginUrlInterceptorInterface::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
{
Q_UNUSED(result);
return {};
}
diff --git a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.h b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.h
index 97d5fe87..2d3493dc 100644
--- a/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.h
+++ b/webengineviewer/src/urlinterceptor/networkpluginurlinterceptorinterface.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILNETWORKPLUGINURLINTERCEPTORINTERFACE_H
#define MAILNETWORKPLUGINURLINTERCEPTORINTERFACE_H
#include <QObject>
#include "webengineviewer_export.h"
class QWebEngineUrlRequestInfo;
class KActionCollection;
class QAction;
namespace WebEngineViewer {
class WebHitTestResult;
class WEBENGINEVIEWER_EXPORT NetworkPluginUrlInterceptorInterface : public QObject
{
Q_OBJECT
public:
explicit NetworkPluginUrlInterceptorInterface(QObject *parent = nullptr);
~NetworkPluginUrlInterceptorInterface();
virtual void createActions(KActionCollection *ac);
virtual QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
virtual bool interceptRequest(QWebEngineUrlRequestInfo &info) = 0;
};
}
#endif // MAILNETWORKPLUGINURLINTERCEPTORINTERFACE_H
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptor.cpp b/webengineviewer/src/urlinterceptor/networkurlinterceptor.cpp
index 252ef77f..4fd6489a 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptor.cpp
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptor.cpp
@@ -1,89 +1,89 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptorinterface.h"
#include "networkurlinterceptor.h"
#include "webengineviewer_debug.h"
#include <QVector>
using namespace WebEngineViewer;
class WebEngineViewer::NetworkUrlInterceptorPrivate
{
public:
NetworkUrlInterceptorPrivate()
{
}
void addInterceptor(NetworkPluginUrlInterceptorInterface *interceptor);
void interceptRequest(QWebEngineUrlRequestInfo &info);
void removeInterceptor(NetworkPluginUrlInterceptorInterface *interceptor);
QVector<NetworkPluginUrlInterceptorInterface *> listInterceptor;
};
void NetworkUrlInterceptorPrivate::addInterceptor(NetworkPluginUrlInterceptorInterface *interceptor)
{
if (!listInterceptor.contains(interceptor)) {
listInterceptor.append(interceptor);
}
}
void NetworkUrlInterceptorPrivate::removeInterceptor(NetworkPluginUrlInterceptorInterface *interceptor)
{
if (listInterceptor.contains(interceptor)) {
listInterceptor.removeOne(interceptor);
}
}
void NetworkUrlInterceptorPrivate::interceptRequest(QWebEngineUrlRequestInfo &info)
{
for (NetworkPluginUrlInterceptorInterface *inter : qAsConst(listInterceptor)) {
if (inter->interceptRequest(info)) {
info.block(true);
break;
}
}
}
NetworkUrlInterceptor::NetworkUrlInterceptor(QObject *parent)
: QWebEngineUrlRequestInterceptor(parent)
, d(new NetworkUrlInterceptorPrivate)
{
}
NetworkUrlInterceptor::~NetworkUrlInterceptor()
{
delete d;
}
void NetworkUrlInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
d->interceptRequest(info);
}
void NetworkUrlInterceptor::addInterceptor(NetworkPluginUrlInterceptorInterface *interceptor)
{
d->addInterceptor(interceptor);
}
void NetworkUrlInterceptor::removeInterceptor(NetworkPluginUrlInterceptorInterface *interceptor)
{
d->removeInterceptor(interceptor);
}
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptor.h b/webengineviewer/src/urlinterceptor/networkurlinterceptor.h
index 1b44fd35..891dd597 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptor.h
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptor.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILNETWORKURLINTERCEPTOR_H
#define MAILNETWORKURLINTERCEPTOR_H
#include <QWebEngineUrlRequestInterceptor>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class NetworkUrlInterceptorPrivate;
class NetworkPluginUrlInterceptorInterface;
class WEBENGINEVIEWER_EXPORT NetworkUrlInterceptor : public QWebEngineUrlRequestInterceptor
{
Q_OBJECT
public:
explicit NetworkUrlInterceptor(QObject *parent = nullptr);
~NetworkUrlInterceptor() override;
void interceptRequest(QWebEngineUrlRequestInfo &info) override;
void addInterceptor(NetworkPluginUrlInterceptorInterface *interceptor);
void removeInterceptor(NetworkPluginUrlInterceptorInterface *interceptor);
private:
NetworkUrlInterceptorPrivate *const d;
};
}
#endif // MAILNETWORKURLINTERCEPTOR_H
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.cpp b/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.cpp
index c9f0fd6a..c4af087d 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.cpp
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.cpp
@@ -1,83 +1,83 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptorinterface.h"
#include "networkurlinterceptormanager.h"
#include "networkurlinterceptorpluginmanager.h"
#include <WebEngineViewer/WebHitTestResult>
using namespace WebEngineViewer;
class WebEngineViewer::NetworkUrlInterceptorManagerPrivate
{
public:
NetworkUrlInterceptorManagerPrivate(QWebEngineView *webEngine, KActionCollection *ac, NetworkUrlInterceptorManager *qq)
: q(qq)
{
createInterfaces(webEngine, ac);
}
QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
void createInterfaces(QWebEngineView *webEngine, KActionCollection *ac);
QVector<WebEngineViewer::NetworkPluginUrlInterceptorInterface *> mListInterface;
private:
NetworkUrlInterceptorManager *q;
};
QList<QAction *> NetworkUrlInterceptorManagerPrivate::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
{
QList<QAction *> lstActions;
for (WebEngineViewer::NetworkPluginUrlInterceptorInterface *interface : qAsConst(mListInterface)) {
lstActions.append(interface->interceptorUrlActions(result));
}
return lstActions;
}
void NetworkUrlInterceptorManagerPrivate::createInterfaces(QWebEngineView *webEngine, KActionCollection *ac)
{
for (NetworkPluginUrlInterceptor *plugin : NetworkUrlInterceptorPluginManager::self()->pluginsList()) {
if (plugin->isEnabled()) {
WebEngineViewer::NetworkPluginUrlInterceptorInterface *interface = plugin->createInterface(webEngine, q);
interface->createActions(ac);
mListInterface.append(interface);
}
}
}
NetworkUrlInterceptorManager::NetworkUrlInterceptorManager(QWebEngineView *webEngine, KActionCollection *ac, QObject *parent)
: QObject(parent)
, d(new NetworkUrlInterceptorManagerPrivate(webEngine, ac, this))
{
}
NetworkUrlInterceptorManager::~NetworkUrlInterceptorManager()
{
delete d;
}
QVector<WebEngineViewer::NetworkPluginUrlInterceptorInterface *> NetworkUrlInterceptorManager::interfaceList() const
{
return d->mListInterface;
}
QList<QAction *> NetworkUrlInterceptorManager::interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const
{
return d->interceptorUrlActions(result);
}
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.h b/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.h
index ac11a0e8..a36834e2 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.h
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptormanager.h
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILNETWORKURLINTERCEPTORMANAGER_H
#define MAILNETWORKURLINTERCEPTORMANAGER_H
#include <QObject>
#include "networkpluginurlinterceptor.h"
#include "networkpluginurlinterceptorinterface.h"
#include "webengineviewer_export.h"
#include <QVector>
#include <WebEngineViewer/WebHitTestResult>
class KActionCollection;
class QWebEngineView;
namespace WebEngineViewer {
class WebHitTestResult;
class NetworkUrlInterceptorManagerPrivate;
class WEBENGINEVIEWER_EXPORT NetworkUrlInterceptorManager : public QObject
{
Q_OBJECT
public:
explicit NetworkUrlInterceptorManager(QWebEngineView *webEngine, KActionCollection *ac, QObject *parent = nullptr);
~NetworkUrlInterceptorManager();
Q_REQUIRED_RESULT QVector<NetworkPluginUrlInterceptorInterface *> interfaceList() const;
Q_REQUIRED_RESULT QList<QAction *> interceptorUrlActions(const WebEngineViewer::WebHitTestResult &result) const;
private:
NetworkUrlInterceptorManagerPrivate *const d;
};
}
#endif // MAILNETWORKURLINTERCEPTORMANAGER_H
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.cpp b/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.cpp
index e119eada..2b328739 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.cpp
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.cpp
@@ -1,208 +1,208 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "networkpluginurlinterceptor.h"
#include "networkurlinterceptorpluginmanager.h"
#include "webengineviewer_debug.h"
#include <KPluginLoader>
#include <KPluginFactory>
#include <QFileInfo>
#include <QVariant>
#include <QSet>
#include <kpluginmetadata.h>
using namespace WebEngineViewer;
class MailNetworkUrlInterceptorPluginInfo
{
public:
MailNetworkUrlInterceptorPluginInfo()
{
}
QString metaDataFileNameBaseName;
QString metaDataFileName;
PimCommon::PluginUtilData pluginData;
WebEngineViewer::NetworkPluginUrlInterceptor *plugin = nullptr;
bool isEnabled = false;
};
namespace {
QString pluginVersion()
{
return QStringLiteral("1.0");
}
}
class WebEngineViewer::NetworkUrlInterceptorPluginManagerPrivate
{
public:
NetworkUrlInterceptorPluginManagerPrivate(NetworkUrlInterceptorPluginManager *qq)
: q(qq)
{
}
void initializePluginList();
void loadPlugin(MailNetworkUrlInterceptorPluginInfo *item);
QVector<WebEngineViewer::NetworkPluginUrlInterceptor *> pluginsList() const;
QString configGroupName() const;
QString configPrefixSettingKey() const;
QVector<PimCommon::PluginUtilData> pluginDataList() const;
NetworkPluginUrlInterceptor *pluginFromIdentifier(const QString &id);
private:
QVector<MailNetworkUrlInterceptorPluginInfo> mPluginList;
QVector<PimCommon::PluginUtilData> mPluginDataList;
NetworkUrlInterceptorPluginManager *q;
};
QString NetworkUrlInterceptorPluginManagerPrivate::configGroupName() const
{
return QStringLiteral("NetworkUrlInterceptorPlugins");
}
QString NetworkUrlInterceptorPluginManagerPrivate::configPrefixSettingKey() const
{
return QStringLiteral("PluginsNetworkUrlInterceptor");
}
QVector<PimCommon::PluginUtilData> NetworkUrlInterceptorPluginManagerPrivate::pluginDataList() const
{
return mPluginDataList;
}
void NetworkUrlInterceptorPluginManagerPrivate::initializePluginList()
{
const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("webengineviewer"), [](const KPluginMetaData &md) {
return md.serviceTypes().contains(QLatin1String("WebEngineViewer/UrlInterceptor"));
});
QVectorIterator<KPluginMetaData> i(plugins);
i.toBack();
QSet<QString> unique;
const QPair<QStringList, QStringList> pair = PimCommon::PluginUtil::loadPluginSetting(configGroupName(), configPrefixSettingKey());
while (i.hasPrevious()) {
MailNetworkUrlInterceptorPluginInfo info;
const KPluginMetaData data = i.previous();
//1) get plugin data => name/description etc.
info.pluginData = PimCommon::PluginUtil::createPluginMetaData(data);
//2) look at if plugin is activated
const bool isPluginActivated = PimCommon::PluginUtil::isPluginActivated(pair.first, pair.second, info.pluginData.mEnableByDefault, info.pluginData.mIdentifier);
info.isEnabled = isPluginActivated;
info.metaDataFileNameBaseName = QFileInfo(data.fileName()).baseName();
info.metaDataFileName = data.fileName();
if (pluginVersion() == data.version()) {
// only load plugins once, even if found multiple times!
if (unique.contains(info.metaDataFileNameBaseName)) {
continue;
}
info.plugin = nullptr;
mPluginList.push_back(info);
unique.insert(info.metaDataFileNameBaseName);
} else {
qCWarning(WEBENGINEVIEWER_LOG) << "Plugin " << data.name() << " doesn't have correction plugin version. It will not be loaded.";
}
}
QVector<MailNetworkUrlInterceptorPluginInfo>::iterator end(mPluginList.end());
for (QVector<MailNetworkUrlInterceptorPluginInfo>::iterator it = mPluginList.begin(); it != end; ++it) {
loadPlugin(&(*it));
}
}
QVector<WebEngineViewer::NetworkPluginUrlInterceptor *> NetworkUrlInterceptorPluginManagerPrivate::pluginsList() const
{
QVector<WebEngineViewer::NetworkPluginUrlInterceptor *> lst;
QVector<MailNetworkUrlInterceptorPluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<MailNetworkUrlInterceptorPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if (auto plugin = (*it).plugin) {
lst << plugin;
}
}
return lst;
}
void NetworkUrlInterceptorPluginManagerPrivate::loadPlugin(MailNetworkUrlInterceptorPluginInfo *item)
{
KPluginLoader pluginLoader(item->metaDataFileName);
if (pluginLoader.factory()) {
item->plugin = pluginLoader.factory()->create<WebEngineViewer::NetworkPluginUrlInterceptor>(q, QVariantList() << item->metaDataFileNameBaseName);
item->plugin->setIsEnabled(item->isEnabled);
item->pluginData.mHasConfigureDialog = item->plugin->hasConfigureDialog();
mPluginDataList.append(item->pluginData);
}
}
WebEngineViewer::NetworkPluginUrlInterceptor *NetworkUrlInterceptorPluginManagerPrivate::pluginFromIdentifier(const QString &id)
{
QVector<MailNetworkUrlInterceptorPluginInfo>::ConstIterator end(mPluginList.constEnd());
for (QVector<MailNetworkUrlInterceptorPluginInfo>::ConstIterator it = mPluginList.constBegin(); it != end; ++it) {
if ((*it).pluginData.mIdentifier == id) {
return (*it).plugin;
}
}
return {};
}
NetworkUrlInterceptorPluginManager *NetworkUrlInterceptorPluginManager::self()
{
static NetworkUrlInterceptorPluginManager s_self;
return &s_self;
}
NetworkUrlInterceptorPluginManager::NetworkUrlInterceptorPluginManager(QObject *parent)
: QObject(parent)
, d(new NetworkUrlInterceptorPluginManagerPrivate(this))
{
d->initializePluginList();
}
NetworkUrlInterceptorPluginManager::~NetworkUrlInterceptorPluginManager()
{
delete d;
}
QVector<WebEngineViewer::NetworkPluginUrlInterceptor *> NetworkUrlInterceptorPluginManager::pluginsList() const
{
return d->pluginsList();
}
QString NetworkUrlInterceptorPluginManager::configGroupName() const
{
return d->configGroupName();
}
QString NetworkUrlInterceptorPluginManager::configPrefixSettingKey() const
{
return d->configPrefixSettingKey();
}
QVector<PimCommon::PluginUtilData> NetworkUrlInterceptorPluginManager::pluginsDataList() const
{
return d->pluginDataList();
}
WebEngineViewer::NetworkPluginUrlInterceptor *NetworkUrlInterceptorPluginManager::pluginFromIdentifier(const QString &id)
{
return d->pluginFromIdentifier(id);
}
diff --git a/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.h b/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.h
index ff4dc22d..33497871 100644
--- a/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.h
+++ b/webengineviewer/src/urlinterceptor/networkurlinterceptorpluginmanager.h
@@ -1,48 +1,48 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILNETWORKURLINTERCEPTORPLUGINMANAGER_H
#define MAILNETWORKURLINTERCEPTORPLUGINMANAGER_H
#include <QObject>
#include <QVector>
#include <PimCommon/PluginUtil>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class NetworkUrlInterceptorPluginManagerPrivate;
class NetworkPluginUrlInterceptor;
class WEBENGINEVIEWER_EXPORT NetworkUrlInterceptorPluginManager : public QObject
{
Q_OBJECT
public:
static NetworkUrlInterceptorPluginManager *self();
explicit NetworkUrlInterceptorPluginManager(QObject *parent = nullptr);
~NetworkUrlInterceptorPluginManager();
Q_REQUIRED_RESULT QVector<WebEngineViewer::NetworkPluginUrlInterceptor *> pluginsList() const;
Q_REQUIRED_RESULT QString configGroupName() const;
Q_REQUIRED_RESULT QString configPrefixSettingKey() const;
Q_REQUIRED_RESULT QVector<PimCommon::PluginUtilData> pluginsDataList() const;
Q_REQUIRED_RESULT WebEngineViewer::NetworkPluginUrlInterceptor *pluginFromIdentifier(const QString &id);
private:
NetworkUrlInterceptorPluginManagerPrivate *const d;
};
}
#endif // MAILNETWORKURLINTERCEPTORPLUGINMANAGER_H
diff --git a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp
index 78075210..73cf2a4d 100644
--- a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp
+++ b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.cpp
@@ -1,123 +1,123 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineaccesskeyanchorfromhtmltest.h"
#include "../webengineaccesskeyutils.h"
#include <WebEngineViewer/WebEngineManageScript>
#include <QTest>
#include <QHBoxLayout>
#include <QWebEngineView>
#include <QSignalSpy>
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
TestWebEngineAccessKey::TestWebEngineAccessKey(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *hbox = new QHBoxLayout(this);
mEngineView = new QWebEngineView(this);
connect(mEngineView, &QWebEngineView::loadFinished, this, &TestWebEngineAccessKey::loadFinished);
hbox->addWidget(mEngineView);
}
TestWebEngineAccessKey::~TestWebEngineAccessKey()
{
}
void TestWebEngineAccessKey::setHtml(const QString &html)
{
mEngineView->setHtml(html);
}
void TestWebEngineAccessKey::handleSearchAccessKey(const QVariant &var)
{
const QVariantList lst = var.toList();
QVector<WebEngineViewer::WebEngineAccessKeyAnchor> anchorList;
anchorList.reserve(lst.count());
for (const QVariant &anchor : lst) {
anchorList << WebEngineViewer::WebEngineAccessKeyAnchor(anchor);
}
Q_EMIT accessKeySearchFinished(anchorList);
}
void TestWebEngineAccessKey::loadFinished(bool b)
{
Q_UNUSED(b);
mEngineView->page()->runJavaScript(WebEngineViewer::WebEngineAccessKeyUtils::script(),
WebEngineViewer::WebEngineManageScript::scriptWordId(),
invoke(this, &TestWebEngineAccessKey::handleSearchAccessKey));
}
Q_DECLARE_METATYPE(QVector<WebEngineViewer::WebEngineAccessKeyAnchor>)
WebEngineAccessKeyAnchorFromHtmlTest::WebEngineAccessKeyAnchorFromHtmlTest(QObject *parent)
: QObject(parent)
{
qRegisterMetaType<QVector<WebEngineViewer::WebEngineAccessKeyAnchor> >();
}
void WebEngineAccessKeyAnchorFromHtmlTest::shouldNotShowAccessKeyWhenHtmlAsNotAnchor()
{
TestWebEngineAccessKey w;
- QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector<WebEngineViewer::WebEngineAccessKeyAnchor>)));
+ QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished);
w.setHtml(QStringLiteral("<body>foo</body>"));
QVERIFY(accessKeySpy.wait());
QCOMPARE(accessKeySpy.count(), 1);
const QVector<WebEngineViewer::WebEngineAccessKeyAnchor> resultLst = accessKeySpy.at(0).at(0).value<QVector<WebEngineViewer::WebEngineAccessKeyAnchor> >();
QCOMPARE(resultLst.count(), 0);
}
void WebEngineAccessKeyAnchorFromHtmlTest::shouldReturnOneAnchor()
{
TestWebEngineAccessKey w;
- QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector<WebEngineViewer::WebEngineAccessKeyAnchor>)));
+ QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished);
w.setHtml(QStringLiteral("<body>foo<a href=\"http://www.kde.org\">foo</a></body>"));
QVERIFY(accessKeySpy.wait());
QCOMPARE(accessKeySpy.count(), 1);
const QVector<WebEngineViewer::WebEngineAccessKeyAnchor> resultLst = accessKeySpy.at(0).at(0).value<QVector<WebEngineViewer::WebEngineAccessKeyAnchor> >();
QCOMPARE(resultLst.count(), 1);
}
void WebEngineAccessKeyAnchorFromHtmlTest::shouldReturnTwoAnchor()
{
TestWebEngineAccessKey w;
- QSignalSpy accessKeySpy(&w, SIGNAL(accessKeySearchFinished(QVector<WebEngineViewer::WebEngineAccessKeyAnchor>)));
+ QSignalSpy accessKeySpy(&w, &TestWebEngineAccessKey::accessKeySearchFinished);
w.setHtml(QStringLiteral("<body>foo<a href=\"http://www.kde.org\">foo</a><a href=\"http://www.kde.vv\">foo</a></body>"));
QVERIFY(accessKeySpy.wait());
QCOMPARE(accessKeySpy.count(), 1);
const QVector<WebEngineViewer::WebEngineAccessKeyAnchor> resultLst = accessKeySpy.at(0).at(0).value<QVector<WebEngineViewer::WebEngineAccessKeyAnchor> >();
QCOMPARE(resultLst.count(), 2);
}
QTEST_MAIN(WebEngineAccessKeyAnchorFromHtmlTest)
diff --git a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.h b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.h
index 1ca0ebd3..85a51fbc 100644
--- a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.h
+++ b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchorfromhtmltest.h
@@ -1,57 +1,57 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEACCESSKEYANCHORFROMHTMLTEST_H
#define MAILWEBENGINEACCESSKEYANCHORFROMHTMLTEST_H
#include <QObject>
#include <QWidget>
#include "../webengineaccesskeyanchor.h"
class QWebEngineView;
class TestWebEngineAccessKey : public QWidget
{
Q_OBJECT
public:
explicit TestWebEngineAccessKey(QWidget *parent = nullptr);
~TestWebEngineAccessKey();
void setHtml(const QString &html);
private Q_SLOTS:
void loadFinished(bool b);
void handleSearchAccessKey(const QVariant &var);
Q_SIGNALS:
void accessKeySearchFinished(const QVector<WebEngineViewer::WebEngineAccessKeyAnchor> &var);
private:
QWebEngineView *mEngineView = nullptr;
};
class WebEngineAccessKeyAnchorFromHtmlTest : public QObject
{
Q_OBJECT
public:
explicit WebEngineAccessKeyAnchorFromHtmlTest(QObject *parent = nullptr);
private Q_SLOTS:
void shouldNotShowAccessKeyWhenHtmlAsNotAnchor();
void shouldReturnOneAnchor();
void shouldReturnTwoAnchor();
};
#endif // MAILWEBENGINEACCESSKEYANCHORFROMHTMLTEST_H
diff --git a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.cpp b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.cpp
index f6eae04c..183cbec7 100644
--- a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.cpp
+++ b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.cpp
@@ -1,45 +1,45 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineaccesskeyanchortest.h"
#include "../webengineaccesskeyanchor.h"
#include <QTest>
WebEngineAccessKeyAnchorTest::WebEngineAccessKeyAnchorTest(QObject *parent)
: QObject(parent)
{
}
WebEngineAccessKeyAnchorTest::~WebEngineAccessKeyAnchorTest()
{
}
void WebEngineAccessKeyAnchorTest::shouldReturnEmptyAccessKeyAnchor()
{
QVariant var;
WebEngineViewer::WebEngineAccessKeyAnchor accessKeyAnchor(var);
QVERIFY(accessKeyAnchor.href().isEmpty());
QVERIFY(accessKeyAnchor.accessKey().isEmpty());
QVERIFY(accessKeyAnchor.boundingRect().isEmpty());
QVERIFY(accessKeyAnchor.tagName().isEmpty());
QVERIFY(accessKeyAnchor.target().isEmpty());
QVERIFY(accessKeyAnchor.innerText().isEmpty());
}
QTEST_APPLESS_MAIN(WebEngineAccessKeyAnchorTest)
diff --git a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.h b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.h
index 5960ebaa..b3b8e154 100644
--- a/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.h
+++ b/webengineviewer/src/webengineaccesskey/autotests/webengineaccesskeyanchortest.h
@@ -1,35 +1,35 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEACCESSKEYANCHORTEST_H
#define MAILWEBENGINEACCESSKEYANCHORTEST_H
#include <QObject>
class WebEngineAccessKeyAnchorTest : public QObject
{
Q_OBJECT
public:
explicit WebEngineAccessKeyAnchorTest(QObject *parent = nullptr);
~WebEngineAccessKeyAnchorTest();
private Q_SLOTS:
void shouldReturnEmptyAccessKeyAnchor();
};
#endif // MAILWEBENGINEACCESSKEYANCHORTEST_H
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskey.cpp b/webengineviewer/src/webengineaccesskey/webengineaccesskey.cpp
index 89bc8434..27a77a92 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskey.cpp
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskey.cpp
@@ -1,365 +1,367 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineaccesskey.h"
#include "webengineaccesskeyanchor.h"
#include "webengineaccesskeyutils.h"
#include "webenginemanagescript.h"
#include <KActionCollection>
#include <QKeyEvent>
#include <QLabel>
#include <QAction>
#include <QWebEngineView>
#include <QToolTip>
#include <QDebug>
using namespace WebEngineViewer;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
class WebEngineViewer::WebEngineAccessKeyPrivate
{
public:
enum AccessKeyState {
NotActivated,
PreActivated,
Activated
};
WebEngineAccessKeyPrivate(WebEngineAccessKey *qq, QWebEngineView *webEngine)
: mWebEngine(webEngine)
, q(qq)
{
}
void makeAccessKeyLabel(QChar accessKey, const WebEngineViewer::WebEngineAccessKeyAnchor &element);
bool checkForAccessKey(QKeyEvent *event);
QList<QLabel *> mAccessKeyLabels;
QHash<QChar, WebEngineViewer::WebEngineAccessKeyAnchor> mAccessKeyNodes;
QHash<QString, QChar> mDuplicateLinkElements;
QWebEngineView *mWebEngine = nullptr;
AccessKeyState mAccessKeyActivated = NotActivated;
KActionCollection *mActionCollection = nullptr;
WebEngineAccessKey *q = nullptr;
};
static QString linkElementKey(const WebEngineViewer::WebEngineAccessKeyAnchor &element, const QUrl &baseUrl)
{
//qDebug()<<" element.href()"<<element.href();
if (!element.href().isEmpty()) {
const QUrl url = baseUrl.resolved(QUrl(element.href()));
//qDebug()<< "URL " << url;
QString linkKey(url.toString());
if (!element.target().isEmpty()) {
linkKey += QLatin1Char('+');
linkKey += element.target();
}
return linkKey;
}
return QString();
}
static void handleDuplicateLinkElements(const WebEngineViewer::WebEngineAccessKeyAnchor &element, QHash<QString, QChar> *dupLinkList, QChar *accessKey, const QUrl &baseUrl)
{
if (element.tagName().compare(QLatin1String("A"), Qt::CaseInsensitive) == 0) {
const QString linkKey(linkElementKey(element, baseUrl));
//qDebug() << "LINK KEY:" << linkKey;
if (dupLinkList->contains(linkKey)) {
//qDebug() << "***** Found duplicate link element:" << linkKey;
*accessKey = dupLinkList->value(linkKey);
} else if (!linkKey.isEmpty()) {
dupLinkList->insert(linkKey, *accessKey);
}
if (linkKey.isEmpty()) {
*accessKey = QChar();
}
}
}
static bool isHiddenElement(const WebEngineViewer::WebEngineAccessKeyAnchor &element)
{
// width or height property set to less than zero
if (element.boundingRect().width() < 1 || element.boundingRect().height() < 1) {
return true;
}
#if 0
// visibility set to 'hidden' in the element itself or its parent elements.
if (element.styleProperty(QStringLiteral("visibility"), QWebElement::ComputedStyle).compare(QLatin1String("hidden"), Qt::CaseInsensitive) == 0) {
return true;
}
// display set to 'none' in the element itself or its parent elements.
if (element.styleProperty(QStringLiteral("display"), QWebElement::ComputedStyle).compare(QLatin1String("none"), Qt::CaseInsensitive) == 0) {
return true;
}
#endif
return false;
}
bool WebEngineAccessKeyPrivate::checkForAccessKey(QKeyEvent *event)
{
if (mAccessKeyLabels.isEmpty()) {
return false;
}
QString text = event->text();
if (text.isEmpty()) {
return false;
}
QChar key = text.at(0).toUpper();
bool handled = false;
if (mAccessKeyNodes.contains(key)) {
WebEngineViewer::WebEngineAccessKeyAnchor element = mAccessKeyNodes[key];
if (element.tagName().compare(QLatin1String("A"), Qt::CaseInsensitive) == 0) {
const QString linkKey(linkElementKey(element, mWebEngine->url()));
if (!linkKey.isEmpty()) {
//qDebug()<<" WebEngineAccessKey::checkForAccessKey****"<<linkKey;
Q_EMIT q->openUrl(QUrl(linkKey));
handled = true;
}
}
}
return handled;
}
void WebEngineAccessKeyPrivate::makeAccessKeyLabel(QChar accessKey, const WebEngineViewer::WebEngineAccessKeyAnchor &element)
{
//qDebug()<<" void WebEngineAccessKey::makeAccessKeyLabel(QChar accessKey, const WebEngineViewer::MailWebEngineAccessKeyAnchor &element)";
QLabel *label = new QLabel(mWebEngine);
QFont font(label->font());
font.setBold(true);
label->setFont(font);
label->setText(accessKey);
QFontMetrics metric(label->font());
label->setFixedWidth(metric.boundingRect(QStringLiteral("WW")).width());
label->setPalette(QToolTip::palette());
label->setAutoFillBackground(true);
label->setFrameStyle(QFrame::Box | QFrame::Plain);
QPoint point = element.boundingRect().center();
label->move(point);
label->show();
point.setX(point.x() - label->width() / 2);
label->move(point);
mAccessKeyLabels.append(label);
mAccessKeyNodes.insertMulti(accessKey, element);
}
WebEngineAccessKey::WebEngineAccessKey(QWebEngineView *webEngine, QObject *parent)
: QObject(parent)
, d(new WebEngineViewer::WebEngineAccessKeyPrivate(this, webEngine))
{
//qDebug() << " WebEngineAccessKey::WebEngineAccessKey(QWebEngineView *webEngine, QObject *parent)";
}
WebEngineAccessKey::~WebEngineAccessKey()
{
delete d;
}
void WebEngineAccessKey::setActionCollection(KActionCollection *ac)
{
d->mActionCollection = ac;
}
void WebEngineAccessKey::wheelEvent(QWheelEvent *e)
{
hideAccessKeys();
if (d->mAccessKeyActivated == WebEngineAccessKeyPrivate::PreActivated && (e->modifiers() & Qt::ControlModifier)) {
d->mAccessKeyActivated = WebEngineAccessKeyPrivate::NotActivated;
}
}
void WebEngineAccessKey::resizeEvent(QResizeEvent *)
{
if (d->mAccessKeyActivated == WebEngineAccessKeyPrivate::Activated) {
hideAccessKeys();
}
}
void WebEngineAccessKey::keyPressEvent(QKeyEvent *e)
{
if (e && d->mWebEngine->hasFocus()) {
if (d->mAccessKeyActivated == WebEngineAccessKeyPrivate::Activated) {
if (d->checkForAccessKey(e)) {
hideAccessKeys();
e->accept();
return;
}
hideAccessKeys();
} else if (e->key() == Qt::Key_Control && e->modifiers() == Qt::ControlModifier
#if 0 //FIXME
&& !isEditableElement(d->mWebView->page())
#endif
) {
d->mAccessKeyActivated = WebEngineAccessKeyPrivate::PreActivated; // Only preactive here, it will be actually activated in key release.
}
}
}
void WebEngineAccessKey::keyReleaseEvent(QKeyEvent *e)
{
//qDebug() << " void WebEngineAccessKey::keyReleaseEvent(QKeyEvent *e)";
if (d->mAccessKeyActivated == WebEngineAccessKeyPrivate::PreActivated) {
// Activate only when the CTRL key is pressed and released by itself.
if (e->key() == Qt::Key_Control && e->modifiers() == Qt::NoModifier) {
showAccessKeys();
} else {
d->mAccessKeyActivated = WebEngineAccessKeyPrivate::NotActivated;
}
}
}
void WebEngineAccessKey::hideAccessKeys()
{
if (!d->mAccessKeyLabels.isEmpty()) {
for (int i = 0, count = d->mAccessKeyLabels.count(); i < count; ++i) {
QLabel *label = d->mAccessKeyLabels[i];
label->hide();
label->deleteLater();
}
d->mAccessKeyLabels.clear();
d->mAccessKeyNodes.clear();
d->mDuplicateLinkElements.clear();
d->mAccessKeyActivated = WebEngineAccessKeyPrivate::NotActivated;
d->mWebEngine->update();
}
}
void WebEngineAccessKey::handleSearchAccessKey(const QVariant &res)
{
//qDebug() << " void WebEngineAccessKey::handleSearchAccessKey(const QVariant &res)" << res;
const QVariantList lst = res.toList();
QVector<WebEngineViewer::WebEngineAccessKeyAnchor> anchorList;
anchorList.reserve(lst.count());
for (const QVariant &var : lst) {
//qDebug()<<" var"<<var;
anchorList << WebEngineViewer::WebEngineAccessKeyAnchor(var);
}
QList<QChar> unusedKeys;
unusedKeys.reserve(10 + ('Z' - 'A' + 1));
for (char c = 'A'; c <= 'Z'; ++c) {
unusedKeys << QLatin1Char(c);
}
for (char c = '0'; c <= '9'; ++c) {
unusedKeys << QLatin1Char(c);
}
if (d->mActionCollection) {
for (QAction *act : d->mActionCollection->actions()) {
QAction *a = qobject_cast<QAction *>(act);
if (a) {
const QKeySequence shortCut = a->shortcut();
if (!shortCut.isEmpty()) {
- Q_FOREACH (QChar c, unusedKeys) { //Don't use for(..:..)
+ auto lst = unusedKeys;
+ for (QChar c : qAsConst(unusedKeys)) {
if (shortCut.matches(QKeySequence(c)) != QKeySequence::NoMatch) {
- unusedKeys.removeOne(c);
+ lst.removeOne(c);
}
}
+ unusedKeys = lst;
}
}
}
}
QVector<WebEngineViewer::WebEngineAccessKeyAnchor> unLabeledElements;
QRect viewport = d->mWebEngine->rect();
for (const WebEngineViewer::WebEngineAccessKeyAnchor &element : qAsConst(anchorList)) {
const QRect geometry = element.boundingRect();
if (geometry.size().isEmpty() || !viewport.contains(geometry.topLeft())) {
continue;
}
if (isHiddenElement(element)) {
continue; // Do not show access key for hidden elements...
}
const QString accessKeyAttribute(element.accessKey().toUpper());
if (accessKeyAttribute.isEmpty()) {
unLabeledElements.append(element);
continue;
}
QChar accessKey;
for (int i = 0; i < accessKeyAttribute.count(); i += 2) {
const QChar &possibleAccessKey = accessKeyAttribute[i];
if (unusedKeys.contains(possibleAccessKey)) {
accessKey = possibleAccessKey;
break;
}
}
if (accessKey.isNull()) {
unLabeledElements.append(element);
continue;
}
handleDuplicateLinkElements(element, &d->mDuplicateLinkElements, &accessKey, d->mWebEngine->url());
if (!accessKey.isNull()) {
unusedKeys.removeOne(accessKey);
d->makeAccessKeyLabel(accessKey, element);
}
}
// Pick an access key first from the letters in the text and then from the
// list of unused access keys
for (const WebEngineViewer::WebEngineAccessKeyAnchor &element : qAsConst(unLabeledElements)) {
const QRect geometry = element.boundingRect();
if (unusedKeys.isEmpty()
|| geometry.size().isEmpty()
|| !viewport.contains(geometry.topLeft())) {
continue;
}
QChar accessKey;
const QString text = element.innerText().toUpper();
for (int i = 0; i < text.count(); ++i) {
const QChar &c = text.at(i);
if (unusedKeys.contains(c)) {
accessKey = c;
break;
}
}
if (accessKey.isNull()) {
accessKey = unusedKeys.takeFirst();
}
handleDuplicateLinkElements(element, &d->mDuplicateLinkElements, &accessKey, d->mWebEngine->url());
if (!accessKey.isNull()) {
unusedKeys.removeOne(accessKey);
d->makeAccessKeyLabel(accessKey, element);
}
}
d->mAccessKeyActivated = (!d->mAccessKeyLabels.isEmpty() ? WebEngineAccessKeyPrivate::Activated : WebEngineAccessKeyPrivate::NotActivated);
}
void WebEngineAccessKey::showAccessKeys()
{
d->mAccessKeyActivated = WebEngineAccessKeyPrivate::Activated;
d->mWebEngine->page()->runJavaScript(WebEngineViewer::WebEngineAccessKeyUtils::script(),
WebEngineManageScript::scriptWordId(),
invoke(this, &WebEngineAccessKey::handleSearchAccessKey));
}
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskey.h b/webengineviewer/src/webengineaccesskey/webengineaccesskey.h
index da63386a..9b0de2ac 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskey.h
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskey.h
@@ -1,62 +1,62 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEACCESSKEY_H
#define MAILWEBENGINEACCESSKEY_H
#include "webengineviewer_export.h"
#include <QObject>
class KActionCollection;
class QWheelEvent;
class QResizeEvent;
class QKeyEvent;
class QWebEngineView;
namespace WebEngineViewer {
class WebEngineAccessKeyPrivate;
class WEBENGINEVIEWER_EXPORT WebEngineAccessKey : public QObject
{
Q_OBJECT
public:
explicit WebEngineAccessKey(QWebEngineView *webEngine, QObject *parent = nullptr);
~WebEngineAccessKey();
void setActionCollection(KActionCollection *ac);
void wheelEvent(QWheelEvent *e);
void resizeEvent(QResizeEvent *);
void keyPressEvent(QKeyEvent *e);
void keyReleaseEvent(QKeyEvent *e);
void showAccessKeys();
Q_SIGNALS:
void openUrl(const QUrl &url);
public Q_SLOTS:
void hideAccessKeys();
private Q_SLOTS:
void handleSearchAccessKey(const QVariant &res);
private:
WebEngineAccessKeyPrivate *const d;
};
}
#endif
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.cpp b/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.cpp
index 5003e9b1..490e58bd 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.cpp
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.cpp
@@ -1,77 +1,77 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineaccesskeyanchor.h"
using namespace WebEngineViewer;
WebEngineAccessKeyAnchor::WebEngineAccessKeyAnchor(const QVariant &result)
{
initialize(result);
}
WebEngineAccessKeyAnchor::WebEngineAccessKeyAnchor()
{
}
void WebEngineAccessKeyAnchor::initialize(const QVariant &result)
{
if (result.isValid()) {
const QVariantMap map = result.toMap();
const QVariantList &rect = map.value(QStringLiteral("boundingRect")).toList();
if (rect.size() == 4) {
mBoundingRect = QRect(rect.at(0).toInt(), rect.at(1).toInt(), rect.at(2).toInt(), rect.at(3).toInt());
}
mHref = map.value(QStringLiteral("src")).toString();
mAccessKey = map.value(QStringLiteral("accessKey")).toString();
mTarget = map.value(QStringLiteral("target")).toString();
mTagName = map.value(QStringLiteral("tagName")).toString();
mInnerText = map.value(QStringLiteral("text")).toString();
}
}
QString WebEngineAccessKeyAnchor::innerText() const
{
return mInnerText;
}
QString WebEngineAccessKeyAnchor::tagName() const
{
return mTagName;
}
QString WebEngineAccessKeyAnchor::target() const
{
return mTarget;
}
QString WebEngineAccessKeyAnchor::href() const
{
return mHref;
}
QString WebEngineAccessKeyAnchor::accessKey() const
{
return mAccessKey;
}
QRect WebEngineAccessKeyAnchor::boundingRect() const
{
return mBoundingRect;
}
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.h b/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.h
index f700db53..93c8b916 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.h
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskeyanchor.h
@@ -1,56 +1,56 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEACCESSKEYANCHOR_H
#define MAILWEBENGINEACCESSKEYANCHOR_H
#include <QVariant>
#include <QRect>
namespace WebEngineViewer {
class WebEngineAccessKeyAnchor
{
public:
WebEngineAccessKeyAnchor(const QVariant &result);
WebEngineAccessKeyAnchor();
Q_REQUIRED_RESULT QRect boundingRect() const;
Q_REQUIRED_RESULT QString accessKey() const;
Q_REQUIRED_RESULT QString href() const;
Q_REQUIRED_RESULT QString target() const;
Q_REQUIRED_RESULT QString tagName() const;
Q_REQUIRED_RESULT QString innerText() const;
private:
void initialize(const QVariant &result);
QRect mBoundingRect;
QString mAccessKey;
QString mHref;
QString mTarget;
QString mTagName;
QString mInnerText;
};
}
Q_DECLARE_TYPEINFO(WebEngineViewer::WebEngineAccessKeyAnchor, Q_MOVABLE_TYPE);
#endif // MAILWEBENGINEACCESSKEYANCHOR_H
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.cpp b/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.cpp
index f65a9b23..b4944c7e 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.cpp
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.cpp
@@ -1,40 +1,40 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineaccesskeyutils.h"
QString WebEngineViewer::WebEngineAccessKeyUtils::script()
{
const QString script = QString::fromLatin1("(function() {"
"var out = [];"
"var matches = document.querySelectorAll(\"a[href], area,button:not([disabled]), input:not([disabled]):not([hidden]),label[for],legend,select:not([disabled]),textarea:not([disabled])\");"
"for (var i = 0; i < matches.length; ++i) {"
" var r = matches[i].getBoundingClientRect();"
" out.push({"
" text: matches[i].innerText,"
" tagName: matches[i].tagName,"
" src: matches[i].href,"
" boundingRect: [r.left, r.top, r.right - r.left, r.bottom - r.top],"
" accessKey: matches[i].getAttribute('accesskey'),"
" target: matches[i].getAttribute('target')"
" });"
"}"
"return out;})()");
return script;
}
diff --git a/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.h b/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.h
index 1b1e749c..c65bdc85 100644
--- a/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.h
+++ b/webengineviewer/src/webengineaccesskey/webengineaccesskeyutils.h
@@ -1,30 +1,30 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef MAILWEBENGINEACCESSKEYUTILS_H
#define MAILWEBENGINEACCESSKEYUTILS_H
#include <QString>
namespace WebEngineViewer {
namespace WebEngineAccessKeyUtils {
Q_REQUIRED_RESULT QString script();
}
}
#endif // MAILWEBENGINEACCESSKEYUTILS_H
diff --git a/webengineviewer/src/webengineexporthtmlpagejob.cpp b/webengineviewer/src/webengineexporthtmlpagejob.cpp
index 398c1c8c..606ba7ba 100644
--- a/webengineviewer/src/webengineexporthtmlpagejob.cpp
+++ b/webengineviewer/src/webengineexporthtmlpagejob.cpp
@@ -1,90 +1,90 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineexporthtmlpagejob.h"
#include <QTemporaryFile>
#include <QWebEngineView>
using namespace WebEngineViewer;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFun)(Arg);
void operator()(Arg result)
{
(receiver->*memberFun)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFun)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFun};
return wrapper;
}
WebEngineExportHtmlPageJob::WebEngineExportHtmlPageJob(QObject *parent)
: QObject(parent)
, mEngineView(nullptr)
{
}
WebEngineExportHtmlPageJob::~WebEngineExportHtmlPageJob()
{
}
void WebEngineExportHtmlPageJob::start()
{
if (!mEngineView) {
Q_EMIT failed();
deleteLater();
return;
}
mEngineView->page()->toHtml(invoke(this, &WebEngineExportHtmlPageJob::slotSaveHtmlToPage));
}
void WebEngineExportHtmlPageJob::slotSaveHtmlToPage(const QString &text)
{
QTemporaryFile temporaryFile;
temporaryFile.setAutoRemove(false);
if (!temporaryFile.open()) {
Q_EMIT failed();
deleteLater();
return;
}
QTextStream stream(&temporaryFile);
stream.setCodec("UTF-8");
QString newText = text;
newText.replace(QLatin1String("<head>"), QLatin1String("<head><meta charset=\"UTF-8\">"));
stream << newText;
temporaryFile.close();
//We need to remove this temporary file
Q_EMIT success(temporaryFile.fileName());
deleteLater();
}
QWebEngineView *WebEngineExportHtmlPageJob::engineView() const
{
return mEngineView;
}
void WebEngineExportHtmlPageJob::setEngineView(QWebEngineView *engineView)
{
mEngineView = engineView;
}
diff --git a/webengineviewer/src/webengineexporthtmlpagejob.h b/webengineviewer/src/webengineexporthtmlpagejob.h
index 6a501c4b..d831002e 100644
--- a/webengineviewer/src/webengineexporthtmlpagejob.h
+++ b/webengineviewer/src/webengineexporthtmlpagejob.h
@@ -1,51 +1,51 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEEXPORTHTMLPAGEJOB_H
#define WEBENGINEEXPORTHTMLPAGEJOB_H
#include <QObject>
#include "webengineviewer_export.h"
class QWebEngineView;
namespace WebEngineViewer {
class WEBENGINEVIEWER_EXPORT WebEngineExportHtmlPageJob : public QObject
{
Q_OBJECT
public:
explicit WebEngineExportHtmlPageJob(QObject *parent = nullptr);
~WebEngineExportHtmlPageJob();
void start();
Q_REQUIRED_RESULT QWebEngineView *engineView() const;
void setEngineView(QWebEngineView *engineView);
Q_SIGNALS:
void failed();
void success(const QString &filename);
private Q_SLOTS:
void slotSaveHtmlToPage(const QString &text);
private:
QWebEngineView *mEngineView = nullptr;
};
}
#endif // WEBENGINEEXPORTHTMLPAGEJOB_H
diff --git a/webengineviewer/src/webenginemanagescript.cpp b/webengineviewer/src/webenginemanagescript.cpp
index 94e3cabf..fdc7944f 100644
--- a/webengineviewer/src/webenginemanagescript.cpp
+++ b/webengineviewer/src/webenginemanagescript.cpp
@@ -1,60 +1,60 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webenginemanagescript.h"
#include "webengineviewer_debug.h"
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
using namespace WebEngineViewer;
WebEngineManageScript::WebEngineManageScript(QObject *parent)
: QObject(parent)
{
}
WebEngineManageScript::~WebEngineManageScript()
{
}
void WebEngineManageScript::addScript(QWebEngineProfile *profile, const QString &source, const QString &scriptName, QWebEngineScript::InjectionPoint injectionPoint)
{
if (profile) {
QWebEngineScript script;
const QList<QWebEngineScript> collectionScripts = profile->scripts()->findScripts(scriptName);
if (!collectionScripts.isEmpty()) {
script = collectionScripts.first();
}
for (const QWebEngineScript &s : collectionScripts) {
profile->scripts()->remove(s);
}
if (script.isNull()) {
script.setName(scriptName);
script.setInjectionPoint(injectionPoint);
script.setRunsOnSubFrames(true);
script.setWorldId(scriptWordId());
}
script.setSourceCode(source);
profile->scripts()->insert(script);
//qCDebug(WEBENGINEVIEWER_LOG) << " void WebEngineManageScript::addScript profile:" << profile;
}
}
diff --git a/webengineviewer/src/webenginemanagescript.h b/webengineviewer/src/webenginemanagescript.h
index a490cad9..14352dde 100644
--- a/webengineviewer/src/webenginemanagescript.h
+++ b/webengineviewer/src/webenginemanagescript.h
@@ -1,43 +1,43 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEMANAGESCRIPT_H
#define WEBENGINEMANAGESCRIPT_H
#include <QObject>
#include <QWebEngineScript>
#include "webengineviewer_export.h"
class QWebEngineProfile;
namespace WebEngineViewer {
class WEBENGINEVIEWER_EXPORT WebEngineManageScript : public QObject
{
Q_OBJECT
public:
explicit WebEngineManageScript(QObject *parent = nullptr);
~WebEngineManageScript();
void addScript(QWebEngineProfile *profile, const QString &source, const QString &scriptName, QWebEngineScript::InjectionPoint injectionPoint);
static quint32 scriptWordId()
{
return QWebEngineScript::UserWorld + 1;
}
};
}
#endif // WEBENGINEMANAGESCRIPT_H
diff --git a/webengineviewer/src/webenginenavigationrequestinterceptor.cpp b/webengineviewer/src/webenginenavigationrequestinterceptor.cpp
index 39aab477..656b88ff 100644
--- a/webengineviewer/src/webenginenavigationrequestinterceptor.cpp
+++ b/webengineviewer/src/webenginenavigationrequestinterceptor.cpp
@@ -1,43 +1,43 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webenginenavigationrequestinterceptor.h"
#include "webenginepage.h"
using namespace WebEngineViewer;
WebEngineNavigationRequestInterceptor::WebEngineNavigationRequestInterceptor(QWebEnginePage *page)
: QWebEnginePage(page)
, mTargetPage(page)
{
}
WebEngineNavigationRequestInterceptor::~WebEngineNavigationRequestInterceptor()
{
}
bool WebEngineNavigationRequestInterceptor::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
Q_UNUSED(isMainFrame);
WebEnginePage *page = qobject_cast<WebEnginePage *>(mTargetPage);
if (type == NavigationTypeLinkClicked && page) {
Q_EMIT page->urlClicked(url);
return false;
}
return false;
}
diff --git a/webengineviewer/src/webenginenavigationrequestinterceptor.h b/webengineviewer/src/webenginenavigationrequestinterceptor.h
index 714b560e..7f042a77 100644
--- a/webengineviewer/src/webenginenavigationrequestinterceptor.h
+++ b/webengineviewer/src/webenginenavigationrequestinterceptor.h
@@ -1,42 +1,42 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINENAVIGATIONREQUESTINTERCEPTOR_H
#define WEBENGINENAVIGATIONREQUESTINTERCEPTOR_H
#include <QWebEnginePage>
namespace WebEngineViewer {
class WebEnginePage;
class WebEngineNavigationRequestInterceptor : public QWebEnginePage
{
Q_OBJECT
public:
explicit WebEngineNavigationRequestInterceptor(QWebEnginePage *page);
~WebEngineNavigationRequestInterceptor() override;
protected:
Q_REQUIRED_RESULT bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override;
private:
QWebEnginePage *mTargetPage = nullptr;
};
}
#endif // WEBENGINENAVIGATIONREQUESTINTERCEPTOR_H
diff --git a/webengineviewer/src/webenginepage.cpp b/webengineviewer/src/webenginepage.cpp
index 55270145..2fcb6c2b 100644
--- a/webengineviewer/src/webenginepage.cpp
+++ b/webengineviewer/src/webenginepage.cpp
@@ -1,106 +1,118 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webenginepage.h"
#include "webhittest.h"
#include "webhittestresult.h"
#include <KLocalizedString>
#include <QEventLoop>
#include <QWebEngineDownloadItem>
#include <QPointer>
#include <QTimer>
#include <QFileDialog>
#include <QWebEngineProfile>
#include <QPrinter>
using namespace WebEngineViewer;
WebEnginePage::WebEnginePage(QObject *parent)
- : QWebEnginePage(parent)
+ : QWebEnginePage(new QWebEngineProfile, parent)
{
+ // Create a private (off the record) QWebEngineProfile here to isolate the
+ // browsing settings, and adopt it as a child so that it will be deleted
+ // when we are destroyed. The profile must remain active for as long as
+ // any QWebEnginePage's belonging to it exist, see the API documentation
+ // of QWebEnginePage::QWebEnginePage(QWebEngineProfile *, QObject *).
+ // Deleting it as our child on destruction is safe.
+ //
+ // Do not try to save a line of code by setting the parent on construction:
+ //
+ // WebEnginePage::WebEnginePage(QObject *parent)
+ // : QWebEnginePage(new QWebEngineProfile(this), parent)
+ //
+ // because the QWebEngineProfile constructor will call out to the QWebEnginePage
+ // and crash because the QWebEnginePage is not fully constructed yet.
+ profile()->setParent(this);
+
init();
}
WebEnginePage::WebEnginePage(QWebEngineProfile *profile, QObject *parent)
: QWebEnginePage(profile, parent)
{
init();
}
-WebEnginePage::~WebEnginePage()
-{
-}
-
void WebEnginePage::init()
{
connect(profile(), &QWebEngineProfile::downloadRequested, this, &WebEnginePage::saveHtml);
}
WebEngineViewer::WebHitTest *WebEnginePage::hitTestContent(const QPoint &pos)
{
return new WebHitTest(this, pos);
}
void WebEnginePage::saveHtml(QWebEngineDownloadItem *download)
{
const QString fileName = QFileDialog::getSaveFileName(view(), i18n("Save HTML Page"));
if (!fileName.isEmpty()) {
download->setSavePageFormat(QWebEngineDownloadItem::SingleHtmlSaveFormat);
download->setPath(fileName);
download->accept();
}
}
bool WebEnginePage::acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame)
{
if (isMainFrame && type == NavigationTypeLinkClicked) {
Q_EMIT urlClicked(url);
return false;
}
return true;
}
void WebEnginePage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID)
{
Q_UNUSED(level);
Q_UNUSED(sourceID);
//Don't convert to debug categories
qDebug() << "WebEnginePage::javaScriptConsoleMessage lineNumber: " << lineNumber << " message: " << message;
Q_EMIT showConsoleMessage(message);
}
bool WebEnginePage::execPrintPreviewPage(QPrinter *printer, int timeout)
{
QPointer<QEventLoop> loop = new QEventLoop;
bool result = false;
QTimer::singleShot(timeout, loop.data(), &QEventLoop::quit);
print(printer, [loop, &result](bool res) {
if (loop && loop->isRunning()) {
result = res;
loop->quit();
}
});
loop->exec();
delete loop;
return result;
}
diff --git a/webengineviewer/src/webenginepage.h b/webengineviewer/src/webenginepage.h
index 3094f980..95c7c760 100644
--- a/webengineviewer/src/webenginepage.h
+++ b/webengineviewer/src/webenginepage.h
@@ -1,56 +1,87 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEPAGE_H
#define WEBENGINEPAGE_H
#include <QWebEnginePage>
#include "webengineviewer_export.h"
class QWebEngineProfile;
class QWebEngineDownloadItem;
class QPrinter;
namespace WebEngineViewer {
class WebHitTest;
class WEBENGINEVIEWER_EXPORT WebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
+ /**
+ * Constructor.
+ *
+ * A private QWebEngineProfile, only applying to this QWebEnginePage,
+ * will be created to implement browser settings. It can be accessed via
+ * @c profile(), but it should not be shared or reused unless care is
+ * taken that the profile is not deleted until all of the QWebEnginePage's
+ * belonging to it are deleted first.
+ *
+ * @param parent The parent object
+ **/
explicit WebEnginePage(QObject *parent = nullptr);
- explicit WebEnginePage(QWebEngineProfile *profile, QObject *parent = nullptr);
- ~WebEnginePage() override;
+ /**
+ * Constructor.
+ *
+ * The specified QWebEngineProfile will be used. See the description of
+ * @c WebEnginePage(QObject *) and the API documentation of QWebEnginePage
+ * for caution regarding the lifetime of the profile.
+ *
+ * @param profile The profile to be used
+ * @param parent The parent object
+ * @deprecated Use the single argument constructor, which creates and uses
+ * a private profile.
+ **/
+#ifndef WEBENGINEVIEWER_NO_DEPRECATED
+ explicit WEBENGINEVIEWER_DEPRECATED WebEnginePage(QWebEngineProfile *profile, QObject *parent = nullptr);
+#endif
+
+ /**
+ * Destructor. If there is a private QWebEngineProfile then it will also
+ * be destroyed.
+ **/
+ virtual ~WebEnginePage() override = default;
+
WebEngineViewer::WebHitTest *hitTestContent(const QPoint &pos);
void saveHtml(QWebEngineDownloadItem *download);
Q_REQUIRED_RESULT bool execPrintPreviewPage(QPrinter *printer, int timeout);
Q_SIGNALS:
void urlClicked(const QUrl &url);
void showConsoleMessage(const QString &message);
protected:
bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override;
void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID) override;
private:
void init();
};
}
#endif // WEBENGINEPAGE_H
diff --git a/webengineviewer/src/webenginescript.cpp b/webengineviewer/src/webenginescript.cpp
index 2e9fcd44..dd267ac5 100644
--- a/webengineviewer/src/webenginescript.cpp
+++ b/webengineviewer/src/webenginescript.cpp
@@ -1,196 +1,191 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webenginescript.h"
-#include <QDebug>
using namespace WebEngineViewer;
QString WebEngineScript::findAllImages()
{
const QString source = QStringLiteral("(function() {"
"var out = [];"
"var imgs = document.getElementsByTagName('img');"
"for (var i = 0; i < imgs.length; ++i) {"
" var e = imgs[i];"
" out.push({"
" src: e.src"
" });"
"}"
"return out;"
"})()");
return source;
}
QString WebEngineScript::findAllScripts()
{
const QString source = QStringLiteral("(function() {"
"var out = [];"
"var scripts = document.getElementsByTagName('script');"
"for (var i = 0; i < scripts.length; ++i) {"
" var e = scripts[i];"
" out.push({"
" src: e.src"
" });"
"}"
"return out;"
"})()");
return source;
}
QString WebEngineScript::findAllAnchors()
{
const QString source = QStringLiteral("(function() {"
"var out = [];"
"var anchors = document.getElementsByTagName('a');"
"for (var i = 0; i < anchors.length; ++i) {"
" var r = anchors[i].getBoundingClientRect();"
" out.push({"
" src: anchors[i].href,"
" title: anchors[i].title,"
" boudingRect: [r.top, r.left, r.width, r.height]"
" });"
"}"
"return out;"
"})()");
return source;
}
QString WebEngineScript::findAllAnchorsAndForms()
{
const QString source = QStringLiteral("(function() {"
"var res = [];"
"var out = [];"
"var anchors = document.getElementsByTagName('a');"
"for (var i = 0; i < anchors.length; ++i) {"
" out.push({"
" src: anchors[i].href,"
" title: anchors[i].title,"
" text: anchors[i].innerText"
" });"
"}"
"var forms = document.getElementsByTagName('form');"
"res.push({"
" anchors: out,"
" forms: forms.length"
" });"
"return res;"
"})()");
return source;
}
QString WebEngineScript::setElementByIdVisible(const QString &elementStr, bool visibility)
{
if (visibility) {
const QString source = QStringLiteral("var element = document.getElementById('%1'); "
"if (element) { "
" element.style.removeProperty( 'display' );"
"}").arg(elementStr);
return source;
} else {
const QString source = QStringLiteral("var element = document.getElementById('%1'); "
"if (element) { "
" element.style.display = \"none\";"
"}").arg(elementStr);
return source;
}
}
QString WebEngineScript::searchElementPosition(const QString &elementStr)
{
const QString source = QStringLiteral("var element = document.getElementById('%1'); "
"if (element) { "
" var geometry = element.getBoundingClientRect(); "
" [(geometry.left + window.scrollX), (geometry.top + window.scrollY)]; "
"}").arg(elementStr);
return source;
}
static QString scrollTop()
{
-#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
return QStringLiteral("document.documentElement.scrollTop");
-#else
- return QStringLiteral("document.body.scrollTop");
-#endif
}
QString WebEngineScript::scrollPercentage(int percent)
{
const QString source = QStringLiteral("var current = ") + scrollTop() + QStringLiteral(";"
- "var docElement = document.documentElement;"
- "var height = docElement.clientHeight;"
- "var newPosition = current + height * %1 /100;"
- "window.scrollTo(window.scrollX, newPosition);").arg(percent);
+ "var docElement = document.documentElement;"
+ "var height = docElement.clientHeight;"
+ "var newPosition = current + height * %1 /100;"
+ "window.scrollTo(window.scrollX, newPosition);").arg(percent);
return source;
}
QString WebEngineScript::scrollUp(int pixel)
{
- const QString source = QString::fromLatin1("window.scrollBy(0, %1);").arg(-pixel);
+ const QString source = QStringLiteral("window.scrollBy(0, %1);").arg(-pixel);
return source;
}
QString WebEngineScript::scrollDown(int pixel)
{
const QString source = QStringLiteral("window.scrollBy(0, %1);").arg(pixel);
return source;
}
QString WebEngineScript::scrollToPosition(const QPoint &pos)
{
const QString source = QStringLiteral("window.scrollTo(%1, %2); [window.scrollX, window.scrollY];").arg(pos.x()).arg(pos.y());
return source;
}
QString WebEngineScript::removeStyleToElement(const QString &elementStr)
{
const QString source = QStringLiteral("var element = document.getElementById('%1'); "
"if (element) { "
" element.removeAttribute(\"style\");"
"}").arg(elementStr);
return source;
}
QString WebEngineScript::setStyleToElement(const QString &elementStr, const QString &style)
{
const QString source = QStringLiteral("var element = document.getElementById('%1'); "
"if (element) { "
" element.style = '%2';"
"}").arg(elementStr, style);
return source;
}
QString WebEngineScript::scrollToRelativePosition(qreal pos)
{
const QString source = QStringLiteral("window.scrollTo(window.scrollX, %1); [window.scrollX, window.scrollY];").arg(pos);
return source;
}
QString WebEngineScript::isScrolledToBottom()
{
return QStringLiteral("(function() { "
"var docElement = document.documentElement;"
"var viewportHeight = docElement.clientHeight;"
"var isAtBottom = ") + scrollTop() + QStringLiteral(" + viewportHeight >= document.body.scrollHeight;"
- "return Boolean(isAtBottom); "
- "}());");
+ "return Boolean(isAtBottom); "
+ "}());");
}
diff --git a/webengineviewer/src/webenginescript.h b/webengineviewer/src/webenginescript.h
index e80dcd1b..2046a9e1 100644
--- a/webengineviewer/src/webenginescript.h
+++ b/webengineviewer/src/webenginescript.h
@@ -1,44 +1,44 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINESCRIPT_H
#define WEBENGINESCRIPT_H
#include <QString>
#include <QPoint>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
namespace WebEngineScript {
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString findAllImages();
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString findAllScripts();
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString findAllAnchors();
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString findAllAnchorsAndForms();
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString searchElementPosition(const QString &elementStr);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString scrollToPosition(const QPoint &pos);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString setElementByIdVisible(const QString &elementStr, bool visibility);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString setStyleToElement(const QString &elementStr, const QString &style);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString scrollDown(int pixel);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString scrollUp(int pixel);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString scrollPercentage(int percent);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString scrollToRelativePosition(qreal pos);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString removeStyleToElement(const QString &element);
Q_REQUIRED_RESULT WEBENGINEVIEWER_EXPORT QString isScrolledToBottom();
}
}
#endif // WEBENGINESCRIPT_H
diff --git a/webengineviewer/src/webengineview.cpp b/webengineviewer/src/webengineview.cpp
index a314aed5..fd21bb11 100644
--- a/webengineviewer/src/webengineview.cpp
+++ b/webengineviewer/src/webengineview.cpp
@@ -1,270 +1,261 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webengineview.h"
#include "webenginenavigationrequestinterceptor.h"
#include "webenginemanagescript.h"
#include "webengineviewer_debug.h"
#include "checkphishingurl/localdatabasemanager.h"
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QTimer>
using namespace WebEngineViewer;
class WebEngineViewer::WebEngineViewPrivate
{
public:
WebEngineViewPrivate(WebEngineView *q)
: mSavedRelativePosition(-1)
, mCurrentWidget(nullptr)
, mWebEngineNavigatorInterceptor(nullptr)
, mWebEngineNavigatorInterceptorView(nullptr)
, mPhishingDatabase(nullptr)
, mCrashCount(0)
, q(q)
{
}
~WebEngineViewPrivate()
{
delete mWebEngineNavigatorInterceptor;
mWebEngineNavigatorInterceptor = nullptr;
delete mWebEngineNavigatorInterceptorView;
mWebEngineNavigatorInterceptorView = nullptr;
}
void renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus status)
{
switch (status) {
case QWebEnginePage::NormalTerminationStatus:
return;
case QWebEnginePage::AbnormalTerminationStatus:
qCInfo(WEBENGINEVIEWER_LOG) << "WebEngine render process terminated abnormally";
break;
case QWebEnginePage::CrashedTerminationStatus:
qCInfo(WEBENGINEVIEWER_LOG) << "WebEngine render process crashed";
break;
case QWebEnginePage::KilledTerminationStatus:
qCInfo(WEBENGINEVIEWER_LOG) << "WebEngine render process killed";
break;
}
// don't get stuck in a loop if the renderer keeps crashing. Five restarts
// is an arbitrary constant.
if (++mCrashCount < 6) {
QTimer::singleShot(0, q, &QWebEngineView::reload);
} else {
// TODO: try to show a sadface page
}
}
qreal mSavedRelativePosition;
QWidget *mCurrentWidget = nullptr;
WebEngineManageScript *mManagerScript = nullptr;
WebEngineNavigationRequestInterceptor *mWebEngineNavigatorInterceptor = nullptr;
WebEngineView *mWebEngineNavigatorInterceptorView = nullptr;
LocalDataBaseManager *mPhishingDatabase = nullptr;
int mCrashCount;
private:
WebEngineView *const q;
};
WebEngineView::WebEngineView(QWidget *parent)
: QWebEngineView(parent)
, d(new WebEngineViewer::WebEngineViewPrivate(this))
{
installEventFilter(this);
d->mManagerScript = new WebEngineManageScript(this);
connect(this, &QWebEngineView::renderProcessTerminated,
this, [this](QWebEnginePage::RenderProcessTerminationStatus status) {
d->renderProcessTerminated(status);
});
connect(this, &QWebEngineView::loadFinished,
this, [this]() {
// Reset the crash counter if we manage to actually load a page.
// This does not perfectly correspond to "we managed to render
// a page", but it's the best we have
d->mCrashCount = 0;
});
}
WebEngineView::~WebEngineView()
{
delete d;
}
WebEngineManageScript *WebEngineView::webEngineManagerScript() const
{
return d->mManagerScript;
}
-void WebEngineView::initializeJQueryScript()
-{
- QFile file(QStringLiteral(":/data/jquery.min.js"));
- file.open(QIODevice::ReadOnly);
- QString jquery = QString::fromUtf8(file.readAll());
- jquery.append(QLatin1String("\nvar qt = { 'jQuery': jQuery.noConflict(true) };"));
- d->mManagerScript->addScript(page()->profile(), jquery, QStringLiteral("jquery"), QWebEngineScript::DocumentCreation);
-}
-
void WebEngineView::addScript(const QString &source, const QString &scriptName, QWebEngineScript::InjectionPoint injectionPoint)
{
d->mManagerScript->addScript(page()->profile(), source, scriptName, injectionPoint);
}
void WebEngineView::forwardWheelEvent(QWheelEvent *event)
{
Q_UNUSED(event);
}
void WebEngineView::forwardKeyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event);
}
void WebEngineView::forwardKeyReleaseEvent(QKeyEvent *event)
{
Q_UNUSED(event);
}
void WebEngineView::forwardMousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event);
}
void WebEngineView::forwardMouseMoveEvent(QMouseEvent *event)
{
Q_UNUSED(event);
}
void WebEngineView::forwardMouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
}
bool WebEngineView::eventFilter(QObject *obj, QEvent *event)
{
// Keyboard events are sent to parent widget
if (obj == this && event->type() == QEvent::ParentChange && parentWidget()) {
parentWidget()->installEventFilter(this);
}
// Hack to find widget that receives input events
if (obj == this && event->type() == QEvent::ChildAdded) {
QTimer::singleShot(0, this, [this]() {
if (focusProxy() && d->mCurrentWidget != focusProxy()) {
d->mCurrentWidget = focusProxy();
d->mCurrentWidget->installEventFilter(this);
}
});
}
// Forward events to WebEngineView
if (obj == d->mCurrentWidget) {
#define HANDLE_EVENT(f, t) \
{ \
bool wasAccepted = event->isAccepted(); \
event->setAccepted(false); \
f(static_cast<t *>(event)); \
bool ret = event->isAccepted(); \
event->setAccepted(wasAccepted); \
return ret; \
}
switch (event->type()) {
case QEvent::KeyPress:
HANDLE_EVENT(forwardKeyPressEvent, QKeyEvent);
case QEvent::KeyRelease:
HANDLE_EVENT(forwardKeyReleaseEvent, QKeyEvent);
case QEvent::MouseButtonPress:
HANDLE_EVENT(forwardMousePressEvent, QMouseEvent);
case QEvent::MouseButtonRelease:
HANDLE_EVENT(forwardMouseReleaseEvent, QMouseEvent);
case QEvent::MouseMove:
HANDLE_EVENT(forwardMouseMoveEvent, QMouseEvent);
case QEvent::Wheel:
HANDLE_EVENT(forwardWheelEvent, QWheelEvent);
default:
break;
}
#undef HANDLE_EVENT
}
// Block already handled events
if (obj == this) {
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
case QEvent::Wheel:
return true;
default:
break;
}
}
return QWebEngineView::eventFilter(obj, event);
}
QWebEngineView *WebEngineView::createWindow(QWebEnginePage::WebWindowType type)
{
Q_UNUSED(type);
delete d->mWebEngineNavigatorInterceptor;
delete d->mWebEngineNavigatorInterceptorView;
d->mWebEngineNavigatorInterceptorView = new WebEngineView();
d->mWebEngineNavigatorInterceptor = new WebEngineNavigationRequestInterceptor(this->page());
d->mWebEngineNavigatorInterceptorView->setPage(d->mWebEngineNavigatorInterceptor);
return d->mWebEngineNavigatorInterceptorView;
}
void WebEngineView::clearRelativePosition()
{
d->mSavedRelativePosition = -1;
}
void WebEngineView::saveRelativePosition()
{
if (d->mSavedRelativePosition != -1) {
d->mSavedRelativePosition = page()->scrollPosition().toPoint().y();
}
}
qreal WebEngineView::relativePosition() const
{
qCDebug(WEBENGINEVIEWER_LOG) << "Relative Position" << d->mSavedRelativePosition;
return d->mSavedRelativePosition;
}
LocalDataBaseManager *WebEngineView::phishingDatabase() const
{
if (!d->mPhishingDatabase) {
d->mPhishingDatabase = new LocalDataBaseManager(const_cast<WebEngineView *>(this));
d->mPhishingDatabase->initialize();
}
return d->mPhishingDatabase;
}
diff --git a/webengineviewer/src/webengineview.h b/webengineviewer/src/webengineview.h
index e7e8bf9f..f407f13a 100644
--- a/webengineviewer/src/webengineview.h
+++ b/webengineviewer/src/webengineview.h
@@ -1,64 +1,63 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEVIEW_H
#define WEBENGINEVIEW_H
#include <QWebEngineView>
#include <QWebEngineScript>
#include "webengineviewer_export.h"
namespace WebEngineViewer {
class WebEngineViewPrivate;
class WebEngineManageScript;
class LocalDataBaseManager;
class WEBENGINEVIEWER_EXPORT WebEngineView : public QWebEngineView
{
Q_OBJECT
public:
explicit WebEngineView(QWidget *parent = nullptr);
~WebEngineView() override;
void clearRelativePosition();
void saveRelativePosition();
Q_REQUIRED_RESULT qreal relativePosition() const;
void addScript(const QString &source, const QString &scriptName, QWebEngineScript::InjectionPoint injectionPoint);
- void initializeJQueryScript();
Q_REQUIRED_RESULT WebEngineManageScript *webEngineManagerScript() const;
void setLinkHovered(const QUrl &url);
Q_REQUIRED_RESULT WebEngineViewer::LocalDataBaseManager *phishingDatabase() const;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;
virtual void forwardWheelEvent(QWheelEvent *event);
virtual void forwardKeyPressEvent(QKeyEvent *event);
virtual void forwardKeyReleaseEvent(QKeyEvent *event);
virtual void forwardMousePressEvent(QMouseEvent *event);
virtual void forwardMouseMoveEvent(QMouseEvent *event);
virtual void forwardMouseReleaseEvent(QMouseEvent *event);
private:
WebEngineViewPrivate *const d;
};
}
#endif // WEBENGINEVIEW_H
diff --git a/webengineviewer/src/webhittest.cpp b/webengineviewer/src/webhittest.cpp
index f4d7b358..f6d7dafd 100644
--- a/webengineviewer/src/webhittest.cpp
+++ b/webengineviewer/src/webhittest.cpp
@@ -1,132 +1,135 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#include "webhittest.h"
#include "webhittestresult.h"
#include <QWebEnginePage>
#include "webenginemanagescript.h"
using namespace WebEngineViewer;
template<typename Arg, typename R, typename C>
struct InvokeWrapper {
R *receiver;
void (C::*memberFunction)(Arg);
void operator()(Arg result)
{
(receiver->*memberFunction)(result);
}
};
template<typename Arg, typename R, typename C>
InvokeWrapper<Arg, R, C> invoke(R *receiver, void (C::*memberFunction)(Arg))
{
InvokeWrapper<Arg, R, C> wrapper = {receiver, memberFunction};
return wrapper;
}
class WebEngineViewer::WebHitTestPrivate
{
public:
WebHitTestPrivate(const QPoint &pos)
: m_pos(pos)
{
}
QPoint m_pos;
QUrl m_pageUrl;
};
WebHitTest::WebHitTest(QWebEnginePage *page, const QPoint &pos, QObject *parent)
: QObject(parent)
, d(new WebHitTestPrivate(pos))
{
QString source = QStringLiteral("(function() {"
"var e = document.elementFromPoint(%1, %2);"
"if (!e)"
" return;"
"function isMediaElement(e) {"
" return e.tagName.toLowerCase() == 'audio' || e.tagName.toLowerCase() == 'video';"
"}"
"function isEditableElement(e) {"
" if (e.isContentEditable)"
" return true;"
" if (e.tagName.toLowerCase() == 'input' || e.tagName.toLowerCase() == 'textarea')"
" return e.getAttribute('readonly') != 'readonly';"
" return false;"
"}"
"function isSelected(e) {"
" var selection = window.getSelection();"
" if (selection.type != 'Range')"
" return false;"
" return window.getSelection().containsNode(e, true);"
"}"
+ "function attributeStr(e, a) {"
+ " return e.getAttribute(a) || '';"
+ "}"
"var res = {"
" alternateText: e.getAttribute('alt'),"
" boundingRect: '',"
" imageUrl: '',"
" contentEditable: isEditableElement(e),"
" contentSelected: isSelected(e),"
" linkTitle: '',"
" linkUrl: '',"
" mediaUrl: '',"
" tagName: e.tagName.toLowerCase()"
"};"
"var r = e.getBoundingClientRect();"
"res.boundingRect = [r.top, r.left, r.width, r.height];"
"if (e.tagName.toLowerCase() == 'img')"
- " res.imageUrl = e.getAttribute('src');"
+ " res.imageUrl = attributeStr(e, 'src').trim();"
"if (e.tagName.toLowerCase() == 'a') {"
" res.linkTitle = e.text;"
- " res.linkUrl = e.getAttribute('href');"
+ " res.linkUrl = attributeStr(e, 'href').trim();"
"}"
"while (e) {"
" if (res.linkTitle == '' && e.tagName.toLowerCase() == 'a')"
" res.linkTitle = e.text;"
" if (res.linkUrl == '' && e.tagName.toLowerCase() == 'a')"
- " res.linkUrl = e.getAttribute('href');"
+ " res.linkUrl = attributeStr(e, 'href').trim();"
" if (res.mediaUrl == '' && isMediaElement(e)) {"
" res.mediaUrl = e.currentSrc;"
" res.mediaPaused = e.paused;"
" res.mediaMuted = e.muted;"
" }"
" e = e.parentElement;"
"}"
"return res;"
"})()");
const QString &js = source.arg(pos.x()).arg(pos.y());
d->m_pageUrl = page->url();
page->runJavaScript(js,
WebEngineViewer::WebEngineManageScript::scriptWordId(),
invoke(this, &WebHitTest::handleHitTest));
}
WebHitTest::~WebHitTest()
{
delete d;
}
void WebHitTest::handleHitTest(const QVariant &result)
{
const WebHitTestResult webHitResult(d->m_pos, d->m_pageUrl, result);
Q_EMIT finished(webHitResult);
deleteLater();
}
diff --git a/webengineviewer/src/webhittest.h b/webengineviewer/src/webhittest.h
index 1bb89e0c..4f064aeb 100644
--- a/webengineviewer/src/webhittest.h
+++ b/webengineviewer/src/webhittest.h
@@ -1,47 +1,47 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
#ifndef WEBHITTEST_H
#define WEBHITTEST_H
#include <QObject>
#include "webengineviewer_export.h"
#include <QPoint>
class QWebEnginePage;
namespace WebEngineViewer {
class WebHitTestResult;
class WebHitTestPrivate;
class WEBENGINEVIEWER_EXPORT WebHitTest : public QObject
{
Q_OBJECT
public:
explicit WebHitTest(QWebEnginePage *page, const QPoint &pos, QObject *parent = nullptr);
~WebHitTest();
Q_SIGNALS:
void finished(const WebEngineViewer::WebHitTestResult &result);
private Q_SLOTS:
void handleHitTest(const QVariant &result);
private:
WebHitTestPrivate *const d;
};
}
#endif // WEBHITTEST_H
diff --git a/webengineviewer/src/webhittestresult.cpp b/webengineviewer/src/webhittestresult.cpp
index 005fe605..b77bdf2c 100644
--- a/webengineviewer/src/webhittestresult.cpp
+++ b/webengineviewer/src/webhittestresult.cpp
@@ -1,201 +1,201 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
/* ============================================================
* QupZilla - QtWebEngine based browser
* Copyright (C) 2015 David Rosca <nowrep@gmail.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 3 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, see <http://www.gnu.org/licenses/>.
+* along with this program. If not, see <https://www.gnu.org/licenses/>.
* ============================================================ */
#include "webhittestresult.h"
#include <QDebug>
using namespace WebEngineViewer;
class WebEngineViewer::WebHitTestResultPrivate
{
public:
WebHitTestResultPrivate(const QPoint &pos = QPoint(), const QUrl &url = QUrl(), const QVariant &result = QVariant())
: mPos(pos)
, mPageUrl(url)
{
init(result.toMap());
}
void init(const QVariantMap &map);
QPoint mPos;
QRect mBoundingRect;
QUrl mImageUrl;
QUrl mLinkUrl;
QUrl mMediaUrl;
QUrl mPageUrl;
QString mTagName;
QString mLinkTitle;
QString mAlternateText;
bool mIsContentEditable = false;
bool mIsContentSelected = false;
bool mMediaPaused = false;
bool mMediaMuted = false;
bool mIsNull = true;
};
void WebHitTestResultPrivate::init(const QVariantMap &map)
{
if (map.isEmpty()) {
return;
}
//qDebug()<<" void WebHitTestResult::init(const QVariantMap &map)"<<map;
mAlternateText = map.value(QStringLiteral("alternateText")).toString();
mImageUrl = map.value(QStringLiteral("imageUrl")).toUrl();
mIsContentEditable = map.value(QStringLiteral("contentEditable")).toBool();
mIsContentSelected = map.value(QStringLiteral("contentSelected")).toBool();
mLinkTitle = map.value(QStringLiteral("linkTitle")).toString();
mLinkUrl = map.value(QStringLiteral("linkUrl")).toUrl();
mMediaUrl = map.value(QStringLiteral("mediaUrl")).toUrl();
mMediaPaused = map.value(QStringLiteral("mediaPaused")).toBool();
mMediaMuted = map.value(QStringLiteral("mediaMuted")).toBool();
mTagName = map.value(QStringLiteral("tagName")).toString();
const QVariantList &rect = map.value(QStringLiteral("boundingRect")).toList();
if (rect.size() == 4) {
mBoundingRect = QRect(rect.at(0).toInt(), rect.at(1).toInt(), rect.at(2).toInt(), rect.at(3).toInt());
}
if (!mImageUrl.isEmpty()) {
mImageUrl = mPageUrl.resolved(mImageUrl);
}
if (!mLinkUrl.isEmpty()) {
mLinkUrl = mPageUrl.resolved(mLinkUrl);
}
if (!mMediaUrl.isEmpty()) {
mMediaUrl = mPageUrl.resolved(mMediaUrl);
}
mIsNull = false;
}
WebHitTestResult::WebHitTestResult()
: d(new WebHitTestResultPrivate)
{
}
WebHitTestResult::WebHitTestResult(const QPoint &pos, const QUrl &pageUrl, const QVariant &result)
: d(new WebHitTestResultPrivate(pos, pageUrl, result))
{
}
WebHitTestResult::WebHitTestResult(const WebHitTestResult &other)
: d(new WebHitTestResultPrivate)
{
(*this) = other;
}
WebHitTestResult::~WebHitTestResult()
{
delete d;
}
WebHitTestResult &WebHitTestResult::operator=(const WebHitTestResult &other)
{
if (this != &other) {
*d = *(other.d);
}
return *this;
}
QString WebHitTestResult::alternateText() const
{
return d->mAlternateText;
}
QRect WebHitTestResult::boundingRect() const
{
return d->mBoundingRect;
}
QUrl WebHitTestResult::imageUrl() const
{
return d->mImageUrl;
}
bool WebHitTestResult::isContentEditable() const
{
return d->mIsContentEditable;
}
bool WebHitTestResult::isContentSelected() const
{
return d->mIsContentSelected;
}
bool WebHitTestResult::isNull() const
{
return d->mIsNull;
}
QString WebHitTestResult::linkTitle() const
{
return d->mLinkTitle;
}
QUrl WebHitTestResult::linkUrl() const
{
return d->mLinkUrl;
}
QUrl WebHitTestResult::mediaUrl() const
{
return d->mMediaUrl;
}
bool WebHitTestResult::mediaPaused() const
{
return d->mMediaPaused;
}
bool WebHitTestResult::mediaMuted() const
{
return d->mMediaMuted;
}
QPoint WebHitTestResult::pos() const
{
return d->mPos;
}
QString WebHitTestResult::tagName() const
{
return d->mTagName;
}
QUrl WebHitTestResult::pageUrl() const
{
return d->mPageUrl;
}
diff --git a/webengineviewer/src/webhittestresult.h b/webengineviewer/src/webhittestresult.h
index a7ea8c04..dfdf1ce1 100644
--- a/webengineviewer/src/webhittestresult.h
+++ b/webengineviewer/src/webhittestresult.h
@@ -1,78 +1,78 @@
/*
- Copyright (C) 2016-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2016-2019 Laurent Montel <montel@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.
*/
/* ============================================================
* QupZilla - QtWebEngine based browser
* Copyright (C) 2015 David Rosca <nowrep@gmail.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 3 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, see <http://www.gnu.org/licenses/>.
+* along with this program. If not, see <https://www.gnu.org/licenses/>.
* ============================================================ */
#ifndef WebHitTestResult_H
#define WebHitTestResult_H
#include "webengineviewer_export.h"
#include <QUrl>
#include <QRect>
#include <QString>
#include <QVariant>
#include <QWebEngineContextMenuData>
namespace WebEngineViewer {
class WebHitTestResultPrivate;
class WEBENGINEVIEWER_EXPORT WebHitTestResult
{
public:
WebHitTestResult();
WebHitTestResult(const QPoint &pos, const QUrl &pageUrl, const QVariant &result);
WebHitTestResult(const WebHitTestResult &other);
~WebHitTestResult();
Q_REQUIRED_RESULT QString alternateText() const;
Q_REQUIRED_RESULT QRect boundingRect() const;
Q_REQUIRED_RESULT QUrl imageUrl() const;
Q_REQUIRED_RESULT bool isContentEditable() const;
Q_REQUIRED_RESULT bool isContentSelected() const;
Q_REQUIRED_RESULT bool isNull() const;
Q_REQUIRED_RESULT QString linkTitle() const;
Q_REQUIRED_RESULT QUrl linkUrl() const;
Q_REQUIRED_RESULT QUrl mediaUrl() const;
Q_REQUIRED_RESULT bool mediaPaused() const;
Q_REQUIRED_RESULT bool mediaMuted() const;
Q_REQUIRED_RESULT QPoint pos() const;
Q_REQUIRED_RESULT QString tagName() const;
Q_REQUIRED_RESULT QUrl pageUrl() const;
WebHitTestResult &operator=(const WebHitTestResult &webHit);
private:
WebHitTestResultPrivate *d;
};
}
Q_DECLARE_METATYPE(WebEngineViewer::WebHitTestResult)
#endif // WEBHITTESTRESULT_H
diff --git a/webengineviewer/src/widgets/zoomactionmenu.cpp b/webengineviewer/src/widgets/zoomactionmenu.cpp
index 13cf363c..3b75083b 100644
--- a/webengineviewer/src/widgets/zoomactionmenu.cpp
+++ b/webengineviewer/src/widgets/zoomactionmenu.cpp
@@ -1,148 +1,148 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#include "zoomactionmenu.h"
#include <KLocalizedString>
#include <KActionCollection>
using namespace WebEngineViewer;
namespace {
qreal zoomBy()
{
return 20;
}
}
class WebEngineViewer::ZoomActionMenuPrivate
{
public:
ZoomActionMenuPrivate(KActionMenu *qq)
: q(qq)
{
}
void createMenu();
qreal mZoomFactor = 100;
QAction *mZoomInAction = nullptr;
QAction *mZoomOutAction = nullptr;
QAction *mZoomResetAction = nullptr;
KActionCollection *mActionCollection = nullptr;
KActionMenu *q = nullptr;
};
ZoomActionMenu::ZoomActionMenu(QObject *parent)
: KActionMenu(parent)
, d(new WebEngineViewer::ZoomActionMenuPrivate(this))
{
}
ZoomActionMenu::~ZoomActionMenu()
{
delete d;
}
void ZoomActionMenu::setActionCollection(KActionCollection *ac)
{
d->mActionCollection = ac;
}
void ZoomActionMenu::createZoomActions()
{
// Zoom actions
d->mZoomInAction = KStandardAction::zoomIn(this, &ZoomActionMenu::slotZoomIn, this);
d->mActionCollection->addAction(QStringLiteral("zoom_in"), d->mZoomInAction);
d->mZoomOutAction = KStandardAction::zoomOut(this, &ZoomActionMenu::slotZoomOut, this);
d->mActionCollection->addAction(QStringLiteral("zoom_out"), d->mZoomOutAction);
d->mZoomResetAction = KStandardAction::actualSize(this, &ZoomActionMenu::slotZoomReset, this);
d->mActionCollection->addAction(QStringLiteral("zoom_reset"), d->mZoomResetAction);
d->createMenu();
}
QAction *ZoomActionMenu::zoomInAction() const
{
return d->mZoomInAction;
}
QAction *ZoomActionMenu::zoomOutAction() const
{
return d->mZoomOutAction;
}
QAction *ZoomActionMenu::zoomResetAction() const
{
return d->mZoomResetAction;
}
void ZoomActionMenu::setZoomFactor(qreal zoomFactor)
{
d->mZoomFactor = zoomFactor;
}
void ZoomActionMenu::setWebViewerZoomFactor(qreal zoomFactor)
{
Q_EMIT zoomChanged(zoomFactor);
}
void ZoomActionMenu::slotZoomIn()
{
if (d->mZoomFactor >= 300) {
return;
}
d->mZoomFactor += zoomBy();
if (d->mZoomFactor > 300) {
d->mZoomFactor = 300;
}
Q_EMIT zoomChanged(d->mZoomFactor / 100.0);
}
void ZoomActionMenu::slotZoomOut()
{
if (d->mZoomFactor <= 10) {
return;
}
d->mZoomFactor -= zoomBy();
if (d->mZoomFactor < 10) {
d->mZoomFactor = 10;
}
Q_EMIT zoomChanged(d->mZoomFactor / 100.0);
}
void ZoomActionMenu::slotZoomReset()
{
d->mZoomFactor = 100;
Q_EMIT zoomChanged(1.0);
}
qreal ZoomActionMenu::zoomFactor() const
{
return d->mZoomFactor;
}
void ZoomActionMenuPrivate::createMenu()
{
q->setText(i18n("Zoom"));
q->addAction(mZoomInAction);
q->addAction(mZoomOutAction);
q->addSeparator();
q->addAction(mZoomResetAction);
mActionCollection->addAction(QStringLiteral("zoom_menu"), q);
}
diff --git a/webengineviewer/src/widgets/zoomactionmenu.h b/webengineviewer/src/widgets/zoomactionmenu.h
index 6c98b0ab..bf8eeff3 100644
--- a/webengineviewer/src/widgets/zoomactionmenu.h
+++ b/webengineviewer/src/widgets/zoomactionmenu.h
@@ -1,63 +1,63 @@
/*
- Copyright (C) 2015-2018 Laurent Montel <montel@kde.org>
+ Copyright (C) 2015-2019 Laurent Montel <montel@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.
*/
#ifndef WEBENGINEZOOMACTIONMENU_H
#define WEBENGINEZOOMACTIONMENU_H
#include <KActionMenu>
#include "webengineviewer_export.h"
class KActionCollection;
namespace WebEngineViewer {
class ZoomActionMenuPrivate;
class WEBENGINEVIEWER_EXPORT ZoomActionMenu : public KActionMenu
{
Q_OBJECT
public:
explicit ZoomActionMenu(QObject *parent = nullptr);
~ZoomActionMenu();
void createZoomActions();
QAction *zoomInAction() const;
QAction *zoomOutAction() const;
QAction *zoomResetAction() const;
void setActionCollection(KActionCollection *ac);
void setZoomFactor(qreal zoomFactor);
qreal zoomFactor() const;
void setWebViewerZoomFactor(qreal zoomFactor);
Q_SIGNALS:
void zoomChanged(qreal value);
public Q_SLOTS:
void slotZoomIn();
void slotZoomOut();
void slotZoomReset();
private:
ZoomActionMenuPrivate *const d;
};
}
#endif // ZOOMACTIONMENU_H