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