[gnome-initial-setup/wip/feborges/crop-avatar] account: Implement our own camera widget



commit c566d3cf96d6ec28269653e50c5160fdd823b994
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Nov 21 15:54:47 2018 +0100

    account: Implement our own camera widget
    
    CheeseAvatarChooser doesn't allow us to provide our own cropping
    widget. Therefore we are now implementing our own camera widget
    with the cheese API. This enables us to implement the designs
    in https://wiki.gnome.org/action/login/Design/OS/AvatarChooser

 gnome-initial-setup/meson.build                    |   1 +
 .../pages/account/account.gresource.xml            |   1 +
 gnome-initial-setup/pages/account/cc-crop-area.c   | 833 +++++++++++++++++++++
 gnome-initial-setup/pages/account/cc-crop-area.h   |  43 ++
 .../pages/account/gis-account-camera-dialog.c      | 151 ++++
 .../pages/account/gis-account-camera-dialog.h      |  35 +
 .../pages/account/gis-account-camera-dialog.ui     | 115 +++
 gnome-initial-setup/pages/account/meson.build      |   4 +
 .../pages/account/um-photo-dialog.c                |  20 +-
 meson.build                                        |   1 -
 10 files changed, 1194 insertions(+), 10 deletions(-)
---
diff --git a/gnome-initial-setup/meson.build b/gnome-initial-setup/meson.build
index c461562..f9c8c7e 100644
--- a/gnome-initial-setup/meson.build
+++ b/gnome-initial-setup/meson.build
@@ -23,6 +23,7 @@ sources += [
 ]
 
 dependencies = [
+    dependency ('clutter-gtk-1.0'),
     dependency ('libnm', version: '>= 1.2'),
     dependency ('libnma', version: '>= 1.0'),
     dependency ('polkit-gobject-1', version: '>= 0.103'),
diff --git a/gnome-initial-setup/pages/account/account.gresource.xml 
b/gnome-initial-setup/pages/account/account.gresource.xml
index d698ba9..6d9356e 100644
--- a/gnome-initial-setup/pages/account/account.gresource.xml
+++ b/gnome-initial-setup/pages/account/account.gresource.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/org/gnome/initial-setup">
     <file preprocess="xml-stripblanks" 
alias="gis-account-avatar-chooser.ui">gis-account-avatar-chooser.ui</file>
+    <file preprocess="xml-stripblanks" 
alias="gis-account-camera-dialog.ui">gis-account-camera-dialog.ui</file>
     <file preprocess="xml-stripblanks" alias="gis-account-page.ui">gis-account-page.ui</file>
     <file preprocess="xml-stripblanks" alias="gis-account-page-local.ui">gis-account-page-local.ui</file>
     <file preprocess="xml-stripblanks" 
alias="gis-account-page-enterprise.ui">gis-account-page-enterprise.ui</file>
diff --git a/gnome-initial-setup/pages/account/cc-crop-area.c 
b/gnome-initial-setup/pages/account/cc-crop-area.c
new file mode 100644
index 0000000..c27740b
--- /dev/null
+++ b/gnome-initial-setup/pages/account/cc-crop-area.c
@@ -0,0 +1,833 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009  Red Hat, Inc,
+ *
+ * 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 2 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "cc-crop-area.h"
+
+typedef struct {
+        GdkPixbuf *browse_pixbuf;
+        GdkPixbuf *pixbuf;
+        GdkPixbuf *color_shifted;
+        gdouble scale;
+        GdkRectangle image;
+        GdkCursorType current_cursor;
+        GdkRectangle crop;
+        gint active_region;
+        gint last_press_x;
+        gint last_press_y;
+        gint base_width;
+        gint base_height;
+        gdouble aspect;
+} CcCropAreaPrivate;
+
+struct _CcCropArea {
+        GtkDrawingArea parent;
+        CcCropAreaPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (CcCropArea, cc_crop_area, GTK_TYPE_DRAWING_AREA);
+
+static inline guchar
+shift_color_byte (guchar b,
+                  int    shift)
+{
+        return CLAMP(b + shift, 0, 255);
+}
+
+static void
+shift_colors (GdkPixbuf *pixbuf,
+              gint       red,
+              gint       green,
+              gint       blue,
+              gint       alpha)
+{
+        gint x, y, offset, y_offset, rowstride, width, height;
+        guchar *pixels;
+        gint channels;
+
+        width = gdk_pixbuf_get_width (pixbuf);
+        height = gdk_pixbuf_get_height (pixbuf);
+        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+        pixels = gdk_pixbuf_get_pixels (pixbuf);
+        channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+        for (y = 0; y < height; y++) {
+                y_offset = y * rowstride;
+                for (x = 0; x < width; x++) {
+                        offset = y_offset + x * channels;
+                        if (red != 0)
+                                pixels[offset] = shift_color_byte (pixels[offset], red);
+                        if (green != 0)
+                                pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
+                        if (blue != 0)
+                                pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
+                        if (alpha != 0 && channels >= 4)
+                                pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
+                }
+        }
+}
+
+static void
+update_pixbufs (CcCropArea *area)
+{
+        gint width;
+        gint height;
+        GtkAllocation allocation;
+        gdouble scale;
+        gint dest_width, dest_height;
+        GtkWidget *widget;
+
+        widget = GTK_WIDGET (area);
+        gtk_widget_get_allocation (widget, &allocation);
+
+        width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+        height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+        scale = allocation.height / (gdouble)height;
+        if (scale * width > allocation.width)
+                scale = allocation.width / (gdouble)width;
+
+        dest_width = width * scale;
+        dest_height = height * scale;
+
+        if (area->priv->pixbuf == NULL ||
+            gdk_pixbuf_get_width (area->priv->pixbuf) != allocation.width ||
+            gdk_pixbuf_get_height (area->priv->pixbuf) != allocation.height) {
+                if (area->priv->pixbuf != NULL)
+                        g_object_unref (area->priv->pixbuf);
+                area->priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+                                                     gdk_pixbuf_get_has_alpha (area->priv->browse_pixbuf),
+                                                     8,
+                                                     dest_width, dest_height);
+                gdk_pixbuf_fill (area->priv->pixbuf, 0x0);
+
+                gdk_pixbuf_scale (area->priv->browse_pixbuf,
+                                  area->priv->pixbuf,
+                                  0, 0,
+                                  dest_width, dest_height,
+                                  0, 0,
+                                  scale, scale,
+                                  GDK_INTERP_BILINEAR);
+
+                if (area->priv->color_shifted)
+                        g_object_unref (area->priv->color_shifted);
+                area->priv->color_shifted = gdk_pixbuf_copy (area->priv->pixbuf);
+                shift_colors (area->priv->color_shifted, -100, -100, -100, 0);
+
+                if (area->priv->scale == 0.0) {
+                        gdouble scale_to_80, scale_to_image, crop_scale;
+
+                        /* Scale the crop rectangle to 80% of the area, or less to fit the image */
+                        scale_to_80 = MIN ((gdouble)gdk_pixbuf_get_width (area->priv->pixbuf) * 0.8 / 
area->priv->base_width,
+                                           (gdouble)gdk_pixbuf_get_height (area->priv->pixbuf) * 0.8 / 
area->priv->base_height);
+                        scale_to_image = MIN ((gdouble)dest_width / area->priv->base_width,
+                                              (gdouble)dest_height / area->priv->base_height);
+                        crop_scale = MIN (scale_to_80, scale_to_image);
+
+                        area->priv->crop.width = crop_scale * area->priv->base_width / scale;
+                        area->priv->crop.height = crop_scale * area->priv->base_height / scale;
+                        area->priv->crop.x = (gdk_pixbuf_get_width (area->priv->browse_pixbuf) - 
area->priv->crop.width) / 2;
+                        area->priv->crop.y = (gdk_pixbuf_get_height (area->priv->browse_pixbuf) - 
area->priv->crop.height) / 2;
+                }
+
+                area->priv->scale = scale;
+                area->priv->image.x = (allocation.width - dest_width) / 2;
+                area->priv->image.y = (allocation.height - dest_height) / 2;
+                area->priv->image.width = dest_width;
+                area->priv->image.height = dest_height;
+        }
+}
+
+static void
+crop_to_widget (CcCropArea    *area,
+                GdkRectangle  *crop)
+{
+        crop->x = area->priv->image.x + area->priv->crop.x * area->priv->scale;
+        crop->y = area->priv->image.y + area->priv->crop.y * area->priv->scale;
+        crop->width = area->priv->crop.width * area->priv->scale;
+        crop->height = area->priv->crop.height * area->priv->scale;
+}
+
+typedef enum {
+        OUTSIDE,
+        INSIDE,
+        TOP,
+        TOP_LEFT,
+        TOP_RIGHT,
+        BOTTOM,
+        BOTTOM_LEFT,
+        BOTTOM_RIGHT,
+        LEFT,
+        RIGHT
+} Location;
+
+static gboolean
+cc_crop_area_draw (GtkWidget *widget,
+                   cairo_t   *cr)
+{
+        GdkRectangle crop;
+        gint width, height, ix, iy;
+        CcCropArea *uarea = CC_CROP_AREA (widget);
+
+        if (uarea->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        update_pixbufs (uarea);
+
+        width = gdk_pixbuf_get_width (uarea->priv->pixbuf);
+        height = gdk_pixbuf_get_height (uarea->priv->pixbuf);
+        crop_to_widget (uarea, &crop);
+
+        ix = uarea->priv->image.x;
+        iy = uarea->priv->image.y;
+
+        gdk_cairo_set_source_pixbuf (cr, uarea->priv->color_shifted, ix, iy);
+        cairo_rectangle (cr, ix, iy, width, height);
+        cairo_fill (cr);
+
+        gdk_cairo_set_source_pixbuf (cr, uarea->priv->pixbuf, ix, iy);
+        cairo_arc (cr, crop.x + crop.width / 2, crop.y + crop.width / 2, crop.width / 2, 0, 2 * G_PI);
+        cairo_fill (cr);
+
+        // draw the four corners
+        cairo_set_source_rgb (cr, 1, 1, 1);
+        cairo_set_line_width (cr, 4.0);
+
+        // top left corner
+        cairo_move_to (cr, crop.x + 15, crop.y);
+        cairo_line_to (cr, crop.x, crop.y);
+        cairo_line_to (cr, crop.x, crop.y + 15);
+
+        // top right corner
+        cairo_move_to (cr, crop.x + crop.width - 15, crop.y);
+        cairo_line_to (cr, crop.x + crop.width, crop.y);
+        cairo_line_to (cr, crop.x + crop.width, crop.y + 15);
+
+        // bottom right corner
+        cairo_move_to (cr, crop.x + crop.width - 15, crop.y + crop.height);
+        cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height);
+        cairo_line_to (cr, crop.x + crop.width, crop.y + crop.height - 15);
+
+        // bottom left corner
+        cairo_move_to (cr, crop.x + 15, crop.y + crop.height);
+        cairo_line_to (cr, crop.x, crop.y + crop.height);
+        cairo_line_to (cr, crop.x, crop.y + crop.height - 15);
+
+        cairo_stroke (cr);
+
+        return FALSE;
+}
+
+typedef enum {
+        BELOW,
+        LOWER,
+        BETWEEN,
+        UPPER,
+        ABOVE
+} Range;
+
+static Range
+find_range (gint x,
+            gint min,
+            gint max)
+{
+        gint tolerance = 12;
+
+        if (x < min - tolerance)
+                return BELOW;
+        if (x <= min + tolerance)
+                return LOWER;
+        if (x < max - tolerance)
+                return BETWEEN;
+        if (x <= max + tolerance)
+                return UPPER;
+        return ABOVE;
+}
+
+static Location
+find_location (GdkRectangle *rect,
+               gint          x,
+               gint          y)
+{
+        Range x_range, y_range;
+        Location location[5][5] = {
+                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE },
+                { OUTSIDE, TOP_LEFT,    TOP,     TOP_RIGHT,    OUTSIDE },
+                { OUTSIDE, LEFT,        INSIDE,  RIGHT,        OUTSIDE },
+                { OUTSIDE, BOTTOM_LEFT, BOTTOM,  BOTTOM_RIGHT, OUTSIDE },
+                { OUTSIDE, OUTSIDE,     OUTSIDE, OUTSIDE,      OUTSIDE }
+        };
+
+        x_range = find_range (x, rect->x, rect->x + rect->width);
+        y_range = find_range (y, rect->y, rect->y + rect->height);
+
+        return location[y_range][x_range];
+}
+
+static void
+update_cursor (CcCropArea *area,
+               gint           x,
+               gint           y)
+{
+        gint cursor_type;
+        GdkRectangle crop;
+        gint region;
+
+        region = area->priv->active_region;
+        if (region == OUTSIDE) {
+                crop_to_widget (area, &crop);
+                region = find_location (&crop, x, y);
+        }
+
+        switch (region) {
+        case OUTSIDE:
+                cursor_type = GDK_LEFT_PTR;
+                break;
+        case TOP_LEFT:
+                cursor_type = GDK_TOP_LEFT_CORNER;
+                break;
+        case TOP:
+                cursor_type = GDK_TOP_SIDE;
+                break;
+        case TOP_RIGHT:
+                cursor_type = GDK_TOP_RIGHT_CORNER;
+                break;
+        case LEFT:
+                cursor_type = GDK_LEFT_SIDE;
+                break;
+        case INSIDE:
+                cursor_type = GDK_FLEUR;
+                break;
+        case RIGHT:
+                cursor_type = GDK_RIGHT_SIDE;
+                break;
+        case BOTTOM_LEFT:
+                cursor_type = GDK_BOTTOM_LEFT_CORNER;
+                break;
+        case BOTTOM:
+                cursor_type = GDK_BOTTOM_SIDE;
+                break;
+        case BOTTOM_RIGHT:
+                cursor_type = GDK_BOTTOM_RIGHT_CORNER;
+                break;
+       default:
+               g_assert_not_reached ();
+        }
+
+        if (cursor_type != area->priv->current_cursor) {
+                GdkCursor *cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (area)),
+                                                                cursor_type);
+                gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
+                g_object_unref (cursor);
+                area->priv->current_cursor = cursor_type;
+        }
+}
+
+static int
+eval_radial_line (gdouble center_x, gdouble center_y,
+                  gdouble bounds_x, gdouble bounds_y,
+                  gdouble user_x)
+{
+        gdouble decision_slope;
+        gdouble decision_intercept;
+
+        decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
+        decision_intercept = -(decision_slope * bounds_x);
+
+        return (int) (decision_slope * user_x + decision_intercept);
+}
+
+static gboolean
+cc_crop_area_motion_notify_event (GtkWidget      *widget,
+                                  GdkEventMotion *event)
+{
+        CcCropArea *area = CC_CROP_AREA (widget);
+        gint x, y;
+        gint delta_x, delta_y;
+        gint width, height;
+        gint adj_width, adj_height;
+        gint pb_width, pb_height;
+        GdkRectangle damage;
+        gint left, right, top, bottom;
+        gdouble new_width, new_height;
+        gdouble center_x, center_y;
+        gint min_width, min_height;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        update_cursor (area, event->x, event->y);
+
+        crop_to_widget (area, &damage);
+        gtk_widget_queue_draw_area (widget,
+                                    damage.x - 4, damage.y - 4,
+                                    damage.width + 6, damage.height + 6);
+
+        pb_width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+        pb_height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+        x = (event->x - area->priv->image.x) / area->priv->scale;
+        y = (event->y - area->priv->image.y) / area->priv->scale;
+
+        delta_x = x - area->priv->last_press_x;
+        delta_y = y - area->priv->last_press_y;
+        area->priv->last_press_x = x;
+        area->priv->last_press_y = y;
+
+        left = area->priv->crop.x;
+        right = area->priv->crop.x + area->priv->crop.width - 1;
+        top = area->priv->crop.y;
+        bottom = area->priv->crop.y + area->priv->crop.height - 1;
+
+        center_x = (left + right) / 2.0;
+        center_y = (top + bottom) / 2.0;
+
+        switch (area->priv->active_region) {
+        case INSIDE:
+                width = right - left + 1;
+                height = bottom - top + 1;
+
+                left += delta_x;
+                right += delta_x;
+                top += delta_y;
+                bottom += delta_y;
+
+                if (left < 0)
+                        left = 0;
+                if (top < 0)
+                        top = 0;
+                if (right > pb_width)
+                        right = pb_width;
+                if (bottom > pb_height)
+                        bottom = pb_height;
+
+                adj_width = right - left + 1;
+                adj_height = bottom - top + 1;
+                if (adj_width != width) {
+                        if (delta_x < 0)
+                                right = left + width - 1;
+                        else
+                                left = right - width + 1;
+                }
+                if (adj_height != height) {
+                        if (delta_y < 0)
+                                bottom = top + height - 1;
+                        else
+                                top = bottom - height + 1;
+                }
+
+                break;
+
+        case TOP_LEFT:
+                if (area->priv->aspect < 0) {
+                        top = y;
+                        left = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
+                        top = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        left = right - new_width;
+                }
+                else {
+                        left = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        top = bottom - new_height;
+                }
+                break;
+
+        case TOP:
+                top = y;
+                if (area->priv->aspect > 0) {
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                break;
+
+        case TOP_RIGHT:
+                if (area->priv->aspect < 0) {
+                        top = y;
+                        right = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
+                        top = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                else {
+                        right = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        top = bottom - new_height;
+                }
+                break;
+
+        case LEFT:
+                left = x;
+                if (area->priv->aspect > 0) {
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                break;
+
+        case BOTTOM_LEFT:
+                if (area->priv->aspect < 0) {
+                        bottom = y;
+                        left = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
+                        left = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                else {
+                        bottom = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        left = right - new_width;
+                }
+                break;
+
+        case RIGHT:
+                right = x;
+                if (area->priv->aspect > 0) {
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                break;
+
+        case BOTTOM_RIGHT:
+                if (area->priv->aspect < 0) {
+                        bottom = y;
+                        right = x;
+                }
+                else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
+                        right = x;
+                        new_height = (right - left) / area->priv->aspect;
+                        bottom = top + new_height;
+                }
+                else {
+                        bottom = y;
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right = left + new_width;
+                }
+                break;
+
+        case BOTTOM:
+                bottom = y;
+                if (area->priv->aspect > 0) {
+                        new_width = (bottom - top) * area->priv->aspect;
+                        right= left + new_width;
+                }
+                break;
+
+        default:
+                return FALSE;
+        }
+
+        min_width = area->priv->base_width / area->priv->scale;
+        min_height = area->priv->base_height / area->priv->scale;
+
+        width = right - left + 1;
+        height = bottom - top + 1;
+        if (area->priv->aspect < 0) {
+                if (left < 0)
+                        left = 0;
+                if (top < 0)
+                        top = 0;
+                if (right > pb_width)
+                        right = pb_width;
+                if (bottom > pb_height)
+                        bottom = pb_height;
+
+                width = right - left + 1;
+                height = bottom - top + 1;
+
+                switch (area->priv->active_region) {
+                case LEFT:
+                case TOP_LEFT:
+                case BOTTOM_LEFT:
+                        if (width < min_width)
+                                left = right - min_width;
+                        break;
+                case RIGHT:
+                case TOP_RIGHT:
+                case BOTTOM_RIGHT:
+                        if (width < min_width)
+                                right = left + min_width;
+                        break;
+
+                default: ;
+                }
+
+                switch (area->priv->active_region) {
+                case TOP:
+                case TOP_LEFT:
+                case TOP_RIGHT:
+                        if (height < min_height)
+                                top = bottom - min_height;
+                        break;
+                case BOTTOM:
+                case BOTTOM_LEFT:
+                case BOTTOM_RIGHT:
+                        if (height < min_height)
+                                bottom = top + min_height;
+                        break;
+
+                default: ;
+                }
+        }
+        else {
+                if (left < 0 || top < 0 ||
+                    right > pb_width || bottom > pb_height ||
+                    width < min_width || height < min_height) {
+                        left = area->priv->crop.x;
+                        right = area->priv->crop.x + area->priv->crop.width - 1;
+                        top = area->priv->crop.y;
+                        bottom = area->priv->crop.y + area->priv->crop.height - 1;
+                }
+        }
+
+        area->priv->crop.x = left;
+        area->priv->crop.y = top;
+        area->priv->crop.width = right - left + 1;
+        area->priv->crop.height = bottom - top + 1;
+
+        crop_to_widget (area, &damage);
+        gtk_widget_queue_draw_area (widget,
+                                    damage.x - 4, damage.y - 4,
+                                    damage.width + 6, damage.height + 6);
+
+        return FALSE;
+}
+
+static gboolean
+cc_crop_area_button_press_event (GtkWidget      *widget,
+                                 GdkEventButton *event)
+{
+        CcCropArea *area = CC_CROP_AREA (widget);
+        GdkRectangle crop;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        crop_to_widget (area, &crop);
+
+        area->priv->last_press_x = (event->x - area->priv->image.x) / area->priv->scale;
+        area->priv->last_press_y = (event->y - area->priv->image.y) / area->priv->scale;
+        area->priv->active_region = find_location (&crop, event->x, event->y);
+
+        gtk_widget_queue_draw_area (widget,
+                                    crop.x - 4, crop.y - 4,
+                                    crop.width + 6, crop.height + 6);
+
+        return FALSE;
+}
+
+static gboolean
+cc_crop_area_button_release_event (GtkWidget      *widget,
+                                   GdkEventButton *event)
+{
+        CcCropArea *area = CC_CROP_AREA (widget);
+        GdkRectangle crop;
+
+        if (area->priv->browse_pixbuf == NULL)
+                return FALSE;
+
+        crop_to_widget (area, &crop);
+
+        area->priv->last_press_x = -1;
+        area->priv->last_press_y = -1;
+        area->priv->active_region = OUTSIDE;
+
+        gtk_widget_queue_draw_area (widget,
+                                    crop.x - 4, crop.y - 4,
+                                    crop.width + 6, crop.height + 6);
+
+        return FALSE;
+}
+
+static void
+cc_crop_area_set_size_request (CcCropArea *area)
+{
+        gtk_widget_set_size_request (GTK_WIDGET (area),
+                                     area->priv->base_width, 
+                                     area->priv->base_height);
+}
+
+static void
+cc_crop_area_finalize (GObject *object)
+{
+        CcCropArea *area = CC_CROP_AREA (object);
+
+        if (area->priv->browse_pixbuf) {
+                g_object_unref (area->priv->browse_pixbuf);
+                area->priv->browse_pixbuf = NULL;
+        }
+        if (area->priv->pixbuf) {
+                g_object_unref (area->priv->pixbuf);
+                area->priv->pixbuf = NULL;
+        }
+        if (area->priv->color_shifted) {
+                g_object_unref (area->priv->color_shifted);
+                area->priv->color_shifted = NULL;
+        }
+}
+
+static void
+cc_crop_area_class_init (CcCropAreaClass *klass)
+{
+        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+        object_class->finalize = cc_crop_area_finalize;
+        widget_class->draw = cc_crop_area_draw;
+        widget_class->button_press_event = cc_crop_area_button_press_event;
+        widget_class->button_release_event = cc_crop_area_button_release_event;
+        widget_class->motion_notify_event = cc_crop_area_motion_notify_event;
+}
+
+static void
+cc_crop_area_init (CcCropArea *area)
+{
+        area->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((area), CC_TYPE_CROP_AREA,
+                                                   CcCropAreaPrivate));
+
+        gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
+                               GDK_BUTTON_PRESS_MASK |
+                               GDK_BUTTON_RELEASE_MASK);
+
+        area->priv->scale = 0.0;
+        area->priv->image.x = 0;
+        area->priv->image.y = 0;
+        area->priv->image.width = 0;
+        area->priv->image.height = 0;
+        area->priv->active_region = OUTSIDE;
+        area->priv->base_width = 48;
+        area->priv->base_height = 48;
+        area->priv->aspect = 1;
+
+        cc_crop_area_set_size_request (area);
+}
+
+GtkWidget *
+cc_crop_area_new (void)
+{
+        return g_object_new (CC_TYPE_CROP_AREA, NULL);
+}
+
+GdkPixbuf *
+cc_crop_area_get_picture (CcCropArea *area)
+{
+        GdkPixbuf *pixbuf, *dest;
+        cairo_surface_t *surface;
+        cairo_t *cr;
+        gint width, height;
+
+        width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+        height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+        width = MIN (area->priv->crop.width, width - area->priv->crop.x);
+        height = MIN (area->priv->crop.height, height - area->priv->crop.y);
+
+        pixbuf = gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
+                                           area->priv->crop.x,
+                                           area->priv->crop.y,
+                                           width, height);
+
+        surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+        cr = cairo_create (surface);
+        gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+        cairo_arc (cr, width/2, height/ 2, width/2, 0, 2*G_PI);
+        cairo_clip (cr);
+        cairo_paint (cr);
+
+        dest = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+
+        cairo_destroy (cr);
+        cairo_surface_destroy (surface);
+
+        return dest;
+}
+
+void
+cc_crop_area_set_picture (CcCropArea *area,
+                          GdkPixbuf  *pixbuf)
+{
+        int width;
+        int height;
+
+        if (area->priv->browse_pixbuf) {
+                g_object_unref (area->priv->browse_pixbuf);
+                area->priv->browse_pixbuf = NULL;
+        }
+        if (pixbuf) {
+                area->priv->browse_pixbuf = g_object_ref (pixbuf);
+                width = gdk_pixbuf_get_width (pixbuf);
+                height = gdk_pixbuf_get_height (pixbuf);
+        } else {
+                width = 0;
+                height = 0;
+        }
+
+        area->priv->crop.width = 2 * area->priv->base_width;
+        area->priv->crop.height = 2 * area->priv->base_height;
+        area->priv->crop.x = (width - area->priv->crop.width) / 2;
+        area->priv->crop.y = (height - area->priv->crop.height) / 2;
+
+        area->priv->scale = 0.0;
+        area->priv->image.x = 0;
+        area->priv->image.y = 0;
+        area->priv->image.width = 0;
+        area->priv->image.height = 0;
+
+        gtk_widget_queue_draw (GTK_WIDGET (area));
+}
+
+void
+cc_crop_area_set_min_size (CcCropArea *area,
+                           gint        width,
+                           gint        height)
+{
+        area->priv->base_width = width;
+        area->priv->base_height = height;
+
+        cc_crop_area_set_size_request (area);
+
+        if (area->priv->aspect > 0) {
+                area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+        }
+}
+
+void
+cc_crop_area_set_constrain_aspect (CcCropArea *area,
+                                   gboolean    constrain)
+{
+        if (constrain) {
+                area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+        }
+        else {
+                area->priv->aspect = -1;
+        }
+}
+
diff --git a/gnome-initial-setup/pages/account/cc-crop-area.h 
b/gnome-initial-setup/pages/account/cc-crop-area.h
new file mode 100644
index 0000000..1cc1788
--- /dev/null
+++ b/gnome-initial-setup/pages/account/cc-crop-area.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2009 Bastien Nocera <hadess hadess net>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CC_CROP_AREA_H_
+#define _CC_CROP_AREA_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_CROP_AREA (cc_crop_area_get_type ())
+G_DECLARE_FINAL_TYPE (CcCropArea, cc_crop_area, CC, CROP_AREA, GtkDrawingArea)
+
+GtkWidget *cc_crop_area_new                  (void);
+GdkPixbuf *cc_crop_area_get_picture          (CcCropArea *area);
+void       cc_crop_area_set_picture          (CcCropArea *area,
+                                              GdkPixbuf  *pixbuf);
+void       cc_crop_area_set_min_size         (CcCropArea *area,
+                                              gint        width,
+                                              gint        height);
+void       cc_crop_area_set_constrain_aspect (CcCropArea *area,
+                                              gboolean    constrain);
+
+G_END_DECLS
+
+#endif /* _CC_CROP_AREA_H_ */
diff --git a/gnome-initial-setup/pages/account/gis-account-camera-dialog.c 
b/gnome-initial-setup/pages/account/gis-account-camera-dialog.c
new file mode 100644
index 0000000..d2e2a05
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-camera-dialog.c
@@ -0,0 +1,151 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2019 Red Hat
+ *
+ * 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 2 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Felipe Borges <felipeborges gnome org>
+ */
+
+#include <cheese/cheese-camera.h>
+#include <cheese/cheese-widget.h>
+
+#include "gis-account-camera-dialog.h"
+#include "cc-crop-area.h"
+
+struct _GisAccountCameraDialog
+{
+  GtkDialog parent;
+
+  GtkStack     *headerbar_stack;
+  GtkStack     *stack;
+  CheeseWidget *camera_feed;
+  CcCropArea   *crop_area;
+
+  gulong photo_taken_id;
+
+  SelectAvatarCallback *callback;
+  gpointer              callback_data;
+};
+
+G_DEFINE_TYPE (GisAccountCameraDialog, gis_account_camera_dialog, GTK_TYPE_DIALOG)
+
+#define CAMERA_FEED "camera-feed"
+#define CROP_VIEW "crop-view"
+
+static void
+gis_account_camera_dialog_set_mode (GisAccountCameraDialog *self,
+                                    const gchar            *mode)
+{
+  gtk_stack_set_visible_child_name (self->headerbar_stack, mode);
+  gtk_stack_set_visible_child_name (self->stack, mode);
+}
+
+static void
+on_take_another_button_clicked (GtkButton              *button,
+                                GisAccountCameraDialog *self)
+{
+  gis_account_camera_dialog_set_mode (self, CAMERA_FEED);
+  cc_crop_area_set_picture (CC_CROP_AREA (self->crop_area), NULL);
+}
+
+static void
+cheese_widget_photo_taken_cb (CheeseCamera           *camera,
+                              GdkPixbuf              *pixbuf,
+                              GisAccountCameraDialog *self)
+{
+  cc_crop_area_set_picture (CC_CROP_AREA (self->crop_area), pixbuf);
+}
+
+static void
+on_take_picture_button_clicked (GtkButton              *button,
+                                GisAccountCameraDialog *self)
+{
+  GObject *camera;
+
+  camera = cheese_widget_get_camera (self->camera_feed);
+
+  if (self->photo_taken_id == 0) {
+    self->photo_taken_id = g_signal_connect (camera, "photo-taken",
+                                             G_CALLBACK (cheese_widget_photo_taken_cb),
+                                             self);
+  }
+
+  if (cheese_camera_take_photo_pixbuf (CHEESE_CAMERA (camera))) {
+    // fire the flash here
+    gis_account_camera_dialog_set_mode (self, CROP_VIEW);
+  } else {
+    g_assert_not_reached ();
+  }
+}
+
+static void
+on_crop_done_button_clicked (GtkButton              *button,
+                             GisAccountCameraDialog *self)
+{
+  GdkPixbuf *pixbuf = cc_crop_area_get_picture (CC_CROP_AREA (self->crop_area));
+
+  gdk_pixbuf_save (pixbuf, "/tmp/photo", "png", NULL, NULL);
+
+  self->callback (NULL, "/tmp/photo", self->callback_data);
+
+  gtk_widget_hide (GTK_WIDGET (self));
+}
+
+GtkWidget *
+gis_account_camera_dialog_new (SelectAvatarCallback callback,
+                               gpointer             data)
+{
+  GisAccountCameraDialog *self;
+
+  self = g_object_new (GIS_TYPE_ACCOUNT_CAMERA_DIALOG,
+                       "use-header-bar", 1,
+                       NULL);
+
+  self->callback = callback;
+  self->callback_data = data;
+
+  return GTK_WIDGET (self);
+}
+
+static void
+gis_account_camera_dialog_class_init (GisAccountCameraDialogClass *klass)
+{
+  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (wclass, 
"/org/gnome/initial-setup/gis-account-camera-dialog.ui");
+
+  gtk_widget_class_bind_template_child (wclass, GisAccountCameraDialog, headerbar_stack);
+  gtk_widget_class_bind_template_child (wclass, GisAccountCameraDialog, stack);
+  gtk_widget_class_bind_template_child (wclass, GisAccountCameraDialog, crop_area);
+  gtk_widget_class_bind_template_child (wclass, GisAccountCameraDialog, camera_feed);
+
+  gtk_widget_class_bind_template_callback (wclass, on_take_picture_button_clicked);
+  gtk_widget_class_bind_template_callback (wclass, on_take_another_button_clicked);
+  gtk_widget_class_bind_template_callback (wclass, on_crop_done_button_clicked);
+}
+
+static void
+gis_account_camera_dialog_init (GisAccountCameraDialog *self)
+{
+  volatile GType type G_GNUC_UNUSED;
+
+  /* register types that the builder needs */
+  type = cc_crop_area_get_type ();
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  cc_crop_area_set_constrain_aspect (self->crop_area, TRUE);
+}
diff --git a/gnome-initial-setup/pages/account/gis-account-camera-dialog.h 
b/gnome-initial-setup/pages/account/gis-account-camera-dialog.h
new file mode 100644
index 0000000..287376c
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-camera-dialog.h
@@ -0,0 +1,35 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2019 Red Hat
+ *
+ * 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 2 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Written by:
+ *     Felipe Borges <felipeborges gnome org>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "um-photo-dialog.h"
+
+G_BEGIN_DECLS
+
+#define GIS_TYPE_ACCOUNT_CAMERA_DIALOG (gis_account_camera_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GisAccountCameraDialog, gis_account_camera_dialog, GIS, ACCOUNT_CAMERA_DIALOG, 
GtkDialog)
+
+GtkWidget *gis_account_camera_dialog_new (SelectAvatarCallback *callback, gpointer data);
+
+G_END_DECLS
diff --git a/gnome-initial-setup/pages/account/gis-account-camera-dialog.ui 
b/gnome-initial-setup/pages/account/gis-account-camera-dialog.ui
new file mode 100644
index 0000000..8457c54
--- /dev/null
+++ b/gnome-initial-setup/pages/account/gis-account-camera-dialog.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.8 -->
+  <template class="GisAccountCameraDialog" parent="GtkDialog">
+    <property name="destroy-with-parent">True</property>
+    <property name="modal">True</property>
+    <property name="use-header-bar">1</property>
+    <property name="title" translatable="yes">Take a Picture</property>
+    <child internal-child="headerbar">
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="show-close-button">False</property>
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">Cancel</property>
+            <signal name="clicked" handler="gtk_widget_hide" object="GisAccountCameraDialog"/>
+          </object>
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="headerbar_stack">
+            <property name="visible">True</property>
+            <child>
+              <object class="GtkButton" id="take_pic_button">
+                <property name="visible">True</property>
+                <property name="halign">end</property>
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+                <signal name="clicked" handler="on_take_picture_button_clicked"/>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">camera-photo-symbolic</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">camera-feed</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="spacing">5</property>
+                <child>
+                  <object class="GtkButton">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Take Another…</property>
+                    <signal name="clicked" handler="on_take_another_button_clicked"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="crop_done_button">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Done</property>
+                    <signal name="clicked" handler="on_crop_done_button_clicked"/>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="name">crop-view</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="border-width">0</property>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="visible">True</property>
+            <child>
+              <object class="CheeseWidget" id="camera_feed">
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="name">camera-feed</property>
+              </packing>
+            </child>
+            <child>
+              <object class="CcCropArea" id="crop_area">
+                <property name="visible">True</property>
+                <property name="vexpand">True</property>
+                <property name="hexpand">True</property>
+              </object>
+              <packing>
+                <property name="name">crop-view</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+
+  <object class="GtkSizeGroup">
+    <widgets>
+      <widget name="take_pic_button"/>
+      <widget name="crop_done_button"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/gnome-initial-setup/pages/account/meson.build b/gnome-initial-setup/pages/account/meson.build
index 1130465..9fe3a66 100644
--- a/gnome-initial-setup/pages/account/meson.build
+++ b/gnome-initial-setup/pages/account/meson.build
@@ -15,10 +15,14 @@ sources += gnome.compile_resources(
 )
 
 sources += files(
+    'cc-crop-area.c',
+    'cc-crop-area.h',
     'gis-account-page.c',
     'gis-account-page.h',
     'gis-account-pages.c',
     'gis-account-pages.h',
+    'gis-account-camera-dialog.c',
+    'gis-account-camera-dialog.h',
     'gis-account-page-local.c',
     'gis-account-page-local.h',
     'gis-account-page-enterprise.c',
diff --git a/gnome-initial-setup/pages/account/um-photo-dialog.c 
b/gnome-initial-setup/pages/account/um-photo-dialog.c
index 4b6c38d..f317a67 100644
--- a/gnome-initial-setup/pages/account/um-photo-dialog.c
+++ b/gnome-initial-setup/pages/account/um-photo-dialog.c
@@ -35,6 +35,7 @@
 #endif /* HAVE_CHEESE */
 
 #include "um-photo-dialog.h"
+#include "gis-account-camera-dialog.h"
 #include "um-utils.h"
 
 #define ROW_SPAN 5
@@ -45,6 +46,7 @@ struct _UmPhotoDialog {
 
         GtkWidget *popup_button;
         GtkWidget *take_picture_button;
+        GtkWidget *camera_dialog;
         GtkWidget *flowbox;
 
 #ifdef HAVE_CHEESE
@@ -92,15 +94,15 @@ webcam_response_cb (GtkDialog     *dialog,
 static void
 webcam_icon_selected (UmPhotoDialog *um)
 {
-        GtkWidget *window;
-
-        window = cheese_avatar_chooser_new ();
-        gtk_window_set_transient_for (GTK_WINDOW (window),
-                                      GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (um))));
-        gtk_window_set_modal (GTK_WINDOW (window), TRUE);
-        g_signal_connect (G_OBJECT (window), "response",
-                          G_CALLBACK (webcam_response_cb), um);
-        gtk_widget_show (window);
+        if (um->camera_dialog == NULL) {
+                um->camera_dialog = gis_account_camera_dialog_new (um->callback, um->data);
+                gtk_window_set_transient_for (GTK_WINDOW (um->camera_dialog),
+                                              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (um))));
+                g_signal_connect (G_OBJECT (um->camera_dialog), "response",
+                                  G_CALLBACK (webcam_response_cb), um);
+        }
+
+        gtk_dialog_run (GTK_DIALOG (um->camera_dialog));
 
         gtk_popover_popdown (GTK_POPOVER (um));
 }
diff --git a/meson.build b/meson.build
index 95634a4..09ae028 100644
--- a/meson.build
+++ b/meson.build
@@ -32,7 +32,6 @@ conf.set('SECRET_API_SUBJECT_TO_CHANGE', true)
 
 # Needed for the 'account' page
 cheese_dep = dependency ('cheese',
-                         version: '>= 3.3.5',
                          required: get_option('cheese'))
 cheese_gtk_dep = dependency ('cheese-gtk',
                          version: '>= 3.3.5',



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]