blob: 076f62d99692b0297df71f5a842a6629406f08c5 [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 */
Sébastien Blinf4f90282017-10-03 14:07:16 -0400235 auto text = QString(message);
236 // NOTE for now, because we only have a send text action from the webkit.
237 if (text.startsWith("SEND:"))
238 text.remove(0, 5);
AmarOkb4253242017-07-13 11:21:39 -0400239 if (!text.trimmed().isEmpty()) {
240 QMap<QString, QString> messages;
241 messages["text/plain"] = text;
242
243 if (priv->call) {
244 // in call message
245 priv->call->addOutgoingMedia<Media::Text>()->send(messages);
246 } else if (priv->person) {
247 // get the chosen cm
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400248 auto cm = get_active_contactmethod(self);
249 if (cm) {
AmarOkb4253242017-07-13 11:21:39 -0400250 if (!cm->sendOfflineTextMessage(messages))
251 g_warning("message failed to send"); // TODO: warn the user about this in the UI
252 } else {
253 g_warning("no ContactMethod chosen; message not sent");
254 }
255 } else if (priv->cm) {
256 if (!priv->cm->sendOfflineTextMessage(messages))
257 g_warning("message failed to send"); // TODO: warn the user about this in the UI
258 } else {
259 g_warning("no Call, Person, or ContactMethod set; message not sent");
260 }
261 }
262}
263
264static void
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500265chat_view_init(ChatView *view)
266{
267 gtk_widget_init_template(GTK_WIDGET(view));
268
269 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400270 priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500271
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400272 g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400273 g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
Nicolas Jager759faea2017-03-22 16:22:00 -0400274 g_signal_connect_swapped(priv->button_send_invitation, "clicked", G_CALLBACK(button_send_invitation_clicked), view);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500275}
276
277static void
278chat_view_class_init(ChatViewClass *klass)
279{
280 G_OBJECT_CLASS(klass)->dispose = chat_view_dispose;
281
282 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
283 "/cx/ring/RingGnome/chatview.ui");
284
aviau039001d2016-09-29 16:39:05 -0400285 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500286 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, scrolledwindow_chat);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500287 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, hbox_chat_info);
288 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
AmarOkcba03952017-07-17 10:13:49 -0400289 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_cm);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500290 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, combobox_cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400291 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400292 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
Nicolas Jager759faea2017-03-22 16:22:00 -0400293 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_send_invitation);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500294
295 chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
296 "new-messages-displayed",
297 G_TYPE_FROM_CLASS(klass),
298 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
299 0,
300 nullptr,
301 nullptr,
302 g_cclosure_marshal_VOID__VOID,
303 G_TYPE_NONE, 0);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400304
305 chat_view_signals[HIDE_VIEW_CLICKED] = g_signal_new (
306 "hide-view-clicked",
307 G_TYPE_FROM_CLASS(klass),
308 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
309 0,
310 nullptr,
311 nullptr,
312 g_cclosure_marshal_VOID__VOID,
313 G_TYPE_NONE, 0);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500314}
315
aviau039001d2016-09-29 16:39:05 -0400316
317
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500318static void
aviau039001d2016-09-29 16:39:05 -0400319print_message_to_buffer(ChatView* self, const QModelIndex &idx)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500320{
aviau039001d2016-09-29 16:39:05 -0400321 if (!idx.isValid()) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500322 g_warning("QModelIndex in im model is not valid");
aviau039001d2016-09-29 16:39:05 -0400323 return;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500324 }
aviau039001d2016-09-29 16:39:05 -0400325
326 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
327
328 webkit_chat_container_print_new_message(
329 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
330 idx
331 );
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500332}
333
aviaufc213552016-11-01 12:39:39 -0400334static void
335set_participant_images(ChatView* self)
336{
337 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
338
339 webkit_chat_container_clear_sender_images(
340 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
341 );
342
343 /* Set the sender image for the peer */
344 ContactMethod* sender_contact_method_peer;
345 QVariant photo_variant_peer;
346
347 if (priv->person)
348 {
349 photo_variant_peer = priv->person->photo();
350 sender_contact_method_peer = get_active_contactmethod(self);
351 }
352 else
353 {
354 if (priv->cm)
355 {
356 sender_contact_method_peer = priv->cm;
357 }
358 else
359 {
360 sender_contact_method_peer = priv->call->peerContactMethod();
361 }
362 photo_variant_peer = sender_contact_method_peer->roleData((int) Call::Role::Photo);
363 }
364
365 if (photo_variant_peer.isValid())
366 {
367 webkit_chat_container_set_sender_image(
368 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
369 sender_contact_method_peer,
370 photo_variant_peer
371 );
372 }
373
374 /* set sender image for "ME" */
375 auto profile = ProfileModel::instance().selectedProfile();
376 if (profile)
377 {
378 auto person = profile->person();
379 if (person)
380 {
381 auto photo_variant_me = person->photo();
382 if (photo_variant_me.isValid())
383 {
384 webkit_chat_container_set_sender_image(
385 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
386 nullptr,
387 photo_variant_me
388 );
389 }
390 }
391 }
392}
393
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500394static void
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500395print_text_recording(Media::TextRecording *recording, ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500396{
397 g_return_if_fail(IS_CHAT_VIEW(self));
398 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
399
aviaufc213552016-11-01 12:39:39 -0400400 /* set the photos of the chat participants */
401 set_participant_images(self);
402
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500403 /* only text messages are supported for now */
404 auto model = recording->instantTextMessagingModel();
405
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500406 /* new model, disconnect from the old model updates and clear the text buffer */
407 QObject::disconnect(priv->new_message_connection);
408
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500409 /* put all the messages in the im model into the text view */
410 for (int row = 0; row < model->rowCount(); ++row) {
411 QModelIndex idx = model->index(row, 0);
aviau039001d2016-09-29 16:39:05 -0400412 print_message_to_buffer(self, idx);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500413 }
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500414 /* mark all messages as read */
415 recording->setAllRead();
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500416
aviau039001d2016-09-29 16:39:05 -0400417
418 /* messages modified */
419 /* connecting on instantMessagingModel and not textMessagingModel */
420 priv->message_changed_connection = QObject::connect(
421 model,
422 &QAbstractItemModel::dataChanged,
423 [self, priv] (const QModelIndex & topLeft, G_GNUC_UNUSED const QModelIndex & bottomRight)
424 {
425 webkit_chat_container_update_message(
426 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
427 topLeft
428 );
429 }
430 );
431
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500432 /* append new messages */
433 priv->new_message_connection = QObject::connect(
434 model,
435 &QAbstractItemModel::rowsInserted,
436 [self, priv, model] (const QModelIndex &parent, int first, int last) {
437 for (int row = first; row <= last; ++row) {
438 QModelIndex idx = model->index(row, 0, parent);
aviau039001d2016-09-29 16:39:05 -0400439 print_message_to_buffer(self, idx);
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500440 /* make sure these messages are marked as read */
441 model->setData(idx, true, static_cast<int>(Media::TextRecording::Role::IsRead));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500442 g_signal_emit(G_OBJECT(self), chat_view_signals[NEW_MESSAGES_DISPLAYED], 0);
443 }
444 }
445 );
446}
447
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500448static void
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400449update_send_invitation(ChatView *self)
450{
451 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
452
453 auto cm = get_active_contactmethod(self);
454
455 if (cm && cm->isConfirmed()) {
456 gtk_widget_hide(priv->button_send_invitation);
457 }
458}
459
460static void
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500461selected_cm_changed(ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500462{
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500463 auto priv = CHAT_VIEW_GET_PRIVATE(self);
464
465 /* make sure the webkit view is ready, in case we get this signal before it is */
466 if (!webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
467 return;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500468
aviaufc213552016-11-01 12:39:39 -0400469 auto cm = get_active_contactmethod(self);
470 if (cm){
471 print_text_recording(cm->textRecording(), self);
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400472
473 QObject::disconnect(priv->update_send_invitation);
474 priv->update_send_invitation = QObject::connect(
475 get_active_contactmethod(self),
476 &ContactMethod::changed,
477 [self] () { update_send_invitation(self); }
478 );
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500479 } else {
480 g_warning("no valid ContactMethod selected to display chat conversation");
481 }
482}
483
484static void
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500485render_contact_method(G_GNUC_UNUSED GtkCellLayout *cell_layout,
486 GtkCellRenderer *cell,
487 GtkTreeModel *model,
488 GtkTreeIter *iter,
489 G_GNUC_UNUSED gpointer data)
490{
491 GValue value = G_VALUE_INIT;
492 gtk_tree_model_get_value(model, iter, 0, &value);
493 auto cm = (ContactMethod *)g_value_get_pointer(&value);
494
495 gchar *number = nullptr;
496 if (cm && cm->category()) {
497 // try to get the number category, eg: "home"
498 number = g_strdup_printf("(%s) %s", cm->category()->name().toUtf8().constData(),
Houminbeab3c92017-05-16 11:12:16 +0800499 cm->bestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500500 } else if (cm) {
Houminbeab3c92017-05-16 11:12:16 +0800501 number = g_strdup_printf("%s", cm->bestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500502 }
503
504 g_object_set(G_OBJECT(cell), "text", number, NULL);
505 g_free(number);
506}
507
508static void
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500509update_contact_methods(ChatView *self)
510{
511 g_return_if_fail(IS_CHAT_VIEW(self));
512 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
513
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500514 g_return_if_fail(priv->call || priv->person || priv->cm);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500515
516 /* model for the combobox for the choice of ContactMethods */
517 auto cm_model = gtk_list_store_new(
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500518 1, G_TYPE_POINTER
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500519 );
520
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400521 Person::ContactMethods cms;
522
523 if (priv->person)
524 cms = priv->person->phoneNumbers();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500525 else if (priv->cm)
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400526 cms << priv->cm;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500527 else if (priv->call)
528 cms << priv->call->peerContactMethod();
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400529
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500530 for (int i = 0; i < cms.size(); ++i) {
531 GtkTreeIter iter;
532 gtk_list_store_append(cm_model, &iter);
533 gtk_list_store_set(cm_model, &iter,
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500534 0, cms.at(i),
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500535 -1);
536 }
537
538 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_cm), GTK_TREE_MODEL(cm_model));
539 g_object_unref(cm_model);
540
541 auto renderer = gtk_cell_renderer_text_new();
542 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
543 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, FALSE);
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500544 gtk_cell_layout_set_cell_data_func(
545 GTK_CELL_LAYOUT(priv->combobox_cm),
546 renderer,
547 (GtkCellLayoutDataFunc)render_contact_method,
548 nullptr, nullptr);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500549
550 /* select the last used cm */
551 if (!cms.isEmpty()) {
552 auto last_used_cm = cms.at(0);
553 int last_used_cm_idx = 0;
554 for (int i = 1; i < cms.size(); ++i) {
555 auto new_cm = cms.at(i);
556 if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0) {
557 last_used_cm = new_cm;
558 last_used_cm_idx = i;
559 }
560 }
561
562 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_cm), last_used_cm_idx);
Guillaume Roguezd35d6492017-06-26 16:40:09 -0400563
564 if (last_used_cm->isConfirmed())
565 gtk_widget_hide(priv->button_send_invitation);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500566 }
567
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400568 /* if there is only one CM, make the combo box insensitive */
AmarOkcba03952017-07-17 10:13:49 -0400569 if (cms.size() < 2) {
570 auto active = cms.at(gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm)));
571 std::string ring_id = active->roleData(static_cast<int>(Ring::Role::Number)).toString().toUtf8().constData();
572 std::string displayed = gtk_label_get_text(GTK_LABEL(priv->label_peer));
573 gtk_label_set_text(GTK_LABEL(priv->label_cm), ring_id.c_str());
574 gtk_widget_hide(priv->combobox_cm);
575 if (displayed == ring_id) {
576 gtk_widget_hide(priv->label_cm);
577 } else {
578 gtk_widget_show(priv->label_cm);
579 }
580 } else {
581 gtk_widget_show(priv->combobox_cm);
582 gtk_widget_hide(priv->label_cm);
583 }
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400584
585 /* if no CMs make the call button insensitive */
586 gtk_widget_set_sensitive(priv->button_placecall, !cms.isEmpty());
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500587}
588
589static void
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400590update_name(ChatView *self)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500591{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500592 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
593
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500594 g_return_if_fail(priv->person || priv->cm || priv->call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500595
596 QString name;
597 if (priv->person) {
598 name = priv->person->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500599 } else if (priv->cm) {
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500600 name = priv->cm->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500601 } else if (priv->call) {
602 name = priv->call->peerContactMethod()->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500603 }
604 gtk_label_set_text(GTK_LABEL(priv->label_peer), name.toUtf8().constData());
605}
606
aviau039001d2016-09-29 16:39:05 -0400607static void
aviau039001d2016-09-29 16:39:05 -0400608webkit_chat_container_ready(ChatView* self)
609{
610 /* The webkit chat container has loaded the javascript libraries, we can
611 * now use it. */
612
613 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
614
615 webkit_chat_container_clear(
616 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
617 );
618
Sébastien Blin70dc0b72017-07-31 16:24:41 -0400619 display_links_toggled(self);
620
aviau039001d2016-09-29 16:39:05 -0400621 /* print the text recordings */
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500622 if (priv->call) {
623 print_text_recording(priv->call->peerContactMethod()->textRecording(), self);
624 } else if (priv->cm) {
625 print_text_recording(priv->cm->textRecording(), self);
626 } else if (priv->person) {
627 /* get the selected cm and print the text recording */
628 selected_cm_changed(self);
aviau039001d2016-09-29 16:39:05 -0400629 }
aviau039001d2016-09-29 16:39:05 -0400630}
631
632static void
633build_chat_view(ChatView* self)
634{
635 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
636
637 gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container);
638 gtk_widget_show(priv->webkit_chat_container);
639
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500640 /* keep name updated */
641 if (priv->call) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400642 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500643 priv->call,
644 &Call::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400645 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500646 );
647 } else if (priv->cm) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400648 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500649 priv->cm,
650 &ContactMethod::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400651 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500652 );
653 } else if (priv->person) {
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400654 priv->update_name = QObject::connect(
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500655 priv->person,
656 &Person::changed,
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400657 [self] () { update_name(self); }
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500658 );
659 }
Sébastien Blinf3681aa2017-07-19 16:53:49 -0400660
661 priv->update_send_invitation = QObject::connect(
662 get_active_contactmethod(self),
663 &ContactMethod::changed,
664 [self] () { update_send_invitation(self); }
665 );
666
667 update_name(self);
668 update_send_invitation(self);
aviau039001d2016-09-29 16:39:05 -0400669
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500670 /* keep selected cm updated */
671 update_contact_methods(self);
672 g_signal_connect_swapped(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self);
673
674 priv->webkit_ready = g_signal_connect_swapped(
aviau039001d2016-09-29 16:39:05 -0400675 priv->webkit_chat_container,
676 "ready",
677 G_CALLBACK(webkit_chat_container_ready),
678 self
679 );
680
AmarOkb4253242017-07-13 11:21:39 -0400681 priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
682 "send-message",
683 G_CALLBACK(webkit_chat_container_send_text),
684 self);
685
aviau039001d2016-09-29 16:39:05 -0400686 if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
aviau039001d2016-09-29 16:39:05 -0400687 webkit_chat_container_ready(self);
aviau039001d2016-09-29 16:39:05 -0400688
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500689 /* we only show the chat info in the case of cm / person */
690 gtk_widget_set_visible(priv->hbox_chat_info, (priv->cm || priv->person));
aviau039001d2016-09-29 16:39:05 -0400691}
692
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500693GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400694chat_view_new_call(WebKitChatContainer *webkit_chat_container, Call *call)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500695{
696 g_return_val_if_fail(call, nullptr);
697
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500698 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500699
aviau039001d2016-09-29 16:39:05 -0400700 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
701 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500702 priv->call = call;
aviau039001d2016-09-29 16:39:05 -0400703
704 build_chat_view(self);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500705
706 return (GtkWidget *)self;
707}
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500708
709GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400710chat_view_new_cm(WebKitChatContainer *webkit_chat_container, ContactMethod *cm)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500711{
712 g_return_val_if_fail(cm, nullptr);
713
714 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500715
aviau039001d2016-09-29 16:39:05 -0400716 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
717 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500718 priv->cm = cm;
aviau039001d2016-09-29 16:39:05 -0400719
720 build_chat_view(self);
721
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500722 return (GtkWidget *)self;
723}
724
725GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400726chat_view_new_person(WebKitChatContainer *webkit_chat_container, Person *p)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500727{
728 g_return_val_if_fail(p, nullptr);
729
730 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500731
aviau039001d2016-09-29 16:39:05 -0400732 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
733 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500734 priv->person = p;
735
aviau039001d2016-09-29 16:39:05 -0400736 build_chat_view(self);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500737
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500738 return (GtkWidget *)self;
739}
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400740
741Call*
742chat_view_get_call(ChatView *self)
743{
744 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
745 auto priv = CHAT_VIEW_GET_PRIVATE(self);
746
747 return priv->call;
748}
749
750ContactMethod*
751chat_view_get_cm(ChatView *self)
752{
753 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
754 auto priv = CHAT_VIEW_GET_PRIVATE(self);
755
756 return priv->cm;
757}
758
759Person*
760chat_view_get_person(ChatView *self)
761{
762 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
763 auto priv = CHAT_VIEW_GET_PRIVATE(self);
764
765 return priv->person;
766}
Stepan Salenikovichdaf3cb32016-10-12 16:39:42 -0400767
768void
769chat_view_set_header_visible(ChatView *self, gboolean visible)
770{
771 auto priv = CHAT_VIEW_GET_PRIVATE(self);
772
773 gtk_widget_set_visible(priv->hbox_chat_info, visible);
774}