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;
}
Пример #7
0
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);
            }
        }
    );
}
Пример #10
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;
    }
}
Пример #12
0
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;
}
Пример #13
0
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;
}
Пример #17
0
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;
}
Пример #18
0
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");
    }
}
Пример #20
0
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));
}
Пример #22
0
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);
            }
        }
    );
}
Пример #23
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), "");
    }
}