File indexing completed on 2024-11-24 04:39:35

0001 /*
0002   SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 */
0007 
0008 #include "textbrowser_p.h"
0009 #include <KCodecs>
0010 #include <KLocalizedString>
0011 #include <KStandardAction>
0012 #include <QAction>
0013 #include <QUrl>
0014 
0015 #include <QApplication>
0016 #include <QClipboard>
0017 #include <QContextMenuEvent>
0018 #include <QMenu>
0019 #include <QRegularExpression>
0020 #include <QTextBlock>
0021 
0022 using namespace Akonadi;
0023 
0024 TextBrowser::TextBrowser(QWidget *parent)
0025     : QTextBrowser(parent)
0026 {
0027     setOpenLinks(false);
0028 }
0029 
0030 void TextBrowser::slotCopyData()
0031 {
0032 #ifndef QT_NO_CLIPBOARD
0033     QClipboard *clip = QApplication::clipboard();
0034     // put the data into the mouse selection and the clipboard
0035     if (mDataToCopy.userType() == QMetaType::QPixmap) {
0036         clip->setPixmap(mDataToCopy.value<QPixmap>(), QClipboard::Clipboard);
0037         clip->setPixmap(mDataToCopy.value<QPixmap>(), QClipboard::Selection);
0038     } else {
0039         clip->setText(mDataToCopy.toString(), QClipboard::Clipboard);
0040         clip->setText(mDataToCopy.toString(), QClipboard::Selection);
0041     }
0042 #endif
0043 }
0044 
0045 #ifndef QT_NO_CONTEXTMENU
0046 void TextBrowser::contextMenuEvent(QContextMenuEvent *event)
0047 {
0048 #ifndef QT_NO_CLIPBOARD
0049     QMenu popup;
0050 
0051     QAction *act = KStandardAction::copy(this, &TextBrowser::copy, this);
0052     act->setEnabled(!textCursor().selectedText().isEmpty());
0053     act->setShortcut(QKeySequence());
0054     popup.addAction(act);
0055 
0056     // Create a new action to correspond with what is under the click
0057     act = new QAction(i18nc("@action:inmenu Copy the text of a general item", "Copy Item"), this);
0058 
0059     mDataToCopy.clear(); // nothing found to copy yet
0060 
0061     QString link = anchorAt(event->pos());
0062     if (!link.isEmpty()) {
0063         if (link.startsWith(QLatin1StringView("mailto:"))) {
0064             mDataToCopy = KCodecs::decodeRFC2047String(QUrl(link).path());
0065             // Action text matches that used in KMail
0066             act->setText(i18nc("@action:inmenu Copy a displayed email address", "Copy Email Address"));
0067         } else {
0068             // A link, but it could be one of our internal ones.  There is
0069             // no point in copying these.  Internal links are always in the
0070             // form "protocol:?argument", whereas valid external links should
0071             // be in the form starting with "protocol://".
0072             if (!link.contains(QRegularExpression(QStringLiteral("^\\w+:\\?")))) {
0073                 mDataToCopy = link;
0074                 // Action text matches that used in Konqueror
0075                 act->setText(i18nc("@action:inmenu Copy a link URL", "Copy Link URL"));
0076             }
0077         }
0078     }
0079 
0080     if (!mDataToCopy.isValid()) { // no link was found above
0081         QTextCursor curs = cursorForPosition(event->pos());
0082         QString text = curs.block().text(); // try the text under cursor
0083 
0084         if (!text.isEmpty()) {
0085             // curs().block().text() over an image (contact photo or QR code)
0086             // returns a string starting with the character 0xFFFC (Unicode
0087             // object replacement character).  See the documentation for
0088             // QTextImageFormat.
0089             if (text.startsWith(QChar(0xFFFC))) {
0090                 QTextCharFormat charFormat = curs.charFormat();
0091                 if (charFormat.isImageFormat()) {
0092                     const QTextImageFormat imageFormat = charFormat.toImageFormat();
0093                     const QString imageName = imageFormat.name();
0094                     const QVariant imageResource = document()->resource(QTextDocument::ImageResource, QUrl(imageName));
0095 
0096                     const auto pix = imageResource.value<QPixmap>();
0097                     if (!pix.isNull()) {
0098                         // There may be other images (e.g. contact type icons) that
0099                         // there is no point in copying.
0100                         if (imageName == QLatin1StringView("contact_photo")) {
0101                             mDataToCopy = pix;
0102                             act->setText(i18nc("@action:inmenu Copy a contact photo", "Copy Photo"));
0103                         } else if (imageName == QLatin1StringView("qrcode")) {
0104                             mDataToCopy = pix;
0105                             act->setText(i18nc("@action:inmenu Copy a QR code image", "Copy Code"));
0106                         }
0107                     }
0108                 }
0109             } else {
0110                 // Added by our formatter (but not I18N'ed) for a mobile
0111                 // telephone number.  See
0112                 // kdepim/kaddressbook/grantlee/grantleecontactformatter.cpp and
0113                 // kdepimlibs/akonadi/contact/standardcontactformatter.cpp
0114                 text.remove(QRegularExpression(QStringLiteral("\\s*\\(SMS\\)$")));
0115 
0116                 // For an item which was formatted with line breaks (as <br>
0117                 // in HTML), the returned text contains the character 0x2028
0118                 // (Unicode line separator).  Convert any of these back to newlines.
0119                 text.replace(QChar(0x2028), QLatin1Char('\n'));
0120 
0121                 mDataToCopy = text;
0122             }
0123         }
0124     }
0125 
0126     if (mDataToCopy.isValid()) {
0127         connect(act, &QAction::triggered, this, &TextBrowser::slotCopyData);
0128     } else {
0129         act->setEnabled(false);
0130     }
0131 
0132     popup.addAction(act);
0133     popup.exec(event->globalPos());
0134 #endif
0135 }
0136 
0137 #endif
0138 
0139 #include "moc_textbrowser_p.cpp"