void chat_view_set_header_visible(ChatView *self, gboolean visible) { auto priv = CHAT_VIEW_GET_PRIVATE(self); gtk_widget_set_visible(priv->hbox_chat_info, visible); }
static void chat_view_dispose(GObject *object) { ChatView *view; ChatViewPrivate *priv; view = CHAT_VIEW(object); priv = CHAT_VIEW_GET_PRIVATE(view); QObject::disconnect(priv->new_message_connection); QObject::disconnect(priv->message_changed_connection); QObject::disconnect(priv->update_name); /* Destroying the box will also destroy its children, and we wouldn't * want that. So we remove the webkit_chat_container from the box. */ if (priv->webkit_chat_container) { /* disconnect for webkit signals */ g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready); priv->webkit_ready = 0; gtk_container_remove( GTK_CONTAINER(priv->box_webkit_chat_container), GTK_WIDGET(priv->webkit_chat_container) ); priv->webkit_chat_container = nullptr; } G_OBJECT_CLASS(chat_view_parent_class)->dispose(object); }
static void set_participant_images(ChatView* self) { ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); webkit_chat_container_clear_sender_images( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container) ); /* Set the sender image for the peer */ ContactMethod* sender_contact_method_peer; QVariant photo_variant_peer; if (priv->person) { photo_variant_peer = priv->person->photo(); sender_contact_method_peer = get_active_contactmethod(self); } else { if (priv->cm) { sender_contact_method_peer = priv->cm; } else { sender_contact_method_peer = priv->call->peerContactMethod(); } photo_variant_peer = sender_contact_method_peer->roleData((int) Call::Role::Photo); } if (photo_variant_peer.isValid()) { webkit_chat_container_set_sender_image( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), sender_contact_method_peer, photo_variant_peer ); } /* set sender image for "ME" */ auto profile = ProfileModel::instance().selectedProfile(); if (profile) { auto person = profile->person(); if (person) { auto photo_variant_me = person->photo(); if (photo_variant_me.isValid()) { webkit_chat_container_set_sender_image( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), nullptr, photo_variant_me ); } } } }
Call* chat_view_get_call(ChatView *self) { g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr); auto priv = CHAT_VIEW_GET_PRIVATE(self); return priv->call; }
ContactMethod* chat_view_get_cm(ChatView *self) { g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr); auto priv = CHAT_VIEW_GET_PRIVATE(self); return priv->cm; }
Person* chat_view_get_person(ChatView *self) { g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr); auto priv = CHAT_VIEW_GET_PRIVATE(self); return priv->person; }
static void update_contact_methods(ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); g_return_if_fail(priv->person); /* model for the combobox for the choice of ContactMethods */ auto cm_model = gtk_list_store_new( 1, G_TYPE_POINTER ); auto cms = priv->person->phoneNumbers(); for (int i = 0; i < cms.size(); ++i) { GtkTreeIter iter; gtk_list_store_append(cm_model, &iter); gtk_list_store_set(cm_model, &iter, 0, cms.at(i), -1); } gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_cm), GTK_TREE_MODEL(cm_model)); g_object_unref(cm_model); auto renderer = gtk_cell_renderer_text_new(); g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_cm), renderer, FALSE); gtk_cell_layout_set_cell_data_func( GTK_CELL_LAYOUT(priv->combobox_cm), renderer, (GtkCellLayoutDataFunc)render_contact_method, nullptr, nullptr); /* select the last used cm */ if (!cms.isEmpty()) { auto last_used_cm = cms.at(0); int last_used_cm_idx = 0; for (int i = 1; i < cms.size(); ++i) { auto new_cm = cms.at(i); if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0) { last_used_cm = new_cm; last_used_cm_idx = i; } } gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_cm), last_used_cm_idx); } /* show the combo box if there is more than one cm to choose from */ if (cms.size() > 1) gtk_widget_show_all(priv->combobox_cm); else gtk_widget_hide(priv->combobox_cm); }
static void chat_view_init(ChatView *view) { gtk_widget_init_template(GTK_WIDGET(view)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view); g_signal_connect(priv->button_chat_input, "clicked", G_CALLBACK(send_chat), view); g_signal_connect(priv->entry_chat_input, "activate", G_CALLBACK(send_chat), view); g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view); g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view); }
static void print_text_recording(Media::TextRecording *recording, ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); /* set the photos of the chat participants */ set_participant_images(self); /* only text messages are supported for now */ auto model = recording->instantTextMessagingModel(); /* new model, disconnect from the old model updates and clear the text buffer */ QObject::disconnect(priv->new_message_connection); /* put all the messages in the im model into the text view */ for (int row = 0; row < model->rowCount(); ++row) { QModelIndex idx = model->index(row, 0); print_message_to_buffer(self, idx); } /* mark all messages as read */ recording->setAllRead(); /* messages modified */ /* connecting on instantMessagingModel and not textMessagingModel */ priv->message_changed_connection = QObject::connect( model, &QAbstractItemModel::dataChanged, [self, priv] (const QModelIndex & topLeft, G_GNUC_UNUSED const QModelIndex & bottomRight) { webkit_chat_container_update_message( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), topLeft ); } ); /* append new messages */ priv->new_message_connection = QObject::connect( model, &QAbstractItemModel::rowsInserted, [self, priv, model] (const QModelIndex &parent, int first, int last) { for (int row = first; row <= last; ++row) { QModelIndex idx = model->index(row, 0, parent); print_message_to_buffer(self, idx); /* make sure these messages are marked as read */ model->setData(idx, true, static_cast<int>(Media::TextRecording::Role::IsRead)); g_signal_emit(G_OBJECT(self), chat_view_signals[NEW_MESSAGES_DISPLAYED], 0); } } ); }
static void chat_view_dispose(GObject *object) { ChatView *view; ChatViewPrivate *priv; view = CHAT_VIEW(object); priv = CHAT_VIEW_GET_PRIVATE(view); QObject::disconnect(priv->new_message_connection); G_OBJECT_CLASS(chat_view_parent_class)->dispose(object); }
ContactMethod* get_active_contactmethod(ChatView *self) { ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); auto cms = priv->person->phoneNumbers(); auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm)); if (active >= 0 && active < cms.size()) { return cms.at(active); } else { return nullptr; } }
GtkWidget * chat_view_new_call(Call *call) { g_return_val_if_fail(call, nullptr); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); priv->call = call; auto cm = priv->call->peerContactMethod(); print_text_recording(cm->textRecording(), self); return (GtkWidget *)self; }
static void selected_cm_changed(GtkComboBox *box, ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); auto cms = priv->person->phoneNumbers(); auto active = gtk_combo_box_get_active(box); if (active >= 0 && active < cms.size()) { print_text_recording(cms.at(active)->textRecording(), self); } else { g_warning("no valid ContactMethod selected to display chat conversation"); } }
GtkWidget * chat_view_new_person(WebKitChatContainer *webkit_chat_container, Person *p) { g_return_val_if_fail(p, nullptr); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container); priv->person = p; build_chat_view(self); return (GtkWidget *)self; }
static void print_message_to_buffer(ChatView* self, const QModelIndex &idx) { if (!idx.isValid()) { g_warning("QModelIndex in im model is not valid"); return; } ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); webkit_chat_container_print_new_message( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), idx ); }
GtkWidget * chat_view_new_cm(WebKitChatContainer *webkit_chat_container, ContactMethod *cm) { g_return_val_if_fail(cm, nullptr); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container); priv->cm = cm; build_chat_view(self); return (GtkWidget *)self; }
GtkWidget * chat_view_new_cm(ContactMethod *cm) { g_return_val_if_fail(cm, nullptr); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); priv->cm = cm; print_text_recording(priv->cm->textRecording(), self); update_name(self); gtk_widget_show(priv->hbox_chat_info); return (GtkWidget *)self; }
static void chat_view_init(ChatView *view) { gtk_widget_init_template(GTK_WIDGET(view)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view); g_signal_connect(priv->button_chat_input, "clicked", G_CALLBACK(send_chat), view); g_signal_connect(priv->entry_chat_input, "activate", G_CALLBACK(send_chat), view); /* the adjustment params will change only when the model is created and when * new messages are added; in these cases we want to scroll to the bottom of * the chat treeview */ GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(priv->scrolledwindow_chat)); g_signal_connect(adjustment, "changed", G_CALLBACK(scroll_to_bottom), NULL); }
static void selected_cm_changed(ChatView *self) { auto priv = CHAT_VIEW_GET_PRIVATE(self); /* make sure the webkit view is ready, in case we get this signal before it is */ if (!webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container))) return; auto cm = get_active_contactmethod(self); if (cm){ print_text_recording(cm->textRecording(), self); } else { g_warning("no valid ContactMethod selected to display chat conversation"); } }
static void update_name(ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); g_return_if_fail(priv->person || priv->cm); QString name; if (priv->person) { name = priv->person->roleData(static_cast<int>(Ring::Role::Name)).toString(); } else { name = priv->cm->roleData(static_cast<int>(Ring::Role::Name)).toString(); } gtk_label_set_text(GTK_LABEL(priv->label_peer), name.toUtf8().constData()); }
static void build_chat_view(ChatView* self) { ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container); gtk_widget_show(priv->webkit_chat_container); /* keep name updated */ if (priv->call) { priv->update_name = QObject::connect( priv->call, &Call::changed, [self] () { update_name(self); } ); } else if (priv->cm) { priv->update_name = QObject::connect( priv->cm, &ContactMethod::changed, [self] () { update_name(self); } ); } else if (priv->person) { priv->update_name = QObject::connect( priv->person, &Person::changed, [self] () { update_name(self); } ); } update_name(self); /* keep selected cm updated */ update_contact_methods(self); g_signal_connect_swapped(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self); priv->webkit_ready = g_signal_connect_swapped( priv->webkit_chat_container, "ready", G_CALLBACK(webkit_chat_container_ready), self ); if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container))) webkit_chat_container_ready(self); /* we only show the chat info in the case of cm / person */ gtk_widget_set_visible(priv->hbox_chat_info, (priv->cm || priv->person)); }
static void print_text_recording(Media::TextRecording *recording, ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); /* only text messages are supported for now */ auto model = recording->instantTextMessagingModel(); /* new model, disconnect from the old model updates and clear the text buffer */ QObject::disconnect(priv->new_message_connection); GtkTextBuffer *new_buffer = gtk_text_buffer_new(NULL); gtk_text_view_set_buffer(GTK_TEXT_VIEW(priv->textview_chat), new_buffer); /* add tags to the buffer */ gtk_text_buffer_create_tag(new_buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL); g_object_unref(new_buffer); /* put all the messages in the im model into the text view */ for (int row = 0; row < model->rowCount(); ++row) { QModelIndex idx = model->index(row, 0); print_message_to_buffer(idx, new_buffer); } /* mark all messages as read */ recording->setAllRead(); /* append new messages */ priv->new_message_connection = QObject::connect( model, &QAbstractItemModel::rowsInserted, [self, priv, model] (const QModelIndex &parent, int first, int last) { for (int row = first; row <= last; ++row) { QModelIndex idx = model->index(row, 0, parent); print_message_to_buffer(idx, gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview_chat))); /* make sure these messages are marked as read */ model->setData(idx, true, static_cast<int>(Media::TextRecording::Role::IsRead)); g_signal_emit(G_OBJECT(self), chat_view_signals[NEW_MESSAGES_DISPLAYED], 0); } } ); }
GtkWidget * chat_view_new_person(Person *p) { g_return_val_if_fail(p, nullptr); ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); priv->person = p; /* connect to the changed signal before setting the cm combo box, so that the correct * conversation will get displayed */ g_signal_connect(priv->combobox_cm, "changed", G_CALLBACK(selected_cm_changed), self); update_contact_methods(self); update_name(self); gtk_widget_show(priv->hbox_chat_info); return (GtkWidget *)self; }
static void placecall_clicked(ChatView *self) { auto priv = CHAT_VIEW_GET_PRIVATE(self); if (priv->person) { // get the chosen cm auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm)); if (active >= 0) { auto cm = priv->person->phoneNumbers().at(active); place_new_call(cm); } else { g_warning("no ContactMethod chosen; cannot place call"); } } else if (priv->cm) { place_new_call(priv->cm); } else { g_warning("no Person or ContactMethod set; cannot place call"); } }
static void webkit_chat_container_ready(ChatView* self) { /* The webkit chat container has loaded the javascript libraries, we can * now use it. */ ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); webkit_chat_container_clear( WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container) ); /* print the text recordings */ if (priv->call) { print_text_recording(priv->call->peerContactMethod()->textRecording(), self); } else if (priv->cm) { print_text_recording(priv->cm->textRecording(), self); } else if (priv->person) { /* get the selected cm and print the text recording */ selected_cm_changed(self); } }
static void send_chat(G_GNUC_UNUSED GtkWidget *widget, ChatView *self) { g_return_if_fail(IS_CHAT_VIEW(self)); ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); /* make sure there is more than just whitespace, but if so, send the original text */ const auto text = QString(gtk_entry_get_text(GTK_ENTRY(priv->entry_chat_input))); if (!text.trimmed().isEmpty()) { QMap<QString, QString> messages; messages["text/plain"] = text; if (priv->call) { // in call message priv->call->addOutgoingMedia<Media::Text>()->send(messages); } else if (priv->person) { // get the chosen cm auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm)); if (active >= 0) { auto cm = priv->person->phoneNumbers().at(active); if (!cm->sendOfflineTextMessage(messages)) g_warning("message failed to send"); // TODO: warn the user about this in the UI } else { g_warning("no ContactMethod chosen; message not sent"); } } else if (priv->cm) { if (!priv->cm->sendOfflineTextMessage(messages)) g_warning("message failed to send"); // TODO: warn the user about this in the UI } else { g_warning("no Call, Person, or ContactMethod set; message not sent"); } /* clear the entry */ gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), ""); } }