Skip to content
Snippets Groups Projects
Select Git revision
  • d2faf09c362d119a5c9009827d26324b611fabdf
  • master default protected
2 results

tls_session.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    avatarmanipulation.cpp 18.59 KiB
    /*
     *  Copyright (C) 2016-2018 Savoir-faire Linux Inc.
     *  Author: Nicolas Jager <nicolas.jager@savoirfairelinux.com>
     *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
     *
     *  This program is free software; you can redistribute it and/or modify
     *  it under the terms of the GNU General Public License as published by
     *  the Free Software Foundation; either version 3 of the License, or
     *  (at your option) any later version.
     *
     *  This program is distributed in the hope that it will be useful,
     *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     *  GNU General Public License for more details.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program; if not, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #include "avatarmanipulation.h"
    
    /* LRC */
    #include <globalinstances.h>
    #include <person.h>
    #include <profile.h>
    #include <profilemodel.h>
    #include <video/configurationproxy.h>
    #include <video/previewmanager.h>
    #include <video/devicemodel.h>
    
    /* client */
    #include "native/pixbufmanipulator.h"
    #include "video/video_widget.h"
    #include "cc-crop-area.h"
    
    /* system */
    #include <glib/gi18n.h>
    
    /* size of avatar */
    static constexpr int AVATAR_WIDTH  = 100; /* px */
    static constexpr int AVATAR_HEIGHT = 100; /* px */
    
    /* size of video widget */
    static constexpr int VIDEO_WIDTH = 300; /* px */
    static constexpr int VIDEO_HEIGHT = 200; /* px */
    
    struct _AvatarManipulation
    {
        GtkBox parent;
    };
    
    struct _AvatarManipulationClass
    {
        GtkBoxClass parent_class;
    };
    
    typedef struct _AvatarManipulationPrivate AvatarManipulationPrivate;
    
    struct _AvatarManipulationPrivate
    {
        GtkWidget *stack_avatar_manipulation;
        GtkWidget *video_widget;
        GtkWidget *box_views_and_controls;
        GtkWidget *box_controls;
    
        GtkWidget *button_box_current;
        GtkWidget *button_box_photo;
        GtkWidget *button_box_edit;
    
        GtkWidget *button_start_camera;
        GtkWidget *button_choose_picture;
        GtkWidget *button_take_photo;
        GtkWidget *button_return_photo;
        GtkWidget *button_set_avatar;
        GtkWidget *button_return_edit;
    
        // GtkWidget *selector_widget;
        GtkWidget *stack_views;
        GtkWidget *image_avatar;
        GtkWidget *vbox_crop_area;
        GtkWidget *frame_video;
    
        AvatarManipulationState state;
        AvatarManipulationState last_state;
    
        /* this is used to keep track of the state of the preview when the camera is used to take a
         * photo; if a call is in progress, then the preview should already be started and we don't want
         * to stop it when the settings are closed, in this case
         */
        gboolean video_started_by_avatar_manipulation;
    
        GtkWidget *crop_area;
    };
    
    G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX);
    
    #define AVATAR_MANIPULATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AVATAR_MANIPULATION_TYPE, \
                                                                                                 AvatarManipulationPrivate))
    
    static void set_state(AvatarManipulation *self, AvatarManipulationState state);
    
    static void start_camera(AvatarManipulation *self);
    static void take_a_photo(AvatarManipulation *self);
    static void choose_picture(AvatarManipulation *self);
    static void return_to_previous(AvatarManipulation *self);
    static void update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
    static void set_avatar(AvatarManipulation *self);
    static void got_snapshot(AvatarManipulation *parent);
    
    static void
    avatar_manipulation_dispose(GObject *object)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
    
        /* make sure we stop the preview and the video widget */
        if (priv->video_started_by_avatar_manipulation)
            Video::PreviewManager::instance().stopPreview();
        if (priv->video_widget) {
            gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
            priv->video_widget = NULL;
        }
    
        G_OBJECT_CLASS(avatar_manipulation_parent_class)->dispose(object);
    }
    
    static void
    avatar_manipulation_finalize(GObject *object)
    {
        G_OBJECT_CLASS(avatar_manipulation_parent_class)->finalize(object);
    }
    
    GtkWidget*
    avatar_manipulation_new(void)
    {
        // a profile must exist
        g_return_val_if_fail(ProfileModel::instance().selectedProfile(), NULL);
    
        return (GtkWidget *)g_object_new(AVATAR_MANIPULATION_TYPE, NULL);
    }
    
    GtkWidget*
    avatar_manipulation_new_from_wizard(void)
    {
        auto self = avatar_manipulation_new();
    
        /* in this mode, we want to automatically go to the PHOTO avatar state, unless one already exists */
        if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid()) {
            // check if there is a camera
            if (Video::DeviceModel::instance().rowCount() > 0)
                set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_PHOTO);
        }
    
        return self;
    }
    
    static void
    avatar_manipulation_class_init(AvatarManipulationClass *klass)
    {
        G_OBJECT_CLASS(klass)->finalize = avatar_manipulation_finalize;
        G_OBJECT_CLASS(klass)->dispose = avatar_manipulation_dispose;
    
        gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass), "/cx/ring/RingGnome/avatarmanipulation.ui");
    
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_views_and_controls);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_controls);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_start_camera);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_photo);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_edit);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, stack_views);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, image_avatar);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, frame_video);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, vbox_crop_area);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_current);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_photo);
        gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_edit);
    }
    
    static void
    avatar_manipulation_init(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
        gtk_widget_init_template(GTK_WIDGET(self));
    
        /* our desired size for the image area */
        gtk_widget_set_size_request(priv->stack_views, VIDEO_WIDTH, VIDEO_HEIGHT);
    
        /* signals */
        g_signal_connect_swapped(priv->button_start_camera, "clicked", G_CALLBACK(start_camera), self);
        g_signal_connect_swapped(priv->button_choose_picture, "clicked", G_CALLBACK(choose_picture), self);
        g_signal_connect_swapped(priv->button_take_photo, "clicked", G_CALLBACK(take_a_photo), self);
        g_signal_connect_swapped(priv->button_return_photo, "clicked", G_CALLBACK(return_to_previous), self);
        g_signal_connect_swapped(priv->button_set_avatar, "clicked", G_CALLBACK(set_avatar), self);
        g_signal_connect_swapped(priv->button_return_edit, "clicked", G_CALLBACK(return_to_previous), self);
    
        set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
    
        gtk_widget_show_all(priv->stack_views);
    }
    
    static void
    set_state(AvatarManipulation *self, AvatarManipulationState state)
    {
        // note: this funciton does not check if the state transition is valid, this is assumed to have
        // been done by the caller
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
    
        // save prev state
        priv->last_state = priv->state;
    
        switch (state) {
            case AVATAR_MANIPULATION_STATE_CURRENT:
            {
                /* get the current or default profile avatar */
                auto photo = GlobalInstances::pixmapManipulator().contactPhoto(
                                ProfileModel::instance().selectedProfile()->person(),
                                QSize(AVATAR_WIDTH, AVATAR_HEIGHT),
                                false);
                std::shared_ptr<GdkPixbuf> pixbuf_photo = photo.value<std::shared_ptr<GdkPixbuf>>();
    
                if (photo.isValid()) {
                    gtk_image_set_from_pixbuf (GTK_IMAGE(priv->image_avatar),  pixbuf_photo.get());
                } else {
                    g_warning("invlid pixbuf");
                }
    
                gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_avatar");
    
                /* available actions: start camera (if available) or choose image */
                if (Video::DeviceModel::instance().rowCount() > 0) {
                    // TODO: update if a video device gets inserted while in this state
                    gtk_widget_set_visible(priv->button_start_camera, true);
                }
                gtk_widget_set_visible(priv->button_box_current, true);
                gtk_widget_set_visible(priv->button_box_photo,   false);
                gtk_widget_set_visible(priv->button_box_edit,    false);
    
                /* make sure video widget and camera is not running */
                if (priv->video_started_by_avatar_manipulation)
                    Video::PreviewManager::instance().stopPreview();
                if (priv->video_widget) {
                    gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
                    priv->video_widget = NULL;
                }
            }
            break;
            case AVATAR_MANIPULATION_STATE_PHOTO:
            {
                // start the video; if its not available we should not be in this state
                priv->video_widget = video_widget_new();
                g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
                gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
                gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
                gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
                gtk_widget_set_visible(priv->video_widget, true);
                gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
    
    
                /* local renderer, but set as "remote" so that it takes up the whole screen */
                video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
                                               Video::PreviewManager::instance().previewRenderer(),
                                               VIDEO_RENDERER_REMOTE);
    
                if (!Video::PreviewManager::instance().isPreviewing()) {
                    priv->video_started_by_avatar_manipulation = TRUE;
                    Video::PreviewManager::instance().startPreview();
                } else {
                    priv->video_started_by_avatar_manipulation = FALSE;
                }
    
                /* available actions: take snapshot, return*/
                gtk_widget_set_visible(priv->button_box_current, false);
                gtk_widget_set_visible(priv->button_box_photo,   true);
                gtk_widget_set_visible(priv->button_box_edit,    false);
            }
            break;
            case AVATAR_MANIPULATION_STATE_EDIT:
            {
                /* make sure video widget and camera is not running */
                if (priv->video_started_by_avatar_manipulation)
                    Video::PreviewManager::instance().stopPreview();
                if (priv->video_widget) {
                    gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
                    priv->video_widget = NULL;
                }
    
                /* available actions: set avatar, return */
                gtk_widget_set_visible(priv->button_box_current, false);
                gtk_widget_set_visible(priv->button_box_photo,   false);
                gtk_widget_set_visible(priv->button_box_edit,    true);
    
                gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_edit_view");
            }
            break;
        }
    
        priv->state = state;
    }
    
    static void
    start_camera(AvatarManipulation *self)
    {
        set_state(self, AVATAR_MANIPULATION_STATE_PHOTO);
    }
    
    static void
    take_a_photo(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
        video_widget_take_snapshot(VIDEO_WIDGET(priv->video_widget));
    }
    
    static void
    set_avatar(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
    
        gchar* png_buffer_signed = nullptr;
        gsize png_buffer_size;
        GError* error =  nullptr;
    
        /* get the cropped area */
        GdkPixbuf *selector_pixbuf = cc_crop_area_get_picture(CC_CROP_AREA(priv->crop_area));
    
        /* scale it */
        GdkPixbuf* pixbuf_frame_resized = gdk_pixbuf_scale_simple(selector_pixbuf, AVATAR_WIDTH, AVATAR_HEIGHT,
                                                                  GDK_INTERP_HYPER);
    
        /* save the png in memory */
        gdk_pixbuf_save_to_buffer(pixbuf_frame_resized, &png_buffer_signed, &png_buffer_size, "png", &error, NULL);
        if (!png_buffer_signed) {
            g_warning("(set_avatar) failed to save pixbuffer to png: %s\n", error->message);
            g_error_free(error);
            return;
        }
    
        /* convert buffer to QByteArray in base 64*/
        QByteArray png_q_byte_array = QByteArray::fromRawData(png_buffer_signed, png_buffer_size).toBase64();
    
        /* save in profile */
        QVariant photo = GlobalInstances::pixmapManipulator().personPhoto(png_q_byte_array);
        ProfileModel::instance().selectedProfile()->person()->setPhoto(photo);
        ProfileModel::instance().selectedProfile()->save();
    
        g_free(png_buffer_signed);
        g_object_unref(selector_pixbuf);
        g_object_unref(pixbuf_frame_resized);
    
        set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
    }
    
    static void
    return_to_previous(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
    
        if (priv->state == AVATAR_MANIPULATION_STATE_PHOTO) {
            // from photo we alway go back to current
            set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
        } else {
            // otherwise, if we were in edit state, we may have come from photo or current state
            set_state(self, priv->last_state);
        }
    }
    
    static void
    choose_picture(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
        GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
        gint res;
    
        auto preview = gtk_image_new();
    
        GtkWidget *ring_main_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
    
        auto dialog = gtk_file_chooser_dialog_new (_("Open Avatar Image"),
                                              GTK_WINDOW(ring_main_window),
                                              action,
                                              _("_Cancel"),
                                              GTK_RESPONSE_CANCEL,
                                              _("_Open"),
                                              GTK_RESPONSE_ACCEPT,
                                              NULL);
    
        /* add an image preview inside the file choose */
        gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER(dialog), preview);
        g_signal_connect (GTK_FILE_CHOOSER(dialog), "update-preview", G_CALLBACK (update_preview_cb), preview);
    
        /* start the file chooser */
        res = gtk_dialog_run (GTK_DIALOG(dialog)); /* blocks until the dialog is closed */
    
        if (res == GTK_RESPONSE_ACCEPT) {
            if(auto filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog))) {
                GError* error =  nullptr; /* initialising to null avoid trouble... */
    
                auto picture = gdk_pixbuf_new_from_file_at_size (filename, VIDEO_WIDTH, VIDEO_HEIGHT, &error);
    
                if (!error) {
                    /* reset crop area */
                    if (priv->crop_area)
                        gtk_container_remove(GTK_CONTAINER(priv->vbox_crop_area), priv->crop_area);
                    priv->crop_area = cc_crop_area_new();
                    gtk_widget_show(priv->crop_area);
                    gtk_box_pack_start(GTK_BOX(priv->vbox_crop_area), priv->crop_area, TRUE, TRUE, 0);
                    cc_crop_area_set_picture(CC_CROP_AREA(priv->crop_area), picture);
                    g_object_unref(picture);
    
                    set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
                } else {
                    g_warning("(choose_picture) failed to load pixbuf from file: %s", error->message);
                    g_error_free(error);
                }
    
                g_free(filename);
            } else {
                g_warning("(choose_picture) filename empty");
            }
        }
    
        gtk_widget_destroy(dialog);
    }
    
    static void
    update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
    {
        gboolean have_preview = FALSE;
        if (auto filename = gtk_file_chooser_get_preview_filename(file_chooser)) {
            GError* error =  nullptr;
            auto pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 128, 128, &error);
            if (!error) {
                gtk_image_set_from_pixbuf(GTK_IMAGE(preview), pixbuf);
                g_object_unref(pixbuf);
                have_preview = TRUE;
            } else {
                // nothing to do, the file is probably not a picture
            }
            g_free (filename);
        }
        gtk_file_chooser_set_preview_widget_active(file_chooser, have_preview);
    }
    
    static void
    got_snapshot(AvatarManipulation *self)
    {
        AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
        GdkPixbuf* pix = video_widget_get_snapshot(VIDEO_WIDGET(priv->video_widget));
    
        if (priv->crop_area)
            gtk_container_remove(GTK_CONTAINER(priv->vbox_crop_area), priv->crop_area);
        priv->crop_area = cc_crop_area_new();
        gtk_widget_show(priv->crop_area);
        gtk_box_pack_start(GTK_BOX(priv->vbox_crop_area), priv->crop_area, TRUE, TRUE, 0);
        cc_crop_area_set_picture(CC_CROP_AREA(priv->crop_area), pix);
    
        set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
    }
    
    void
    avatar_manipulation_wizard_completed(AvatarManipulation *self)
    {
        auto priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
    
        /* Tuleap: #1441
         * if the user did not validate the avatar area selection, we still take that as the image
         * for their avatar; otherwise many users end up with no avatar by default
         * TODO: improve avatar creation process to not need this fix
         */
        if (priv->state == AVATAR_MANIPULATION_STATE_EDIT)
            set_avatar(self);
    }