File indexing completed on 2024-11-24 04:34:18

0001 /***************************************************************************
0002  *   SPDX-License-Identifier: GPL-2.0-or-later
0003  *                                                                         *
0004  *   SPDX-FileCopyrightText: 2004-2022 Thomas Fischer <fischer@unix-ag.uni-kl.de>
0005  *                                                                         *
0006  *   This program is free software; you can redistribute it and/or modify  *
0007  *   it under the terms of the GNU General Public License as published by  *
0008  *   the Free Software Foundation; either version 2 of the License, or     *
0009  *   (at your option) any later version.                                   *
0010  *                                                                         *
0011  *   This program is distributed in the hope that it will be useful,       *
0012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0014  *   GNU General Public License for more details.                          *
0015  *                                                                         *
0016  *   You should have received a copy of the GNU General Public License     *
0017  *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
0018  ***************************************************************************/
0019 
0020 #include "fieldlineedit.h"
0021 
0022 #include <typeinfo>
0023 
0024 #include <QMenu>
0025 #include <QBuffer>
0026 #include <QFileInfo>
0027 #include <QDir>
0028 #include <QDragEnterEvent>
0029 #include <QDropEvent>
0030 #include <QPushButton>
0031 #include <QFontDatabase>
0032 #include <QUrl>
0033 #include <QMimeType>
0034 #include <QMimeData>
0035 #include <QRegularExpression>
0036 
0037 #include <kio_version.h>
0038 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0039 #include <KIO/OpenUrlJob>
0040 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0041 #include <KIO/JobUiDelegateFactory>
0042 #else // < 5.98.0
0043 #include <KIO/JobUiDelegate>
0044 #endif // QT_VERSION_CHECK(5, 98, 0)
0045 #else // < 5.71.0
0046 #include <KRun>
0047 #endif // KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0)
0048 #include <KMessageBox>
0049 #include <KLocalizedString>
0050 
0051 #include <BibTeXFields>
0052 #include <Preferences>
0053 #include <File>
0054 #include <Entry>
0055 #include <Value>
0056 #include <FileInfo>
0057 #include <FileImporterBibTeX>
0058 #include <FileExporterBibTeX>
0059 #include <EncoderLaTeX>
0060 #include "logging_gui.h"
0061 
0062 class FieldLineEdit::FieldLineEditPrivate
0063 {
0064 private:
0065     FieldLineEdit *parent;
0066     KBibTeX::TypeFlags typeFlags;
0067     QPushButton *buttonOpenUrl;
0068 
0069 public:
0070     QMenu *menuTypes;
0071     const KBibTeX::TypeFlag preferredTypeFlag;
0072     KBibTeX::TypeFlag typeFlag;
0073     QUrl urlToOpen;
0074     const File *file;
0075     QString fieldKey;
0076 
0077     FieldLineEditPrivate(KBibTeX::TypeFlag ptf, KBibTeX::TypeFlags tf, FieldLineEdit *p)
0078             : parent(p), typeFlags(tf), preferredTypeFlag(ptf), file(nullptr) {
0079         menuTypes = new QMenu(parent);
0080         setupMenu();
0081 
0082         buttonOpenUrl = new QPushButton(QIcon::fromTheme(QStringLiteral("document-open-remote")), QString(), parent);
0083         buttonOpenUrl->setVisible(false);
0084         buttonOpenUrl->setProperty("isConst", true);
0085         parent->appendWidget(buttonOpenUrl);
0086         connect(buttonOpenUrl, &QPushButton::clicked, parent, [this]() {
0087             openUrl();
0088         });
0089 
0090         connect(p, &FieldLineEdit::textChanged, p, [this](const QString & text) {
0091             textChanged(text);
0092         });
0093 
0094         Value value;
0095         typeFlag = determineTypeFlag(value, preferredTypeFlag, typeFlags);
0096         updateGUI(typeFlag);
0097     }
0098 
0099     bool reset(const Value &value, const KBibTeX::TypeFlag preferredTypeFlag) {
0100         bool result = false;
0101         QString text;
0102         typeFlag = determineTypeFlag(value, preferredTypeFlag, typeFlags);
0103         updateGUI(typeFlag);
0104 
0105         if (!value.isEmpty()) {
0106             if (typeFlag == KBibTeX::TypeFlag::Source) {
0107                 /// simple case: field's value is to be shown as BibTeX code, including surrounding curly braces
0108                 FileExporterBibTeX exporter(parent);
0109                 text = exporter.valueToBibTeX(value, Encoder::TargetEncoding::UTF8);
0110                 result = true;
0111             } else {
0112                 /// except for the source view type flag, type flag views do not support composed values,
0113                 /// therefore only the first value will be shown
0114                 const QSharedPointer<ValueItem> first = value.first();
0115 
0116                 const QSharedPointer<PlainText> plainText = first.dynamicCast<PlainText>();
0117                 if (typeFlag == KBibTeX::TypeFlag::PlainText && !plainText.isNull()) {
0118                     text = plainText->text();
0119                     result = true;
0120                 } else {
0121                     const QSharedPointer<Person> person = first.dynamicCast<Person>();
0122                     if (typeFlag == KBibTeX::TypeFlag::Person && !person.isNull()) {
0123                         text = Person::transcribePersonName(person.data(), Preferences::instance().personNameFormat());
0124                         result = true;
0125                     } else {
0126                         const QSharedPointer<MacroKey> macroKey = first.dynamicCast<MacroKey>();
0127                         if (typeFlag == KBibTeX::TypeFlag::Reference && !macroKey.isNull()) {
0128                             text = macroKey->text();
0129                             result = true;
0130                         } else {
0131                             const QSharedPointer<Keyword> keyword = first.dynamicCast<Keyword>();
0132                             if (typeFlag == KBibTeX::TypeFlag::Keyword && !keyword.isNull()) {
0133                                 text = keyword->text();
0134                                 result = true;
0135                             } else {
0136                                 const QSharedPointer<VerbatimText> verbatimText = first.dynamicCast<VerbatimText>();
0137                                 if (typeFlag == KBibTeX::TypeFlag::Verbatim && !verbatimText.isNull()) {
0138                                     text = verbatimText->text();
0139                                     result = true;
0140                                 } else
0141                                     qCWarning(LOG_KBIBTEX_GUI) << "Could not reset: " << typeFlag << " " << typeid((void)*first).name() << " : " << PlainTextValue::text(value);
0142                             }
0143                         }
0144                     }
0145                 }
0146             }
0147         }
0148 
0149         updateURL(text);
0150 
0151         parent->setText(text);
0152         return result;
0153     }
0154 
0155     bool apply(Value &value) const {
0156         value.clear();
0157         /// Remove unnecessary white space from input
0158         /// Exception: source and verbatim content is kept unmodified
0159         const QString text = typeFlag == KBibTeX::TypeFlag::Source || typeFlag == KBibTeX::TypeFlag::Verbatim ? parent->text() : parent->text().simplified();
0160         if (text.isEmpty())
0161             return true;
0162 
0163         const EncoderLaTeX &encoder = EncoderLaTeX::instance();
0164         const QString encodedText = encoder.decode(text);
0165         static const QRegularExpression invalidCharsForReferenceRegExp(QStringLiteral("[^-_:/a-zA-Z0-9]"));
0166         if (encodedText.isEmpty())
0167             return true;
0168         else if (typeFlag == KBibTeX::TypeFlag::PlainText) {
0169             value.append(QSharedPointer<PlainText>(new PlainText(encodedText)));
0170             return true;
0171         } else if (typeFlag == KBibTeX::TypeFlag::Reference && !encodedText.contains(invalidCharsForReferenceRegExp)) {
0172             value.append(QSharedPointer<MacroKey>(new MacroKey(encodedText)));
0173             return true;
0174         } else if (typeFlag == KBibTeX::TypeFlag::Person) {
0175             QSharedPointer<Person> person = FileImporterBibTeX::personFromString(encodedText);
0176             if (!person.isNull())
0177                 value.append(person);
0178             return true;
0179         } else if (typeFlag == KBibTeX::TypeFlag::Keyword) {
0180             const QList<QSharedPointer<Keyword> > keywords = FileImporterBibTeX::splitKeywords(encodedText);
0181             for (const auto &keyword : keywords)
0182                 value.append(keyword);
0183             return true;
0184         } else if (typeFlag == KBibTeX::TypeFlag::Source) {
0185             const QString key = typeFlags.testFlag(KBibTeX::TypeFlag::Person) ? QStringLiteral("author") : QStringLiteral("title");
0186             FileImporterBibTeX importer(parent);
0187             const QString fakeBibTeXFile = QString(QStringLiteral("@article{dummy, %1=%2}")).arg(key, encodedText);
0188 
0189             const QScopedPointer<const File> file(importer.fromString(fakeBibTeXFile));
0190             if (!file.isNull() && file->count() == 1) {
0191                 QSharedPointer<Entry> entry = file->first().dynamicCast<Entry>();
0192                 if (!entry.isNull()) {
0193                     value = entry->value(key);
0194                     return !value.isEmpty();
0195                 } else
0196                     qCWarning(LOG_KBIBTEX_GUI) << "Parsing " << fakeBibTeXFile << " did not result in valid entry";
0197             }
0198         } else if (typeFlag == KBibTeX::TypeFlag::Verbatim) {
0199             value.append(QSharedPointer<VerbatimText>(new VerbatimText(text)));
0200             return true;
0201         }
0202 
0203         return false;
0204     }
0205 
0206     int validateCurlyBracketContext(const QString &text) const {
0207         int openingCB = 0, closingCB = 0;
0208 
0209         for (int i = 0; i < text.length(); ++i) {
0210             if (i == 0 || text[i - 1] != QLatin1Char('\\')) {
0211                 if (text[i] == QLatin1Char('{')) ++openingCB;
0212                 else if (text[i] == QLatin1Char('}')) ++closingCB;
0213             }
0214         }
0215 
0216         return openingCB - closingCB;
0217     }
0218 
0219     bool validate(QWidget **widgetWithIssue, QString &message) const {
0220         message.clear();
0221 
0222         /// Remove unnecessary white space from input
0223         /// Exception: source and verbatim content is kept unmodified
0224         const QString text = typeFlag == KBibTeX::TypeFlag::Source || typeFlag == KBibTeX::TypeFlag::Verbatim ? parent->text() : parent->text().simplified();
0225         if (text.isEmpty())
0226             return true;
0227 
0228         const EncoderLaTeX &encoder = EncoderLaTeX::instance();
0229         const QString encodedText = encoder.decode(text);
0230         if (encodedText.isEmpty())
0231             return true;
0232 
0233         bool result = false;
0234         if (typeFlag == KBibTeX::TypeFlag::PlainText || typeFlag == KBibTeX::TypeFlag::Person || typeFlag == KBibTeX::TypeFlag::Keyword) {
0235             result = validateCurlyBracketContext(text) == 0;
0236             if (!result) message = i18n("Opening and closing curly brackets do not match.");
0237         } else if (typeFlag == KBibTeX::TypeFlag::Reference) {
0238             static const QRegularExpression validReferenceRegExp(QStringLiteral("^[-_:/a-zA-Z0-9]+$"));
0239             const QRegularExpressionMatch validReferenceMatch = validReferenceRegExp.match(text);
0240             result = validReferenceMatch.hasMatch() && validReferenceMatch.captured() == text;
0241             if (!result) message = i18n("Reference contains characters outside of the allowed set.");
0242         } else if (typeFlag == KBibTeX::TypeFlag::Source) {
0243             const QString key = typeFlags.testFlag(KBibTeX::TypeFlag::Person) ? QStringLiteral("author") : QStringLiteral("title");
0244             FileImporterBibTeX importer(parent);
0245             const QString fakeBibTeXFile = QString(QStringLiteral("@article{dummy, %1=%2}")).arg(key, encodedText);
0246 
0247             const QScopedPointer<const File> file(importer.fromString(fakeBibTeXFile));
0248             if (file.isNull() || file->count() != 1) return false;
0249             QSharedPointer<Entry> entry = file->first().dynamicCast<Entry>();
0250             result = !entry.isNull() && entry->count() == 1;
0251             if (!result) message = i18n("Source code could not be parsed correctly.");
0252         } else if (typeFlag == KBibTeX::TypeFlag::Verbatim) {
0253             result = validateCurlyBracketContext(text) == 0;
0254             if (!result) message = i18n("Opening and closing curly brackets do not match.");
0255         }
0256 
0257         if (!result && widgetWithIssue != nullptr)
0258             *widgetWithIssue = parent;
0259 
0260         return result;
0261     }
0262 
0263     void clear() {
0264         const KBibTeX::TypeFlag newTypeFlag = typeFlags.testFlag(preferredTypeFlag) ? preferredTypeFlag : KBibTeX::TypeFlag::Source;
0265         if (newTypeFlag != typeFlag)
0266             updateGUI(typeFlag = newTypeFlag);
0267     }
0268 
0269     KBibTeX::TypeFlag determineTypeFlag(const Value &value, KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags availableTypeFlags) {
0270         KBibTeX::TypeFlag result = KBibTeX::TypeFlag::Source;
0271         if (availableTypeFlags.testFlag(preferredTypeFlag) && typeFlagSupported(value, preferredTypeFlag))
0272             result = preferredTypeFlag;
0273         else if (value.count() == 1) {
0274             int p = 1;
0275             for (int i = 1; i < 8; ++i, p <<= 1) {
0276                 const KBibTeX::TypeFlag flag = static_cast<KBibTeX::TypeFlag>(p);
0277                 if (availableTypeFlags.testFlag(flag) && typeFlagSupported(value, flag)) {
0278                     result = flag; break;
0279                 }
0280             }
0281         }
0282         return result;
0283     }
0284 
0285     bool typeFlagSupported(const Value &value, KBibTeX::TypeFlag typeFlag) {
0286         if (value.isEmpty() || typeFlag == KBibTeX::TypeFlag::Source)
0287             return true;
0288 
0289         const QSharedPointer<ValueItem> first = value.first();
0290         if (value.count() > 1)
0291             return typeFlag == KBibTeX::TypeFlag::Source;
0292         else if (typeFlag == KBibTeX::TypeFlag::Keyword && Keyword::isKeyword(*first))
0293             return true;
0294         else if (typeFlag == KBibTeX::TypeFlag::Person && Person::isPerson(*first))
0295             return true;
0296         else if (typeFlag == KBibTeX::TypeFlag::PlainText && PlainText::isPlainText(*first))
0297             return true;
0298         else if (typeFlag == KBibTeX::TypeFlag::Reference && MacroKey::isMacroKey(*first))
0299             return true;
0300         else if (typeFlag == KBibTeX::TypeFlag::Verbatim && VerbatimText::isVerbatimText(*first))
0301             return true;
0302         else
0303             return false;
0304     }
0305 
0306 
0307     void setupMenu() {
0308         menuTypes->clear();
0309 
0310         if (typeFlags.testFlag(KBibTeX::TypeFlag::PlainText)) {
0311             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::PlainText), i18n("Plain Text"));
0312             connect(action, &QAction::triggered, parent, [this]() {
0313                 typeChanged(KBibTeX::TypeFlag::PlainText);
0314             });
0315         }
0316         if (typeFlags.testFlag(KBibTeX::TypeFlag::Reference)) {
0317             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::Reference), i18n("Reference"));
0318             connect(action, &QAction::triggered, parent, [this]() {
0319                 typeChanged(KBibTeX::TypeFlag::Reference);
0320             });
0321         }
0322         if (typeFlags.testFlag(KBibTeX::TypeFlag::Person)) {
0323             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::Person), i18n("Person"));
0324             connect(action, &QAction::triggered, parent, [this]() {
0325                 typeChanged(KBibTeX::TypeFlag::Person);
0326             });
0327         }
0328         if (typeFlags.testFlag(KBibTeX::TypeFlag::Keyword)) {
0329             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::Keyword), i18n("Keyword"));
0330             connect(action, &QAction::triggered, parent, [this]() {
0331                 typeChanged(KBibTeX::TypeFlag::Keyword);
0332             });
0333         }
0334         if (typeFlags.testFlag(KBibTeX::TypeFlag::Source)) {
0335             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::Source), i18n("Source Code"));
0336             connect(action, &QAction::triggered, parent, [this]() {
0337                 typeChanged(KBibTeX::TypeFlag::Source);
0338             });
0339         }
0340         if (typeFlags.testFlag(KBibTeX::TypeFlag::Verbatim)) {
0341             QAction *action = menuTypes->addAction(iconForTypeFlag(KBibTeX::TypeFlag::Verbatim), i18n("Verbatim Text"));
0342             connect(action, &QAction::triggered, parent, [this]() {
0343                 typeChanged(KBibTeX::TypeFlag::Verbatim);
0344             });
0345         }
0346     }
0347 
0348     QIcon iconForTypeFlag(KBibTeX::TypeFlag typeFlag) {
0349         switch (typeFlag) {
0350         case KBibTeX::TypeFlag::Invalid: return QIcon();
0351         case KBibTeX::TypeFlag::PlainText: return QIcon::fromTheme(QStringLiteral("draw-text"));
0352         case KBibTeX::TypeFlag::Reference: return QIcon::fromTheme(QStringLiteral("emblem-symbolic-link"));
0353         case KBibTeX::TypeFlag::Person: return QIcon::fromTheme(QStringLiteral("user-identity"));
0354         case KBibTeX::TypeFlag::Keyword: return QIcon::fromTheme(QStringLiteral("edit-find"));
0355         case KBibTeX::TypeFlag::Source: return QIcon::fromTheme(QStringLiteral("code-context"));
0356         case KBibTeX::TypeFlag::Verbatim: return QIcon::fromTheme(QStringLiteral("preferences-desktop-keyboard"));
0357         }
0358         return QIcon(); //< should never happen as switch above covers all cases
0359     }
0360 
0361     void updateGUI(KBibTeX::TypeFlag typeFlag) {
0362         parent->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
0363         parent->setIcon(iconForTypeFlag(typeFlag));
0364         switch (typeFlag) {
0365         case KBibTeX::TypeFlag::Invalid: parent->setButtonToolTip(QString()); break;
0366         case KBibTeX::TypeFlag::PlainText: parent->setButtonToolTip(i18n("Plain Text")); break;
0367         case KBibTeX::TypeFlag::Reference: parent->setButtonToolTip(i18n("Reference")); break;
0368         case KBibTeX::TypeFlag::Person: parent->setButtonToolTip(i18n("Person")); break;
0369         case KBibTeX::TypeFlag::Keyword: parent->setButtonToolTip(i18n("Keyword")); break;
0370         case KBibTeX::TypeFlag::Source:
0371             parent->setButtonToolTip(i18n("Source Code"));
0372             parent->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0373             break;
0374         case KBibTeX::TypeFlag::Verbatim: parent->setButtonToolTip(i18n("Verbatim Text")); break;
0375         }
0376     }
0377 
0378     void openUrl() {
0379         if (urlToOpen.isValid()) {
0380             /// Guess mime type for url to open
0381             QMimeType mimeType = FileInfo::mimeTypeForUrl(urlToOpen);
0382             const QString mimeTypeName = mimeType.name();
0383             /// Ask KDE subsystem to open url in viewer matching mime type
0384 #if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
0385             KRun::runUrl(urlToOpen, mimeTypeName, parent, KRun::RunFlags());
0386 #else // KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) // >= 5.71.0
0387             KIO::OpenUrlJob *job = new KIO::OpenUrlJob(urlToOpen, mimeTypeName);
0388 #if KIO_VERSION < QT_VERSION_CHECK(5, 98, 0) // < 5.98.0
0389             job->setUiDelegate(new KIO::JobUiDelegate());
0390 #else // KIO_VERSION < QT_VERSION_CHECK(5, 98, 0) // >= 5.98.0
0391             job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parent));
0392 #endif // KIO_VERSION < QT_VERSION_CHECK(5, 98, 0)
0393             job->start();
0394 #endif // KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
0395         }
0396     }
0397 
0398     bool convertValueType(Value &value, KBibTeX::TypeFlag destType) {
0399         if (value.isEmpty()) return true; /// simple case
0400         if (destType == KBibTeX::TypeFlag::Source) return true; /// simple case
0401 
0402         bool result = true;
0403         const EncoderLaTeX &encoder = EncoderLaTeX::instance();
0404         QString rawText;
0405         const QSharedPointer<ValueItem> first = value.first();
0406 
0407         const QSharedPointer<PlainText> plainText = first.dynamicCast<PlainText>();
0408         if (!plainText.isNull())
0409             rawText = encoder.encode(plainText->text(), Encoder::TargetEncoding::ASCII);
0410         else {
0411             const QSharedPointer<VerbatimText> verbatimText = first.dynamicCast<VerbatimText>();
0412             if (!verbatimText.isNull())
0413                 rawText = verbatimText->text();
0414             else {
0415                 const QSharedPointer<MacroKey> macroKey = first.dynamicCast<MacroKey>();
0416                 if (!macroKey.isNull())
0417                     rawText = macroKey->text();
0418                 else {
0419                     const QSharedPointer<Person> person = first.dynamicCast<Person>();
0420                     if (!person.isNull())
0421                         rawText = encoder.encode(QString(QStringLiteral("%1 %2")).arg(person->firstName(), person->lastName()), Encoder::TargetEncoding::ASCII); // FIXME proper name conversion
0422                     else {
0423                         const QSharedPointer<Keyword> keyword = first.dynamicCast<Keyword>();
0424                         if (!keyword.isNull())
0425                             rawText = encoder.encode(keyword->text(), Encoder::TargetEncoding::ASCII);
0426                         else {
0427                             // TODO case missed?
0428                             result = false;
0429                         }
0430                     }
0431                 }
0432             }
0433         }
0434 
0435         switch (destType) {
0436         case KBibTeX::TypeFlag::PlainText:
0437             value.clear();
0438             value.append(QSharedPointer<PlainText>(new PlainText(encoder.decode(rawText))));
0439             break;
0440         case KBibTeX::TypeFlag::Verbatim:
0441             value.clear();
0442             value.append(QSharedPointer<VerbatimText>(new VerbatimText(rawText)));
0443             break;
0444         case KBibTeX::TypeFlag::Person:
0445             value.clear();
0446             value.append(QSharedPointer<Person>(FileImporterBibTeX::splitName(encoder.decode(rawText))));
0447             break;
0448         case KBibTeX::TypeFlag::Reference: {
0449             MacroKey *macroKey = new MacroKey(rawText);
0450             if (macroKey->isValid()) {
0451                 value.clear();
0452                 value.append(QSharedPointer<MacroKey>(macroKey));
0453             } else {
0454                 delete macroKey;
0455                 result = false;
0456             }
0457         }
0458         break;
0459         case KBibTeX::TypeFlag::Keyword:
0460             value.clear();
0461             value.append(QSharedPointer<Keyword>(new Keyword(encoder.decode(rawText))));
0462             break;
0463         default: {
0464             // TODO
0465             result = false;
0466         }
0467         }
0468 
0469         return result;
0470     }
0471 
0472     void updateURL(const QString &text) {
0473         QSet<QUrl> urls;
0474         FileInfo::urlsInText(text, FileInfo::TestExistence::Yes, file != nullptr && file->property(File::Url).toUrl().isValid() ? QUrl(file->property(File::Url).toUrl()).path() : QString(), urls);
0475         QSet<QUrl>::ConstIterator urlsIt = urls.constBegin();
0476         if (urlsIt != urls.constEnd() && (*urlsIt).isValid())
0477             urlToOpen = (*urlsIt);
0478         else
0479             urlToOpen = QUrl();
0480 
0481         /// set special "open URL" button visible if URL (or file or DOI) found
0482         buttonOpenUrl->setVisible(urlToOpen.isValid());
0483         buttonOpenUrl->setToolTip(i18n("Open '%1'", urlToOpen.url(QUrl::PreferLocalFile)));
0484     }
0485 
0486     void textChanged(const QString &text) {
0487         updateURL(text);
0488     }
0489 
0490     void typeChanged(const KBibTeX::TypeFlag newTypeFlag)
0491     {
0492         Value value;
0493         apply(value);
0494 
0495         if (convertValueType(value, newTypeFlag)) {
0496             reset(value, newTypeFlag);
0497 #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
0498             QMetaObject::invokeMethod(parent, "modified", Qt::DirectConnection, QGenericReturnArgument());
0499 #else // QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
0500             QMetaObject::invokeMethod(parent, "modified", Qt::DirectConnection, QMetaMethodReturnArgument());
0501 #endif
0502         } else
0503             KMessageBox::error(parent, i18n("The current text cannot be used as value of type '%1'.\n\nSwitching back to type '%2'.", BibTeXFields::typeFlagToString(newTypeFlag), BibTeXFields::typeFlagToString(typeFlag)));
0504     }
0505 };
0506 
0507 FieldLineEdit::FieldLineEdit(KBibTeX::TypeFlag preferredTypeFlag, KBibTeX::TypeFlags typeFlags, bool isMultiLine, QWidget *parent)
0508         : MenuLineEdit(isMultiLine, parent), d(new FieldLineEdit::FieldLineEditPrivate(preferredTypeFlag, typeFlags, this))
0509 {
0510     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0511     setObjectName(QStringLiteral("FieldLineEdit"));
0512     setMenu(d->menuTypes);
0513     setChildAcceptDrops(false);
0514     setAcceptDrops(true);
0515 }
0516 
0517 FieldLineEdit::~FieldLineEdit()
0518 {
0519     delete d;
0520 }
0521 
0522 bool FieldLineEdit::apply(Value &value) const
0523 {
0524     return d->apply(value);
0525 }
0526 
0527 bool FieldLineEdit::reset(const Value &value)
0528 {
0529     return d->reset(value, d->preferredTypeFlag);
0530 }
0531 
0532 bool FieldLineEdit::validate(QWidget **widgetWithIssue, QString &message) const
0533 {
0534     return d->validate(widgetWithIssue, message);
0535 }
0536 
0537 void FieldLineEdit::clear()
0538 {
0539     MenuLineEdit::clear();
0540     d->clear();
0541 }
0542 
0543 void FieldLineEdit::setReadOnly(bool isReadOnly)
0544 {
0545     MenuLineEdit::setReadOnly(isReadOnly);
0546 }
0547 
0548 void FieldLineEdit::setFile(const File *file)
0549 {
0550     d->file = file;
0551 }
0552 
0553 void FieldLineEdit::setElement(const Element *element)
0554 {
0555     Q_UNUSED(element)
0556 }
0557 
0558 void FieldLineEdit::setFieldKey(const QString &fieldKey)
0559 {
0560     d->fieldKey = fieldKey;
0561 }
0562 
0563 void FieldLineEdit::dragEnterEvent(QDragEnterEvent *event)
0564 {
0565     if (event->mimeData()->hasFormat(QStringLiteral("text/plain")) || event->mimeData()->hasFormat(QStringLiteral("text/x-bibtex")))
0566         event->acceptProposedAction();
0567 }
0568 
0569 void FieldLineEdit::dropEvent(QDropEvent *event)
0570 {
0571     const QString clipboardText = QString::fromUtf8(event->mimeData()->data(QStringLiteral("text/plain")));
0572     event->acceptProposedAction();
0573     if (clipboardText.isEmpty()) return;
0574 
0575     bool success = false;
0576     if (!d->fieldKey.isEmpty() && clipboardText.startsWith(QStringLiteral("@"))) {
0577         FileImporterBibTeX importer(this);
0578         QScopedPointer<File> file(importer.fromString(clipboardText));
0579         const QSharedPointer<Entry> entry = (!file.isNull() && file->count() == 1) ? file->first().dynamicCast<Entry>() : QSharedPointer<Entry>();
0580 
0581         if (!entry.isNull() && d->fieldKey == Entry::ftCrossRef) {
0582             /// handle drop on crossref line differently (use dropped entry's id)
0583             Value v;
0584             v.append(QSharedPointer<VerbatimText>(new VerbatimText(entry->id())));
0585             reset(v);
0586             Q_EMIT textChanged(entry->id());
0587             success = true;
0588         } else if (!entry.isNull() && entry->contains(d->fieldKey)) {
0589             /// case for "normal" fields like for journal, pages, ...
0590             reset(entry->value(d->fieldKey));
0591             Q_EMIT textChanged(text());
0592             success = true;
0593         }
0594     }
0595 
0596     if (!success) {
0597         /// In case above cases were not met and thus 'success' is still false,
0598         /// clear this line edit and use the clipboad text as its content
0599         setText(clipboardText);
0600         Q_EMIT textChanged(clipboardText);
0601     }
0602 }