Skip to content
Snippets Groups Projects
Select Git revision
  • a4b758611c12de4d8867af8dfc8b34a85a2d32f7
  • master default protected
  • nightly/20250806.0
  • nightly/20250805.0
  • beta/202508051403
  • beta/202508051107
  • nightly/20250722.0
  • beta/202507211539
  • stable/20250718.0
  • nightly/20250718.0
  • nightly/20250714.0
  • beta/202507141552
  • beta/202506161038
  • stable/20250613.0
  • nightly/20250613.0
  • beta/202506101658
  • stable/20250610.0
  • nightly/20250610.0
  • beta/202506091027
  • beta/202506061543
  • nightly/20250605.0
  • beta/202506051039
22 results

SBSMessageBase.qml

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    videoview.cpp 14.22 KiB
    /***************************************************************************
     * Copyright (C) 2015-2019 by Savoir-faire Linux                           *
     * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
     * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
     *                                                                         *
     * 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 3 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, see <http://www.gnu.org/licenses/>.   *
     **************************************************************************/
    
    #include "videoview.h"
    #include "ui_videoview.h"
    
    #include "utils.h"
    #include "lrcinstance.h"
    
    #include <QGraphicsOpacityEffect>
    #include <QPropertyAnimation>
    #include <QDesktopWidget>
    #include <QMenu>
    #include <QFileDialog>
    #include <QMimeData>
    #include <QSplitter>
    #include <QScreen>
    #include <QWindow>
    
    #include <memory>
    
    #include "videooverlay.h"
    #include "selectareadialog.h"
    
    VideoView::VideoView(QWidget* parent) :
        QWidget(parent),
        ui(new Ui::VideoView)
    {
        ui->setupUi(this);
    
        overlay_ = new VideoOverlay(this);
        auto effect = new QGraphicsOpacityEffect(overlay_);
        effect->setOpacity(maxOverlayOpacity_);
        overlay_->setGraphicsEffect(effect);
        fadeAnim_ = new QPropertyAnimation(this);
        fadeAnim_->setTargetObject(effect);
        fadeAnim_->setPropertyName("opacity");
        fadeAnim_->setDuration(fadeOverlayTime_);
        fadeAnim_->setStartValue(effect->opacity());
        fadeAnim_->setEndValue(0);
        fadeAnim_->setEasingCurve(QEasingCurve::OutQuad);
    
        // Setup the timer to start the fade when the mouse stops moving
        this->setMouseTracking(true);
        overlay_->setMouseTracking(true);
        fadeTimer_.setSingleShot(true);
        connect(&fadeTimer_, SIGNAL(timeout()), this, SLOT(fadeOverlayOut()));
    
        this->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(showContextMenu(const QPoint&)));
        connect(overlay_, &VideoOverlay::setChatVisibility, [=](bool visible) {
            emit this->setChatVisibility(visible);
        connect(this, SIGNAL(toggleFullScreenClicked()), ui->videoWidget, SLOT(slotToggleFullScreenClicked()));
        });
    
    }
    
    VideoView::~VideoView()
    {
        delete ui;
        delete overlay_;
        delete fadeAnim_;
    }
    
    void
    VideoView::resizeEvent(QResizeEvent* event)
    {
        int marginWidth = ui->videoWidget->getPreviewMargin();
        QRect& previewRect = ui->videoWidget->getPreviewRect();
        int deltaW = event->size().width() - event->oldSize().width();
        int deltaH = event->size().height() - event->oldSize().height();
    
        QPoint previewCenter = ui->videoWidget->getPreviewRect().center();
        int cx = (event->oldSize().width()) / 2;
        int cy = (event->oldSize().height()) / 2;
        QPoint center = QPoint(cx, cy);
    
        // first we check if we want to displace the preview
        if (previewRect.x() + deltaW > 0 && previewRect.y() + deltaH > 0) {
            // then we check which way
            if (center.x() - previewCenter.x() < 0 && center.y() - previewCenter.y() < 0)
                ui->videoWidget->getPreviewRect().translate(deltaW, deltaH);
            else if (center.x() - previewCenter.x() > 0 && center.y() - previewCenter.y() < 0)
                ui->videoWidget->getPreviewRect().translate(0, deltaH);
            else if (center.x() - previewCenter.x() < 0 && center.y() - previewCenter.y() > 0)
                ui->videoWidget->getPreviewRect().translate(deltaW, 0);
        }
    
        if (previewRect.left() <= 0)
            previewRect.moveLeft(marginWidth);
        previewRect.moveRight(width() - marginWidth);
    
        if (previewRect.right() >= width())
            previewRect.moveRight(width() - marginWidth);
    
        if (previewRect.top() <= 0)
            previewRect.moveTop(marginWidth);
        previewRect.moveBottom(height() - marginWidth);
    
        if (previewRect.bottom() >= height())
            previewRect.moveBottom(height() - marginWidth);
    
        ui->videoWidget->resetPreview();
    
        overlay_->resize(this->size());
        overlay_->show();
        overlay_->raise();
    }
    
    void
    VideoView::enterEvent(QEvent* event)
    {
        Q_UNUSED(event)
        showOverlay();
    }
    
    void
    VideoView::leaveEvent(QEvent* event)
    {
        Q_UNUSED(event)
        fadeOverlayOut();
    }
    
    void
    VideoView::showOverlay()
    {
        fadeAnim_->stop();
        fadeAnim_->targetObject()->setProperty(fadeAnim_->propertyName(), fadeAnim_->startValue());
    }
    
    void
    VideoView::fadeOverlayOut()
    {
        if (!overlay_->isDialogVisible() && !overlay_->shouldShowOverlay()) {
            fadeAnim_->start(QAbstractAnimation::KeepWhenStopped);
        }
    }
    
    void
    VideoView::slotCallStatusChanged(const std::string& callId)
    {
        using namespace lrc::api::call;
        auto call = LRCInstance::getCurrentCallModel()->getCall(callId);
        switch (call.status) {
        case Status::IN_PROGRESS:
        {
            ui->videoWidget->show();
            auto convInfo = Utils::getConversationFromCallId(call.id);
            if (!convInfo.uid.empty()) {
                auto contactInfo = LRCInstance::getCurrentAccountInfo().contactModel->getContact(convInfo.participants[0]);
                auto contactName = Utils::bestNameForContact(contactInfo);
                overlay_->setName(QString::fromStdString(contactName));
            }
            return;
        }
        case Status::ENDED:
            emit closing(call.id);
        default:
            //emit closing(call.id);
            break;
        }
        QObject::disconnect(timerConnection_);
    }
    
    void
    VideoView::simulateShowChatview(bool checked)
    {
        Q_UNUSED(checked);
        overlay_->simulateShowChatview(true);
    }
    
    void
    VideoView::mouseDoubleClickEvent(QMouseEvent* e) {
        QWidget::mouseDoubleClickEvent(e);
        toggleFullScreen();
    }
    
    void
    VideoView::dragEnterEvent(QDragEnterEvent* event)
    {
        if (event->mimeData()->hasUrls())
            event->accept();
    }
    
    void
    VideoView::dragLeaveEvent(QDragLeaveEvent* event)
    {
        event->accept();
    }
    void
    VideoView::dragMoveEvent(QDragMoveEvent* event)
    {
        if (event->mimeData()->hasUrls())
            event->accept();
    }
    
    void
    VideoView::dropEvent(QDropEvent* event)
    {
        // take only the first file
        QString urlString = event->mimeData()->urls().at(0).toString();
        auto selectedConvUid = LRCInstance::getSelectedConvUid();
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
        auto callIdList = LRCInstance::getActiveCalls();
        for (auto callId : callIdList) {
            if (callId == conversation->callId) {
                LRCInstance::avModel().setInputFile(urlString.toStdString());
                break;
            }
        }
    }
    
    void
    VideoView::toggleFullScreen()
    {
        emit toggleFullScreenClicked();
    }
    
    void
    VideoView::showContextMenu(const QPoint& pos)
    {
        QPoint globalPos = this->mapToGlobal(pos);
    
        QMenu menu;
    
        auto selectedConvUid = LRCInstance::getSelectedConvUid();
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
        auto callIdList = LRCInstance::getActiveCalls();
        std::string thisCallId;
        for (auto callId : callIdList) {
            if (callId == conversation->callId) {
                thisCallId = callId;
                break;
            }
        }
        if (thisCallId.empty()) {
            return;
        }
    
        auto activeDevice = LRCInstance::avModel().getCurrentRenderedDevice(thisCallId);
    
        // video input devices
        auto devices = LRCInstance::avModel().getDevices();
        auto device = LRCInstance::avModel().getDefaultDeviceName();
        for (auto d : devices) {
            auto deviceName = QString::fromStdString(d).toUtf8();
            auto deviceAction = new QAction(deviceName, this);
            menu.addAction(deviceAction);
            deviceAction->setCheckable(true);
            if (d == activeDevice.name) {
                deviceAction->setChecked(true);
            }
            connect(deviceAction, &QAction::triggered,
                [this, deviceName, thisCallId]() {
                    LRCInstance::avModel().switchInputTo(deviceName.toStdString());
                });
        }
    
        menu.addSeparator();
    
        // entire screen share
        auto shareAction = new QAction(tr("Share entire screen"), this);
        menu.addAction(shareAction);
        shareAction->setCheckable(true);
        connect(shareAction, &QAction::triggered,
            [this]() {
                auto screenNumber = qApp->desktop()->screenNumber(this);
                QScreen* screen = qApp->screens().at(screenNumber);
                QRect rect = screen ? screen->geometry() : qApp->primaryScreen()->geometry();
    #if defined(Q_OS_WIN) && (PROCESS_DPI_AWARE)
                rect.setSize(Utils::getRealSize(screen));
    #endif
                LRCInstance::avModel().setDisplay(screenNumber,
                    rect.x(), rect.y(), rect.width(), rect.height()
                );
                sharingEntireScreen_ = true;
            });
    
        // area of screen share
        auto shareAreaAction = new QAction(tr("Share screen area"), this);
        menu.addAction(shareAreaAction);
        shareAreaAction->setCheckable(true);
        connect(shareAreaAction, &QAction::triggered,
            [this]() {
                SelectAreaDialog selectAreaDialog;
                selectAreaDialog.exec();
                sharingEntireScreen_ = false;
            });
    
        // share a media file
        auto shareFileAction = new QAction(tr("Share file"), this);
        menu.addAction(shareFileAction);
        shareFileAction->setCheckable(true);
        connect(shareFileAction, &QAction::triggered,
            [this]() {
                QFileDialog fileDialog(this);
                fileDialog.setFileMode(QFileDialog::AnyFile);
                QStringList fileNames;
                if (!fileDialog.exec())
                    return;
                fileNames = fileDialog.selectedFiles();
                auto resource = QUrl::fromLocalFile(fileNames.at(0)).toString();
                LRCInstance::avModel().setInputFile(resource.toStdString());
            });
    
        // possibly select the alternative video sharing device
        switch (activeDevice.type) {
        case lrc::api::video::DeviceType::DISPLAY:
            sharingEntireScreen_ ?
                shareAction->setChecked(true) :
                shareAreaAction->setChecked(true);
            break;
        case lrc::api::video::DeviceType::FILE:
            shareFileAction->setChecked(true);
            break;
        default:
            // a camera must have already been selected
            break;
        }
    
        menu.exec(globalPos);
    }
    
    void
    VideoView::pushRenderer(const std::string& callId, bool isSIP) {
        auto callModel = LRCInstance::getCurrentCallModel();
    
        QObject::disconnect(ui->videoWidget);
        QObject::disconnect(callStatusChangedConnection_);
    
        if (!callModel->hasCall(callId)) {
            return;
        }
    
        auto call = callModel->getCall(callId);
    
        if (call.isAudioOnly) {
            return;
        }
    
        // transfer call will only happen in SIP calls
        this->overlay_->setTransferCallAvailability(isSIP);
        this->overlay_->callStarted(callId);
        this->overlay_->setVideoMuteVisibility(!LRCInstance::getCurrentCallModel()->getCall(callId).isAudioOnly);
    
        callStatusChangedConnection_ = QObject::connect(callModel, &lrc::api::NewCallModel::callStatusChanged,
            this, &VideoView::slotCallStatusChanged);
    
        ui->videoWidget->connectRendering();
        ui->videoWidget->setPreviewDisplay(call.type != lrc::api::call::Type::CONFERENCE);
    }
    
    void
    VideoView::mousePressEvent(QMouseEvent* event)
    {
        QPoint clickPosition = event->pos();
        if (ui->videoWidget->getPreviewRect().contains(clickPosition)) {
            QLine distance = QLine(clickPosition, ui->videoWidget->getPreviewRect().bottomRight());
            if (distance.dy() < resizeGrip_ and distance.dx() < resizeGrip_) {
                QApplication::setOverrideCursor(Qt::SizeFDiagCursor);
                resizingPreview_ = true;
            } else {
                originMouseDisplacement_ = event->pos() - ui->videoWidget->getPreviewRect().topLeft();
                QApplication::setOverrideCursor(Qt::SizeAllCursor);
                draggingPreview_ = true;
            }
        }
    }
    
    void
    VideoView::mouseReleaseEvent(QMouseEvent* event)
    {
        Q_UNUSED(event)
    
        draggingPreview_ = false;
        resizingPreview_ = false;
        QApplication::setOverrideCursor(Qt::ArrowCursor);
    }
    
    void
    VideoView::mouseMoveEvent(QMouseEvent* event)
    {
        // start/restart the timer after which the overlay will fade
        if (fadeTimer_.isActive()) {
            showOverlay();
        } else {
            fadeTimer_.start(startfadeOverlayTime_);
        }
    
        QRect& previewRect =  ui->videoWidget->getPreviewRect();
        if (draggingPreview_) {
            if (previewRect.left() > 0
                    && previewRect.top() > 0
                    && previewRect.right() < width()
                    && previewRect.bottom() < height()) {
    
                previewRect.moveTo(event->pos() - originMouseDisplacement_);
                if (previewRect.left() <= 0)
                    previewRect.moveLeft(1);
    
                if (previewRect.right() >= width())
                    previewRect.moveRight(width() - 1);
    
                if (previewRect.top() <= 0)
                    previewRect.moveTop(1);
    
                if (previewRect.bottom() >= height())
                    previewRect.moveBottom(height() - 1);
            }
        }
    
        QLine distance = QLine(previewRect.topLeft(), event->pos());
    
        if (resizingPreview_
                and distance.dx() > minimalSize_
                and distance.dy() > minimalSize_
                and geometry().contains(event->pos()))
            previewRect.setBottomRight(event->pos());
    }
    
    void
    VideoView::setCurrentCalleeName(const QString& CalleeDisplayName)
    {
        overlay_->setCurrentSelectedCalleeDisplayName(CalleeDisplayName);
    }
    
    void
    VideoView::resetVideoOverlay(bool isAudioMuted, bool isVideoMuted, bool isRecording, bool isHolding)
    {
        emit overlay_->setChatVisibility(false);
        overlay_->resetOverlay(isAudioMuted, isVideoMuted, isRecording, isHolding);
    }