Commit ce2ab0ec authored by Laurent Montel's avatar Laurent Montel 😁

Move from libkdepim

parent a4d7a0d1
Pipeline #23702 passed with stage
in 9 minutes and 52 seconds
cmake_minimum_required(VERSION 3.5)
set(PIM_VERSION "5.14.45")
set(PIM_VERSION "5.14.46")
project(pimcommon VERSION ${PIM_VERSION})
......@@ -22,7 +22,7 @@ include(FeatureSummary)
include(ECMQtDeclareLoggingCategory)
include(ECMAddTests)
include(ECMInstallIcons)
include(ECMAddQtDesignerPlugin)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
......@@ -69,6 +69,16 @@ find_package(KF5Purpose ${KF5_MIN_VERSION} CONFIG REQUIRED)
option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON)
add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer")
option(FORCE_DISABLE_AKONADI_SEARCH "Disable features and API that require akonadi-search, e.g. because xapian isn't available" OFF)
if (NOT FORCE_DISABLE_AKONADI_SEARCH)
find_package(KF5AkonadiSearch ${AKONADISEARCH_LIB_VERSION} CONFIG REQUIRED)
set_package_properties(KF5AkonadiSearch PROPERTIES DESCRIPTION "The Akonadi Search libraries" URL "https://www.kde.org" TYPE REQUIRED PURPOSE "Provides search capabilities in KMail and Akonadi")
endif()
set(DISABLE_AKONADI_SEARCH ${FORCE_DISABLE_AKONADI_SEARCH})
configure_file(config-akonadi-search.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/config-akonadi-search.h)
find_package(Xsltproc)
set_package_properties(Xsltproc PROPERTIES DESCRIPTION "XSLT processor from libxslt" TYPE REQUIRED PURPOSE "Required to generate D-Bus interfaces for all Akonadi resources.")
......
#cmakedefine01 DISABLE_AKONADI_SEARCH
......@@ -83,6 +83,37 @@ set(libpimcommonakonadi_collection_SRCS
collectionpage/contenttypewidget.cpp
)
set(libpimcommonakonadi_addressline_LIB_SRCS
addressline/completionorder/completionordereditor.cpp
addressline/completionorder/completionorderwidget.cpp
addressline/addresslineedit/addresseelineedit.cpp
addressline/addresslineedit/addresseelineedit_p.cpp
addressline/addresslineedit/addresseelineeditmanager.cpp
addressline/addresslineedit/addresseelineeditldap.cpp
addressline/addresslineedit/addresseelineeditakonadi.cpp
addressline/addresslineedit/addresseelineeditbaloo.cpp
addressline/addresslineedit/kmailcompletion.cpp
addressline/addresslineedit/baloocompletionemail.cpp
addressline/completionconfiguredialog/completionconfiguredialog.cpp
addressline/addresslineedit/addresseelineeditutil.cpp
addressline/recentaddress/recentaddresswidget.cpp
addressline/recentaddress/recentaddresses.cpp
)
if (NOT FORCE_DISABLE_AKONADI_SEARCH)
set(libpimcommonakonadi_addressline_blacklist_baloo_LIB_SRCS
addressline/blacklistbaloocompletion/blacklistbalooemaillist.cpp
addressline/blacklistbaloocompletion/blacklistbalooemailutil.cpp
addressline/blacklistbaloocompletion/blacklistbalooemailcompletionwidget.cpp
addressline/blacklistbaloocompletion/blacklistbalooemailwarning.cpp
addressline/blacklistbaloocompletion/blacklistbalooemailsearchjob.cpp
)
endif()
set(libpimcommonakonadi_addressline_plugin_LIB_SRCS
addressline/plugins/addressesslineeditabstractplugin.cpp
addressline/plugins/addressesslineeditpluginmanager.cpp
)
set(libpimcommonakonadi_SRCS
${libpimcommonakonadi_ldap_SRCS}
......@@ -95,6 +126,9 @@ set(libpimcommonakonadi_SRCS
${libpimcommonakonadi_widgets_SRCS}
${libpimcommonakonadi_util_SRCS}
${libpimcommonakonadi_progresswidget_SRCS}
${libpimcommonakonadi_addressline_LIB_SRCS}
${libpimcommonakonadi_addressline_blacklist_baloo_LIB_SRCS}
${libpimcommonakonadi_addressline_plugin_LIB_SRCS}
)
......@@ -129,8 +163,14 @@ target_link_libraries(KF5PimCommonAkonadi
KF5::KIOWidgets
KF5::KCMUtils
KF5::Ldap
KF5::ItemViews
)
if (NOT FORCE_DISABLE_AKONADI_SEARCH)
target_link_libraries(KF5PimCommonAkonadi PRIVATE KF5::AkonadiSearchPIM)
endif()
target_include_directories(KF5PimCommonAkonadi INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/PimCommonAkonadi/;${KDE_INSTALL_INCLUDEDIR_KF5}/pimcommonakonadi>")
target_include_directories(KF5PimCommonAkonadi PUBLIC "$<BUILD_INTERFACE:${pimcommon_SOURCE_DIR}/src;${pimcommon_BINARY_DIR}/src>")
......@@ -152,8 +192,53 @@ if (BUILD_TESTING)
add_subdirectory(folderdialog/tests/)
add_subdirectory(collectionpage/autotests/)
add_subdirectory(ldap/tests)
add_subdirectory(addressline/tests)
add_subdirectory(addressline/autotests)
if (NOT FORCE_DISABLE_AKONADI_SEARCH)
add_subdirectory(addressline/blacklistbaloocompletion/autotests)
endif()
endif()
if(BUILD_DESIGNERPLUGIN)
add_subdirectory(designer)
endif()
ecm_generate_headers(PimCommonAkonadi_Camelcaseaddressline_HEADERS
HEADER_NAMES
AddresseeLineEdit
REQUIRED_HEADERS PimCommonAkonadi_addressline_HEADERS
PREFIX PimCommonAkonadi
RELATIVE addressline/addresslineedit/
)
ecm_generate_headers(PimCommonAkonadi_Camelcaserecentaddress_HEADERS
HEADER_NAMES
RecentAddresses
REQUIRED_HEADERS PimCommonAkonadi_recentaddress_HEADERS
PREFIX PimCommonAkonadi
RELATIVE addressline/recentaddress/
)
ecm_generate_headers(PimCommonAkonadi_Camelcasecompletionorder_HEADERS
HEADER_NAMES
CompletionOrderEditor
REQUIRED_HEADERS PimCommonAkonadi_completionorder_HEADERS
PREFIX PimCommonAkonadi
RELATIVE addressline/completionorder
)
ecm_generate_headers(PimCommonAkonadi_Camelcasecompletionconfiguredialog_HEADERS
HEADER_NAMES
CompletionConfigureDialog
REQUIRED_HEADERS PimCommonAkonadi_completionconfiguredialog_HEADERS
PREFIX PimCommonAkonadi
RELATIVE addressline/completionconfiguredialog/
)
ecm_generate_headers(PimCommonAkonadi_Camelcasecollectionpage_HEADERS
HEADER_NAMES
IncidencesForWidget
......@@ -183,7 +268,7 @@ ecm_generate_headers(PimCommonAkonadiUtil_CamelCase_HEADERS
RELATIVE util
)
ecm_generate_headers(libkdepimakonadi_Camelcaseprogresswidget_HEADERS
ecm_generate_headers(PimCommonAkonadi_Camelcaseprogresswidget_HEADERS
HEADER_NAMES
ProgressManagerAkonadi
REQUIRED_HEADERS libkdepimakonadi_progresswidget_HEADERS
......@@ -258,7 +343,11 @@ install(FILES
${PimCommon_Camelcasemanagerserversidesubscription_HEADERS}
${PimCommonAkonadi_Camelcasewidgets_HEADERS}
${PimCommonAkonadiUtil_CamelCase_HEADERS}
${libkdepimakonadi_Camelcaseprogresswidget_HEADERS}
${PimCommonAkonadiCamel_caseprogresswidget_HEADERS}
${PimCommonAkonadi_Camelcasecompletionconfiguredialog_HEADERS}
${PimCommonAkonadi_Camelcasecompletionorder_HEADERS}
${PimCommonAkonadi_Camelcaseaddressline_HEADERS}
${PimCommonAkonadi_Camelcaserecentaddress_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/PimCommonAkonadi
COMPONENT Devel
)
......@@ -276,7 +365,12 @@ install(FILES
${PimCommonAkonadi_widgets_HEADERS}
${PimCommon_managerserversidesubscription_HEADERS}
${PimCommonAkonadiUtil_HEADERS}
${libkdepimakonadi_progresswidget_HEADERS}
${PimCommonAkonadi_progresswidget_HEADERS}
${PimCommonAkonadi_completionconfiguredialog_HEADERS}
${PimCommonAkonadi_completionorder_HEADERS}
${PimCommonAkonadi_addressline_HEADERS}
${PimCommonAkonadi_recentaddress_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/pimcommonakonadi
COMPONENT Devel
......
/*
This file is part of libkdepim.
Copyright (c) 2002 Helge Deller <deller@gmx.de>
Copyright (c) 2002 Lubos Lunak <llunak@suse.cz>
Copyright (c) 2001,2003 Carsten Pfeiffer <pfeiffer@kde.org>
Copyright (c) 2001 Waldo Bastian <bastian@kde.org>
Copyright (c) 2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se>
Copyright (c) 2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
Copyright (C) 2017-2020 Laurent Montel <montel@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; 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 "addresseelineedit.h"
#include "addresseelineedit_p.h"
#include "addresseelineeditutil.h"
#include "addresseelineeditmanager.h"
#include "addressline/recentaddress/recentaddresses.h"
#include <KLDAP/LdapClientSearch>
#include <KContacts/VCardConverter>
#include <Job>
#include <QUrl>
#include <KConfigGroup>
#include <KEmailAddress>
#include <KColorScheme>
#include <KJobWidgets>
#include <kcontacts/contactgrouptool.h>
#include <KIO/StoredTransferJob>
#include <Akonadi/Contact/ContactGroupExpandJob>
#include <Akonadi/Contact/ContactGroupSearchJob>
#include <KCompletionBox>
#include "pimcommonakonadi_debug.h"
#include <KLocalizedString>
#include <KStandardShortcut>
#include <KCodecs>
#include <QMimeData>
#include <QApplication>
#include <QObject>
#include <QBuffer>
#include <QEvent>
#include <QClipboard>
#include <QKeyEvent>
#include <QDropEvent>
#include <QMouseEvent>
#include <QMenu>
#include <KSharedConfig>
#include <KMessageBox>
#include <ContactGroupExpandJob>
#include <kcontacts/vcarddrag.h>
#include <addressline/completionconfiguredialog/completionconfiguredialog.h>
using namespace PimCommon;
inline bool itemIsHeader(const QListWidgetItem *item)
{
return item && !item->text().startsWith(QLatin1String(" "));
}
// needs to be unique, but the actual name doesn't matter much
static QString newLineEditObjectName()
{
static int s_count = 0;
QString name(QStringLiteral("KPIM::AddresseeLineEdit"));
if (s_count++) {
name += QLatin1Char('-');
name += QString::number(s_count);
}
return name;
}
AddresseeLineEdit::AddresseeLineEdit(QWidget *parent, bool enableCompletion)
: KLineEdit(parent)
, d(new AddresseeLineEditPrivate(this, enableCompletion))
{
setUrlDropsEnabled(false);
setObjectName(newLineEditObjectName());
setPlaceholderText(QString());
d->init();
}
AddresseeLineEdit::~AddresseeLineEdit()
{
delete d;
}
void AddresseeLineEdit::setFont(const QFont &font)
{
KLineEdit::setFont(font);
if (d->useCompletion()) {
completionBox()->setFont(font);
}
}
void AddresseeLineEdit::setIcon(const QIcon &icon, const QString &tooltip)
{
d->setIcon(icon, tooltip);
}
bool AddresseeLineEdit::expandIntern() const
{
return d->expandIntern();
}
void AddresseeLineEdit::setExpandIntern(bool expand)
{
d->setExpandIntern(expand);
}
void AddresseeLineEdit::setEnableBalooSearch(bool enable)
{
d->setEnableBalooSearch(enable);
}
bool AddresseeLineEdit::enableBalooSearch() const
{
return d->enableAkonadiSearch();
}
void AddresseeLineEdit::setEnableAkonadiSearch(bool enable)
{
d->setEnableAkonadiSearch(enable);
}
bool AddresseeLineEdit::enableAkonadiSearch() const
{
return d->enableAkonadiSearch();
}
void AddresseeLineEdit::allowSemicolonAsSeparator(bool useSemicolonAsSeparator)
{
d->setUseSemicolonAsSeparator(useSemicolonAsSeparator);
}
bool AddresseeLineEdit::showRecentAddresses() const
{
return d->showRecentAddresses();
}
void AddresseeLineEdit::setShowRecentAddresses(bool b)
{
d->setShowRecentAddresses(b);
}
void AddresseeLineEdit::keyPressEvent(QKeyEvent *event)
{
bool accept = false;
const int key = event->key() | event->modifiers();
if (KStandardShortcut::shortcut(KStandardShortcut::SubstringCompletion).contains(key)) {
//TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch
d->updateSearchString();
d->startSearches();
d->doCompletion(true);
accept = true;
} else if (KStandardShortcut::shortcut(KStandardShortcut::TextCompletion).contains(key)) {
const int len = text().length();
if (len == cursorPosition()) { // at End?
d->updateSearchString();
d->startSearches();
d->doCompletion(true);
accept = true;
}
}
const QString oldContent = text();
if (!accept) {
KLineEdit::keyPressEvent(event);
}
// if the text didn't change (eg. because a cursor navigation key was pressed)
// we don't need to trigger a new search
if (oldContent == text()) {
return;
}
if (event->isAccepted()) {
d->updateSearchString();
QString searchString(d->searchString());
//LDAP does not know about our string manipulation, remove it
if (d->searchExtended()) {
searchString = d->searchString().mid(1);
}
d->restartTime(searchString);
}
}
void AddresseeLineEdit::insert(const QString &t)
{
if (!d->smartPaste()) {
KLineEdit::insert(t);
return;
}
QString newText = t.trimmed();
if (newText.isEmpty()) {
return;
}
newText = PimCommon::AddresseeLineEditUtil::adaptPasteMails(newText);
QString contents = text();
int pos = cursorPosition();
if (hasSelectedText()) {
// Cut away the selection.
int start_sel = selectionStart();
pos = start_sel;
contents = contents.left(start_sel) + contents.mid(start_sel + selectedText().length());
}
int eot = contents.length();
while ((eot > 0) && contents.at(eot - 1).isSpace()) {
--eot;
}
if (eot == 0) {
contents.clear();
} else if (pos >= eot) {
if (contents.at(eot - 1) == QLatin1Char(',')) {
--eot;
}
contents.truncate(eot);
contents += QStringLiteral(", ");
pos = eot + 2;
}
contents = contents.left(pos) + newText + contents.mid(pos);
setText(contents);
setModified(true);
setCursorPosition(pos + newText.length());
}
void AddresseeLineEdit::setText(const QString &text)
{
const int cursorPos = cursorPosition();
KLineEdit::setText(text.trimmed());
setCursorPosition(cursorPos);
}
void AddresseeLineEdit::paste()
{
if (d->useCompletion()) {
d->setSmartPaste(true);
}
KLineEdit::paste();
d->setSmartPaste(false);
}
void AddresseeLineEdit::mouseReleaseEvent(QMouseEvent *event)
{
// reimplemented from QLineEdit::mouseReleaseEvent()
#ifndef QT_NO_CLIPBOARD
if (d->useCompletion()
&& QApplication::clipboard()->supportsSelection()
&& !isReadOnly()
&& event->button() == Qt::MidButton) {
d->setSmartPaste(true);
}
#endif
KLineEdit::mouseReleaseEvent(event);
d->setSmartPaste(false);
}
#ifndef QT_NO_DRAGANDDROP
void AddresseeLineEdit::dropEvent(QDropEvent *event)
{
const QMimeData *md = event->mimeData();
// Case one: The user dropped a text/directory (i.e. vcard), so decode its
// contents
if (KContacts::VCardDrag::canDecode(md)) {
KContacts::Addressee::List list;
KContacts::VCardDrag::fromMimeData(md, list);
const KContacts::Addressee::List::ConstIterator end(list.constEnd());
for (KContacts::Addressee::List::ConstIterator ait = list.constBegin(); ait != end; ++ait) {
insertEmails((*ait).emails());
}
}
// Case two: The user dropped a list or Urls.
// Iterate over that list. For mailto: Urls, just add the addressee to the list,
// and for other Urls, download the Url and assume it points to a vCard
else if (md->hasUrls()) {
const QList<QUrl> urls = md->urls();
KContacts::Addressee::List list;
for (const QUrl &url : urls) {
// First, let's deal with mailto Urls. The path() part contains the
// email-address.
if (url.scheme() == QLatin1String("mailto")) {
KContacts::Addressee addressee;
addressee.insertEmail(KEmailAddress::decodeMailtoUrl(url), true /* preferred */);
list += addressee;
} else { // Otherwise, download the vCard to which the Url points
KContacts::VCardConverter converter;
auto job = KIO::storedGet(url);
KJobWidgets::setWindow(job, parentWidget());
if (job->exec()) {
QByteArray data = job->data();
list += converter.parseVCards(data);
if (list.isEmpty()) { // try to parse a contact group
KContacts::ContactGroup group;
QBuffer dataStream(&data);
dataStream.open(QIODevice::ReadOnly);
QString error;
if (KContacts::ContactGroupTool::convertFromXml(&dataStream, group, &error)) {
Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group);
connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &AddresseeLineEdit::groupExpandResult);
expandJob->start();
} else {
qCWarning(PIMCOMMONAKONADI_LOG) << "Error during converting contactgroup " << error;
}
}
} else {
const QString caption(i18n("vCard Import Failed"));
const QString text = i18n("<qt>Unable to access <b>%1</b>.</qt>", url.url());
KMessageBox::error(parentWidget(), text, caption);
}
}
}
// Now, let the user choose which addressee to add.
for (const KContacts::Addressee &addressee : qAsConst(list)) {
insertEmails(addressee.emails());
}
}
// Case three: Let AddresseeLineEdit deal with the rest
else {
if (!isReadOnly()) {
const QList<QUrl> uriList = event->mimeData()->urls();
if (!uriList.isEmpty()) {
QString contents = text();
// remove trailing white space and comma
int eot = contents.length();
while ((eot > 0) && contents.at(eot - 1).isSpace()) {
--eot;
}
if (eot == 0) {
contents.clear();
} else if (contents.at(eot - 1) == QLatin1Char(',')) {
--eot;
contents.truncate(eot);
}
bool mailtoURL = false;
// append the mailto URLs
for (const QUrl &url : uriList) {
if (url.scheme() == QLatin1String("mailto")) {
mailtoURL = true;
QString address;
address = QUrl::fromPercentEncoding(url.path().toLatin1());
address = KCodecs::decodeRFC2047String(address);
if (!contents.isEmpty()) {
contents.append(QLatin1String(", "));
}
contents.append(address);
}
}
if (mailtoURL) {
setText(contents);
setModified(true);
return;
}
} else {
// Let's see if this drop contains a comma separated list of emails
if (md->hasText()) {
const QString dropData = md->text();
const QStringList addrs = KEmailAddress::splitAddressList(dropData);
if (!addrs.isEmpty()) {
if (addrs.count() == 1) {
QUrl url(dropData);
if (url.scheme() == QLatin1String("mailto")) {
KContacts::Addressee addressee;
addressee.insertEmail(KEmailAddress::decodeMailtoUrl(url), true /* preferred */);
insertEmails(addressee.emails());
} else {
setText(KEmailAddress::normalizeAddressesAndDecodeIdn(dropData));
}
} else {
setText(KEmailAddress::normalizeAddressesAndDecodeIdn(dropData));
}