callwidget.cpp 45 KB
Newer Older
1
/***************************************************************************
2
 * Copyright (C) 2015-2019 by Savoir-faire Linux                           *
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>          *
7
 * Author: Isa Nanic <isa.nanic@savoirfairelinux.com>                      *
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *                                                                         *
 * 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"

26
#include <QComboBox>
27 28
#include <QDesktopServices>
#include <QScrollBar>
29
#include <QWebEngineScript>
30

31
#include <algorithm>
32
#include <memory>
33

34
#include <qrencode.h>
35

36 37 38
//ERROR is defined in windows.h
#include "utils.h"
#undef ERROR
Edric Milaret's avatar
Edric Milaret committed
39
#undef interface
40

41
// lrc
Edric Milaret's avatar
Edric Milaret committed
42
#include "globalinstances.h"
43

44
// client
45
#include "animationhelpers.h"
46
#include "conversationitemdelegate.h"
47
#include "globalsystemtray.h"
48
#include "lrcinstance.h"
49
#include "mainwindow.h"
50 51 52 53
#include "pixbufmanipulator.h"
#include "ringthemeutils.h"
#include "settingskey.h"
#include "windowscontactbackend.h"
Edric Milaret's avatar
Edric Milaret committed
54

55
CallWidget::CallWidget(QWidget* parent) :
56
    NavWidget(parent),
57
    ui(new Ui::CallWidget),
58
    menu_(new QMenu())
59 60 61
{
    ui->setupUi(this);

62
    using namespace lrc::api;
63

64
    QApplication::setEffectEnabled(Qt::UI_AnimateCombo, false);
65
    clipboard_ = QApplication::clipboard();
66

67
    QPixmap logo(":/images/logo-jami-standard-coul.png");
68 69 70
    ui->ringLogo->setPixmap(logo.scaledToHeight(100, Qt::SmoothTransformation));
    ui->ringLogo->setAlignment(Qt::AlignHCenter);

71
    ui->qrLabel->hide();
72

73
    videoRenderer_ = nullptr;
74

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

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    // 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);
94
        // get account index and set the currentAccountComboBox
95 96
        auto index = Utils::indexInVector(accountList, accountIdToStartWith);
        if (index != -1) {
97
            ui->currentAccountComboBox->setCurrentIndex(index);
98 99
        }
    }
100

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

    ui->mainActivitySplitter->setCollapsible(0, false);
107
    ui->mainActivitySplitter->setCollapsible(1, false);
108

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

112 113
    // conversation list
    ui->smartList->setContextMenuPolicy(Qt::CustomContextMenu);
114

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

120
    // connections
121
    connect(ui->currentAccountComboBox, &CurrentAccountComboBox::settingsButtonClicked,
122
            this, &CallWidget::settingsButtonClicked);
123

Andreas Traczyk's avatar
Andreas Traczyk committed
124 125 126 127 128
    connect(ui->currentAccountComboBox, &CurrentAccountComboBox::newAccountClicked,
        [this]() {
            emit NavigationRequested(ScreenEnum::WizardScreen);
        });

129 130
    connect(ui->videoWidget, &VideoView::setChatVisibility,
        [this](bool visible) {
131 132 133 134 135
            if (visible) {
                ui->messagesWidget->show();
            } else {
                ui->messagesWidget->hide();
            }
136 137 138 139
        });

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

146 147
    connect(ui->videoWidget, &VideoView::videoSettingsClicked,
            this, &CallWidget::settingsButtonClicked);
148

149 150 151 152 153 154
    connect(ui->videoWidget, &VideoView::toggleFullScreenClicked,
        this, &CallWidget::slotToggleFullScreenClicked);

    connect(ui->videoWidget, &VideoView::closing,
        this, &CallWidget::slotVideoViewDestroyed);

155
    connect(ui->btnConversations, &QPushButton::clicked,
156
            this, &CallWidget::conversationsButtonClicked);
157

158
    connect(ui->btnInvites, &QPushButton::clicked,
159
            this, &CallWidget::invitationsButtonClicked);
160

161
    connect(ui->smartList, &QTreeView::customContextMenuRequested,
162
            this, &CallWidget::slotCustomContextMenuRequested);
163

164 165
    connect(ui->smartList, &SmartListView::btnAcceptInviteClicked,
            this, &CallWidget::slotAcceptInviteClicked);
166

167 168
    connect(ui->smartList, &SmartListView::btnBlockInviteClicked,
            this, &CallWidget::slotBlockInviteClicked);
169

170 171
    connect(ui->smartList, &SmartListView::btnIgnoreInviteClicked,
            this, &CallWidget::slotIgnoreInviteClicked);
172

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

176 177
    connect(&LRCInstance::behaviorController(), &BehaviorController::showIncomingCallView,
            this, &CallWidget::slotShowIncomingCallView);
178

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

182 183 184
    connect(ui->currentAccountComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &CallWidget::slotAccountChanged);

185 186 187 188 189 190 191 192 193
    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);

194 195
    connect(ui->currentAccountComboBox, QOverload<int>::of(&CurrentAccountComboBox::currentIndexChanged),
        [this] {
196 197 198 199
            ui->btnConversations->setChecked(true);
            ui->btnInvites->setChecked(false);
        });

200 201 202 203 204
    connect(ui->messageView, &MessageWebView::conversationRemoved,
        [this] {
            backToWelcomePage();
        });

205 206 207
    connect(ui->messageView, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(ShowContextMenu(const QPoint&)));

208 209
    // set first view to welcome view
    ui->stackedWidget->setCurrentWidget(ui->welcomePage);
210 211 212 213
    ui->btnConversations->setChecked(true);

    // chat view
    ui->messageView->buildView();
214
    ui->messageView->setContextMenuPolicy(Qt::CustomContextMenu);
215

216
    // hide the call stack
217
    setCallPanelVisibility(false);
218 219

    ui->containerWidget->setVisible(false);
220 221 222 223 224
}

CallWidget::~CallWidget()
{
    delete ui;
225
    delete menu_;
226 227
}

228 229 230 231 232 233 234
void
CallWidget::navigated(bool to)
{
    ui->containerWidget->setVisible(to);
    if (to) {
        updateSmartList();
        connectConversationModel();
235 236 237 238 239 240 241 242 243
        try {
            auto accountList = LRCInstance::accountModel().getAccountList();
            if (accountList.size() == 1) {
                auto index = Utils::indexInVector(accountList, LRCInstance::getCurrAccId());
                if (index != -1) {
                    slotAccountChanged(index);
                }
            }
        } catch (...) {}
244
        ui->currentAccountComboBox->updateComboBoxDisplay();
245 246 247 248 249 250 251 252 253
        auto selectedConvUid = LRCInstance::getSelectedConvUid();
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
        if (!selectedConvUid.empty() && conversation != convModel->allFilteredConversations().end()) {
            selectSmartlistItem(selectedConvUid);
            ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        } else {
            backToWelcomePage();
        }
254 255 256 257 258 259
    } else {
        QObject::disconnect(smartlistSelectionConnection_);
        smartListModel_.reset(nullptr);
    }
}

260 261
void
CallWidget::updateCustomUI()
Andreas Traczyk's avatar
Andreas Traczyk committed
262
{
263 264 265 266 267 268 269
    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
270 271
}

272 273 274 275
void
CallWidget::slotAccountOnBoarded()
{}

276 277 278 279 280 281
int
CallWidget::getLeftPanelWidth()
{
    return ui->currentAccountComboBox->width();
}

282
void
283 284 285
CallWidget::onIncomingMessage(const std::string& convUid,
                              uint64_t interactionId,
                              const lrc::api::interaction::Info& interaction)
286
{
287
    Q_UNUSED(interactionId);
288
    if (!QApplication::focusWidget()) {
289 290
        auto convModel = LRCInstance::getCurrentConversationModel();
        auto conversation = Utils::getConversationFromUid(convUid, *convModel);
291 292 293
        if (conversation == convModel->allFilteredConversations().end()) {
            return;
        }
294
        auto bestName = Utils::bestNameForConversation(*conversation, *convModel);
295
        Utils::showSystemNotification(this,
Andreas Traczyk's avatar
Andreas Traczyk committed
296 297
            QString::fromStdString(bestName),
            QString::fromStdString(interaction.body));
298
    }
299
    updateConversationsFilterWidget();
300
    if (convUid != LRCInstance::getSelectedConvUid()) {
301
        return;
302
    }
303 304 305

    auto convModel = LRCInstance::getCurrentConversationModel();
    convModel->clearUnreadInteractions(convUid);
306 307 308
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        ui->messageView->printNewInteraction(*convModel, interactionId, interaction);
309
    }
Edric Milaret's avatar
Edric Milaret committed
310 311 312
}

void
313
CallWidget::setupSmartListContextMenu(const QPoint& pos)
314
{
315 316 317
    QPoint globalPos = ui->smartList->mapToGlobal(pos);
    auto index = ui->smartList->indexAt(pos);
    if (not index.isValid()) {
318
        return;
319
    }
320

321 322 323 324 325 326 327
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID))
        .value<QString>()
        .toStdString();
    auto conversation = Utils::getConversationFromUid(convUid, *convModel);
    auto contactUid = (*conversation).participants.at(0);
    auto contact = LRCInstance::getCurrentAccountInfo().contactModel.get()->getContact(contactUid);
328

329
    if (!Utils::isContactValid(contactUid, *convModel)) {
330
        return;
331
    }
332

333
    QMenu menu;
334

335 336 337 338 339 340 341
    // 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)));
342
            if (convUid != LRCInstance::getSelectedConvUid()) {
343 344
                selectConversation(*conversation, *convModel);
            }
345
        });
346 347 348 349 350 351 352
    // 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)));
353
            if (convUid != LRCInstance::getSelectedConvUid()) {
354 355
                selectConversation(*conversation, *convModel);
            }
356
        });
357
    // clear conversation
358
    auto clearConversationAction = new QAction(tr("Clear conversation"), this);
359 360 361 362 363 364 365 366 367 368 369 370
    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);
        });
371 372 373 374 375 376 377 378 379 380 381

    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
382
            });
383

384 385 386 387 388 389 390 391 392 393 394 395
        // 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)
                );
            });
396
    }
397
    smartListModel_->isContextMenuOpen = true;
398
    menu.exec(globalPos);
399
    smartListModel_->isContextMenuOpen = false;
Edric Milaret's avatar
Edric Milaret committed
400 401
}

402 403
void
CallWidget::setupQRCode(QString ringID)
404
{
405
    auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
Edric Milaret's avatar
Edric Milaret committed
406 407
                                     0, //Let the version be decided by libqrencode
                                     QR_ECLEVEL_L, // Lowest level of error correction
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
                                     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
424
    unsigned char* p;
425 426
    p = rcode->data;
    for(int y = 0; y < rcode->width; y++) {
Edric Milaret's avatar
Edric Milaret committed
427
        unsigned char* row = (p + (y * rcode->width));
428 429 430 431 432 433 434 435 436
        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);
437 438
    ui->qrLabel->setPixmap(QPixmap::fromImage(result.scaled(QSize(qrSize_, qrSize_),
                           Qt::KeepAspectRatio)));
439 440
}

441
void
442
CallWidget::on_smartList_clicked(const QModelIndex& index)
443
{
444
    Q_UNUSED(index);
445 446 447 448 449
}

void
CallWidget::on_acceptButton_clicked()
{
450 451 452 453
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->accept(convInfo.callId);
    }
454 455 456 457 458
}

void
CallWidget::on_refuseButton_clicked()
{
459 460 461 462 463
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->hangUp(convInfo.callId);
        showConversationView();
    }
464 465 466
}

void
467
CallWidget::on_cancelButton_clicked()
468
{
469
    on_refuseButton_clicked();
470 471 472
}

void
473
CallWidget::showConversationView()
474
{
475 476 477 478
    if (LRCInstance::getSelectedConvUid().empty()) {
        backToWelcomePage();
        return;
    }
479 480
    ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
    ui->messageView->setFocus();
481 482 483
    if (ui->messagesWidget->isHidden()) {
        ui->messagesWidget->show();
    }
484
}
485

486
bool
487
CallWidget::selectSmartlistItem(const std::string & convUid)
488
{
489
    if (convUid.empty() || !ui->smartList->selectionModel())
490
        return false;
491 492 493 494
    ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto conversation = Utils::getConversationFromUid(convUid, *convModel);
    if (conversation == convModel->allFilteredConversations().end()) {
495
        return false;
496 497 498 499
    }
    auto contactURI = QString::fromStdString((*conversation).participants[0]);
    if (contactURI.isEmpty() ||
        convModel->owner.contactModel->getContact(contactURI.toStdString()).profileInfo.type == lrc::api::profile::Type::TEMPORARY) {
500
        return false;
501 502 503 504 505 506
    }
    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);
507
            return true;
508
        }
509
    }
510
    return false;
511 512
}

513
void
514
CallWidget::on_smartList_doubleClicked(const QModelIndex& index)
515
{
516 517
    if (!index.isValid())
        return;
518

519 520
    selectConversation(index);

521
    LRCInstance::getCurrentConversationModel()->placeCall(LRCInstance::getSelectedConvUid());
522

523
    ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(LRCInstance::getSelectedConvUid())));
524
}
525

526 527
QImage
CallWidget::imageForConv(const std::string& convUid)
528
{
529
    return Utils::conversationPhoto(convUid, LRCInstance::getCurrentAccountInfo());
530
}
531 532

void
533
CallWidget::smartListSelectionChanged(const QItemSelection  &selected, const QItemSelection  &deselected)
534
{
535 536 537 538
    Q_UNUSED(deselected);
    QModelIndexList indices = selected.indexes();

    if (indices.isEmpty()) {
539
        return;
540
    }
541

542
    auto selectedIndex = indices.at(0);
543

544 545
    if (not selectedIndex.isValid()) {
        return;
546
    }
547 548

    selectConversation(selectedIndex);
549 550 551
}

void
552
CallWidget::conversationsButtonClicked()
553
{
554 555
    ui->btnConversations->setChecked(true);
    ui->btnInvites->setChecked(false);
556
    ui->ringContactLineEdit->setPlaceholderString(tr("Find a new or existing contact"));
557
    setConversationFilter(lrc::api::profile::Type::RING);
558 559
}

560
void
561
CallWidget::invitationsButtonClicked()
562
{
563 564
    ui->btnConversations->setChecked(false);
    ui->btnInvites->setChecked(true);
565
    ui->ringContactLineEdit->setPlaceholderString(tr("Search your received invitations"));
566
    setConversationFilter(lrc::api::profile::Type::PENDING);
567 568
}

569 570
void
CallWidget::settingsButtonClicked()
571
{
572
    emit NavigationRequested(ScreenEnum::SetttingsScreen);
573 574
}

575
void
576
CallWidget::processContactLineEdit()
577 578
{
    auto contactLineText = ui->ringContactLineEdit->text();
579
    setConversationFilter(contactLineText);
580 581 582
}

void
583 584
CallWidget::on_ringContactLineEdit_returnPressed()
{
585 586 587
    // select current temporary item and show conversation
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto conversations = convModel->allFilteredConversations();
588 589
    if (!conversations.empty() &&
        Utils::isContactValid(conversations.at(0).participants.at(0), *convModel)) {
590 591
        selectConversation(smartListModel_->index(0));
    }
592 593
}

594
void CallWidget::slotAcceptInviteClicked(const QModelIndex & index)
595
{
596 597 598
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
    LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
    ui->messageView->setInvitation(false);
599 600
}

601
void CallWidget::slotBlockInviteClicked(const QModelIndex & index)
602
{
603 604 605 606 607
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
    if (!convUid.empty() && convUid == LRCInstance::getSelectedConvUid()) {
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
608
}
609

610
void CallWidget::slotIgnoreInviteClicked(const QModelIndex & index)
611
{
612 613 614 615 616
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
    if (!convUid.empty() && convUid == LRCInstance::getSelectedConvUid()) {
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
617
}
618

619 620 621 622
void CallWidget::slotCustomContextMenuRequested(const QPoint& pos)
{
    setupSmartListContextMenu(pos);
}
623

624 625
void CallWidget::slotAccountChanged(int index)
{
Andreas Traczyk's avatar
Andreas Traczyk committed
626 627 628 629
    try {
        auto accountList = LRCInstance::accountModel().getAccountList();
        setSelectedAccount(accountList.at(index));
    } catch (...) {
630
        qWarning() << "CallWidget::slotAccountChanged exception";
Andreas Traczyk's avatar
Andreas Traczyk committed
631
    }
632 633
}

634 635 636 637 638
void CallWidget::slotShowCallView(const std::string& accountId,
                                  const lrc::api::conversation::Info& convInfo)
{
    Q_UNUSED(accountId);
    Q_UNUSED(convInfo);
639
    qDebug() << "slotShowCallView";
640
    setCallPanelVisibility(true);
641
    ui->callStackWidget->setCurrentWidget(ui->videoPage);
642
    ui->videoWidget->showChatviewIfToggled();
643 644
    hideMiniSpinner();
}
645

646
void CallWidget::slotShowIncomingCallView(const std::string& accountId,
647 648
                                          const lrc::api::conversation::Info& convInfo)
{
649
    Q_UNUSED(accountId);
650 651
    qDebug() << "slotShowIncomingCallView";

652
    auto callModel = LRCInstance::getCurrentCallModel();
653 654 655 656 657 658 659 660 661 662 663

    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 : "";

664
    auto call = callModel->getCall(convInfo.callId);
665
    auto isCallSelected = LRCInstance::getSelectedConvUid() == convInfo.uid;
666

667
    auto itemInCurrentFilter = false;
668
    if (call.isOutgoing) {
669 670 671 672
        if (isCallSelected) {
            miniSpinner_->start();
            ui->spinnerLabel->show();
            ui->callStackWidget->setCurrentWidget(ui->outgoingCallPage);
673
            setCallPanelVisibility(true);
674
        }
675
    } else {
676
        if (!QApplication::focusWidget()) {
677
            auto formattedName = Utils::bestNameForConversation(convInfo, *convModel);
678 679
            Utils::showSystemNotification(this,
                QString(tr("Call incoming from %1")).arg(QString::fromStdString(formattedName)));
680
        }
681 682
        auto selectedAccountId = LRCInstance::getCurrentAccountInfo().id;
        auto accountProperties = LRCInstance::accountModel().getAccountConfig(selectedAccountId);
683 684
        if (!isCallSelected)
            itemInCurrentFilter = selectSmartlistItem(convInfo.uid);
685 686
        if (accountProperties.autoAnswer) {
            ui->callStackWidget->setCurrentWidget(ui->videoPage);
687
        } else if (isCallSelected || !itemInCurrentFilter) {
688 689
            ui->callStackWidget->setCurrentWidget(ui->incomingCallPage);
        }
690
        setCallPanelVisibility(true);
691
    }
692

693
    if (!itemInCurrentFilter && !isCallSelected) {
694 695
        if (ui->smartList->selectionModel())
            ui->smartList->selectionModel()->clear();
696 697 698
        LRCInstance::setSelectedConvId(convInfo.uid);
        showChatView(accountId, convInfo);
    } else if (ui->messagesWidget->isHidden()) {
699 700 701
        ui->messagesWidget->show();
    }

702
    ui->videoWidget->pushRenderer(convInfo.callId);
703

704 705
    QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
    QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
706

707 708 709 710 711 712 713 714
    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);
715

716
    ui->smartList->update();
717
}
718

719
void CallWidget::slotShowChatView(const std::string& accountId,
720 721
                      const lrc::api::conversation::Info& convInfo)
{
722 723
    Q_UNUSED(accountId);
    Q_UNUSED(convInfo);
724

725
    setCallPanelVisibility(false);
726 727
    showConversationView();
}
728

729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
void
CallWidget::slotToggleFullScreenClicked()
{
    if (ui->mainActivityWidget->isFullScreen()) {
        ui->stackedWidget->addWidget(ui->mainActivityWidget);
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->showNormal();
    } else {
        ui->stackedWidget->removeWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->setParent(0);
        ui->mainActivityWidget->showFullScreen();
    }
}

void
CallWidget::slotVideoViewDestroyed(const std::string& callid)
{
746
    auto conversation = Utils::getSelectedConversation();
747
    if (conversation.uid.empty() || callid != conversation.callId) {
748 749
        return;
    }
750 751 752 753 754 755 756 757
    if (ui->mainActivityWidget->isFullScreen()) {
        ui->stackedWidget->addWidget(ui->mainActivityWidget);
        ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
        ui->mainActivityWidget->showNormal();
    }
    showConversationView();
}

758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
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) {
        Utils::setStackWidget(ui->stackedWidget, ui->welcomePage);
    }

    // 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();
783
    connectAccount(accountId);
784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800
}

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_);
801
    }
802
    ui->conversationsFilterWidget->setVisible(invites);
803 804
    ui->searchTopLeftWidget->setVisible(invites);
    ui->searchTopRightWidget->setVisible(invites);
805
    ui->conversationsFilterWidget->update();
806
    ui->conversationsFilterWidget->updateBadges();
807 808 809 810 811
}

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

814
void
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
CallWidget::showChatView(const QModelIndex& nodeIdx)
{
    auto convUid = nodeIdx.data(static_cast<int>(SmartListModel::Role::UID)).toString().toStdString();
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto convInfo = Utils::getConversationFromUid(convUid, *convModel);
    if (convInfo != convModel->allFilteredConversations().end())
        setupChatView(*convInfo);
}

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

void
CallWidget::setupChatView(const lrc::api::conversation::Info& convInfo)
833
{
834 835 836 837 838
    auto& accInfo = LRCInstance::getCurrentAccountInfo();
    auto& contact = accInfo.contactModel->getContact(convInfo.participants.at(0));
    QString displayName = QString::fromStdString(Utils::bestNameForContact(contact));
    QString displayId = QString::fromStdString(Utils::bestIdForContact(contact));
    QString contactURI = QString::fromStdString(convInfo.participants.at(0));
839 840

    bool isContact = false;
841
    auto selectedAccountId = LRCInstance::getCurrAccId();
842 843 844
    auto& accountInfo = LRCInstance::accountModel().getAccountInfo(selectedAccountId);
    bool isRINGAccount = accountInfo.profileInfo.type == lrc::api::profile::Type::RING;
    try {
845 846 847 848
        auto contactInfo = accountInfo.contactModel->getContact(contactURI.toStdString());
        if (contactInfo.isTrusted) {
            isContact = true;
        }
849
    } catch (...) {}
Olivier SOLDANO's avatar
Olivier SOLDANO committed
850

851
    ui->imNameLabel->setText(QString(tr("%1", "%1 is the contact username"))
852
        .arg(displayName));
Olivier SOLDANO's avatar
Olivier SOLDANO committed
853

854
    if (isRINGAccount && displayName != displayId) {
Olivier SOLDANO's avatar
Olivier SOLDANO committed
855 856
        ui->imIdLabel->show();
        ui->imIdLabel->setText(QString(tr("%1", "%1 is the contact unique identifier"))
857
            .arg(displayId));
Olivier SOLDANO's avatar
Olivier SOLDANO committed
858 859 860 861
    } else {
        ui->imIdLabel->hide();
    }

862
    bool shouldShowSendContactRequestBtn = !isContact && isRINGAccount;
863
    ui->sendContactRequestButton->setVisible(shouldShowSendContactRequestBtn);
864

Andreas Traczyk's avatar
Andreas Traczyk committed
865
    ui->messageView->setMessagesVisibility(false);
866
    ui->messageView->clear();
867
    ui->messageView->setInvitation(false);
Andreas Traczyk's avatar
Andreas Traczyk committed
868
    Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
869
        [this, convInfo] {
Andreas Traczyk's avatar
Andreas Traczyk committed
870
            auto convModel = LRCInstance::getCurrentConversationModel();
871
            ui->messageView->printHistory(*convModel, convInfo.interactions);
Andreas Traczyk's avatar
Andreas Traczyk committed
872 873 874 875 876 877
            Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesLoaded,
                [this] {
                    ui->messageView->setMessagesVisibility(true);
                });
            // Contact Avatars
            auto accInfo = &LRCInstance::getCurrentAccountInfo();
878
            auto contactUri = convInfo.participants.front();
Andreas Traczyk's avatar
Andreas Traczyk committed
879 880
            try {
                auto& contact = accInfo->contactModel->getContact(contactUri);
881
                auto bestName = Utils::bestNameForConversation(convInfo, *convModel);
Andreas Traczyk's avatar
Andreas Traczyk committed
882 883 884 885 886 887 888 889 890 891
                ui->messageView->setInvitation(
                    (contact.profileInfo.type == lrc::api::profile::Type::PENDING),
                    bestName,
                    accInfo->contactModel->getContactProfileId(contact.profileInfo.uri)
                );
                if (!contact.profileInfo.avatar.empty()) {
                    ui->messageView->setSenderImage(
                        accInfo->contactModel->getContactProfileId(contactUri),
                        contact.profileInfo.avatar);
                } else {
892
                    auto avatar = Utils::conversationPhoto(convInfo.uid, *accInfo);
Andreas Traczyk's avatar
Andreas Traczyk committed
893 894 895 896 897 898 899 900 901 902
                    QByteArray ba;
                    QBuffer bu(&ba);
                    avatar.save(&bu, "PNG");
                    std::string avatarString = ba.toBase64().toStdString();
                    ui->messageView->setSenderImage(
                        accInfo->contactModel->getContactProfileId(contactUri),
                        avatarString);
                }
            } catch (...) {}
        });
903
}
904 905

void
906
CallWidget::on_ringContactLineEdit_textChanged(const QString& text)
907
{
908 909
    Q_UNUSED(text);
    processContactLineEdit();
910
}
Edric Milaret's avatar
Edric Milaret committed
911

912
void
913
CallWidget::backToWelcomePage()
Edric Milaret's avatar
Edric Milaret committed
914
{
915 916
    deselectConversation();
    ui->stackedWidget->setCurrentWidget(ui->welcomePage);
917 918
}

919 920
void
CallWidget::hideMiniSpinner()
921 922 923 924 925 926 927
{
    if(ui->spinnerLabel->isVisible()){
        miniSpinner_->stop();
        ui->spinnerLabel->hide();
    }
}

928 929 930
void
CallWidget::on_imBackButton_clicked()
{
931
    ui->messageView->clear();
Andreas Traczyk's avatar
Andreas Traczyk committed
932 933 934
    Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
        [this] {
            QTimer::singleShot(33, this, [this] { backToWelcomePage(); });
935
        });
936 937
}

938 939 940 941 942 943
void
CallWidget::on_qrButton_toggled(bool checked)
{
    ui->qrLabel->setVisible(checked);
}

944 945 946
void
CallWidget::on_shareButton_clicked()
{
947
    Utils::InvokeMailto(tr("Contact me on Jami"), tr("My Id is : ") + ui->ringIdLabel->text());
948
}
949 950

void
951
CallWidget::on_sendContactRequestButton_clicked()
952
{
953 954 955 956 957
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->makePermanent(convInfo.uid);
        ui->sendContactRequestButton->hide();
    }
958
}
959

960
void
961
CallWidget::on_btnAudioCall_clicked()
962
{
963 964 965 966 967
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->placeAudioOnlyCall(convInfo.uid);
        ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
    }
968 969 970 971 972
}

void
CallWidget::on_btnVideoCall_clicked()
{
973 974 975 976 977
    auto convInfo = Utils::getSelectedConversation();
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->placeCall(convInfo.uid);
        ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
    }
978 979 980 981 982 983 984 985 986
}

bool
CallWidget::connectConversationModel()
{
    auto currentConversationModel = LRCInstance::getCurrentAccountInfo().conversationModel.get();

    if (ui->smartList->selectionModel()) {
        ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
987 988
    }

989 990 991 992 993 994 995 996
    QObject::disconnect(modelSortedConnection_);
    QObject::disconnect(modelUpdatedConnection_);
    QObject::disconnect(filterChangedConnection_);
    QObject::disconnect(newConversationConnection_);
    QObject::disconnect(conversationRemovedConnection_);
    QObject::disconnect(conversationClearedConnection);
    QObject::disconnect(interactionStatusUpdatedConnection_);
    QObject::disconnect(newInteractionConnection_);
997
    QObject::disconnect(interactionRemovedConnection_);
998 999 1000 1001 1002

    modelSortedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::modelSorted,
        [this]() {
            updateConversationsFilterWidget();
1003
            selectSmartlistItem(Utils::getSelectedConversation().uid);
1004
            ui->smartList->update();
1005 1006
        }
    );
1007 1008 1009 1010 1011
    modelUpdatedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationUpdated,
        [this](const std::string& convUid) {
            Q_UNUSED(convUid);
            ui->smartList->update();
1012 1013
        }
    );
1014 1015 1016 1017 1018 1019
    filterChangedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::filterChanged,
        [this]() {
            updateSmartList();
            updateConversationsFilterWidget();
            ui->smartList->update();
1020 1021
        }
    );
1022 1023 1024 1025 1026 1027
    newConversationConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::newConversation,
        [this](const std::string& convUid) {
            updateSmartList();
            updateConversationForNewContact(convUid);
            ui->conversationsFilterWidget->update();
1028 1029
        }
    );
1030 1031 1032 1033
    conversationRemovedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationRemoved,
        [this]() {
            backToWelcomePage();
1034 1035
        }
    );
1036 1037 1038
    conversationClearedConnection = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationCleared,
        [this](const std::string& convUid) {
1039
            ui->messageView->clear();
1040 1041
            // if currently selected,
            // switch to welcome screen (deselecting current smartlist item )
1042
            if (convUid != LRCInstance::getSelectedConvUid()) {
1043 1044 1045
                return;
            }
            backToWelcomePage();
1046 1047
        }
    );
1048 1049
    interactionStatusUpdatedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
1050
        [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
1051
            if (convUid != LRCInstance::getSelectedConvUid()) {
1052 1053
                return;
            }
1054 1055 1056 1057 1058
            auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
            auto currentConversationModel = currentAccountInfo.conversationModel.get();
            currentConversationModel->clearUnreadInteractions(convUid);
            ui->conversationsFilterWidget->update();
            ui->messageView->updateInteraction(*currentConversationModel, interactionId, interaction);
1059 1060
        }
    );
1061 1062 1063 1064
    newInteractionConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::newInteraction,
        [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
            onIncomingMessage(convUid, interactionId, interaction);
1065 1066 1067
            if (interaction.type == lrc::api::interaction::Type::CALL) {
                return;
            }
1068
            if (convUid == LRCInstance::getSelectedConvUid()) {
1069 1070
                ui->videoWidget->simulateShowChatview(true);
            }
1071 1072
        }
    );
1073 1074 1075 1076 1077 1078 1079
    interactionRemovedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::interactionRemoved,
        [this](const std::string& convUid, uint64_t interactionId) {
            Q_UNUSED(convUid);
            ui->messageView->removeInteraction(interactionId);
        }
    );
1080 1081 1082 1083
    currentConversationModel->setFilter("");
    // clear search field
    ui->ringContactLineEdit->setText("");
    return true;
1084
}
1085 1086

void
1087
CallWidget::updateConversationView(const std::string& convUid)
1088
{
1089
    if (convUid != LRCInstance::getSelectedConvUid()) {
1090 1091
        return;
    }
1092 1093
}

1094
void
1095
CallWidget::selectConversation(const QModelIndex& index)
1096
{
1097 1098 1099 1100 1101 1102 1103 1104 1105
    auto currentConversationModel = LRCInstance::getCurrentConversationModel();

    if (currentConversationModel == nullptr || !index.isValid()) {
        return;
    }

    const auto item = currentConversationModel->filteredConversation(index.row());

    if (selectConversation(item, *currentConversationModel)) {
1106
        showChatView(index);
1107
        auto convUid = LRCInstance::getSelectedConvUid();
1108 1109 1110 1111 1112 1113 1114 1115
        if (!lastConvUid_.compare(convUid)) {
            return;
        }
        lastConvUid_.assign(convUid);
        auto currentConversationModel = LRCInstance::getCurrentConversationModel();
        auto callModel = LRCInstance::getCurrentCallModel();
        auto conversation = Utils::getConversationFromUid(convUid, *currentConversationModel);
        const auto item = currentConversationModel->filteredConversation(index.row());
1116
        if (callModel->hasCall(conversation->callId) && item.callId == conversation->callId) {
1117
            setCallPanelVisibility(true);
1118
            return;
1119
        }
1120
        setCallPanelVisibility(false);
1121
    }
1122
}
1123

1124 1125 1126
bool
CallWidget::selectConversation( const lrc::api::conversation::Info& item,
                                lrc::api::ConversationModel& convModel)
1127
{
1128
    if (LRCInstance::getSelectedConvUid() == item.uid) {
1129 1130 1131
        return false;
    } else if (item.participants.size() > 0) {
        LRCInstance::setSelectedConvId(item.uid);
1132
        convModel.selectConversation(item.uid);
1133 1134 1135 1136
        convModel.clearUnreadInteractions(item.uid);
        ui->conversationsFilterWidget->update();
        return true;
    }
1137
}
1138

1139 1140
void
CallWidget::deselectConversation()
1141
{
1142 1143 1144 1145
    auto currentConversationModel = LRCInstance::getCurrentConversationModel();

    if (currentConversationModel == nullptr) {
        return;
1146
    }
1147 1148 1149 1150 1151 1152

    currentConversationModel->selectConversation("");
    LRCInstance::setSelectedConvId("");

    ui->smartList->selectionModel()->clear();
    disconnect(imConnection_);
1153
}
1154

1155 1156
void
CallWidget::updateConversationForNewContact(const std::string& convUid)
1157
{
1158 1159 1160