Commit c6a3b98a authored by Stepan Salenikovich's avatar Stepan Salenikovich

out of call chat

Initial implementation. Now clicking on an item in the Conversations
view (RecentModel) will bring up a chat view. In the case of a Person
(contact) it will select the chat with the last used ContactMethod of
that person. If there is more than one ContactMethod, as combo box will
be displayed giving the choice of ContactMethods to use.

To make a call, double-click the item as before. Any call (incoming or
outgoing) will superseed the chat view.

Out of call chats use the account based chat API. In call chats still
use the call based chat API.

Change-Id: I3deb09fd22c3dda7b78ea9be0eef32a6f27adecb
Tuleap: #203
parent d2cad06c
...@@ -47,8 +47,17 @@ struct _ChatViewPrivate ...@@ -47,8 +47,17 @@ struct _ChatViewPrivate
GtkWidget *button_chat_input; GtkWidget *button_chat_input;
GtkWidget *entry_chat_input; GtkWidget *entry_chat_input;
GtkWidget *scrolledwindow_chat; GtkWidget *scrolledwindow_chat;
GtkWidget *hbox_chat_info;
Call *call; GtkWidget *label_peer;
GtkWidget *combobox_cm;
/* only one of the three following pointers should be non void;
* either this is an in-call chat (and so the in-call chat APIs will be used)
* or it is an out of call chat (and so the account chat APIs will be used)
*/
Call *call;
Person *person;
ContactMethod *cm;
QMetaObject::Connection new_message_connection; QMetaObject::Connection new_message_connection;
}; };
...@@ -90,7 +99,27 @@ send_chat(G_GNUC_UNUSED GtkWidget *widget, ChatView *self) ...@@ -90,7 +99,27 @@ send_chat(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
if (text && strlen(text) > 0) { if (text && strlen(text) > 0) {
QMap<QString, QString> messages; QMap<QString, QString> messages;
messages["text/plain"] = text; messages["text/plain"] = text;
priv->call->addOutgoingMedia<Media::Text>()->send(messages);
if (priv->call) {
// in call message
priv->call->addOutgoingMedia<Media::Text>()->send(messages);
} else if (priv->person) {
// get the chosen cm
auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
if (active >= 0) {
auto cm = priv->person->phoneNumbers().at(active);
if (!cm->sendOfflineTextMessage(messages))
g_warning("message failed to send"); // TODO: warn the user about this in the UI
} else {
g_warning("no ContactMethod chosen; message not esnt");
}
} else if (priv->cm) {
if (!priv->cm->sendOfflineTextMessage(messages))
g_warning("message failed to send"); // TODO: warn the user about this in the UI
} else {
g_warning("no Call, Person, or ContactMethod set; message not sent");
}
/* clear the entry */ /* clear the entry */
gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), ""); gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), "");
} }
...@@ -132,6 +161,9 @@ chat_view_class_init(ChatViewClass *klass) ...@@ -132,6 +161,9 @@ chat_view_class_init(ChatViewClass *klass)
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_chat_input); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_chat_input);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, entry_chat_input); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, entry_chat_input);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, scrolledwindow_chat); gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, scrolledwindow_chat);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, hbox_chat_info);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, combobox_cm);
chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new ( chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
"new-messages-displayed", "new-messages-displayed",
...@@ -217,9 +249,96 @@ parse_chat_model(QAbstractItemModel *model, ChatView *self) ...@@ -217,9 +249,96 @@ parse_chat_model(QAbstractItemModel *model, ChatView *self)
); );
} }
static void
selected_cm_changed(GtkComboBox *box, ChatView *self)
{
g_return_if_fail(IS_CHAT_VIEW(self));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
auto cms = priv->person->phoneNumbers();
auto active = gtk_combo_box_get_active(box);
if (active >= 0 && active < cms.size()) {
parse_chat_model(cms.at(active)->textRecording()->instantMessagingModel(), self);
} else {
g_warning("no valid ContactMethod selected to display chat conversation");
}
}
static void
update_contact_methods(ChatView *self)
{
g_return_if_fail(IS_CHAT_VIEW(self));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
g_return_if_fail(priv->person);
/* model for the combobox for the choice of ContactMethods */
auto cm_model = gtk_list_store_new(
2, G_TYPE_STRING, G_TYPE_POINTER
);
auto cms = priv->person->phoneNumbers();
for (int i = 0; i < cms.size(); ++i) {
GtkTreeIter iter;
gtk_list_store_append(cm_model, &iter);
gtk_list_store_set(cm_model, &iter,
0, cms.at(i)->uri().toUtf8().constData(),
1, cms.at(i),
-1);
}
gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_cm), GTK_TREE_MODEL(cm_model));
g_object_unref(cm_model);
auto renderer = gtk_cell_renderer_text_new();
g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, FALSE);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, "text", 0, NULL);
/* select the last used cm */
if (!cms.isEmpty()) {
auto last_used_cm = cms.at(0);
int last_used_cm_idx = 0;
for (int i = 1; i < cms.size(); ++i) {
auto new_cm = cms.at(i);
if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0) {
last_used_cm = new_cm;
last_used_cm_idx = i;
}
}
gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_cm), last_used_cm_idx);
}
/* show the combo box if there is more than one cm to choose from */
if (cms.size() > 1)
gtk_widget_show_all(priv->combobox_cm);
else
gtk_widget_hide(priv->combobox_cm);
}
static void
update_name(ChatView *self)
{
g_return_if_fail(IS_CHAT_VIEW(self));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
g_return_if_fail(priv->person || priv->cm);
QString name;
if (priv->person) {
name = priv->person->roleData(static_cast<int>(Ring::Role::Name)).toString();
} else {
name = priv->cm->roleData(static_cast<int>(Ring::Role::Name)).toString();
}
gtk_label_set_text(GTK_LABEL(priv->label_peer), name.toUtf8().constData());
}
GtkWidget * GtkWidget *
chat_view_new(Call *call) chat_view_new_call(Call *call)
{ {
g_return_val_if_fail(call, nullptr);
ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
...@@ -229,3 +348,41 @@ chat_view_new(Call *call) ...@@ -229,3 +348,41 @@ chat_view_new(Call *call)
return (GtkWidget *)self; return (GtkWidget *)self;
} }
GtkWidget *
chat_view_new_cm(ContactMethod *cm)
{
g_return_val_if_fail(cm, nullptr);
ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
priv->cm = cm;
parse_chat_model(priv->cm->textRecording()->instantMessagingModel(), self);
update_name(self);
gtk_widget_show(priv->hbox_chat_info);
return (GtkWidget *)self;
}
GtkWidget *
chat_view_new_person(Person *p)
{
g_return_val_if_fail(p, nullptr);
ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
priv->person = p;
/* connect to the changed signal before setting the cm combo box, so that the correct
* conversation will get displayed */
g_signal_connect(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self);
update_contact_methods(self);
update_name(self);
gtk_widget_show(priv->hbox_chat_info);
return (GtkWidget *)self;
}
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
class Call; class Call;
class ContactMethod;
class Person;
G_BEGIN_DECLS G_BEGIN_DECLS
...@@ -36,8 +38,10 @@ typedef struct _ChatView ChatView; ...@@ -36,8 +38,10 @@ typedef struct _ChatView ChatView;
typedef struct _ChatViewClass ChatViewClass; typedef struct _ChatViewClass ChatViewClass;
GType chat_view_get_type (void) G_GNUC_CONST; GType chat_view_get_type (void) G_GNUC_CONST;
GtkWidget *chat_view_new (Call* call); GtkWidget *chat_view_new_call (Call* call);
GtkWidget *chat_view_new_cm (ContactMethod* cm);
GtkWidget *chat_view_new_person (Person* p);
G_END_DECLS G_END_DECLS
......
...@@ -637,7 +637,7 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) { ...@@ -637,7 +637,7 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
} }
/* init chat view */ /* init chat view */
auto chat_view = chat_view_new(priv->call); auto chat_view = chat_view_new_call(priv->call);
gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view); gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);
/* check if there were any chat notifications and open the chat view if so */ /* check if there were any chat notifications and open the chat view if so */
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
#include "ringwelcomeview.h" #include "ringwelcomeview.h"
#include "recentcontactsview.h" #include "recentcontactsview.h"
#include <recentmodel.h> #include <recentmodel.h>
#include "chatview.h"
static constexpr const char* CALL_VIEW_NAME = "calls"; static constexpr const char* CALL_VIEW_NAME = "calls";
static constexpr const char* CREATE_ACCOUNT_1_VIEW_NAME = "create1"; static constexpr const char* CREATE_ACCOUNT_1_VIEW_NAME = "create1";
...@@ -189,12 +190,13 @@ video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self) ...@@ -189,12 +190,13 @@ video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
* This takes the RecentModel index as the argument and displays the corresponding view: * This takes the RecentModel index as the argument and displays the corresponding view:
* - incoming call view * - incoming call view
* - current call view * - current call view
* TODO: chat view * - chat view
* - welcome view (if no index is selected) * - welcome view (if no index is selected)
*/ */
static void static void
selection_changed(const QModelIndex& idx, RingMainWindow *win) selection_changed(const QModelIndex& recent_idx, RingMainWindow *win)
{ {
// g_debug("selection changed");
g_return_if_fail(IS_RING_MAIN_WINDOW(win)); g_return_if_fail(IS_RING_MAIN_WINDOW(win));
RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
...@@ -208,12 +210,17 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win) ...@@ -208,12 +210,17 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win)
/* make sure we leave full screen, since the call selection is changing */ /* make sure we leave full screen, since the call selection is changing */
leave_full_screen(win); leave_full_screen(win);
/* show the call if there is one associated with this index */ /* check which object type is selected */
auto call_idx = CallModel::instance().getIndex(RecentModel::instance().getActiveCall(idx)); auto type = recent_idx.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
auto object = recent_idx.data(static_cast<int>(Ring::Role::Object));
/* try to get the call model index, in case its a call, since we're still using the CallModel as well */
auto call_idx = CallModel::instance().getIndex(RecentModel::instance().getActiveCall(recent_idx));
/* we prioritize showing the call view */
if (call_idx.isValid()) { if (call_idx.isValid()) {
/* show the call view */
QVariant state = call_idx.data(static_cast<int>(Call::Role::LifeCycleState)); QVariant state = call_idx.data(static_cast<int>(Call::Role::LifeCycleState));
GtkWidget *new_call_view = NULL; GtkWidget *new_call_view = NULL;
char* new_call_view_name = NULL;
switch(state.value<Call::LifeCycleState>()) { switch(state.value<Call::LifeCycleState>()) {
case Call::LifeCycleState::CREATION: case Call::LifeCycleState::CREATION:
...@@ -221,15 +228,11 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win) ...@@ -221,15 +228,11 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win)
case Call::LifeCycleState::FINISHED: case Call::LifeCycleState::FINISHED:
new_call_view = incoming_call_view_new(); new_call_view = incoming_call_view_new();
incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), call_idx); incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), call_idx);
/* use the pointer of the call as a unique name */
new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance().getCall(call_idx));
break; break;
case Call::LifeCycleState::PROGRESS: case Call::LifeCycleState::PROGRESS:
new_call_view = current_call_view_new(); new_call_view = current_call_view_new();
g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win); g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), call_idx); current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), call_idx);
/* use the pointer of the call as a unique name */
new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance().getCall(call_idx));
break; break;
case Call::LifeCycleState::COUNT__: case Call::LifeCycleState::COUNT__:
g_warning("LifeCycleState should never be COUNT"); g_warning("LifeCycleState should never be COUNT");
...@@ -239,9 +242,20 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win) ...@@ -239,9 +242,20 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win)
gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view); gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view); gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
gtk_widget_show(new_call_view); gtk_widget_show(new_call_view);
g_free(new_call_view_name); } else if (type == Ring::ObjectType::Person && object.isValid()) {
/* show chat view constructed from Person object */
auto new_chat_view = chat_view_new_person(object.value<Person *>());
gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
gtk_widget_show(new_chat_view);
} else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
/* show chat view constructed from CM */
auto new_chat_view = chat_view_new_cm(object.value<ContactMethod *>());
gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
gtk_widget_show(new_chat_view);
} else { } else {
/* nothing selected in the call model, so show the default screen */ /* nothing selected that we can display, show the welcome view */
gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view); gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view); gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
} }
...@@ -250,56 +264,81 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win) ...@@ -250,56 +264,81 @@ selection_changed(const QModelIndex& idx, RingMainWindow *win)
static void static void
call_state_changed(Call *call, RingMainWindow *win) call_state_changed(Call *call, RingMainWindow *win)
{ {
// g_debug("call state changed") ; // g_debug("call state changed");
RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
/* if we're showing the settings, then nothing needs to be done as the call /* if we're showing the settings, then nothing needs to be done as the call
view is not shown */ view is not shown */
if (priv->show_settings) return; if (priv->show_settings) return;
/* we prioritize showing the call view; but if the call is over we go back to showing the chat view */
/* check if the call that changed state is the same as the selected call */ /* check if the call that changed state is the same as the selected call */
QModelIndex idx_selected = CallModel::instance().selectionModel()->currentIndex(); auto idx_selected = RecentModel::instance().selectionModel()->currentIndex();
if( idx_selected.isValid() && call == CallModel::instance().getCall(idx_selected)) { if(call == RecentModel::instance().getActiveCall(idx_selected)) {
// g_debug("selected call state changed");
/* check if we need to change the view */ /* check if we need to change the view */
GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call)); auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
GtkWidget *new_call_view = NULL;
QVariant state = CallModel::instance().data(idx_selected, static_cast<int>(Call::Role::LifeCycleState)); QVariant state = CallModel::instance().data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
/* check what the current state is vs what is displayed */ /* check what the current state is vs what is displayed */
switch(state.value<Call::LifeCycleState>()) { switch(state.value<Call::LifeCycleState>()) {
case Call::LifeCycleState::CREATION: case Call::LifeCycleState::CREATION:
case Call::LifeCycleState::FINISHED:
/* go back to incoming call view;
* it will show that the call failed and offer to hang it up */
case Call::LifeCycleState::INITIALIZATION: case Call::LifeCycleState::INITIALIZATION:
/* LifeCycleState cannot go backwards, so it should not be possible {
* that the call is displayed as current (meaning that its in progress) /* show the incoming call view */
* but have the state 'initialization' */ if (!IS_INCOMING_CALL_VIEW(current_view)) {
if (IS_CURRENT_CALL_VIEW(old_call_view)) auto new_view = incoming_call_view_new();
g_warning("call displayed as current, but is in state of initialization"); incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
gtk_widget_show(new_view);
}
}
break; break;
case Call::LifeCycleState::PROGRESS: case Call::LifeCycleState::PROGRESS:
if (IS_INCOMING_CALL_VIEW(old_call_view)) { {
/* change from incoming to current */ /* show the current call view */
new_call_view = current_call_view_new(); if (!IS_CURRENT_CALL_VIEW(current_view)) {
current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected); auto new_view = current_call_view_new();
/* use the pointer of the call as a unique name */ g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
char* new_call_view_name = NULL; current_call_view_set_call_info(CURRENT_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance().getCall(idx_selected)); gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
g_free(new_call_view_name); gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view); gtk_widget_show(new_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view); }
gtk_widget_show(new_call_view);
g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
} }
break; break;
case Call::LifeCycleState::FINISHED:
/* leave fullscreen if call is over */
leave_full_screen(win);
break;
case Call::LifeCycleState::COUNT__: case Call::LifeCycleState::COUNT__:
g_warning("LifeCycleState should never be COUNT"); g_warning("LifeCycleState should never be COUNT");
break; break;
} }
} else if (idx_selected.isValid()) {
/* otherwise, the call is over and is already removed from the RecentModel */
auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
leave_full_screen(win);
/* show the chat view */
if (!IS_CHAT_VIEW(current_view)) {
auto type = idx_selected.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
auto object = idx_selected.data(static_cast<int>(Ring::Role::Object));
if (type == Ring::ObjectType::Person && object.isValid()) {
/* show chat view constructed from Person object */
auto new_view = chat_view_new_person(object.value<Person *>());
gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
gtk_widget_show(new_view);
} else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
/* show chat view constructed from CM */
auto new_view = chat_view_new_cm(object.value<ContactMethod *>());
gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
gtk_widget_show(new_view);
}
}
} }
} }
...@@ -910,7 +949,16 @@ ring_main_window_init(RingMainWindow *win) ...@@ -910,7 +949,16 @@ ring_main_window_init(RingMainWindow *win)
QObject::connect( QObject::connect(
&CallModel::instance(), &CallModel::instance(),
&CallModel::callStateChanged, &CallModel::callStateChanged,
[=](Call* call, G_GNUC_UNUSED Call::State previousState) { [win](Call* call, G_GNUC_UNUSED Call::State previousState) {
call_state_changed(call, win);
}
);
/* also connect to the incoming call, in case the RecentModel item we already selected gets a call */
QObject::connect(
&CallModel::instance(),
&CallModel::incomingCall,
[win](Call* call) {
call_state_changed(call, win); call_state_changed(call, win);
} }
); );
......
...@@ -3,7 +3,48 @@ ...@@ -3,7 +3,48 @@
<requires lib="gtk+" version="3.10"/> <requires lib="gtk+" version="3.10"/>
<template class="ChatView" parent="GtkBox"> <template class="ChatView" parent="GtkBox">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="spacing">5</property>
<!-- chat info (only show for out of call conversations) -->
<child>
<object class="GtkBox" id="hbox_chat_info">
<property name="visible">False</property>
<property name="no-show-all">True</property>
<property name="orientation">horizontal</property>
<property name="spacing">5</property>
<property name="border-width">5</property>
<child>
<object class="GtkLabel" id="label_peer">
<property name="visible">True</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_cm">
<property name="visible">False</property>
<property name="popup-fixed-width">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of chat info -->
<!-- start of chat text view --> <!-- start of chat text view -->
<child> <child>
<object class="GtkScrolledWindow" id="scrolledwindow_chat"> <object class="GtkScrolledWindow" id="scrolledwindow_chat">
...@@ -26,12 +67,12 @@ ...@@ -26,12 +67,12 @@
</packing> </packing>
</child> </child>
<!-- end of chat text view --> <!-- end of chat text view -->
<!-- start of chat entry --> <!-- start of chat entry -->
<child> <child>
<object class="GtkBox" id="hbox_chat_input"> <object class="GtkBox" id="hbox_chat_input">
<property name="visible">True</property> <property name="visible">True</property>
<property name="orientation">horizontal</property> <property name="orientation">horizontal</property>
<property name="spacing">5</property>
<child> <child>
<object class="GtkEntry" id="entry_chat_input"> <object class="GtkEntry" id="entry_chat_input">
<property name="visible">True</property> <property name="visible">True</property>
...@@ -53,5 +94,7 @@ ...@@ -53,5 +94,7 @@
<property name="fill">True</property> <property name="fill">True</property>
</packing> </packing>
</child> </child>
<!-- end of chat entry -->