noteswidget.cpp 7.85 KB
Newer Older
1
/*
Alexander Lohnau's avatar
Alexander Lohnau committed
2
    SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org>
3

Camille Moulin's avatar
Camille Moulin committed
4
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5
*/
6
7

#include "noteswidget.h"
8
#include "bin/bin.h"
9
10
#include "core.h"
#include "kdenlive_debug.h"
11

12
#include <QMenu>
13
#include <QMimeData>
14
#include <QMouseEvent>
Nicolas Carion's avatar
Nicolas Carion committed
15
#include <klocalizedstring.h>
16

17
18
NotesWidget::NotesWidget(QWidget *parent)
    : QTextEdit(parent)
19
20
21
22
{
    setMouseTracking(true);
}

Nicolas Carion's avatar
linting    
Nicolas Carion committed
23
NotesWidget::~NotesWidget() = default;
24

25
void NotesWidget::contextMenuEvent(QContextMenuEvent *event)
26
{
27
    QMenu *menu = createStandardContextMenu();
28
29
30
31
    if (menu) {
        QAction *a = new QAction(i18n("Insert current timecode"), menu);
        connect(a, &QAction::triggered, this, &NotesWidget::insertNotesTimecode);
        menu->insertAction(menu->actions().at(0), a);
32
33
34
35
36
37
38
39
40
41
42
        QPair <QStringList, QList <QPoint> > result = getSelectedAnchors();
        QStringList anchors = result.first;
        QList <QPoint> anchorPoints = result.second;
        if (anchors.isEmpty()) {
            const QString anchor = anchorAt(event->pos());
            if (!anchor.isEmpty()) {
                anchors << anchor;
            }
        }
        if (!anchors.isEmpty()) {
            a = new QAction(i18np("Create marker", "create markers", anchors.count()), menu);
Vincent Pinon's avatar
Vincent Pinon committed
43
            connect(a, &QAction::triggered, this, [this, anchors] () {
44
45
46
47
48
                createMarker(anchors);
            });
            menu->insertAction(menu->actions().at(1), a);
            if (!anchorPoints.isEmpty()) {
                a = new QAction(i18n("Assign timestamps to current Bin Clip"), menu);
Vincent Pinon's avatar
Vincent Pinon committed
49
                connect(a, &QAction::triggered, this, [this, anchors, anchorPoints] () {
50
51
52
53
54
55
56
57
58
59
                    emit reAssign(anchors, anchorPoints);
                });
                menu->insertAction(menu->actions().at(2), a);
            }
        }
        menu->exec(event->globalPos());
        delete menu;
    }
}

60
void NotesWidget::createMarker(const QStringList &anchors)
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
{
    QMap <QString, QList<int>> clipMarkers;
    QList<int> guides;
    for (const QString &anchor : anchors) {
        if (anchor.contains(QLatin1Char('#'))) {
            // That's a Bin Clip reference.
            const QString binId = anchor.section(QLatin1Char('#'), 0, 0);
            QList <int> timecodes;
            if (clipMarkers.contains(binId)) {
                timecodes = clipMarkers.value(binId);
                timecodes << anchor.section(QLatin1Char('#'), 1).toInt();
            } else {
                timecodes = {anchor.section(QLatin1Char('#'), 1).toInt()};
            }
            clipMarkers.insert(binId, timecodes);
        } else {
            // That is a guide
78
79
80
81
82
            if (anchor.contains(QLatin1Char('?'))) {
                guides << anchor.section(QLatin1Char('?'), 0, 0).toInt();
            } else {
                guides << anchor.toInt();
            }
83
84
85
86
87
88
89
90
91
92
        }
    }
    QMapIterator<QString, QList<int>> i(clipMarkers);
    while (i.hasNext()) {
        i.next();
        // That's a Bin Clip reference.
        pCore->bin()->addClipMarker(i.key(), i.value());
    }
    if (!clipMarkers.isEmpty()) {
        const QString &binId = clipMarkers.firstKey();
93
        pCore->selectBinClip(binId, true, clipMarkers.value(binId).constFirst(), QPoint());
94
95
96
    }
    if (!guides.isEmpty()) {
        pCore->addGuides(guides);
97
    }
98
99
}

Laurent Montel's avatar
Laurent Montel committed
100
void NotesWidget::mouseMoveEvent(QMouseEvent *e)
101
{
102
    const QString anchor = anchorAt(e->pos());
Laurent Montel's avatar
Laurent Montel committed
103
    if (anchor.isEmpty()) {
104
        viewport()->setCursor(Qt::IBeamCursor);
Laurent Montel's avatar
Laurent Montel committed
105
    } else {
106
        viewport()->setCursor(Qt::PointingHandCursor);
Laurent Montel's avatar
Laurent Montel committed
107
    }
108
    QTextEdit::mouseMoveEvent(e);
109
110
}

Laurent Montel's avatar
Laurent Montel committed
111
void NotesWidget::mousePressEvent(QMouseEvent *e)
112
113
{
    QString anchor = anchorAt(e->pos());
114
    if (anchor.isEmpty() || e->button() != Qt::LeftButton) {
115
        QTextEdit::mousePressEvent(e);
116
117
        return;
    }
118
119
    if (anchor.contains(QLatin1Char('#'))) {
        // That's a Bin Clip reference.
120
        pCore->selectBinClip(anchor.section(QLatin1Char('#'), 0, 0), true, anchor.section(QLatin1Char('#'), 1).toInt(), QPoint());
121
    } else {
122
        emit seekProject(anchor);
123
    }
124
125
    e->setAccepted(true);
}
126

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
QPair <QStringList, QList <QPoint> > NotesWidget::getSelectedAnchors()
{
    int startPos = textCursor().selectionStart();
    int endPos = textCursor().selectionEnd();
    QStringList anchors;
    QList <QPoint> anchorPoints;
    if (endPos > startPos) {
        textCursor().clearSelection();
        QTextCursor cur(textCursor());
        // Ensure we are at the start of current selection
        if (!cur.atBlockStart()) {
            cur.setPosition(startPos, QTextCursor::MoveAnchor);
            int pos = startPos;
            const QString an = anchorAt(cursorRect(cur).center());
            while (!cur.atBlockStart()) {
                pos--;
                cur.setPosition(pos, QTextCursor::MoveAnchor);
                if (anchorAt(cursorRect(cur).center()) == an) {
                    startPos = pos;
                } else {
                    break;
                }
            }
        }
        bool isInAnchor = false;
        QPoint anchorPoint;
        for (int p = startPos; p <= endPos; ++p) {
            cur.setPosition(p, QTextCursor::MoveAnchor);
            const QString anchor = anchorAt(cursorRect(cur).center());
            if (isInAnchor && !anchor.isEmpty() && p == endPos) {
                endPos++;
            }
            if (isInAnchor && (anchor.isEmpty() || !anchors.contains(anchor) || cur.atEnd())) {
                // End of current anchor
                anchorPoint.setY(p);
                anchorPoints.prepend(anchorPoint);
                isInAnchor = false;
            }
            if (!anchor.isEmpty() && !anchors.contains(anchor)) {
                anchors.prepend(anchor);
                if (!isInAnchor) {
                    isInAnchor = true;
                    anchorPoint.setX(p);
                }
            }
        }
    }
    return {anchors, anchorPoints};
}

void NotesWidget::assignProjectNote()
{
    QPair <QStringList, QList <QPoint> > result = getSelectedAnchors();
    QStringList anchors = result.first;
    QList <QPoint> anchorPoints = result.second;
    if (!anchors.isEmpty()) {
        emit reAssign(anchors, anchorPoints);
    } else {
185
        pCore->displayMessage(i18n("Select some timecodes to reassign"), ErrorMessage);
186
187
188
189
190
191
192
193
194
195
    }
}

void NotesWidget::createMarkers()
{
    QPair <QStringList, QList <QPoint> > result = getSelectedAnchors();
    QStringList anchors = result.first;
    if (!anchors.isEmpty()) {
        createMarker(anchors);
    } else {
196
        pCore->displayMessage(i18n("Select some timecodes to create markers"), ErrorMessage);
197
198
199
    }
}

200
201
202
203
204
205
206
207
void NotesWidget::addProjectNote()
{
    if (!textCursor().atBlockStart()) {
        textCursor().movePosition(QTextCursor::EndOfBlock);
        insertPlainText(QStringLiteral("\n"));
    }
    emit insertNotesTimecode();
}
208

209
210
211
212
213
214
215
216
217
void NotesWidget::addTextNote(const QString &text)
{
    if (!textCursor().atBlockStart()) {
        textCursor().movePosition(QTextCursor::EndOfBlock);
        insertPlainText(QStringLiteral("\n"));
    }
    emit insertTextNote(text);
}

218
219
220
void NotesWidget::insertFromMimeData(const QMimeData *source)
{
    QString pastedText = source->text();
221
    bool enforceHtml = false;
222
223
    // Check for timecodes
    QStringList words = pastedText.split(QLatin1Char(' '));
Vincent Pinon's avatar
Vincent Pinon committed
224
    for (const QString &w : qAsConst(words)) {
225
226
227
228
229
        if (w.size() > 4 && w.size() < 13 && w.count(QLatin1Char(':')) > 1) {
            // This is probably a timecode
            int frames = pCore->timecode().getFrameCount(w);
            if (frames > 0) {
                pastedText.replace(w, QStringLiteral("<a href=\"") + QString::number(frames) + QStringLiteral("\">") + w + QStringLiteral("</a> "));
230
                enforceHtml = true;
231
232
233
            }
        }
    }
234
235
236
237
238
    if (enforceHtml || Qt::mightBeRichText(pastedText)) {
        insertHtml(pastedText);
    } else {
        insertPlainText(pastedText);
    }
239
}