blob: 5a9c70a78649789059985d4b82fe03798b577004 [file] [log] [blame]
Edric Milaret029b95a2015-06-09 09:51:44 -04001/***************************************************************************
Anthony LĂ©onard2fde81d2017-04-17 10:06:55 -04002 * Copyright (C) 2015-2017 by Savoir-faire Linux *
Edric Milaret029b95a2015-06-09 09:51:44 -04003 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 3 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 **************************************************************************/
18
19#include "videoview.h"
20#include "ui_videoview.h"
21
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040022#include "utils.h"
23#include "lrcinstance.h"
24
Edric Milaret029b95a2015-06-09 09:51:44 -040025#include "video/devicemodel.h"
26#include "video/sourcemodel.h"
Edric Milareted0b2802015-10-01 15:10:02 -040027#include "recentmodel.h"
Edric Milaret0e1074d2015-12-08 12:08:52 -050028#include "media/video.h"
Edric Milaret029b95a2015-06-09 09:51:44 -040029
30#include <QGraphicsOpacityEffect>
31#include <QPropertyAnimation>
32#include <QDesktopWidget>
33#include <QMenu>
Edric Milaret303bbf12015-06-22 14:24:13 -040034#include <QFileDialog>
35#include <QMimeData>
Nicolas Jagere0854512016-02-29 12:08:10 -050036#include <QSplitter>
Edricb09982a2016-05-19 16:28:38 -040037#include <QScreen>
Edric Milaret029b95a2015-06-09 09:51:44 -040038
39#include <memory>
40
41#include "videooverlay.h"
42#include "selectareadialog.h"
43
Edric Milaret80e0b212015-10-16 10:07:43 -040044VideoView::VideoView(QWidget* parent) :
Edric Milaret029b95a2015-06-09 09:51:44 -040045 QWidget(parent),
46 ui(new Ui::VideoView)
47{
48 ui->setupUi(this);
49
Edric Milareta3e47282015-10-23 15:20:30 -040050 connect(&CallModel::instance(), SIGNAL(callStateChanged(Call*, Call::State)),
Edric Milaret029b95a2015-06-09 09:51:44 -040051 this, SLOT(callStateChanged(Call*, Call::State)));
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040052
Edric Milaret029b95a2015-06-09 09:51:44 -040053 overlay_ = new VideoOverlay(this);
54 auto effect = new QGraphicsOpacityEffect(overlay_);
Andreas Traczyk8d5e5492018-08-09 15:41:52 -040055 effect->setOpacity(maxOverlayOpacity_);
Edric Milaret029b95a2015-06-09 09:51:44 -040056 overlay_->setGraphicsEffect(effect);
57 fadeAnim_ = new QPropertyAnimation(this);
58 fadeAnim_->setTargetObject(effect);
59 fadeAnim_->setPropertyName("opacity");
60 fadeAnim_->setDuration(fadeOverlayTime_);
61 fadeAnim_->setStartValue(effect->opacity());
62 fadeAnim_->setEndValue(0);
63 fadeAnim_->setEasingCurve(QEasingCurve::OutQuad);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040064
Andreas Traczykca320d82018-08-09 18:02:07 -040065 // Setup the timer to start the fade when the mouse stops moving
66 this->setMouseTracking(true);
67 overlay_->setMouseTracking(true);
68 fadeTimer_.setSingleShot(true);
69 connect(&fadeTimer_, SIGNAL(timeout()), this, SLOT(fadeOverlayOut()));
Edric Milaret029b95a2015-06-09 09:51:44 -040070
Edric Milaret029b95a2015-06-09 09:51:44 -040071 this->setContextMenuPolicy(Qt::CustomContextMenu);
72 connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
73 this, SLOT(showContextMenu(const QPoint&)));
Edric Milaret3aca8e32015-06-12 10:01:40 -040074 connect(overlay_, &VideoOverlay::setChatVisibility, [=](bool visible) {
75 emit this->setChatVisibility(visible);
76 });
Olivier SOLDANO2bcdfd72017-05-02 14:37:19 -040077 connect(overlay_, &VideoOverlay::videoCfgBtnClicked, [=](){emit videoSettingsClicked();});
Edric Milaret029b95a2015-06-09 09:51:44 -040078}
79
80VideoView::~VideoView()
81{
82 delete ui;
Edric Milaret01f23842015-06-22 14:46:01 -040083 delete overlay_;
Edric Milaret01f23842015-06-22 14:46:01 -040084 delete fadeAnim_;
Edric Milaret029b95a2015-06-09 09:51:44 -040085}
86
87void
Edric Milaret80e0b212015-10-16 10:07:43 -040088VideoView::resizeEvent(QResizeEvent* event)
Edric Milaret029b95a2015-06-09 09:51:44 -040089{
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -050090 QRect& previewRect = ui->videoWidget->getPreviewRect();
91 int deltaW = event->size().width() - event->oldSize().width();
92 int deltaH = event->size().height() - event->oldSize().height();
93
94 QPoint previewCenter = ui->videoWidget->getPreviewRect().center();
Edricb09982a2016-05-19 16:28:38 -040095 int cx = (event->oldSize().width()) / 2;
96 int cy = (event->oldSize().height()) / 2;
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -050097 QPoint center = QPoint(cx, cy);
98
99 // first we check if we want to displace the preview
100 if (previewRect.x() + deltaW > 0 && previewRect.y() + deltaH > 0) {
101 // then we check which way
102 if (center.x() - previewCenter.x() < 0 && center.y() - previewCenter.y() < 0)
103 ui->videoWidget->getPreviewRect().translate(deltaW, deltaH);
104 else if (center.x() - previewCenter.x() > 0 && center.y() - previewCenter.y() < 0)
105 ui->videoWidget->getPreviewRect().translate(0, deltaH);
106 else if (center.x() - previewCenter.x() < 0 && center.y() - previewCenter.y() > 0)
107 ui->videoWidget->getPreviewRect().translate(deltaW, 0);
108 }
109
110 if (previewRect.left() <= 0)
111 previewRect.moveLeft(1);
112
113 if (previewRect.right() >= width())
114 previewRect.moveRight(width() - 1);
115
116 if (previewRect.top() <= 0)
117 previewRect.moveTop(1);
118
119 if (previewRect.bottom() >= height())
120 previewRect.moveBottom(height() - 1);
121
Edric Milaret029b95a2015-06-09 09:51:44 -0400122 overlay_->resize(this->size());
123 overlay_->show();
124 overlay_->raise();
125}
126
127void
128VideoView::enterEvent(QEvent* event)
129{
130 Q_UNUSED(event)
Andreas Traczykca320d82018-08-09 18:02:07 -0400131 showOverlay();
Edric Milaret029b95a2015-06-09 09:51:44 -0400132}
133
134void
135VideoView::leaveEvent(QEvent* event)
136{
137 Q_UNUSED(event)
Andreas Traczykca320d82018-08-09 18:02:07 -0400138 fadeOverlayOut();
139}
140
141void
142VideoView::showOverlay()
143{
144 fadeAnim_->stop();
145 fadeAnim_->targetObject()->setProperty(fadeAnim_->propertyName(), fadeAnim_->startValue());
146}
147
148void
149VideoView::fadeOverlayOut()
150{
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400151 if (!overlay_->isDialogVisible() && !overlay_->shouldShowOverlay()) {
Nicolas Jager0a9fc602016-03-11 18:35:42 -0500152 fadeAnim_->start(QAbstractAnimation::KeepWhenStopped);
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400153 }
Edric Milaret029b95a2015-06-09 09:51:44 -0400154}
155
156void
157VideoView::callStateChanged(Call* call, Call::State previousState)
158{
159 Q_UNUSED(previousState)
Edric Milaret80e0b212015-10-16 10:07:43 -0400160
Edric Milaret029b95a2015-06-09 09:51:44 -0400161 if (call->state() == Call::State::CURRENT) {
162 ui->videoWidget->show();
Edric Milareted0b2802015-10-01 15:10:02 -0400163 timerConnection_ = connect(call, SIGNAL(changed()), this, SLOT(updateCall()));
Edric Milaret029b95a2015-06-09 09:51:44 -0400164 }
165 else {
Edric Milaret80e0b212015-10-16 10:07:43 -0400166 QObject::disconnect(timerConnection_);
Edric Milaret3aca8e32015-06-12 10:01:40 -0400167 emit setChatVisibility(false);
Edric Milaret029b95a2015-06-09 09:51:44 -0400168 if (isFullScreen())
169 toggleFullScreen();
Edric Milaret029b95a2015-06-09 09:51:44 -0400170 }
171}
172
173void
Edric Milareted0b2802015-10-01 15:10:02 -0400174VideoView::updateCall()
Edric Milaret029b95a2015-06-09 09:51:44 -0400175{
Edric Milaret80e0b212015-10-16 10:07:43 -0400176 if (auto call = CallModel::instance().selectedCall()) {
177 overlay_->setName(call->formattedName());
178 overlay_->setTime(call->length());
179 }
Edric Milaret029b95a2015-06-09 09:51:44 -0400180}
181
182void
183VideoView::mouseDoubleClickEvent(QMouseEvent* e) {
184 QWidget::mouseDoubleClickEvent(e);
185 toggleFullScreen();
186}
187
Edric Milaret80e0b212015-10-16 10:07:43 -0400188void
189VideoView::dragEnterEvent(QDragEnterEvent* event)
Edric Milaret303bbf12015-06-22 14:24:13 -0400190{
191 if (event->mimeData()->hasUrls())
192 event->acceptProposedAction();
193}
194
Edric Milaret80e0b212015-10-16 10:07:43 -0400195void
196VideoView::dropEvent(QDropEvent* event)
Edric Milaret303bbf12015-06-22 14:24:13 -0400197{
198 auto urls = event->mimeData()->urls();
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400199 auto selectedConvUid = LRCInstance::getSelectedConvUid();
200 auto convModel = LRCInstance::getCurrentConversationModel();
201 auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
202 auto callList = CallModel::instance().getActiveCalls();
203 Call* thisCall = nullptr;
204 for (auto call : callList) {
205 if (call->historyId() == QString::fromStdString(conversation->callId)) {
206 thisCall = call;
207 break;
208 }
209 }
210 if (thisCall) {
211 if (auto outVideo = thisCall->firstMedia<media::Video>(media::Media::Direction::OUT)) {
Edric Milaret0e1074d2015-12-08 12:08:52 -0500212 outVideo->sourceModel()->setFile(urls.at(0));
213 }
214 }
Edric Milaret303bbf12015-06-22 14:24:13 -0400215}
216
Edric Milaret029b95a2015-06-09 09:51:44 -0400217void
218VideoView::toggleFullScreen()
219{
Olivier SOLDANO223bcc82017-07-19 16:05:05 -0400220 overlay_->toggleContextButtons(isFullScreen());
Edric Milaret029b95a2015-06-09 09:51:44 -0400221 if(isFullScreen()) {
Nicolas Jagere0854512016-02-29 12:08:10 -0500222 dynamic_cast<QSplitter*>(oldParent_)->insertWidget(0,this);
Edric Milaret029b95a2015-06-09 09:51:44 -0400223 this->resize(oldSize_.width(), oldSize_.height());
Edric Milaret58f57622015-12-09 09:57:15 -0500224 this->showNormal();
Edric Milaret029b95a2015-06-09 09:51:44 -0400225 } else {
226 oldSize_ = this->size();
227 oldParent_ = static_cast<QWidget*>(this->parent());
228 this->setParent(0);
229 this->showFullScreen();
Edric Milaret029b95a2015-06-09 09:51:44 -0400230 }
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -0500231 ui->videoWidget->setResetPreview(true);
Edric Milaret029b95a2015-06-09 09:51:44 -0400232}
233
234void
235VideoView::showContextMenu(const QPoint& pos)
236{
237 QPoint globalPos = this->mapToGlobal(pos);
238
239 QMenu menu;
Andreas Traczyke86fb3c2018-08-02 17:38:34 -0400240 media::Video* outVideo = nullptr;
Edric Milaretc4b1a102016-02-02 11:48:30 -0500241 int activeIndex = -1;
242
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400243 auto selectedConvUid = LRCInstance::getSelectedConvUid();
244 auto convModel = LRCInstance::getCurrentConversationModel();
245 auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
246 auto callList = CallModel::instance().getActiveCalls();
247 Call* thisCall = nullptr;
248 for (auto call : callList) {
249 if (call->historyId() == QString::fromStdString(conversation->callId)) {
250 thisCall = call;
251 break;
252 }
253 }
254 if (thisCall) {
255 outVideo = thisCall->firstMedia<media::Video>(media::Media::Direction::OUT);
Edric Milaretc4b1a102016-02-02 11:48:30 -0500256 if (outVideo)
257 activeIndex = outVideo->sourceModel()->activeIndex();
258 }
Edric Milaret029b95a2015-06-09 09:51:44 -0400259
Edric Milareta3e47282015-10-23 15:20:30 -0400260 for (auto device : Video::DeviceModel::instance().devices()) {
Edric Milaret029b95a2015-06-09 09:51:44 -0400261 std::unique_ptr<QAction> deviceAction(new QAction(device->name(), this));
262 deviceAction->setCheckable(true);
Edric Milaretc4b1a102016-02-02 11:48:30 -0500263 if (outVideo)
264 if (outVideo->sourceModel()->getDeviceIndex(device) == activeIndex)
265 deviceAction->setChecked(true);
Edric Milaret029b95a2015-06-09 09:51:44 -0400266 auto ptr = deviceAction.release();
267 menu.addAction(ptr);
268 connect(ptr, &QAction::toggled, [=](bool checked) {
269 if (checked == true) {
Edric Milaretc4b1a102016-02-02 11:48:30 -0500270 if (outVideo)
271 outVideo->sourceModel()->switchTo(device);
Edric Milareta3e47282015-10-23 15:20:30 -0400272 Video::DeviceModel::instance().setActive(device);
Edric Milaret029b95a2015-06-09 09:51:44 -0400273 }
274 });
275 }
276
277 menu.addSeparator();
278
Edric Milaret53ac6e52015-09-14 13:37:06 -0400279 auto shareAction = new QAction(tr("Share entire screen"), this);
Edric Milaret029b95a2015-06-09 09:51:44 -0400280 menu.addAction(shareAction);
Edric Milaretc4b1a102016-02-02 11:48:30 -0500281 shareAction->setCheckable(true);
Edric Milaret53ac6e52015-09-14 13:37:06 -0400282 auto shareAreaAction = new QAction(tr("Share screen area"), this);
Edric Milaret029b95a2015-06-09 09:51:44 -0400283 menu.addAction(shareAreaAction);
Edric Milaretc4b1a102016-02-02 11:48:30 -0500284 shareAreaAction->setCheckable(true);
Edric Milaret029b95a2015-06-09 09:51:44 -0400285 connect(shareAreaAction, &QAction::triggered, [=]() {
286 SelectAreaDialog selec;
287 selec.exec();
288 });
Edric Milaret53ac6e52015-09-14 13:37:06 -0400289 auto shareFileAction = new QAction(tr("Share file"), this);
Edric Milaret303bbf12015-06-22 14:24:13 -0400290 menu.addAction(shareFileAction);
Edric Milaretc4b1a102016-02-02 11:48:30 -0500291 shareFileAction->setCheckable(true);
292
293 switch(activeIndex) {
294
295 case Video::SourceModel::ExtendedDeviceList::SCREEN:
296 shareAction->setChecked(true);
297 break;
298 case Video::SourceModel::ExtendedDeviceList::FILE:
299 shareFileAction->setChecked(true);
300 break;
301
302 }
303
304 connect(shareAction, &QAction::triggered, [=]() {
Edricb09982a2016-05-19 16:28:38 -0400305 if (outVideo) {
306 auto realRect = QApplication::desktop()->geometry();
307 realRect.setWidth(static_cast<int>(realRect.width() * QApplication::primaryScreen()->devicePixelRatio()));
308 realRect.setHeight(static_cast<int>(realRect.height() * QApplication::primaryScreen()->devicePixelRatio()));
309 outVideo->sourceModel()->setDisplay(0, realRect);
310 }
Edric Milaretc4b1a102016-02-02 11:48:30 -0500311 });
Edric Milaret303bbf12015-06-22 14:24:13 -0400312 connect(shareFileAction, &QAction::triggered, [=]() {
313 QFileDialog dialog(this);
314 dialog.setFileMode(QFileDialog::AnyFile);
315 QStringList fileNames;
316 if (!dialog.exec())
317 return;
318 fileNames = dialog.selectedFiles();
Edric Milaretc4b1a102016-02-02 11:48:30 -0500319 if (outVideo)
320 outVideo->sourceModel()->setFile(QUrl::fromLocalFile(fileNames.at(0)));
Edric Milaret303bbf12015-06-22 14:24:13 -0400321 });
Edric Milaret029b95a2015-06-09 09:51:44 -0400322
323 menu.exec(globalPos);
324}
325
Edric Milaret80e0b212015-10-16 10:07:43 -0400326void
327VideoView::pushRenderer(Call* call) {
328 if (not call) {
329 disconnect(videoStartedConnection_);
330 return;
331 }
332 if (auto renderer = call->videoRenderer()) {
333 slotVideoStarted(renderer);
334 } else {
335 disconnect(videoStartedConnection_);
336 videoStartedConnection_ = connect(call,
337 SIGNAL(videoStarted(Video::Renderer*)),
338 this,
339 SLOT(slotVideoStarted(Video::Renderer*)));
340 }
341 ui->videoWidget->setPreviewDisplay(call->type() != Call::Type::CONFERENCE);
342}
343
344void
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400345VideoView::pushRenderer(const std::string& callUid) {
346 auto callModel = LRCInstance::getCurrentCallModel();
347
348 QObject::disconnect(videoStartedConnection_);
349 if (!callModel->hasCall(callUid)) {
350 return;
351 }
352
353 auto call = callModel->getCall(callUid);
354
355 videoStartedConnection_ = QObject::connect(callModel, &lrc::api::NewCallModel::remotePreviewStarted,
356 [this](const std::string& callId, Video::Renderer* renderer) {
357 Q_UNUSED(callId);
358 slotVideoStarted(renderer);
359 this->overlay_->setVideoMuteVisibility(LRCInstance::getCurrentCallModel()->getCall(callId).isAudioOnly);
360 });
361 ui->videoWidget->setPreviewDisplay(call.type != lrc::api::call::Type::CONFERENCE);
362}
363
364void
Edric Milaret80e0b212015-10-16 10:07:43 -0400365VideoView::slotVideoStarted(Video::Renderer* renderer) {
366 ui->videoWidget->show();
367 ui->videoWidget->setDistantRenderer(renderer);
368}
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -0500369
370void
371VideoView::mousePressEvent(QMouseEvent* event)
372{
373 QPoint clickPosition = event->pos();
374 if (ui->videoWidget->getPreviewRect().contains(clickPosition)) {
375 QLine distance = QLine(clickPosition, ui->videoWidget->getPreviewRect().bottomRight());
376 if (distance.dy() < resizeGrip_ and distance.dx() < resizeGrip_) {
377 QApplication::setOverrideCursor(Qt::SizeFDiagCursor);
378 resizingPreview_ = true;
379 } else {
380 originMouseDisplacement_ = event->pos() - ui->videoWidget->getPreviewRect().topLeft();
381 QApplication::setOverrideCursor(Qt::SizeAllCursor);
382 draggingPreview_ = true;
383 }
384 }
385}
386
387void
388VideoView::mouseReleaseEvent(QMouseEvent* event)
389{
Edric Milaret6a785af2016-03-07 15:39:30 -0500390 Q_UNUSED(event)
391
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -0500392 draggingPreview_ = false;
393 resizingPreview_ = false;
394 QApplication::setOverrideCursor(Qt::ArrowCursor);
395}
396
397void
398VideoView::mouseMoveEvent(QMouseEvent* event)
399{
Andreas Traczykca320d82018-08-09 18:02:07 -0400400 // start/restart the timer after which the overlay will fade
401 if (fadeTimer_.isActive()) {
402 showOverlay();
403 } else {
404 fadeTimer_.start(startfadeOverlayTime_);
405 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400406
Nicolas Jagerc7b8e4b2016-02-29 09:20:40 -0500407 QRect& previewRect = ui->videoWidget->getPreviewRect();
408 if (draggingPreview_) {
409 if (previewRect.left() > 0
410 && previewRect.top() > 0
411 && previewRect.right() < width()
412 && previewRect.bottom() < height()) {
413
414 previewRect.moveTo(event->pos() - originMouseDisplacement_);
415 if (previewRect.left() <= 0)
416 previewRect.moveLeft(1);
417
418 if (previewRect.right() >= width())
419 previewRect.moveRight(width() - 1);
420
421 if (previewRect.top() <= 0)
422 previewRect.moveTop(1);
423
424 if (previewRect.bottom() >= height())
425 previewRect.moveBottom(height() - 1);
426 }
427 }
428
429 QLine distance = QLine(previewRect.topLeft(), event->pos());
430
431 if (resizingPreview_
432 and distance.dx() > minimalSize_
433 and distance.dy() > minimalSize_
434 and geometry().contains(event->pos()))
435 previewRect.setBottomRight(event->pos());
436}