File indexing completed on 2025-02-23 04:35:41

0001 /*
0002     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "waveformwidget.h"
0008 
0009 #include "appglobal.h"
0010 #include "application.h"
0011 #include "scconfig.h"
0012 #include "core/subtitleline.h"
0013 #include "videoplayer/videoplayer.h"
0014 #include "actions/useraction.h"
0015 #include "actions/useractionnames.h"
0016 #include "gui/treeview/lineswidget.h"
0017 #include "gui/waveform/wavebuffer.h"
0018 #include "gui/waveform/waverenderer.h"
0019 #include "gui/waveform/zoombuffer.h"
0020 
0021 #include <QRect>
0022 #include <QPainter>
0023 #include <QPaintEvent>
0024 #include <QPolygon>
0025 #include <QRegion>
0026 #include <QThread>
0027 
0028 #include <QProgressBar>
0029 #include <QLabel>
0030 #include <QMenu>
0031 #include <QBoxLayout>
0032 #include <QToolButton>
0033 #include <QScrollBar>
0034 #include <QPropertyAnimation>
0035 #include <QDebug>
0036 
0037 #include <KLocalizedString>
0038 
0039 using namespace SubtitleComposer;
0040 
0041 #define ZOOM_MIN (1 << 3)
0042 
0043 WaveformWidget::WaveformWidget(QWidget *parent)
0044     : QWidget(parent),
0045       m_mediaFile(QString()),
0046       m_streamIndex(-1),
0047       m_subtitle(nullptr),
0048       m_timeStart(0.),
0049       m_timeCurrent(0.),
0050       m_timeEnd(WaveBuffer::MAX_WINDOW_ZOOM()),
0051       m_zoom(1 << 6),
0052       m_RMBDown(false),
0053       m_MMBDown(false),
0054       m_scrollBar(nullptr),
0055       m_scrollAnimation(nullptr),
0056       m_autoScroll(true),
0057       m_autoScrollPause(false),
0058       m_hoverScrollAmount(.0),
0059       m_waveformGraphics(new WaveRenderer(this)),
0060       m_progressWidget(new QWidget(this)),
0061       m_visibleLinesDirty(true),
0062       m_draggedLine(nullptr),
0063       m_widgetLayout(nullptr),
0064       m_translationMode(false),
0065       m_showTranslation(false),
0066       m_wfBuffer(new WaveBuffer(this)),
0067       m_zoomData(nullptr),
0068       m_zoomDataLen(0)
0069 {
0070     m_widgetLayout = new QBoxLayout(QBoxLayout::LeftToRight);
0071     m_widgetLayout->setContentsMargins(0, 0, 0, 0);
0072     m_widgetLayout->setSpacing(0);
0073 
0074     m_waveformGraphics->installEventFilter(this);
0075     m_widgetLayout->addWidget(m_waveformGraphics);
0076 
0077     connect(m_wfBuffer->zoomBuffer(), &ZoomBuffer::zoomedBufferReady, m_waveformGraphics, QOverload<>::of(&QWidget::update));
0078 
0079     m_scrollBar = new QScrollBar(Qt::Vertical, this);
0080     m_scrollBar->setPageStep(windowSize());
0081     m_scrollBar->setRange(0, windowSize());
0082     m_scrollBar->installEventFilter(this);
0083     m_widgetLayout->addWidget(m_scrollBar);
0084 
0085     m_scrollAnimation = new QPropertyAnimation(m_scrollBar, QByteArrayLiteral("value"), this);
0086     m_scrollAnimation->setDuration(150);
0087 
0088     m_btnZoomOut = createToolButton(QStringLiteral(ACT_WAVEFORM_ZOOM_OUT));
0089     m_btnZoomIn = createToolButton(QStringLiteral(ACT_WAVEFORM_ZOOM_IN));
0090     m_btnAutoScroll = createToolButton(QStringLiteral(ACT_WAVEFORM_AUTOSCROLL));
0091 
0092     QHBoxLayout *toolbarLayout = new QHBoxLayout();
0093     toolbarLayout->setContentsMargins(0, 0, 0, 0);
0094     toolbarLayout->setSpacing(2);
0095     toolbarLayout->addWidget(m_btnZoomOut);
0096     toolbarLayout->addWidget(m_btnZoomIn);
0097     toolbarLayout->addSpacerItem(new QSpacerItem(2, 2, QSizePolicy::Preferred, QSizePolicy::Preferred));
0098     toolbarLayout->addWidget(m_btnAutoScroll);
0099     toolbarLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred));
0100 
0101     m_toolbar = new QWidget(this);
0102     m_toolbar->setLayout(toolbarLayout);
0103 
0104     QBoxLayout *mainLayout = new QVBoxLayout(this);
0105     mainLayout->setContentsMargins(0, 0, 0, 0);
0106     mainLayout->setSpacing(5);
0107     mainLayout->addWidget(m_toolbar);
0108     mainLayout->addLayout(m_widgetLayout);
0109 
0110     setMinimumWidth(300);
0111 
0112     // Progress Bar
0113     m_progressWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
0114     m_progressWidget->hide();
0115 
0116     QLabel *label = new QLabel(i18n("Generating waveform"), m_progressWidget);
0117 
0118     m_progressBar = new QProgressBar(m_progressWidget);
0119     m_progressBar->setFormat(i18nc("%p is the percent value, % is the percent sign", "%p%"));
0120     m_progressBar->setMinimumWidth(300);
0121     m_progressBar->setTextVisible(true);
0122 
0123     QLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight, m_progressWidget);
0124     layout->setContentsMargins(1, 0, 1, 0);
0125     layout->setSpacing(1);
0126     layout->addWidget(label);
0127     layout->addWidget(m_progressBar);
0128 
0129     connect(m_scrollBar, &QScrollBar::valueChanged, this, &WaveformWidget::onScrollBarValueChanged);
0130 
0131     connect(VideoPlayer::instance(), &VideoPlayer::positionChanged, this, &WaveformWidget::onPlayerPositionChanged);
0132 
0133     m_hoverScrollTimer.setInterval(50);
0134     m_hoverScrollTimer.setSingleShot(false);
0135     connect(&m_hoverScrollTimer, &QTimer::timeout, this, &WaveformWidget::onHoverScrollTimeout);
0136 
0137     connect(app(), &Application::actionsReady, this, &WaveformWidget::updateActions);
0138     connect(m_wfBuffer, &WaveBuffer::waveformUpdated, this, [this]() {
0139         onWaveformResize(m_waveformGraphics->span());
0140     });
0141 }
0142 
0143 void
0144 WaveformWidget::updateActions()
0145 {
0146     const Application *app = SubtitleComposer::app();
0147 
0148     const quint32 span = m_waveformGraphics->span();
0149     const quint32 maxZoom = span ? m_wfBuffer->lengthSamples() / span : 0;
0150 
0151     m_btnZoomIn->setDefaultAction(app->action(ACT_WAVEFORM_ZOOM_IN));
0152     m_btnZoomIn->setEnabled(m_zoom > ZOOM_MIN);
0153 
0154     m_btnZoomOut->setDefaultAction(app->action(ACT_WAVEFORM_ZOOM_OUT));
0155     m_btnZoomOut->setEnabled(m_zoom < maxZoom);
0156 
0157     QAction *action = app->action(ACT_WAVEFORM_AUTOSCROLL);
0158     action->setChecked(m_autoScroll);
0159     m_btnAutoScroll->setDefaultAction(action);
0160     m_btnAutoScroll->setEnabled(m_wfBuffer->waveformDuration() > 0);
0161 }
0162 
0163 WaveformWidget::~WaveformWidget()
0164 {
0165     clearAudioStream();
0166 }
0167 
0168 double
0169 WaveformWidget::windowSizeInner(double *autoScrollPadding) const
0170 {
0171     const double winSize = windowSize();
0172     const double scrollPad = winSize * double(SCConfig::wfAutoscrollPadding()) / 100.;
0173     if(autoScrollPadding)
0174         *autoScrollPadding = scrollPad;
0175     const double innerSize = winSize - 2. * scrollPad;
0176     return qMax(innerSize, 1.);
0177 }
0178 
0179 void
0180 WaveformWidget::setZoom(quint32 val)
0181 {
0182     const quint32 span = m_waveformGraphics->span();
0183     if(span) {
0184         const quint32 maxZoom = m_wfBuffer->lengthSamples() / span;
0185         if(maxZoom && val > maxZoom)
0186             val = maxZoom;
0187     }
0188     if(val < ZOOM_MIN)
0189         val = ZOOM_MIN;
0190 
0191     if(m_zoom == val)
0192         return;
0193 
0194     m_wfBuffer->zoomBuffer()->setZoomScale(val);
0195 
0196     const quint32 samRt = m_wfBuffer->sampleRate();
0197     if(samRt) {
0198         const qint32 msSpanOld = m_zoom * span * 1000 / samRt;
0199         m_zoom = val;
0200 
0201         const quint32 msSpanNew = val * span * 1000 / samRt;
0202         m_timeStart.shift((msSpanOld - qint32(msSpanNew)) / 2);
0203         m_timeEnd = m_timeStart.shifted(msSpanNew);
0204         handleTimeUpdate(msSpanNew);
0205     } else {
0206         m_zoom = val;
0207     }
0208 
0209     updateActions();
0210 }
0211 
0212 void
0213 WaveformWidget::handleTimeUpdate(quint32 msSpan)
0214 {
0215     // make sure start time is good
0216     const quint32 msDuration = m_wfBuffer->waveformDuration() * 1000;
0217     const quint32 maxTime = msDuration < msSpan ? 0 : msDuration - msSpan;
0218     if(m_timeStart.toMillis() > maxTime) {
0219         m_timeStart.setMillisTime(maxTime);
0220         m_timeEnd.setMillisTime(maxTime + msSpan);
0221     }
0222 
0223     QSignalBlocker s(m_scrollBar);
0224     m_scrollBar->setPageStep(msSpan);
0225     m_scrollBar->setRange(0, maxTime);
0226     m_scrollBar->setValue(m_timeStart.toMillis());
0227 
0228     const quint16 chans = m_wfBuffer->channels();
0229     if(chans) {
0230         m_wfBuffer->zoomBuffer()->setZoomScale(m_zoom);
0231         if(!m_zoomData)
0232             m_zoomData = new WaveZoomData *[chans];
0233         m_wfBuffer->zoomBuffer()->zoomedBuffer(m_timeStart.toMillis(), m_timeEnd.toMillis(), m_zoomData, &m_zoomDataLen);
0234     }
0235 
0236     m_visibleLinesDirty = true;
0237     m_waveformGraphics->update();
0238 }
0239 
0240 void
0241 WaveformWidget::onWaveformResize(quint32 span)
0242 {
0243     const quint32 samRt = m_wfBuffer->sampleRate();
0244     if(samRt) {
0245         const double windowSize = m_zoom * span * 1000 / samRt;
0246         m_timeEnd = m_timeStart.shifted(windowSize);
0247         handleTimeUpdate(windowSize);
0248         updateActions();
0249     } else {
0250         m_visibleLinesDirty = true;
0251         m_waveformGraphics->update();
0252     }
0253 }
0254 
0255 void
0256 WaveformWidget::onWaveformRotate(bool vertical)
0257 {
0258     if(vertical) {
0259         m_widgetLayout->setDirection(QBoxLayout::LeftToRight);
0260         m_scrollBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
0261         m_scrollBar->setOrientation(Qt::Vertical);
0262     } else {
0263         m_widgetLayout->setDirection(QBoxLayout::TopToBottom);
0264         m_scrollBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
0265         m_scrollBar->setOrientation(Qt::Horizontal);
0266     }
0267 }
0268 
0269 void
0270 WaveformWidget::setAutoscroll(bool autoscroll)
0271 {
0272     m_autoScroll = autoscroll;
0273     app()->action(ACT_WAVEFORM_AUTOSCROLL)->setChecked(m_autoScroll);
0274 }
0275 
0276 void
0277 WaveformWidget::onScrollBarValueChanged(int value)
0278 {
0279     double winSize = windowSize();
0280     m_timeStart = value;
0281     m_timeEnd = m_timeStart.shifted(winSize);
0282     handleTimeUpdate(winSize);
0283 }
0284 
0285 void
0286 WaveformWidget::setSubtitle(Subtitle *subtitle)
0287 {
0288     if(m_subtitle) {
0289         disconnect(m_subtitle.constData(), &Subtitle::primaryChanged, this, &WaveformWidget::onSubtitleChanged);
0290         disconnect(m_subtitle.constData(), &Subtitle::secondaryChanged, this, &WaveformWidget::onSubtitleChanged);
0291         disconnect(m_subtitle.constData(), &Subtitle::lineAnchorChanged, this, &WaveformWidget::onSubtitleChanged);
0292     }
0293 
0294     m_subtitle = subtitle;
0295 
0296     if(m_subtitle) {
0297         connect(m_subtitle.constData(), &Subtitle::primaryChanged, this, &WaveformWidget::onSubtitleChanged);
0298         connect(m_subtitle.constData(), &Subtitle::secondaryChanged, this, &WaveformWidget::onSubtitleChanged);
0299         connect(m_subtitle.constData(), &Subtitle::lineAnchorChanged, this, &WaveformWidget::onSubtitleChanged);
0300     }
0301 
0302     m_visibleLines.clear();
0303     m_visibleLinesDirty = true;
0304 
0305     m_waveformGraphics->update();
0306 }
0307 
0308 void
0309 WaveformWidget::onSubtitleChanged()
0310 {
0311     m_visibleLinesDirty = true;
0312     m_waveformGraphics->update();
0313 }
0314 
0315 QWidget *
0316 WaveformWidget::progressWidget()
0317 {
0318     return m_progressWidget;
0319 }
0320 
0321 QWidget *
0322 WaveformWidget::toolbarWidget()
0323 {
0324     return m_toolbar;
0325 }
0326 
0327 void
0328 WaveformWidget::setAudioStream(const QString &mediaFile, int audioStream)
0329 {
0330     if(m_mediaFile == mediaFile && audioStream == m_streamIndex)
0331         return;
0332 
0333     clearAudioStream();
0334 
0335     m_mediaFile = mediaFile;
0336     m_streamIndex = audioStream;
0337 
0338     m_wfBuffer->setAudioStream(m_mediaFile, m_streamIndex);
0339 }
0340 
0341 void
0342 WaveformWidget::setNullAudioStream(quint64 msecVideoLength)
0343 {
0344     clearAudioStream();
0345 
0346     m_timeStart.setMillisTime(0.);
0347     m_timeStart.setMillisTime(msecVideoLength);
0348     // will do onTimeUpdated() during event from m_wfBuffer->setNullAudioStream()
0349     m_wfBuffer->setNullAudioStream(msecVideoLength);
0350 }
0351 
0352 void
0353 WaveformWidget::clearAudioStream()
0354 {
0355     m_wfBuffer->clearAudioStream();
0356 
0357     m_mediaFile.clear();
0358     m_streamIndex = -1;
0359 
0360     delete[] m_zoomData;
0361     m_zoomData = nullptr;
0362     m_zoomDataLen = 0;
0363 }
0364 
0365 void
0366 WaveformWidget::updateVisibleLines()
0367 {
0368     if(!m_subtitle || !m_visibleLinesDirty)
0369         return;
0370 
0371     m_visibleLinesDirty = false;
0372 
0373     auto it = m_visibleLines.begin();
0374     while(it != m_visibleLines.end()) {
0375         if(*it == m_draggedLine) {
0376             it = m_visibleLines.erase(it);
0377         } else if((*it)->line()->subtitle() != m_subtitle.data()
0378             || !(*it)->line()->intersectsTimespan(m_timeStart, m_timeEnd)) {
0379             delete *it;
0380             it = m_visibleLines.erase(it);
0381         } else {
0382             ++it;
0383         }
0384     }
0385 
0386     it = m_visibleLines.begin();
0387     for(int i = 0, n = m_subtitle->count(); i < n; i++) {
0388         SubtitleLine *sub = m_subtitle->at(i);
0389         const bool isDragged = m_draggedLine != nullptr && sub == m_draggedLine->line();
0390         if(!sub->intersectsTimespan(m_timeStart, m_timeEnd) && !isDragged)
0391             continue;
0392         const Time showTime = isDragged ? m_draggedLine->showTime() : sub->showTime();
0393         while(it != m_visibleLines.end() && (*it)->showTime() < showTime) {
0394             if((*it)->line() == sub)
0395                 break;
0396             ++it;
0397         }
0398         if(it == m_visibleLines.end() || (*it)->line() != sub) {
0399             if(isDragged) {
0400                 m_visibleLines.emplace(it, m_draggedLine);
0401                 it = m_visibleLines.begin();
0402             } else {
0403                 it = m_visibleLines.emplace(it, new WaveSubtitle(sub, m_waveformGraphics));
0404                 ++it;
0405             }
0406         }
0407     }
0408 }
0409 
0410 void
0411 WaveformWidget::leaveEvent(QEvent */*event*/)
0412 {
0413     m_pointerTime.setMillisTime(std::numeric_limits<double>::max());
0414 
0415     if(m_autoScrollPause) {
0416         if(!m_RMBDown)
0417             m_autoScrollPause = false;
0418         if(m_autoScroll && !m_draggedLine && !m_autoScrollPause)
0419             scrollToTime(m_timeCurrent, true);
0420     }
0421 
0422     m_waveformGraphics->update();
0423 }
0424 
0425 bool
0426 WaveformWidget::eventFilter(QObject */*obj*/, QEvent *event)
0427 {
0428     switch(event->type()) {
0429     case QEvent::Wheel: {
0430         QPoint delta = static_cast<QWheelEvent *>(event)->angleDelta() / 8;
0431         if(delta.isNull())
0432             delta = static_cast<QWheelEvent *>(event)->pixelDelta();
0433         if(delta.isNull())
0434             return false;
0435 
0436         m_autoScrollPause = true;
0437 
0438         m_scrollBar->setValue(m_timeStart.shifted(-4 * double(delta.ry()) * windowSize() / m_waveformGraphics->span()).toMillis());
0439         return true; // stop wheel events from propagating
0440     }
0441     case QEvent::MouseButtonPress:
0442         m_autoScrollPause = true;
0443         return false;
0444 
0445     default:
0446         return false;
0447     }
0448 }
0449 
0450 Time
0451 WaveformWidget::timeAt(int y)
0452 {
0453     return m_timeStart + double(y * qint32(windowSize()) / m_waveformGraphics->span());
0454 }
0455 
0456 DragPosition
0457 WaveformWidget::draggableAt(double posTime, WaveSubtitle **result)
0458 {
0459     if(result)
0460         *result = nullptr;
0461 
0462     if(!m_wfBuffer->sampleRate())
0463         return DRAG_NONE;
0464 
0465     const double msTolerance = 10. * m_wfBuffer->zoomBuffer()->samplesPerPixel() * 1000. / m_wfBuffer->sampleRate();
0466 
0467     DragPosition dragMode = DRAG_NONE;
0468     for(WaveSubtitle *sub: m_visibleLines) {
0469         DragPosition dm = sub->draggableAt(posTime, msTolerance);
0470         if(dm > DRAG_FORBIDDEN || dragMode == DRAG_NONE) {
0471             dragMode = dm;
0472             if(result && dm > DRAG_FORBIDDEN)
0473                 *result = sub;
0474         }
0475     }
0476 
0477     return dragMode;
0478 }
0479 
0480 SubtitleLine *
0481 WaveformWidget::subtitleLineAtMousePosition() const
0482 {
0483     const Time mouseTime = m_RMBDown ? m_timeRMBRelease : m_pointerTime;
0484     for(const WaveSubtitle *sub: qAsConst(m_visibleLines)) {
0485         if(sub->line()->containsTime(mouseTime))
0486             return sub->line();
0487     }
0488     return nullptr;
0489 }
0490 
0491 void
0492 WaveformWidget::setScrollPosition(double milliseconds)
0493 {
0494     if(milliseconds < m_timeStart.toMillis() || milliseconds > m_timeEnd.toMillis()) {
0495         scrollToTime(milliseconds, true);
0496         m_visibleLinesDirty = true;
0497         m_waveformGraphics->update();
0498     }
0499 }
0500 
0501 void
0502 WaveformWidget::onHoverScrollTimeout()
0503 {
0504     if(!m_draggedLine && !m_RMBDown && !m_MMBDown) {
0505         m_hoverScrollAmount = .0;
0506         m_hoverScrollTimer.stop();
0507         return;
0508     }
0509 
0510     if(m_hoverScrollAmount == .0)
0511         return;
0512 
0513     const Time ptrTime(m_pointerTime.toMillis() + m_hoverScrollAmount);
0514     if(m_draggedLine)
0515         m_draggedLine->dragUpdate(ptrTime.toMillis());
0516     if(m_RMBDown)
0517         m_timeRMBRelease = ptrTime;
0518     m_scrollBar->setValue(m_timeStart.toMillis() + m_hoverScrollAmount);
0519 }
0520 
0521 void
0522 WaveformWidget::updatePointerTime(int pos)
0523 {
0524     m_pointerTime = timeAt(pos);
0525 
0526     if(m_RMBDown) {
0527         m_timeRMBRelease = m_pointerTime;
0528         scrollToTime(m_pointerTime, false);
0529     }
0530 
0531     if(m_MMBDown) {
0532         scrollToTime(m_pointerTime, false);
0533         emit middleMouseMove(m_pointerTime);
0534     }
0535 
0536     if(m_draggedLine) {
0537         m_draggedLine->dragUpdate(m_pointerTime.toMillis());
0538         scrollToTime(m_pointerTime, false);
0539     } else {
0540         const double posTime = timeAt(pos).toMillis();
0541         DragPosition res = draggableAt(posTime, nullptr);
0542         if(res == DRAG_FORBIDDEN)
0543             m_waveformGraphics->setCursor(QCursor(Qt::ForbiddenCursor));
0544         else if(res == DRAG_LINE)
0545             m_waveformGraphics->setCursor(QCursor(m_waveformGraphics->vertical() ? Qt::SizeVerCursor : Qt::SizeHorCursor));
0546         else if(res == DRAG_SHOW || res == DRAG_HIDE)
0547             m_waveformGraphics->setCursor(QCursor(m_waveformGraphics->vertical() ? Qt::SplitVCursor : Qt::SplitHCursor));
0548         else
0549             m_waveformGraphics->unsetCursor();
0550     }
0551 }
0552 
0553 bool
0554 WaveformWidget::mousePress(int pos, Qt::MouseButton button)
0555 {
0556     if(button == Qt::RightButton) {
0557         m_timeRMBPress = m_timeRMBRelease = timeAt(pos);
0558         m_RMBDown = true;
0559         return false;
0560     }
0561 
0562     if(button == Qt::MiddleButton) {
0563         m_MMBDown = true;
0564         emit middleMouseDown(timeAt(pos));
0565         return false;
0566     }
0567 
0568     if(button != Qt::LeftButton)
0569         return false;
0570 
0571     const double posTime = timeAt(pos).toMillis();
0572     DragPosition dragMode = draggableAt(posTime, &m_draggedLine);
0573     if(m_draggedLine) {
0574         m_pointerTime.setMillisTime(posTime);
0575         m_draggedLine->dragStart(dragMode, posTime);
0576         emit dragStart(m_draggedLine->line(), dragMode);
0577     }
0578 
0579     return true;
0580 }
0581 
0582 bool
0583 WaveformWidget::mouseRelease(int pos, Qt::MouseButton button, const QPointF &globalPos)
0584 {
0585     if(button == Qt::RightButton) {
0586         m_timeRMBRelease = timeAt(pos);
0587         m_hoverScrollTimer.stop();
0588         showContextMenu(globalPos.toPoint());
0589         m_RMBDown = false;
0590         return false;
0591     }
0592 
0593     if(button == Qt::MiddleButton) {
0594         emit middleMouseUp(timeAt(pos));
0595         m_hoverScrollTimer.stop();
0596         m_MMBDown = false;
0597         return true;
0598     }
0599 
0600     if(button != Qt::LeftButton)
0601         return false;
0602 
0603     if(m_draggedLine) {
0604         DragPosition mode = m_draggedLine->dragEnd(timeAt(pos).toMillis());
0605         emit dragEnd(m_draggedLine->line(), mode);
0606         m_draggedLine = nullptr;
0607     }
0608     return true;
0609 }
0610 
0611 bool
0612 WaveformWidget::scrollToTime(const Time &time, bool scrollToPage)
0613 {
0614     double windowPadding;
0615     const double windowSize = windowSizeInner(&windowPadding);
0616     if(m_draggedLine || m_RMBDown || m_MMBDown)
0617         windowPadding = this->windowSize() / 5.;
0618 
0619     const double topPadding = m_timeStart.toMillis() + windowPadding;
0620     const double bottomPadding = m_timeEnd.toMillis() - windowPadding;
0621     if(time <= bottomPadding && time >= topPadding) {
0622         if(!scrollToPage) {
0623             m_hoverScrollAmount = .0;
0624             m_hoverScrollTimer.stop();
0625         }
0626         return false;
0627     }
0628 
0629     if(scrollToPage) {
0630         const int scrollPosition = int(time.toMillis() / windowSize) * windowSize - windowPadding;
0631         if(SCConfig::wfSmoothScroll()) {
0632             m_scrollAnimation->setEndValue(scrollPosition);
0633             m_scrollAnimation->start();
0634         } else {
0635             m_scrollBar->setValue(scrollPosition);
0636         }
0637     } else {
0638         const double bd = time.toMillis() - (time < topPadding ? topPadding : bottomPadding);
0639         m_hoverScrollAmount = bd * bd * bd / (3. * windowPadding * windowPadding);
0640         if(!m_hoverScrollTimer.isActive())
0641             m_hoverScrollTimer.start();
0642     }
0643 
0644     return true;
0645 }
0646 
0647 void
0648 WaveformWidget::onPlayerPositionChanged(double seconds)
0649 {
0650     Time playingPosition;
0651     playingPosition.setSecondsTime(seconds);
0652 
0653     if(m_timeCurrent != playingPosition) {
0654         m_timeCurrent = playingPosition;
0655 
0656         if(m_autoScroll && !m_draggedLine && !m_autoScrollPause)
0657             scrollToTime(m_timeCurrent, true);
0658 
0659         if(m_waveformGraphics)
0660             m_waveformGraphics->update();
0661     }
0662 }
0663 
0664 QToolButton *
0665 WaveformWidget::createToolButton(const QString &actionName, int iconSize)
0666 {
0667     QToolButton *toolButton = new QToolButton(this);
0668     toolButton->setObjectName(actionName);
0669     toolButton->setMinimumSize(iconSize, iconSize);
0670     toolButton->setIconSize(iconSize >= 32 ? QSize(iconSize - 6, iconSize - 6) : QSize(iconSize, iconSize));
0671     toolButton->setAutoRaise(true);
0672     toolButton->setFocusPolicy(Qt::NoFocus);
0673     return toolButton;
0674 }
0675 
0676 void
0677 WaveformWidget::showContextMenu(QPoint pos)
0678 {
0679     static QMenu *menu = nullptr;
0680     static QList<QAction *> needCurrentLine;
0681     static QList<QAction *> needSubtitle;
0682 
0683     const Application *app = SubtitleComposer::app();
0684     SubtitleLine *currentLine = subtitleLineAtMousePosition();
0685     SubtitleLine *selectedLine = app->linesWidget()->currentLine();
0686 
0687     if(!menu) {
0688         UserActionManager *actionManager = UserActionManager::instance();
0689         menu = new QMenu(this);
0690 
0691         needCurrentLine.append(
0692             menu->addAction(QIcon::fromTheme(QStringLiteral("select")), i18n("Select Line"), [&](){
0693                 app->linesWidget()->setCurrentLine(currentLine, true);
0694             }));
0695         menu->addSeparator();
0696         actionManager->addAction(
0697             menu->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Insert Line"), [&](){
0698                 const Time timeShow = rightMouseSoonerTime();
0699                 const Time timeHide = rightMouseLaterTime();
0700                 SubtitleLine *newLine = new SubtitleLine(timeShow,
0701                     timeHide.toMillis() - timeShow.toMillis() > SCConfig::minDuration() ? timeHide : timeShow + SCConfig::minDuration());
0702                 m_subtitle->insertLine(newLine);
0703                 app->linesWidget()->setCurrentLine(newLine, true);
0704             }),
0705             UserAction::SubOpened);
0706         needCurrentLine.append(
0707             menu->addAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove Line"), [&](){
0708                 m_subtitle->removeLines(RangeList(Range(currentLine->index())), SubtitleTarget::Both);
0709                 if(selectedLine != currentLine)
0710                     app->linesWidget()->setCurrentLine(selectedLine, true);
0711             }));
0712         menu->addSeparator();
0713         needSubtitle.append(
0714             menu->addAction(i18n("Join Lines"), this, [&](){
0715                 int startIndex = -1, endIndex = -1;
0716                 const Time startTime = rightMouseSoonerTime();
0717                 const Time endTime = rightMouseLaterTime();
0718                 for(int idx = 0, n = m_subtitle->count(); idx < n; idx++) {
0719                     const SubtitleLine *sub = m_subtitle->at(idx);
0720                     if(sub->intersectsTimespan(startTime, endTime)) {
0721                         if(startIndex == -1 || startIndex > idx)
0722                             startIndex = idx;
0723                         if(endIndex == -1 || endIndex < idx)
0724                             endIndex = idx;
0725                     }
0726                 }
0727                 if(endIndex >= 0 && startIndex != endIndex)
0728                     m_subtitle->joinLines(RangeList(Range(startIndex, endIndex)));
0729             })
0730         );
0731         needCurrentLine.append(
0732             menu->addAction(i18n("Split Line"), this, [&](){
0733                 // TODO: split the line at exact waveform mouse position
0734                 m_subtitle->splitLines(RangeList(Range(currentLine->index())));
0735             }));
0736         menu->addSeparator();
0737         needCurrentLine.append(
0738             menu->addAction(i18n("Toggle Anchor"), this, [&](){ m_subtitle->toggleLineAnchor(currentLine); }));
0739         menu->addAction(app->action(ACT_ANCHOR_REMOVE_ALL));
0740         menu->addSeparator();
0741         actionManager->addAction(
0742             menu->addAction(QIcon::fromTheme(QStringLiteral("set_show_time")), i18n("Set Current Line Show Time"), [&](){
0743                 selectedLine->setShowTime(m_timeRMBRelease);
0744             }),
0745             UserAction::HasSelection | UserAction::EditableShowTime);
0746         actionManager->addAction(
0747             menu->addAction(QIcon::fromTheme(QStringLiteral("set_hide_time")), i18n("Set Current Line Hide Time"), [&](){
0748                 selectedLine->setHideTime(m_timeRMBRelease);
0749             }),
0750             UserAction::HasSelection | UserAction::EditableShowTime);
0751     }
0752 
0753     foreach(QAction *action, needCurrentLine)
0754         action->setDisabled(currentLine == nullptr);
0755     foreach(QAction *action, needSubtitle)
0756         action->setDisabled(m_subtitle == nullptr);
0757 
0758     menu->exec(pos);
0759 }
0760 
0761 void
0762 WaveformWidget::setTranslationMode(bool enabled)
0763 {
0764     m_translationMode = enabled;
0765 
0766     if(!m_translationMode)
0767         setShowTranslation(false);
0768 }
0769 
0770 void
0771 WaveformWidget::setShowTranslation(bool showTranslation)
0772 {
0773     if(m_showTranslation != showTranslation) {
0774         m_showTranslation = showTranslation;
0775         m_waveformGraphics->update();
0776     }
0777 }