File indexing completed on 2024-12-22 04:40:13

0001 /*
0002     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
0003     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "playerwidget.h"
0009 
0010 #include "appglobal.h"
0011 #include "application.h"
0012 #include "actions/useractionnames.h"
0013 #include "core/richtext/richdocument.h"
0014 #include "videoplayer/videoplayer.h"
0015 #include "widgets/layeredwidget.h"
0016 #include "widgets/attachablewidget.h"
0017 #include "widgets/pointingslider.h"
0018 #include "widgets/timeedit.h"
0019 
0020 #include <QEvent>
0021 #include <QDropEvent>
0022 #include <QMimeData>
0023 #include <QKeyEvent>
0024 
0025 #include <QMenu>
0026 #include <QPushButton>
0027 #include <QCursor>
0028 #include <QLabel>
0029 #include <QToolButton>
0030 #include <QGroupBox>
0031 #include <QGridLayout>
0032 
0033 #include <KConfigGroup>
0034 #include <KMessageBox>
0035 #include <KLocalizedString>
0036 
0037 using namespace SubtitleComposer;
0038 
0039 #define HIDE_MOUSE_MSECS 1000
0040 #define UNKNOWN_LENGTH_STRING (" / " + Time().toString(false) + ' ')
0041 
0042 PlayerWidget::PlayerWidget(QWidget *parent) :
0043     QWidget(parent),
0044     m_subtitle(0),
0045     m_translationMode(false),
0046     m_showTranslation(false),
0047     m_pauseAfterPlayingLine(nullptr),
0048     m_fullScreenTID(0),
0049     m_fullScreenMode(false),
0050     m_lengthString(UNKNOWN_LENGTH_STRING),
0051     m_showPositionTimeEdit(SCConfig::showPositionTimeEdit())
0052 {
0053     m_layeredWidget = new LayeredWidget(this);
0054     m_layeredWidget->setAcceptDrops(true);
0055     m_layeredWidget->installEventFilter(this);
0056     m_layeredWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0057 
0058     m_seekSlider = new PointingSlider(Qt::Horizontal, this);
0059     m_seekSlider->setTickPosition(QSlider::NoTicks);
0060     m_seekSlider->setMinimum(0);
0061     m_seekSlider->setMaximum(1000);
0062     m_seekSlider->setPageStep(10);
0063     m_seekSlider->setFocusPolicy(Qt::NoFocus);
0064 
0065     m_infoControlsGroupBox = new QWidget(this);
0066     m_infoControlsGroupBox->setAcceptDrops(true);
0067     m_infoControlsGroupBox->installEventFilter(this);
0068 
0069     QLabel *positionTagLabel = new QLabel(m_infoControlsGroupBox);
0070     positionTagLabel->setText(i18n("<b>Position</b>"));
0071     positionTagLabel->installEventFilter(this);
0072 
0073     m_positionLabel = new QLabel(m_infoControlsGroupBox);
0074     m_positionEdit = new TimeEdit(m_infoControlsGroupBox);
0075     m_positionEdit->setFocusPolicy(Qt::NoFocus);
0076 
0077     QLabel *lengthTagLabel = new QLabel(m_infoControlsGroupBox);
0078     lengthTagLabel->setText(i18n("<b>Length</b>"));
0079     lengthTagLabel->installEventFilter(this);
0080     m_lengthLabel = new QLabel(m_infoControlsGroupBox);
0081 
0082     QLabel *fpsTagLabel = new QLabel(m_infoControlsGroupBox);
0083     fpsTagLabel->setText(i18n("<b>FPS</b>"));
0084     fpsTagLabel->installEventFilter(this);
0085     m_fpsLabel = new QLabel(m_infoControlsGroupBox);
0086     m_fpsLabel->setMinimumWidth(m_positionEdit->sizeHint().width());        // sets the minimum width for the whole group
0087 
0088     QLabel *rateTagLabel = new QLabel(m_infoControlsGroupBox);
0089     rateTagLabel->setText(i18n("<b>Playback Rate</b>"));
0090     rateTagLabel->installEventFilter(this);
0091     m_rateLabel = new QLabel(m_infoControlsGroupBox);
0092 
0093     m_volumeSlider = new PointingSlider(Qt::Vertical, this);
0094     m_volumeSlider->setFocusPolicy(Qt::NoFocus);
0095     m_volumeSlider->setTickPosition(QSlider::NoTicks);
0096     m_volumeSlider->setPageStep(5);
0097     m_volumeSlider->setMinimum(0);
0098     m_volumeSlider->setMaximum(100);
0099     m_volumeSlider->setFocusPolicy(Qt::NoFocus);
0100 
0101     QGridLayout *videoControlsLayout = new QGridLayout();
0102     videoControlsLayout->setContentsMargins(0, 0, 0, 0);
0103     videoControlsLayout->setSpacing(2);
0104     videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_PAUSE, 16), 0, 0);
0105     videoControlsLayout->addWidget(createToolButton(this, ACT_STOP, 16), 0, 1);
0106     videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_BACKWARD, 16), 0, 2);
0107     videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_FORWARD, 16), 0, 3);
0108     videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 4);
0109     videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_TO_PREVIOUS_LINE, 16), 0, 5);
0110     videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_TO_NEXT_LINE, 16), 0, 6);
0111     videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 7);
0112     videoControlsLayout->addWidget(createToolButton(this, ACT_SET_CURRENT_LINE_SHOW_TIME, 16), 0, 8);
0113     videoControlsLayout->addWidget(createToolButton(this, ACT_SET_CURRENT_LINE_HIDE_TIME, 16), 0, 9);
0114     videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 10);
0115     videoControlsLayout->addWidget(createToolButton(this, ACT_CURRENT_LINE_FOLLOWS_VIDEO, 16), 0, 11);
0116     videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 12);
0117     videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_RATE_DECREASE, 16), 0, 13);
0118     videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_RATE_INCREASE, 16), 0, 14);
0119     videoControlsLayout->addWidget(m_seekSlider, 0, 15);
0120 
0121     QGridLayout *audioControlsLayout = new QGridLayout();
0122     audioControlsLayout->setContentsMargins(0, 0, 0, 0);
0123     audioControlsLayout->addWidget(createToolButton(this, ACT_TOGGLE_MUTED, 16), 0, 0, Qt::AlignHCenter);
0124     audioControlsLayout->addWidget(m_volumeSlider, 1, 0, Qt::AlignHCenter);
0125 
0126     QGridLayout *infoControlsLayout = new QGridLayout(m_infoControlsGroupBox);
0127     infoControlsLayout->setSpacing(5);
0128     infoControlsLayout->addWidget(fpsTagLabel, 0, 0);
0129     infoControlsLayout->addWidget(m_fpsLabel, 1, 0);
0130     infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 2, 0);
0131     infoControlsLayout->addWidget(rateTagLabel, 3, 0);
0132     infoControlsLayout->addWidget(m_rateLabel, 4, 0);
0133     infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 5, 0);
0134     infoControlsLayout->addWidget(lengthTagLabel, 6, 0);
0135     infoControlsLayout->addWidget(m_lengthLabel, 7, 0);
0136     infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 8, 0);
0137     infoControlsLayout->addWidget(positionTagLabel, 9, 0);
0138     infoControlsLayout->addWidget(m_positionLabel, 10, 0);
0139     infoControlsLayout->addWidget(m_positionEdit, 11, 0);
0140 
0141     m_mainLayout = new QGridLayout(this);
0142     m_mainLayout->setContentsMargins(0, 0, 0, 0);
0143     m_mainLayout->setSpacing(5);
0144     m_mainLayout->addWidget(m_infoControlsGroupBox, 0, 0, 2, 1);
0145     m_mainLayout->addWidget(m_layeredWidget, 0, 1);
0146     m_mainLayout->addLayout(audioControlsLayout, 0, 2);
0147     m_mainLayout->addLayout(videoControlsLayout, 1, 1);
0148     m_mainLayout->addWidget(createToolButton(this, ACT_TOGGLE_FULL_SCREEN, 16), 1, 2);
0149 
0150     m_fullScreenControls = new AttachableWidget(AttachableWidget::Bottom, 4);
0151     m_fullScreenControls->setAutoFillBackground(true);
0152     m_layeredWidget->setWidgetMode(m_fullScreenControls, LayeredWidget::IgnoreResize);
0153 
0154     m_fsSeekSlider = new PointingSlider(Qt::Horizontal, m_fullScreenControls);
0155     m_fsSeekSlider->setTickPosition(QSlider::NoTicks);
0156     m_fsSeekSlider->setMinimum(0);
0157     m_fsSeekSlider->setMaximum(1000);
0158     m_fsSeekSlider->setPageStep(10);
0159 
0160     m_fsVolumeSlider = new PointingSlider(Qt::Horizontal, m_fullScreenControls);
0161     m_fsVolumeSlider->setFocusPolicy(Qt::NoFocus);
0162     m_fsVolumeSlider->setTickPosition(QSlider::NoTicks);
0163     m_fsVolumeSlider->setPageStep(5);
0164     m_fsVolumeSlider->setMinimum(0);
0165     m_fsVolumeSlider->setMaximum(100);
0166 
0167     m_fsPositionLabel = new QLabel(m_fullScreenControls);
0168     QPalette fsPositionPalette;
0169     fsPositionPalette.setColor(m_fsPositionLabel->backgroundRole(), Qt::black);
0170     fsPositionPalette.setColor(m_fsPositionLabel->foregroundRole(), Qt::white);
0171     m_fsPositionLabel->setPalette(fsPositionPalette);
0172     m_fsPositionLabel->setAutoFillBackground(true);
0173     m_fsPositionLabel->setFrameShape(QFrame::Panel);
0174     m_fsPositionLabel->setText(Time().toString(false) + " /  " + Time().toString(false));
0175     m_fsPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0176     m_fsPositionLabel->adjustSize();
0177     m_fsPositionLabel->setMinimumWidth(m_fsPositionLabel->width());
0178 
0179     QHBoxLayout *fullScreenControlsLayout = new QHBoxLayout(m_fullScreenControls);
0180     fullScreenControlsLayout->setContentsMargins(0, 0, 0, 0);
0181     fullScreenControlsLayout->setSpacing(0);
0182 
0183     const int FS_BUTTON_SIZE = 32;
0184     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_PAUSE, FS_BUTTON_SIZE));
0185     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_STOP, FS_BUTTON_SIZE));
0186     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_BACKWARD, FS_BUTTON_SIZE));
0187     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_FORWARD, FS_BUTTON_SIZE));
0188     fullScreenControlsLayout->addSpacing(3);
0189     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_TO_PREVIOUS_LINE, FS_BUTTON_SIZE));
0190     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_TO_NEXT_LINE, FS_BUTTON_SIZE));
0191     fullScreenControlsLayout->addSpacing(3);
0192     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_RATE_DECREASE, FS_BUTTON_SIZE));
0193     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_RATE_INCREASE, FS_BUTTON_SIZE));
0194     fullScreenControlsLayout->addSpacing(3);
0195     fullScreenControlsLayout->addWidget(m_fsSeekSlider, 9);
0196     fullScreenControlsLayout->addWidget(m_fsPositionLabel);
0197     fullScreenControlsLayout->addWidget(m_fsVolumeSlider, 2);
0198     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_TOGGLE_MUTED, FS_BUTTON_SIZE));
0199     fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_TOGGLE_FULL_SCREEN, FS_BUTTON_SIZE));
0200     m_fullScreenControls->adjustSize();
0201 
0202     connect(m_volumeSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onVolumeSliderMoved);
0203     connect(m_fsVolumeSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onVolumeSliderMoved);
0204 
0205     connect(m_seekSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onSeekSliderMoved);
0206     connect(m_fsSeekSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onSeekSliderMoved);
0207 
0208     connect(m_positionEdit, &TimeEdit::valueChanged, this, &PlayerWidget::onPositionEditValueChanged);
0209     connect(m_positionEdit, &TimeEdit::valueEntered, this, &PlayerWidget::onPositionEditValueChanged);
0210 
0211     connect(SCConfig::self(), &KCoreConfigSkeleton::configChanged, this, &PlayerWidget::onConfigChanged);
0212 
0213     VideoPlayer *videoPlayer = VideoPlayer::instance();
0214     videoPlayer->init(m_layeredWidget);
0215 
0216     connect(videoPlayer, &VideoPlayer::fileOpened, this, &PlayerWidget::onPlayerFileOpened);
0217     connect(videoPlayer, &VideoPlayer::fileOpenError, this, &PlayerWidget::onPlayerFileOpenError);
0218     connect(videoPlayer, &VideoPlayer::fileClosed, this, &PlayerWidget::onPlayerFileClosed);
0219     connect(videoPlayer, &VideoPlayer::playbackError, this, &PlayerWidget::onPlayerPlaybackError);
0220     connect(videoPlayer, &VideoPlayer::playing, this, &PlayerWidget::onPlayerPlaying);
0221     connect(videoPlayer, &VideoPlayer::stopped, this, &PlayerWidget::onPlayerStopped);
0222     connect(videoPlayer, &VideoPlayer::positionChanged, this, &PlayerWidget::onPlayerPositionChanged);
0223     connect(videoPlayer, &VideoPlayer::durationChanged, this, &PlayerWidget::onPlayerLengthChanged);
0224     connect(videoPlayer, &VideoPlayer::fpsChanged, this, &PlayerWidget::onPlayerFramesPerSecondChanged);
0225     connect(videoPlayer, &VideoPlayer::playSpeedChanged, this, &PlayerWidget::onPlayerPlaybackRateChanged);
0226     connect(videoPlayer, &VideoPlayer::volumeChanged, this, &PlayerWidget::onPlayerVolumeChanged);
0227     connect(videoPlayer, &VideoPlayer::muteChanged, m_fsVolumeSlider, &QWidget::setDisabled);
0228     connect(videoPlayer, &VideoPlayer::muteChanged, m_volumeSlider, &QWidget::setDisabled);
0229 
0230     connect(videoPlayer, &VideoPlayer::leftClicked, this, &PlayerWidget::onPlayerLeftClicked);
0231     connect(videoPlayer, &VideoPlayer::rightClicked, this, &PlayerWidget::onPlayerRightClicked);
0232     connect(videoPlayer, &VideoPlayer::doubleClicked, this, &PlayerWidget::onPlayerDoubleClicked);
0233 
0234     onPlayerFileClosed();
0235     onConfigChanged();    // initializes the font
0236 
0237     setFullScreenMode(m_fullScreenMode);
0238 
0239     connect(app(), &Application::actionsReady, this, [this](){
0240         toolButton(this, ACT_STOP)->setDefaultAction(app()->action(ACT_STOP));
0241         toolButton(this, ACT_PLAY_PAUSE)->setDefaultAction(app()->action(ACT_PLAY_PAUSE));
0242         toolButton(this, ACT_SEEK_BACKWARD)->setDefaultAction(app()->action(ACT_SEEK_BACKWARD));
0243         toolButton(this, ACT_SEEK_FORWARD)->setDefaultAction(app()->action(ACT_SEEK_FORWARD));
0244         toolButton(this, ACT_SEEK_TO_PREVIOUS_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
0245         toolButton(this, ACT_SEEK_TO_NEXT_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
0246         toolButton(this, ACT_SET_CURRENT_LINE_SHOW_TIME)->setDefaultAction(app()->action(ACT_SET_CURRENT_LINE_SHOW_TIME));
0247         toolButton(this, ACT_SET_CURRENT_LINE_HIDE_TIME)->setDefaultAction(app()->action(ACT_SET_CURRENT_LINE_HIDE_TIME));
0248         toolButton(this, ACT_CURRENT_LINE_FOLLOWS_VIDEO)->setDefaultAction(app()->action(ACT_CURRENT_LINE_FOLLOWS_VIDEO));
0249         toolButton(this, ACT_TOGGLE_MUTED)->setDefaultAction(app()->action(ACT_TOGGLE_MUTED));
0250         toolButton(this, ACT_TOGGLE_FULL_SCREEN)->setDefaultAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
0251         toolButton(this, ACT_PLAY_RATE_DECREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_DECREASE));
0252         toolButton(this, ACT_PLAY_RATE_INCREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_INCREASE));
0253 
0254         toolButton(m_fullScreenControls, ACT_STOP)->setDefaultAction(app()->action(ACT_STOP));
0255         toolButton(m_fullScreenControls, ACT_PLAY_PAUSE)->setDefaultAction(app()->action(ACT_PLAY_PAUSE));
0256         toolButton(m_fullScreenControls, ACT_SEEK_BACKWARD)->setDefaultAction(app()->action(ACT_SEEK_BACKWARD));
0257         toolButton(m_fullScreenControls, ACT_SEEK_FORWARD)->setDefaultAction(app()->action(ACT_SEEK_FORWARD));
0258         toolButton(m_fullScreenControls, ACT_SEEK_TO_PREVIOUS_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
0259         toolButton(m_fullScreenControls, ACT_SEEK_TO_NEXT_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
0260         toolButton(m_fullScreenControls, ACT_TOGGLE_MUTED)->setDefaultAction(app()->action(ACT_TOGGLE_MUTED));
0261         toolButton(m_fullScreenControls, ACT_TOGGLE_FULL_SCREEN)->setDefaultAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
0262         toolButton(m_fullScreenControls, ACT_PLAY_RATE_DECREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_DECREASE));
0263         toolButton(m_fullScreenControls, ACT_PLAY_RATE_INCREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_INCREASE));
0264     });
0265 }
0266 
0267 PlayerWidget::~PlayerWidget()
0268 {
0269     m_fullScreenControls->deleteLater();
0270 }
0271 
0272 QWidget *
0273 PlayerWidget::infoSidebarWidget()
0274 {
0275     return m_infoControlsGroupBox;
0276 }
0277 
0278 QToolButton *
0279 PlayerWidget::toolButton(QWidget *parent, const char *name)
0280 {
0281     return parent->findChild<QToolButton *>(name);
0282 }
0283 
0284 QToolButton *
0285 PlayerWidget::createToolButton(QWidget *parent, const char *name, int size)
0286 {
0287     QToolButton *toolButton = new QToolButton(parent);
0288     toolButton->setObjectName(name);
0289     toolButton->setMinimumSize(size, size);
0290     toolButton->setIconSize(size >= 32 ? QSize(size - 6, size - 6) : QSize(size, size));
0291     toolButton->setAutoRaise(true);
0292     toolButton->setFocusPolicy(Qt::NoFocus);
0293     return toolButton;
0294 }
0295 
0296 void
0297 PlayerWidget::loadConfig()
0298 {
0299     onPlayerVolumeChanged(VideoPlayer::instance()->volume());
0300 }
0301 
0302 void
0303 PlayerWidget::saveConfig()
0304 {}
0305 
0306 void
0307 PlayerWidget::setFullScreenMode(bool fullScreenMode)
0308 {
0309     if(m_fullScreenMode == fullScreenMode)
0310         return;
0311 
0312     m_fullScreenMode = fullScreenMode;
0313 
0314     if(m_fullScreenMode) {
0315         window()->hide();
0316 
0317         // Move m_layeredWidget to a temporary widget which will be
0318         // displayed in full screen mode.
0319         // Can not call showFullScreen() on m_layeredWidget directly
0320         // because restoring the previous state is buggy under
0321         // some desktop environments / window managers.
0322 
0323         auto *fullScreenWidget = new QWidget();
0324         fullScreenWidget->installEventFilter(this);
0325         auto *fullScreenLayout = new QHBoxLayout();
0326         fullScreenLayout->setContentsMargins(0, 0, 0, 0);
0327         fullScreenWidget->setLayout(fullScreenLayout);
0328         m_layeredWidget->setParent(fullScreenWidget);
0329         fullScreenLayout->addWidget(m_layeredWidget);
0330         fullScreenWidget->showFullScreen();
0331 
0332         m_layeredWidget->unsetCursor();
0333         m_layeredWidget->setMouseTracking(true);
0334         m_fullScreenControls->attach(m_layeredWidget);
0335 
0336         m_fullScreenTID = startTimer(HIDE_MOUSE_MSECS);
0337 
0338         VideoPlayer::instance()->subtitleOverlay().setBottomPadding(m_fullScreenControls->height());
0339     } else {
0340         if(m_fullScreenTID) {
0341             killTimer(m_fullScreenTID);
0342             m_fullScreenTID = 0;
0343         }
0344 
0345         m_fullScreenControls->dettach();
0346         m_layeredWidget->setMouseTracking(false);
0347         m_layeredWidget->unsetCursor();
0348 
0349         // delete temporary parent widget later and set this as parent again
0350         m_layeredWidget->parent()->deleteLater();
0351         m_layeredWidget->setParent(this);
0352 
0353         m_mainLayout->addWidget(m_layeredWidget, 0, 1);
0354 
0355         VideoPlayer::instance()->subtitleOverlay().setBottomPadding(0);
0356 
0357         window()->show();
0358     }
0359 }
0360 
0361 void
0362 PlayerWidget::timerEvent(QTimerEvent *event)
0363 {
0364     Q_UNUSED(event);
0365     if(m_currentCursorPos != m_savedCursorPos) {
0366         m_savedCursorPos = m_currentCursorPos;
0367     } else if(!m_fullScreenControls->underMouse()) {
0368         if(m_layeredWidget->cursor().shape() != Qt::BlankCursor)
0369             m_layeredWidget->setCursor(QCursor(Qt::BlankCursor));
0370         if(m_fullScreenControls->isAttached())
0371             m_fullScreenControls->toggleVisible(false);
0372     }
0373 }
0374 
0375 bool
0376 PlayerWidget::eventFilter(QObject *object, QEvent *event)
0377 {
0378     if(object == m_layeredWidget) {
0379         switch(event->type()) {
0380         case QEvent::DragEnter:
0381         case QEvent::Drop:
0382             foreach(const QUrl &url, static_cast<QDropEvent *>(event)->mimeData()->urls()) {
0383                 if(url.scheme() == QLatin1String("file")) {
0384                     event->accept();
0385                     if(event->type() == QEvent::Drop)
0386                         app()->openVideo(url);
0387                     return true; // eat event
0388                 }
0389             }
0390             event->ignore();
0391             return true;
0392 
0393         case QEvent::DragMove:
0394             return true; // eat event
0395 
0396         case QEvent::KeyPress: {
0397             // NOTE: when on full screen mode, the keyboard input is received but
0398             // for some reason it doesn't trigger the correct actions automatically
0399             // so we process the event and handle the issue ourselves.
0400             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0401             if(m_fullScreenMode && keyEvent->key() == Qt::Key_Escape) {
0402                 app()->action(ACT_TOGGLE_FULL_SCREEN)->trigger();
0403                 return true;
0404             }
0405             return app()->triggerAction(QKeySequence((keyEvent->modifiers() & ~Qt::KeypadModifier) | keyEvent->key()));
0406         }
0407 
0408         case QEvent::MouseMove: {
0409             QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
0410 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0411             if(mouseEvent->globalPos() != m_currentCursorPos) {
0412                 m_currentCursorPos = mouseEvent->globalPos();
0413 #else
0414             if(mouseEvent->globalPosition() != m_currentCursorPos) {
0415                 m_currentCursorPos = mouseEvent->globalPosition();
0416 #endif
0417                 if(m_layeredWidget->cursor().shape() == Qt::BlankCursor)
0418                     m_layeredWidget->unsetCursor();
0419                 if(m_fullScreenControls->isAttached())
0420                     m_fullScreenControls->toggleVisible(true);
0421             }
0422             break;
0423         }
0424 
0425         default:
0426             break;
0427         }
0428     } else if(object == m_infoControlsGroupBox || object->parent() == m_infoControlsGroupBox) {
0429         if(event->type() != QEvent::MouseButtonRelease)
0430             return false;
0431 
0432         QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
0433 
0434         if(mouseEvent->button() != Qt::RightButton)
0435             return false;
0436 
0437         QMenu menu;
0438         QAction *action = menu.addAction(i18n("Show editable position control"));
0439         action->setCheckable(true);
0440         action->setChecked(SCConfig::showPositionTimeEdit());
0441 
0442 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0443         if(menu.exec(mouseEvent->globalPos()) == action)
0444 #else
0445         if(menu.exec(mouseEvent->globalPosition().toPoint()) == action)
0446 #endif
0447             SCConfig::setShowPositionTimeEdit(!SCConfig::showPositionTimeEdit());
0448 
0449         return true; // eat event
0450     } else if(m_fullScreenMode && object == m_layeredWidget->parentWidget() && event->type() == QEvent::Close) {
0451         app()->action(ACT_TOGGLE_FULL_SCREEN)->trigger();
0452         event->ignore();
0453         return true; // eat event
0454     }
0455 
0456     return false;
0457 }
0458 
0459 void
0460 PlayerWidget::setSubtitle(Subtitle *subtitle)
0461 {
0462     if(m_subtitle) {
0463         disconnect(m_subtitle.constData(), &Subtitle::linesInserted, this, &PlayerWidget::setPlayingLineFromVideo);
0464         disconnect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &PlayerWidget::setPlayingLineFromVideo);
0465 
0466         m_subtitle = nullptr;
0467 
0468         setPlayingLine(nullptr);
0469     }
0470 
0471     m_subtitle = subtitle;
0472 
0473     if(m_subtitle) {
0474         connect(m_subtitle.constData(), &Subtitle::linesInserted, this, &PlayerWidget::setPlayingLineFromVideo);
0475         connect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &PlayerWidget::setPlayingLineFromVideo);
0476     }
0477 }
0478 
0479 void
0480 PlayerWidget::setTranslationMode(bool enabled)
0481 {
0482     m_translationMode = enabled;
0483 
0484     if(!m_translationMode)
0485         setShowTranslation(false);
0486 }
0487 
0488 void
0489 PlayerWidget::setShowTranslation(bool showTranslation)
0490 {
0491     if(m_showTranslation == showTranslation)
0492         return;
0493 
0494     m_showTranslation = showTranslation;
0495 
0496     setPlayingLine(nullptr);
0497     setPlayingLineFromVideo();
0498 }
0499 
0500 void
0501 PlayerWidget::increaseFontSize(int size)
0502 {
0503     SCConfig::setFontSize(SCConfig::fontSize() + size);
0504     VideoPlayer::instance()->subtitleOverlay().setFontSize(SCConfig::fontSize());
0505 }
0506 
0507 void
0508 PlayerWidget::decreaseFontSize(int size)
0509 {
0510     SCConfig::setFontSize(SCConfig::fontSize() - size);
0511     VideoPlayer::instance()->subtitleOverlay().setFontSize(SCConfig::fontSize());
0512 }
0513 
0514 void
0515 PlayerWidget::updatePlayingLine(const Time &videoPosition)
0516 {
0517     if(!m_subtitle || m_subtitle->isEmpty()) {
0518         setPlayingLine(nullptr);
0519         return;
0520     }
0521 
0522     if(m_playingLine && m_playingLine->containsTime(videoPosition))
0523         return; // playing line is still valid
0524 
0525     SubtitleLine *firstLine = m_subtitle->firstLine();
0526     SubtitleLine *lastLine = m_subtitle->lastLine();
0527     bool pnValid = true;
0528     if(m_prevLine ? videoPosition < m_prevLine->showTime() : m_nextLine != firstLine)
0529         pnValid = false;
0530     else if(m_nextLine ? videoPosition > m_nextLine->hideTime() : m_prevLine != lastLine)
0531         pnValid = false;
0532 
0533     if(!pnValid) {
0534         // prev/next line are invalid
0535         m_prevLine = m_nextLine = nullptr;
0536         int first = 0;
0537         int last = m_subtitle->lastIndex();
0538         if(videoPosition < firstLine->showTime()) {
0539             m_nextLine = firstLine;
0540         } else if(videoPosition > lastLine->hideTime()) {
0541             m_prevLine = lastLine;
0542         } else {
0543             while(last - first > 1) {
0544                 // log2 search lines
0545                 int mid = (first + last) / 2;
0546                 SubtitleLine *line = m_subtitle->at(mid);
0547                 if(videoPosition <= line->hideTime())
0548                     last = mid;
0549                 if(videoPosition >= line->showTime())
0550                     first = mid;
0551             }
0552             m_prevLine = m_subtitle->at(first);
0553             m_nextLine = m_subtitle->at(last);
0554         }
0555     }
0556 
0557     if(m_prevLine && m_prevLine->containsTime(videoPosition))
0558         setPlayingLine(m_prevLine);
0559     else if(m_nextLine && m_nextLine->containsTime(videoPosition))
0560         setPlayingLine(m_nextLine);
0561     else
0562         setPlayingLine(nullptr);
0563 }
0564 
0565 void
0566 PlayerWidget::pauseAfterPlayingLine(const SubtitleLine *line)
0567 {
0568     m_pauseAfterPlayingLine = line;
0569 }
0570 
0571 void
0572 PlayerWidget::setPlayingLineFromVideo()
0573 {
0574     updatePlayingLine(VideoPlayer::instance()->position() * 1000.);
0575 }
0576 
0577 void
0578 PlayerWidget::setPlayingLine(SubtitleLine *line)
0579 {
0580     if(m_subtitle && line && m_subtitle != line->subtitle())
0581         line = nullptr;
0582 
0583     if(m_playingLine == line)
0584         return;
0585 
0586     SubtitleTextOverlay &ovr = VideoPlayer::instance()->subtitleOverlay();
0587     if(m_playingLine) {
0588         disconnect(m_playingLine, &SubtitleLine::showTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
0589         disconnect(m_playingLine, &SubtitleLine::hideTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
0590         disconnect(m_playingLine, &SubtitleLine::positionChanged, &ovr, &SubtitleTextOverlay::forceRepaint);
0591     }
0592 
0593     emit playingLineChanged(m_playingLine = line);
0594 
0595     if(m_playingLine) {
0596         connect(m_playingLine, &SubtitleLine::showTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
0597         connect(m_playingLine, &SubtitleLine::hideTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
0598         connect(m_playingLine, &SubtitleLine::positionChanged, &ovr, &SubtitleTextOverlay::forceRepaint);
0599         ovr.setDoc(m_showTranslation ? m_playingLine->secondaryDoc() : m_playingLine->primaryDoc());
0600         ovr.setDocRect(&m_playingLine->pos());
0601     } else {
0602         ovr.setDoc(nullptr);
0603         ovr.setDocRect(nullptr);
0604     }
0605 }
0606 
0607 void
0608 PlayerWidget::updatePositionEditVisibility()
0609 {
0610     if(m_showPositionTimeEdit && VideoPlayer::instance()->state() >= VideoPlayer::Playing)
0611         m_positionEdit->show();
0612     else
0613         m_positionEdit->hide();
0614 }
0615 
0616 void
0617 PlayerWidget::onVolumeSliderMoved(int value)
0618 {
0619     VideoPlayer::instance()->setVolume(value);
0620 }
0621 
0622 void
0623 PlayerWidget::onSeekSliderMoved(int value)
0624 {
0625     VideoPlayer *videoPlayer = VideoPlayer::instance();
0626     pauseAfterPlayingLine(nullptr);
0627     videoPlayer->seek(videoPlayer->duration() * value / 1000.0);
0628 
0629     Time time((long)(videoPlayer->duration() * value));
0630 
0631     m_positionLabel->setText(time.toString());
0632     m_fsPositionLabel->setText(time.toString(false) + m_lengthString);
0633 
0634     if(m_showPositionTimeEdit)
0635         m_positionEdit->setValue(time.toMillis());
0636 }
0637 
0638 void
0639 PlayerWidget::onPositionEditValueChanged(int position)
0640 {
0641     if(m_positionEdit->hasFocus()) {
0642         pauseAfterPlayingLine(nullptr);
0643         VideoPlayer::instance()->seek(position / 1000.0);
0644     }
0645 }
0646 
0647 void
0648 PlayerWidget::onConfigChanged()
0649 {
0650     if(m_showPositionTimeEdit != SCConfig::showPositionTimeEdit()) {
0651         m_showPositionTimeEdit = SCConfig::showPositionTimeEdit();
0652         updatePositionEditVisibility();
0653     }
0654 
0655     SubtitleTextOverlay &subtitleOverlay = VideoPlayer::instance()->subtitleOverlay();
0656     subtitleOverlay.setTextColor(SCConfig::fontColor());
0657     subtitleOverlay.setFontFamily(SCConfig::fontFamily());
0658     subtitleOverlay.setFontSize(SCConfig::fontSize());
0659     subtitleOverlay.setOutlineColor(SCConfig::outlineColor());
0660     subtitleOverlay.setOutlineWidth(SCConfig::outlineWidth());
0661 }
0662 
0663 void
0664 PlayerWidget::onPlayerFileOpened(const QString & /*filePath */)
0665 {
0666     m_infoControlsGroupBox->setEnabled(true);
0667 
0668     updatePositionEditVisibility();
0669 }
0670 
0671 void
0672 PlayerWidget::onPlayerFileOpenError(const QString &filePath, const QString &reason)
0673 {
0674     QString message = i18n("<qt>There was an error opening media file %1.</qt>", filePath);
0675     if(!reason.isEmpty())
0676         message += "\n" + reason;
0677     KMessageBox::error(this, message);
0678 }
0679 
0680 void
0681 PlayerWidget::onPlayerFileClosed()
0682 {
0683     setPlayingLine(nullptr);
0684 
0685     m_infoControlsGroupBox->setEnabled(false);
0686 
0687     updatePositionEditVisibility();
0688     m_positionEdit->setValue(0);
0689 
0690     m_positionLabel->setText(i18n("<i>Unknown</i>"));
0691     m_lengthLabel->setText(i18n("<i>Unknown</i>"));
0692     m_fpsLabel->setText(i18n("<i>Unknown</i>"));
0693     m_rateLabel->setText(i18n("<i>Unknown</i>"));
0694 
0695     m_lengthString = UNKNOWN_LENGTH_STRING;
0696     m_fsPositionLabel->setText(Time().toString(false) + m_lengthString);
0697 
0698     m_seekSlider->setEnabled(false);
0699     m_fsSeekSlider->setEnabled(false);
0700 }
0701 
0702 void
0703 PlayerWidget::onPlayerPlaybackError(const QString &errorMessage)
0704 {
0705     if(errorMessage.isEmpty())
0706         KMessageBox::error(this, i18n("Unexpected error when playing file."), i18n("Error Playing File"));
0707     else
0708         KMessageBox::detailedError(this, i18n("Unexpected error when playing file."), errorMessage, i18n("Error Playing File"));
0709 }
0710 
0711 void
0712 PlayerWidget::onPlayerPlaying()
0713 {
0714     m_seekSlider->setEnabled(true);
0715     m_fsSeekSlider->setEnabled(true);
0716 
0717     updatePositionEditVisibility();
0718 }
0719 
0720 void
0721 PlayerWidget::onPlayerPositionChanged(double seconds)
0722 {
0723     const Time videoPosition(seconds * 1000.);
0724 
0725     // pause if requested
0726     if(m_pauseAfterPlayingLine) {
0727         const Time &pauseTime = m_pauseAfterPlayingLine->hideTime();
0728         if(videoPosition >= pauseTime) {
0729             VideoPlayer *videoPlayer = VideoPlayer::instance();
0730             m_pauseAfterPlayingLine = nullptr;
0731             videoPlayer->pause();
0732             videoPlayer->seek(pauseTime.toSeconds());
0733             return;
0734         }
0735     }
0736 
0737     m_positionLabel->setText(videoPosition.toString());
0738     m_fsPositionLabel->setText(videoPosition.toString(false) + m_lengthString);
0739 
0740     if(m_showPositionTimeEdit && !m_positionEdit->hasFocus()) {
0741         QSignalBlocker sb(m_positionEdit);
0742         m_positionEdit->setValue(videoPosition.toMillis());
0743     }
0744 
0745     updatePlayingLine(videoPosition);
0746 
0747     QSignalBlocker s1(m_seekSlider), s2(m_fsSeekSlider);
0748     const int sliderValue = int((seconds / VideoPlayer::instance()->duration()) * 1000.0);
0749     m_seekSlider->setValue(sliderValue);
0750     m_fsSeekSlider->setValue(sliderValue);
0751 }
0752 
0753 void
0754 PlayerWidget::onPlayerLengthChanged(double seconds)
0755 {
0756     if(seconds > 0) {
0757         m_lengthLabel->setText(Time((long)(seconds * 1000)).toString());
0758         m_lengthString = " / " + m_lengthLabel->text().left(8) + ' ';
0759     } else {
0760         m_lengthLabel->setText(i18n("<i>Unknown</i>"));
0761         m_lengthString = UNKNOWN_LENGTH_STRING;
0762     }
0763 }
0764 
0765 void
0766 PlayerWidget::onPlayerFramesPerSecondChanged(double fps)
0767 {
0768     m_fpsLabel->setText(fps > 0 ? QString::number(fps, 'f', 3) : i18n("<i>Unknown</i>"));
0769 }
0770 
0771 void
0772 PlayerWidget::onPlayerPlaybackRateChanged(double rate)
0773 {
0774     m_rateLabel->setText(rate > .0 ? QStringLiteral("%1x").arg(rate, 0, 'g', 3) : i18n("<i>Unknown</i>"));
0775 }
0776 
0777 void
0778 PlayerWidget::onPlayerStopped()
0779 {
0780     onPlayerPositionChanged(0);
0781 
0782     m_seekSlider->setEnabled(false);
0783     m_fsSeekSlider->setEnabled(false);
0784 
0785     setPlayingLine(nullptr);
0786 
0787     updatePositionEditVisibility();
0788 }
0789 
0790 void
0791 PlayerWidget::onPlayerVolumeChanged(double volume)
0792 {
0793     QSignalBlocker s1(m_volumeSlider), s2(m_fsVolumeSlider);
0794     m_volumeSlider->setValue(int(volume + 0.5));
0795     m_fsVolumeSlider->setValue(int(volume + 0.5));
0796 }
0797 
0798 void
0799 PlayerWidget::onPlayerLeftClicked(const QPointF &point)
0800 {
0801     Q_UNUSED(point);
0802     VideoPlayer::instance()->togglePlayPaused();
0803 }
0804 
0805 void
0806 PlayerWidget::onPlayerRightClicked(const QPointF &point)
0807 {
0808     static QMenu *menu = new QMenu(this);
0809 
0810     menu->clear();
0811 
0812     menu->addAction(app()->action(ACT_OPEN_VIDEO));
0813     menu->addAction(app()->action(ACT_CLOSE_VIDEO));
0814 
0815     menu->addSeparator();
0816 
0817     menu->addAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
0818 
0819     menu->addSeparator();
0820 
0821     menu->addAction(app()->action(ACT_STOP));
0822     menu->addAction(app()->action(ACT_PLAY_PAUSE));
0823     menu->addAction(app()->action(ACT_SEEK_BACKWARD));
0824     menu->addAction(app()->action(ACT_SEEK_FORWARD));
0825 
0826     menu->addSeparator();
0827 
0828     menu->addAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
0829     menu->addAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
0830 
0831     menu->addSeparator();
0832 
0833     menu->addAction(app()->action(ACT_PLAY_RATE_DECREASE));
0834     menu->addAction(app()->action(ACT_PLAY_RATE_INCREASE));
0835 
0836     menu->addSeparator();
0837 
0838     menu->addAction(app()->action(ACT_SET_ACTIVE_AUDIO_STREAM));
0839     menu->addAction(app()->action(ACT_INCREASE_VOLUME));
0840     menu->addAction(app()->action(ACT_DECREASE_VOLUME));
0841     menu->addAction(app()->action(ACT_TOGGLE_MUTED));
0842 
0843     menu->addSeparator();
0844 
0845     if(m_translationMode)
0846         menu->addAction(app()->action(ACT_SET_ACTIVE_SUBTITLE_STREAM));
0847 
0848     menu->addAction(app()->action(ACT_INCREASE_SUBTITLE_FONT));
0849     menu->addAction(app()->action(ACT_DECREASE_SUBTITLE_FONT));
0850 
0851     // NOTE do not use popup->exec() here!!! it freezes the application
0852     // when using the mplayer backend. i think it's related to the fact
0853     // that exec() creates a different event loop and the mplayer backend
0854     // depends on the main loop for catching synchronization signals
0855     menu->popup(point.toPoint());
0856 }
0857 
0858 void
0859 PlayerWidget::onPlayerDoubleClicked(const QPointF &point)
0860 {
0861     Q_UNUSED(point);
0862     app()->toggleFullScreenMode();
0863 }