textbrowser.cpp 5.27 KB
Newer Older
1
/*
2
  SPDX-FileCopyrightText: 2012-2020 Laurent Montel <montel@kde.org>
3

4
  SPDX-License-Identifier: LGPL-2.0-or-later
5
6
7
8

*/

#include "textbrowser_p.h"
Laurent Montel's avatar
Laurent Montel committed
9
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
10
#include <QUrl>
Laurent Montel's avatar
Laurent Montel committed
11
#include <QAction>
12
#include <KStandardAction>
13
#include <KCodecs>
14
15
16
17
18

#include <QApplication>
#include <QContextMenuEvent>
#include <QMenu>
#include <QClipboard>
19
#include <QRegularExpression>
20
#include <QTextBlock>
21
22
23

using namespace Akonadi;

Guy Maurel's avatar
Guy Maurel committed
24
25
TextBrowser::TextBrowser(QWidget *parent)
    : QTextBrowser(parent)
26
{
Laurent Montel's avatar
Laurent Montel committed
27
    setOpenLinks(false);
28
29
}

30
void TextBrowser::slotCopyData()
31
32
{
#ifndef QT_NO_CLIPBOARD
Guy Maurel's avatar
Guy Maurel committed
33
34
35
36
37
38
39
40
41
    QClipboard *clip = QApplication::clipboard();
    // put the data into the mouse selection and the clipboard
    if (mDataToCopy.type() == QVariant::Pixmap) {
        clip->setPixmap(mDataToCopy.value<QPixmap>(), QClipboard::Clipboard);
        clip->setPixmap(mDataToCopy.value<QPixmap>(), QClipboard::Selection);
    } else {
        clip->setText(mDataToCopy.toString(), QClipboard::Clipboard);
        clip->setText(mDataToCopy.toString(), QClipboard::Selection);
    }
42
43
44
45
#endif
}

#ifndef QT_NO_CONTEXTMENU
Guy Maurel's avatar
Guy Maurel committed
46
void TextBrowser::contextMenuEvent(QContextMenuEvent *event)
47
48
{
#ifndef QT_NO_CLIPBOARD
Guy Maurel's avatar
Guy Maurel committed
49
50
    QMenu popup;

Laurent Montel's avatar
Laurent Montel committed
51
    QAction *act = KStandardAction::copy(this, &TextBrowser::copy, this);
Guy Maurel's avatar
Guy Maurel committed
52
53
54
55
56
57
58
59
60
61
62
    act->setEnabled(!textCursor().selectedText().isEmpty());
    act->setShortcut(QKeySequence());
    popup.addAction(act);

    // Create a new action to correspond with what is under the click
    act = new QAction(i18nc("@action:inmenu Copy the text of a general item", "Copy Item"), this);

    mDataToCopy.clear();                                       // nothing found to copy yet

    QString link = anchorAt(event->pos());
    if (!link.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
63
        if (link.startsWith(QLatin1String("mailto:"))) {
64
            mDataToCopy = KCodecs::decodeRFC2047String(QUrl(link).path());
Guy Maurel's avatar
Guy Maurel committed
65
66
67
68
69
70
71
            // Action text matches that used in KMail
            act->setText(i18nc("@action:inmenu Copy a displayed email address", "Copy Email Address"));
        } else {
            // A link, but it could be one of our internal ones.  There is
            // no point in copying these.  Internal links are always in the
            // form "protocol:?argument", whereas valid external links should
            // be in the form starting with "protocol://".
72
            if (!link.contains(QRegularExpression(QStringLiteral("^\\w+:\\?")))) {
Guy Maurel's avatar
Guy Maurel committed
73
74
75
                mDataToCopy = link;
                // Action text matches that used in Konqueror
                act->setText(i18nc("@action:inmenu Copy a link URL", "Copy Link URL"));
76
77
            }
        }
Guy Maurel's avatar
Guy Maurel committed
78
    }
79

Guy Maurel's avatar
Guy Maurel committed
80
81
82
83
84
85
86
87
88
89
90
91
    if (!mDataToCopy.isValid()) {               // no link was found above
        QTextCursor curs = cursorForPosition(event->pos());
        QString text = curs.block().text();     // try the text under cursor

        if (!text.isEmpty()) {
            // curs().block().text() over an image (contact photo or QR code)
            // returns a string starting with the character 0xFFFC (Unicode
            // object replacement character).  See the documentation for
            // QTextImageFormat.
            if (text.startsWith(QChar(0xFFFC))) {
                QTextCharFormat charFormat = curs.charFormat();
                if (charFormat.isImageFormat()) {
Laurent Montel's avatar
Laurent Montel committed
92
93
94
                    const QTextImageFormat imageFormat = charFormat.toImageFormat();
                    const QString imageName = imageFormat.name();
                    const QVariant imageResource = document()->resource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
95
                                                                  QUrl(imageName));
Guy Maurel's avatar
Guy Maurel committed
96

Laurent Montel's avatar
Laurent Montel committed
97
                    const QPixmap pix = imageResource.value<QPixmap>();
Guy Maurel's avatar
Guy Maurel committed
98
99
100
101
102
103
                    if (!pix.isNull()) {
                        // There may be other images (e.g. contact type icons) that
                        // there is no point in copying.
                        if (imageName == QLatin1String("contact_photo")) {
                            mDataToCopy = pix;
                            act->setText(i18nc("@action:inmenu Copy a contact photo", "Copy Photo"));
104
                        } else if (imageName == QLatin1String("qrcode")) {
Guy Maurel's avatar
Guy Maurel committed
105
                            mDataToCopy = pix;
106
                            act->setText(i18nc("@action:inmenu Copy a QR code image", "Copy Code"));
Guy Maurel's avatar
Guy Maurel committed
107
108
109
110
111
112
113
114
                        }
                    }
                }
            } else {
                // Added by our formatter (but not I18N'ed) for a mobile
                // telephone number.  See
                // kdepim/kaddressbook/grantlee/grantleecontactformatter.cpp and
                // kdepimlibs/akonadi/contact/standardcontactformatter.cpp
115
                text.remove(QRegularExpression(QStringLiteral("\\s*\\(SMS\\)$")));
Guy Maurel's avatar
Guy Maurel committed
116
117
118
119
120
121
122
123
124

                // For an item which was formatted with line breaks (as <br>
                // in HTML), the returned text contains the character 0x2028
                // (Unicode line separator).  Convert any of these back to newlines.
                text.replace(QChar(0x2028), QLatin1Char('\n'));

                mDataToCopy = text;
            }
        }
125
126
    }

Guy Maurel's avatar
Guy Maurel committed
127
128
129
130
131
    if (mDataToCopy.isValid()) {
        connect(act, &QAction::triggered, this, &TextBrowser::slotCopyData);
    } else {
        act->setEnabled(false);
    }
132

Guy Maurel's avatar
Guy Maurel committed
133
134
    popup.addAction(act);
    popup.exec(event->globalPos());
135
#endif
136
}
Laurent Montel's avatar
Laurent Montel committed
137

138
#endif