videoviewadapter.cpp 10.9 KB
Newer Older
1
// vim: set tabstop=4 shiftwidth=4 expandtab:
2 3
/*
Gwenview: an image viewer
Aurélien Gâteau's avatar
Aurélien Gâteau committed
4
Copyright 2009 Aurélien Gâteau <agateau@kde.org>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.

*/
// Self
David Edmundson's avatar
David Edmundson committed
22
#include "videoviewadapter.h"
23 24 25 26 27 28

// Qt
#include <Phonon/AudioOutput>
#include <Phonon/MediaObject>
#include <Phonon/Path>
#include <Phonon/VideoWidget>
29
#include <QAction>
30
#include <QGraphicsLinearLayout>
31
#include <QGraphicsProxyWidget>
32
#include <QMouseEvent>
Laurent Montel's avatar
Laurent Montel committed
33
#include <QElapsedTimer>
Laurent Montel's avatar
Laurent Montel committed
34
#include "gwenview_lib_debug.h"
David Edmundson's avatar
David Edmundson committed
35
#include <QIcon>
Ben Cooksley's avatar
Ben Cooksley committed
36
#include <QTime>
37

Lukáš Tinkl's avatar
Lukáš Tinkl committed
38 39
// KDE

40
// Local
41
#include <document/documentfactory.h>
42 43 44
#include <hud/hudbutton.h>
#include <hud/hudslider.h>
#include <hud/hudwidget.h>
45
#include <graphicswidgetfloater.h>
46
#include <lib/gwenviewconfig.h>
47

48 49
namespace Gwenview
{
50

Aurélien Gâteau's avatar
Aurélien Gâteau committed
51 52
struct VideoViewAdapterPrivate
{
53 54 55 56
    VideoViewAdapter* q;
    Phonon::MediaObject* mMediaObject;
    Phonon::VideoWidget* mVideoWidget;
    Phonon::AudioOutput* mAudioOutput;
57
    HudWidget* mHud;
58
    GraphicsWidgetFloater* mFloater;
59

60
    HudSlider* mSeekSlider;
Laurent Montel's avatar
Laurent Montel committed
61
    QElapsedTimer mLastSeekSliderActionTime;
62

63 64
    QAction* mPlayPauseAction;
    QAction* mMuteAction;
65
    QGraphicsProxyWidget* mProxy;
66

67
    HudSlider* mVolumeSlider;
68 69
    QTime mLastVolumeSliderChangeTime;

70 71
    Document::Ptr mDocument;

Aurélien Gâteau's avatar
Aurélien Gâteau committed
72
    void setupActions()
73
    {
74 75
        mPlayPauseAction = new QAction(q);
        mPlayPauseAction->setShortcut(Qt::Key_P);
Laurent Montel's avatar
Laurent Montel committed
76 77
        QObject::connect(mPlayPauseAction, &QAction::triggered, q, &VideoViewAdapter::slotPlayPauseClicked);
        QObject::connect(mMediaObject, &Phonon::MediaObject::stateChanged, q, &VideoViewAdapter::updatePlayUi);
78

Aurélien Gâteau's avatar
Aurélien Gâteau committed
79 80
        mMuteAction = new QAction(q);
        mMuteAction->setShortcut(Qt::Key_M);
Laurent Montel's avatar
Laurent Montel committed
81 82
        QObject::connect(mMuteAction, &QAction::triggered, q, &VideoViewAdapter::slotMuteClicked);
        QObject::connect(mAudioOutput, &Phonon::AudioOutput::mutedChanged, q, &VideoViewAdapter::updateMuteAction);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
83 84 85 86 87
    }

    void setupHud(QGraphicsWidget* parent)
    {
        // Play/Pause
88
        HudButton* playPauseButton = new HudButton;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
89 90 91
        playPauseButton->setDefaultAction(mPlayPauseAction);

        // Seek
92
        mSeekSlider = new HudSlider;
93 94
        mSeekSlider->setPageStep(5000);
        mSeekSlider->setSingleStep(200);
Laurent Montel's avatar
Laurent Montel committed
95 96 97 98
        QObject::connect(mSeekSlider, &HudSlider::actionTriggered, q, &VideoViewAdapter::slotSeekSliderActionTriggered);
        QObject::connect(mMediaObject, &Phonon::MediaObject::tick, q, &VideoViewAdapter::slotTicked);
        QObject::connect(mMediaObject, &Phonon::MediaObject::totalTimeChanged, q, &VideoViewAdapter::updatePlayUi);
        QObject::connect(mMediaObject, &Phonon::MediaObject::seekableChanged, q, &VideoViewAdapter::updatePlayUi);
99

Aurélien Gâteau's avatar
Aurélien Gâteau committed
100
        // Mute
101
        HudButton* muteButton = new HudButton;
102
        muteButton->setDefaultAction(mMuteAction);
103

Aurélien Gâteau's avatar
Aurélien Gâteau committed
104
        // Volume
105
        mVolumeSlider = new HudSlider;
106 107 108 109
        mVolumeSlider->setMinimumWidth(100);
        mVolumeSlider->setRange(0, 100);
        mVolumeSlider->setPageStep(5);
        mVolumeSlider->setSingleStep(1);
Laurent Montel's avatar
Laurent Montel committed
110 111
        QObject::connect(mVolumeSlider, &HudSlider::valueChanged, q, &VideoViewAdapter::slotVolumeSliderChanged);
        QObject::connect(mAudioOutput, &Phonon::AudioOutput::volumeChanged, q, &VideoViewAdapter::slotOutputVolumeChanged);
112

Aurélien Gâteau's avatar
Aurélien Gâteau committed
113
        // Layout
114 115
        QGraphicsWidget* hudContent = new QGraphicsWidget;
        QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(hudContent);
116
        layout->addItem(playPauseButton);
117 118
        layout->addItem(mSeekSlider);
        layout->setStretchFactor(mSeekSlider, 5);
119
        layout->addItem(muteButton);
120 121
        layout->addItem(mVolumeSlider);
        layout->setStretchFactor(mVolumeSlider, 1);
122 123

        // Create hud
124 125
        mHud = new HudWidget(parent);
        mHud->init(hudContent, HudWidget::OptionNone);
126
        mHud->setZValue(1);
127 128

        // Init floater
129
        mFloater = new GraphicsWidgetFloater(parent);
130 131 132 133
        mFloater->setChildWidget(mHud);
        mFloater->setAlignment(Qt::AlignJustify | Qt::AlignBottom);
    }

Aurélien Gâteau's avatar
Aurélien Gâteau committed
134 135
    bool isPlaying() const
    {
136
        switch (mMediaObject->state()) {
137 138
        case Phonon::PlayingState:
        case Phonon::BufferingState:
139
            return true;
140 141
        default:
            return false;
142 143 144 145 146
        }
    }

    void updateHudVisibility(int yPos)
    {
147
        const int floaterY = mVideoWidget->height() - mFloater->verticalMargin() - mHud->effectiveSizeHint(Qt::MinimumSize).height() * 3 / 2;
Aurélien Gâteau's avatar
Aurélien Gâteau committed
148 149 150 151
        if (yPos < floaterY) {
            mHud->fadeOut();
        } else {
            mHud->fadeIn();
152 153
        }
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168

    void keyPressEvent(QKeyEvent* event)
    {
        if (event->modifiers() != Qt::NoModifier) {
            return;
        }

        switch (event->key()) {
        case Qt::Key_Left:
            mSeekSlider->triggerAction(QAbstractSlider::SliderSingleStepSub);
            break;
        case Qt::Key_Right:
            mSeekSlider->triggerAction(QAbstractSlider::SliderSingleStepAdd);
            break;
        case Qt::Key_Up:
Alexander Volkov's avatar
Alexander Volkov committed
169
            emit q->previousImageRequested();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
170 171
            break;
        case Qt::Key_Down:
Alexander Volkov's avatar
Alexander Volkov committed
172
            emit q->nextImageRequested();
Aurélien Gâteau's avatar
Aurélien Gâteau committed
173 174 175 176 177
            break;
        default:
            break;
        }
    }
178 179
};

180 181 182 183 184 185 186 187 188 189 190 191
/**
 * This is a workaround for a bug in QGraphicsProxyWidget: it does not forward
 * double-click events to the proxy-fied widget.
 *
 * QGraphicsProxyWidget::mouseDoubleClickEvent() correctly forwards the event
 * to its QWidget, but it is never called. This is because for it to be called,
 * the implementation of mousePressEvent() must call
 * QGraphicsItem::mousePressEvent() but it does not.
 */
class DoubleClickableProxyWidget : public QGraphicsProxyWidget
{
protected:
192
    void mousePressEvent(QGraphicsSceneMouseEvent* event) override
193 194 195 196 197
    {
        QGraphicsWidget::mousePressEvent(event);
    }
};

198
VideoViewAdapter::VideoViewAdapter()
199 200 201 202
: d(new VideoViewAdapterPrivate)
{
    d->q = this;
    d->mMediaObject = new Phonon::MediaObject(this);
203
    d->mMediaObject->setTickInterval(350);
Laurent Montel's avatar
Laurent Montel committed
204
    connect(d->mMediaObject, &Phonon::MediaObject::finished, this, &VideoViewAdapter::videoFinished);
205

206 207 208
    d->mVideoWidget = new Phonon::VideoWidget;
    d->mVideoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    d->mVideoWidget->setAttribute(Qt::WA_Hover);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
209
    d->mVideoWidget->installEventFilter(this);
210

211
    Phonon::createPath(d->mMediaObject, d->mVideoWidget);
212

213 214
    d->mAudioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this);
    Phonon::createPath(d->mMediaObject, d->mAudioOutput);
215

216 217 218 219 220
    d->mProxy = new DoubleClickableProxyWidget;
    d->mProxy->setFlag(QGraphicsItem::ItemIsSelectable); // Needed for doubleclick to work
    d->mProxy->setWidget(d->mVideoWidget);
    d->mProxy->setAcceptHoverEvents(GwenviewConfig::autoplayVideos()); // Makes hud visible when autoplay is disabled
    setWidget(d->mProxy);
221

Aurélien Gâteau's avatar
Aurélien Gâteau committed
222
    d->setupActions();
223
    d->setupHud(d->mProxy);
Aurélien Gâteau's avatar
Aurélien Gâteau committed
224 225 226

    updatePlayUi();
    updateMuteAction();
227 228
}

229 230
VideoViewAdapter::~VideoViewAdapter()
{
231 232 233 234 235
    // This prevents a memory leak that can occur after switching
    // to the next/previous video. For details see:
    // https://git.reviewboard.kde.org/r/108070/
    d->mMediaObject->stop();

236
    delete d;
237 238
}

239
void VideoViewAdapter::setDocument(const Document::Ptr &doc)
240 241 242 243
{
    d->mHud->show();
    d->mDocument = doc;
    d->mMediaObject->setCurrentSource(d->mDocument->url());
244 245 246
    if (GwenviewConfig::autoplayVideos()) {
        d->mMediaObject->play();
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
247 248 249
    // If we do not use a queued connection, the signal arrives too early,
    // preventing the listing of the dir content when Gwenview is started with
    // a video as an argument.
250
    QMetaObject::invokeMethod(this, &VideoViewAdapter::completed, Qt::QueuedConnection);
251 252
}

253 254 255
Document::Ptr VideoViewAdapter::document() const
{
    return d->mDocument;
256 257
}

258 259 260 261
void VideoViewAdapter::slotPlayPauseClicked()
{
    if (d->isPlaying()) {
        d->mMediaObject->pause();
262 263
        d->mHud->fadeIn();
        d->mProxy->setAcceptHoverEvents(false);
264 265
    } else {
        d->mMediaObject->play();
266
        d->mProxy->setAcceptHoverEvents(true);
267
    }
268 269
}

270 271 272 273 274
void VideoViewAdapter::slotMuteClicked()
{
    d->mAudioOutput->setMuted(!d->mAudioOutput->isMuted());
}

275 276 277 278
bool VideoViewAdapter::eventFilter(QObject*, QEvent* event)
{
    if (event->type() == QEvent::MouseMove) {
        d->updateHudVisibility(static_cast<QMouseEvent*>(event)->y());
Aurélien Gâteau's avatar
Aurélien Gâteau committed
279 280
    } else if (event->type() == QEvent::KeyPress) {
        d->keyPressEvent(static_cast<QKeyEvent*>(event));
281 282
    } else if (event->type() == QEvent::MouseButtonDblClick) {
        if (static_cast<QMouseEvent*>(event)->modifiers() == Qt::NoModifier) {
Alexander Volkov's avatar
Alexander Volkov committed
283
            emit toggleFullScreenRequested();
284
        }
285 286
    }
    return false;
287 288
}

289
void VideoViewAdapter::updatePlayUi()
290 291
{
    if (d->isPlaying()) {
292
        d->mPlayPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
293
    } else {
294
        d->mPlayPauseAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
295
    }
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

    d->mLastSeekSliderActionTime.restart();
    d->mSeekSlider->setRange(0, d->mMediaObject->totalTime());

    switch (d->mMediaObject->state()) {
    case Phonon::PlayingState:
    case Phonon::BufferingState:
    case Phonon::PausedState:
        d->mSeekSlider->setEnabled(true);
        break;
    case Phonon::StoppedState:
    case Phonon::LoadingState:
    case Phonon::ErrorState:
        d->mSeekSlider->setEnabled(false);
        d->mSeekSlider->setValue(0);
        break;
    }
313 314
}

315
void VideoViewAdapter::updateMuteAction()
316
{
317
    d->mMuteAction->setIcon(
318
        QIcon::fromTheme(d->mAudioOutput->isMuted() ? QStringLiteral("player-volume-muted") : QStringLiteral("player-volume"))
319 320 321
        );
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335
void VideoViewAdapter::slotVolumeSliderChanged(int value)
{
    d->mLastVolumeSliderChangeTime.restart();
    d->mAudioOutput->setVolume(value / 100.);
}

void VideoViewAdapter::slotOutputVolumeChanged(qreal value)
{
    if (d->mLastVolumeSliderChangeTime.isValid() && d->mLastVolumeSliderChangeTime.elapsed() < 2000) {
        return;
    }
    d->mVolumeSlider->setValue(qRound(value * 100));
}

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
void VideoViewAdapter::slotSeekSliderActionTriggered(int /*action*/)
{
    d->mLastSeekSliderActionTime.restart();
    d->mMediaObject->seek(d->mSeekSlider->sliderPosition());
}

void VideoViewAdapter::slotTicked(qint64 value)
{
    if (d->mLastSeekSliderActionTime.isValid() && d->mLastSeekSliderActionTime.elapsed() < 2000) {
        return;
    }
    if (!d->mSeekSlider->isSliderDown()) {
        d->mSeekSlider->setValue(value);
    }
}

352
} // namespace