callwidget.cpp 15.2 KB
Newer Older
Edric Milaret's avatar
Edric Milaret committed
1
/***************************************************************************
2
 * Copyright (C) 2015 by Savoir-Faire Linux                                *
Edric Milaret's avatar
Edric Milaret committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
 *                                                                         *
 * 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"

22 23
#include <QClipboard>

24
#include <memory>
Edric Milaret's avatar
Edric Milaret committed
25

26 27 28 29
//ERROR is defined in windows.h
#include "utils.h"
#undef ERROR

Edric Milaret's avatar
Edric Milaret committed
30 31
#include "audio/settings.h"
#include "personmodel.h"
32
#include "person.h"
Edric Milaret's avatar
Edric Milaret committed
33
#include "fallbackpersoncollection.h"
34
#include "categorizedcontactmodel.h"
Edric Milaret's avatar
Edric Milaret committed
35
#include "localhistorycollection.h"
36 37 38
#include "media/text.h"
#include "media/recording.h"
#include "media/textrecording.h"
Edric Milaret's avatar
Edric Milaret committed
39

40
#include "wizarddialog.h"
Edric Milaret's avatar
Edric Milaret committed
41
#include "windowscontactbackend.h"
42 43
#include "contactdialog.h"
#include "contactpicker.h"
44
#include "contactmethodpicker.h"
Edric Milaret's avatar
Edric Milaret committed
45 46 47

CallWidget::CallWidget(QWidget *parent) :
    NavWidget(Main ,parent),
Edric Milaret's avatar
Edric Milaret committed
48 49
    ui(new Ui::CallWidget),
    menu_(new QMenu())
Edric Milaret's avatar
Edric Milaret committed
50 51 52 53
{
    ui->setupUi(this);

    ui->callInvite->setVisible(false);
Edric Milaret's avatar
Edric Milaret committed
54
    ui->videoWidget->setVisible(false);
Edric Milaret's avatar
Edric Milaret committed
55

56
    setActualCall(nullptr);
Edric Milaret's avatar
Edric Milaret committed
57 58
    videoRenderer_ = nullptr;

59 60 61
    connect(ui->videoWidget, SIGNAL(setChatVisibility(bool)),
            ui->instantMessagingWidget, SLOT(setVisible(bool)));

62
    ui->spinnerWidget->hide();
63 64 65 66 67
    spinner_ = new QMovie(":/images/spinner.gif");
    if (spinner_->isValid()) {
        ui->spinnerLabel->setMovie(spinner_);
    }

Edric Milaret's avatar
Edric Milaret committed
68 69 70 71 72 73 74 75 76 77
    try {
        callModel_ = CallModel::instance();

        connect(callModel_, SIGNAL(incomingCall(Call*)),
                this, SLOT(callIncoming(Call*)));
        connect(callModel_, SIGNAL(callAdded(Call*,Call*)),
                this, SLOT(addedCall(Call*, Call*)));
        connect(callModel_, SIGNAL(callStateChanged(Call*, Call::State)),
                this, SLOT(callStateChanged(Call*, Call::State)));

78 79 80 81 82
        connect(AccountModel::instance(),
                SIGNAL(accountStateChanged(Account*,Account::RegistrationState)),
                this,
                SLOT(checkRegistrationState(Account*,Account::RegistrationState)));

83 84 85 86 87
        connect(AccountModel::instance()
                , SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>))
                , this
                , SLOT(findRingAccount(QModelIndex, QModelIndex, QVector<int>)));

Edric Milaret's avatar
Edric Milaret committed
88
        ui->callList->setModel(callModel_);
89
        ui->callList->setSelectionModel(callModel_->selectionModel());
Edric Milaret's avatar
Edric Milaret committed
90

91 92
        auto personCollection = PersonModel::instance()->
                addCollection<WindowsContactBackend>(LoadOptions::FORCE_ENABLED);
Edric Milaret's avatar
Edric Milaret committed
93

94 95 96 97 98
        CategorizedContactModel::instance()->setSortAlphabetical(false);
        CategorizedContactModel::instance()->setUnreachableHidden(true);
        ui->contactView->setModel(CategorizedContactModel::instance());
        contactDelegate_ = new ContactDelegate();
        ui->contactView->setItemDelegate(contactDelegate_);
99

100 101
        CategorizedHistoryModel::instance()->
                addCollection<LocalHistoryCollection>(LoadOptions::FORCE_ENABLED);
102

Edric Milaret's avatar
Edric Milaret committed
103 104
        ui->historyList->setModel(CategorizedHistoryModel::SortedProxy::instance()->model());
        CategorizedHistoryModel::SortedProxy::instance()->model()->sort(0, Qt::DescendingOrder);
105
        ui->historyList->setHeaderHidden(true);
Edric Milaret's avatar
Edric Milaret committed
106 107
        historyDelegate_ = new HistoryDelegate();
        ui->historyList->setItemDelegate(historyDelegate_);
108 109 110 111 112 113 114

        connect(CategorizedHistoryModel::SortedProxy::instance()->model(), &QSortFilterProxyModel::layoutChanged, [=]() {
            auto idx = CategorizedHistoryModel::SortedProxy::instance()->model()->index(0,0);
            if (idx.isValid())
                ui->historyList->setExpanded(idx, true);
        });

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
        ui->historyList->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(ui->historyList, &QListView::customContextMenuRequested, [=](const QPoint& pos){
            if (ui->historyList->currentIndex().parent().isValid()) {
                QPoint globalPos = ui->historyList->mapToGlobal(pos);
                QMenu menu;

                ContactMethod* contactMethod = ui->historyList->currentIndex()
                        .data(static_cast<int>(Call::Role::ContactMethod)).value<ContactMethod*>();

                auto copyAction = new QAction("Copy number", this);
                menu.addAction(copyAction);
                connect(copyAction, &QAction::triggered, [=]() {
                    QApplication::clipboard()->setText(contactMethod->uri());
                });
                if (not contactMethod->contact()) {
                    auto addNew = new QAction("Add to new contact", this);
                    menu.addAction(addNew);
                    connect(addNew, &QAction::triggered, [=]() {
                        ContactDialog dialog(contactMethod->uri());
                        auto ret = dialog.exec();
                        if (!ret || dialog.getName().isEmpty())
                            return;
                        auto *newPerson = new Person();
                        newPerson->setFormattedName(dialog.getName());
                        Person::ContactMethods cM;
                        cM.append(contactMethod);
                        newPerson->setContactMethods(cM);
                        newPerson->setUid(Utils::GenGUID().toLocal8Bit());
                        PersonModel::instance()->addNewPerson(newPerson, personCollection);
                    });
                    auto addExisting = new QAction("Add to existing contact", this);
                    menu.addAction(addExisting);
                    connect(addExisting, &QAction::triggered, [=]() {
                        /* Force LRC to update contact model as adding a number
                        to a contact without one didn't render him reachable */
                        CategorizedContactModel::instance()->setUnreachableHidden(false);

                        ContactPicker contactPicker;
                        contactPicker.move(globalPos.x(), globalPos.y() - (contactPicker.height()/2));
                        auto ret = contactPicker.exec();
                        if (!ret)
                            return;
                        auto p = contactPicker.getPersonSelected();
                        Person::ContactMethods cM (p->phoneNumbers());
                        cM.append(contactMethod);
                        p->setContactMethods(cM);
                        p->save();
                        CategorizedContactModel::instance()->setUnreachableHidden(true);
                    });
                }
                menu.exec(globalPos);
            }
        });
Edric Milaret's avatar
Edric Milaret committed
168 169

        ui->sortComboBox->setModel(CategorizedHistoryModel::SortedProxy::instance()->categoryModel());
Edric Milaret's avatar
Edric Milaret committed
170

Edric Milaret's avatar
Edric Milaret committed
171 172 173 174 175 176 177 178 179 180
        findRingAccount();

    } catch (...) {
        qDebug() << "INIT ERROR";
    }
}

CallWidget::~CallWidget()
{
    delete ui;
181 182
    delete spinner_;
    delete menu_;
Edric Milaret's avatar
Edric Milaret committed
183 184
    delete historyDelegate_;
    delete contactDelegate_;
Edric Milaret's avatar
Edric Milaret committed
185 186 187
}

void
188 189 190 191 192 193 194 195 196 197 198 199 200 201
CallWidget::findRingAccount(QModelIndex idx1, QModelIndex idx2, QVector<int> vec)
{
    Q_UNUSED(idx1)
    Q_UNUSED(idx2)
    Q_UNUSED(vec)

    auto a_count = AccountModel::instance()->rowCount();
    auto found = false;
    for (int i = 0; i < a_count; ++i) {
        auto idx = AccountModel::instance()->index(i, 0);
        auto protocol = idx.data(static_cast<int>(Account::Role::Proto));
        if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING) {
            auto username = idx.data(static_cast<int>(Account::Role::Username));
            ui->ringIdLabel->setText(
202
                         "Your Ring ID: " + username.toString());
203 204 205 206 207 208 209 210 211
            found = true;
            return;
        }
    }
    if (not found){
        ui->ringIdLabel->setText("NO RING ACCOUNT FOUND");
    }
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
void
CallWidget::checkRegistrationState(Account *account,
                                   Account::RegistrationState state)
{
    Q_UNUSED(account);

    QPixmap pm(20,20);
    pm.fill();
    QPainter p(&pm);
    QPen pen(Qt::black, 2);
    p.setPen(pen);
    p.setRenderHint(QPainter::Antialiasing, true);

    switch(state) {
    case Account::RegistrationState::ERROR:
        p.setBrush(Qt::red);
        break;
    case Account::RegistrationState::TRYING:
        p.setBrush(Qt::yellow);
        break;
    case Account::RegistrationState::READY:
Edric Milaret's avatar
Edric Milaret committed
233
    {
234 235 236 237 238 239 240 241 242 243 244 245 246
        p.setBrush(Qt::green);
        auto a_count = AccountModel::instance()->rowCount();
        for (int i = 0; i < a_count; ++i) {
            auto idx = AccountModel::instance()->index(i, 0);
            auto stateAcc = idx.data(
                        static_cast<int>(Account::Role::RegistrationState));
            if (stateAcc.value<Account::RegistrationState>()
                    != Account::RegistrationState::READY) {
                checkRegistrationState(nullptr,
                                       stateAcc.value<Account::RegistrationState>());
                return;
            }
        }
Edric Milaret's avatar
Edric Milaret committed
247 248 249
    }
        break;
     default:
250 251 252 253 254 255 256
        break;
    }
    p.drawEllipse(3, 3, 16, 16);
    ui->accountStateHintLabel->setPixmap(pm);
    ui->accountStateHintLabel->show();
}

257 258 259
void
CallWidget::findRingAccount()
{
260

Edric Milaret's avatar
Edric Milaret committed
261 262 263 264 265 266
    auto a_count = AccountModel::instance()->rowCount();
    auto found = false;
    for (int i = 0; i < a_count; ++i) {
        auto idx = AccountModel::instance()->index(i, 0);
        auto protocol = idx.data(static_cast<int>(Account::Role::Proto));
        if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING) {
267 268 269 270
            auto account = AccountModel::instance()->getAccountByModelIndex(idx);
            if (account->displayName().isEmpty())
                account->displayName() = account->alias();
            auto username = account->username();
Edric Milaret's avatar
Edric Milaret committed
271
            ui->ringIdLabel->setText(
272
                        "Your Ring ID: " + username);
Edric Milaret's avatar
Edric Milaret committed
273 274 275 276 277
            found = true;
            return;
        }
    }
    if (!found) {
278 279 280
        WizardDialog *wizardDialog = new WizardDialog();
        wizardDialog->exec();
        delete wizardDialog;
Edric Milaret's avatar
Edric Milaret committed
281 282 283 284 285 286
    }
}

void
CallWidget::callIncoming(Call *call)
{
Edric Milaret's avatar
Edric Milaret committed
287
    if (!call->account()->isAutoAnswer()) {
288
        ui->callLabel->setText("Call from " + call->formattedName());
Edric Milaret's avatar
Edric Milaret committed
289
        ui->callInvite->setVisible(true);
Edric Milaret's avatar
Edric Milaret committed
290 291
        ui->callInvite->raise();
    }
292
    setActualCall(call);
Edric Milaret's avatar
Edric Milaret committed
293 294 295 296 297 298
}

void
CallWidget::on_acceptButton_clicked()
{
    if (actualCall_ != nullptr)
299
        actualCall_->performAction(Call::Action::ACCEPT);
Edric Milaret's avatar
Edric Milaret committed
300 301 302 303 304 305 306 307 308
    ui->callInvite->setVisible(false);
}

void
CallWidget::on_refuseButton_clicked()
{
    if (actualCall_ == nullptr)
        return;
    actualCall_->performAction(Call::Action::REFUSE);
309
    setActualCall(nullptr);
Edric Milaret's avatar
Edric Milaret committed
310 311 312 313
    ui->callInvite->setVisible(false);
}

void
314 315
CallWidget::addedCall(Call* call, Call* parent)
{
Edric Milaret's avatar
Edric Milaret committed
316 317
    Q_UNUSED(parent);
    if (call->direction() == Call::Direction::OUTGOING) {
318
        displaySpinner(true);
319
        setActualCall(call);
Edric Milaret's avatar
Edric Milaret committed
320 321 322 323
    }
}

void
324 325
CallWidget::callStateChanged(Call* call, Call::State previousState)
{
Edric Milaret's avatar
Edric Milaret committed
326
    Q_UNUSED(previousState)
327 328
    if (call == nullptr)
        return;
Edric Milaret's avatar
Edric Milaret committed
329
    ui->callList->setCurrentIndex(callModel_->getIndex(actualCall_));
330 331 332 333
    if (call->state() == Call::State::OVER
            || call->state() == Call::State::ERROR
            || call->state() == Call::State::FAILURE
            || call->state() == Call::State::ABORTED) {
334
        setActualCall(nullptr);
Edric Milaret's avatar
Edric Milaret committed
335
        ui->videoWidget->hide();
336
        displaySpinner(false);
337
        auto onHoldCall = callModel_->getActiveCalls().first();
338
        if (onHoldCall != nullptr && onHoldCall->state() == Call::State::HOLD) {
339 340 341
            setActualCall(onHoldCall);
            onHoldCall->performAction(Call::Action::HOLD);
        }
342
    } else if (call->state() == Call::State::CURRENT) {
343
        displaySpinner(false);
Edric Milaret's avatar
Edric Milaret committed
344 345
        ui->videoWidget->show();
    }
346
    ui->callStateLabel->setText("Call State : " + call->toHumanStateName());
Edric Milaret's avatar
Edric Milaret committed
347 348 349 350 351 352 353 354 355
}

void
CallWidget::on_callList_activated(const QModelIndex &index)
{
    Call *callSelected = callModel_->getCall(index);
    if (actualCall_ != nullptr) {
        if (callSelected == actualCall_)
            return;
Edric Milaret's avatar
Edric Milaret committed
356
        ui->videoWidget->hide();
Edric Milaret's avatar
Edric Milaret committed
357 358
        actualCall_->performAction(Call::Action::HOLD);
    }
359
    setActualCall(callSelected);
Edric Milaret's avatar
Edric Milaret committed
360
    actualCall_->performAction(Call::Action::HOLD);
Edric Milaret's avatar
Edric Milaret committed
361
    ui->videoWidget->show();
Edric Milaret's avatar
Edric Milaret committed
362 363 364
}

void
365 366
CallWidget::atExit()
{
Edric Milaret's avatar
Edric Milaret committed
367 368

}
369 370 371 372

void
CallWidget::on_contactView_doubleClicked(const QModelIndex &index)
{
373 374 375
    if (not index.isValid())
        return;

376
    ContactMethod* uri = nullptr;
377

Edric Milaret's avatar
Edric Milaret committed
378 379 380 381
    auto var = index.child(0,0).data(
                static_cast<int>(Person::Role::Object));
    if (var.isValid()) {
        Person* person = var.value<Person*>();
382 383 384 385 386 387 388 389 390 391 392 393 394
        if (person->phoneNumbers().size() == 1) {
            uri = person->phoneNumbers().at(0);
        } else if (person->phoneNumbers().size() > 1) {
            ContactMethodPicker dlg(person->phoneNumbers());
            auto pos = QCursor::pos();
            dlg.move(pos.x(), pos.y());
            if (dlg.exec())
                uri = dlg.getSelected();
        }
        if (uri) {
            auto outCall = CallModel::instance()->dialingCall(person->formattedName());
            outCall->setDialNumber(uri);
            outCall->performAction(Call::Action::ACCEPT);
395 396 397
        }
    }
}
398

399 400
void
CallWidget::on_historyList_doubleClicked(const QModelIndex &index)
401
{
Edric Milaret's avatar
Edric Milaret committed
402 403 404
    if (not index.isValid())
        return;

405 406 407
    auto number = index.data(static_cast<int>(Call::Role::ContactMethod)).value<ContactMethod*>();
    if (number) {
        auto outCall = CallModel::instance()->dialingCall();
408 409 410 411
        outCall->setDialNumber(number);
        outCall->performAction(Call::Action::ACCEPT);
    }
}
412 413 414 415 416

void
CallWidget::setActualCall(Call* value)
{
    actualCall_ = value;
Edric Milaret's avatar
Edric Milaret committed
417
    ui->instantMessagingWidget->setMediaText(actualCall_);
Edric Milaret's avatar
Edric Milaret committed
418
}
Edric Milaret's avatar
Edric Milaret committed
419

Edric Milaret's avatar
Edric Milaret committed
420 421 422 423 424 425 426 427
void
CallWidget::on_sortComboBox_currentIndexChanged(int index)
{
    auto idx = CategorizedHistoryModel::SortedProxy::instance()->
            categoryModel()->index(index, 0);
    CategorizedHistoryModel::SortedProxy::instance()->categorySelectionModel()->
            setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
    ui->historyList->setModel(CategorizedHistoryModel::SortedProxy::instance()->model());
428
}
429 430 431 432

void
CallWidget::displaySpinner(bool display)
{
433
    display ? ui->spinnerWidget->show() : ui->spinnerWidget->hide();
434 435 436
    if (ui->spinnerLabel->movie())
        display ? ui->spinnerLabel->movie()->start() : ui->spinnerLabel->movie()->stop();
}
437 438 439 440 441 442

void CallWidget::on_cancelButton_clicked()
{
    if (actualCall_)
        actualCall_->performAction(Call::Action::REFUSE);
}