subtitleedit.cpp 11.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/***************************************************************************
 *   Copyright (C) 2020 by Jean-Baptiste Mardelle                          *
 *   This file is part of Kdenlive. See www.kdenlive.org.                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) version 3 or any later version accepted by the       *
 *   membership of KDE e.V. (or its successor approved  by the membership  *
 *   of KDE e.V.), which shall act as a proxy defined in Section 14 of     *
 *   version 3 of the license.                                             *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "subtitleedit.h"
#include "bin/model/subtitlemodel.hpp"
#include "monitor/monitor.h"

#include "core.h"
#include "kdenlivesettings.h"
28
#include "timecodedisplay.h"
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

#include "klocalizedstring.h"
#include "QTextEdit"

#include <QEvent>
#include <QKeyEvent>
#include <QToolButton>

ShiftEnterFilter::ShiftEnterFilter(QObject *parent)
    : QObject(parent)
{}

bool ShiftEnterFilter::eventFilter(QObject *obj, QEvent *event)
{
    if(event->type() == QEvent::KeyPress)
    {
45
        auto *keyEvent = static_cast <QKeyEvent*> (event);
46 47 48 49 50
        if((keyEvent->modifiers() & Qt::ShiftModifier) && ((keyEvent->key() == Qt::Key_Enter) || (keyEvent->key() == Qt::Key_Return))) {
            emit triggerUpdate();
            return true;
        }
    }
51 52 53 54 55
    if (event->type() == QEvent::FocusOut)
    {
        emit triggerUpdate();
        return true;
    }
56 57 58 59 60 61 62 63 64 65 66
    return QObject::eventFilter(obj, event);
}


SubtitleEdit::SubtitleEdit(QWidget *parent)
    : QWidget(parent)
    , m_model(nullptr)
{
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
    setupUi(this);
    buttonApply->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")));
67
    buttonAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
68
    buttonCut->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut")));
69 70 71
    buttonIn->setIcon(QIcon::fromTheme(QStringLiteral("zone-in")));
    buttonOut->setIcon(QIcon::fromTheme(QStringLiteral("zone-out")));
    buttonDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
72
    buttonLock->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-lock")));
73 74 75
    auto *keyFilter = new ShiftEnterFilter(this);
    subText->installEventFilter(keyFilter);
    connect(keyFilter, &ShiftEnterFilter::triggerUpdate, this, &SubtitleEdit::updateSubtitle);
76
    connect(subText, &KTextEdit::textChanged, [this]() {
77 78 79
        if (m_activeSub > -1) {
            buttonApply->setEnabled(true);
        }
80
    });
81
    connect(subText, &KTextEdit::cursorPositionChanged, [this]() {
82 83 84
        if (m_activeSub > -1) {
            buttonCut->setEnabled(true);
        }
85
    });
86 87 88 89
    
    m_position = new TimecodeDisplay(pCore->timecode(), this);
    m_endPosition = new TimecodeDisplay(pCore->timecode(), this);
    m_duration = new TimecodeDisplay(pCore->timecode(), this);
90 91
    frame_position->setEnabled(false);
    buttonDelete->setEnabled(false);
92 93

    position_box->addWidget(m_position);
94 95 96
    auto *spacer = new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
    position_box->addSpacerItem(spacer);
    spacer = new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
97
    end_box->addWidget(m_endPosition);
98
    end_box->addSpacerItem(spacer);
99
    duration_box->addWidget(m_duration);
100 101
    spacer = new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
    duration_box->addSpacerItem(spacer);
102
    connect(m_position, &TimecodeDisplay::timeCodeEditingFinished, [this] (int value) {
103
        updateSubtitle();
104 105 106 107 108 109 110
        if (buttonLock->isChecked()) {
            // Perform a move instead of a resize
            m_model->requestSubtitleMove(m_activeSub, GenTime(value, pCore->getCurrentFps()));
        } else {
            GenTime duration = m_endPos - GenTime(value, pCore->getCurrentFps());
            m_model->requestResize(m_activeSub, duration.frames(pCore->getCurrentFps()), false);
        }
111 112
    });
    connect(m_endPosition, &TimecodeDisplay::timeCodeEditingFinished, [this] (int value) {
113
        updateSubtitle();
114 115 116 117 118 119 120
        if (buttonLock->isChecked()) {
            // Perform a move instead of a resize
            m_model->requestSubtitleMove(m_activeSub, GenTime(value, pCore->getCurrentFps()) - (m_endPos - m_startPos));
        } else {
            GenTime duration = GenTime(value, pCore->getCurrentFps()) - m_startPos;
            m_model->requestResize(m_activeSub, duration.frames(pCore->getCurrentFps()), true);
        }
121 122
    });
    connect(m_duration, &TimecodeDisplay::timeCodeEditingFinished, [this] (int value) {
123
        updateSubtitle();
124 125
        m_model->requestResize(m_activeSub, value, true);
    });
126 127 128 129
    connect(buttonAdd, &QToolButton::clicked, this, [this]() {
        emit addSubtitle(subText->toPlainText());
    });
    connect(buttonCut, &QToolButton::clicked, this, [this]() {
130 131
        if (m_activeSub > -1 && subText->hasFocus()) {
            int pos = subText->textCursor().position();
132
            updateSubtitle();
133 134 135
            emit cutSubtitle(m_activeSub, pos);
        }
    });
136 137 138
    connect(buttonApply, &QToolButton::clicked, this, &SubtitleEdit::updateSubtitle);
    connect(buttonPrev, &QToolButton::clicked, this, &SubtitleEdit::goToPrevious);
    connect(buttonNext, &QToolButton::clicked, this, &SubtitleEdit::goToNext);
Vincent Pinon's avatar
Vincent Pinon committed
139
    connect(buttonIn, &QToolButton::clicked, []() {
140 141
        pCore->triggerAction(QStringLiteral("resize_timeline_clip_start"));
    });
Vincent Pinon's avatar
Vincent Pinon committed
142
    connect(buttonOut, &QToolButton::clicked, []() {
143 144
        pCore->triggerAction(QStringLiteral("resize_timeline_clip_end"));
    });
Vincent Pinon's avatar
Vincent Pinon committed
145
    connect(buttonDelete, &QToolButton::clicked, []() {
146 147
        pCore->triggerAction(QStringLiteral("delete_timeline_clip"));
    });
148 149 150 151 152 153
    buttonNext->setToolTip(i18n("Go to next subtitle"));
    buttonPrev->setToolTip(i18n("Go to previous subtitle"));
    buttonAdd->setToolTip(i18n("Add subtitle"));
    buttonCut->setToolTip(i18n("Split subtitle at cursor position"));
    buttonApply->setToolTip(i18n("Update subtitle text"));
    buttonDelete->setToolTip(i18n("Delete subtitle"));
154 155 156 157 158 159 160
}

void SubtitleEdit::setModel(std::shared_ptr<SubtitleModel> model)
{
    m_model = model;
    m_activeSub = -1;
    buttonApply->setEnabled(false);
161
    buttonCut->setEnabled(false);
162 163 164 165 166
    if (m_model == nullptr) {
        QSignalBlocker bk(subText);
        subText->clear();
    } else {
        connect(m_model.get(), &SubtitleModel::dataChanged, [this](const QModelIndex &start, const QModelIndex &, const QVector <int>&roles) {
167 168 169 170
            if (m_activeSub > -1 && start.row() == m_model->getRowForId(m_activeSub)) {
                if (roles.contains(SubtitleModel::SubtitleRole) || roles.contains(SubtitleModel::StartFrameRole) || roles.contains(SubtitleModel::EndFrameRole)) {
                    setActiveSubtitle(m_activeSub);
                }
171 172 173
            }
        });
    }
174 175 176 177
}

void SubtitleEdit::updateSubtitle()
{
178 179 180 181
    if (!buttonApply->isEnabled()) {
        return;
    }
    buttonApply->setEnabled(false);
182
    if (m_activeSub > -1 && m_model) {
183 184 185
        QString txt = subText->toPlainText().trimmed();
        txt.replace(QLatin1String("\n\n"), QStringLiteral("\n"));
        m_model->setText(m_activeSub, txt);
186 187 188 189 190 191
    }
}

void SubtitleEdit::setActiveSubtitle(int id)
{
    m_activeSub = id;
192 193
    buttonApply->setEnabled(false);
    buttonCut->setEnabled(false);
194 195 196
    if (m_model && id > -1) {
        subText->setEnabled(true);
        QSignalBlocker bk(subText);
197 198
        frame_position->setEnabled(true);
        buttonDelete->setEnabled(true);
199
        QSignalBlocker bk2(m_position);
200
        QSignalBlocker bk3(m_endPosition);
201
        QSignalBlocker bk4(m_duration);
202
        subText->setPlainText(m_model->getText(id));
203 204 205 206 207 208
        m_startPos = m_model->getStartPosForId(id);
        GenTime duration = GenTime(m_model->getSubtitlePlaytime(id), pCore->getCurrentFps());
        m_endPos = m_startPos + duration;
        m_position->setValue(m_startPos);
        m_endPosition->setValue(m_endPos);
        m_duration->setValue(duration);
209 210 211
        m_position->setEnabled(true);
        m_endPosition->setEnabled(true);
        m_duration->setEnabled(true);
212
    } else {
213 214 215
        m_position->setEnabled(false);
        m_endPosition->setEnabled(false);
        m_duration->setEnabled(false);
216 217
        frame_position->setEnabled(false);
        buttonDelete->setEnabled(false);
218 219 220 221 222 223 224 225
        QSignalBlocker bk(subText);
        subText->clear();
    }
}

void SubtitleEdit::goToPrevious()
{
    if (m_model) {
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
        int id = -1;
        if (m_activeSub > -1) {
            id = m_model->getPreviousSub(m_activeSub);
        } else {
            // Start from timeline cursor position
            int cursorPos = pCore->getTimelinePosition();
            std::unordered_set<int> sids = m_model->getItemsInRange(cursorPos, cursorPos);
            if (sids.empty()) {
                sids = m_model->getItemsInRange(0, cursorPos);
                for (int s : sids) {
                    if (id == -1 || m_model->getSubtitleEnd(s) > m_model->getSubtitleEnd(id)) {
                        id = s;
                    }
                }
            } else {
                id = m_model->getPreviousSub(*sids.begin());
            }
        }
244 245 246 247 248 249
        if (id > -1) {
            if (buttonApply->isEnabled()) {
                updateSubtitle();
            }
            GenTime prev = m_model->getStartPosForId(id);
            pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(prev.frames(pCore->getCurrentFps()));
250
            pCore->selectTimelineItem(id);
251 252 253 254 255 256 257
        }
    }
}

void SubtitleEdit::goToNext()
{
    if (m_model) {
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
        int id = -1;
        if (m_activeSub > -1) {
             id = m_model->getNextSub(m_activeSub);
        } else {
            // Start from timeline cursor position
            int cursorPos = pCore->getTimelinePosition();
            std::unordered_set<int> sids = m_model->getItemsInRange(cursorPos, cursorPos);
            if (sids.empty()) {
                sids = m_model->getItemsInRange(cursorPos, -1);
                for (int s : sids) {
                    if (id == -1 || m_model->getStartPosForId(s) < m_model->getStartPosForId(id)) {
                        id = s;
                    }
                }
            } else {
                id = m_model->getNextSub(*sids.begin());
            }
        }
276 277 278 279 280 281
        if (id > -1) {
            if (buttonApply->isEnabled()) {
                updateSubtitle();
            }
            GenTime prev = m_model->getStartPosForId(id);
            pCore->getMonitor(Kdenlive::ProjectMonitor)->requestSeek(prev.frames(pCore->getCurrentFps()));
282
            pCore->selectTimelineItem(id);
283 284 285
        }
    }
}