videooverlay.cpp 15.8 KB
Newer Older
1
/***************************************************************************
2
 * Copyright (C) 2015-2019 by Savoir-faire Linux                           *
3
 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
4
 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 *                                                                         *
 * 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 "videooverlay.h"
#include "ui_videooverlay.h"

Yang Wang's avatar
Yang Wang committed
23
#include "videoview.h"
24
#include "lrcinstance.h"
25
#include "contactpicker.h"
26
#include "popupdialog.h"
27
#include "sipinputpanel.h"
28
#include "utils.h"
29

Yang Wang's avatar
Yang Wang committed
30
#include <QTime>
31
32
33
34
35

VideoOverlay::VideoOverlay(QWidget* parent)
    : FadeOutable(parent)
    , ui(new Ui::VideoOverlay)
    , oneSecondTimer_(new QTimer(this))
36
37
38
{
    ui->setupUi(this);

39
40
    ui->bottomButtons->setMouseTracking(true);

41
42
    ui->chatButton->setCheckable(true);

43
    ui->onHoldLabel->setVisible(false);
44

45
46
47
    ui->transferCallButton->setVisible(false);
    ui->transferCallButton->setCheckable(true);

Andreas Traczyk's avatar
Andreas Traczyk committed
48
49
50
    ui->addToConferenceButton->setVisible(true);
    ui->addToConferenceButton->setCheckable(true);

51
52
    ui->holdButton->setVisible(false);

53
54
55
56
57
58
59
}

VideoOverlay::~VideoOverlay()
{
    delete ui;
}

Andreas Traczyk's avatar
Andreas Traczyk committed
60
void
Andreas Traczyk's avatar
Andreas Traczyk committed
61
VideoOverlay::updateCall(const conversation::Info& convInfo)
Andreas Traczyk's avatar
Andreas Traczyk committed
62
{
Andreas Traczyk's avatar
Andreas Traczyk committed
63
64
65
    accountId_ = convInfo.accountId;
    convUid_ = convInfo.uid;

66
    setTime();
Andreas Traczyk's avatar
Andreas Traczyk committed
67
68
69
    QObject::disconnect(oneSecondTimer_);
    QObject::connect(oneSecondTimer_, &QTimer::timeout,
                     this, &VideoOverlay::setTime);
70
    oneSecondTimer_->start(20);
71
    resetToFadeIn(true);
Andreas Traczyk's avatar
Andreas Traczyk committed
72

Andreas Traczyk's avatar
Andreas Traczyk committed
73
74
    // close chat panel
    emit setChatVisibility(false);
75

Andreas Traczyk's avatar
Andreas Traczyk committed
76
    auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_);
77

Andreas Traczyk's avatar
Andreas Traczyk committed
78
79
80
81
    auto& convModel = accInfo.conversationModel;

    auto call = LRCInstance::getCallInfoForConversation(convInfo);
    if (!call) {
Andreas Traczyk's avatar
Andreas Traczyk committed
82
83
        return;
    }
Andreas Traczyk's avatar
Andreas Traczyk committed
84

85
    auto bestName = Utils::bestNameForConversation(convInfo, *convModel);
86
    currentBestCalleeDisplayName_ = bestName;
Andreas Traczyk's avatar
Andreas Traczyk committed
87
88
89
90
91
92
93
94
95
96
97
98
    ui->nameLabel->setText(bestName);

    bool isPaused = call->status == lrc::api::call::Status::PAUSED;
    bool isAudioOnly = call->isAudioOnly && !isPaused;
    bool isAudioMuted = call->audioMuted && (call->status != lrc::api::call::Status::PAUSED);
    bool isVideoMuted = call->videoMuted && !isPaused && !call->isAudioOnly;
    bool isRecording = accInfo.callModel->isRecording(convInfo.callId);

    //Set irrelevant buttons invisible
    ui->noVideoButton->setVisible(!isAudioOnly);

    // Block the signals of buttons
99
100
101
102
103
    ui->noMicButton->setOverlayButtonChecked(isAudioMuted);
    ui->noVideoButton->setOverlayButtonChecked(isVideoMuted);
    ui->recButton->setOverlayButtonChecked(isRecording);
    ui->holdButton->setOverlayButtonChecked(isPaused);
    ui->onHoldLabel->setVisible(isPaused);
Andreas Traczyk's avatar
Andreas Traczyk committed
104
105
106
107
108

    bool isSIP = accInfo.profileInfo.type == lrc::api::profile::Type::SIP;
    ui->addToConferenceButton->setVisible(!isSIP);
    ui->transferCallButton->setVisible(isSIP);
    ui->sipInputPanelButton->setVisible(isSIP);
109
110

    // only show the hold/pause button for non-conference calls
111
    ui->holdButton->setVisible(convInfo.confId.isEmpty());
112
113
}

114
void
Andreas Traczyk's avatar
Andreas Traczyk committed
115
VideoOverlay::setTime()
116
{
Andreas Traczyk's avatar
Andreas Traczyk committed
117
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
118
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
Andreas Traczyk's avatar
Andreas Traczyk committed
119
120
121
122
123
124
        return;
    }
    auto callInfo = LRCInstance::getCurrentCallModel()->getCall(callId);
    if (callInfo.status == lrc::api::call::Status::IN_PROGRESS ||
        callInfo.status == lrc::api::call::Status::PAUSED) {
        auto timeString = LRCInstance::getCurrentCallModel()->getFormattedCallDuration(callId);
125
        ui->timerLabel->setText(timeString);
Andreas Traczyk's avatar
Andreas Traczyk committed
126
    }
127
128
129
}

bool
130
VideoOverlay::shouldFadeOut()
131
{
Andreas Traczyk's avatar
Andreas Traczyk committed
132
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
133
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
134
135
        return false;
    }
Andreas Traczyk's avatar
Andreas Traczyk committed
136
    auto callInfo = LRCInstance::getCurrentCallModel()->getCall(callId);
137
    bool hoveringOnButtons = ui->bottomButtons->underMouse() || ui->topInfoBar->underMouse();
138
139
    return not (hoveringOnButtons ||
               (callInfo.status == lrc::api::call::Status::PAUSED) ||
140
                popUpShown_);
141
142
}

143
144
145
146
147
148
149
150
151
152
153
void
VideoOverlay::updateGeometry(const QSize& size)
{
    resize(size);
    QRect endRect = rect();
    setFadeOutStartLocation(endRect);
    endRect.setHeight(endRect.height() + ui->bottomButtons->height() + ui->bottomButtons->height());
    endRect.setY(endRect.y() - ui->bottomButtons->height() - ui->bottomButtons->height());
    setFadeOutEndLocation(endRect);
}

154
155
156
157
158
159
void
VideoOverlay::simulateShowChatview(bool checked)
{
    ui->chatButton->setChecked(checked);
}

160
161
162
void
VideoOverlay::on_hangupButton_clicked()
{
163
    auto convInfo = LRCInstance::getCurrentConversation();
164
    if (!convInfo.uid.isEmpty()) {
165
        auto callModel = LRCInstance::getCurrentCallModel();
166
        if (callModel->hasCall(convInfo.callId)) {
167
168
            // Store the last remaining participant of the conference
            // so we can switch the smartlist index after termination.
169
            if (!convInfo.confId.isEmpty()) {
170
171
172
173
174
175
176
177
178
                auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId);
                if (callList.size() == 2) {
                    for (const auto& cId : callList) {
                        if (cId != convInfo.callId) {
                            LRCInstance::instance().pushLastConferencee(convInfo.confId, cId);
                        }
                    }
                }
            }
179
            callModel->hangUp(convInfo.callId);
180
        }
181
    }
182
    ui->chatButton->setChecked(false);
183
184
185
186
187
}

void
VideoOverlay::on_chatButton_toggled(bool checked)
{
188
    emit setChatVisibility(checked);
189
190
}

Edric Milaret's avatar
Edric Milaret committed
191
void
Yang Wang's avatar
Yang Wang committed
192
VideoOverlay::on_holdButton_toggled(bool checked)
Edric Milaret's avatar
Edric Milaret committed
193
{
194
195
    // why is 'checked' unused?
    Q_UNUSED(checked);
Andreas Traczyk's avatar
Andreas Traczyk committed
196
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
197
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
Andreas Traczyk's avatar
Andreas Traczyk committed
198
199
        return;
    }
200
    auto callModel = LRCInstance::getCurrentCallModel();
201
    bool onHold { false };
Andreas Traczyk's avatar
Andreas Traczyk committed
202
203
204
    if (callModel->hasCall(callId)) {
        callModel->togglePause(callId);
        onHold = callModel->getCall(callId).status == lrc::api::call::Status::PAUSED;
205
    }
206
    ui->onHoldLabel->setVisible(onHold);
Edric Milaret's avatar
Edric Milaret committed
207
208
}

209
void
210
VideoOverlay::on_noMicButton_toggled(bool checked)
211
{
212
    Q_UNUSED(checked);
Andreas Traczyk's avatar
Andreas Traczyk committed
213
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
214
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
Andreas Traczyk's avatar
Andreas Traczyk committed
215
216
        return;
    }
217
    auto callModel = LRCInstance::getCurrentCallModel();
Andreas Traczyk's avatar
Andreas Traczyk committed
218
219
    if (callModel->hasCall(callId)) {
        callModel->toggleMedia(callId, lrc::api::NewCallModel::Media::AUDIO);
220
    }
221
222
223
}

void
224
VideoOverlay::on_noVideoButton_toggled(bool checked)
225
{
226
    Q_UNUSED(checked);
Andreas Traczyk's avatar
Andreas Traczyk committed
227
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
228
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
Andreas Traczyk's avatar
Andreas Traczyk committed
229
230
        return;
    }
231
    auto callModel = LRCInstance::getCurrentCallModel();
Andreas Traczyk's avatar
Andreas Traczyk committed
232
233
    if (callModel->hasCall(callId)) {
        callModel->toggleMedia(callId, lrc::api::NewCallModel::Media::VIDEO);
234
    }
235
    emit videoMuteStateChanged(checked);
236
237
}

Edric Milaret's avatar
Edric Milaret committed
238
239
240
void
VideoOverlay::on_recButton_clicked()
{
Andreas Traczyk's avatar
Andreas Traczyk committed
241
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
242
    if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
Andreas Traczyk's avatar
Andreas Traczyk committed
243
244
        return;
    }
245
    auto callModel = LRCInstance::getCurrentCallModel();
Andreas Traczyk's avatar
Andreas Traczyk committed
246
247
    if (callModel->hasCall(callId)) {
        callModel->toggleAudioRecord(callId);
248
    }
Edric Milaret's avatar
Edric Milaret committed
249
}
250

251
void
Andreas Traczyk's avatar
Andreas Traczyk committed
252
VideoOverlay::on_addToConferenceButton_toggled(bool checked)
253
{
Andreas Traczyk's avatar
Andreas Traczyk committed
254
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
255
    if ( callId.isEmpty() ||
Andreas Traczyk's avatar
Andreas Traczyk committed
256
257
258
259
260
         !LRCInstance::getCurrentCallModel()->hasCall(callId) ||
         !checked) {
        return;
    }

261
    contactPickerPopup(SmartListModel::Type::CONFERENCE, ui->addToConferenceButton);
262
263
264
}

void
265
VideoOverlay::on_transferCallButton_toggled(bool checked)
266
{
Andreas Traczyk's avatar
Andreas Traczyk committed
267
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
268
    if (callId.isEmpty() ||
Andreas Traczyk's avatar
Andreas Traczyk committed
269
270
        !LRCInstance::getCurrentCallModel()->hasCall(callId) ||
        !checked) {
271
272
273
        return;
    }

274
    contactPickerPopup(SmartListModel::Type::TRANSFER, ui->transferCallButton);
275
276
277
}

void
278
VideoOverlay::slotWillDoTransfer(const QString& contactUri)
279
280
{
    auto callModel = LRCInstance::getCurrentCallModel();
281

282
    ui->transferCallButton->setChecked(false);
Andreas Traczyk's avatar
Andreas Traczyk committed
283
284
285
    ui->transferCallButton->resetToOriginal();

    auto conversation = LRCInstance::getCurrentConversation();
286
    if (conversation.uid.isEmpty()) {
Andreas Traczyk's avatar
Andreas Traczyk committed
287
288
        return;
    }
289
    auto callId = conversation.confId.isEmpty() ? conversation.callId : conversation.confId;
Andreas Traczyk's avatar
Andreas Traczyk committed
290

291
    QString destCallId;
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313

    try {
        //check if the call exist - (check non-finished calls)
        auto callInfo = callModel->getCallFromURI(contactUri, true);
        destCallId = callInfo.id;
    } catch(std::exception& e) {
        qDebug().noquote() << e.what();
        destCallId = "";
    }
    // if no second call -> blind transfer
    // if there is a second call -> attended transfer
    if (destCallId.size() == 0) {
        callModel->transfer(callId, "sip:" + contactUri);
        callModel->hangUp(callId);
    }else{
        callModel->transferToCall(callId, destCallId);
        callModel->hangUp(callId);
        callModel->hangUp(destCallId);
    }
}

void
314
VideoOverlay::slotContactWillJoinConference(const QString& contactUri)
315
{
Andreas Traczyk's avatar
Andreas Traczyk committed
316
    auto callModel = LRCInstance::getCurrentCallModel();
317

Andreas Traczyk's avatar
Andreas Traczyk committed
318
319
320
321
322
323
324
325
326
    ui->addToConferenceButton->setChecked(false);
    ui->addToConferenceButton->resetToOriginal();

    auto conversation = LRCInstance::getCurrentConversation();
    auto call = LRCInstance::getCallInfoForConversation(conversation);
    if (!call) {
        return;
    }
    callModel->callAndAddParticipant(contactUri, call->id, call->isAudioOnly);
327
328
}

329
void
330
VideoOverlay::slotCallWillJoinConference(const QString& callId)
331
{
Andreas Traczyk's avatar
Andreas Traczyk committed
332
    auto callModel = LRCInstance::getCurrentCallModel();
333

Andreas Traczyk's avatar
Andreas Traczyk committed
334
335
336
337
    ui->addToConferenceButton->setChecked(false);
    ui->addToConferenceButton->resetToOriginal();

    auto conversation = LRCInstance::getCurrentConversation();
338
    if (conversation.uid.isEmpty()) {
Andreas Traczyk's avatar
Andreas Traczyk committed
339
340
        return;
    }
341
    auto thisCallId = conversation.confId.isEmpty() ? conversation.callId : conversation.confId;
Andreas Traczyk's avatar
Andreas Traczyk committed
342
343

    callModel->joinCalls(thisCallId, callId);
344
}
345
346
347
348

void
VideoOverlay::on_sipInputPanelButton_toggled(bool checked)
{
Andreas Traczyk's avatar
Andreas Traczyk committed
349
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
350
    if (callId.isEmpty() ||
Andreas Traczyk's avatar
Andreas Traczyk committed
351
352
        !LRCInstance::getCurrentCallModel()->hasCall(callId) ||
        !checked) {
353
354
355
        return;
    }

356
357
358
359
360
    SIPInputPanelWidget sipInputPanel(this);

    connect(&sipInputPanel, &SIPInputPanelWidget::sipInputPanelClicked,
        this, &VideoOverlay::slotSIPInputPanelClicked);

361
362
363
    QPoint globalPos_button = mapToGlobal(ui->sipInputPanelButton->pos());
    QPoint globalPos_bottomButtons = mapToGlobal(ui->bottomButtons->pos());

364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    if (auto container = sipInputPanel.getContainer().toStrongRef()) {

        container->move(globalPos_button.x(),
                        globalPos_bottomButtons.y() - sipInputPanel.height() - popupMargin_);

        // receive the signal that ensure the button checked status is correct and contactpicker
        // is properly hidden
        Utils::oneShotConnect(container.data(), &PopupDialog::willClose,
            [this](QMouseEvent* event) {
                // check if current mouse position is on button
                auto relativeClickPos = ui->sipInputPanelButton->mapFromGlobal(event->globalPos());
                if (!ui->sipInputPanelButton->rect().contains(relativeClickPos)) {
                    ui->sipInputPanelButton->setChecked(false);
                    ui->sipInputPanelButton->resetToOriginal();
                }
            });

        // for esc key, receive reject signal
        Utils::oneShotConnect(container.data(), &QDialog::rejected,
            [this] {
384
                ui->sipInputPanelButton->setChecked(false);
385
                ui->sipInputPanelButton->resetToOriginal();
386
            });
387

388
389
390
391
        popUpShown_ = true;
        container->exec();
        popUpShown_ = false;
    }
392
393
394
395
396
}

void
VideoOverlay::slotSIPInputPanelClicked(const int& id)
{
Andreas Traczyk's avatar
Andreas Traczyk committed
397
    auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_);
398
    if ( callId.isEmpty() ||
Andreas Traczyk's avatar
Andreas Traczyk committed
399
400
401
402
         !LRCInstance::getCurrentCallModel()->hasCall(callId)) {
        return;
    }

403
404
405
    switch (id)
    {
    case 10:
Andreas Traczyk's avatar
Andreas Traczyk committed
406
        LRCInstance::getCurrentCallModel()->playDTMF(callId, "#");
407
408
        break;
    case 11:
Andreas Traczyk's avatar
Andreas Traczyk committed
409
        LRCInstance::getCurrentCallModel()->playDTMF(callId, "*");
410
411
        break;
    default:
412
413
414
        // id from 12 to 15 are A,B,C,D
        if (id >= 12) {
            //ASCII Key_A = 65
415
            LRCInstance::getCurrentCallModel()->playDTMF(callId, QString(char(id - 12 + 65)));
416
        } else {
417
            LRCInstance::getCurrentCallModel()->playDTMF(callId, QString::number(id));
418
        }
419
420
421
        break;
    }
}
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469

void
VideoOverlay::contactPickerPopup(SmartListModel::Type type, OverlayButton* locateButton)
{
    ContactPicker contactPicker(this);
    contactPicker.setType(type);
    contactPicker.setTitle(QObject::tr("Add to conference"));
    contactPicker.setCurrentCalleeDisplayName(currentBestCalleeDisplayName_);

    connect(&contactPicker, &ContactPicker::contactWillJoinConference,
        this, &VideoOverlay::slotContactWillJoinConference);
    connect(&contactPicker, &ContactPicker::callWillJoinConference,
        this, &VideoOverlay::slotCallWillJoinConference);
    connect(&contactPicker, &ContactPicker::contactWillDoTransfer,
        this, &VideoOverlay::slotWillDoTransfer);

    QPoint globalPos_button = mapToGlobal(locateButton->pos());
    QPoint globalPos_bottomButtons = mapToGlobal(ui->bottomButtons->pos());

    if (auto container = contactPicker.getContainer().toStrongRef()) {

        container->move(globalPos_button.x(),
            globalPos_bottomButtons.y() - contactPicker.height() - popupMargin_);

        // receive the signal that ensure the button checked status is correct and contactpicker
        // is properly hidden
        Utils::oneShotConnect(container.data(), &PopupDialog::willClose,
            [locateButton, this](QMouseEvent* event) {
                // check if current mouse position is on button
                auto relativeClickPos = locateButton->mapFromGlobal(event->globalPos());
                if (!locateButton->rect().contains(relativeClickPos)) {
                    locateButton->setChecked(false);
                    locateButton->resetToOriginal();
                }
            });

        // for esc key, receive reject signal
        Utils::oneShotConnect(container.data(), &QDialog::rejected,
            [this] {
                ui->addToConferenceButton->setChecked(false);
                ui->addToConferenceButton->resetToOriginal();
            });

        popUpShown_ = true;
        container->exec();
        popUpShown_ = false;
    }
}