TelepathyQt4: call/call-widget.cpp

| Home · All Classes · All Namespaces · Modules · Functions · Files

# call/call-widget.cpp

    /*
     * This file is part of TelepathyQt4
     *
     * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/>
     * Copyright © 2009 Nokia Corporation
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     *
     * This library 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
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     */

    #include "call-widget.h"
    #include "_gen/call-widget.moc.hpp"

    #include "video-widget.h"

    #include <TelepathyQt4/Channel>
    #include <TelepathyQt4/Connection>
    #include <TelepathyQt4/Contact>
    #include <TelepathyQt4/ContactManager>
    #include <TelepathyQt4/PendingChannel>
    #include <TelepathyQt4/PendingReady>
    #include <TelepathyQt4/StreamedMediaChannel>

    #include <QDebug>
    #include <QHBoxLayout>
    #include <QLabel>
    #include <QPushButton>
    #include <QStatusBar>
    #include <QVBoxLayout>

    using namespace Tp;

    CallWidget::CallWidget(const StreamedMediaChannelPtr &chan,
            const ContactPtr &contact,
            QWidget *parent)
        : QWidget(parent),
          mChan(chan),
          mContact(contact),
          mTfChan(new FarsightChannel(chan, this)),
          mPmsAudio(0),
          mPmsVideo(0)
    {
        setWindowTitle(QString(QLatin1String("Call (%1)")).arg(mContact->id()));

        setAttribute(Qt::WA_DeleteOnClose);

        setupGui();

        connect(mChan->becomeReady(StreamedMediaChannel::FeatureStreams),
                SIGNAL(finished(Tp::PendingOperation*)),
                SLOT(onChannelReady(Tp::PendingOperation*)));
        connect(mChan.data(),
                SIGNAL(invalidated(Tp::DBusProxy *, const QString &, const QString &)),
                SLOT(onChannelInvalidated(Tp::DBusProxy *, const QString &, const QString &)));

        connect(mTfChan,
                SIGNAL(statusChanged(Tp::FarsightChannel::Status)),
                SLOT(onTfChannelStatusChanged(Tp::FarsightChannel::Status)));
    }

    CallWidget::~CallWidget()
    {
        if (mChan) {
            mChan->requestClose();
        }
    }

    void CallWidget::setupGui()
    {
        QVBoxLayout *mainBox = new QVBoxLayout;

        // buttons
        QHBoxLayout *btnBox = new QHBoxLayout;

        mBtnHangup = new QPushButton(QLatin1String("Hangup"));
        connect(mBtnHangup,
                SIGNAL(clicked(bool)),
                SLOT(onBtnHangupClicked()));
        btnBox->addWidget(mBtnHangup);

        mBtnSendAudio = new QPushButton(QLatin1String("Send Audio"));
        mBtnSendAudio->setCheckable(true);
        mBtnSendAudio->setChecked(true);
        mBtnSendAudio->setEnabled(false);
        connect(mBtnSendAudio,
                SIGNAL(toggled(bool)),
                SLOT(onBtnSendAudioToggled(bool)));
        btnBox->addWidget(mBtnSendAudio);

        mBtnSendVideo = new QPushButton(QLatin1String("Send Video"));
        mBtnSendVideo->setCheckable(true);
        mBtnSendVideo->setChecked(false);
        mBtnSendVideo->setEnabled(false);
        connect(mBtnSendVideo,
                SIGNAL(toggled(bool)),
                SLOT(onBtnSendVideoToggled(bool)));
        btnBox->addWidget(mBtnSendVideo);

        mainBox->addLayout(btnBox);

        // video
        QHBoxLayout *videoBox = new QHBoxLayout;

        VideoWidget *videoWidget = mTfChan->videoWidget();
        videoWidget->setMinimumSize(320, 240);
        videoBox->addWidget(videoWidget);

        QVBoxLayout *previewBox = new QVBoxLayout;

        VideoWidget *videoPreview = mTfChan->videoPreview();
        videoPreview->setFixedSize(160, 120);
        previewBox->addWidget(videoPreview);

        previewBox->addStretch(1);

        videoBox->addLayout(previewBox);

        mainBox->addLayout(videoBox);

        // stream status
        QFrame *frame = new QFrame;
        frame->setFrameStyle(QFrame::Box | QFrame::Sunken);

        QVBoxLayout *streamBox = new QVBoxLayout;

        QLabel *lbl = new QLabel(QLatin1String("<b>Streams</b>"));
        streamBox->addWidget(lbl);
        streamBox->addSpacing(4);

        lbl = new QLabel(QLatin1String("<b>Audio</b>"));
        streamBox->addWidget(lbl);
        mLblAudioDirection = new QLabel(QLatin1String("Direction: None"));
        streamBox->addWidget(mLblAudioDirection);
        mLblAudioState = new QLabel(QLatin1String("State: Disconnected"));
        streamBox->addWidget(mLblAudioState);
        streamBox->addSpacing(4);

        lbl = new QLabel(QLatin1String("<b>Video</b>"));
        streamBox->addWidget(lbl);
        mLblVideoDirection = new QLabel(QLatin1String("Direction: None"));
        streamBox->addWidget(mLblVideoDirection);
        mLblVideoState = new QLabel(QLatin1String("State: Disconnected"));
        streamBox->addWidget(mLblVideoState);

        streamBox->addStretch(1);
        frame->setLayout(streamBox);

        videoBox->addSpacing(4);
        frame->setLayout(streamBox);
        videoBox->addWidget(frame);

        // status bar
        mStatusBar = new QStatusBar;
        mainBox->addWidget(mStatusBar);

        setLayout(mainBox);
    }

    void CallWidget::onChannelReady(PendingOperation *op)
    {
        if (op->isError()) {
            qWarning() << "CallWidget::onChannelReady: channel cannot become ready:" <<
                op->errorName() << "-" << op->errorMessage();
            mChan->requestClose();
            callEnded(QLatin1String("Unable to establish call"));
            return;
        }

        connect(mChan.data(),
                SIGNAL(streamAdded(const Tp::MediaStreamPtr &)),
                SLOT(onStreamAdded(const Tp::MediaStreamPtr &)));
        connect(mChan.data(),
                SIGNAL(streamRemoved(const Tp::MediaStreamPtr &)),
                SLOT(onStreamRemoved(const Tp::MediaStreamPtr &)));
        connect(mChan.data(),
                SIGNAL(streamDirectionChanged(const Tp::MediaStreamPtr &,
                                              Tp::MediaStreamDirection,
                                              Tp::MediaStreamPendingSend)),
                SLOT(onStreamDirectionChanged(const Tp::MediaStreamPtr &,
                                              Tp::MediaStreamDirection,
                                              Tp::MediaStreamPendingSend)));
        connect(mChan.data(),
                SIGNAL(streamStateChanged(const Tp::MediaStreamPtr &,
                                          Tp::MediaStreamState)),
                SLOT(onStreamStateChanged(const Tp::MediaStreamPtr &,
                                          Tp::MediaStreamState)));

        MediaStreams streams = mChan->streams();
        qDebug() << "CallWidget::onChannelReady: number of streams:" << streams.size();
        if (streams.size() > 0) {
            foreach (const MediaStreamPtr &stream, streams) {
                qDebug() << "  type:" <<
                    (stream->type() == Tp::MediaStreamTypeAudio ? "Audio" : "Video");
                qDebug() << "  direction:" << stream->direction();
                qDebug() << "  state:" << stream->state();

                onStreamDirectionChanged(stream, stream->direction(),
                        stream->pendingSend());
                onStreamStateChanged(stream, stream->state());
            }
        }

        mBtnSendAudio->setEnabled(true);
        mBtnSendVideo->setEnabled(true);

        onBtnSendAudioToggled(mBtnSendAudio->isChecked());
    }

    void CallWidget::onChannelInvalidated(DBusProxy *proxy,
            const QString &errorName, const QString &errorMessage)
    {
        qDebug() << "CallWidget::onChannelInvalidated: channel became invalid:" <<
            errorName << "-" << errorMessage;
        callEnded(errorMessage);
    }

    void CallWidget::onStreamCreated(PendingOperation *op)
    {
        if (op == mPmsAudio) {
            mPmsAudio = 0;
        } else if (op == mPmsVideo) {
            mPmsVideo = 0;
        }

        if (op->isError()) {
            qWarning() << "CallWidget::onStreamCreated: unable to create stream:" <<
                op->errorName() << "-" << op->errorMessage();

            QPushButton *btn;

            // we cannot create the stream for some reason, update buttons
            if (!streamForType(Tp::MediaStreamTypeAudio)) {
                btn = mBtnSendAudio;
            } else if (!streamForType(Tp::MediaStreamTypeVideo)) {
                btn = mBtnSendVideo;
            } else {
                // we only handle audio and video, so ignore here
                return;
            }

            btn->blockSignals(true);
            btn->setChecked(false);
            btn->blockSignals(false);

            MediaStreams streams = mChan->streams();
            if (streams.size() == 0) {
                callEnded(op->errorMessage());
            }
            return;
        }

        // do nothing as streamAdded signal will be emitted
    }

    void CallWidget::onStreamAdded(const MediaStreamPtr &stream)
    {
        qDebug() << "CallWidget::onStreamAdded:" <<
            (stream->type() == Tp::MediaStreamTypeAudio ? "Audio" : "Video") <<
            "stream created";
        qDebug() << " direction:" << stream->direction();
        qDebug() << " state:" << stream->state();

        updateStreamDirection(stream);
        onStreamDirectionChanged(stream, stream->direction(),
                stream->pendingSend());
        onStreamStateChanged(stream, stream->state());
    }

    void CallWidget::onStreamRemoved(const MediaStreamPtr &stream)
    {
        qDebug() << "CallWidget::onStreamRemoved:" <<
            (stream->type() == Tp::MediaStreamTypeAudio ? "Audio" : "Video") <<
            "stream removed";
        if (stream->type() == Tp::MediaStreamTypeAudio) {
            mBtnSendAudio->blockSignals(true);
            mBtnSendAudio->setChecked(false);
            mBtnSendAudio->blockSignals(false);
            mLblAudioDirection->setText(QLatin1String("Direction: None"));
            mLblAudioState->setText(QLatin1String("State: Disconnected"));
        } else if (stream->type() == Tp::MediaStreamTypeVideo) {
            mBtnSendVideo->blockSignals(true);
            mBtnSendVideo->setChecked(false);
            mBtnSendVideo->blockSignals(false);
            mLblVideoDirection->setText(QLatin1String("Direction: None"));
            mLblVideoState->setText(QLatin1String("State: Disconnected"));
        }
    }

    void CallWidget::onStreamDirectionChanged(const MediaStreamPtr &stream,
            Tp::MediaStreamDirection direction,
            Tp::MediaStreamPendingSend pendingSend)
    {
        qDebug() << "CallWidget::onStreamDirectionChanged:" <<
            (stream->type() == Tp::MediaStreamTypeAudio ? "Audio" : "Video") <<
            "stream direction changed to" << direction;

        QLabel *lbl = 0;
        QPushButton *btn = 0;
        if (stream->type() == Tp::MediaStreamTypeAudio) {
            lbl = mLblAudioDirection;
            btn = mBtnSendAudio;
        } else if (stream->type() == Tp::MediaStreamTypeVideo) {
            lbl = mLblVideoDirection;
            btn = mBtnSendVideo;
        } else {
            Q_ASSERT(false);
        }

        if (direction == Tp::MediaStreamDirectionSend) {
            lbl->setText(QLatin1String("Direction: Sending"));
        } else if (direction == Tp::MediaStreamDirectionReceive) {
            lbl->setText(QLatin1String("Direction: Receiving"));
        } else if (direction == (Tp::MediaStreamDirectionSend | Tp::MediaStreamDirectionReceive)) {
            lbl->setText(QLatin1String("Direction: Sending/Receiving"));
        } else {
            lbl->setText(QLatin1String("Direction: None"));
        }

        btn->blockSignals(true);
        btn->setChecked(direction & Tp::MediaStreamDirectionSend);
        btn->blockSignals(false);
    }

    void CallWidget::onStreamStateChanged(const MediaStreamPtr &stream,
            Tp::MediaStreamState state)
    {
        qDebug() << "CallWidget::onStreamStateChanged:" <<
            (stream->type() == Tp::MediaStreamTypeAudio ? "Audio" : "Video") <<
            "stream state changed to" << state;

        QLabel *lbl = 0;
        if (stream->type() == Tp::MediaStreamTypeAudio) {
            lbl = mLblAudioState;
        } else if (stream->type() == Tp::MediaStreamTypeVideo) {
            lbl = mLblVideoState;
        } else {
            // ignore unknown streams
            return;
        }
        if (state == Tp::MediaStreamStateDisconnected) {
            lbl->setText(QLatin1String("State: Disconnected"));
        } else if (state == Tp::MediaStreamStateConnecting) {
            lbl->setText(QLatin1String("State: Connecting"));
        } else if (state == Tp::MediaStreamStateConnected) {
            lbl->setText(QLatin1String("State: Connected"));
        }
    }

    void CallWidget::onTfChannelStatusChanged(FarsightChannel::Status status)
    {
        switch (status) {
        case FarsightChannel::StatusConnecting:
            mStatusBar->showMessage(QLatin1String("Connecting..."));
            break;
        case FarsightChannel::StatusConnected:
            mStatusBar->showMessage(QLatin1String("Connected"));
            break;
        case FarsightChannel::StatusDisconnected:
            mChan->requestClose();
            callEnded(QLatin1String("Call terminated"));
            break;
        }
    }

    void CallWidget::onBtnHangupClicked()
    {
        mChan->requestClose();
        callEnded(QLatin1String("Call terminated"));
    }

    void CallWidget::onBtnSendAudioToggled(bool checked)
    {
        MediaStreamPtr stream =
            streamForType(Tp::MediaStreamTypeAudio);
        qDebug() << "CallWidget::onBtnSendAudioToggled: checked:" << checked;
        if (!stream) {
            if (mPmsAudio) {
                return;
            }
            qDebug() << "CallWidget::onBtnSendAudioToggled: creating audio stream";
            mPmsAudio = mChan->requestStream(mContact, Tp::MediaStreamTypeAudio);
            connect(mPmsAudio,
                    SIGNAL(finished(Tp::PendingOperation*)),
                    SLOT(onStreamCreated(Tp::PendingOperation*)));
        } else {
            updateStreamDirection(stream);
        }
    }

    void CallWidget::onBtnSendVideoToggled(bool checked)
    {
        MediaStreamPtr stream =
            streamForType(Tp::MediaStreamTypeVideo);
        qDebug() << "CallWidget::onBtnSendVideoToggled: checked:" << checked;
        if (!stream) {
            if (mPmsVideo) {
                return;
            }
            qDebug() << "CallWidget::onBtnSendVideoToggled: creating video stream";
            mPmsVideo = mChan->requestStream(mContact, Tp::MediaStreamTypeVideo);
            connect(mPmsVideo,
                    SIGNAL(finished(Tp::PendingOperation*)),
                    SLOT(onStreamCreated(Tp::PendingOperation*)));
        } else {
            updateStreamDirection(stream);
        }
    }

    MediaStreamPtr CallWidget::streamForType(Tp::MediaStreamType type) const
    {
        MediaStreams streams = mChan->streams();
        foreach (const MediaStreamPtr &stream, streams) {
            if (stream->type() == type) {
                return stream;
            }
        }
        return MediaStreamPtr();
    }

    void CallWidget::updateStreamDirection(const MediaStreamPtr &stream)
    {
        bool checked = false;
        if (stream->type() == Tp::MediaStreamTypeAudio) {
            checked = mBtnSendAudio->isChecked();
        } else if (stream->type() == Tp::MediaStreamTypeVideo) {
            checked = mBtnSendVideo->isChecked();
        } else {
            Q_ASSERT(false);
        }

        qDebug() << "CallWidget::updateStreamDirection: updating" <<
            (stream->type() == Tp::MediaStreamTypeAudio ? "audio" : "video") <<
            "stream direction";

        if (checked) {
            if (!(stream->direction() & Tp::MediaStreamDirectionSend)) {
                int dir = stream->direction() | Tp::MediaStreamDirectionSend;
                stream->requestDirection((Tp::MediaStreamDirection) dir);
                qDebug() << "CallWidget::updateStreamDirection: start sending" <<
                    (stream->type() == Tp::MediaStreamTypeAudio ? "audio" : "video");
            } else {
                qDebug() << "CallWidget::updateStreamDirection:" <<
                    (stream->type() == Tp::MediaStreamTypeAudio ? "audio" : "video") <<
                    "stream updated";
            }
        } else {
            if (stream->direction() & Tp::MediaStreamDirectionSend) {
                int dir = stream->direction() & ~Tp::MediaStreamDirectionSend;
                qDebug() << "CallWidget::updateStreamDirection: stop sending " <<
                    (stream->type() == Tp::MediaStreamTypeAudio ? "audio" : "video");
                stream->requestDirection((Tp::MediaStreamDirection) dir);
            } else {
                qDebug() << "CallWidget::updateStreamDirection:" <<
                    (stream->type() == Tp::MediaStreamTypeAudio ? "audio" : "video") <<
                    "stream updated";
            }
        }
    }

    void CallWidget::callEnded(const QString &message)
    {
        mStatusBar->showMessage(message);
        disconnect(mChan.data(),
                   SIGNAL(invalidated(Tp::DBusProxy *, const QString &, const QString &)),
                   this,
                   SLOT(onChannelInvalidated(Tp::DBusProxy *, const QString &, const QString &)));
        disconnect(mTfChan,
                   SIGNAL(statusChanged(Tp::FarsightChannel::Status)),
                   this,
                   SLOT(onTfChannelStatusChanged(Tp::FarsightChannel::Status)));
        setEnabled(false);
    }