blob: 969abcf0c88e6704772bb0223e1b129f4e127092 [file] [log] [blame]
/***************************************************************************
* Copyright (C) 2015-2017 by Savoir-faire Linux *
* Author: Edric Ladent Milaret <edric.ladent-milaret@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 <memory>
#include "videooverlay.h"
#include "selectareadialog.h"
VideoView::VideoView(QWidget* parent) :
QWidget(parent),
ui(new Ui::VideoView)
{
ui->setupUi(this);
connect(&CallModel::instance(), SIGNAL(callStateChanged(Call*, Call::State)),
this, SLOT(callStateChanged(Call*, Call::State)));
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(overlay_, &VideoOverlay::videoCfgBtnClicked, [=](){emit videoSettingsClicked();});
}
VideoView::~VideoView()
{
delete ui;
delete overlay_;
delete fadeAnim_;
}
void
VideoView::resizeEvent(QResizeEvent* event)
{
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(1);
if (previewRect.right() >= width())
previewRect.moveRight(width() - 1);
if (previewRect.top() <= 0)
previewRect.moveTop(1);
if (previewRect.bottom() >= height())
previewRect.moveBottom(height() - 1);
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::callStateChanged(Call* call, Call::State previousState)
{
Q_UNUSED(previousState)
if (call->state() == Call::State::CURRENT) {
ui->videoWidget->show();
timerConnection_ = connect(call, SIGNAL(changed()), this, SLOT(updateCall()));
} else {
QObject::disconnect(timerConnection_);
try {
if (call) {
emit closing(call->historyId().toStdString());
}
} catch (...) {
qWarning() << "VideoView::callStateChanged except";
}
}
}
void
VideoView::showChatviewIfToggled()
{
emit setChatVisibility(overlay_->getShowChatView());
}
void
VideoView::simulateShowChatview(bool checked)
{
Q_UNUSED(checked);
overlay_->simulateShowChatview(true);
}
void
VideoView::updateCall()
{
if (auto call = CallModel::instance().selectedCall()) {
overlay_->setName(call->formattedName());
}
}
void
VideoView::mouseDoubleClickEvent(QMouseEvent* e) {
QWidget::mouseDoubleClickEvent(e);
toggleFullScreen();
}
void
VideoView::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls())
event->acceptProposedAction();
}
void
VideoView::dropEvent(QDropEvent* event)
{
auto urls = event->mimeData()->urls();
auto selectedConvUid = LRCInstance::getSelectedConvUid();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
auto callList = CallModel::instance().getActiveCalls();
Call* thisCall = nullptr;
for (auto call : callList) {
if (call->historyId() == QString::fromStdString(conversation->callId)) {
thisCall = call;
break;
}
}
if (thisCall) {
if (auto outVideo = thisCall->firstMedia<media::Video>(media::Media::Direction::OUT)) {
outVideo->sourceModel()->setFile(urls.at(0));
}
}
}
void
VideoView::toggleFullScreen()
{
emit toggleFullScreenClicked();
}
void
VideoView::showContextMenu(const QPoint& pos)
{
QPoint globalPos = this->mapToGlobal(pos);
QMenu menu;
media::Video* outVideo = nullptr;
int activeIndex = -1;
auto selectedConvUid = LRCInstance::getSelectedConvUid();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
auto callList = CallModel::instance().getActiveCalls();
Call* thisCall = nullptr;
for (auto call : callList) {
if (call->historyId() == QString::fromStdString(conversation->callId)) {
thisCall = call;
break;
}
}
if (thisCall) {
outVideo = thisCall->firstMedia<media::Video>(media::Media::Direction::OUT);
if (outVideo)
activeIndex = outVideo->sourceModel()->activeIndex();
}
for (auto device : Video::DeviceModel::instance().devices()) {
std::unique_ptr<QAction> deviceAction(new QAction(device->name(), this));
deviceAction->setCheckable(true);
if (outVideo)
if (outVideo->sourceModel()->getDeviceIndex(device) == activeIndex)
deviceAction->setChecked(true);
auto ptr = deviceAction.release();
menu.addAction(ptr);
connect(ptr, &QAction::toggled, [=](bool checked) {
if (checked == true) {
if (outVideo)
outVideo->sourceModel()->switchTo(device);
Video::DeviceModel::instance().setActive(device);
}
});
}
menu.addSeparator();
auto shareAction = new QAction(tr("Share entire screen"), this);
menu.addAction(shareAction);
shareAction->setCheckable(true);
auto shareAreaAction = new QAction(tr("Share screen area"), this);
menu.addAction(shareAreaAction);
shareAreaAction->setCheckable(true);
connect(shareAreaAction, &QAction::triggered, [=]() {
SelectAreaDialog selec;
selec.exec();
});
auto shareFileAction = new QAction(tr("Share file"), this);
menu.addAction(shareFileAction);
shareFileAction->setCheckable(true);
switch(activeIndex) {
case Video::SourceModel::ExtendedDeviceList::SCREEN:
shareAction->setChecked(true);
break;
case Video::SourceModel::ExtendedDeviceList::FILE:
shareFileAction->setChecked(true);
break;
}
connect(shareAction, &QAction::triggered, [=]() {
if (outVideo) {
auto realRect = QApplication::desktop()->geometry();
realRect.setWidth(static_cast<int>(realRect.width() * QApplication::primaryScreen()->devicePixelRatio()));
realRect.setHeight(static_cast<int>(realRect.height() * QApplication::primaryScreen()->devicePixelRatio()));
outVideo->sourceModel()->setDisplay(0, realRect);
}
});
connect(shareFileAction, &QAction::triggered, [=]() {
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::AnyFile);
QStringList fileNames;
if (!dialog.exec())
return;
fileNames = dialog.selectedFiles();
if (outVideo)
outVideo->sourceModel()->setFile(QUrl::fromLocalFile(fileNames.at(0)));
});
menu.exec(globalPos);
}
void
VideoView::pushRenderer(const std::string& callId) {
auto callModel = LRCInstance::getCurrentCallModel();
QObject::disconnect(videoStartedConnection_);
if (!callModel->hasCall(callId)) {
return;
}
auto call = callModel->getCall(callId);
this->overlay_->callStarted(callId);
this->overlay_->setVideoMuteVisibility(!LRCInstance::getCurrentCallModel()->getCall(callId).isAudioOnly);
videoStartedConnection_ = QObject::connect(callModel, &lrc::api::NewCallModel::remotePreviewStarted,
[this](const std::string& callId, Video::Renderer* renderer) {
Q_UNUSED(callId);
slotVideoStarted(renderer);
});
ui->videoWidget->setPreviewDisplay(call.type != lrc::api::call::Type::CONFERENCE);
}
void
VideoView::slotVideoStarted(Video::Renderer* renderer) {
ui->videoWidget->show();
ui->videoWidget->setDistantRenderer(renderer);
}
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());
}