sip: fix the transfer feature
Readd the ability to transfer a SIP call (blind or attended) from
the current call view.
Change-Id: I46f5ade5ef71e5479bc3410f9b1f4c200825132c
Gitlab: #803
Reviewed-by: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
diff --git a/pixmaps/pixmaps.gresource.xml b/pixmaps/pixmaps.gresource.xml
index 6f66dc5..83a7999 100644
--- a/pixmaps/pixmaps.gresource.xml
+++ b/pixmaps/pixmaps.gresource.xml
@@ -33,5 +33,6 @@
<file alias="temporary-item">ic_search_black_48px.svg</file>
<file alias="audio_only_call_start">ic_call_black_24px.svg</file>
<file alias="fallbackavatar">fallbackavatar.svg</file>
+ <file alias="transfer">transfer.svg</file>
</gresource>
</gresources>
diff --git a/pixmaps/transfer.svg b/pixmaps/transfer.svg
new file mode 100644
index 0000000..3251e30
--- /dev/null
+++ b/pixmaps/transfer.svg
@@ -0,0 +1,4 @@
+<svg fill="#ffffff" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M0 0h24v24H0z" fill="none"/>
+ <path d="M18 11l5-5-5-5v3h-4v4h4v3zm2 4.5c-1.25 0-2.45-.2-3.57-.57-.35-.11-.74-.03-1.02.24l-2.2 2.2c-2.83-1.44-5.15-3.75-6.59-6.59l2.2-2.21c.28-.26.36-.65.25-1C8.7 6.45 8.5 5.25 8.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1z"/>
+</svg>
diff --git a/src/currentcallview.cpp b/src/currentcallview.cpp
index 06cb289..3a9ffbd 100644
--- a/src/currentcallview.cpp
+++ b/src/currentcallview.cpp
@@ -44,6 +44,8 @@
#include "utils/files.h"
#include "video/video_widget.h"
+#include <iostream>
+
namespace { namespace details
{
class CppImpl;
@@ -79,6 +81,10 @@
GtkWidget *togglebutton_chat;
GtkWidget *togglebutton_muteaudio;
GtkWidget *togglebutton_mutevideo;
+ GtkWidget *togglebutton_transfer;
+ GtkWidget* siptransfer_popover;
+ GtkWidget* siptransfer_filter_entry;
+ GtkWidget* list_conversations;
GtkWidget *togglebutton_hold;
GtkWidget *togglebutton_record;
GtkWidget *button_hangup;
@@ -234,6 +240,7 @@
void setup(WebKitChatContainer* chat_widget,
AccountInfoPointer const & account_info,
lrc::api::conversation::Info* conversation);
+ void add_transfer_contact(const std::string& uri);
void insertControls();
void checkControlsFading();
@@ -542,6 +549,127 @@
}
}
+static void
+transfer_to_peer(CurrentCallViewPrivate* priv, const std::string& peerUri)
+{
+ if (peerUri == priv->cpp->conversation->participants.front()) {
+ g_warning("avoid to transfer to the same call, abort.");
+#if GTK_CHECK_VERSION(3,22,0)
+ gtk_popover_popdown(GTK_POPOVER(priv->siptransfer_popover));
+#else
+ gtk_widget_hide(GTK_WIDGET(priv->siptransfer_popover));
+#endif
+ return;
+ }
+ try {
+ // If a call is already present with a peer, try an attended transfer.
+ auto callInfo = (*priv->cpp->accountInfo)->callModel->getCallFromURI(peerUri, true);
+ (*priv->cpp->accountInfo)->callModel->transferToCall(
+ priv->cpp->conversation->callId, callInfo.id);
+ } catch (std::out_of_range&) {
+ // No current call found with this URI, perform a blind transfer
+ (*priv->cpp->accountInfo)->callModel->transfer(
+ priv->cpp->conversation->callId, peerUri);
+ }
+#if GTK_CHECK_VERSION(3,22,0)
+ gtk_popover_popdown(GTK_POPOVER(priv->siptransfer_popover));
+#else
+ gtk_widget_hide(GTK_WIDGET(priv->siptransfer_popover));
+#endif
+}
+
+static void
+on_siptransfer_filter_activated(CurrentCallView* self)
+{
+ g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
+ auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
+
+ transfer_to_peer(priv, gtk_entry_get_text(GTK_ENTRY(priv->siptransfer_filter_entry)));
+}
+
+static GtkLabel*
+get_sip_address_label(GtkListBoxRow* row)
+{
+ auto* row_children = gtk_container_get_children(GTK_CONTAINER(row));
+ auto* box_infos = g_list_first(row_children)->data;
+ auto* children = gtk_container_get_children(GTK_CONTAINER(box_infos));
+ return GTK_LABEL(g_list_last(children)->data);
+}
+
+static void
+transfer_to_conversation(GtkListBox*, GtkListBoxRow* row, CurrentCallView* self)
+{
+ g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
+ auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
+ auto* sip_address = get_sip_address_label(row);
+ transfer_to_peer(priv, gtk_label_get_text(GTK_LABEL(sip_address)));
+}
+
+static void
+filter_transfer_list(CurrentCallView *self)
+{
+ g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
+ auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
+
+ std::string currentFilter = gtk_entry_get_text(GTK_ENTRY(priv->siptransfer_filter_entry));
+
+ auto row = 0;
+ while (GtkWidget* children = GTK_WIDGET(gtk_list_box_get_row_at_index(GTK_LIST_BOX(priv->list_conversations), row))) {
+ auto* sip_address = get_sip_address_label(GTK_LIST_BOX_ROW(children));;
+ if (row == 0) {
+ // Update searching item
+ if (currentFilter.empty() || currentFilter == priv->cpp->conversation->participants.front()) {
+ // Hide temporary item if filter is empty or same number
+ gtk_widget_hide(children);
+ } else {
+ // Else, show the temporary item (and select it)
+ gtk_label_set_text(GTK_LABEL(sip_address), currentFilter.c_str());
+ gtk_widget_show_all(children);
+ gtk_list_box_select_row(GTK_LIST_BOX(priv->list_conversations), GTK_LIST_BOX_ROW(children));
+ }
+ } else {
+ // It's a contact
+ std::string item_address = gtk_label_get_text(GTK_LABEL(sip_address));
+
+ if (item_address == priv->cpp->conversation->participants.front())
+ // if item is the current conversation, hide it
+ gtk_widget_hide(children);
+ else if (currentFilter.empty())
+ // filter is empty, show all items
+ gtk_widget_show_all(children);
+ else if (item_address.find(currentFilter) == std::string::npos || item_address == currentFilter)
+ // avoid duplicates and unwanted numbers
+ gtk_widget_hide(children);
+ else
+ // Item is filtered
+ gtk_widget_show_all(children);
+ }
+ ++row;
+ }
+}
+
+static void
+on_button_transfer_clicked(CurrentCallView *self)
+{
+ // Show and init list
+ g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
+ auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
+ gtk_popover_set_relative_to(GTK_POPOVER(priv->siptransfer_popover), GTK_WIDGET(priv->togglebutton_transfer));
+#if GTK_CHECK_VERSION(3,22,0)
+ gtk_popover_popdown(GTK_POPOVER(priv->siptransfer_popover));
+#else
+ gtk_widget_show_all(GTK_WIDGET(priv->siptransfer_popover));
+#endif
+ gtk_widget_show_all(priv->siptransfer_popover);
+ filter_transfer_list(self);
+}
+
+static void
+on_siptransfer_text_changed(GtkSearchEntry*, CurrentCallView* self)
+{
+ filter_transfer_list(self);
+}
+
} // namespace gtk_callbacks
CppImpl::CppImpl(CurrentCallView& widget)
@@ -571,7 +699,8 @@
// CSS styles
auto provider = gtk_css_provider_new();
gtk_css_provider_load_from_data(provider,
- ".smartinfo-block-style { color: #8ae234; background-color: rgba(1, 1, 1, 0.33); } \
+ ".search-entry-style { border: 0; border-radius: 0; } \
+ .smartinfo-block-style { color: #8ae234; background-color: rgba(1, 1, 1, 0.33); } \
@keyframes blink { 0% {opacity: 1;} 49% {opacity: 1;} 50% {opacity: 0;} 100% {opacity: 0;} } \
.record-button { background: rgba(0, 0, 0, 1); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
.record-button:checked { animation: blink 1s; animation-iteration-count: infinite; } \
@@ -605,6 +734,34 @@
conversation = conv_info;
accountInfo = &account_info;
setCallInfo();
+
+ if ((*accountInfo)->profileInfo.type == lrc::api::profile::Type::RING)
+ gtk_widget_hide(widgets->togglebutton_transfer);
+ else {
+ // Remove previous list
+ while (GtkWidget* children = GTK_WIDGET(gtk_list_box_get_row_at_index(GTK_LIST_BOX(widgets->list_conversations), 10)))
+ gtk_container_remove(GTK_CONTAINER(widgets->list_conversations), children);
+ // Fill with SIP contacts
+ add_transfer_contact(""); // Temporary item
+ for (const auto& c : (*accountInfo)->conversationModel->getFilteredConversations(lrc::api::profile::Type::SIP))
+ add_transfer_contact(c.participants.front());
+ gtk_widget_show_all(widgets->list_conversations);
+ gtk_widget_show(widgets->togglebutton_transfer);
+ }
+}
+
+void
+CppImpl::add_transfer_contact(const std::string& uri)
+{
+ auto* box_item = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ auto pixbufmanipulator = Interfaces::PixbufManipulator();
+ auto image_buf = pixbufmanipulator.generateAvatar("", uri.empty() ? uri : "sip" + uri);
+ auto scaled = pixbufmanipulator.scaleAndFrame(image_buf.get(), QSize(48, 48));
+ auto* avatar = gtk_image_new_from_pixbuf(scaled.get());
+ auto* address = gtk_label_new(uri.c_str());
+ gtk_container_add(GTK_CONTAINER(box_item), GTK_WIDGET(avatar));
+ gtk_container_add(GTK_CONTAINER(box_item), GTK_WIDGET(address));
+ gtk_list_box_insert(GTK_LIST_BOX(widgets->list_conversations), GTK_WIDGET(box_item), -1);
}
void
@@ -748,6 +905,10 @@
/* connect the controllers (new model) */
g_signal_connect_swapped(widgets->button_hangup, "clicked", G_CALLBACK(on_button_hangup_clicked), self);
+ g_signal_connect_swapped(widgets->togglebutton_transfer, "clicked", G_CALLBACK(on_button_transfer_clicked), self);
+ g_signal_connect_swapped(widgets->siptransfer_filter_entry, "activate", G_CALLBACK(on_siptransfer_filter_activated), self);
+ g_signal_connect(widgets->siptransfer_filter_entry, "search-changed", G_CALLBACK(on_siptransfer_text_changed), self);
+ g_signal_connect(widgets->list_conversations, "row-activated", G_CALLBACK(transfer_to_conversation), self);
g_signal_connect_swapped(widgets->togglebutton_hold, "clicked", G_CALLBACK(on_togglebutton_hold_clicked), self);
g_signal_connect_swapped(widgets->togglebutton_muteaudio, "clicked", G_CALLBACK(on_togglebutton_muteaudio_clicked), self);
g_signal_connect_swapped(widgets->togglebutton_record, "clicked", G_CALLBACK(on_togglebutton_record_clicked), self);
@@ -1065,12 +1226,16 @@
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_transfer);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_hold);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_muteaudio);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_record);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_mutevideo);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scalebutton_quality);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, siptransfer_popover);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, siptransfer_filter_entry);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, list_conversations);
details::current_call_view_signals[VIDEO_DOUBLE_CLICKED] = g_signal_new (
"video-double-clicked",
@@ -1092,6 +1257,5 @@
auto* priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
priv->cpp->setup(chat_widget, accountInfo, conversation);
-
return GTK_WIDGET(self);
}
diff --git a/ui/currentcallview.ui b/ui/currentcallview.ui
index 4145043..74eb794 100644
--- a/ui/currentcallview.ui
+++ b/ui/currentcallview.ui
@@ -262,6 +262,25 @@
</packing>
</child>
<child>
+ <object class="GtkToggleButton" id="togglebutton_transfer">
+ <style>
+ <class name="call-button"/>
+ </style>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="width-request">48</property>
+ <property name="height-request">48</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip-text" translatable="yes">Toggle transfer</property>
+ <property name="image">image_transfer</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkToggleButton" id="togglebutton_hold">
<style>
<class name="call-button"/>
@@ -449,6 +468,15 @@
</object>
</child>
</object>
+ <object class="GtkImage" id="image_transfer">
+ <property name="visible">True</property>
+ <property name="resource">/cx/ring/RingGnome/transfer</property>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="image_transfer-atkobject">
+ <property name="AtkObject::accessible-description" translatable="yes">Transfer</property>
+ </object>
+ </child>
+ </object>
<object class="GtkImage" id="image_end">
<property name="visible">True</property>
<property name="resource">/cx/ring/RingGnome/call_end</property>
@@ -483,4 +511,67 @@
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
+ <object class="GtkPopover" id="siptransfer_popover">
+ <property name="can_focus">False</property>
+ <property name="height_request">300</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">False</property>
+ <property name="halign">center</property>
+ <property name="label" translatable="yes">Transfer to</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSearchEntry" id="siptransfer_filter_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_name">edit-find-symbolic</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">False</property>
+ <style>
+ <class name="search-entry-style"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkListBox" id="list_conversations">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
</interface>