blob: dc6da250fd91245302b62c1208b1847e4a38f2c5 [file] [log] [blame]
Stepan Salenikovichd2cad062016-01-08 13:43:49 -05001/*
2 * Copyright (C) 2016 Savoir-faire Linux Inc.
3 * 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"
aviau039001d2016-09-29 16:39:05 -040037#include "webkitchatcontainer.h"
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050038
Nicolas Jager759faea2017-03-22 16:22:00 -040039// LRC
40#include <account.h>
41
42
Stepan Salenikovichce06adb2016-02-19 12:53:53 -050043static constexpr GdkRGBA RING_BLUE = {0.0508, 0.594, 0.676, 1.0}; // outgoing msg color: (13, 152, 173)
44
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050045struct _ChatView
46{
47 GtkBox parent;
48};
49
50struct _ChatViewClass
51{
52 GtkBoxClass parent_class;
53};
54
55typedef struct _ChatViewPrivate ChatViewPrivate;
56
57struct _ChatViewPrivate
58{
aviau039001d2016-09-29 16:39:05 -040059 GtkWidget *box_webkit_chat_container;
60 GtkWidget *webkit_chat_container;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050061 GtkWidget *button_chat_input;
62 GtkWidget *entry_chat_input;
63 GtkWidget *scrolledwindow_chat;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050064 GtkWidget *hbox_chat_info;
65 GtkWidget *label_peer;
66 GtkWidget *combobox_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
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050071 /* only one of the three following pointers should be non void;
72 * either this is an in-call chat (and so the in-call chat APIs will be used)
aviau039001d2016-09-29 16:39:05 -040073 * or it is an out of call chat (and so the account chat APIs will be used) */
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050074 Call *call;
75 Person *person;
76 ContactMethod *cm;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050077
78 QMetaObject::Connection new_message_connection;
aviau039001d2016-09-29 16:39:05 -040079 QMetaObject::Connection message_changed_connection;
Stepan Salenikovich1f731212016-11-10 11:55:10 -050080 QMetaObject::Connection update_name;
81
82 gulong webkit_ready;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050083};
84
85G_DEFINE_TYPE_WITH_PRIVATE(ChatView, chat_view, GTK_TYPE_BOX);
86
87#define CHAT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CHAT_VIEW_TYPE, ChatViewPrivate))
88
89enum {
90 NEW_MESSAGES_DISPLAYED,
Stepan Salenikovich8043a562016-03-18 13:56:40 -040091 HIDE_VIEW_CLICKED,
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050092 LAST_SIGNAL
93};
94
95static guint chat_view_signals[LAST_SIGNAL] = { 0 };
96
97static void
98chat_view_dispose(GObject *object)
99{
100 ChatView *view;
101 ChatViewPrivate *priv;
102
103 view = CHAT_VIEW(object);
104 priv = CHAT_VIEW_GET_PRIVATE(view);
105
106 QObject::disconnect(priv->new_message_connection);
aviau039001d2016-09-29 16:39:05 -0400107 QObject::disconnect(priv->message_changed_connection);
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500108 QObject::disconnect(priv->update_name);
aviau039001d2016-09-29 16:39:05 -0400109
110 /* Destroying the box will also destroy its children, and we wouldn't
111 * want that. So we remove the webkit_chat_container from the box. */
112 if (priv->webkit_chat_container) {
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500113 /* disconnect for webkit signals */
114 g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
115 priv->webkit_ready = 0;
116
aviau039001d2016-09-29 16:39:05 -0400117 gtk_container_remove(
118 GTK_CONTAINER(priv->box_webkit_chat_container),
119 GTK_WIDGET(priv->webkit_chat_container)
120 );
121 priv->webkit_chat_container = nullptr;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500122
aviau039001d2016-09-29 16:39:05 -0400123 }
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500124
125 G_OBJECT_CLASS(chat_view_parent_class)->dispose(object);
126}
127
128
129static void
130send_chat(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
131{
132 g_return_if_fail(IS_CHAT_VIEW(self));
133 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
134
Stepan Salenikovich4fbc3bf2016-11-03 15:01:10 -0400135 /* make sure there is more than just whitespace, but if so, send the original text */
136 const auto text = QString(gtk_entry_get_text(GTK_ENTRY(priv->entry_chat_input)));
137 if (!text.trimmed().isEmpty()) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500138 QMap<QString, QString> messages;
139 messages["text/plain"] = text;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500140
141 if (priv->call) {
142 // in call message
143 priv->call->addOutgoingMedia<Media::Text>()->send(messages);
144 } else if (priv->person) {
145 // get the chosen cm
146 auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
147 if (active >= 0) {
148 auto cm = priv->person->phoneNumbers().at(active);
149 if (!cm->sendOfflineTextMessage(messages))
150 g_warning("message failed to send"); // TODO: warn the user about this in the UI
151 } else {
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400152 g_warning("no ContactMethod chosen; message not sent");
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500153 }
154 } else if (priv->cm) {
155 if (!priv->cm->sendOfflineTextMessage(messages))
156 g_warning("message failed to send"); // TODO: warn the user about this in the UI
157 } else {
158 g_warning("no Call, Person, or ContactMethod set; message not sent");
159 }
160
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500161 /* clear the entry */
162 gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), "");
163 }
164}
165
166static void
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400167hide_chat_view(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
168{
169 g_signal_emit(G_OBJECT(self), chat_view_signals[HIDE_VIEW_CLICKED], 0);
170}
171
172static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400173placecall_clicked(ChatView *self)
174{
175 auto priv = CHAT_VIEW_GET_PRIVATE(self);
176
177 if (priv->person) {
178 // get the chosen cm
179 auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
180 if (active >= 0) {
181 auto cm = priv->person->phoneNumbers().at(active);
182 place_new_call(cm);
183 } else {
184 g_warning("no ContactMethod chosen; cannot place call");
185 }
186 } else if (priv->cm) {
187 place_new_call(priv->cm);
188 } else {
189 g_warning("no Person or ContactMethod set; cannot place call");
190 }
191}
192
193static void
Nicolas Jager759faea2017-03-22 16:22:00 -0400194button_send_invitation_clicked(ChatView *self)
195{
196 auto priv = CHAT_VIEW_GET_PRIVATE(self);
197
198 // get the account associated to the selected cm
199 auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
200
201 if (priv->person)
202 priv->cm = priv->person->phoneNumbers().at(active);
203
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
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500230chat_view_init(ChatView *view)
231{
232 gtk_widget_init_template(GTK_WIDGET(view));
233
234 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
235
236 g_signal_connect(priv->button_chat_input, "clicked", G_CALLBACK(send_chat), view);
237 g_signal_connect(priv->entry_chat_input, "activate", G_CALLBACK(send_chat), view);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400238 g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400239 g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
Nicolas Jager759faea2017-03-22 16:22:00 -0400240 g_signal_connect_swapped(priv->button_send_invitation, "clicked", G_CALLBACK(button_send_invitation_clicked), view);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500241}
242
243static void
244chat_view_class_init(ChatViewClass *klass)
245{
246 G_OBJECT_CLASS(klass)->dispose = chat_view_dispose;
247
248 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
249 "/cx/ring/RingGnome/chatview.ui");
250
aviau039001d2016-09-29 16:39:05 -0400251 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500252 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_chat_input);
253 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, entry_chat_input);
254 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, scrolledwindow_chat);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500255 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, hbox_chat_info);
256 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
257 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, combobox_cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400258 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400259 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
Nicolas Jager759faea2017-03-22 16:22:00 -0400260 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_send_invitation);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500261
262 chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
263 "new-messages-displayed",
264 G_TYPE_FROM_CLASS(klass),
265 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
266 0,
267 nullptr,
268 nullptr,
269 g_cclosure_marshal_VOID__VOID,
270 G_TYPE_NONE, 0);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400271
272 chat_view_signals[HIDE_VIEW_CLICKED] = g_signal_new (
273 "hide-view-clicked",
274 G_TYPE_FROM_CLASS(klass),
275 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
276 0,
277 nullptr,
278 nullptr,
279 g_cclosure_marshal_VOID__VOID,
280 G_TYPE_NONE, 0);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500281}
282
aviau039001d2016-09-29 16:39:05 -0400283
284
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500285static void
aviau039001d2016-09-29 16:39:05 -0400286print_message_to_buffer(ChatView* self, const QModelIndex &idx)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500287{
aviau039001d2016-09-29 16:39:05 -0400288 if (!idx.isValid()) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500289 g_warning("QModelIndex in im model is not valid");
aviau039001d2016-09-29 16:39:05 -0400290 return;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500291 }
aviau039001d2016-09-29 16:39:05 -0400292
293 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
294
295 webkit_chat_container_print_new_message(
296 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
297 idx
298 );
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500299}
300
aviaufc213552016-11-01 12:39:39 -0400301ContactMethod*
302get_active_contactmethod(ChatView *self)
303{
304 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
305
306 auto cms = priv->person->phoneNumbers();
307 auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
308 if (active >= 0 && active < cms.size()) {
309 return cms.at(active);
310 } else {
311 return nullptr;
312 }
313}
314
315static void
316set_participant_images(ChatView* self)
317{
318 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
319
320 webkit_chat_container_clear_sender_images(
321 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
322 );
323
324 /* Set the sender image for the peer */
325 ContactMethod* sender_contact_method_peer;
326 QVariant photo_variant_peer;
327
328 if (priv->person)
329 {
330 photo_variant_peer = priv->person->photo();
331 sender_contact_method_peer = get_active_contactmethod(self);
332 }
333 else
334 {
335 if (priv->cm)
336 {
337 sender_contact_method_peer = priv->cm;
338 }
339 else
340 {
341 sender_contact_method_peer = priv->call->peerContactMethod();
342 }
343 photo_variant_peer = sender_contact_method_peer->roleData((int) Call::Role::Photo);
344 }
345
346 if (photo_variant_peer.isValid())
347 {
348 webkit_chat_container_set_sender_image(
349 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
350 sender_contact_method_peer,
351 photo_variant_peer
352 );
353 }
354
355 /* set sender image for "ME" */
356 auto profile = ProfileModel::instance().selectedProfile();
357 if (profile)
358 {
359 auto person = profile->person();
360 if (person)
361 {
362 auto photo_variant_me = person->photo();
363 if (photo_variant_me.isValid())
364 {
365 webkit_chat_container_set_sender_image(
366 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
367 nullptr,
368 photo_variant_me
369 );
370 }
371 }
372 }
373}
374
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500375static void
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500376print_text_recording(Media::TextRecording *recording, ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500377{
378 g_return_if_fail(IS_CHAT_VIEW(self));
379 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
380
aviaufc213552016-11-01 12:39:39 -0400381 /* set the photos of the chat participants */
382 set_participant_images(self);
383
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500384 /* only text messages are supported for now */
385 auto model = recording->instantTextMessagingModel();
386
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500387 /* new model, disconnect from the old model updates and clear the text buffer */
388 QObject::disconnect(priv->new_message_connection);
389
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500390 /* put all the messages in the im model into the text view */
391 for (int row = 0; row < model->rowCount(); ++row) {
392 QModelIndex idx = model->index(row, 0);
aviau039001d2016-09-29 16:39:05 -0400393 print_message_to_buffer(self, idx);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500394 }
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500395 /* mark all messages as read */
396 recording->setAllRead();
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500397
aviau039001d2016-09-29 16:39:05 -0400398
399 /* messages modified */
400 /* connecting on instantMessagingModel and not textMessagingModel */
401 priv->message_changed_connection = QObject::connect(
402 model,
403 &QAbstractItemModel::dataChanged,
404 [self, priv] (const QModelIndex & topLeft, G_GNUC_UNUSED const QModelIndex & bottomRight)
405 {
406 webkit_chat_container_update_message(
407 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
408 topLeft
409 );
410 }
411 );
412
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500413 /* append new messages */
414 priv->new_message_connection = QObject::connect(
415 model,
416 &QAbstractItemModel::rowsInserted,
417 [self, priv, model] (const QModelIndex &parent, int first, int last) {
418 for (int row = first; row <= last; ++row) {
419 QModelIndex idx = model->index(row, 0, parent);
aviau039001d2016-09-29 16:39:05 -0400420 print_message_to_buffer(self, idx);
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500421 /* make sure these messages are marked as read */
422 model->setData(idx, true, static_cast<int>(Media::TextRecording::Role::IsRead));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500423 g_signal_emit(G_OBJECT(self), chat_view_signals[NEW_MESSAGES_DISPLAYED], 0);
424 }
425 }
426 );
427}
428
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500429static void
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500430selected_cm_changed(ChatView *self)
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500431{
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500432 auto priv = CHAT_VIEW_GET_PRIVATE(self);
433
434 /* make sure the webkit view is ready, in case we get this signal before it is */
435 if (!webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
436 return;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500437
aviaufc213552016-11-01 12:39:39 -0400438 auto cm = get_active_contactmethod(self);
439 if (cm){
440 print_text_recording(cm->textRecording(), self);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500441 } else {
442 g_warning("no valid ContactMethod selected to display chat conversation");
443 }
444}
445
446static void
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500447render_contact_method(G_GNUC_UNUSED GtkCellLayout *cell_layout,
448 GtkCellRenderer *cell,
449 GtkTreeModel *model,
450 GtkTreeIter *iter,
451 G_GNUC_UNUSED gpointer data)
452{
453 GValue value = G_VALUE_INIT;
454 gtk_tree_model_get_value(model, iter, 0, &value);
455 auto cm = (ContactMethod *)g_value_get_pointer(&value);
456
457 gchar *number = nullptr;
458 if (cm && cm->category()) {
459 // try to get the number category, eg: "home"
460 number = g_strdup_printf("(%s) %s", cm->category()->name().toUtf8().constData(),
Nicolas Jager153b7652017-01-17 10:06:54 -0500461 cm->getBestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500462 } else if (cm) {
Nicolas Jager153b7652017-01-17 10:06:54 -0500463 number = g_strdup_printf("%s", cm->getBestId().toUtf8().constData());
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500464 }
465
466 g_object_set(G_OBJECT(cell), "text", number, NULL);
467 g_free(number);
468}
469
470static void
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500471update_contact_methods(ChatView *self)
472{
473 g_return_if_fail(IS_CHAT_VIEW(self));
474 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
475
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500476 g_return_if_fail(priv->call || priv->person || priv->cm);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500477
478 /* model for the combobox for the choice of ContactMethods */
479 auto cm_model = gtk_list_store_new(
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500480 1, G_TYPE_POINTER
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500481 );
482
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400483 Person::ContactMethods cms;
484
485 if (priv->person)
486 cms = priv->person->phoneNumbers();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500487 else if (priv->cm)
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400488 cms << priv->cm;
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500489 else if (priv->call)
490 cms << priv->call->peerContactMethod();
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400491
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500492 for (int i = 0; i < cms.size(); ++i) {
493 GtkTreeIter iter;
494 gtk_list_store_append(cm_model, &iter);
495 gtk_list_store_set(cm_model, &iter,
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500496 0, cms.at(i),
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500497 -1);
498 }
499
500 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_cm), GTK_TREE_MODEL(cm_model));
501 g_object_unref(cm_model);
502
503 auto renderer = gtk_cell_renderer_text_new();
504 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
505 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, FALSE);
Stepan Salenikovich5039c9b2016-02-12 14:09:51 -0500506 gtk_cell_layout_set_cell_data_func(
507 GTK_CELL_LAYOUT(priv->combobox_cm),
508 renderer,
509 (GtkCellLayoutDataFunc)render_contact_method,
510 nullptr, nullptr);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500511
512 /* select the last used cm */
513 if (!cms.isEmpty()) {
514 auto last_used_cm = cms.at(0);
515 int last_used_cm_idx = 0;
516 for (int i = 1; i < cms.size(); ++i) {
517 auto new_cm = cms.at(i);
518 if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0) {
519 last_used_cm = new_cm;
520 last_used_cm_idx = i;
521 }
522 }
523
524 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_cm), last_used_cm_idx);
525 }
526
Stepan Salenikovichbbfa4402016-05-04 15:07:29 -0400527 /* if there is only one CM, make the combo box insensitive */
528 if (cms.size() < 2)
529 gtk_widget_set_sensitive(priv->combobox_cm, FALSE);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400530
531 /* if no CMs make the call button insensitive */
532 gtk_widget_set_sensitive(priv->button_placecall, !cms.isEmpty());
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500533}
534
535static void
536update_name(ChatView *self)
537{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500538 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
539
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500540 g_return_if_fail(priv->person || priv->cm || priv->call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500541
542 QString name;
543 if (priv->person) {
544 name = priv->person->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500545 } else if (priv->cm) {
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500546 name = priv->cm->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500547 } else if (priv->call) {
548 name = priv->call->peerContactMethod()->roleData(static_cast<int>(Ring::Role::Name)).toString();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500549 }
550 gtk_label_set_text(GTK_LABEL(priv->label_peer), name.toUtf8().constData());
551}
552
aviau039001d2016-09-29 16:39:05 -0400553static void
aviau039001d2016-09-29 16:39:05 -0400554webkit_chat_container_ready(ChatView* self)
555{
556 /* The webkit chat container has loaded the javascript libraries, we can
557 * now use it. */
558
559 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
560
561 webkit_chat_container_clear(
562 WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
563 );
564
aviau039001d2016-09-29 16:39:05 -0400565 /* print the text recordings */
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500566 if (priv->call) {
567 print_text_recording(priv->call->peerContactMethod()->textRecording(), self);
568 } else if (priv->cm) {
569 print_text_recording(priv->cm->textRecording(), self);
570 } else if (priv->person) {
571 /* get the selected cm and print the text recording */
572 selected_cm_changed(self);
aviau039001d2016-09-29 16:39:05 -0400573 }
aviau039001d2016-09-29 16:39:05 -0400574}
575
576static void
577build_chat_view(ChatView* self)
578{
579 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
580
581 gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container);
582 gtk_widget_show(priv->webkit_chat_container);
583
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500584 /* keep name updated */
585 if (priv->call) {
586 priv->update_name = QObject::connect(
587 priv->call,
588 &Call::changed,
589 [self] () { update_name(self); }
590 );
591 } else if (priv->cm) {
592 priv->update_name = QObject::connect(
593 priv->cm,
594 &ContactMethod::changed,
595 [self] () { update_name(self); }
596 );
597 } else if (priv->person) {
598 priv->update_name = QObject::connect(
599 priv->person,
600 &Person::changed,
601 [self] () { update_name(self); }
602 );
603 }
604 update_name(self);
aviau039001d2016-09-29 16:39:05 -0400605
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500606 /* keep selected cm updated */
607 update_contact_methods(self);
608 g_signal_connect_swapped(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self);
609
610 priv->webkit_ready = g_signal_connect_swapped(
aviau039001d2016-09-29 16:39:05 -0400611 priv->webkit_chat_container,
612 "ready",
613 G_CALLBACK(webkit_chat_container_ready),
614 self
615 );
616
617 if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
aviau039001d2016-09-29 16:39:05 -0400618 webkit_chat_container_ready(self);
aviau039001d2016-09-29 16:39:05 -0400619
Stepan Salenikovich1f731212016-11-10 11:55:10 -0500620 /* we only show the chat info in the case of cm / person */
621 gtk_widget_set_visible(priv->hbox_chat_info, (priv->cm || priv->person));
aviau039001d2016-09-29 16:39:05 -0400622}
623
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500624GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400625chat_view_new_call(WebKitChatContainer *webkit_chat_container, Call *call)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500626{
627 g_return_val_if_fail(call, nullptr);
628
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500629 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500630
aviau039001d2016-09-29 16:39:05 -0400631 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
632 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500633 priv->call = call;
aviau039001d2016-09-29 16:39:05 -0400634
635 build_chat_view(self);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500636
637 return (GtkWidget *)self;
638}
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500639
640GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400641chat_view_new_cm(WebKitChatContainer *webkit_chat_container, ContactMethod *cm)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500642{
643 g_return_val_if_fail(cm, nullptr);
644
645 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500646
aviau039001d2016-09-29 16:39:05 -0400647 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
648 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500649 priv->cm = cm;
aviau039001d2016-09-29 16:39:05 -0400650
651 build_chat_view(self);
652
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500653 return (GtkWidget *)self;
654}
655
656GtkWidget *
aviau039001d2016-09-29 16:39:05 -0400657chat_view_new_person(WebKitChatContainer *webkit_chat_container, Person *p)
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500658{
659 g_return_val_if_fail(p, nullptr);
660
661 ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500662
aviau039001d2016-09-29 16:39:05 -0400663 ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
664 priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500665 priv->person = p;
666
aviau039001d2016-09-29 16:39:05 -0400667 build_chat_view(self);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500668
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500669 return (GtkWidget *)self;
670}
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400671
672Call*
673chat_view_get_call(ChatView *self)
674{
675 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
676 auto priv = CHAT_VIEW_GET_PRIVATE(self);
677
678 return priv->call;
679}
680
681ContactMethod*
682chat_view_get_cm(ChatView *self)
683{
684 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
685 auto priv = CHAT_VIEW_GET_PRIVATE(self);
686
687 return priv->cm;
688}
689
690Person*
691chat_view_get_person(ChatView *self)
692{
693 g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
694 auto priv = CHAT_VIEW_GET_PRIVATE(self);
695
696 return priv->person;
697}
Stepan Salenikovichdaf3cb32016-10-12 16:39:42 -0400698
699void
700chat_view_set_header_visible(ChatView *self, gboolean visible)
701{
702 auto priv = CHAT_VIEW_GET_PRIVATE(self);
703
704 gtk_widget_set_visible(priv->hbox_chat_info, visible);
705}