callwidget.cpp 62.6 KB
Newer Older
Edric Milaret's avatar
Edric Milaret committed
1
/***************************************************************************
Sébastien Blin's avatar
Sébastien Blin committed
2
 * Copyright (C) 2015-2019 by Savoir-faire Linux                           *
Edric Milaret's avatar
Edric Milaret committed
3
 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
4
5
 * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com>          *
 * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com>          *
6
 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
Isa's avatar
Isa committed
7
 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>                      *
8
 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>              *
Edric Milaret's avatar
Edric Milaret committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 *                                                                         *
 * 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 "callwidget.h"
#include "ui_callwidget.h"

27
// client
28
#include "utils.h"
29
30
#ifdef Q_OS_WIN
//ERROR is defined in windows.h
31
#undef ERROR
Edric Milaret's avatar
Edric Milaret committed
32
#undef interface
33
#endif
34
#include "animationhelpers.h"
35
#include "conversationitemdelegate.h"
36
#include "globalsystemtray.h"
37
#include "lrcinstance.h"
38
#include "mainwindow.h"
39
40
41
#include "pixbufmanipulator.h"
#include "ringthemeutils.h"
#include "settingskey.h"
42
#include "aboutdialog.h"
Edric Milaret's avatar
Edric Milaret committed
43

44
45
46
47
48
#include "globalinstances.h"

#include <qrencode.h>

#include <QComboBox>
49
#include <QtConcurrent/QtConcurrent>
50
51
52
53
#include <QDesktopServices>
#include <QScrollBar>
#include <QWebEngineScript>
#include <QMimeData>
54
#include <QShortcut>
55
56
57
58

#include <algorithm>
#include <memory>

59
CallWidget::CallWidget(QWidget* parent) :
60
    NavWidget(parent),
Edric Milaret's avatar
Edric Milaret committed
61
    ui(new Ui::CallWidget),
62
    menu_(new QMenu())
Edric Milaret's avatar
Edric Milaret committed
63
64
65
{
    ui->setupUi(this);

66
    using namespace lrc::api;
Edric Milaret's avatar
Edric Milaret committed
67

68
    QApplication::setEffectEnabled(Qt::UI_AnimateCombo, false);
69
    clipboard_ = QApplication::clipboard();
70

71
    QPixmap logo(":/images/logo-jami-standard-coul.png");
72
73
74
    ui->ringLogo->setPixmap(logo.scaledToHeight(100, Qt::SmoothTransformation));
    ui->ringLogo->setAlignment(Qt::AlignHCenter);

75
    ui->qrLabel->hide();
76

Andreas Traczyk's avatar
Andreas Traczyk committed
77
    QSettings settings("jami.net", "Jami");
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    // select last used account if stored in registry
    auto accountList = LRCInstance::accountModel().getAccountList();
    if (!accountList.empty()) {
        std::string accountIdToStartWith;
        if (settings.contains(SettingsKey::selectedAccount)) {
            accountIdToStartWith = settings
                .value(SettingsKey::selectedAccount, true)
                .value<QString>()
                .toStdString();
            if (Utils::indexInVector(accountList, accountIdToStartWith) == -1) {
                accountIdToStartWith = accountList.at(0);
            }
        }
        else {
            accountIdToStartWith = accountList.at(0);
        }
        setSelectedAccount(accountIdToStartWith);
96
        // get account index and set the currentAccountComboBox
97
98
        auto index = Utils::indexInVector(accountList, accountIdToStartWith);
        if (index != -1) {
99
            ui->currentAccountComboBox->setCurrentIndex(index);
100
101
        }
    }
Edric Milaret's avatar
Edric Milaret committed
102

103
104
105
106
107
108
    if (settings.contains(SettingsKey::mainSplitterState)) {
        auto splitterStates = settings.value(SettingsKey::mainSplitterState).toByteArray();
        ui->mainActivitySplitter->restoreState(splitterStates);
    }

    ui->mainActivitySplitter->setCollapsible(0, false);
109
    ui->mainActivitySplitter->setCollapsible(1, false);
110

111
112
113
    //disable dropdown shadow on combobox
    ui->currentAccountComboBox->view()->window()->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);

114
115
    // conversation list
    ui->smartList->setContextMenuPolicy(Qt::CustomContextMenu);
116

117
118
119
120
    // setup searchingfor mini spinner
    miniSpinner_ = new QMovie(":/images/waiting.gif");
    ui->spinnerLabel->setMovie(miniSpinner_);
    ui->spinnerLabel->hide();
121

122
123
124
    // shortcuts
    registerShortCuts();

125
    // connections
Isa's avatar
Isa committed
126
    connect(ui->currentAccountComboBox, &CurrentAccountComboBox::settingsButtonClicked,
127
            this, &CallWidget::settingsButtonClicked);
Edric Milaret's avatar
Edric Milaret committed
128

Andreas Traczyk's avatar
Andreas Traczyk committed
129
130
131
132
133
    connect(ui->currentAccountComboBox, &CurrentAccountComboBox::newAccountClicked,
        [this]() {
            emit NavigationRequested(ScreenEnum::WizardScreen);
        });

134
135
    connect(ui->mainActivitySplitter, &QSplitter::splitterMoved,
        [this](int pos, int index) {
136
137
            Q_UNUSED(index);
            Q_UNUSED(pos);
Andreas Traczyk's avatar
Andreas Traczyk committed
138
            QSettings settings("jami.net", "Jami");
139
140
            settings.setValue(SettingsKey::mainSplitterState, ui->mainActivitySplitter->saveState());
        });
141

142
    connect(ui->btnConversations, &QPushButton::clicked,
143
            this, &CallWidget::conversationsButtonClicked);
144

145
    connect(ui->btnInvites, &QPushButton::clicked,
146
            this, &CallWidget::invitationsButtonClicked);
147

148
    connect(ui->smartList, &QTreeView::customContextMenuRequested,
149
            this, &CallWidget::slotCustomContextMenuRequested);
150

151
152
    connect(ui->smartList, &SmartListView::btnAcceptInviteClicked,
            this, &CallWidget::slotAcceptInviteClicked);
153

154
155
    connect(ui->smartList, &SmartListView::btnBlockInviteClicked,
            this, &CallWidget::slotBlockInviteClicked);
156

157
158
    connect(ui->smartList, &SmartListView::btnIgnoreInviteClicked,
            this, &CallWidget::slotIgnoreInviteClicked);
Olivier SOLDANO's avatar
Olivier SOLDANO committed
159

160
161
162
    connect(ui->currentAccountComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &CallWidget::slotAccountChanged);

163
164
165
166
167
168
169
170
171
    connect(ui->sendContactRequestButton, &QPushButton::clicked,
            this, &CallWidget::on_sendContactRequestButton_clicked);

    connect(ui->btnAudioCall, &QPushButton::clicked,
            this, &CallWidget::on_sendContactRequestButton_clicked);

    connect(ui->btnVideoCall, &QPushButton::clicked,
            this, &CallWidget::on_sendContactRequestButton_clicked);

Isa's avatar
Isa committed
172
173
    connect(ui->currentAccountComboBox, QOverload<int>::of(&CurrentAccountComboBox::currentIndexChanged),
        [this] {
174
175
176
177
            ui->btnConversations->setChecked(true);
            ui->btnInvites->setChecked(false);
        });

178
179
180
181
182
    connect(ui->messageView, &MessageWebView::conversationRemoved,
        [this] {
            backToWelcomePage();
        });

183
184
185
    connect(ui->messageView, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(ShowContextMenu(const QPoint&)));

186
187
188
    connect(ui->messageView, &MessageWebView::pasteKeyDetected,
            this, &CallWidget::Paste);

189
190
    connect(ui->messageView, &MessageWebView::invitationAccepted,
            [this] {
Andreas Traczyk's avatar
Andreas Traczyk committed
191
                auto convUid = LRCInstance::getCurrentConvUid();
192
193
194
195
                LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
                ui->sendContactRequestButton->hide();
            });

196
197
    // video view
    connect(ui->videoView, &VideoView::setChatVisibility,
198
            this, &CallWidget::slotSetChatVisibility, Qt::DirectConnection);
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

    connect(ui->videoView, &VideoView::toggleFullScreenClicked,
            this, &CallWidget::slotToggleFullScreenClicked);

    connect(&LRCInstance::behaviorController(), &BehaviorController::showCallView,
            this, &CallWidget::slotShowCallView);

    connect(&LRCInstance::behaviorController(), &BehaviorController::showChatView,
            this, &CallWidget::slotShowChatView);

    // these may trigger notifications
    connect(&LRCInstance::behaviorController(), &BehaviorController::showIncomingCallView,
            this, &CallWidget::slotShowIncomingCallView);

    connect(&LRCInstance::behaviorController(), &BehaviorController::newTrustRequest,
            this, &CallWidget::slotNewTrustRequest);

    connect(&LRCInstance::behaviorController(), &BehaviorController::newUnreadInteraction,
        [this](const std::string& accountId, const std::string& conversation,
               uint64_t interactionId, const interaction::Info& interaction) {
            if (LRCInstance::getCurrAccId() != accountId) {
220
                onNewInteraction(accountId, conversation, interactionId, interaction);
221
222
223
            }
        });

224
225
226
    connect(LRCInstance::renderer(), &RenderManager::videoDeviceListChanged,
            this, &CallWidget::slotVideoDeviceListChanged);

227
228
    connect(ui->changelogButton, &QAbstractButton::clicked,
        [this] {
229
230
            auto aboutDialog = std::make_unique<AboutDialog>(&MainWindow::instance());
            aboutDialog->getContainer()->exec();
231
232
        });

Andreas Traczyk's avatar
Andreas Traczyk committed
233
234
    // set first view to welcome view
    ui->stackedWidget->setCurrentWidget(ui->welcomePage);
235
236
237
238
    ui->btnConversations->setChecked(true);

    // chat view
    ui->messageView->buildView();
239
    ui->messageView->setContextMenuPolicy(Qt::CustomContextMenu);
240

241
    // hide the call stack
Andreas Traczyk's avatar
Andreas Traczyk committed
242
    setCallPanelVisibility(false);
243
244

    ui->containerWidget->setVisible(false);
Edric Milaret's avatar
Edric Milaret committed
245
246
247
248
249
}

CallWidget::~CallWidget()
{
    delete ui;
250
    delete menu_;
251
252
}

253
254
255
256
257
258
259
void
CallWidget::navigated(bool to)
{
    ui->containerWidget->setVisible(to);
    if (to) {
        updateSmartList();
        connectConversationModel();
260
261
262
263
264
265
266
267
268
        try {
            auto accountList = LRCInstance::accountModel().getAccountList();
            if (accountList.size() == 1) {
                auto index = Utils::indexInVector(accountList, LRCInstance::getCurrAccId());
                if (index != -1) {
                    slotAccountChanged(index);
                }
            }
        } catch (...) {}
269
        ui->currentAccountComboBox->updateComboBoxDisplay();
270
271
272
        auto& conversation = LRCInstance::getCurrentConversation();
        if (!conversation.uid.empty()) {
            selectSmartlistItem(conversation.uid);
273
274
275
276
            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        } else {
            backToWelcomePage();
        }
277
278
279
280
        /**
         * This will resize/position the preview when returning from the settings
         * in case of a resolution change.
         */
281
        ui->videoView->resetPreview();
282
        setFocus();
283
284
285
286
287
288
    } else {
        QObject::disconnect(smartlistSelectionConnection_);
        smartListModel_.reset(nullptr);
    }
}

289
290
void
CallWidget::updateCustomUI()
Andreas Traczyk's avatar
Andreas Traczyk committed
291
{
292
293
294
295
296
297
298
    auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
    if (scalingRatio > 1.0) {
        ui->messageView->setZoomFactor(1.15);
    } else {
        ui->messageView->setZoomFactor(1.0);
    }

Andreas Traczyk's avatar
Andreas Traczyk committed
299
300
}

301
void
302
CallWidget::slotAccountListChanged()
303
304
{}

305
306
307
308
309
310
int
CallWidget::getLeftPanelWidth()
{
    return ui->currentAccountComboBox->width();
}

311
void
312
CallWidget::onNewInteraction(const std::string& accountId, const std::string& convUid,
313
                              uint64_t interactionId, const interaction::Info& interaction)
314
{
315
    Q_UNUSED(interactionId);
316
317
318
319
320
    try {
        auto& accountInfo = LRCInstance::getAccountInfo(accountId);
        auto& convModel = accountInfo.conversationModel;
        auto& conversation = LRCInstance::getConversationFromConvUid(convUid, accountId);
        if (conversation.uid.empty()) {
321
322
            return;
        }
323
324
        if (!interaction.authorUri.empty() &&
            (!QApplication::focusWidget() || LRCInstance::getCurrAccId() != accountId)) {
325
326
327
328
329
330
            auto bestName = Utils::bestNameForConversation(conversation, *convModel);
            Utils::showSystemNotification(this,
                QString::fromStdString(bestName),
                QString::fromStdString(interaction.body));
        }
        updateConversationsFilterWidget();
Andreas Traczyk's avatar
Andreas Traczyk committed
331
        if (convUid != LRCInstance::getCurrentConvUid()) {
332
333
334
            return;
        }
        convModel->clearUnreadInteractions(convUid);
335
        ui->messageView->printNewInteraction(*convModel, interactionId, interaction);
336
337
338
339
        if (interaction.type != interaction::Type::CALL) {
            ui->videoView->simulateShowChatview(true);
        }
    } catch (...) {}
Edric Milaret's avatar
Edric Milaret committed
340
341
342
}

void
343
CallWidget::setupSmartListContextMenu(const QPoint& pos)
Olivier SOLDANO's avatar
Olivier SOLDANO committed
344
{
345
346
347
    QPoint globalPos = ui->smartList->mapToGlobal(pos);
    auto index = ui->smartList->indexAt(pos);
    if (not index.isValid()) {
Olivier SOLDANO's avatar
Olivier SOLDANO committed
348
        return;
349
    }
Olivier SOLDANO's avatar
Olivier SOLDANO committed
350

351
352
353
354
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID))
        .value<QString>()
        .toStdString();
355
356
357
    auto& conversation = LRCInstance::getConversationFromConvUid(convUid);
    if (conversation.uid.empty()) return;
    auto contactUid = conversation.participants.at(0);
358
    auto contact = LRCInstance::getCurrentAccountInfo().contactModel.get()->getContact(contactUid);
Olivier SOLDANO's avatar
Olivier SOLDANO committed
359

360
    if (!Utils::isContactValid(contactUid, *convModel)) {
Olivier SOLDANO's avatar
Olivier SOLDANO committed
361
        return;
362
    }
Olivier SOLDANO's avatar
Olivier SOLDANO committed
363

364
    QMenu menu;
Olivier SOLDANO's avatar
Olivier SOLDANO committed
365

366
367
368
369
370
371
372
    // video call
    auto videoCallAction = new QAction(tr("Start video call"), this);
    menu.addAction(videoCallAction);
    connect(videoCallAction, &QAction::triggered,
        [this, convUid, conversation, convModel]() {
            convModel->placeCall(convUid);
            ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convUid)));
Andreas Traczyk's avatar
Andreas Traczyk committed
373
            if (convUid != LRCInstance::getCurrentConvUid()) {
374
                selectConversation(conversation);
375
            }
Olivier SOLDANO's avatar
Olivier SOLDANO committed
376
        });
377
378
379
380
381
382
383
    // audio call
    auto audioCallAction = new QAction(tr("Start audio call"), this);
    menu.addAction(audioCallAction);
    connect(audioCallAction, &QAction::triggered,
        [this, convUid, conversation, convModel]() {
            convModel->placeAudioOnlyCall(convUid);
            ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convUid)));
Andreas Traczyk's avatar
Andreas Traczyk committed
384
            if (convUid != LRCInstance::getCurrentConvUid()) {
385
                selectConversation(conversation);
386
            }
Olivier SOLDANO's avatar
Olivier SOLDANO committed
387
        });
388
    // clear conversation
389
    auto clearConversationAction = new QAction(tr("Clear conversation"), this);
390
391
392
393
394
395
396
397
398
399
400
401
    menu.addAction(clearConversationAction);
    connect(clearConversationAction, &QAction::triggered,
        [convUid]() {
            LRCInstance::getCurrentConversationModel()->clearHistory(convUid);
        });
    // remove contact
    auto removeContactAction = new QAction(tr("Remove contact"), this);
    menu.addAction(removeContactAction);
    connect(removeContactAction, &QAction::triggered,
        [convUid]() {
            LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
        });
402
403
404
405
406
407
408
409
410
411
412

    if (contact.profileInfo.type == lrc::api::profile::Type::RING) {
        // separator
        menu.addSeparator();

        // block contact
        auto blockContactAction = new QAction(tr("Block contact"), this);
        menu.addAction(blockContactAction);
        connect(blockContactAction, &QAction::triggered,
            [convUid]() {
                LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
Edric Milaret's avatar
Edric Milaret committed
413
            });
Olivier SOLDANO's avatar
Olivier SOLDANO committed
414

415
416
417
418
419
420
421
422
423
424
425
426
        // separator
        menu.addSeparator();

        // copy number(infohash)
        auto copyNumberAction = new QAction(tr("Copy number"), this);
        menu.addAction(copyNumberAction);
        connect(copyNumberAction, &QAction::triggered,
            [contact]() {
                QApplication::clipboard()->setText(
                    QString::fromStdString(contact.profileInfo.uri)
                );
            });
Olivier SOLDANO's avatar
Olivier SOLDANO committed
427
    }
428
    smartListModel_->isContextMenuOpen = true;
Olivier SOLDANO's avatar
Olivier SOLDANO committed
429
    menu.exec(globalPos);
430
    smartListModel_->isContextMenuOpen = false;
Edric Milaret's avatar
Edric Milaret committed
431
432
}

433
434
void
CallWidget::setupQRCode(QString ringID)
435
{
436
    auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
Edric Milaret's avatar
Edric Milaret committed
437
438
                                     0, //Let the version be decided by libqrencode
                                     QR_ECLEVEL_L, // Lowest level of error correction
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
                                     QR_MODE_8, // 8-bit data mode
                                     1);
    if (not rcode) {
        qWarning() << "Failed to generate QR code: " << strerror(errno);
        return;
    }

    auto margin = 5;
    int qrwidth = rcode->width + margin * 2;
    QImage result(QSize(qrwidth, qrwidth), QImage::Format_Mono);
    QPainter painter;
    painter.begin(&result);
    painter.setClipRect(QRect(0, 0, qrwidth, qrwidth));
    painter.setPen(QPen(Qt::black, 0.1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
    painter.setBrush(Qt::black);
    painter.fillRect(QRect(0, 0, qrwidth, qrwidth), Qt::white);
Edric Milaret's avatar
Edric Milaret committed
455
    unsigned char* p;
456
457
    p = rcode->data;
    for(int y = 0; y < rcode->width; y++) {
Edric Milaret's avatar
Edric Milaret committed
458
        unsigned char* row = (p + (y * rcode->width));
459
460
461
462
463
464
465
466
467
        for(int x = 0; x < rcode->width; x++) {
            if(*(row + x) & 0x1) {
                painter.drawRect(margin + x, margin + y, 1, 1);
            }
        }

    }
    painter.end();
    QRcode_free(rcode);
468
469
    ui->qrLabel->setPixmap(QPixmap::fromImage(result.scaled(QSize(qrSize_, qrSize_),
                           Qt::KeepAspectRatio)));
470
471
}

Edric Milaret's avatar
Edric Milaret committed
472
void
473
CallWidget::on_smartList_clicked(const QModelIndex& index)
Edric Milaret's avatar
Edric Milaret committed
474
{
475
    Q_UNUSED(index);
Edric Milaret's avatar
Edric Milaret committed
476
477
478
479
480
}

void
CallWidget::on_acceptButton_clicked()
{
481
    auto convInfo = LRCInstance::getCurrentConversation();
482
483
484
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->accept(convInfo.callId);
    }
Edric Milaret's avatar
Edric Milaret committed
485
486
487
488
489
}

void
CallWidget::on_refuseButton_clicked()
{
490
    auto convInfo = LRCInstance::getCurrentConversation();
491
    if (!convInfo.uid.empty()) {
492
        LRCInstance::getCurrentCallModel()->refuse(convInfo.callId);
493
494
        showConversationView();
    }
Edric Milaret's avatar
Edric Milaret committed
495
496
497
}

void
498
CallWidget::on_cancelButton_clicked()
499
{
500
    auto convInfo = LRCInstance::getCurrentConversation();
501
502
503
504
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->hangUp(convInfo.callId);
        showConversationView();
    }
Edric Milaret's avatar
Edric Milaret committed
505
506
507
}

void
508
CallWidget::showConversationView()
509
{
Andreas Traczyk's avatar
Andreas Traczyk committed
510
    if (LRCInstance::getCurrentConvUid().empty()) {
511
512
513
        backToWelcomePage();
        return;
    }
514
515
    ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
    ui->messageView->setFocus();
516
517
518
    if (ui->messagesWidget->isHidden()) {
        ui->messagesWidget->show();
    }
519
    updateChatviewFrame();
Edric Milaret's avatar
Edric Milaret committed
520
}
521

522
bool
523
CallWidget::selectSmartlistItem(const std::string& convUid)
524
{
525
    if (convUid.empty() || !ui->smartList->selectionModel())
526
        return false;
527
528
    ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
    auto convModel = LRCInstance::getCurrentConversationModel();
529
530
    auto& conversation = LRCInstance::getConversationFromConvUid(convUid);
    if (conversation.uid.empty()) {
531
        return false;
532
    }
533
    auto contactURI = QString::fromStdString(conversation.participants[0]);
534
535
    if (contactURI.isEmpty() ||
        convModel->owner.contactModel->getContact(contactURI.toStdString()).profileInfo.type == lrc::api::profile::Type::TEMPORARY) {
536
        return false;
537
538
539
540
541
542
    }
    for (int row = 0; row < smartListModel_->rowCount(); row++) {
        QModelIndex index = smartListModel_->index(row);
        auto indexContactURI = index.data(SmartListModel::Role::URI).value<QString>();
        if (indexContactURI == contactURI) {
            ui->smartList->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
543
            return true;
544
        }
545
    }
546
    return false;
547
548
}

549
void
550
CallWidget::on_smartList_doubleClicked(const QModelIndex& index)
551
{
552
553
    if (!index.isValid())
        return;
Edric Milaret's avatar
Edric Milaret committed
554

555
556
    selectConversation(index);

Andreas Traczyk's avatar
Andreas Traczyk committed
557
    LRCInstance::getCurrentConversationModel()->placeCall(LRCInstance::getCurrentConvUid());
558

Andreas Traczyk's avatar
Andreas Traczyk committed
559
    ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(LRCInstance::getCurrentConvUid())));
560
}
561

562
563
QImage
CallWidget::imageForConv(const std::string& convUid)
564
{
565
    return Utils::conversationPhoto(convUid, LRCInstance::getCurrentAccountInfo());
566
}
567
568

void
569
CallWidget::smartListSelectionChanged(const QItemSelection  &selected, const QItemSelection  &deselected)
570
{
571
572
573
574
    Q_UNUSED(deselected);
    QModelIndexList indices = selected.indexes();

    if (indices.isEmpty()) {
575
        return;
576
    }
577

578
    auto selectedIndex = indices.at(0);
579

580
581
    if (not selectedIndex.isValid()) {
        return;
582
    }
583
584

    selectConversation(selectedIndex);
585
586
}

587
588
589
590
591
void CallWidget::slotSetChatVisibility(bool visible)
{
    ui->messagesWidget->setVisible(visible);
}

592
593
594
595
596
597
598
599
void
CallWidget::slotVideoDeviceListChanged()
{
    if (LRCInstance::hasVideoCall()) {
        LRCInstance::renderer()->startPreviewing();
    }
}

600
void
601
CallWidget::conversationsButtonClicked()
602
{
603
604
    ui->btnConversations->setChecked(true);
    ui->btnInvites->setChecked(false);
605
    ui->ringContactLineEdit->setPlaceholderString(tr("Find a new or existing contact"));
606
    setConversationFilter(lrc::api::profile::Type::RING);
607
608
}

609
void
610
CallWidget::invitationsButtonClicked()
611
{
612
613
    ui->btnConversations->setChecked(false);
    ui->btnInvites->setChecked(true);
614
    ui->ringContactLineEdit->setPlaceholderString(tr("Search your received invitations"));
615
    setConversationFilter(lrc::api::profile::Type::PENDING);
616
617
}

618
619
void
CallWidget::settingsButtonClicked()
620
{
621
    emit NavigationRequested(ScreenEnum::SetttingsScreen);
622
623
}

624
void
625
CallWidget::processContactLineEdit()
626
627
{
    auto contactLineText = ui->ringContactLineEdit->text();
628
    setConversationFilter(contactLineText);
629
630
631
}

void
632
633
CallWidget::on_ringContactLineEdit_returnPressed()
{
634
635
636
    // select current temporary item and show conversation
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto conversations = convModel->allFilteredConversations();
637
638
    if (!conversations.empty() &&
        Utils::isContactValid(conversations.at(0).participants.at(0), *convModel)) {
639
640
        selectConversation(smartListModel_->index(0));
    }
641
642
}

643
644
void
CallWidget::slotAcceptInviteClicked(const QModelIndex & index)
645
{
646
647
648
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
    LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
    ui->messageView->setInvitation(false);
649
    ui->sendContactRequestButton->hide();
650
651
}

652
653
void
CallWidget::slotBlockInviteClicked(const QModelIndex & index)
654
{
655
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
Andreas Traczyk's avatar
Andreas Traczyk committed
656
    if (!convUid.empty() && convUid == LRCInstance::getCurrentConvUid()) {
657
658
659
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
660
}
661

662
663
void
CallWidget::slotIgnoreInviteClicked(const QModelIndex & index)
664
{
665
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
Andreas Traczyk's avatar
Andreas Traczyk committed
666
    if (!convUid.empty() && convUid == LRCInstance::getCurrentConvUid()) {
667
668
669
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
670
}
671

672
673
void
CallWidget::slotCustomContextMenuRequested(const QPoint& pos)
674
675
676
{
    setupSmartListContextMenu(pos);
}
677

678
679
void
CallWidget::slotAccountChanged(int index)
680
{
Andreas Traczyk's avatar
Andreas Traczyk committed
681
682
683
684
    try {
        auto accountList = LRCInstance::accountModel().getAccountList();
        setSelectedAccount(accountList.at(index));
    } catch (...) {
685
        qWarning() << "CallWidget::slotAccountChanged exception";
Andreas Traczyk's avatar
Andreas Traczyk committed
686
    }
687
688
}

689
690
void
CallWidget::slotShowCallView(const std::string& accountId,
691
                             const lrc::api::conversation::Info& convInfo)
692
693
{
    Q_UNUSED(accountId);
694
    qDebug() << "slotShowCallView";
695
696

    // control visible callwidget buttons
697
    setCallPanelVisibility(true);
698

699
    ui->callStackWidget->setCurrentWidget(ui->videoPage);
700
    hideMiniSpinner();
701

Andreas Traczyk's avatar
Andreas Traczyk committed
702
    ui->videoView->updateCall(convInfo.uid, accountId);
703
704
    ui->videoView->show();
    ui->videoView->setFocus();
705
}
706

707
708
void
CallWidget::slotShowIncomingCallView(const std::string& accountId,
709
                                     const conversation::Info& convInfo)
710
{
711
    Q_UNUSED(accountId);
712
713
    qDebug() << "slotShowIncomingCallView";

714
    auto callModel = LRCInstance::getCurrentCallModel();
715
716
717
718
719
720
721
722
723
724
725

    if (!callModel->hasCall(convInfo.callId)) {
        return;
    }

    auto convModel = LRCInstance::getCurrentConversationModel();
    ui->callerPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
    auto bestName = QString::fromStdString(Utils::bestNameForConversation(convInfo, *convModel));
    auto bestId = QString::fromStdString(Utils::bestIdForConversation(convInfo, *convModel));
    auto finalBestId = (bestName != bestId) ? bestId : "";

726
    auto call = callModel->getCall(convInfo.callId);
Andreas Traczyk's avatar
Andreas Traczyk committed
727
    auto isCallSelected = LRCInstance::getCurrentConvUid() == convInfo.uid;
728
729
    ui->callingStatusLabel->setText(QString::fromStdString(lrc::api::call::to_string(call.status)));

730
    auto itemInCurrentFilter = false;
731
    if (call.isOutgoing) {
732
733
734
735
        if (isCallSelected) {
            miniSpinner_->start();
            ui->spinnerLabel->show();
            ui->callStackWidget->setCurrentWidget(ui->outgoingCallPage);
Andreas Traczyk's avatar
Andreas Traczyk committed
736
            setCallPanelVisibility(true);
737
        }
738
    } else {
739
        if (!QApplication::focusWidget()) {
740
            auto formattedName = Utils::bestNameForConversation(convInfo, *convModel);
741
742
            Utils::showSystemNotification(this,
                QString(tr("Call incoming from %1")).arg(QString::fromStdString(formattedName)));
743
        }
744
745
        auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
        auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
746
747
        if (!isCallSelected)
            itemInCurrentFilter = selectSmartlistItem(convInfo.uid);
748
749
        if (accountProperties.autoAnswer) {
            ui->callStackWidget->setCurrentWidget(ui->videoPage);
750
        } else if (isCallSelected || !itemInCurrentFilter) {
751
752
            ui->callStackWidget->setCurrentWidget(ui->incomingCallPage);
        }
753
        setCallPanelVisibility(true);
754
    }
755

756
    if (!itemInCurrentFilter && !isCallSelected) {
757
758
        if (ui->smartList->selectionModel())
            ui->smartList->selectionModel()->clear();
759
760
761
        LRCInstance::setSelectedConvId(convInfo.uid);
        showChatView(accountId, convInfo);
    } else if (ui->messagesWidget->isHidden()) {
762
763
764
        ui->messagesWidget->show();
    }

765
766
    QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
    QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
767

768
769
770
771
772
773
774
775
    QString elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callerBestNameLabel->width());
    ui->callerBestNameLabel->setText(elidedLabel);

    elidedLabel = primaryCallLabelFontMetrics.elidedText(bestName, Qt::ElideRight, ui->callingBestNameLabel->width());
    ui->callingBestNameLabel->setText(elidedLabel);

    elidedLabel = sencondaryCallLabelFontMetrics.elidedText(finalBestId, Qt::ElideRight, ui->callingBestIdLabel->width());
    ui->callingBestIdLabel->setText(elidedLabel);
776

777
    ui->smartList->update();
778
}
779

780
781
void
CallWidget::slotShowChatView(const std::string& accountId,
782
783
                      const lrc::api::conversation::Info& convInfo)
{
784
785
    Q_UNUSED(accountId);
    Q_UNUSED(convInfo);
786

Andreas Traczyk's avatar
Andreas Traczyk committed
787
    setCallPanelVisibility(false);
788
789
    showConversationView();
}
790

791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
void
CallWidget::slotNewTrustRequest(const std::string& accountId, const std::string& contactUri)
{
    try {
        auto& accountInfo = LRCInstance::getAccountInfo(accountId);
        auto& contactModel = accountInfo.contactModel;
        if (!QApplication::focusWidget() || LRCInstance::getCurrAccId() != accountId) {
            try {
                auto contactInfo = contactModel->getContact(contactUri);
                auto bestName = Utils::bestNameForContact(contactInfo);
                Utils::showSystemNotification(this,
                    QString::fromStdString(bestName),
                    QObject::tr("Contact request"));
            } catch (...) {
                qDebug() << "Can't get contact: ", contactUri.c_str();
                return;
            }
        }
    } catch (...) {
        qDebug() << "Can't get account:" << accountId.c_str();
    }
}

814
815
816
817
818
819
820
void
CallWidget::slotToggleFullScreenClicked()
{
    if (ui->mainActivityWidget->isFullScreen()) {
        ui->stackedWidget->addWidget(ui->mainActivityWidget);
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->showNormal();
821
        MainWindow::instance().show();
822
    } else {
823
        auto geo = ui->stackedWidget->window()->geometry();
824
825
        ui->stackedWidget->removeWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->setParent(0);
826
827
        ui->mainActivityWidget->window()->setGeometry(geo);
        MainWindow::instance().hide();
828
829
830
831
832
        ui->mainActivityWidget->showFullScreen();
    }
}

void
833
CallWidget::callTerminating(const std::string& id)
834
{
835
836
837
838
    auto conversation = LRCInstance::getCurrentConversation();
    if ( conversation.uid.empty() &&
         conversation.callId != id &&
         conversation.confId != id) {
839
840
        return;
    }
841
842
843
844
    if (ui->mainActivityWidget->isFullScreen()) {
        ui->stackedWidget->addWidget(ui->mainActivityWidget);
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->showNormal();
845
        MainWindow::instance().showWindow();
846
847
848
849
    }
    showConversationView();
}

850
851
852
853
854
855
856
857
void
CallWidget::setSelectedAccount(const std::string& accountId)
{
    LRCInstance::setSelectedAccountId(accountId);

    // First, we get back to the welcome view (except if in call)
    if (ui->stackedWidget->currentWidget() != ui->videoPage &&
        ui->stackedWidget->currentWidget() != ui->welcomePage) {
858
        backToWelcomePage();
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
    }

    // We setup the ringIdLabel and the QRCode
    auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountId);
    auto id = accountInfo.registeredName.empty() ? accountInfo.profileInfo.uri : accountInfo.registeredName;
    auto isRingAccount = accountInfo.profileInfo.type == lrc::api::profile::Type::RING;
    if (isRingAccount) {
        ui->ringIdLabel->setText(QString::fromStdString(id));
        setupQRCode(QString::fromStdString(accountInfo.profileInfo.uri));
    }

    updateSmartList();
    currentTypeFilter_ = accountInfo.profileInfo.type;
    LRCInstance::getCurrentConversationModel()->setFilter(accountInfo.profileInfo.type);
    updateConversationsFilterWidget();
    connectConversationModel();
875
    connectAccount(accountId);
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
}

void CallWidget::setConversationFilter(lrc::api::profile::Type filter)
{
    if (currentTypeFilter_ == filter) {
        return;
    }
    currentTypeFilter_ = filter;
    LRCInstance::getCurrentConversationModel()->setFilter(currentTypeFilter_);
}

void CallWidget::updateConversationsFilterWidget()
{
    auto invites = LRCInstance::getCurrentAccountInfo().contactModel->pendingRequestCount();
    if (invites == 0 && currentTypeFilter_ == lrc::api::profile::Type::PENDING) {
        currentTypeFilter_ = lrc::api::profile::Type::RING;
        LRCInstance::getCurrentConversationModel()->setFilter(currentTypeFilter_);
893
    }
894
    ui->conversationsFilterWidget->setVisible(invites);
895
896
897
898
    if (!invites) {
        Utils::whileBlocking<QPushButton>(ui->btnConversations)->setChecked(true);
        Utils::whileBlocking<QPushButton>(ui->btnInvites)->setChecked(false);
    }
899
900
    ui->searchTopLeftWidget->setVisible(invites);
    ui->searchTopRightWidget->setVisible(invites);
901
    ui->conversationsFilterWidget->update();
902
    ui->conversationsFilterWidget->updateBadges();
903
904
905
906
907
}

void CallWidget::setConversationFilter(const QString & filter)
{
    LRCInstance::getCurrentConversationModel()->setFilter(filter.toStdString());
908
909
}

910
void
911
912
913
CallWidget::showChatView(const QModelIndex& nodeIdx)
{
    auto convUid = nodeIdx.data(static_cast<int>(SmartListModel::Role::UID)).toString().toStdString();
914
915
916
917
    auto& conversation = LRCInstance::getConversationFromConvUid(convUid);
    if (!conversation.uid.empty()) {
        setupChatView(conversation);
    }
918
919
920
921
922
923
924
925
926
}

void
CallWidget::showChatView(const std::string& accountId, const lrc::api::conversation::Info& convInfo)
{
    Q_UNUSED(accountId);
    setupChatView(convInfo);
}

927
928
929
930
931
932
void
CallWidget::setConversationProfileData(const lrc::api::conversation::Info& convInfo)
{
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto accInfo = &LRCInstance::getCurrentAccountInfo();
    auto contactUri = convInfo.participants.front();
933
934
935
    if (contactUri.empty()) {
        return;
    }
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
    try {
        auto& contact = accInfo->contactModel->getContact(contactUri);
        auto bestName = Utils::bestNameForConversation(convInfo, *convModel);
        ui->messageView->setInvitation(
            (contact.profileInfo.type == lrc::api::profile::Type::PENDING),
            bestName,
            contactUri
        );
        if (!contact.profileInfo.avatar.empty()) {
            ui->messageView->setSenderImage(contactUri, contact.profileInfo.avatar);
        } else {
            auto avatar = Utils::conversationPhoto(convInfo.uid, *accInfo);
            QByteArray ba;
            QBuffer bu(&ba);
            avatar.save(&bu, "PNG");
            std::string avatarString = ba.toBase64().toStdString();
            ui->messageView->setSenderImage(contactUri, avatarString);
        }
    } catch (...) {}
}

Andreas Traczyk's avatar