1 /*
2 SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
3 SPDX-FileCopyrightText: 2010-2019 Mladen Milinkovic <max@smoothware.net>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "playerwidget.h"
9 #include "application.h"
10 #include "actions/useractionnames.h"
11 #include "helpers/commondefs.h"
12 #include "core/richdocument.h"
13 #include "core/subtitleiterator.h"
14 #include "videoplayer/videoplayer.h"
15 #include "widgets/layeredwidget.h"
16 #include "widgets/textoverlaywidget.h"
17 #include "widgets/attachablewidget.h"
18 #include "widgets/pointingslider.h"
19 #include "widgets/timeedit.h"
20
21 #include <QEvent>
22 #include <QDropEvent>
23 #include <QMimeData>
24 #include <QKeyEvent>
25
26 #include <QMenu>
27 #include <QPushButton>
28 #include <QDesktopWidget>
29 #include <QCursor>
30 #include <QLabel>
31 #include <QToolButton>
32 #include <QGroupBox>
33 #include <QGridLayout>
34
35 #include <KConfigGroup>
36 #include <KMessageBox>
37 #include <KLocalizedString>
38
39 using namespace SubtitleComposer;
40
41 #define HIDE_MOUSE_MSECS 1000
42 #define UNKNOWN_LENGTH_STRING (" / " + Time().toString(false) + ' ')
43
PlayerWidget(QWidget * parent)44 PlayerWidget::PlayerWidget(QWidget *parent) :
45 QWidget(parent),
46 m_subtitle(0),
47 m_translationMode(false),
48 m_showTranslation(false),
49 m_pauseAfterPlayingLine(nullptr),
50 m_fullScreenTID(0),
51 m_fullScreenMode(false),
52 m_lengthString(UNKNOWN_LENGTH_STRING),
53 m_showPositionTimeEdit(SCConfig::showPositionTimeEdit())
54 {
55 m_layeredWidget = new LayeredWidget(this);
56 m_layeredWidget->setAcceptDrops(true);
57 m_layeredWidget->installEventFilter(this);
58 m_layeredWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
59
60 m_seekSlider = new PointingSlider(Qt::Horizontal, this);
61 m_seekSlider->setTickPosition(QSlider::NoTicks);
62 m_seekSlider->setMinimum(0);
63 m_seekSlider->setMaximum(1000);
64 m_seekSlider->setPageStep(10);
65 m_seekSlider->setFocusPolicy(Qt::NoFocus);
66
67 m_infoControlsGroupBox = new QWidget(this);
68 m_infoControlsGroupBox->setAcceptDrops(true);
69 m_infoControlsGroupBox->installEventFilter(this);
70
71 QLabel *positionTagLabel = new QLabel(m_infoControlsGroupBox);
72 positionTagLabel->setText(i18n("<b>Position</b>"));
73 positionTagLabel->installEventFilter(this);
74
75 m_positionLabel = new QLabel(m_infoControlsGroupBox);
76 m_positionEdit = new TimeEdit(m_infoControlsGroupBox);
77 m_positionEdit->setFocusPolicy(Qt::NoFocus);
78
79 QLabel *lengthTagLabel = new QLabel(m_infoControlsGroupBox);
80 lengthTagLabel->setText(i18n("<b>Length</b>"));
81 lengthTagLabel->installEventFilter(this);
82 m_lengthLabel = new QLabel(m_infoControlsGroupBox);
83
84 QLabel *fpsTagLabel = new QLabel(m_infoControlsGroupBox);
85 fpsTagLabel->setText(i18n("<b>FPS</b>"));
86 fpsTagLabel->installEventFilter(this);
87 m_fpsLabel = new QLabel(m_infoControlsGroupBox);
88 m_fpsLabel->setMinimumWidth(m_positionEdit->sizeHint().width()); // sets the minimum width for the whole group
89
90 QLabel *rateTagLabel = new QLabel(m_infoControlsGroupBox);
91 rateTagLabel->setText(i18n("<b>Playback Rate</b>"));
92 rateTagLabel->installEventFilter(this);
93 m_rateLabel = new QLabel(m_infoControlsGroupBox);
94
95 m_volumeSlider = new PointingSlider(Qt::Vertical, this);
96 m_volumeSlider->setFocusPolicy(Qt::NoFocus);
97 m_volumeSlider->setTickPosition(QSlider::NoTicks);
98 m_volumeSlider->setPageStep(5);
99 m_volumeSlider->setMinimum(0);
100 m_volumeSlider->setMaximum(100);
101 m_volumeSlider->setFocusPolicy(Qt::NoFocus);
102
103 QGridLayout *videoControlsLayout = new QGridLayout();
104 videoControlsLayout->setMargin(0);
105 videoControlsLayout->setSpacing(2);
106 videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_PAUSE, 16), 0, 0);
107 videoControlsLayout->addWidget(createToolButton(this, ACT_STOP, 16), 0, 1);
108 videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_BACKWARD, 16), 0, 2);
109 videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_FORWARD, 16), 0, 3);
110 videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 4);
111 videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_TO_PREVIOUS_LINE, 16), 0, 5);
112 videoControlsLayout->addWidget(createToolButton(this, ACT_SEEK_TO_NEXT_LINE, 16), 0, 6);
113 videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 7);
114 videoControlsLayout->addWidget(createToolButton(this, ACT_SET_CURRENT_LINE_SHOW_TIME, 16), 0, 8);
115 videoControlsLayout->addWidget(createToolButton(this, ACT_SET_CURRENT_LINE_HIDE_TIME, 16), 0, 9);
116 videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 10);
117 videoControlsLayout->addWidget(createToolButton(this, ACT_CURRENT_LINE_FOLLOWS_VIDEO, 16), 0, 11);
118 videoControlsLayout->addItem(new QSpacerItem(2, 2), 0, 12);
119 videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_RATE_DECREASE, 16), 0, 13);
120 videoControlsLayout->addWidget(createToolButton(this, ACT_PLAY_RATE_INCREASE, 16), 0, 14);
121 videoControlsLayout->addWidget(m_seekSlider, 0, 15);
122
123 QGridLayout *audioControlsLayout = new QGridLayout();
124 audioControlsLayout->setMargin(0);
125 audioControlsLayout->addWidget(createToolButton(this, ACT_TOGGLE_MUTED, 16), 0, 0, Qt::AlignHCenter);
126 audioControlsLayout->addWidget(m_volumeSlider, 1, 0, Qt::AlignHCenter);
127
128 QGridLayout *infoControlsLayout = new QGridLayout(m_infoControlsGroupBox);
129 infoControlsLayout->setSpacing(5);
130 infoControlsLayout->addWidget(fpsTagLabel, 0, 0);
131 infoControlsLayout->addWidget(m_fpsLabel, 1, 0);
132 infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 2, 0);
133 infoControlsLayout->addWidget(rateTagLabel, 3, 0);
134 infoControlsLayout->addWidget(m_rateLabel, 4, 0);
135 infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 5, 0);
136 infoControlsLayout->addWidget(lengthTagLabel, 6, 0);
137 infoControlsLayout->addWidget(m_lengthLabel, 7, 0);
138 infoControlsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 8, 0);
139 infoControlsLayout->addWidget(positionTagLabel, 9, 0);
140 infoControlsLayout->addWidget(m_positionLabel, 10, 0);
141 infoControlsLayout->addWidget(m_positionEdit, 11, 0);
142
143 m_mainLayout = new QGridLayout(this);
144 m_mainLayout->setMargin(0);
145 m_mainLayout->setSpacing(5);
146 m_mainLayout->addWidget(m_infoControlsGroupBox, 0, 0, 2, 1);
147 m_mainLayout->addWidget(m_layeredWidget, 0, 1);
148 m_mainLayout->addLayout(audioControlsLayout, 0, 2);
149 m_mainLayout->addLayout(videoControlsLayout, 1, 1);
150 m_mainLayout->addWidget(createToolButton(this, ACT_TOGGLE_FULL_SCREEN, 16), 1, 2);
151
152 m_fullScreenControls = new AttachableWidget(AttachableWidget::Bottom, 4);
153 m_fullScreenControls->setAutoFillBackground(true);
154 m_layeredWidget->setWidgetMode(m_fullScreenControls, LayeredWidget::IgnoreResize);
155
156 m_fsSeekSlider = new PointingSlider(Qt::Horizontal, m_fullScreenControls);
157 m_fsSeekSlider->setTickPosition(QSlider::NoTicks);
158 m_fsSeekSlider->setMinimum(0);
159 m_fsSeekSlider->setMaximum(1000);
160 m_fsSeekSlider->setPageStep(10);
161
162 m_fsVolumeSlider = new PointingSlider(Qt::Horizontal, m_fullScreenControls);
163 m_fsVolumeSlider->setFocusPolicy(Qt::NoFocus);
164 m_fsVolumeSlider->setTickPosition(QSlider::NoTicks);
165 m_fsVolumeSlider->setPageStep(5);
166 m_fsVolumeSlider->setMinimum(0);
167 m_fsVolumeSlider->setMaximum(100);
168
169 m_fsPositionLabel = new QLabel(m_fullScreenControls);
170 QPalette fsPositionPalette;
171 fsPositionPalette.setColor(m_fsPositionLabel->backgroundRole(), Qt::black);
172 fsPositionPalette.setColor(m_fsPositionLabel->foregroundRole(), Qt::white);
173 m_fsPositionLabel->setPalette(fsPositionPalette);
174 m_fsPositionLabel->setAutoFillBackground(true);
175 m_fsPositionLabel->setFrameShape(QFrame::Panel);
176 m_fsPositionLabel->setText(Time().toString(false) + " / " + Time().toString(false));
177 m_fsPositionLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
178 m_fsPositionLabel->adjustSize();
179 m_fsPositionLabel->setMinimumWidth(m_fsPositionLabel->width());
180
181 QHBoxLayout *fullScreenControlsLayout = new QHBoxLayout(m_fullScreenControls);
182 fullScreenControlsLayout->setMargin(0);
183 fullScreenControlsLayout->setSpacing(0);
184
185 const int FS_BUTTON_SIZE = 32;
186 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_PAUSE, FS_BUTTON_SIZE));
187 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_STOP, FS_BUTTON_SIZE));
188 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_BACKWARD, FS_BUTTON_SIZE));
189 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_FORWARD, FS_BUTTON_SIZE));
190 fullScreenControlsLayout->addSpacing(3);
191 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_TO_PREVIOUS_LINE, FS_BUTTON_SIZE));
192 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_SEEK_TO_NEXT_LINE, FS_BUTTON_SIZE));
193 fullScreenControlsLayout->addSpacing(3);
194 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_RATE_DECREASE, FS_BUTTON_SIZE));
195 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_PLAY_RATE_INCREASE, FS_BUTTON_SIZE));
196 fullScreenControlsLayout->addSpacing(3);
197 fullScreenControlsLayout->addWidget(m_fsSeekSlider, 9);
198 fullScreenControlsLayout->addWidget(m_fsPositionLabel);
199 fullScreenControlsLayout->addWidget(m_fsVolumeSlider, 2);
200 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_TOGGLE_MUTED, FS_BUTTON_SIZE));
201 fullScreenControlsLayout->addWidget(createToolButton(m_fullScreenControls, ACT_TOGGLE_FULL_SCREEN, FS_BUTTON_SIZE));
202 m_fullScreenControls->adjustSize();
203
204 connect(m_volumeSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onVolumeSliderMoved);
205 connect(m_fsVolumeSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onVolumeSliderMoved);
206
207 connect(m_seekSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onSeekSliderMoved);
208 connect(m_fsSeekSlider, &QAbstractSlider::valueChanged, this, &PlayerWidget::onSeekSliderMoved);
209
210 connect(m_positionEdit, &TimeEdit::valueChanged, this, &PlayerWidget::onPositionEditValueChanged);
211 connect(m_positionEdit, &TimeEdit::valueEntered, this, &PlayerWidget::onPositionEditValueChanged);
212
213 connect(SCConfig::self(), &KCoreConfigSkeleton::configChanged, this, &PlayerWidget::onConfigChanged);
214
215 VideoPlayer *videoPlayer = VideoPlayer::instance();
216 videoPlayer->init(m_layeredWidget);
217
218 connect(videoPlayer, &VideoPlayer::fileOpened, this, &PlayerWidget::onPlayerFileOpened);
219 connect(videoPlayer, &VideoPlayer::fileOpenError, this, &PlayerWidget::onPlayerFileOpenError);
220 connect(videoPlayer, &VideoPlayer::fileClosed, this, &PlayerWidget::onPlayerFileClosed);
221 connect(videoPlayer, &VideoPlayer::playbackError, this, &PlayerWidget::onPlayerPlaybackError);
222 connect(videoPlayer, &VideoPlayer::playing, this, &PlayerWidget::onPlayerPlaying);
223 connect(videoPlayer, &VideoPlayer::stopped, this, &PlayerWidget::onPlayerStopped);
224 connect(videoPlayer, &VideoPlayer::positionChanged, this, &PlayerWidget::onPlayerPositionChanged);
225 connect(videoPlayer, &VideoPlayer::durationChanged, this, &PlayerWidget::onPlayerLengthChanged);
226 connect(videoPlayer, &VideoPlayer::fpsChanged, this, &PlayerWidget::onPlayerFramesPerSecondChanged);
227 connect(videoPlayer, &VideoPlayer::playSpeedChanged, this, &PlayerWidget::onPlayerPlaybackRateChanged);
228 connect(videoPlayer, &VideoPlayer::volumeChanged, this, &PlayerWidget::onPlayerVolumeChanged);
229 connect(videoPlayer, &VideoPlayer::muteChanged, m_fsVolumeSlider, &QWidget::setDisabled);
230 connect(videoPlayer, &VideoPlayer::muteChanged, m_volumeSlider, &QWidget::setDisabled);
231
232 connect(videoPlayer, &VideoPlayer::leftClicked, this, &PlayerWidget::onPlayerLeftClicked);
233 connect(videoPlayer, &VideoPlayer::rightClicked, this, &PlayerWidget::onPlayerRightClicked);
234 connect(videoPlayer, &VideoPlayer::doubleClicked, this, &PlayerWidget::onPlayerDoubleClicked);
235
236 onPlayerFileClosed();
237 onConfigChanged(); // initializes the font
238
239 setFullScreenMode(m_fullScreenMode);
240
241 connect(app(), &Application::actionsReady, [this](){
242 toolButton(this, ACT_STOP)->setDefaultAction(app()->action(ACT_STOP));
243 toolButton(this, ACT_PLAY_PAUSE)->setDefaultAction(app()->action(ACT_PLAY_PAUSE));
244 toolButton(this, ACT_SEEK_BACKWARD)->setDefaultAction(app()->action(ACT_SEEK_BACKWARD));
245 toolButton(this, ACT_SEEK_FORWARD)->setDefaultAction(app()->action(ACT_SEEK_FORWARD));
246 toolButton(this, ACT_SEEK_TO_PREVIOUS_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
247 toolButton(this, ACT_SEEK_TO_NEXT_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
248 toolButton(this, ACT_SET_CURRENT_LINE_SHOW_TIME)->setDefaultAction(app()->action(ACT_SET_CURRENT_LINE_SHOW_TIME));
249 toolButton(this, ACT_SET_CURRENT_LINE_HIDE_TIME)->setDefaultAction(app()->action(ACT_SET_CURRENT_LINE_HIDE_TIME));
250 toolButton(this, ACT_CURRENT_LINE_FOLLOWS_VIDEO)->setDefaultAction(app()->action(ACT_CURRENT_LINE_FOLLOWS_VIDEO));
251 toolButton(this, ACT_TOGGLE_MUTED)->setDefaultAction(app()->action(ACT_TOGGLE_MUTED));
252 toolButton(this, ACT_TOGGLE_FULL_SCREEN)->setDefaultAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
253 toolButton(this, ACT_PLAY_RATE_DECREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_DECREASE));
254 toolButton(this, ACT_PLAY_RATE_INCREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_INCREASE));
255
256 toolButton(m_fullScreenControls, ACT_STOP)->setDefaultAction(app()->action(ACT_STOP));
257 toolButton(m_fullScreenControls, ACT_PLAY_PAUSE)->setDefaultAction(app()->action(ACT_PLAY_PAUSE));
258 toolButton(m_fullScreenControls, ACT_SEEK_BACKWARD)->setDefaultAction(app()->action(ACT_SEEK_BACKWARD));
259 toolButton(m_fullScreenControls, ACT_SEEK_FORWARD)->setDefaultAction(app()->action(ACT_SEEK_FORWARD));
260 toolButton(m_fullScreenControls, ACT_SEEK_TO_PREVIOUS_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
261 toolButton(m_fullScreenControls, ACT_SEEK_TO_NEXT_LINE)->setDefaultAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
262 toolButton(m_fullScreenControls, ACT_TOGGLE_MUTED)->setDefaultAction(app()->action(ACT_TOGGLE_MUTED));
263 toolButton(m_fullScreenControls, ACT_TOGGLE_FULL_SCREEN)->setDefaultAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
264 toolButton(m_fullScreenControls, ACT_PLAY_RATE_DECREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_DECREASE));
265 toolButton(m_fullScreenControls, ACT_PLAY_RATE_INCREASE)->setDefaultAction(app()->action(ACT_PLAY_RATE_INCREASE));
266 });
267 }
268
~PlayerWidget()269 PlayerWidget::~PlayerWidget()
270 {
271 m_fullScreenControls->deleteLater();
272 }
273
274 QWidget *
infoSidebarWidget()275 PlayerWidget::infoSidebarWidget()
276 {
277 return m_infoControlsGroupBox;
278 }
279
280 QToolButton *
toolButton(QWidget * parent,const char * name)281 PlayerWidget::toolButton(QWidget *parent, const char *name)
282 {
283 return parent->findChild<QToolButton *>(name);
284 }
285
286 QToolButton *
createToolButton(QWidget * parent,const char * name,int size)287 PlayerWidget::createToolButton(QWidget *parent, const char *name, int size)
288 {
289 QToolButton *toolButton = new QToolButton(parent);
290 toolButton->setObjectName(name);
291 toolButton->setMinimumSize(size, size);
292 toolButton->setIconSize(size >= 32 ? QSize(size - 6, size - 6) : QSize(size, size));
293 toolButton->setAutoRaise(true);
294 toolButton->setFocusPolicy(Qt::NoFocus);
295 return toolButton;
296 }
297
298 void
loadConfig()299 PlayerWidget::loadConfig()
300 {
301 onPlayerVolumeChanged(VideoPlayer::instance()->volume());
302 }
303
304 void
saveConfig()305 PlayerWidget::saveConfig()
306 {}
307
308 void
setFullScreenMode(bool fullScreenMode)309 PlayerWidget::setFullScreenMode(bool fullScreenMode)
310 {
311 VideoPlayer::instance()->subtitleOverlay().setRenderScale(fullScreenMode ? 1.0 : 1.2);
312
313 if(m_fullScreenMode == fullScreenMode)
314 return;
315
316 m_fullScreenMode = fullScreenMode;
317
318 if(m_fullScreenMode) {
319 window()->hide();
320
321 // Move m_layeredWidget to a temporary widget which will be
322 // displayed in full screen mode.
323 // Can not call showFullScreen() on m_layeredWidget directly
324 // because restoring the previous state is buggy under
325 // some desktop environments / window managers.
326
327 auto *fullScreenWidget = new QWidget;
328 auto *fullScreenLayout = new QHBoxLayout;
329 fullScreenLayout->setMargin(0);
330 fullScreenWidget->setLayout(fullScreenLayout);
331 m_layeredWidget->setParent(fullScreenWidget);
332 fullScreenLayout->addWidget(m_layeredWidget);
333 fullScreenWidget->showFullScreen();
334
335 m_layeredWidget->unsetCursor();
336 m_layeredWidget->setMouseTracking(true);
337 m_fullScreenControls->attach(m_layeredWidget);
338
339 m_fullScreenTID = startTimer(HIDE_MOUSE_MSECS);
340 } else {
341 if(m_fullScreenTID) {
342 killTimer(m_fullScreenTID);
343 m_fullScreenTID = 0;
344 }
345
346 m_fullScreenControls->dettach();
347 m_layeredWidget->setMouseTracking(false);
348 m_layeredWidget->unsetCursor();
349
350 // delete temporary parent widget later and set this as parent again
351 m_layeredWidget->parent()->deleteLater();
352 m_layeredWidget->setParent(this);
353
354 m_mainLayout->addWidget(m_layeredWidget, 0, 1);
355
356 window()->show();
357 }
358 }
359
360 void
timerEvent(QTimerEvent *)361 PlayerWidget::timerEvent(QTimerEvent * /*event */)
362 {
363 if(m_currentCursorPos != m_savedCursorPos) {
364 m_savedCursorPos = m_currentCursorPos;
365 } else if(!m_fullScreenControls->underMouse()) {
366 if(m_layeredWidget->cursor().shape() != Qt::BlankCursor)
367 m_layeredWidget->setCursor(QCursor(Qt::BlankCursor));
368 if(m_fullScreenControls->isAttached())
369 m_fullScreenControls->toggleVisible(false);
370 }
371 }
372
373 bool
eventFilter(QObject * object,QEvent * event)374 PlayerWidget::eventFilter(QObject *object, QEvent *event)
375 {
376 if(object == m_layeredWidget) {
377 switch(event->type()) {
378 case QEvent::DragEnter:
379 case QEvent::Drop:
380 foreach(const QUrl &url, static_cast<QDropEvent *>(event)->mimeData()->urls()) {
381 if(url.scheme() == QLatin1String("file")) {
382 event->accept();
383 if(event->type() == QEvent::Drop) {
384 app()->openVideo(url);
385 }
386 return true; // eat event
387 }
388 }
389 event->ignore();
390 return true;
391
392 case QEvent::DragMove:
393 return true; // eat event
394
395 case QEvent::KeyPress: {
396 // NOTE: when on full screen mode, the keyboard input is received but
397 // for some reason it doesn't trigger the correct actions automatically
398 // so we process the event and handle the issue ourselves.
399 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
400 if(m_fullScreenMode && keyEvent->key() == Qt::Key_Escape) {
401 app()->action(ACT_TOGGLE_FULL_SCREEN)->trigger();
402 return true;
403 }
404 return app()->triggerAction(QKeySequence((keyEvent->modifiers() & ~Qt::KeypadModifier) + keyEvent->key()));
405 }
406
407 case QEvent::MouseMove: {
408 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
409 if(mouseEvent->globalPos() != m_currentCursorPos) {
410 m_currentCursorPos = mouseEvent->globalPos();
411 if(m_layeredWidget->cursor().shape() == Qt::BlankCursor)
412 m_layeredWidget->unsetCursor();
413 if(m_fullScreenControls->isAttached())
414 m_fullScreenControls->toggleVisible(true);
415 }
416 break;
417 }
418
419 default:
420 ;
421 }
422
423 } else if(object == m_infoControlsGroupBox || object->parent() == m_infoControlsGroupBox) {
424 if(event->type() != QEvent::MouseButtonRelease)
425 return false;
426
427 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
428
429 if(mouseEvent->button() != Qt::RightButton)
430 return false;
431
432 QMenu menu;
433 QAction *action = menu.addAction(i18n("Show editable position control"));
434 action->setCheckable(true);
435 action->setChecked(SCConfig::showPositionTimeEdit());
436
437 if(menu.exec(mouseEvent->globalPos()) == action)
438 SCConfig::setShowPositionTimeEdit(!SCConfig::showPositionTimeEdit());
439
440 return true; // eat event
441 }
442
443 return false;
444 }
445
446 void
setSubtitle(Subtitle * subtitle)447 PlayerWidget::setSubtitle(Subtitle *subtitle)
448 {
449 if(m_subtitle) {
450 disconnect(m_subtitle.constData(), &Subtitle::linesInserted, this, &PlayerWidget::setPlayingLineFromVideo);
451 disconnect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &PlayerWidget::setPlayingLineFromVideo);
452
453 m_subtitle = nullptr;
454
455 setPlayingLine(nullptr);
456 }
457
458 m_subtitle = subtitle;
459
460 if(m_subtitle) {
461 connect(m_subtitle.constData(), &Subtitle::linesInserted, this, &PlayerWidget::setPlayingLineFromVideo);
462 connect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &PlayerWidget::setPlayingLineFromVideo);
463 }
464 }
465
466 void
setTranslationMode(bool enabled)467 PlayerWidget::setTranslationMode(bool enabled)
468 {
469 m_translationMode = enabled;
470
471 if(!m_translationMode)
472 setShowTranslation(false);
473 }
474
475 void
setShowTranslation(bool showTranslation)476 PlayerWidget::setShowTranslation(bool showTranslation)
477 {
478 if(m_showTranslation == showTranslation)
479 return;
480
481 m_showTranslation = showTranslation;
482
483 setPlayingLine(nullptr);
484 setPlayingLineFromVideo();
485 }
486
487 void
increaseFontSize(int size)488 PlayerWidget::increaseFontSize(int size)
489 {
490 SCConfig::setFontSize(SCConfig::fontSize() + size);
491 VideoPlayer::instance()->subtitleOverlay().setFontSize(SCConfig::fontSize());
492 }
493
494 void
decreaseFontSize(int size)495 PlayerWidget::decreaseFontSize(int size)
496 {
497 SCConfig::setFontSize(SCConfig::fontSize() - size);
498 VideoPlayer::instance()->subtitleOverlay().setFontSize(SCConfig::fontSize());
499 }
500
501 void
updatePlayingLine(const Time & videoPosition)502 PlayerWidget::updatePlayingLine(const Time &videoPosition)
503 {
504 if(!m_subtitle || m_subtitle->isEmpty()) {
505 setPlayingLine(nullptr);
506 return;
507 }
508
509 if(m_playingLine && m_playingLine->containsTime(videoPosition))
510 return; // playing line is still valid
511
512 SubtitleLine *firstLine = m_subtitle->firstLine();
513 SubtitleLine *lastLine = m_subtitle->lastLine();
514 bool pnValid = true;
515 if(m_prevLine ? videoPosition < m_prevLine->showTime() : m_nextLine != firstLine)
516 pnValid = false;
517 else if(m_nextLine ? videoPosition > m_nextLine->hideTime() : m_prevLine != lastLine)
518 pnValid = false;
519
520 if(!pnValid) {
521 // prev/next line are invalid
522 m_prevLine = m_nextLine = nullptr;
523 int first = 0;
524 int last = m_subtitle->lastIndex();
525 if(videoPosition < firstLine->showTime()) {
526 m_nextLine = firstLine;
527 } else if(videoPosition > lastLine->hideTime()) {
528 m_prevLine = lastLine;
529 } else {
530 while(last - first > 1) {
531 // log2 search lines
532 int mid = (first + last) / 2;
533 SubtitleLine *line = m_subtitle->at(mid);
534 if(videoPosition <= line->hideTime())
535 last = mid;
536 if(videoPosition >= line->showTime())
537 first = mid;
538 }
539 m_prevLine = m_subtitle->at(first);
540 m_nextLine = m_subtitle->at(last);
541 }
542 }
543
544 if(m_prevLine && m_prevLine->containsTime(videoPosition))
545 setPlayingLine(m_prevLine);
546 else if(m_nextLine && m_nextLine->containsTime(videoPosition))
547 setPlayingLine(m_nextLine);
548 else
549 setPlayingLine(nullptr);
550 }
551
552 void
pauseAfterPlayingLine(const SubtitleLine * line)553 PlayerWidget::pauseAfterPlayingLine(const SubtitleLine *line)
554 {
555 m_pauseAfterPlayingLine = line;
556 }
557
558 void
setPlayingLineFromVideo()559 PlayerWidget::setPlayingLineFromVideo()
560 {
561 updatePlayingLine(VideoPlayer::instance()->position() * 1000.);
562 }
563
564 void
setPlayingLine(SubtitleLine * line)565 PlayerWidget::setPlayingLine(SubtitleLine *line)
566 {
567 if(m_subtitle && line && m_subtitle != line->subtitle())
568 line = nullptr;
569
570 if(m_playingLine == line)
571 return;
572
573 if(m_playingLine) {
574 disconnect(m_playingLine, &SubtitleLine::showTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
575 disconnect(m_playingLine, &SubtitleLine::hideTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
576 }
577
578 emit playingLineChanged(m_playingLine = line);
579
580 if(m_playingLine) {
581 connect(m_playingLine, &SubtitleLine::showTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
582 connect(m_playingLine, &SubtitleLine::hideTimeChanged, this, &PlayerWidget::setPlayingLineFromVideo);
583 VideoPlayer::instance()->subtitleOverlay().setDoc(m_showTranslation ? m_playingLine->secondaryDoc() : m_playingLine->primaryDoc());
584 } else {
585 VideoPlayer::instance()->subtitleOverlay().setDoc(nullptr);
586 }
587 }
588
589 void
updatePositionEditVisibility()590 PlayerWidget::updatePositionEditVisibility()
591 {
592 if(m_showPositionTimeEdit && VideoPlayer::instance()->state() >= VideoPlayer::Playing)
593 m_positionEdit->show();
594 else
595 m_positionEdit->hide();
596 }
597
598 void
onVolumeSliderMoved(int value)599 PlayerWidget::onVolumeSliderMoved(int value)
600 {
601 VideoPlayer::instance()->setVolume(value);
602 }
603
604 void
onSeekSliderMoved(int value)605 PlayerWidget::onSeekSliderMoved(int value)
606 {
607 VideoPlayer *videoPlayer = VideoPlayer::instance();
608 pauseAfterPlayingLine(nullptr);
609 videoPlayer->seek(videoPlayer->duration() * value / 1000.0);
610
611 Time time((long)(videoPlayer->duration() * value));
612
613 m_positionLabel->setText(time.toString());
614 m_fsPositionLabel->setText(time.toString(false) + m_lengthString);
615
616 if(m_showPositionTimeEdit)
617 m_positionEdit->setValue(time.toMillis());
618 }
619
620 void
onPositionEditValueChanged(int position)621 PlayerWidget::onPositionEditValueChanged(int position)
622 {
623 if(m_positionEdit->hasFocus()) {
624 pauseAfterPlayingLine(nullptr);
625 VideoPlayer::instance()->seek(position / 1000.0);
626 }
627 }
628
629 void
onConfigChanged()630 PlayerWidget::onConfigChanged()
631 {
632 if(m_showPositionTimeEdit != SCConfig::showPositionTimeEdit()) {
633 m_showPositionTimeEdit = SCConfig::showPositionTimeEdit();
634 updatePositionEditVisibility();
635 }
636
637 SubtitleTextOverlay &subtitleOverlay = VideoPlayer::instance()->subtitleOverlay();
638 subtitleOverlay.setTextColor(SCConfig::fontColor());
639 subtitleOverlay.setFontFamily(SCConfig::fontFamily());
640 subtitleOverlay.setFontSize(SCConfig::fontSize());
641 subtitleOverlay.setOutlineColor(SCConfig::outlineColor());
642 subtitleOverlay.setOutlineWidth(SCConfig::outlineWidth());
643 }
644
645 void
onPlayerFileOpened(const QString &)646 PlayerWidget::onPlayerFileOpened(const QString & /*filePath */)
647 {
648 m_infoControlsGroupBox->setEnabled(true);
649
650 updatePositionEditVisibility();
651 }
652
653 void
onPlayerFileOpenError(const QString & filePath,const QString & reason)654 PlayerWidget::onPlayerFileOpenError(const QString &filePath, const QString &reason)
655 {
656 QString message = i18n("<qt>There was an error opening media file %1.</qt>", filePath);
657 if(!reason.isEmpty())
658 message += "\n" + reason;
659 KMessageBox::sorry(this, message);
660 }
661
662 void
onPlayerFileClosed()663 PlayerWidget::onPlayerFileClosed()
664 {
665 setPlayingLine(nullptr);
666
667 m_infoControlsGroupBox->setEnabled(false);
668
669 updatePositionEditVisibility();
670 m_positionEdit->setValue(0);
671
672 m_positionLabel->setText(i18n("<i>Unknown</i>"));
673 m_lengthLabel->setText(i18n("<i>Unknown</i>"));
674 m_fpsLabel->setText(i18n("<i>Unknown</i>"));
675 m_rateLabel->setText(i18n("<i>Unknown</i>"));
676
677 m_lengthString = UNKNOWN_LENGTH_STRING;
678 m_fsPositionLabel->setText(Time().toString(false) + m_lengthString);
679
680 m_seekSlider->setEnabled(false);
681 m_fsSeekSlider->setEnabled(false);
682 }
683
684 void
onPlayerPlaybackError(const QString & errorMessage)685 PlayerWidget::onPlayerPlaybackError(const QString &errorMessage)
686 {
687 if(errorMessage.isEmpty())
688 KMessageBox::error(this, i18n("Unexpected error when playing file."), i18n("Error Playing File"));
689 else
690 KMessageBox::detailedError(this, i18n("Unexpected error when playing file."), errorMessage, i18n("Error Playing File"));
691 }
692
693 void
onPlayerPlaying()694 PlayerWidget::onPlayerPlaying()
695 {
696 m_seekSlider->setEnabled(true);
697 m_fsSeekSlider->setEnabled(true);
698
699 updatePositionEditVisibility();
700 }
701
702 void
onPlayerPositionChanged(double seconds)703 PlayerWidget::onPlayerPositionChanged(double seconds)
704 {
705 const Time videoPosition(seconds * 1000.);
706
707 // pause if requested
708 if(m_pauseAfterPlayingLine) {
709 const Time &pauseTime = m_pauseAfterPlayingLine->hideTime();
710 if(videoPosition >= pauseTime) {
711 VideoPlayer *videoPlayer = VideoPlayer::instance();
712 m_pauseAfterPlayingLine = nullptr;
713 videoPlayer->pause();
714 videoPlayer->seek(pauseTime.toSeconds());
715 return;
716 }
717 }
718
719 m_positionLabel->setText(videoPosition.toString());
720 m_fsPositionLabel->setText(videoPosition.toString(false) + m_lengthString);
721
722 if(m_showPositionTimeEdit && !m_positionEdit->hasFocus()) {
723 QSignalBlocker sb(m_positionEdit);
724 m_positionEdit->setValue(videoPosition.toMillis());
725 }
726
727 updatePlayingLine(videoPosition);
728
729 QSignalBlocker s1(m_seekSlider), s2(m_fsSeekSlider);
730 const int sliderValue = int((seconds / VideoPlayer::instance()->duration()) * 1000.0);
731 m_seekSlider->setValue(sliderValue);
732 m_fsSeekSlider->setValue(sliderValue);
733 }
734
735 void
onPlayerLengthChanged(double seconds)736 PlayerWidget::onPlayerLengthChanged(double seconds)
737 {
738 if(seconds > 0) {
739 m_lengthLabel->setText(Time((long)(seconds * 1000)).toString());
740 m_lengthString = " / " + m_lengthLabel->text().left(8) + ' ';
741 } else {
742 m_lengthLabel->setText(i18n("<i>Unknown</i>"));
743 m_lengthString = UNKNOWN_LENGTH_STRING;
744 }
745 }
746
747 void
onPlayerFramesPerSecondChanged(double fps)748 PlayerWidget::onPlayerFramesPerSecondChanged(double fps)
749 {
750 m_fpsLabel->setText(fps > 0 ? QString::number(fps, 'f', 3) : i18n("<i>Unknown</i>"));
751 }
752
753 void
onPlayerPlaybackRateChanged(double rate)754 PlayerWidget::onPlayerPlaybackRateChanged(double rate)
755 {
756 m_rateLabel->setText(rate > .0 ? QStringLiteral("%1x").arg(rate, 0, 'g', 3) : i18n("<i>Unknown</i>"));
757 }
758
759 void
onPlayerStopped()760 PlayerWidget::onPlayerStopped()
761 {
762 onPlayerPositionChanged(0);
763
764 m_seekSlider->setEnabled(false);
765 m_fsSeekSlider->setEnabled(false);
766
767 setPlayingLine(nullptr);
768
769 updatePositionEditVisibility();
770 }
771
772 void
onPlayerVolumeChanged(double volume)773 PlayerWidget::onPlayerVolumeChanged(double volume)
774 {
775 QSignalBlocker s1(m_volumeSlider), s2(m_fsVolumeSlider);
776 m_volumeSlider->setValue(int(volume + 0.5));
777 m_fsVolumeSlider->setValue(int(volume + 0.5));
778 }
779
780 void
onPlayerLeftClicked(const QPoint &)781 PlayerWidget::onPlayerLeftClicked(const QPoint & /*point */)
782 {
783 VideoPlayer::instance()->togglePlayPaused();
784 }
785
786 void
onPlayerRightClicked(const QPoint & point)787 PlayerWidget::onPlayerRightClicked(const QPoint &point)
788 {
789 static QMenu *menu = new QMenu(this);
790
791 menu->clear();
792
793 menu->addAction(app()->action(ACT_OPEN_VIDEO));
794 menu->addAction(app()->action(ACT_CLOSE_VIDEO));
795
796 menu->addSeparator();
797
798 menu->addAction(app()->action(ACT_TOGGLE_FULL_SCREEN));
799
800 menu->addSeparator();
801
802 menu->addAction(app()->action(ACT_STOP));
803 menu->addAction(app()->action(ACT_PLAY_PAUSE));
804 menu->addAction(app()->action(ACT_SEEK_BACKWARD));
805 menu->addAction(app()->action(ACT_SEEK_FORWARD));
806
807 menu->addSeparator();
808
809 menu->addAction(app()->action(ACT_SEEK_TO_PREVIOUS_LINE));
810 menu->addAction(app()->action(ACT_SEEK_TO_NEXT_LINE));
811
812 menu->addSeparator();
813
814 menu->addAction(app()->action(ACT_PLAY_RATE_DECREASE));
815 menu->addAction(app()->action(ACT_PLAY_RATE_INCREASE));
816
817 menu->addSeparator();
818
819 menu->addAction(app()->action(ACT_SET_ACTIVE_AUDIO_STREAM));
820 menu->addAction(app()->action(ACT_INCREASE_VOLUME));
821 menu->addAction(app()->action(ACT_DECREASE_VOLUME));
822 menu->addAction(app()->action(ACT_TOGGLE_MUTED));
823
824 menu->addSeparator();
825
826 if(m_translationMode)
827 menu->addAction(app()->action(ACT_SET_ACTIVE_SUBTITLE_STREAM));
828
829 menu->addAction(app()->action(ACT_INCREASE_SUBTITLE_FONT));
830 menu->addAction(app()->action(ACT_DECREASE_SUBTITLE_FONT));
831
832 // NOTE do not use popup->exec() here!!! it freezes the application
833 // when using the mplayer backend. i think it's related to the fact
834 // that exec() creates a different event loop and the mplayer backend
835 // depends on the main loop for catching synchronization signals
836 menu->popup(point);
837 }
838
839 void
onPlayerDoubleClicked(const QPoint &)840 PlayerWidget::onPlayerDoubleClicked(const QPoint & /*point */)
841 {
842 app()->toggleFullScreenMode();
843 }
844