callwidget.cpp 54.5 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
 * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>              *
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 54 55 56 57
#include <QDesktopServices>
#include <QScrollBar>
#include <QWebEngineScript>
#include <QMimeData>

#include <algorithm>
#include <memory>

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

65
    using namespace lrc::api;
66

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

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

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

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

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

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

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

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

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

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

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

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

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

138
    connect(ui->btnConversations, &QPushButton::clicked,
139
            this, &CallWidget::conversationsButtonClicked);
140

141
    connect(ui->btnInvites, &QPushButton::clicked,
142
            this, &CallWidget::invitationsButtonClicked);
143

144
    connect(ui->smartList, &QTreeView::customContextMenuRequested,
145
            this, &CallWidget::slotCustomContextMenuRequested);
146

147 148
    connect(ui->smartList, &SmartListView::btnAcceptInviteClicked,
            this, &CallWidget::slotAcceptInviteClicked);
149

150 151
    connect(ui->smartList, &SmartListView::btnBlockInviteClicked,
            this, &CallWidget::slotBlockInviteClicked);
152

153 154
    connect(ui->smartList, &SmartListView::btnIgnoreInviteClicked,
            this, &CallWidget::slotIgnoreInviteClicked);
155

156 157 158
    connect(ui->currentAccountComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
            this, &CallWidget::slotAccountChanged);

159 160 161 162 163 164 165 166 167
    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);

168 169
    connect(ui->currentAccountComboBox, QOverload<int>::of(&CurrentAccountComboBox::currentIndexChanged),
        [this] {
170 171 172 173
            ui->btnConversations->setChecked(true);
            ui->btnInvites->setChecked(false);
        });

174 175 176 177 178
    connect(ui->messageView, &MessageWebView::conversationRemoved,
        [this] {
            backToWelcomePage();
        });

179 180 181
    connect(ui->messageView, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(ShowContextMenu(const QPoint&)));

182 183 184
    connect(ui->messageView, &MessageWebView::pasteKeyDetected,
            this, &CallWidget::Paste);

185 186
    connect(ui->messageView, &MessageWebView::invitationAccepted,
            [this] {
187
                auto convUid = LRCInstance::getCurrentConvUid();
188 189 190 191
                LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
                ui->sendContactRequestButton->hide();
            });

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

    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) {
216
                onNewInteraction(accountId, conversation, interactionId, interaction);
217 218 219
            }
        });

220 221 222
    connect(LRCInstance::renderer(), &RenderManager::videoDeviceListChanged,
            this, &CallWidget::slotVideoDeviceListChanged);

223 224
    connect(ui->changelogButton, &QAbstractButton::clicked,
        [this] {
225 226
            auto aboutDialog = std::make_unique<AboutDialog>(&MainWindow::instance());
            aboutDialog->getContainer()->exec();
227 228
        });

229 230
    // set first view to welcome view
    ui->stackedWidget->setCurrentWidget(ui->welcomePage);
231 232 233 234
    ui->btnConversations->setChecked(true);

    // chat view
    ui->messageView->buildView();
235
    ui->messageView->setContextMenuPolicy(Qt::CustomContextMenu);
236

237
    // hide the call stack
238
    setCallPanelVisibility(false);
239 240

    ui->containerWidget->setVisible(false);
241 242 243 244 245
}

CallWidget::~CallWidget()
{
    delete ui;
246
    delete menu_;
247 248
}

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

284 285
void
CallWidget::updateCustomUI()
Andreas Traczyk's avatar
Andreas Traczyk committed
286
{
287 288 289 290 291 292 293
    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
294 295
}

296
void
297
CallWidget::slotAccountListChanged()
298 299
{}

300 301 302 303 304 305
int
CallWidget::getLeftPanelWidth()
{
    return ui->currentAccountComboBox->width();
}

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

void
338
CallWidget::setupSmartListContextMenu(const QPoint& pos)
339
{
340 341 342
    QPoint globalPos = ui->smartList->mapToGlobal(pos);
    auto index = ui->smartList->indexAt(pos);
    if (not index.isValid()) {
343
        return;
344
    }
345

346 347 348 349
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID))
        .value<QString>()
        .toStdString();
350 351 352
    auto& conversation = LRCInstance::getConversationFromConvUid(convUid);
    if (conversation.uid.empty()) return;
    auto contactUid = conversation.participants.at(0);
353
    auto contact = LRCInstance::getCurrentAccountInfo().contactModel.get()->getContact(contactUid);
354

355
    if (!Utils::isContactValid(contactUid, *convModel)) {
356
        return;
357
    }
358

359
    QMenu menu;
360

361 362 363 364 365 366 367
    // 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)));
368
            if (convUid != LRCInstance::getCurrentConvUid()) {
369
                selectConversation(conversation, *convModel);
370
            }
371
        });
372 373 374 375 376 377 378
    // 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)));
379
            if (convUid != LRCInstance::getCurrentConvUid()) {
380
                selectConversation(conversation, *convModel);
381
            }
382
        });
383
    // clear conversation
384
    auto clearConversationAction = new QAction(tr("Clear conversation"), this);
385 386 387 388 389 390 391 392 393 394 395 396
    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);
        });
397 398 399 400 401 402 403 404 405 406 407

    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
408
            });
409

410 411 412 413 414 415 416 417 418 419 420 421
        // 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)
                );
            });
422
    }
423
    smartListModel_->isContextMenuOpen = true;
424
    menu.exec(globalPos);
425
    smartListModel_->isContextMenuOpen = false;
Edric Milaret's avatar
Edric Milaret committed
426 427
}

428 429
void
CallWidget::setupQRCode(QString ringID)
430
{
431
    auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
Edric Milaret's avatar
Edric Milaret committed
432 433
                                     0, //Let the version be decided by libqrencode
                                     QR_ECLEVEL_L, // Lowest level of error correction
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
                                     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
450
    unsigned char* p;
451 452
    p = rcode->data;
    for(int y = 0; y < rcode->width; y++) {
Edric Milaret's avatar
Edric Milaret committed
453
        unsigned char* row = (p + (y * rcode->width));
454 455 456 457 458 459 460 461 462
        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);
463 464
    ui->qrLabel->setPixmap(QPixmap::fromImage(result.scaled(QSize(qrSize_, qrSize_),
                           Qt::KeepAspectRatio)));
465 466
}

467
void
468
CallWidget::on_smartList_clicked(const QModelIndex& index)
469
{
470
    Q_UNUSED(index);
471 472 473 474 475
}

void
CallWidget::on_acceptButton_clicked()
{
476
    auto convInfo = LRCInstance::getCurrentConversation();
477 478 479
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->accept(convInfo.callId);
    }
480 481 482 483 484
}

void
CallWidget::on_refuseButton_clicked()
{
485
    auto convInfo = LRCInstance::getCurrentConversation();
486
    if (!convInfo.uid.empty()) {
487
        LRCInstance::getCurrentCallModel()->refuse(convInfo.callId);
488 489
        showConversationView();
    }
490 491 492
}

void
493
CallWidget::on_cancelButton_clicked()
494
{
495
    auto convInfo = LRCInstance::getCurrentConversation();
496 497 498 499
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentCallModel()->hangUp(convInfo.callId);
        showConversationView();
    }
500 501 502
}

void
503
CallWidget::showConversationView()
504
{
505
    if (LRCInstance::getCurrentConvUid().empty()) {
506 507 508
        backToWelcomePage();
        return;
    }
509 510
    ui->stackedWidget->setCurrentWidget(ui->mainActivityWidget);
    ui->messageView->setFocus();
511 512 513
    if (ui->messagesWidget->isHidden()) {
        ui->messagesWidget->show();
    }
514
    updateChatviewFrame();
515
}
516

517
bool
518
CallWidget::selectSmartlistItem(const std::string& convUid)
519
{
520
    if (convUid.empty() || !ui->smartList->selectionModel())
521
        return false;
522 523
    ui->smartList->selectionModel()->setCurrentIndex(QModelIndex(), QItemSelectionModel::Deselect);
    auto convModel = LRCInstance::getCurrentConversationModel();
524 525
    auto& conversation = LRCInstance::getConversationFromConvUid(convUid);
    if (conversation.uid.empty()) {
526
        return false;
527
    }
528
    auto contactURI = QString::fromStdString(conversation.participants[0]);
529 530
    if (contactURI.isEmpty() ||
        convModel->owner.contactModel->getContact(contactURI.toStdString()).profileInfo.type == lrc::api::profile::Type::TEMPORARY) {
531
        return false;
532 533 534 535 536 537
    }
    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);
538
            return true;
539
        }
540
    }
541
    return false;
542 543
}

544
void
545
CallWidget::on_smartList_doubleClicked(const QModelIndex& index)
546
{
547 548
    if (!index.isValid())
        return;
549

550 551
    selectConversation(index);

552
    LRCInstance::getCurrentConversationModel()->placeCall(LRCInstance::getCurrentConvUid());
553

554
    ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(LRCInstance::getCurrentConvUid())));
555
}
556

557 558
QImage
CallWidget::imageForConv(const std::string& convUid)
559
{
560
    return Utils::conversationPhoto(convUid, LRCInstance::getCurrentAccountInfo());
561
}
562 563

void
564
CallWidget::smartListSelectionChanged(const QItemSelection  &selected, const QItemSelection  &deselected)
565
{
566 567 568 569
    Q_UNUSED(deselected);
    QModelIndexList indices = selected.indexes();

    if (indices.isEmpty()) {
570
        return;
571
    }
572

573
    auto selectedIndex = indices.at(0);
574

575 576
    if (not selectedIndex.isValid()) {
        return;
577
    }
578 579

    selectConversation(selectedIndex);
580 581
}

582 583 584 585 586
void CallWidget::slotSetChatVisibility(bool visible)
{
    ui->messagesWidget->setVisible(visible);
}

587 588 589 590 591 592 593 594
void
CallWidget::slotVideoDeviceListChanged()
{
    if (LRCInstance::hasVideoCall()) {
        LRCInstance::renderer()->startPreviewing();
    }
}

595
void
596
CallWidget::conversationsButtonClicked()
597
{
598 599
    ui->btnConversations->setChecked(true);
    ui->btnInvites->setChecked(false);
600
    ui->ringContactLineEdit->setPlaceholderString(tr("Find a new or existing contact"));
601
    setConversationFilter(lrc::api::profile::Type::RING);
602 603
}

604
void
605
CallWidget::invitationsButtonClicked()
606
{
607 608
    ui->btnConversations->setChecked(false);
    ui->btnInvites->setChecked(true);
609
    ui->ringContactLineEdit->setPlaceholderString(tr("Search your received invitations"));
610
    setConversationFilter(lrc::api::profile::Type::PENDING);
611 612
}

613 614
void
CallWidget::settingsButtonClicked()
615
{
616
    emit NavigationRequested(ScreenEnum::SetttingsScreen);
617 618
}

619
void
620
CallWidget::processContactLineEdit()
621 622
{
    auto contactLineText = ui->ringContactLineEdit->text();
623
    setConversationFilter(contactLineText);
624 625 626
}

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

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

647 648
void
CallWidget::slotBlockInviteClicked(const QModelIndex & index)
649
{
650
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
651
    if (!convUid.empty() && convUid == LRCInstance::getCurrentConvUid()) {
652 653 654
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
655
}
656

657 658
void
CallWidget::slotIgnoreInviteClicked(const QModelIndex & index)
659
{
660
    auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
661
    if (!convUid.empty() && convUid == LRCInstance::getCurrentConvUid()) {
662 663 664
        backToWelcomePage();
    }
    LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
665
}
666

667 668
void
CallWidget::slotCustomContextMenuRequested(const QPoint& pos)
669 670 671
{
    setupSmartListContextMenu(pos);
}
672

673 674
void
CallWidget::slotAccountChanged(int index)
675
{
Andreas Traczyk's avatar
Andreas Traczyk committed
676 677 678 679
    try {
        auto accountList = LRCInstance::accountModel().getAccountList();
        setSelectedAccount(accountList.at(index));
    } catch (...) {
680
        qWarning() << "CallWidget::slotAccountChanged exception";
Andreas Traczyk's avatar
Andreas Traczyk committed
681
    }
682 683
}

684 685
void
CallWidget::slotShowCallView(const std::string& accountId,
686
                             const lrc::api::conversation::Info& convInfo)
687 688
{
    Q_UNUSED(accountId);
689
    qDebug() << "slotShowCallView";
690 691

    // control visible callwidget buttons
692
    setCallPanelVisibility(true);
693

694
    ui->callStackWidget->setCurrentWidget(ui->videoPage);
695
    hideMiniSpinner();
696

697
    ui->videoView->updateCall(convInfo.uid, accountId);
698 699
    ui->videoView->show();
    ui->videoView->setFocus();
700
}
701

702 703
void
CallWidget::slotShowIncomingCallView(const std::string& accountId,
704
                                     const conversation::Info& convInfo)
705
{
706
    Q_UNUSED(accountId);
707 708
    qDebug() << "slotShowIncomingCallView";

709
    auto callModel = LRCInstance::getCurrentCallModel();
710 711 712 713 714 715 716 717 718 719 720

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

721
    auto call = callModel->getCall(convInfo.callId);
722
    auto isCallSelected = LRCInstance::getCurrentConvUid() == convInfo.uid;
723 724
    ui->callingStatusLabel->setText(QString::fromStdString(lrc::api::call::to_string(call.status)));

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

751
    if (!itemInCurrentFilter && !isCallSelected) {
752 753
        if (ui->smartList->selectionModel())
            ui->smartList->selectionModel()->clear();
754 755 756
        LRCInstance::setSelectedConvId(convInfo.uid);
        showChatView(accountId, convInfo);
    } else if (ui->messagesWidget->isHidden()) {
757 758 759
        ui->messagesWidget->show();
    }

760 761
    QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
    QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
762

763 764 765 766 767 768 769 770
    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);
771

772
    ui->smartList->update();
773
}
774

775 776
void
CallWidget::slotShowChatView(const std::string& accountId,
777 778
                      const lrc::api::conversation::Info& convInfo)
{
779 780
    Q_UNUSED(accountId);
    Q_UNUSED(convInfo);
781

782
    setCallPanelVisibility(false);
783 784
    showConversationView();
}
785

786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
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();
    }
}

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

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

845 846 847 848 849 850 851 852
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) {
853
        backToWelcomePage();
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
    }

    // 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();
870
    connectAccount(accountId);
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
}

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_);
888
    }
889
    ui->conversationsFilterWidget->setVisible(invites);
890 891 892 893
    if (!invites) {
        Utils::whileBlocking<QPushButton>(ui->btnConversations)->setChecked(true);
        Utils::whileBlocking<QPushButton>(ui->btnInvites)->setChecked(false);
    }
894 895
    ui->searchTopLeftWidget->setVisible(invites);
    ui->searchTopRightWidget->setVisible(invites);
896
    ui->conversationsFilterWidget->update();
897
    ui->conversationsFilterWidget->updateBadges();
898 899 900 901 902
}

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

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

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

922 923 924 925 926 927
void
CallWidget::setConversationProfileData(const lrc::api::conversation::Info& convInfo)
{
    auto convModel = LRCInstance::getCurrentConversationModel();
    auto accInfo = &LRCInstance::getCurrentAccountInfo();
    auto contactUri = convInfo.participants.front();
928 929 930
    if (contactUri.empty()) {
        return;
    }
931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
    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 (...) {}
}

952 953
void
CallWidget::setupChatView(const lrc::api::conversation::Info& convInfo)
954
{
955 956 957 958 959
    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));
960 961

    bool isContact = false;
962
    auto selectedAccountId = LRCInstance::getCurrAccId();
963 964 965
    auto& accountInfo = LRCInstance::accountModel().getAccountInfo(selectedAccountId);
    bool isRINGAccount = accountInfo.profileInfo.type == lrc::api::profile::Type::RING;
    try {
966 967 968 969
        auto contactInfo = accountInfo.contactModel->getContact(contactURI.toStdString());
        if (contactInfo.isTrusted) {
            isContact = true;
        }
970
    } catch (...) {}
Olivier SOLDANO's avatar
Olivier SOLDANO committed
971

972 973 974
    ui->imNameLabel->setText(displayName);
    ui->imIdLabel->setText(displayId);
    ui->imIdLabel->setVisible(isRINGAccount && displayName != displayId);
Olivier SOLDANO's avatar
Olivier SOLDANO committed
975

976
    bool shouldShowSendContactRequestBtn = !isContact && isRINGAccount;
977
    ui->sendContactRequestButton->setVisible(shouldShowSendContactRequestBtn);
978

Andreas Traczyk's avatar
Andreas Traczyk committed
979
    ui->messageView->setMessagesVisibility(false);
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994
    Utils::oneShotConnect(ui->messageView, &MessageWebView::sendMessageContentSaved,
        [this, &convInfo, &accountInfo, lastConvUid = lastConvUid_](const QString& content) {
            if (!lastConvUid.empty()) {
                LRCInstance::setContentDraft(
                    lastConvUid.c_str(), accountInfo.id.c_str(), content);
            }
            Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
                [this, &convInfo] {
                    auto convModel = LRCInstance::getCurrentConversationModel();
                    ui->messageView->printHistory(*convModel, convInfo.interactions);
                    Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesLoaded,
                        [this] {
                            ui->messageView->setMessagesVisibility(true);
                        });
                    setConversationProfileData(convInfo);
Andreas Traczyk's avatar
Andreas Traczyk committed
995
                });
996 997 998 999 1000 1001
            ui->messageView->setInvitation(false);
            ui->messageView->clear();
            auto restoredContent = LRCInstance::getContentDraft(
                convInfo.uid.c_str(), accountInfo.id.c_str());
            ui->messageView->setSendMessageContent(restoredContent);
            ui->smartList->update();
Andreas Traczyk's avatar
Andreas Traczyk committed
1002
        });
1003
    ui->messageView->requestSendMessageContent();
1004
}
1005 1006

void
1007
CallWidget::on_ringContactLineEdit_textChanged(const QString& text)
1008
{
1009 1010
    Q_UNUSED(text);
    processContactLineEdit();
1011
}
Edric Milaret's avatar
Edric Milaret committed
1012

1013
void
1014
CallWidget::backToWelcomePage()
Edric Milaret's avatar
Edric Milaret committed
1015
{
1016 1017
    deselectConversation();
    ui->stackedWidget->setCurrentWidget(ui->welcomePage);
1018 1019
}

1020 1021
void
CallWidget::hideMiniSpinner()
1022 1023 1024 1025 1026 1027 1028
{
    if(ui->spinnerLabel->isVisible()){
        miniSpinner_->stop();
        ui->spinnerLabel->hide();
    }
}

1029 1030 1031
void
CallWidget::on_imBackButton_clicked()
{
1032
    ui->messageView->clear();
Andreas Traczyk's avatar
Andreas Traczyk committed
1033 1034 1035
    Utils::oneShotConnect(ui->messageView, &MessageWebView::messagesCleared,
        [this] {
            QTimer::singleShot(33, this, [this] { backToWelcomePage(); });
1036
        });
1037 1038
}

1039 1040 1041 1042 1043 1044
void
CallWidget::on_qrButton_toggled(bool checked)
{
    ui->qrLabel->setVisible(checked);
}

1045 1046 1047
void
CallWidget::on_shareButton_clicked()
{
1048
    Utils::InvokeMailto(tr("Contact me on Jami"), tr("My Id is : ") + ui->ringIdLabel->text());
1049
}
1050 1051

void
1052
CallWidget::on_sendContactRequestButton_clicked()
1053
{
1054
    auto convInfo = LRCInstance::getCurrentConversation();
1055 1056 1057 1058
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->makePermanent(convInfo.uid);
        ui->sendContactRequestButton->hide();
    }
1059
}
1060

1061
void
1062
CallWidget::on_btnAudioCall_clicked()
1063
{
1064
    auto convInfo = LRCInstance::getCurrentConversation();
1065 1066 1067 1068
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->placeAudioOnlyCall(convInfo.uid);
        ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
    }
1069 1070 1071 1072 1073
}

void
CallWidget::on_btnVideoCall_clicked()
{
1074
    auto convInfo = LRCInstance::getCurrentConversation();
1075 1076 1077 1078
    if (!convInfo.uid.empty()) {
        LRCInstance::getCurrentConversationModel()->placeCall(convInfo.uid);
        ui->callingPhoto->setPixmap(QPixmap::fromImage(imageForConv(convInfo.uid)));
    }
1079 1080 1081 1082 1083 1084 1085 1086 1087
}

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

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

1090 1091 1092 1093 1094 1095 1096 1097
    QObject::disconnect(modelSortedConnection_);
    QObject::disconnect(modelUpdatedConnection_);
    QObject::disconnect(filterChangedConnection_);
    QObject::disconnect(newConversationConnection_);
    QObject::disconnect(conversationRemovedConnection_);
    QObject::disconnect(conversationClearedConnection);
    QObject::disconnect(interactionStatusUpdatedConnection_);
    QObject::disconnect(newInteractionConnection_);
1098
    QObject::disconnect(interactionRemovedConnection_);
1099 1100 1101 1102 1103

    modelSortedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::modelSorted,
        [this]() {
            updateConversationsFilterWidget();
1104
            selectSmartlistItem(LRCInstance::getCurrentConversation().uid);
1105
            ui->smartList->update();
1106 1107
        }
    );
1108 1109 1110 1111 1112
    modelUpdatedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationUpdated,
        [this](const std::string& convUid) {
            Q_UNUSED(convUid);
            ui->smartList->update();
1113 1114
        }
    );
1115 1116 1117 1118 1119 1120
    filterChangedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::filterChanged,
        [this]() {
            updateSmartList();
            updateConversationsFilterWidget();
            ui->smartList->update();
1121 1122
        }
    );
1123 1124 1125 1126 1127 1128
    newConversationConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::newConversation,
        [this](const std::string& convUid) {
            updateSmartList();
            updateConversationForNewContact(convUid);
            ui->conversationsFilterWidget->update();
1129
            ui->currentAccountComboBox->canPlaceAudioOnlyCall(convUid);
1130 1131
        }
    );
1132 1133 1134 1135
    conversationRemovedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationRemoved,
        [this]() {
            backToWelcomePage();
1136 1137
        }
    );
1138 1139 1140
    conversationClearedConnection = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::conversationCleared,
        [this](const std::string& convUid) {
1141
            ui->messageView->clear();
1142 1143
            // if currently selected,
            // switch to welcome screen (deselecting current smartlist item )
1144
            if (convUid != LRCInstance::getCurrentConvUid()) {
1145 1146 1147
                return;
            }
            backToWelcomePage();
1148 1149
        }
    );
1150 1151
    interactionStatusUpdatedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
1152
        [this](const std::string& convUid, uint64_t interactionId, const lrc::api::interaction::Info& interaction) {
1153
            if (convUid != LRCInstance::getCurrentConvUid()) {
1154 1155
                return;
            }
1156 1157 1158 1159 1160
            auto& currentAccountInfo = LRCInstance::getCurrentAccountInfo();
            auto currentConversationModel = currentAccountInfo.conversationModel.get();
            currentConversationModel->clearUnreadInteractions(convUid);
            ui->conversationsFilterWidget->update();
            ui->messageView->updateInteraction(*currentConversationModel, interactionId, interaction);
1161 1162
        }
    );
1163 1164
    newInteractionConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::newInteraction,
1165 1166 1167
        [this](const std::string& convUid, uint64_t interactionId,
               const lrc::api::interaction::Info& interaction) {
            auto accountId = LRCInstance::getCurrAccId();
1168
            onNewInteraction(accountId, convUid, interactionId, interaction);
1169 1170
        }
    );
1171 1172 1173 1174 1175 1176 1177
    interactionRemovedConnection_ = QObject::connect(
        currentConversationModel, &lrc::api::ConversationModel::interactionRemoved,
        [this](const std