]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/information/mediawidget.cpp
Add margins to the zoom menu entry
[dolphin.git] / src / panels / information / mediawidget.cpp
1 /*
2 SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "mediawidget.h"
8
9 #include <KLocalizedString>
10
11 #include <QAudioOutput>
12 #include <QMediaPlayer>
13 #include <QVideoWidget>
14
15 #include <QShowEvent>
16 #include <QSlider>
17 #include <QStyle>
18 #include <QStyleOptionSlider>
19 #include <QToolButton>
20 #include <QVBoxLayout>
21
22 class EmbeddedVideoPlayer : public QVideoWidget
23 {
24 Q_OBJECT
25
26 public:
27 EmbeddedVideoPlayer(QWidget *parent = nullptr)
28 : QVideoWidget(parent)
29 {
30 }
31
32 void setSizeHint(const QSize &size)
33 {
34 m_sizeHint = size;
35 updateGeometry();
36 }
37
38 QSize sizeHint() const override
39 {
40 return m_sizeHint.isValid() ? m_sizeHint : QVideoWidget::sizeHint();
41 }
42
43 private:
44 QSize m_sizeHint;
45 };
46
47 class SeekSlider : public QSlider
48 {
49 Q_OBJECT
50
51 public:
52 SeekSlider(Qt::Orientation orientation, QWidget *parent = nullptr)
53 : QSlider(orientation, parent)
54 {
55 }
56
57 protected:
58 // Function copied from qslider.cpp
59 inline int pick(const QPoint &pt) const
60 {
61 return orientation() == Qt::Horizontal ? pt.x() : pt.y();
62 }
63
64 // Function copied from qslider.cpp and modified to make it compile
65 int pixelPosToRangeValue(int pos) const
66 {
67 QStyleOptionSlider opt;
68 initStyleOption(&opt);
69 QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
70 QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
71 int sliderMin, sliderMax, sliderLength;
72
73 if (orientation() == Qt::Horizontal) {
74 sliderLength = sr.width();
75 sliderMin = gr.x();
76 sliderMax = gr.right() - sliderLength + 1;
77 } else {
78 sliderLength = sr.height();
79 sliderMin = gr.y();
80 sliderMax = gr.bottom() - sliderLength + 1;
81 }
82 return QStyle::sliderValueFromPosition(minimum(), maximum(), pos - sliderMin, sliderMax - sliderMin, opt.upsideDown);
83 }
84
85 // Based on code from qslider.cpp
86 void mousePressEvent(QMouseEvent *event) override
87 {
88 if (event->button() == Qt::LeftButton) {
89 QStyleOptionSlider opt;
90 initStyleOption(&opt);
91 const QRect sliderRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
92 const QPoint center = sliderRect.center() - sliderRect.topLeft();
93 // to take half of the slider off for the setSliderPosition call we use the center - topLeft
94
95 if (!sliderRect.contains(event->pos())) {
96 event->accept();
97
98 int position = pixelPosToRangeValue(pick(event->pos() - center));
99 setSliderPosition(position);
100 triggerAction(SliderMove);
101 setRepeatAction(SliderNoAction);
102
103 Q_EMIT sliderMoved(position);
104 } else {
105 QSlider::mousePressEvent(event);
106 }
107 } else {
108 QSlider::mousePressEvent(event);
109 }
110 }
111
112 void keyPressEvent(QKeyEvent *event) override
113 {
114 int newPosition = -1;
115 if (event->key() == Qt::Key_Right) {
116 // slide right 1%
117 newPosition = std::min(maximum(), sliderPosition() + maximum() / 100);
118 } else if (event->key() == Qt::Key_Left) {
119 // slide left 1%
120 newPosition = std::max(0, sliderPosition() - maximum() / 100);
121 }
122
123 if (newPosition != -1) {
124 event->accept();
125
126 if (newPosition != sliderPosition()) {
127 setSliderPosition(newPosition);
128 triggerAction(SliderMove);
129 setRepeatAction(SliderNoAction);
130
131 Q_EMIT sliderMoved(newPosition);
132 }
133 } else {
134 QSlider::keyPressEvent(event);
135 }
136 }
137 };
138
139 MediaWidget::MediaWidget(QWidget *parent)
140 : QWidget(parent)
141 , m_url()
142 , m_playButton(nullptr)
143 , m_pauseButton(nullptr)
144 , m_topLayout(nullptr)
145 , m_player(nullptr)
146 , m_seekSlider(nullptr)
147 , m_videoWidget(nullptr)
148 {
149 }
150
151 void MediaWidget::setUrl(const QUrl &url, MediaKind kind)
152 {
153 if (m_url != url) {
154 m_url = url;
155 m_isVideo = kind == MediaKind::Video;
156 m_seekSlider->setValue(0);
157 }
158 if (m_autoPlay) {
159 play();
160 } else {
161 stop();
162 }
163 }
164
165 void MediaWidget::setAutoPlay(bool autoPlay)
166 {
167 m_autoPlay = autoPlay;
168 if (!m_url.isEmpty() && (m_player == nullptr || m_player->playbackState() != QMediaPlayer::PlayingState) && m_autoPlay && isVisible()) {
169 play();
170 }
171 }
172
173 QUrl MediaWidget::url() const
174 {
175 return m_url;
176 }
177
178 void MediaWidget::clearUrl()
179 {
180 m_url.clear();
181 }
182
183 void MediaWidget::togglePlayback()
184 {
185 if (m_player && m_player->playbackState() == QMediaPlayer::PlayingState) {
186 m_player->pause();
187 } else {
188 play();
189 }
190 }
191
192 bool MediaWidget::eventFilter(QObject *object, QEvent *event)
193 {
194 Q_UNUSED(object)
195 if (event->type() == QEvent::MouseButtonPress) {
196 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
197 if (mouseEvent->button() == Qt::LeftButton) {
198 // toggle playback
199 togglePlayback();
200 return true;
201 }
202 }
203 return false;
204 }
205
206 void MediaWidget::setVideoSize(const QSize &size)
207 {
208 if (m_videoSize != size) {
209 m_videoSize = size;
210 applyVideoSize();
211 }
212 }
213
214 QSize MediaWidget::videoSize() const
215 {
216 return m_videoSize;
217 }
218
219 void MediaWidget::showEvent(QShowEvent *event)
220 {
221 if (event->spontaneous()) {
222 QWidget::showEvent(event);
223 return;
224 }
225
226 if (!m_topLayout) {
227 m_topLayout = new QVBoxLayout(this);
228 m_topLayout->setContentsMargins(0, 0, 0, 0);
229
230 QHBoxLayout *controlsLayout = new QHBoxLayout();
231 controlsLayout->setContentsMargins(0, 0, 0, 0);
232 controlsLayout->setSpacing(0);
233
234 m_playButton = new QToolButton(this);
235 m_pauseButton = new QToolButton(this);
236 m_seekSlider = new SeekSlider(Qt::Orientation::Horizontal, this);
237 connect(m_seekSlider, &QAbstractSlider::sliderMoved, this, &MediaWidget::setPosition);
238
239 controlsLayout->addWidget(m_playButton);
240 controlsLayout->addWidget(m_pauseButton);
241 controlsLayout->addWidget(m_seekSlider);
242
243 m_topLayout->addLayout(controlsLayout);
244
245 const int smallIconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
246 const QSize buttonSize(smallIconSize, smallIconSize);
247
248 m_playButton->setToolTip(i18n("play"));
249 m_playButton->setIconSize(buttonSize);
250 m_playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
251 m_playButton->setAutoRaise(true);
252 connect(m_playButton, &QToolButton::clicked, this, &MediaWidget::play);
253
254 m_pauseButton->setToolTip(i18n("pause"));
255 m_pauseButton->setIconSize(buttonSize);
256 m_pauseButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
257 m_pauseButton->setAutoRaise(true);
258 m_pauseButton->hide();
259 connect(m_pauseButton, &QToolButton::clicked, this, &MediaWidget::togglePlayback);
260 }
261 }
262
263 void MediaWidget::hideEvent(QHideEvent *event)
264 {
265 QWidget::hideEvent(event);
266 if (!event->spontaneous()) {
267 stop();
268 }
269 }
270
271 void MediaWidget::onStateChanged(QMediaPlayer::PlaybackState newState)
272 {
273 setUpdatesEnabled(false);
274 switch (newState) {
275 case QMediaPlayer::PlaybackState::PlayingState:
276 m_playButton->hide();
277 m_pauseButton->show();
278 break;
279 default:
280 m_pauseButton->hide();
281 m_playButton->show();
282 break;
283 }
284 setUpdatesEnabled(true);
285 }
286
287 void MediaWidget::initPlayer()
288 {
289 if (!m_player) {
290 m_player = new QMediaPlayer;
291 m_player->setAudioOutput(new QAudioOutput);
292
293 m_videoWidget = new EmbeddedVideoPlayer(this);
294 m_videoWidget->setCursor(Qt::PointingHandCursor);
295
296 m_videoWidget->installEventFilter(this);
297 m_player->setVideoOutput(m_videoWidget);
298 m_topLayout->insertWidget(0, m_videoWidget);
299
300 applyVideoSize();
301
302 connect(m_player, &QMediaPlayer::playbackStateChanged, this, &MediaWidget::onStateChanged);
303 connect(m_player, &QMediaPlayer::positionChanged, this, &MediaWidget::onPositionChanged);
304 connect(m_player, &QMediaPlayer::durationChanged, this, &MediaWidget::onDurationChanged);
305 }
306
307 if (m_url != m_player->source()) {
308 m_player->setSource(m_url);
309 m_seekSlider->setSliderPosition(0);
310 }
311
312 Q_EMIT hasVideoChanged(m_isVideo);
313
314 m_videoWidget->setVisible(m_isVideo);
315 }
316
317 void MediaWidget::play()
318 {
319 initPlayer();
320
321 m_player->play();
322 }
323
324 void MediaWidget::finished()
325 {
326 if (m_isVideo) {
327 m_videoWidget->hide();
328 Q_EMIT hasVideoChanged(false);
329 }
330 }
331
332 QMediaPlayer::PlaybackState MediaWidget::state() const
333 {
334 return m_player == nullptr ? QMediaPlayer::PlaybackState::StoppedState : m_player->playbackState();
335 }
336
337 void MediaWidget::stop()
338 {
339 if (m_player) {
340 m_player->stop();
341 m_videoWidget->hide();
342 Q_EMIT hasVideoChanged(false);
343 }
344 }
345
346 void MediaWidget::applyVideoSize()
347 {
348 if ((m_videoWidget) && m_videoSize.isValid()) {
349 m_videoWidget->setSizeHint(m_videoSize);
350 }
351 }
352
353 void MediaWidget::setPosition(qint64 position)
354 {
355 if (!m_player || m_player->playbackState() == QMediaPlayer::StoppedState) {
356 initPlayer();
357
358 auto prevDuration = m_seekSlider->maximum();
359
360 connect(
361 m_player,
362 &QMediaPlayer::mediaStatusChanged,
363 this,
364 [prevDuration, position, this](QMediaPlayer::MediaStatus status) {
365 if (status == QMediaPlayer::BufferedMedia) {
366 m_player->setPosition(float(position) / prevDuration * m_player->duration());
367 m_player->pause();
368 }
369 },
370 Qt::SingleShotConnection);
371
372 m_player->play();
373 } else {
374 m_player->setPosition(position);
375 }
376 }
377
378 void MediaWidget::onPositionChanged(qint64 position)
379 {
380 m_seekSlider->setValue(static_cast<int>(position));
381 }
382
383 void MediaWidget::onDurationChanged(qint64 duration)
384 {
385 m_seekSlider->setMaximum(static_cast<int>(duration));
386 }
387
388 #include "mediawidget.moc"
389 #include "moc_mediawidget.cpp"