Commit 76714e3e authored by David Jarvie's avatar David Jarvie
Browse files

Fix drag-and-drop of text onto line edits in alarm edit dialog

parent 1e35dd37
Pipeline #23790 passed with stage
in 11 minutes and 36 seconds
KAlarm Change Log
=== Version 3.0.0 --- 15 June 2020 ===
=== Version 3.0.0 --- 16 June 2020 ===
+ Provide option to use file system resources instead of Akonadi resources.
+ Enable selection of multiple calendar files in Import Alarms dialogue.
+ Show alarm calendars sorted by name in calendars list.
+ Return to last used tab in Configuration dialogue when it is reopened.
+ Fix handling of calendar update or save errors when making alarm changes.
+ Allow drag-and-drop of email onto display alarm text edit field.
+ Fix drag-and-drop of text onto line edits in alarm edit dialogue.
+ Refactor AlarmCalendar to split out resources and display calendars.
=== Version 2.14.2 (KDE Applications 20.04.2) --- 8 June 2020 ===
......
......@@ -29,6 +29,7 @@ set(libkalarm_SRCS
lib/combobox.cpp
lib/config.cpp
lib/desktop.cpp
lib/dragdrop.cpp
lib/file.cpp
lib/filedialog.cpp
lib/groupbox.cpp
......
......@@ -40,6 +40,7 @@
#include "lib/buttongroup.h"
#include "lib/checkbox.h"
#include "lib/colourbutton.h"
#include "lib/dragdrop.h"
#include "lib/file.h"
#include "lib/lineedit.h"
#include "lib/messagebox.h"
......@@ -1880,7 +1881,7 @@ void TextEdit::dragEnterEvent(QDragEnterEvent* e)
e->ignore(); // don't accept "text/calendar" objects
return;
}
if (mEmailDrop && KAlarm::mayHaveRFC822(e->mimeData()))
if (mEmailDrop && DragDrop::mayHaveRFC822(e->mimeData()))
{
e->acceptProposedAction();
return;
......@@ -1890,7 +1891,7 @@ void TextEdit::dragEnterEvent(QDragEnterEvent* e)
void TextEdit::dragMoveEvent(QDragMoveEvent* e)
{
if (mEmailDrop && KAlarm::mayHaveRFC822(e->mimeData()))
if (mEmailDrop && DragDrop::mayHaveRFC822(e->mimeData()))
{
e->acceptProposedAction();
return;
......@@ -1903,12 +1904,12 @@ void TextEdit::dragMoveEvent(QDragMoveEvent* e)
*/
void TextEdit::dropEvent(QDropEvent* e)
{
const QMimeData* data = e->mimeData();
if (mEmailDrop)
{
const QMimeData* data = e->mimeData();
AlarmText alarmText;
bool haveEmail = false;
if (KAlarm::dropRFC822(data, alarmText))
if (DragDrop::dropRFC822(data, alarmText))
{
// Email message(s). Ignore all but the first.
qCDebug(KALARM_LOG) << "TextEdit::dropEvent: email";
......@@ -1918,7 +1919,7 @@ void TextEdit::dropEvent(QDropEvent* e)
{
QUrl url;
Akonadi::Item item;
if (KAlarm::dropAkonadiEmail(data, url, item, alarmText))
if (DragDrop::dropAkonadiEmail(data, url, item, alarmText))
{
// It's an email held in Akonadi
qCDebug(KALARM_LOG) << "TextEdit::dropEvent: Akonadi email";
......@@ -1932,6 +1933,12 @@ void TextEdit::dropEvent(QDropEvent* e)
return;
}
}
QString text;
if (DragDrop::dropPlainText(data, text))
{
setPlainText(text);
return;
}
KTextEdit::dropEvent(e);
}
......
......@@ -47,9 +47,6 @@
#include <KAlarmCal/Identities>
#include <KAlarmCal/KAEvent>
#include <AkonadiCore/ItemFetchJob>
#include <AkonadiCore/ItemFetchScope>
#include <KMime/Message>
#include <KCalendarCore/Event>
#include <KCalendarCore/ICalFormat>
#include <KCalendarCore/Person>
......@@ -82,8 +79,6 @@ using namespace KCalendarCore;
#include <QStandardPaths>
#include <QPushButton>
#include <QTemporaryFile>
#include <QMimeData>
#include <QUrlQuery>
namespace
......@@ -150,8 +145,6 @@ const char* DONT_SHOW_ERRORS_GROUP = "DontShowErrors";
KAlarm::UpdateResult updateEvent(KAEvent&, KAlarm::UpdateError, QWidget* msgParent);
void editNewTemplate(EditAlarmDlg::Type, const KAEvent* preset, QWidget* parent);
void displayUpdateError(QWidget* parent, KAlarm::UpdateError, const UpdateStatusData&, bool showKOrgError = true);
KAlarmCal::AlarmText kMimeEmailToAlarmText(KMime::Content&, Akonadi::Item::Id);
QString getMailHeader(const char* header, KMime::Content&);
KAlarm::UpdateResult sendToKOrganizer(const KAEvent&);
KAlarm::UpdateResult deleteFromKOrganizer(const QString& eventID);
KAlarm::UpdateResult runKOrganizer();
......@@ -1647,82 +1640,6 @@ void refreshAlarmsIfQueued()
}
}
/******************************************************************************
* Check whether drag-and-drop data may contain an RFC822 message (Akonadi or not).
*/
bool mayHaveRFC822(const QMimeData* data)
{
return data->hasFormat(QStringLiteral("message/rfc822"))
|| data->hasUrls();
}
/******************************************************************************
* Extract dragged and dropped RFC822 message data.
*/
bool dropRFC822(const QMimeData* data, KAlarmCal::AlarmText& alarmText)
{
const QByteArray bytes = data->data(QStringLiteral("message/rfc822"));
if (bytes.isEmpty())
{
alarmText.clear();
return false;
}
// Email message(s). Ignore all but the first.
qCDebug(KALARM_LOG) << "KAlarm::dropRFC822: have email";
KMime::Content content;
content.setContent(bytes);
content.parse();
alarmText = kMimeEmailToAlarmText(content, -1);
return true;
}
/******************************************************************************
* Extract dragged and dropped Akonadi RFC822 message data.
*/
bool dropAkonadiEmail(const QMimeData* data, QUrl& url, Akonadi::Item& item, KAlarmCal::AlarmText& alarmText)
{
alarmText.clear();
const QList<QUrl> urls = data->urls();
if (urls.isEmpty())
{
url = QUrl();
item.setId(-1);
return false;
}
url = urls.at(0);
item = Akonadi::Item::fromUrl(url);
if (!item.isValid())
return false;
// It's an Akonadi item
qCDebug(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi item" << item.id();
if (QUrlQuery(url).queryItemValue(QStringLiteral("type")) != QLatin1String("message/rfc822"))
return false;
// It's an email held in Akonadi
qCDebug(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi email";
Akonadi::ItemFetchJob* job = new Akonadi::ItemFetchJob(item);
job->fetchScope().fetchFullPayload();
Akonadi::Item::List items;
if (job->exec())
items = job->items();
if (items.isEmpty())
qCWarning(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi item" << item.id() << "not found";
else
{
const Akonadi::Item& it = items.at(0);
if (!it.isValid() || !it.hasPayload<KMime::Message::Ptr>())
qCWarning(KALARM_LOG) << "KAlarm::dropAkonadiEmail: invalid email";
else
{
KMime::Message::Ptr message = it.payload<KMime::Message::Ptr>();
alarmText = kMimeEmailToAlarmText(*message, it.id());
}
}
return true;
}
/******************************************************************************
* Start KMail if it isn't already running, optionally minimised.
* Reply = reason for failure to run KMail
......@@ -2059,31 +1976,6 @@ void displayUpdateError(QWidget* parent, KAlarm::UpdateError code, const UpdateS
displayKOrgUpdateError(parent, code, status.status, status.warnKOrg);
}
/******************************************************************************
* Convert a KMime email instance to AlarmText.
*/
KAlarmCal::AlarmText kMimeEmailToAlarmText(KMime::Content& content, Akonadi::Item::Id itemId)
{
QString body;
if (content.textContent())
body = content.textContent()->decodedText(true, true); // strip trailing newlines & spaces
AlarmText alarmText;
alarmText.setEmail(getMailHeader("To", content),
getMailHeader("From", content),
getMailHeader("Cc", content),
getMailHeader("Date", content),
getMailHeader("Subject", content),
body,
itemId);
return alarmText;
}
QString getMailHeader(const char* header, KMime::Content& content)
{
KMime::Headers::Base* hd = content.headerByType(header);
return hd ? hd->asUnicodeString() : QString();
}
/******************************************************************************
* Tell KOrganizer to put an alarm in its calendar.
* It will be held by KOrganizer as a simple event, without alarms - KAlarm
......
......@@ -36,7 +36,6 @@ using namespace KAlarmCal;
namespace KCal { class Event; }
class QWidget;
class QAction;
class QMimeData;
class KToggleAction;
class Resource;
class MainWindow;
......@@ -105,28 +104,6 @@ void outputAlarmWarnings(QWidget* parent, const KAEvent* = nullpt
void refreshAlarms();
void refreshAlarmsIfQueued(); // must only be called from KAlarmApp::processQueue()
/** Check whether drag-and-drop data may contain an RFC822 message (Akonadi or not). */
bool mayHaveRFC822(const QMimeData* data);
/** Extract dragged and dropped RFC822 message data.
* If there is more than one message, only the first is extracted.
* @param data Dropped data.
* @param alarmText Extracted email data.
* @return true if @p data contained RFC822 message data, false if not.
*/
bool dropRFC822(const QMimeData* data, KAlarmCal::AlarmText& alarmText);
/** Extract dragged and dropped Akonadi RFC822 message data.
* @param data Dropped data.
* @param url Receives the first URL in the data, or empty if data does
* not provide URLs.
* @param item Receives the Akonadi Item specified by @p url, or invalid
* if not an Akonadi URL.
* @param alarmText Extracted email data.
* @return true if @p data contained RFC822 message data, false if not.
*/
bool dropAkonadiEmail(const QMimeData* data, QUrl& url, Akonadi::Item& item, KAlarmCal::AlarmText& alarmText);
QString runKMail();
QStringList dontShowErrors(const EventId&);
......
/*
* dragdrop.cpp - drag and drop functions
* Program: kalarm
* Copyright © 2003-2020 David Jarvie <djarvie@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 "dragdrop.h"
#include "kalarm_debug.h"
#include <KAlarmCal/AlarmText>
#include <AkonadiCore/ItemFetchJob>
#include <AkonadiCore/ItemFetchScope>
#include <KMime/Message>
#include <QMimeData>
#include <QUrlQuery>
namespace
{
KAlarmCal::AlarmText kMimeEmailToAlarmText(KMime::Content&, Akonadi::Item::Id);
QString getMailHeader(const char* header, KMime::Content&);
}
namespace DragDrop
{
/******************************************************************************
* Get plain text from a drag-and-drop object.
*/
bool dropPlainText(const QMimeData* data, QString& text)
{
static const QString TextPlain = QStringLiteral("text/plain");
static const QString TextPlainUtf8 = QStringLiteral("text/plain;charset=utf-8");
if (data->hasFormat(TextPlainUtf8))
text = QString::fromUtf8(data->data(TextPlainUtf8));
else if (data->hasFormat(TextPlain))
text = QString::fromLocal8Bit(data->data(TextPlain));
else
{
text.clear();
return false;
}
return true;
}
/******************************************************************************
* Check whether drag-and-drop data may contain an RFC822 message (Akonadi or not).
*/
bool mayHaveRFC822(const QMimeData* data)
{
return data->hasFormat(QStringLiteral("message/rfc822"))
|| data->hasUrls();
}
/******************************************************************************
* Extract dragged and dropped RFC822 message data.
*/
bool dropRFC822(const QMimeData* data, KAlarmCal::AlarmText& alarmText)
{
const QByteArray bytes = data->data(QStringLiteral("message/rfc822"));
if (bytes.isEmpty())
{
alarmText.clear();
return false;
}
// Email message(s). Ignore all but the first.
qCDebug(KALARM_LOG) << "KAlarm::dropRFC822: have email";
KMime::Content content;
content.setContent(bytes);
content.parse();
alarmText = kMimeEmailToAlarmText(content, -1);
return true;
}
/******************************************************************************
* Extract dragged and dropped Akonadi RFC822 message data.
*/
bool dropAkonadiEmail(const QMimeData* data, QUrl& url, Akonadi::Item& item, KAlarmCal::AlarmText& alarmText)
{
alarmText.clear();
const QList<QUrl> urls = data->urls();
if (urls.isEmpty())
{
url = QUrl();
item.setId(-1);
return false;
}
url = urls.at(0);
item = Akonadi::Item::fromUrl(url);
if (!item.isValid())
return false;
// It's an Akonadi item
qCDebug(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi item" << item.id();
if (QUrlQuery(url).queryItemValue(QStringLiteral("type")) != QLatin1String("message/rfc822"))
return false;
// It's an email held in Akonadi
qCDebug(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi email";
Akonadi::ItemFetchJob* job = new Akonadi::ItemFetchJob(item);
job->fetchScope().fetchFullPayload();
Akonadi::Item::List items;
if (job->exec())
items = job->items();
if (items.isEmpty())
qCWarning(KALARM_LOG) << "KAlarm::dropAkonadiEmail: Akonadi item" << item.id() << "not found";
else
{
const Akonadi::Item& it = items.at(0);
if (!it.isValid() || !it.hasPayload<KMime::Message::Ptr>())
qCWarning(KALARM_LOG) << "KAlarm::dropAkonadiEmail: invalid email";
else
{
KMime::Message::Ptr message = it.payload<KMime::Message::Ptr>();
alarmText = kMimeEmailToAlarmText(*message, it.id());
}
}
return true;
}
} // namespace DragDrop
namespace
{
/******************************************************************************
* Convert a KMime email instance to AlarmText.
*/
KAlarmCal::AlarmText kMimeEmailToAlarmText(KMime::Content& content, Akonadi::Item::Id itemId)
{
QString body;
if (content.textContent())
body = content.textContent()->decodedText(true, true); // strip trailing newlines & spaces
KAlarmCal::AlarmText alarmText;
alarmText.setEmail(getMailHeader("To", content),
getMailHeader("From", content),
getMailHeader("Cc", content),
getMailHeader("Date", content),
getMailHeader("Subject", content),
body,
itemId);
return alarmText;
}
QString getMailHeader(const char* header, KMime::Content& content)
{
KMime::Headers::Base* hd = content.headerByType(header);
return hd ? hd->asUnicodeString() : QString();
}
}
// vim: et sw=4:
/*
* dragdrop.h - drag and drop functions
* Program: kalarm
* Copyright © 2020 David Jarvie <djarvie@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 DRAGDROP_H
#define DRAGDROP_H
namespace KAlarmCal { class AlarmText; }
namespace Akonadi { class Item; }
class QString;
class QUrl;
class QMimeData;
namespace DragDrop
{
/** Get plain text from a drag-and-drop object.
* @param data Dropped data.
* @param text Extracted text.
* @return true if @p data contained plain text data, false if not.
*/
bool dropPlainText(const QMimeData* data, QString& text);
/** Check whether drag-and-drop data may contain an RFC822 message (Akonadi or not). */
bool mayHaveRFC822(const QMimeData* data);
/** Extract dragged and dropped RFC822 message data.
* If there is more than one message, only the first is extracted.
* @param data Dropped data.
* @param alarmText Extracted email data.
* @return true if @p data contained RFC822 message data, false if not.
*/
bool dropRFC822(const QMimeData* data, KAlarmCal::AlarmText& alarmText);
/** Extract dragged and dropped Akonadi RFC822 message data.
* @param data Dropped data.
* @param url Receives the first URL in the data, or empty if data does
* not provide URLs.
* @param item Receives the Akonadi Item specified by @p url, or invalid
* if not an Akonadi URL.
* @param alarmText Extracted email data.
* @return true if @p data contained RFC822 message data, false if not.
*/
bool dropAkonadiEmail(const QMimeData* data, QUrl& url, Akonadi::Item& item, KAlarmCal::AlarmText& alarmText);
} // namespace KAlarm
#endif // DRAGDROP_H
// vim: et sw=4:
/*
* lineedit.cpp - Line edit widget with extra drag and drop options
* Program: kalarm
* Copyright © 2003-2019 David Jarvie <djarvie@kde.org>
* Copyright © 2003-2020 David Jarvie <djarvie@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
......@@ -20,6 +20,8 @@
#include "lineedit.h"
#include "dragdrop.h"
#include <KContacts/VCardDrag>
#include <KCalUtils/ICalDrag>
......@@ -32,6 +34,7 @@
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFocusEvent>
#include <QDebug>
/*=============================================================================
......@@ -56,7 +59,6 @@ LineEdit::LineEdit(QWidget* parent)
void LineEdit::init()
{
setAcceptDrops(false);
if (mType == Url)
{
setCompletionMode(KCompletion::CompletionShell);
......@@ -104,21 +106,22 @@ void LineEdit::dragEnterEvent(QDragEnterEvent* e)
|| data->hasUrls()
|| (mType == Emails && KContacts::VCardDrag::canDecode(data)));
if (ok)
e->accept(rect());
e->acceptProposedAction();
else
e->ignore(rect());
e->ignore();
}
void LineEdit::dropEvent(QDropEvent* e)
{
const QMimeData* data = e->mimeData();
QString txt;
QString newText;
QStringList newEmails;
QList<QUrl> files;
KContacts::Addressee::List addrList;
if (mType == Emails
&& KContacts::VCardDrag::canDecode(data) && KContacts::VCardDrag::fromMimeData(data, addrList))
&& KContacts::VCardDrag::canDecode(data) && KContacts::VCardDrag::fromMimeData(data, addrList))
{
// KAddressBook entries
for (KContacts::Addressee::List::Iterator it = addrList.begin(); it != addrList.end(); ++it)
......@@ -153,10 +156,9 @@ void LineEdit::dropEvent(QDropEvent* e)
break;
}
}
else if (data->hasText())
else if (DragDrop::dropPlainText(data, txt))
{
// Plain text
const QString txt = data->text();
if (mType == Emails)
{
// Remove newlines from a list of email addresses, and allow an eventual mailto: scheme
......
......@@ -22,6 +22,7 @@
#define LINEEDIT_H
#include <KLineEdit>
class QDragEnterEvent;
class QFocusEvent;
class QDropEvent;
......@@ -45,54 +46,56 @@ class QDropEvent;
*/
class LineEdit : public KLineEdit
{
Q_OBJECT
public:
/** Types of drag and drop content which will be accepted.
* @li Text - the line edit contains general text. It accepts text, a URL
* or an email from KMail (the subject line is used). If multiple
* URLs or emails are dropped, only the first is used; the
* rest are ignored.
* @li Url - the line edit contains a URL. It accepts text or a URL. If
* multiple URLs are dropped, only the first URL is used; the
* rest are ignored.
* @li Emails - the line edit contains email addresses. It accepts text,
* mailto: URLs, emails from KMail (the From address is used)
* or vcard data (e.g. from KAddressBook). If multiple emails
* are dropped, only the first is used; the rest are ignored.
*/
enum Type { Text, Url, Emails };
/** Constructor.
* @param type The content type for the line edit.
* @param parent The parent object of this widget.
*/
explicit LineEdit(Type type, QWidget* parent = nullptr);
/** Constructs a line edit whose content type is Text.
* @param parent The parent object of this widget.
*/
explicit LineEdit(QWidget* parent = nullptr);
/** Return the entered text.
* If the type is Url, tilde expansion is performed.
*/
QString text() const;
/** Prevents the line edit's contents being selected when the widget receives focus. */
void setNoSelect() { mNoSelect = true; }
/** Sets whether the cursor should be set at the beginning or end of the text when
* setText() is called.
*/
void setCursorAtEnd(bool end = true) { mSetCursorAtEnd = end; }
public Q_SLOTS:
/** Sets the contents of the line edit to be @p str. */
void setText(const QString& str) override;
protected:
void focusInEvent(QFocusEvent*) override;
void dragEnterEvent(QDragEnterEvent*) override;