use CcCropArea to crop avatar

CcCropArea code is taken from gnome-control-center code.

It is used to crop avatar images in the control center and in
gnome contacts.

Using it unifies the interface more with other gnome programs and
also gets rid some of the small bugs in the previous implementation.

Now instead of creating a new selection each time, the user simply
moves and resizes one selection.

Change-Id: I764e958cf9e5e6f1aadd754ddd1ad5d542415365
Tuleap: #917
diff --git a/src/avatarmanipulation.cpp b/src/avatarmanipulation.cpp
index 100153c..dfb2099 100644
--- a/src/avatarmanipulation.cpp
+++ b/src/avatarmanipulation.cpp
@@ -32,10 +32,10 @@
 /* client */
 #include "native/pixbufmanipulator.h"
 #include "video/video_widget.h"
+#include "cc-crop-area.h"
 
 /* system */
 #include <glib/gi18n.h>
-#include <cmath>
 
 /* size of avatar */
 static constexpr int AVATAR_WIDTH  = 100; /* px */
@@ -45,15 +45,6 @@
 static constexpr int VIDEO_WIDTH = 300; /* px */
 static constexpr int VIDEO_HEIGHT = 200; /* px */
 
-/* initial length of the selector */
-static constexpr int INITIAL_LENTGH = 100; /* px */
-
-/* mouse interactions with selector */
-enum ActionOnSelector {
-    MOVE_SELECTOR,
-    PICK_SELECTOR
-};
-
 struct _AvatarManipulation
 {
     GtkBox parent;
@@ -84,22 +75,13 @@
     GtkWidget *button_set_avatar;
     GtkWidget *button_return_edit;
 
-    GtkWidget *selector_widget;
+    // GtkWidget *selector_widget;
     GtkWidget *stack_views;
     GtkWidget *image_avatar;
-    GtkWidget *vbox_selector;
+    GtkWidget *vbox_crop_area;
     GtkWidget *frame_video;
     GdkPixbuf *pix_scaled;
 
-    /* selector widget properties */
-    cairo_surface_t * selector_widget_surface;
-    int origin[2];            /* top left coordinates of the selector */
-    int relative_position[2]; /* this position refers the pointer to selector origin */
-    int length;               /* selector length */
-    ActionOnSelector action_on_selector;
-    bool do_action;
-    GdkModifierType button_pressed;
-
     AvatarManipulationState state;
     AvatarManipulationState last_state;
 
@@ -108,6 +90,8 @@
      * 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);
@@ -125,15 +109,6 @@
 static void set_avatar(AvatarManipulation *self);
 static void got_snapshot(AvatarManipulation *parent);
 
-/* area selected */
-static gboolean selector_widget_button_press_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
-static gboolean selector_widget_button_release_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
-static gboolean selector_widget_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self);
-static gboolean selector_widget_configure_event(GtkWidget *widget, GdkEventConfigure *event, AvatarManipulation *self);
-static gboolean selector_widget_draw(GtkWidget *widget, cairo_t *cr, AvatarManipulation *self);
-static void update_and_draw(gdouble x, gdouble y, AvatarManipulation *self);
-static void rescale(gdouble x, gdouble y, AvatarManipulation *self);
-
 static void
 avatar_manipulation_dispose(GObject *object)
 {
@@ -155,9 +130,6 @@
 {
     AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
 
-    if (priv->selector_widget_surface)
-        cairo_surface_destroy(priv->selector_widget_surface);
-
     if (priv->pix_scaled)
         g_object_unref(priv->pix_scaled);
 
@@ -207,7 +179,7 @@
     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_selector);
+    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);
@@ -219,26 +191,13 @@
     AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
     gtk_widget_init_template(GTK_WIDGET(self));
 
-    /* selector widget */
-    priv->selector_widget = gtk_drawing_area_new();
-    gtk_box_pack_start(GTK_BOX(priv->vbox_selector), priv->selector_widget, FALSE, TRUE, 0);
+    /* crop area */
+    priv->crop_area = cc_crop_area_new();
+    gtk_box_pack_start(GTK_BOX(priv->vbox_crop_area), priv->crop_area, TRUE, TRUE, 0);
 
     /* our desired size for the image area */
     gtk_widget_set_size_request(priv->stack_views, VIDEO_WIDTH, VIDEO_HEIGHT);
 
-    /* Signals used to handle backing surface */
-    g_signal_connect(priv->selector_widget, "draw", G_CALLBACK (selector_widget_draw), self);
-    g_signal_connect(priv->selector_widget, "configure-event", G_CALLBACK (selector_widget_configure_event), self);
-    /* Event signals */
-    g_signal_connect(priv->selector_widget, "motion-notify-event", G_CALLBACK(selector_widget_motion_notify_event), self);
-    g_signal_connect(priv->selector_widget, "button-press-event", G_CALLBACK(selector_widget_button_press_event), self);
-    g_signal_connect(priv->selector_widget, "button-release-event", G_CALLBACK(selector_widget_button_release_event), self);
-    /* Ask to receive events the drawing area doesn't normally subscribe to */
-    gtk_widget_set_events (priv->selector_widget, gtk_widget_get_events (priv->selector_widget)
-                           | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
-                           | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
-
-
     /* 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);
@@ -338,11 +297,6 @@
                 priv->video_widget = NULL;
             }
 
-            /* reset the selector */
-            priv->origin[0] = 0;
-            priv->origin[1] = 0;
-            priv->length = INITIAL_LENTGH;
-
             /* available actions: set avatar, return */
             gtk_widget_set_visible(priv->button_box_current, false);
             gtk_widget_set_visible(priv->button_box_photo,   false);
@@ -378,9 +332,8 @@
     gsize png_buffer_size;
     GError* error =  nullptr;
 
-    /* get the selected zone */
-    GdkPixbuf *selector_pixbuf = gdk_pixbuf_new_subpixbuf(priv->pix_scaled, priv->origin[0], priv->origin[1],
-                                                          priv->length, priv->length);
+    /* 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,
@@ -494,290 +447,13 @@
     gtk_file_chooser_set_preview_widget_active(file_chooser, have_preview);
 }
 
-static gboolean
-selector_widget_configure_event(G_GNUC_UNUSED GtkWidget *widget,
-                                G_GNUC_UNUSED GdkEventConfigure *event,
-                                AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-    GtkAllocation allocation;
-
-    gtk_widget_get_allocation (widget, &allocation);
-    if (priv->selector_widget_surface) {
-        cairo_surface_destroy(priv->selector_widget_surface);
-        priv->selector_widget_surface = nullptr;
-    }
-
-    priv->selector_widget_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
-                                               CAIRO_CONTENT_COLOR,
-                                               allocation.width,
-                                               allocation.height);
-
-    /* TRUE = do not propagate */
-    return TRUE;
-}
-
-static gboolean
-selector_widget_draw(G_GNUC_UNUSED GtkWidget *widget, cairo_t *cr, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    if(!priv->pix_scaled)
-        return FALSE;
-
-    int w = gdk_pixbuf_get_width(priv->pix_scaled);
-    int h = gdk_pixbuf_get_height(priv->pix_scaled);
-    gtk_widget_set_size_request(priv->selector_widget, w, h);
-
-    /* add the snapshot/picture on it */
-    gdk_cairo_set_source_pixbuf(cr, priv->pix_scaled, 0, 0);
-    cairo_paint(cr);
-
-    /* dark around the selector : */
-    cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
-    cairo_set_line_width(cr, 2);
-    /* left */
-    cairo_rectangle(cr, 0, 0, priv->origin[0], VIDEO_HEIGHT);
-    cairo_fill(cr);
-    /* right */
-    cairo_rectangle(cr, priv->origin[0]+priv->length, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
-    cairo_fill(cr);
-    /* up */
-    cairo_rectangle(cr, priv->origin[0], 0, priv->length, priv->origin[1]);
-    cairo_fill(cr);
-    /* down */
-    cairo_rectangle(cr, priv->origin[0], priv->origin[1]+priv->length, priv->length, VIDEO_HEIGHT);
-    cairo_fill(cr);
-
-    /* black border around the selector */
-    cairo_set_source_rgb(cr, 0., 0., 0.);
-    cairo_set_line_width(cr, 2);
-    cairo_rectangle(cr, priv->origin[0], priv->origin[1], priv->length, priv->length);
-    cairo_stroke(cr);
-
-    /* white border around the selector */
-    cairo_set_source_rgb(cr, 1., 1., 1.);
-    cairo_set_line_width(cr, 2);
-    cairo_rectangle(cr, priv->origin[0]+2, priv->origin[1]+2, priv->length-4, priv->length-4);
-    cairo_stroke(cr);
-
-    /* crosshair */
-    cairo_set_line_width(cr, 1);
-    double lg = (double)priv->length;
-    if(priv->do_action) {
-        /* horizontales */
-        cairo_move_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+2);
-        cairo_line_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+priv->length-4);
-        cairo_move_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+2);
-        cairo_line_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+priv->length-4);
-        /* verticales */
-        cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(lg/3.0))+2);
-        cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(lg/3.0))+2);
-        cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(2.0*lg/3.0))+2);
-        cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(2.0*lg/3.0))+2);
-        cairo_stroke(cr);
-    }
-
-    return TRUE;
-}
-
-static gboolean
-selector_widget_motion_notify_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    int x, y;
-    GdkModifierType state;
-    gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
-
-    if (priv->do_action && state == priv->button_pressed) {
-        switch (priv->action_on_selector) {
-        case MOVE_SELECTOR:
-            update_and_draw( x - priv->relative_position[0], y - priv->relative_position[1] , self );
-        break;
-        case PICK_SELECTOR:
-            rescale( x, y , self );
-        break;
-        }
-    } else { /* is the pointer just over the selector ? */
-        if (x > priv->origin[0] && x < priv->origin[0] +priv->length
-                                && y > priv->origin[1] && y < priv->origin[1] + priv->length) {
-            GdkWindow *window = gtk_widget_get_window( GTK_WIDGET(priv->selector_widget));
-            GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_FLEUR);
-            gdk_window_set_cursor(window, cursor);
-            priv->action_on_selector = MOVE_SELECTOR;
-        } else {
-            GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(priv->selector_widget));
-            GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_CROSSHAIR);
-            gdk_window_set_cursor(window, cursor);
-            priv->action_on_selector = PICK_SELECTOR;
-        }
-    }
-
-  return TRUE;
-}
-
-static gboolean
-selector_widget_button_press_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    int x, y;
-    GdkModifierType state;
-    gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
-
-    switch (priv->action_on_selector) {
-    case MOVE_SELECTOR:
-        priv->do_action = true;
-        priv->relative_position[0] = x - priv->origin[0];
-        priv->relative_position[1] = y - priv->origin[1];
-    break;
-    case PICK_SELECTOR:
-        priv->do_action = true;
-        priv->length = 0;
-        update_and_draw( x, y , self );
-    break;
-    }
-
-    priv->button_pressed = state;
-
-  return TRUE;
-}
-
-static gboolean
-selector_widget_button_release_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    int x, y;
-    GdkModifierType state;
-    gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
-
-    if (priv->do_action)
-        switch ( priv->action_on_selector ) {
-        case MOVE_SELECTOR:
-            update_and_draw( x-priv->relative_position[0], y-priv->relative_position[1], self );
-        break;
-        case PICK_SELECTOR:
-            update_and_draw( priv->origin[0], priv->origin[1], self );
-        break;
-        }
-
-    priv->do_action = false;
-
-    return TRUE;
-}
-
-static void
-update_and_draw(gdouble x, gdouble y, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    GdkRectangle update_rect;
-    cairo_t *cr;
-
-    if (!priv->pix_scaled) {
-        g_warning("(update_and_draw) pix_scaled is null");
-        return;
-    }
-    auto width = gdk_pixbuf_get_width(priv->pix_scaled);
-    auto height = gdk_pixbuf_get_height(priv->pix_scaled);
-
-    update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 30;
-    update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 30;
-    update_rect.width = width - update_rect.x;
-    update_rect.height = height - update_rect.y;
-
-    if (x > width - priv->length)
-        priv->origin[0] = (x > width - priv->length) ? width - priv->length : x;
-    else
-        priv->origin[0] = (x > 0) ? x : 0;
-
-    if (y > height - priv->length )
-        priv->origin[1] = (y > height - priv->length ) ? height - priv->length : y;
-    else
-        priv->origin[1] = (y > 0) ? y : 0;
-
-    /* cairo operations */
-    cr = cairo_create(priv->selector_widget_surface);
-    gdk_cairo_rectangle(cr, &update_rect);
-    cairo_fill(cr);
-    cairo_destroy(cr);
-
-    /* invalidate the affected region */
-    gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
-}
-
-static void
-rescale(gdouble x, gdouble y, AvatarManipulation *self)
-{
-    AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
-
-    GdkRectangle update_rect;
-    cairo_t *cr;
-
-    if (!priv->pix_scaled) {
-        g_warning("(rescale) pix_scaled is null");
-        return;
-    }
-    auto width = gdk_pixbuf_get_width(priv->pix_scaled);
-    auto height = gdk_pixbuf_get_height(priv->pix_scaled);
-
-    update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 3;
-    update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 3;
-    update_rect.width = width - update_rect.x;
-    update_rect.height = height - update_rect.y;
-
-    int old_length = priv->length;
-    priv->length = sqrt( (priv->origin[0] - x)*(priv->origin[0] - x) + (priv->origin[1] - y)*(priv->origin[1] - y) );
-
-    if (priv->length < 10)
-        priv->length = 10;
-
-    if (priv->origin[0] + priv->length > width)
-        priv->length = old_length;
-
-    if (priv->origin[1] + priv->length > height)
-        priv->length = old_length;
-
-    /* cairo operations */
-    cr = cairo_create(priv->selector_widget_surface);
-
-    gdk_cairo_rectangle(cr, &update_rect);
-    cairo_fill(cr);
-
-    cairo_destroy(cr);
-
-    /* invalidate the affected region */
-    gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
-}
-
 static void
 got_snapshot(AvatarManipulation *self)
 {
     AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
     GdkPixbuf* pix = video_widget_get_snapshot(VIDEO_WIDGET(priv->video_widget));
 
-    /* in this case we have to deal with the aspect ratio */
-    float w = ((float)gdk_pixbuf_get_width(pix));
-    float h = ((float)gdk_pixbuf_get_height(pix));
-    const float ratio = h/w;
-    const float W = VIDEO_WIDTH;
-    const float H = VIDEO_HEIGHT;
-
-    if (h > w) {
-        h = H;
-        w = h / ratio;
-    } else {
-        w = W;
-        h = w * ratio;
-    }
-
-    if (priv->pix_scaled) {
-        g_object_unref(priv->pix_scaled);
-        priv->pix_scaled = nullptr;
-    }
-    priv->pix_scaled = gdk_pixbuf_scale_simple(pix, w, h, GDK_INTERP_HYPER);
+    cc_crop_area_set_picture(CC_CROP_AREA(priv->crop_area), pix);
 
     set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
 }
diff --git a/src/cc-crop-area.c b/src/cc-crop-area.c
new file mode 100644
index 0000000..33c7e3e
--- /dev/null
+++ b/src/cc-crop-area.c
@@ -0,0 +1,830 @@
+/* -*- 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"
+
+struct _CcCropAreaPrivate {
+        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;
+};
+
+G_DEFINE_TYPE (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, -32, -32, -32, 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, crop.y - iy);
+        cairo_rectangle (cr, ix, crop.y, crop.x - ix, crop.height);
+        cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.width - (crop.x - ix), crop.height);
+        cairo_rectangle (cr, ix, crop.y + crop.height, width, height - crop.height - (crop.y - iy));
+        cairo_fill (cr);
+
+        gdk_cairo_set_source_pixbuf (cr, uarea->priv->pixbuf, ix, iy);
+        cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height);
+        cairo_fill (cr);
+
+        if (uarea->priv->active_region != OUTSIDE) {
+                gint x1, x2, y1, y2;
+                cairo_set_source_rgb (cr, 1, 1, 1);
+                cairo_set_line_width (cr, 1.0);
+                x1 = crop.x + crop.width / 3.0;
+                x2 = crop.x + 2 * crop.width / 3.0;
+                y1 = crop.y + crop.height / 3.0;
+                y2 = crop.y + 2 * crop.height / 3.0;
+
+                cairo_move_to (cr, x1 + 0.5, crop.y);
+                cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
+
+                cairo_move_to (cr, x2 + 0.5, crop.y);
+                cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
+
+                cairo_move_to (cr, crop.x, y1 + 0.5);
+                cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
+
+                cairo_move_to (cr, crop.x, y2 + 0.5);
+                cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
+                cairo_stroke (cr);
+        }
+
+        cairo_set_source_rgb (cr,  0, 0, 0);
+        cairo_set_line_width (cr, 1.0);
+        cairo_rectangle (cr,
+                         crop.x + 0.5,
+                         crop.y + 0.5,
+                         crop.width - 1.0,
+                         crop.height - 1.0);
+        cairo_stroke (cr);
+
+        cairo_set_source_rgb (cr, 1, 1, 1);
+        cairo_set_line_width (cr, 2.0);
+        cairo_rectangle (cr,
+                         crop.x + 2.0,
+                         crop.y + 2.0,
+                         crop.width - 4.0,
+                         crop.height - 4.0);
+        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 - 1, damage.y - 1,
+                                    damage.width + 2, damage.height + 2);
+
+        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 - 1, damage.y - 1,
+                                    damage.width + 2, damage.height + 2);
+
+        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 - 1, crop.y - 1,
+                                    crop.width + 2, crop.height + 2);
+
+        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 - 1, crop.y - 1,
+                                    crop.width + 2, crop.height + 2);
+
+        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;
+
+        g_type_class_add_private (klass, sizeof (CcCropAreaPrivate));
+}
+
+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)
+{
+        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);
+
+        return gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
+                                         area->priv->crop.x,
+                                         area->priv->crop.y,
+                                         width, height);
+}
+
+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/src/cc-crop-area.h b/src/cc-crop-area.h
new file mode 100644
index 0000000..38657c6
--- /dev/null
+++ b/src/cc-crop-area.h
@@ -0,0 +1,65 @@
+/*
+ * 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 ())
+#define CC_CROP_AREA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_CROP_AREA, \
+                                                                           CcCropArea))
+#define CC_CROP_AREA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CC_TYPE_CROP_AREA, \
+                                                                        CcCropAreaClass))
+#define CC_IS_CROP_AREA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_CROP_AREA))
+#define CC_IS_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CC_TYPE_CROP_AREA))
+#define CC_CROP_AREA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CC_TYPE_CROP_AREA, \
+                                                                          CcCropAreaClass))
+
+typedef struct _CcCropAreaClass CcCropAreaClass;
+typedef struct _CcCropArea CcCropArea;
+typedef struct _CcCropAreaPrivate CcCropAreaPrivate;
+
+struct _CcCropAreaClass {
+        GtkDrawingAreaClass parent_class;
+};
+
+struct _CcCropArea {
+        GtkDrawingArea parent_instance;
+        CcCropAreaPrivate *priv;
+};
+
+GType      cc_crop_area_get_type             (void) G_GNUC_CONST;
+
+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_ */