From 01da004adfdcc9ce18659067f8beda6a1186588d Mon Sep 17 00:00:00 2001
From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com>
Date: Wed, 7 Aug 2019 13:02:06 -0400
Subject: [PATCH] transferCall: add blind/attended transfer call support

Change-Id: I7a159ac51cb7527ce8fc1010c2c6caebd7251c0e
---
 callwidget.cpp                | 13 +++---
 contactpicker.cpp             | 24 +++++++---
 contactpicker.h               |  7 +--
 contactpicker.ui              | 12 +++--
 contactpickeritemdelegate.cpp | 64 ++++++++++++++++++++++++++-
 contactpickeritemdelegate.h   |  2 +-
 stylesheet.css                |  6 +--
 videooverlay.cpp              | 82 +++++++++++++++++++++++++++++++++++
 videooverlay.h                |  4 ++
 videoview.cpp                 | 11 ++++-
 videoview.h                   |  4 +-
 11 files changed, 199 insertions(+), 30 deletions(-)

diff --git a/callwidget.cpp b/callwidget.cpp
index 4f28dd4..2840691 100644
--- a/callwidget.cpp
+++ b/callwidget.cpp
@@ -642,11 +642,13 @@ void CallWidget::slotShowCallView(const std::string& accountId,
                                   const lrc::api::conversation::Info& convInfo)
 {
     Q_UNUSED(accountId);
-    Q_UNUSED(convInfo);
     qDebug() << "slotShowCallView";
-    setCallPanelVisibility(true);
-
+    // find out the best name for current callee, remove the search result from smart list
     auto callModel = LRCInstance::getCurrentCallModel();
+    auto convModel = LRCInstance::getCurrentConversationModel();
+    auto bestName = QString::fromStdString(Utils::bestNameForConversation(convInfo, *convModel));
+    ui->videoWidget->setCurrentCalleeName(bestName);
+    setCallPanelVisibility(true);
 
     if (callModel->hasCall(convInfo.callId)) {
         auto call = callModel->getCall(convInfo.callId);
@@ -659,7 +661,7 @@ void CallWidget::slotShowCallView(const std::string& accountId,
     }
     ui->callStackWidget->setCurrentWidget(ui->videoPage);
     hideMiniSpinner();
-    ui->videoWidget->pushRenderer(convInfo.callId);
+    ui->videoWidget->pushRenderer(convInfo.callId, LRCInstance::accountModel().getAccountInfo(accountId).profileInfo.type == lrc::api::profile::Type::SIP);
 }
 
 void CallWidget::slotShowIncomingCallView(const std::string& accountId,
@@ -683,6 +685,7 @@ void CallWidget::slotShowIncomingCallView(const std::string& accountId,
     auto call = callModel->getCall(convInfo.callId);
     auto isCallSelected = LRCInstance::getSelectedConvUid() == convInfo.uid;
     ui->callingStatusLabel->setText(QString::fromStdString(lrc::api::call::to_string(call.status)));
+    ui->videoWidget->setCurrentCalleeName(bestName);
 
     connect(callModel, &lrc::api::NewCallModel::callStatusChanged, ui->incomingCallPage,
         [this, accountId](const std::string& callId) {
@@ -726,7 +729,7 @@ void CallWidget::slotShowIncomingCallView(const std::string& accountId,
         ui->messagesWidget->show();
     }
 
-    ui->videoWidget->pushRenderer(convInfo.callId);
+    ui->videoWidget->pushRenderer(convInfo.callId, LRCInstance::accountModel().getAccountInfo(accountId).profileInfo.type == lrc::api::profile::Type::SIP);
 
     QFontMetrics primaryCallLabelFontMetrics(ui->callingBestNameLabel->font());
     QFontMetrics sencondaryCallLabelFontMetrics(ui->callingBestIdLabel->font());
diff --git a/contactpicker.cpp b/contactpicker.cpp
index 70ccd7f..4830882 100644
--- a/contactpicker.cpp
+++ b/contactpicker.cpp
@@ -73,9 +73,8 @@ ContactPicker::accept()
         case Type::CONFERENCE:
             emit contactWillJoinConference(thisCallId, contactUri);
             break;
-        case Type::BLIND_TRANSFER:
-        case Type::ATTENDED_TRANSFER:
-            emit contactWillDoBlindTransfer(thisCallId, contactUri);
+        case Type::TRANSFER:
+            emit contactWillDoTransfer(thisCallId, contactUri);
             break;
         default:
             break;
@@ -96,7 +95,6 @@ ContactPicker::mousePressEvent(QMouseEvent *event)
 {
     auto contactPickerWidgetRect = ui->contactPickerWidget->rect();
     if (!contactPickerWidgetRect.contains(event->pos())) {
-        //close();
         emit willClose(event);
     }
 }
@@ -131,11 +129,17 @@ ContactPicker::setType(const Type& type)
                         !index.parent().isValid();
             });
         break;
-    case Type::BLIND_TRANSFER:
-    case Type::ATTENDED_TRANSFER:
+    case Type::TRANSFER:
         selectableProxyModel_->setPredicate(
             [this](const QModelIndex& index, const QRegExp& regexp) {
-                return true;
+                // Regex to remove current callee
+                QRegExp matchExcept= QRegExp(QString("\\b(?!" + CalleeDisplayName_ + "\\b)\\w+"));
+                bool match = false;
+                bool match_non_self = matchExcept.indexIn(index.data(Qt::DisplayRole).toString()) != -1;
+                if (match_non_self) {
+                    match = regexp.indexIn(index.data(Qt::DisplayRole).toString()) != -1;
+                }
+                return  match && !index.parent().isValid();
             });
         break;
     default:
@@ -143,3 +147,9 @@ ContactPicker::setType(const Type& type)
     }
     selectableProxyModel_->invalidate();
 }
+
+void
+ContactPicker::setCurrentCalleeDisplayName(const QString& CalleeDisplayName)
+{
+    CalleeDisplayName_ = CalleeDisplayName;
+}
diff --git a/contactpicker.h b/contactpicker.h
index b9e9b4a..c491b5f 100644
--- a/contactpicker.h
+++ b/contactpicker.h
@@ -63,8 +63,7 @@ class ContactPicker : public QDialog
 public:
     enum class Type {
         CONFERENCE,
-        BLIND_TRANSFER,
-        ATTENDED_TRANSFER,
+        TRANSFER,
         COUNT__
     };
 
@@ -72,13 +71,14 @@ public:
     ~ContactPicker();
     void setTitle(const std::string& title);
     void setType(const Type& type);
+    void setCurrentCalleeDisplayName(const QString& CalleeDisplayName);
 
 protected:
     void mousePressEvent(QMouseEvent *event);
 
 signals:
     void contactWillJoinConference(const std::string& callId, const std::string& contactUri);
-    void contactWillDoBlindTransfer(const std::string& callId, const std::string& contactUri);
+    void contactWillDoTransfer(const std::string& callId, const std::string& contactUri);
     void willClose(QMouseEvent *event);
 
 protected slots:
@@ -94,5 +94,6 @@ private:
     std::unique_ptr<SmartListModel> smartListModel_;
     SelectableProxyModel* selectableProxyModel_;
     Type type_;
+    QString CalleeDisplayName_;
 
 };
diff --git a/contactpicker.ui b/contactpicker.ui
index a943292..e42fe84 100644
--- a/contactpicker.ui
+++ b/contactpicker.ui
@@ -190,7 +190,7 @@
                <cursorShape>IBeamCursor</cursorShape>
               </property>
               <property name="toolTip">
-               <string>Search contact text input</string>
+               <string>Search contact</string>
               </property>
               <property name="maxLength">
                <number>100</number>
@@ -251,10 +251,10 @@
            <attribute name="headerVisible">
             <bool>false</bool>
            </attribute>
-           <attribute name="headerDefaultSectionSize">
+           <attribute name="headerMinimumSectionSize">
             <number>0</number>
            </attribute>
-           <attribute name="headerMinimumSectionSize">
+           <attribute name="headerDefaultSectionSize">
             <number>0</number>
            </attribute>
           </widget>
@@ -294,7 +294,7 @@
       <string/>
      </property>
      <property name="pixmap">
-      <pixmap resource="ressources.qrc">:/images/spike.png</pixmap>
+      <pixmap>:/images/spike.png</pixmap>
      </property>
     </widget>
    </item>
@@ -312,8 +312,6 @@
    <header>smartlistview.h</header>
   </customwidget>
  </customwidgets>
- <resources>
-  <include location="ressources.qrc"/>
- </resources>
+ <resources/>
  <connections/>
 </ui>
diff --git a/contactpickeritemdelegate.cpp b/contactpickeritemdelegate.cpp
index 476ebf6..b86bf5b 100644
--- a/contactpickeritemdelegate.cpp
+++ b/contactpickeritemdelegate.cpp
@@ -104,6 +104,7 @@ ContactPickerItemDelegate::paint(QPainter* painter
         paintRingContactItem(painter, option, rect, index);
         break;
     case profile::Type::SIP:
+        paintSIPContactItem(painter, option, rect, index);
         break;
     default:
         paintRingContactItem(painter, option, rect, index);
@@ -183,9 +184,68 @@ ContactPickerItemDelegate::paintRingContactItem(QPainter* painter,
 void
 ContactPickerItemDelegate::paintSIPContactItem(QPainter* painter,
     const QStyleOptionViewItem& option,
+    const QRect& rect,
     const QModelIndex& index) const
 {
-    Q_UNUSED(painter);
     Q_UNUSED(option);
-    Q_UNUSED(index);
+    QRect rectName1;
+    QRect rectName2;
+    QFont font(painter->font());
+    QPen pen(painter->pen());
+    painter->setPen(pen);
+
+    auto scalingRatio = MainWindow::instance().getCurrentScalingRatio();
+    if (scalingRatio > 1.0) {
+        font.setPointSize(fontSize_ - 2);
+    } else {
+        font.setPointSize(fontSize_);
+    }
+
+    auto leftMargin = dx_ + sizeImage_ + dx_;
+    auto rightMargin = dx_;
+    auto topMargin = 4;
+    auto bottomMargin = 8;
+
+    if (!rect.isEmpty()) {
+
+        rectName1 = QRect(rect.left() + leftMargin,
+        rect.top() + topMargin,
+        rect.width() - leftMargin * 2,
+        rect.height() / 2 - 2);
+
+        rectName2 =  QRect(rectName1.left(),
+            rectName1.top() + rectName1.height(),
+            rectName1.width(),
+            rectName1.height() - bottomMargin);
+    }
+
+    QFontMetrics fontMetrics(font);
+
+    // The name is displayed at the avatar's right
+    QString nameStr = index.data(static_cast<int>(SmartListModel::Role::DisplayName)).value<QString>();
+    if (!nameStr.isNull()) {
+        font.setItalic(false);
+        font.setBold(false);
+        pen.setColor(RingTheme::lightBlack_);
+        painter->setPen(pen);
+        painter->setFont(font);
+        if (!rectName1.isEmpty()) {
+            QString elidedNameStr = fontMetrics.elidedText(nameStr, Qt::ElideRight, rectName1.width());
+            painter->drawText(rectName1, Qt::AlignVCenter | Qt::AlignLeft, elidedNameStr);
+        }
+    }
+
+    // Display the ID under the name
+    QString idStr = index.data(static_cast<int>(SmartListModel::Role::DisplayID)).value<QString>();
+    if (idStr != nameStr && !idStr.isNull()) {
+        font.setItalic(false);
+        font.setBold(false);
+        pen.setColor(RingTheme::grey_);
+        painter->setPen(pen);
+        painter->setFont(font);
+        if (!rectName2.isEmpty()) {
+            idStr = fontMetrics.elidedText(idStr, Qt::ElideRight, rectName2.width());
+            painter->drawText(rectName2, Qt::AlignVCenter | Qt::AlignLeft, idStr);
+        }
+    }
 }
diff --git a/contactpickeritemdelegate.h b/contactpickeritemdelegate.h
index 6cdc225..e6def29 100644
--- a/contactpickeritemdelegate.h
+++ b/contactpickeritemdelegate.h
@@ -35,7 +35,7 @@ protected:
 
 private:
     void paintRingContactItem(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QModelIndex& index) const;
-    void paintSIPContactItem(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+    void paintSIPContactItem(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QModelIndex& index) const;
 
     constexpr static int sizeImage_ = 48;
     constexpr static int cellHeight_ = 60;
diff --git a/stylesheet.css b/stylesheet.css
index f80075c..d2e012f 100644
--- a/stylesheet.css
+++ b/stylesheet.css
@@ -177,7 +177,7 @@ QWidget#messageViewLayoutWidget, QWidget#welcomePage {
 
 QPushButton#holdButton, QPushButton#chatButton, QPushButton#noMicButton, QPushButton#noVideoButton,
 QPushButton#transferButton, QPushButton#addPersonButton, QPushButton#joinButton,
-QPushButton#qualityButton, QPushButton#recButton {
+QPushButton#qualityButton, QPushButton#recButton, QPushButton#transferCallButton {
     background-color: rgba(0, 0, 0, 140);
     border-radius: 18px;
     border: solid 1px;
@@ -186,7 +186,7 @@ QPushButton#qualityButton, QPushButton#recButton {
 QPushButton#holdButton:hover, QPushButton#chatButton:hover, QPushButton#noMicButton:hover,
 QPushButton#noVideoButton:hover, QPushButton#transferButton:hover, QPushButton#addPersonButton:hover,
 QPushButton#joinButton:hover, QPushButton#qualityButton:hover, QPushButton#addToContactButton:hover,
-QPushButton#recButton:hover {
+QPushButton#recButton:hover, QPushButton#transferCallButton:hover {
     background-color: rgba(0, 192, 213, 0.6);
 }
 
@@ -199,7 +199,7 @@ QPushButton#recButton:pressed {
 
 QPushButton#holdButton:checked, QPushButton#noMicButton:checked,
 QPushButton#noVideoButton:checked, QPushButton#recButton:checked,
-QPushButton#chatButton:checked{
+QPushButton#chatButton:checked, QPushButton#transferCallButton:checked {
     background-color: rgba(0, 192, 213, 0.8);
 }
 
diff --git a/videooverlay.cpp b/videooverlay.cpp
index 17d2d26..820065c 100644
--- a/videooverlay.cpp
+++ b/videooverlay.cpp
@@ -44,6 +44,15 @@ VideoOverlay::VideoOverlay(QWidget* parent) :
     ui->onHoldLabel->setVisible(false);
 
     ui->recButton->setVisible(false);
+
+    ui->transferCallButton->setVisible(false);
+    ui->transferCallButton->setCheckable(true);
+
+    contactPicker_->setVisible(false);
+    contactPicker_->setTitle("Select Peer to Tranfer");
+
+    connect(ui->transferCallButton, &QPushButton::toggled, this, &VideoOverlay::on_transferButton_toggled);
+    connect(contactPicker_, &ContactPicker::contactWillDoTransfer, this, &VideoOverlay::on_transferCall_requested);
 }
 
 VideoOverlay::~VideoOverlay()
@@ -176,6 +185,79 @@ VideoOverlay::on_recButton_clicked()
     }
 }
 
+void
+VideoOverlay::setTransferCallAvailability(bool visible)
+{
+    ui->transferCallButton->setVisible(visible);
+}
+
+void
+VideoOverlay::on_transferButton_toggled(bool checked)
+{
+    if (callId_.empty() || !checked) {
+        return;
+    }
+    contactPicker_->setType(ContactPicker::Type::TRANSFER);
+
+    QPoint globalPos_button = mapToGlobal(ui->transferCallButton->pos());
+    QPoint globalPos_bottomButtons = mapToGlobal(ui->bottomButtons->pos());
+
+    contactPicker_->move(globalPos_button.x(), globalPos_bottomButtons.y() - contactPicker_->height());
+
+    // receive the signal that ensure the button checked status is correct and contactpicker
+    // is properly hidden
+    Utils::oneShotConnect(contactPicker_, &ContactPicker::willClose, [this](QMouseEvent *event) {
+        contactPicker_->hide();
+        // check if current mouse position is on button
+        auto relativeCursorPos = ui->transferCallButton->mapFromGlobal(event->pos());
+        if (!ui->transferCallButton->rect().contains(relativeCursorPos)) {
+            ui->transferCallButton->setChecked(false);
+        }
+    });
+
+    // for esc key, receive reject signal
+    Utils::oneShotConnect(contactPicker_, &QDialog::rejected,
+    [this] {
+        ui->transferCallButton->setChecked(false);
+    });
+
+    contactPicker_->show();
+}
+
+void
+VideoOverlay::on_transferCall_requested(const std::string& callId, const std::string& contactUri)
+{
+    auto callModel = LRCInstance::getCurrentCallModel();
+    contactPicker_->hide();
+    ui->transferCallButton->setChecked(false);
+    std::string destCallId;
+
+    try {
+        //check if the call exist - (check non-finished calls)
+        auto callInfo = callModel->getCallFromURI(contactUri, true);
+        destCallId = callInfo.id;
+    } catch(std::exception& e) {
+        qDebug().noquote() << e.what();
+        destCallId = "";
+    }
+    // if no second call -> blind transfer
+    // if there is a second call -> attended transfer
+    if (destCallId.size() == 0) {
+        callModel->transfer(callId, "sip:" + contactUri);
+        callModel->hangUp(callId);
+    }else{
+        callModel->transferToCall(callId, destCallId);
+        callModel->hangUp(callId);
+        callModel->hangUp(destCallId);
+    }
+}
+
+void
+VideoOverlay::setCurrentSelectedCalleeDisplayName(const QString& CalleeDisplayName)
+{
+    contactPicker_->setCurrentCalleeDisplayName(CalleeDisplayName);
+}
+
 void
 VideoOverlay::resetOverlay(bool isAudioMuted, bool isVideoMuted, bool isRecording, bool isHolding)
 {
diff --git a/videooverlay.h b/videooverlay.h
index 640c1eb..c7b1c67 100644
--- a/videooverlay.h
+++ b/videooverlay.h
@@ -45,6 +45,8 @@ public:
     bool shouldShowOverlay();
     void simulateShowChatview(bool checked);
     bool getShowChatView();
+    void setTransferCallAvailability(bool visible);
+    void setCurrentSelectedCalleeDisplayName(const QString& CalleeDisplayName);
     void resetOverlay(bool isAudioMuted, bool isVideoMuted, bool isRecording, bool isHolding);
 
 //UI SLOTS
@@ -56,6 +58,8 @@ private slots:
     void on_noMicButton_toggled(bool checked);
     void on_noVideoButton_toggled(bool checked);
     void on_recButton_clicked();
+    void on_transferButton_toggled(bool checked);
+    void on_transferCall_requested(const std::string& callId, const std::string& contactUri);
 
 private:
     Ui::VideoOverlay* ui;
diff --git a/videoview.cpp b/videoview.cpp
index 61e54e3..f15af67 100644
--- a/videoview.cpp
+++ b/videoview.cpp
@@ -333,7 +333,7 @@ VideoView::showContextMenu(const QPoint& pos)
 }
 
 void
-VideoView::pushRenderer(const std::string& callId) {
+VideoView::pushRenderer(const std::string& callId, bool isSIP) {
     auto callModel = LRCInstance::getCurrentCallModel();
 
     QObject::disconnect(ui->videoWidget);
@@ -349,6 +349,8 @@ VideoView::pushRenderer(const std::string& callId) {
         return;
     }
 
+    // transfer call will only happen in SIP calls
+    this->overlay_->setTransferCallAvailability(isSIP);
     this->overlay_->callStarted(callId);
     this->overlay_->setVideoMuteVisibility(!LRCInstance::getCurrentCallModel()->getCall(callId).isAudioOnly);
 
@@ -426,6 +428,13 @@ VideoView::mouseMoveEvent(QMouseEvent* event)
             and geometry().contains(event->pos()))
         previewRect.setBottomRight(event->pos());
 }
+
+void
+VideoView::setCurrentCalleeName(const QString& CalleeDisplayName)
+{
+    overlay_->setCurrentSelectedCalleeDisplayName(CalleeDisplayName);
+}
+
 void
 VideoView::resetVideoOverlay(bool isAudioMuted, bool isVideoMuted, bool isRecording, bool isHolding)
 {
diff --git a/videoview.h b/videoview.h
index f07bef2..c73ac01 100644
--- a/videoview.h
+++ b/videoview.h
@@ -36,8 +36,10 @@ class VideoView : public QWidget
 public:
     explicit VideoView(QWidget* parent = 0);
     ~VideoView();
-    void pushRenderer(const std::string& callUid);
+    void pushRenderer(const std::string& callUid, bool isSIP);
+    void showChatviewIfToggled();
     void simulateShowChatview(bool checked);
+    void setCurrentCalleeName(const QString& CalleeDisplayName);
     void resetVideoOverlay(bool isAudioMuted, bool isVideoMuted, bool isRecording, bool isHolding);
 
 protected:
-- 
GitLab