summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelipe Borges <felipeborges@gnome.org>2018-11-21 15:54:47 +0100
committerFelipe Borges <felipeborges@gnome.org>2018-12-05 11:37:11 +0100
commitc566d3cf96d6ec28269653e50c5160fdd823b994 (patch)
treea58ad355d255fb4b6a3415bab1c382c357c72e80
parent55ef13470c183bc7e8d8c5c9ea28100699c51b6c (diff)
downloadgnome-initial-setup-wip/feborges/crop-avatar.tar.gz
account: Implement our own camera widgetwip/feborges/crop-avatar
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
-rw-r--r--gnome-initial-setup/meson.build1
-rw-r--r--gnome-initial-setup/pages/account/account.gresource.xml1
-rw-r--r--gnome-initial-setup/pages/account/cc-crop-area.c833
-rw-r--r--gnome-initial-setup/pages/account/cc-crop-area.h43
-rw-r--r--gnome-initial-setup/pages/account/gis-account-camera-dialog.c151
-rw-r--r--gnome-initial-setup/pages/account/gis-account-camera-dialog.h35
-rw-r--r--gnome-initial-setup/pages/account/gis-account-camera-dialog.ui115
-rw-r--r--gnome-initial-setup/pages/account/meson.build4
-rw-r--r--gnome-initial-setup/pages/account/um-photo-dialog.c20
-rw-r--r--meson.build1
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',