blob: 6b528fe2fa5178d45fc73c741e6b2c114cfa053f [file] [log] [blame]
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05001/*
Guillaume Roguez2a6150d2017-07-19 18:24:47 -04002 * Copyright (C) 2016-2017 Savoir-faire Linux Inc.
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
aviau039001d2016-09-29 16:39:05 -04004 * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#include "chatview.h"
Nicolas Jager759faea2017-03-22 16:22:00 -040022#include "utils/accounts.h"
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050023
24#include <gtk/gtk.h>
25#include <call.h>
26#include <callmodel.h>
27#include <contactmethod.h>
28#include <person.h>
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050029#include <media/text.h>
30#include <media/textrecording.h>
31#include "ringnotify.h"
aviau039001d2016-09-29 16:39:05 -040032#include "profilemodel.h"
33#include "profile.h"
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -050034#include "numbercategory.h"
Stepan Salenikovich1b7100a2016-02-19 11:50:46 -050035#include <QtCore/QDateTime>
Stepan Salenikoviche9933242016-06-21 18:08:48 -040036#include "utils/calling.h"
Sébastien Blin70dc0b72017-07-31 16:24:41 -040037#include "utils/files.h"
aviau039001d2016-09-29 16:39:05 -040038#include "webkitchatcontainer.h"
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050039
Nicolas Jager759faea2017-03-22 16:22:00 -040040// LRC
41#include <account.h>
42
43
Stepan Salenikovichce06adb2016-02-19 12:53:53 -050044static constexpr GdkRGBA RING_BLUE = {0.0508, 0.594, 0.676, 1.0}; // outgoing msg color: (13, 152, 173)
45
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050046struct _ChatView
47{
48 GtkBox parent;
49};
50
51struct _ChatViewClass
52{
53 GtkBoxClass parent_class;
54};
55
56typedef struct _ChatViewPrivate ChatViewPrivate;
57
58struct _ChatViewPrivate
59{
aviau039001d2016-09-29 16:39:05 -040060 GtkWidget *box_webkit_chat_container;
61 GtkWidget *webkit_chat_container;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050062 GtkWidget *scrolledwindow_chat;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050063 GtkWidget *hbox_chat_info;
64 GtkWidget *label_peer;
65 GtkWidget *combobox_cm;
AmarOkcba03952017-07-17 10:13:49 -040066 GtkWidget *label_cm;
Stepan Salenikovich8043a562016-03-18 13:56:40 -040067 GtkWidget *button_close_chatview;
Stepan Salenikoviche9933242016-06-21 18:08:48 -040068 GtkWidget *button_placecall;
Nicolas Jager759faea2017-03-22 16:22:00 -040069 GtkWidget *button_send_invitation;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050070
Sébastien Blin70dc0b72017-07-31 16:24:41 -040071 GSettings *settings;
72
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050073 /* only one of the three following pointers should be non void;
74 * either this is an in-call chat (and so the in-call chat APIs will be used)
aviau039001d2016-09-29 16:39:05 -040075 * or it is an out of call chat (and so the account chat APIs will be used) */
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050076 Call *call;
77 Person *person;
78 ContactMethod *cm;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050079
80 QMetaObject::Connection new_message_connection;
aviau039001d2016-09-29 16:39:05 -040081 QMetaObject::Connection message_changed_connection;
Sébastien Blinf3681aa2017-07-19 16:53:49 -040082 QMetaObject::Connection update_name;
83 QMetaObject::Connection update_send_invitation;
Stepan Salenikovich1f731212016-11-10 11:55:10 -050084
85 gulong webkit_ready;
AmarOkb4253242017-07-13 11:21:39 -040086 gulong webkit_send_text;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050087};
88
89G_DEFINE_TYPE_WITH_PRIVATE(ChatView, chat_view, GTK_TYPE_BOX);
90
91#define CHAT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CHAT_VIEW_TYPE, ChatViewPrivate))
92
93enum {
94 NEW_MESSAGES_DISPLAYED,
Stepan Salenikovich8043a562016-03-18 13:56:40 -040095 HIDE_VIEW_CLICKED,
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050096 LAST_SIGNAL
97};
98
99static guint chat_view_signals[LAST_SIGNAL] = { 0 };
100
101static void
102chat_view_dispose(GObject *object)
103{
104 ChatView *view;
105 ChatViewPrivate *priv;
106
107 view = CHAT_VIEW(object);
108 priv = CHAT_VIEW_GET_PRIVATE(view);
109
110 QObject::disconnect(priv->new_message_connection);
aviau039001d2016-09-29 16:39:05 -0400111 QObject::disconnect(priv->message_changed_connection);
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400112 QObject::disconnect(priv->update_name);
113 QObject::disconnect(priv->update_send_invitation);
aviau039001d2016-09-29 16:39:05 -0400114
115 /* Destroying the box will also destroy its children, and we wouldn't
116 * want that. So we remove the webkit_chat_container from the box. */
117 if (priv->webkit_chat_container) {
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500118 /* disconnect for webkit signals */
119 g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
120 priv->webkit_ready = 0;
AmarOkb4253242017-07-13 11:21:39 -0400121 g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
122 priv->webkit_send_text = 0;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500123
aviau039001d2016-09-29 16:39:05 -0400124 gtk_container_remove(
125 GTK_CONTAINER(priv->box_webkit_chat_container),
126 GTK_WIDGET(priv->webkit_chat_container)
127 );
128 priv->webkit_chat_container = nullptr;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500129
aviau039001d2016-09-29 16:39:05 -0400130 }
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500131
132 G_OBJECT_CLASS(chat_view_parent_class)->dispose(object);
133}
134
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500135static void
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400136hide_chat_view(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
137{
138 g_signal_emit(G_OBJECT(self), chat_view_signals[HIDE_VIEW_CLICKED], 0);
139}
140
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400141ContactMethod*
142get_active_contactmethod(ChatView *self)
143{
144 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
145
146 if (priv->cm) {
147 return priv->cm;
148 } else if (priv->person) {
149 auto cms = priv->person->phoneNumbers();
150 if (cms.size() == 1) {
151 return cms.first();
152 } else if (cms.size() > 1) {
153 auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
154 if (active >= 0 && active < cms.size()) {
155 return cms.at(active);
156 }
157 }
158 }
159
160 return nullptr;
161}
162
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400163static void
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400164display_links_toggled(ChatView *self)
165{
166 auto priv = CHAT_VIEW_GET_PRIVATE(self);
167 if (priv->webkit_chat_container) {
168 webkit_chat_container_set_display_links(
169 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
170 g_settings_get_boolean(priv->settings, "enable-display-links")
171 );
172 }
173}
174
175static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400176placecall_clicked(ChatView *self)
177{
178 auto priv = CHAT_VIEW_GET_PRIVATE(self);
179
180 if (priv->person) {
181 // get the chosen cm
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400182 auto cm = get_active_contactmethod(self);
183 if (cm) {
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400184 place_new_call(cm);
185 } else {
186 g_warning("no ContactMethod chosen; cannot place call");
187 }
188 } else if (priv->cm) {
189 place_new_call(priv->cm);
190 } else {
191 g_warning("no Person or ContactMethod set; cannot place call");
192 }
193}
194
195static void
Nicolas Jager759faea2017-03-22 16:22:00 -0400196button_send_invitation_clicked(ChatView *self)
197{
198 auto priv = CHAT_VIEW_GET_PRIVATE(self);
199
Guillaume Roguezcea60ca2017-06-02 16:38:25 -0400200 if (priv->person) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400201 priv->cm = get_active_contactmethod(self);
Guillaume Roguezcea60ca2017-06-02 16:38:25 -0400202 }
Nicolas Jager759faea2017-03-22 16:22:00 -0400203
204 if (!priv->cm) {
205 g_warning("invalid contact, cannot send invitation!");
206 return;
207 }
208
209 // try first to use the account associated to the contact method
210 auto account = priv->cm->account();
211 if (not account) {
212
213 // get the choosen account
214 account = get_active_ring_account();
215
216 if (not account) {
217 g_warning("invalid account, cannot send invitation!");
218 return;
219 }
220 }
221
222 // perform the request
223 if (not account->sendContactRequest(priv->cm))
224 g_warning("contact request not forwarded, cannot send invitation!");
225
226 // TODO : add an entry in the conversation to tell the user an invitation was sent.
227}
228
229static void
AmarOkb4253242017-07-13 11:21:39 -0400230webkit_chat_container_send_text(G_GNUC_UNUSED GtkWidget* webview, gchar *message, ChatView* self)
231{
232 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
233
234 /* make sure there is more than just whitespace, but if so, send the original text */
235 const auto text = QString(message);
236 if (!text.trimmed().isEmpty()) {
237 QMap<QString, QString> messages;
238 messages["text/plain"] = text;
239
240 if (priv->call) {
241 // in call message
242 priv->call->addOutgoingMedia<Media::Text>()->send(messages);
243 } else if (priv->person) {
244 // get the chosen cm
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400245 auto cm = get_active_contactmethod(self);
246 if (cm) {
AmarOkb4253242017-07-13 11:21:39 -0400247 if (!cm->sendOfflineTextMessage(messages))
248 g_warning("message failed to send"); // TODO: warn the user about this in the UI
249 } else {
250 g_warning("no ContactMethod chosen; message not sent");
251 }
252 } else if (priv->cm) {
253 if (!priv->cm->sendOfflineTextMessage(messages))
254 g_warning("message failed to send"); // TODO: warn the user about this in the UI
255 } else {
256 g_warning("no Call, Person, or ContactMethod set; message not sent");
257 }
258 }
259}
260
261static void
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500262chat_view_init(ChatView *view)
263{
264 gtk_widget_init_template(GTK_WIDGET(view));
265
266 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400267 priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500268
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400269 g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400270 g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
Nicolas Jager759faea2017-03-22 16:22:00 -0400271 g_signal_connect_swapped(priv->button_send_invitation, "clicked", G_CALLBACK(button_send_invitation_clicked), view);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500272}
273
274static void
275chat_view_class_init(ChatViewClass *klass)
276{
277 G_OBJECT_CLASS(klass)->dispose = chat_view_dispose;
278
279 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
280 "/cx/ring/RingGnome/chatview.ui");
281
aviau039001d2016-09-29 16:39:05 -0400282 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500283 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, scrolledwindow_chat);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500284 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, hbox_chat_info);
285 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
AmarOkcba03952017-07-17 10:13:49 -0400286 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_cm);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500287 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, combobox_cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400288 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400289 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
Nicolas Jager759faea2017-03-22 16:22:00 -0400290 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_send_invitation);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500291
292 chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
293 "new-messages-displayed",
294 G_TYPE_FROM_CLASS(klass),
295 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
296 0,
297 nullptr,
298 nullptr,
299 g_cclosure_marshal_VOID__VOID,
300 G_TYPE_NONE, 0);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400301
302 chat_view_signals[HIDE_VIEW_CLICKED] = g_signal_new (
303 "hide-view-clicked",
304 G_TYPE_FROM_CLASS(klass),
305 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
306 0,
307 nullptr,
308 nullptr,
309 g_cclosure_marshal_VOID__VOID,
310 G_TYPE_NONE, 0);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500311}
312
aviau039001d2016-09-29 16:39:05 -0400313
314
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500315static void
aviau039001d2016-09-29 16:39:05 -0400316print_message_to_buffer(ChatView* self, const QModelIndex &idx)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500317{
aviau039001d2016-09-29 16:39:05 -0400318 if (!idx.isValid()) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500319 g_warning("QModelIndex in im model is not valid");
aviau039001d2016-09-29 16:39:05 -0400320 return;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500321 }
aviau039001d2016-09-29 16:39:05 -0400322
323 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
324
325 webkit_chat_container_print_new_message(
326 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
327 idx
328 );
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500329}
330
aviaufc213552016-11-01 12:39:39 -0400331static void
332set_participant_images(ChatView* self)
333{
334 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
335
336 webkit_chat_container_clear_sender_images(
337 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
338 );
339
340 /* Set the sender image for the peer */
341 ContactMethod* sender_contact_method_peer;
342 QVariant photo_variant_peer;
343
344 if (priv->person)
345 {
346 photo_variant_peer = priv->person->photo();
347 sender_contact_method_peer = get_active_contactmethod(self);
348 }
349 else
350 {
351 if (priv->cm)
352 {
353 sender_contact_method_peer = priv->cm;
354 }
355 else
356 {
357 sender_contact_method_peer = priv->call->peerContactMethod();
358 }
359 photo_variant_peer = sender_contact_method_peer->roleData((int) Call::Role::Photo);
360 }
361
362 if (photo_variant_peer.isValid())
363 {
364 webkit_chat_container_set_sender_image(
365 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
366 sender_contact_method_peer,
367 photo_variant_peer
368 );
369 }
370
371 /* set sender image for "ME" */
372 auto profile = ProfileModel::instance().selectedProfile();
373 if (profile)
374 {
375 auto person = profile->person();
376 if (person)
377 {
378 auto photo_variant_me = person->photo();
379 if (photo_variant_me.isValid())
380 {
381 webkit_chat_container_set_sender_image(
382 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
383 nullptr,
384 photo_variant_me
385 );
386 }
387 }
388 }
389}
390
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500391static void
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500392print_text_recording(Media::TextRecording *recording, ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500393{
394 g_return_if_fail(IS_CHAT_VIEW(self));
395 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
396
aviaufc213552016-11-01 12:39:39 -0400397 /* set the photos of the chat participants */
398 set_participant_images(self);
399
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500400 /* only text messages are supported for now */
401 auto model = recording->instantTextMessagingModel();
402
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500403 /* new model, disconnect from the old model updates and clear the text buffer */
404 QObject::disconnect(priv->new_message_connection);
405
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500406 /* put all the messages in the im model into the text view */
407 for (int row = 0; row < model->rowCount(); ++row) {
408 QModelIndex idx = model->index(row, 0);
aviau039001d2016-09-29 16:39:05 -0400409 print_message_to_buffer(self, idx);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500410 }
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500411 /* mark all messages as read */
412 recording->setAllRead();
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500413
aviau039001d2016-09-29 16:39:05 -0400414
415 /* messages modified */
416 /* connecting on instantMessagingModel and not textMessagingModel */
417 priv->message_changed_connection = QObject::connect(
418 model,
419 &QAbstractItemModel::dataChanged,
420 [self, priv] (const QModelIndex & topLeft, G_GNUC_UNUSED const QModelIndex & bottomRight)
421 {
422 webkit_chat_container_update_message(
423 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
424 topLeft
425 );
426 }
427 );
428
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500429 /* append new messages */
430 priv->new_message_connection = QObject::connect(
431 model,
432 &QAbstractItemModel::rowsInserted,
433 [self, priv, model] (const QModelIndex &parent, int first, int last) {
434 for (int row = first; row <= last; ++row) {
435 QModelIndex idx = model->index(row, 0, parent);
aviau039001d2016-09-29 16:39:05 -0400436 print_message_to_buffer(self, idx);
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500437 /* make sure these messages are marked as read */
438 model->setData(idx, true, static_cast<int>(Media::TextRecording::Role::IsRead));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500439 g_signal_emit(G_OBJECT(self), chat_view_signals[NEW_MESSAGES_DISPLAYED], 0);
440 }
441 }
442 );
443}
444
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500445static void
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400446update_send_invitation(ChatView *self)
447{
448 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
449
450 auto cm = get_active_contactmethod(self);
451
452 if (cm && cm->isConfirmed()) {
453 gtk_widget_hide(priv->button_send_invitation);
454 }
455}
456
457static void
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500458selected_cm_changed(ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500459{
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500460 auto priv = CHAT_VIEW_GET_PRIVATE(self);
461
462 /* make sure the webkit view is ready, in case we get this signal before it is */
463 if (!webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
464 return;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500465
aviaufc213552016-11-01 12:39:39 -0400466 auto cm = get_active_contactmethod(self);
467 if (cm){
468 print_text_recording(cm->textRecording(), self);
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400469
470 QObject::disconnect(priv->update_send_invitation);
471 priv->update_send_invitation = QObject::connect(
472 get_active_contactmethod(self),
473 &ContactMethod::changed,
474 [self] () { update_send_invitation(self); }
475 );
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500476 } else {
477 g_warning("no valid ContactMethod selected to display chat conversation");
478 }
479}
480
481static void
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500482render_contact_method(G_GNUC_UNUSED GtkCellLayout *cell_layout,
483 GtkCellRenderer *cell,
484 GtkTreeModel *model,
485 GtkTreeIter *iter,
486 G_GNUC_UNUSED gpointer data)
487{
488 GValue value = G_VALUE_INIT;
489 gtk_tree_model_get_value(model, iter, 0, &value);
490 auto cm = (ContactMethod *)g_value_get_pointer(&value);
491
492 gchar *number = nullptr;
493 if (cm && cm->category()) {
494 // try to get the number category, eg: "home"
495 number = g_strdup_printf("(%s) %s", cm->category()->name().toUtf8().constData(),
Houminbeab3c92017-05-16 11:12:16 +0800496 cm->bestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500497 } else if (cm) {
Houminbeab3c92017-05-16 11:12:16 +0800498 number = g_strdup_printf("%s", cm->bestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500499 }
500
501 g_object_set(G_OBJECT(cell), "text", number, NULL);
502 g_free(number);
503}
504
505static void
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500506update_contact_methods(ChatView *self)
507{
508 g_return_if_fail(IS_CHAT_VIEW(self));
509 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
510
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500511 g_return_if_fail(priv->call || priv->person || priv->cm);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500512
513 /* model for the combobox for the choice of ContactMethods */
514 auto cm_model = gtk_list_store_new(
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500515 1, G_TYPE_POINTER
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500516 );
517
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400518 Person::ContactMethods cms;
519
520 if (priv->person)
521 cms = priv->person->phoneNumbers();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500522 else if (priv->cm)
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400523 cms << priv->cm;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500524 else if (priv->call)
525 cms << priv->call->peerContactMethod();
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400526
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500527 for (int i = 0; i < cms.size(); ++i) {
528 GtkTreeIter iter;
529 gtk_list_store_append(cm_model, &iter);
530 gtk_list_store_set(cm_model, &iter,
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500531 0, cms.at(i),
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500532 -1);
533 }
534
535 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_cm), GTK_TREE_MODEL(cm_model));
536 g_object_unref(cm_model);
537
538 auto renderer = gtk_cell_renderer_text_new();
539 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
540 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, FALSE);
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500541 gtk_cell_layout_set_cell_data_func(
542 GTK_CELL_LAYOUT(priv->combobox_cm),
543 renderer,
544 (GtkCellLayoutDataFunc)render_contact_method,
545 nullptr, nullptr);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500546
547 /* select the last used cm */
548 if (!cms.isEmpty()) {
549 auto last_used_cm = cms.at(0);
550 int last_used_cm_idx = 0;
551 for (int i = 1; i < cms.size(); ++i) {
552 auto new_cm = cms.at(i);
553 if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0) {
554 last_used_cm = new_cm;
555 last_used_cm_idx = i;
556 }
557 }
558
559 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_cm), last_used_cm_idx);
Guillaume Roguezd35d6492017-06-26 16:40:09 -0400560
561 if (last_used_cm->isConfirmed())
562 gtk_widget_hide(priv->button_send_invitation);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500563 }
564
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400565 /* if there is only one CM, make the combo box insensitive */
AmarOkcba03952017-07-17 10:13:49 -0400566 if (cms.size() < 2) {
567 auto active = cms.at(gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm)));
568 std::string ring_id = active->roleData(static_cast<int>(Ring::Role::Number)).toString().toUtf8().constData();
569 std::string displayed = gtk_label_get_text(GTK_LABEL(priv->label_peer));
570 gtk_label_set_text(GTK_LABEL(priv->label_cm), ring_id.c_str());
571 gtk_widget_hide(priv->combobox_cm);
572 if (displayed == ring_id) {
573 gtk_widget_hide(priv->label_cm);
574 } else {
575 gtk_widget_show(priv->label_cm);
576 }
577 } else {
578 gtk_widget_show(priv->combobox_cm);
579 gtk_widget_hide(priv->label_cm);
580 }
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400581
582 /* if no CMs make the call button insensitive */
583 gtk_widget_set_sensitive(priv->button_placecall, !cms.isEmpty());
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500584}
585
586static void
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400587update_name(ChatView *self)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500588{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500589 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
590
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500591 g_return_if_fail(priv->person || priv->cm || priv->call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500592
593 QString name;
594 if (priv->person) {
595 name = priv->person->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500596 } else if (priv->cm) {
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500597 name = priv->cm->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500598 } else if (priv->call) {
599 name = priv->call->peerContactMethod()->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500600 }
601 gtk_label_set_text(GTK_LABEL(priv->label_peer), name.toUtf8().constData());
602}
603
aviau039001d2016-09-29 16:39:05 -0400604static void
aviau039001d2016-09-29 16:39:05 -0400605webkit_chat_container_ready(ChatView* self)
606{
607 /* The webkit chat container has loaded the javascript libraries, we can
608 * now use it. */
609
610 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
611
612 webkit_chat_container_clear(
613 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
614 );
615
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400616 display_links_toggled(self);
617
aviau039001d2016-09-29 16:39:05 -0400618 /* print the text recordings */
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500619 if (priv->call) {
620 print_text_recording(priv->call->peerContactMethod()->textRecording(), self);
621 } else if (priv->cm) {
622 print_text_recording(priv->cm->textRecording(), self);
623 } else if (priv->person) {
624 /* get the selected cm and print the text recording */
625 selected_cm_changed(self);
aviau039001d2016-09-29 16:39:05 -0400626 }
aviau039001d2016-09-29 16:39:05 -0400627}
628
629static void
630build_chat_view(ChatView* self)
631{
632 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
633
634 gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container);
635 gtk_widget_show(priv->webkit_chat_container);
636
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500637 /* keep name updated */
638 if (priv->call) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400639 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500640 priv->call,
641 &Call::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400642 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500643 );
644 } else if (priv->cm) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400645 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500646 priv->cm,
647 &ContactMethod::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400648 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500649 );
650 } else if (priv->person) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400651 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500652 priv->person,
653 &Person::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400654 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500655 );
656 }
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400657
658 priv->update_send_invitation = QObject::connect(
659 get_active_contactmethod(self),
660 &ContactMethod::changed,
661 [self] () { update_send_invitation(self); }
662 );
663
664 update_name(self);
665 update_send_invitation(self);
aviau039001d2016-09-29 16:39:05 -0400666
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500667 /* keep selected cm updated */
668 update_contact_methods(self);
669 g_signal_connect_swapped(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self);
670
671 priv->webkit_ready = g_signal_connect_swapped(
aviau039001d2016-09-29 16:39:05 -0400672 priv->webkit_chat_container,
673 "ready",
674 G_CALLBACK(webkit_chat_container_ready),
675 self
676 );
677
AmarOkb4253242017-07-13 11:21:39 -0400678 priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
679 "send-message",
680 G_CALLBACK(webkit_chat_container_send_text),
681 self);
682
aviau039001d2016-09-29 16:39:05 -0400683 if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
aviau039001d2016-09-29 16:39:05 -0400684 webkit_chat_container_ready(self);
aviau039001d2016-09-29 16:39:05 -0400685
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500686 /* we only show the chat info in the case of cm / person */
687 gtk_widget_set_visible(priv->hbox_chat_info, (priv->cm || priv->person));
aviau039001d2016-09-29 16:39:05 -0400688}
689
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500690GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400691chat_view_new_call(WebKitChatContainer *webkit_chat_container, Call *call)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500692{
693 g_return_val_if_fail(call, nullptr);
694
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500695 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500696
aviau039001d2016-09-29 16:39:05 -0400697 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
698 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500699 priv->call = call;
aviau039001d2016-09-29 16:39:05 -0400700
701 build_chat_view(self);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500702
703 return (GtkWidget *)self;
704}
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500705
706GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400707chat_view_new_cm(WebKitChatContainer *webkit_chat_container, ContactMethod *cm)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500708{
709 g_return_val_if_fail(cm, nullptr);
710
711 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500712
aviau039001d2016-09-29 16:39:05 -0400713 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
714 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500715 priv->cm = cm;
aviau039001d2016-09-29 16:39:05 -0400716
717 build_chat_view(self);
718
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500719 return (GtkWidget *)self;
720}
721
722GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400723chat_view_new_person(WebKitChatContainer *webkit_chat_container, Person *p)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500724{
725 g_return_val_if_fail(p, nullptr);
726
727 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500728
aviau039001d2016-09-29 16:39:05 -0400729 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
730 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500731 priv->person = p;
732
aviau039001d2016-09-29 16:39:05 -0400733 build_chat_view(self);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500734
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500735 return (GtkWidget *)self;
736}
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400737
738Call*
739chat_view_get_call(ChatView *self)
740{
741 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
742 auto priv = CHAT_VIEW_GET_PRIVATE(self);
743
744 return priv->call;
745}
746
747ContactMethod*
748chat_view_get_cm(ChatView *self)
749{
750 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
751 auto priv = CHAT_VIEW_GET_PRIVATE(self);
752
753 return priv->cm;
754}
755
756Person*
757chat_view_get_person(ChatView *self)
758{
759 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
760 auto priv = CHAT_VIEW_GET_PRIVATE(self);
761
762 return priv->person;
763}
Stepan Salenikovichdaf3cb32016-10-12 16:39:42 -0400764
765void
766chat_view_set_header_visible(ChatView *self, gboolean visible)
767{
768 auto priv = CHAT_VIEW_GET_PRIVATE(self);
769
770 gtk_widget_set_visible(priv->hbox_chat_info, visible);
771}