File indexing completed on 2024-04-28 04:52:14
0001 /* 0002 SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "noteswidget.h" 0008 #include "bin/bin.h" 0009 #include "bin/projectclip.h" 0010 #include "bin/projectitemmodel.h" 0011 #include "core.h" 0012 #include "kdenlive_debug.h" 0013 0014 #include <KLocalizedString> 0015 #include <QMenu> 0016 #include <QMimeData> 0017 #include <QMouseEvent> 0018 #include <QToolTip> 0019 #include <QUuid> 0020 0021 NotesWidget::NotesWidget(QWidget *parent) 0022 : QTextEdit(parent) 0023 { 0024 setMouseTracking(true); 0025 } 0026 0027 NotesWidget::~NotesWidget() = default; 0028 0029 void NotesWidget::contextMenuEvent(QContextMenuEvent *event) 0030 { 0031 QMenu *menu = createStandardContextMenu(); 0032 if (menu) { 0033 QAction *a = new QAction(i18n("Insert current timecode"), menu); 0034 connect(a, &QAction::triggered, this, &NotesWidget::insertNotesTimecode); 0035 menu->insertAction(menu->actions().at(0), a); 0036 QPair<QStringList, QList<QPoint>> result = getSelectedAnchors(); 0037 QStringList anchors = result.first; 0038 QList<QPoint> anchorPoints = result.second; 0039 if (anchors.isEmpty()) { 0040 const QString anchor = anchorAt(event->pos()); 0041 if (!anchor.isEmpty()) { 0042 anchors << anchor; 0043 } 0044 } 0045 if (!anchors.isEmpty()) { 0046 a = new QAction(i18np("Create marker", "create markers", anchors.count()), menu); 0047 connect(a, &QAction::triggered, this, [this, anchors]() { createMarker(anchors); }); 0048 menu->insertAction(menu->actions().at(1), a); 0049 if (!anchorPoints.isEmpty()) { 0050 a = new QAction(i18n("Assign timestamps to current Bin Clip"), menu); 0051 connect(a, &QAction::triggered, this, [this, anchors, anchorPoints]() { Q_EMIT reAssign(anchors, anchorPoints); }); 0052 menu->insertAction(menu->actions().at(2), a); 0053 } 0054 } 0055 menu->exec(event->globalPos()); 0056 delete menu; 0057 } 0058 } 0059 0060 void NotesWidget::createMarker(const QStringList &anchors) 0061 { 0062 QMap<QString, QList<int>> clipMarkers; 0063 QList<int> guides; 0064 for (const QString &anchor : anchors) { 0065 if (anchor.contains(QLatin1Char('#'))) { 0066 // That's a Bin Clip reference. 0067 const QString binId = anchor.section(QLatin1Char('#'), 0, 0); 0068 QList<int> timecodes; 0069 if (clipMarkers.contains(binId)) { 0070 timecodes = clipMarkers.value(binId); 0071 timecodes << anchor.section(QLatin1Char('#'), 1).toInt(); 0072 } else { 0073 timecodes = {anchor.section(QLatin1Char('#'), 1).toInt()}; 0074 } 0075 clipMarkers.insert(binId, timecodes); 0076 } else { 0077 // That is a guide 0078 if (anchor.contains(QLatin1Char('?'))) { 0079 guides << anchor.section(QLatin1Char('?'), 0, 0).toInt(); 0080 } else { 0081 guides << anchor.toInt(); 0082 } 0083 } 0084 } 0085 QMapIterator<QString, QList<int>> i(clipMarkers); 0086 while (i.hasNext()) { 0087 i.next(); 0088 // That's a Bin Clip reference. 0089 pCore->bin()->addClipMarker(i.key(), i.value()); 0090 } 0091 if (!clipMarkers.isEmpty()) { 0092 const QString &binId = clipMarkers.firstKey(); 0093 pCore->selectBinClip(binId, true, clipMarkers.value(binId).constFirst(), QPoint()); 0094 } 0095 if (!guides.isEmpty()) { 0096 pCore->addGuides(guides); 0097 } 0098 } 0099 0100 void NotesWidget::mouseMoveEvent(QMouseEvent *e) 0101 { 0102 const QString anchor = anchorAt(e->pos()); 0103 if (anchor.isEmpty()) { 0104 viewport()->setCursor(Qt::IBeamCursor); 0105 } else { 0106 viewport()->setCursor(Qt::PointingHandCursor); 0107 } 0108 QTextEdit::mouseMoveEvent(e); 0109 } 0110 0111 void NotesWidget::mousePressEvent(QMouseEvent *e) 0112 { 0113 QString anchor = anchorAt(e->pos()); 0114 if (anchor.isEmpty() || e->button() != Qt::LeftButton) { 0115 QTextEdit::mousePressEvent(e); 0116 return; 0117 } 0118 if (anchor.contains(QLatin1Char('#'))) { 0119 // That's a Bin Clip reference. 0120 pCore->selectBinClip(anchor.section(QLatin1Char('#'), 0, 0), true, anchor.section(QLatin1Char('#'), 1).toInt(), QPoint()); 0121 } else { 0122 Q_EMIT seekProject(anchor); 0123 } 0124 e->setAccepted(true); 0125 } 0126 0127 QPair<QStringList, QList<QPoint>> NotesWidget::getSelectedAnchors() 0128 { 0129 int startPos = textCursor().selectionStart(); 0130 int endPos = textCursor().selectionEnd(); 0131 QStringList anchors; 0132 QList<QPoint> anchorPoints; 0133 if (endPos > startPos) { 0134 textCursor().clearSelection(); 0135 QTextCursor cur(textCursor()); 0136 // Ensure we are at the start of current selection 0137 if (!cur.atBlockStart()) { 0138 cur.setPosition(startPos, QTextCursor::MoveAnchor); 0139 int pos = startPos; 0140 const QString an = anchorAt(cursorRect(cur).center()); 0141 while (!cur.atBlockStart()) { 0142 pos--; 0143 cur.setPosition(pos, QTextCursor::MoveAnchor); 0144 if (anchorAt(cursorRect(cur).center()) == an) { 0145 startPos = pos; 0146 } else { 0147 break; 0148 } 0149 } 0150 } 0151 bool isInAnchor = false; 0152 QPoint anchorPoint; 0153 for (int p = startPos; p <= endPos; ++p) { 0154 cur.setPosition(p, QTextCursor::MoveAnchor); 0155 const QString anchor = anchorAt(cursorRect(cur).center()); 0156 if (isInAnchor && !anchor.isEmpty() && p == endPos) { 0157 endPos++; 0158 } 0159 if (isInAnchor && (anchor.isEmpty() || !anchors.contains(anchor) || cur.atEnd())) { 0160 // End of current anchor 0161 anchorPoint.setY(p); 0162 anchorPoints.prepend(anchorPoint); 0163 isInAnchor = false; 0164 } 0165 if (!anchor.isEmpty() && !anchors.contains(anchor)) { 0166 anchors.prepend(anchor); 0167 if (!isInAnchor) { 0168 isInAnchor = true; 0169 anchorPoint.setX(p); 0170 } 0171 } 0172 } 0173 } 0174 return {anchors, anchorPoints}; 0175 } 0176 0177 void NotesWidget::assignProjectNote() 0178 { 0179 QPair<QStringList, QList<QPoint>> result = getSelectedAnchors(); 0180 QStringList anchors = result.first; 0181 QList<QPoint> anchorPoints = result.second; 0182 if (!anchors.isEmpty()) { 0183 Q_EMIT reAssign(anchors, anchorPoints); 0184 } else { 0185 pCore->displayMessage(i18n("Select some timecodes to reassign"), ErrorMessage); 0186 } 0187 } 0188 0189 void NotesWidget::createMarkers() 0190 { 0191 QPair<QStringList, QList<QPoint>> result = getSelectedAnchors(); 0192 QStringList anchors = result.first; 0193 if (!anchors.isEmpty()) { 0194 createMarker(anchors); 0195 } else { 0196 pCore->displayMessage(i18n("Select some timecodes to create markers"), ErrorMessage); 0197 } 0198 } 0199 0200 void NotesWidget::addProjectNote() 0201 { 0202 if (!textCursor().atBlockStart()) { 0203 QTextCursor cur = textCursor(); 0204 cur.movePosition(QTextCursor::EndOfBlock); 0205 setTextCursor(cur); 0206 insertPlainText(QStringLiteral("\n")); 0207 } 0208 Q_EMIT insertNotesTimecode(); 0209 } 0210 0211 void NotesWidget::addTextNote(const QString &text) 0212 { 0213 if (!textCursor().atEnd()) { 0214 QTextCursor cur = textCursor(); 0215 cur.movePosition(QTextCursor::End); 0216 setTextCursor(cur); 0217 } 0218 Q_EMIT insertTextNote(text); 0219 } 0220 0221 void NotesWidget::insertFromMimeData(const QMimeData *source) 0222 { 0223 QString pastedText = source->text(); 0224 bool enforceHtml = false; 0225 // Check for timecodes 0226 QStringList words = pastedText.split(QLatin1Char(' ')); 0227 for (const QString &w : qAsConst(words)) { 0228 if (w.size() > 4 && w.size() < 13 && w.count(QLatin1Char(':')) > 1) { 0229 // This is probably a timecode 0230 int frames = pCore->timecode().getFrameCount(w); 0231 if (frames > 0) { 0232 pastedText.replace(w, QStringLiteral("<a href=\"") + QString::number(frames) + QStringLiteral("\">") + w + QStringLiteral("</a> ")); 0233 enforceHtml = true; 0234 } 0235 } 0236 } 0237 if (enforceHtml || Qt::mightBeRichText(pastedText)) { 0238 insertHtml(pastedText); 0239 } else { 0240 insertPlainText(pastedText); 0241 } 0242 } 0243 0244 bool NotesWidget::event(QEvent *event) 0245 { 0246 if (event->type() == QEvent::ToolTip) { 0247 QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); 0248 const QString anchor = anchorAt(helpEvent->pos()); 0249 if (!anchor.isEmpty()) { 0250 QString sequenceName; 0251 if (anchor.contains(QLatin1Char('!'))) { 0252 // We have a sequence reference 0253 const QString binId = pCore->projectItemModel()->getSequenceId(QUuid(anchor.section(QLatin1Char('!'), 0, 0))); 0254 if (!binId.isEmpty()) { 0255 std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(binId); 0256 if (clip) { 0257 sequenceName = clip->clipName(); 0258 } 0259 } 0260 } 0261 if (!sequenceName.isEmpty()) { 0262 QToolTip::showText(helpEvent->globalPos(), sequenceName); 0263 } else { 0264 QToolTip::hideText(); 0265 } 0266 } else { 0267 QToolTip::hideText(); 0268 } 0269 return true; 0270 } 0271 return QTextEdit::event(event); 0272 }