703 lines
19 KiB
C
703 lines
19 KiB
C
/*
|
|
* Copyright 2021 Red Hat, Inc,
|
|
*
|
|
* Authors:
|
|
* - Matthias Clasen <mclasen@redhat.com>
|
|
* - Niels De Graef <nielsdg@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 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/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gsk/gl/gskglrenderer.h>
|
|
|
|
#include "cc-crop-area.h"
|
|
|
|
/**
|
|
* CcCropArea:
|
|
*
|
|
* A widget that shows a [iface@Gdk.Paintable] and allows the user specify a
|
|
* cropping rectangle to effectively crop to that given area.
|
|
*/
|
|
|
|
/* Location of the cursor relative to the cropping rectangle/circle */
|
|
typedef enum {
|
|
OUTSIDE,
|
|
INSIDE,
|
|
TOP,
|
|
TOP_LEFT,
|
|
TOP_RIGHT,
|
|
BOTTOM,
|
|
BOTTOM_LEFT,
|
|
BOTTOM_RIGHT,
|
|
LEFT,
|
|
RIGHT
|
|
} Location;
|
|
|
|
struct _CcCropArea {
|
|
GtkWidget parent_instance;
|
|
|
|
GdkPaintable *paintable;
|
|
|
|
double scale; /* scale factor to go from paintable size to widget size */
|
|
|
|
const char *current_cursor;
|
|
Location active_region;
|
|
double drag_offx;
|
|
double drag_offy;
|
|
|
|
/* In source coordinates. See get_scaled_crop() for widget coordinates */
|
|
GdkRectangle crop;
|
|
|
|
/* In widget coordinates */
|
|
GdkRectangle image;
|
|
int min_crop_width;
|
|
int min_crop_height;
|
|
};
|
|
|
|
G_DEFINE_TYPE (CcCropArea, cc_crop_area, GTK_TYPE_WIDGET);
|
|
|
|
static void
|
|
update_image_and_crop (CcCropArea *area)
|
|
{
|
|
GtkAllocation allocation;
|
|
int width, height;
|
|
int dest_width, dest_height;
|
|
double scale;
|
|
|
|
if (area->paintable == NULL)
|
|
return;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
|
|
|
|
/* Get the size of the paintable */
|
|
width = gdk_paintable_get_intrinsic_width (area->paintable);
|
|
height = gdk_paintable_get_intrinsic_height (area->paintable);
|
|
|
|
/* Find out the scale to convert to widget width/height */
|
|
scale = allocation.height / (double) height;
|
|
if (scale * width > allocation.width)
|
|
scale = allocation.width / (double) width;
|
|
|
|
dest_width = width * scale;
|
|
dest_height = height * scale;
|
|
|
|
if (area->scale == 0.0) {
|
|
double scale_to_80, scale_to_image, crop_scale;
|
|
|
|
/* Start with a crop area of 80% of the area, unless it's larger than min_size */
|
|
scale_to_80 = MIN ((double) dest_width * 0.8, (double) dest_height * 0.8);
|
|
scale_to_image = MIN ((double) area->min_crop_width, (double) area->min_crop_height);
|
|
crop_scale = MAX (scale_to_80, scale_to_image);
|
|
|
|
/* Divide by `scale` to get back to paintable coordinates */
|
|
area->crop.width = crop_scale / scale;
|
|
area->crop.height = crop_scale / scale;
|
|
area->crop.x = (width - area->crop.width) / 2;
|
|
area->crop.y = (height - area->crop.height) / 2;
|
|
}
|
|
|
|
area->scale = scale;
|
|
area->image.x = (allocation.width - dest_width) / 2;
|
|
area->image.y = (allocation.height - dest_height) / 2;
|
|
area->image.width = dest_width;
|
|
area->image.height = dest_height;
|
|
}
|
|
|
|
/* Returns area->crop in widget coordinates (vs paintable coordsinates) */
|
|
static void
|
|
get_scaled_crop (CcCropArea *area,
|
|
GdkRectangle *crop)
|
|
{
|
|
crop->x = area->image.x + area->crop.x * area->scale;
|
|
crop->y = area->image.y + area->crop.y * area->scale;
|
|
crop->width = area->crop.width * area->scale;
|
|
crop->height = area->crop.height * area->scale;
|
|
}
|
|
|
|
typedef enum {
|
|
BELOW,
|
|
LOWER,
|
|
BETWEEN,
|
|
UPPER,
|
|
ABOVE
|
|
} Range;
|
|
|
|
static Range
|
|
find_range (int x,
|
|
int min,
|
|
int max)
|
|
{
|
|
int 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;
|
|
}
|
|
|
|
/* Finds the location of (@x, @y) relative to the crop @rect */
|
|
static Location
|
|
find_location (GdkRectangle *rect,
|
|
int x,
|
|
int 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,
|
|
int x,
|
|
int y)
|
|
{
|
|
const char *cursor_type;
|
|
GdkRectangle crop;
|
|
int region;
|
|
|
|
region = area->active_region;
|
|
if (region == OUTSIDE) {
|
|
get_scaled_crop (area, &crop);
|
|
region = find_location (&crop, x, y);
|
|
}
|
|
|
|
switch (region) {
|
|
case OUTSIDE:
|
|
cursor_type = "default";
|
|
break;
|
|
case TOP_LEFT:
|
|
cursor_type = "nw-resize";
|
|
break;
|
|
case TOP:
|
|
cursor_type = "n-resize";
|
|
break;
|
|
case TOP_RIGHT:
|
|
cursor_type = "ne-resize";
|
|
break;
|
|
case LEFT:
|
|
cursor_type = "w-resize";
|
|
break;
|
|
case INSIDE:
|
|
cursor_type = "move";
|
|
break;
|
|
case RIGHT:
|
|
cursor_type = "e-resize";
|
|
break;
|
|
case BOTTOM_LEFT:
|
|
cursor_type = "sw-resize";
|
|
break;
|
|
case BOTTOM:
|
|
cursor_type = "s-resize";
|
|
break;
|
|
case BOTTOM_RIGHT:
|
|
cursor_type = "se-resize";
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (cursor_type != area->current_cursor) {
|
|
GtkNative *native;
|
|
g_autoptr (GdkCursor) cursor = NULL;
|
|
|
|
native = gtk_widget_get_native (GTK_WIDGET (area));
|
|
if (!native) {
|
|
g_warning ("Can't adjust cursor: no GtkNative found");
|
|
return;
|
|
}
|
|
cursor = gdk_cursor_new_from_name (cursor_type, NULL);
|
|
gdk_surface_set_cursor (gtk_native_get_surface (native), cursor);
|
|
area->current_cursor = cursor_type;
|
|
}
|
|
}
|
|
|
|
static int
|
|
eval_radial_line (double center_x, double center_y,
|
|
double bounds_x, double bounds_y,
|
|
double user_x)
|
|
{
|
|
double decision_slope;
|
|
double 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
|
|
on_motion (GtkEventControllerMotion *controller,
|
|
double event_x,
|
|
double event_y,
|
|
void *user_data)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (user_data);
|
|
|
|
if (area->paintable == NULL)
|
|
return FALSE;
|
|
|
|
update_cursor (area, event_x, event_y);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
on_drag_begin (GtkGestureDrag *gesture,
|
|
double start_x,
|
|
double start_y,
|
|
void *user_data)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (user_data);
|
|
GdkRectangle crop;
|
|
|
|
if (area->paintable == NULL)
|
|
return;
|
|
|
|
update_cursor (area, start_x, start_y);
|
|
|
|
get_scaled_crop (area, &crop);
|
|
|
|
area->active_region = find_location (&crop, start_x, start_y);
|
|
|
|
area->drag_offx = 0.0;
|
|
area->drag_offy = 0.0;
|
|
}
|
|
|
|
static void
|
|
on_drag_update (GtkGestureDrag *gesture,
|
|
double offset_x,
|
|
double offset_y,
|
|
void *user_data)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (user_data);
|
|
double start_x, start_y;
|
|
int x, y, delta_x, delta_y;
|
|
int width, height;
|
|
int adj_width, adj_height;
|
|
int pb_width, pb_height;
|
|
int left, right, top, bottom;
|
|
double new_width, new_height;
|
|
double center_x, center_y;
|
|
int min_width, min_height;
|
|
|
|
pb_width = gdk_paintable_get_intrinsic_width (area->paintable);
|
|
pb_height = gdk_paintable_get_intrinsic_height (area->paintable);
|
|
|
|
gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
|
|
|
|
/* Get the x, y, dx, dy in paintable coords */
|
|
x = (start_x + offset_x - area->image.x) / area->scale;
|
|
y = (start_y + offset_y - area->image.y) / area->scale;
|
|
delta_x = (offset_x - area->drag_offx) / area->scale;
|
|
delta_y = (offset_y - area->drag_offy) / area->scale;
|
|
|
|
/* Helper variables */
|
|
left = area->crop.x;
|
|
right = area->crop.x + area->crop.width - 1;
|
|
top = area->crop.y;
|
|
bottom = area->crop.y + area->crop.height - 1;
|
|
|
|
center_x = (left + right) / 2.0;
|
|
center_y = (top + bottom) / 2.0;
|
|
|
|
/* What we have to do depends on where the user started dragging */
|
|
switch (area->active_region) {
|
|
case INSIDE:
|
|
width = right - left + 1;
|
|
height = bottom - top + 1;
|
|
|
|
left = MAX (left + delta_x, 0);
|
|
right = MIN (right + delta_x, pb_width);
|
|
top = MAX (top + delta_y, 0);
|
|
bottom = MIN (bottom + delta_y, 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 (y < eval_radial_line (center_x, center_y, left, top, x)) {
|
|
top = y;
|
|
new_width = bottom - top;
|
|
left = right - new_width;
|
|
} else {
|
|
left = x;
|
|
new_height = right - left;
|
|
top = bottom - new_height;
|
|
}
|
|
break;
|
|
|
|
case TOP:
|
|
top = y;
|
|
new_width = bottom - top;
|
|
right = left + new_width;
|
|
break;
|
|
|
|
case TOP_RIGHT:
|
|
if (y < eval_radial_line (center_x, center_y, right, top, x)) {
|
|
top = y;
|
|
new_width = bottom - top;
|
|
right = left + new_width;
|
|
} else {
|
|
right = x;
|
|
new_height = right - left;
|
|
top = bottom - new_height;
|
|
}
|
|
break;
|
|
|
|
case LEFT:
|
|
left = x;
|
|
new_height = right - left;
|
|
bottom = top + new_height;
|
|
break;
|
|
|
|
case BOTTOM_LEFT:
|
|
if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
|
|
left = x;
|
|
new_height = right - left;
|
|
bottom = top + new_height;
|
|
} else {
|
|
bottom = y;
|
|
new_width = bottom - top;
|
|
left = right - new_width;
|
|
}
|
|
break;
|
|
|
|
case RIGHT:
|
|
right = x;
|
|
new_height = right - left;
|
|
bottom = top + new_height;
|
|
break;
|
|
|
|
case BOTTOM_RIGHT:
|
|
if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
|
|
right = x;
|
|
new_height = right - left;
|
|
bottom = top + new_height;
|
|
} else {
|
|
bottom = y;
|
|
new_width = bottom - top;
|
|
right = left + new_width;
|
|
}
|
|
break;
|
|
|
|
case BOTTOM:
|
|
bottom = y;
|
|
new_width = bottom - top;
|
|
right= left + new_width;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
min_width = area->min_crop_width / area->scale;
|
|
min_height = area->min_crop_height / area->scale;
|
|
|
|
width = right - left + 1;
|
|
height = bottom - top + 1;
|
|
if (left < 0 || top < 0 ||
|
|
right > pb_width || bottom > pb_height ||
|
|
width < min_width || height < min_height) {
|
|
left = area->crop.x;
|
|
right = area->crop.x + area->crop.width - 1;
|
|
top = area->crop.y;
|
|
bottom = area->crop.y + area->crop.height - 1;
|
|
}
|
|
|
|
area->crop.x = left;
|
|
area->crop.y = top;
|
|
area->crop.width = right - left + 1;
|
|
area->crop.height = bottom - top + 1;
|
|
|
|
area->drag_offx = offset_x;
|
|
area->drag_offy = offset_y;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
}
|
|
|
|
static void
|
|
on_drag_end (GtkGestureDrag *gesture,
|
|
double offset_x,
|
|
double offset_y,
|
|
void *user_data)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (user_data);
|
|
|
|
area->active_region = OUTSIDE;
|
|
area->drag_offx = 0.0;
|
|
area->drag_offy = 0.0;
|
|
}
|
|
|
|
static void
|
|
on_drag_cancel (GtkGesture *gesture,
|
|
GdkEventSequence *sequence,
|
|
void *user_data)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (user_data);
|
|
|
|
area->active_region = OUTSIDE;
|
|
area->drag_offx = 0;
|
|
area->drag_offy = 0;
|
|
}
|
|
|
|
static void
|
|
cc_crop_area_snapshot (GtkWidget *widget,
|
|
GtkSnapshot *snapshot)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (widget);
|
|
cairo_t *cr;
|
|
GdkRectangle crop;
|
|
|
|
if (area->paintable == NULL)
|
|
return;
|
|
|
|
update_image_and_crop (area);
|
|
|
|
|
|
gtk_snapshot_save (snapshot);
|
|
|
|
/* First draw the picture */
|
|
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (area->image.x, area->image.y));
|
|
|
|
gdk_paintable_snapshot (area->paintable, snapshot, area->image.width, area->image.height);
|
|
|
|
/* Draw the cropping UI on top with cairo */
|
|
cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, area->image.width, area->image.height));
|
|
|
|
get_scaled_crop (area, &crop);
|
|
crop.x -= area->image.x;
|
|
crop.y -= area->image.y;
|
|
|
|
/* Draw the circle */
|
|
cairo_save (cr);
|
|
cairo_arc (cr, crop.x + crop.width / 2, crop.y + crop.width / 2, crop.width / 2, 0, 2 * G_PI);
|
|
cairo_rectangle (cr, 0, 0, area->image.width, area->image.height);
|
|
cairo_set_source_rgba (cr, 0, 0, 0, 0.4);
|
|
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
|
cairo_fill (cr);
|
|
cairo_restore (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);
|
|
|
|
gtk_snapshot_restore (snapshot);
|
|
}
|
|
|
|
static void
|
|
cc_crop_area_finalize (GObject *object)
|
|
{
|
|
CcCropArea *area = CC_CROP_AREA (object);
|
|
|
|
g_clear_object (&area->paintable);
|
|
}
|
|
|
|
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->snapshot = cc_crop_area_snapshot;
|
|
}
|
|
|
|
static void
|
|
cc_crop_area_init (CcCropArea *area)
|
|
{
|
|
GtkGesture *gesture;
|
|
GtkEventController *controller;
|
|
|
|
/* Add handlers for dragging */
|
|
gesture = gtk_gesture_drag_new ();
|
|
g_signal_connect (gesture, "drag-begin", G_CALLBACK (on_drag_begin), area);
|
|
g_signal_connect (gesture, "drag-update", G_CALLBACK (on_drag_update),
|
|
area);
|
|
g_signal_connect (gesture, "drag-end", G_CALLBACK (on_drag_end), area);
|
|
g_signal_connect (gesture, "cancel", G_CALLBACK (on_drag_cancel), area);
|
|
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture));
|
|
|
|
/* Add handlers for motion events */
|
|
controller = gtk_event_controller_motion_new ();
|
|
g_signal_connect (controller, "motion", G_CALLBACK (on_motion), area);
|
|
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (controller));
|
|
|
|
area->scale = 0.0;
|
|
area->image.x = 0;
|
|
area->image.y = 0;
|
|
area->image.width = 0;
|
|
area->image.height = 0;
|
|
area->active_region = OUTSIDE;
|
|
area->min_crop_width = 48;
|
|
area->min_crop_height = 48;
|
|
|
|
gtk_widget_set_size_request (GTK_WIDGET (area), 48, 48);
|
|
}
|
|
|
|
GtkWidget *
|
|
cc_crop_area_new (void)
|
|
{
|
|
return g_object_new (CC_TYPE_CROP_AREA, NULL);
|
|
}
|
|
|
|
/**
|
|
* cc_crop_area_create_pixbuf:
|
|
* @area: A crop area
|
|
*
|
|
* Renders the area's paintable, with the cropping applied by the user, into a
|
|
* GdkPixbuf.
|
|
*
|
|
* Returns: (transfer full): The cropped picture
|
|
*/
|
|
GdkPixbuf *
|
|
cc_crop_area_create_pixbuf (CcCropArea *area)
|
|
{
|
|
g_autoptr (GtkSnapshot) snapshot = NULL;
|
|
g_autoptr (GskRenderNode) node = NULL;
|
|
g_autoptr (GskRenderer) renderer = NULL;
|
|
g_autoptr (GdkTexture) texture = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
graphene_rect_t viewport;
|
|
|
|
g_return_val_if_fail (CC_IS_CROP_AREA (area), NULL);
|
|
|
|
snapshot = gtk_snapshot_new ();
|
|
gdk_paintable_snapshot (area->paintable, snapshot,
|
|
gdk_paintable_get_intrinsic_width (area->paintable),
|
|
gdk_paintable_get_intrinsic_height (area->paintable));
|
|
node = gtk_snapshot_free_to_node (g_steal_pointer (&snapshot));
|
|
|
|
renderer = gsk_gl_renderer_new ();
|
|
if (!gsk_renderer_realize (renderer, NULL, &error)) {
|
|
g_warning ("Couldn't realize GL renderer: %s", error->message);
|
|
return NULL;
|
|
}
|
|
viewport = GRAPHENE_RECT_INIT (area->crop.x, area->crop.y,
|
|
area->crop.width, area->crop.height);
|
|
texture = gsk_renderer_render_texture (renderer, node, &viewport);
|
|
gsk_renderer_unrealize (renderer);
|
|
|
|
return gdk_pixbuf_get_from_texture (texture);
|
|
}
|
|
|
|
/**
|
|
* cc_crop_area_get_paintable:
|
|
* @area: A crop area
|
|
*
|
|
* Returns the area's paintable, unmodified.
|
|
*
|
|
* Returns: (transfer none) (nullable): The paintable which the user can crop
|
|
*/
|
|
GdkPaintable *
|
|
cc_crop_area_get_paintable (CcCropArea *area)
|
|
{
|
|
g_return_val_if_fail (CC_IS_CROP_AREA (area), NULL);
|
|
|
|
return area->paintable;
|
|
}
|
|
|
|
void
|
|
cc_crop_area_set_paintable (CcCropArea *area,
|
|
GdkPaintable *paintable)
|
|
{
|
|
g_return_if_fail (CC_IS_CROP_AREA (area));
|
|
g_return_if_fail (GDK_IS_PAINTABLE (paintable));
|
|
|
|
g_set_object (&area->paintable, paintable);
|
|
|
|
area->scale = 0.0;
|
|
area->image.x = 0;
|
|
area->image.y = 0;
|
|
area->image.width = 0;
|
|
area->image.height = 0;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (area));
|
|
}
|
|
|
|
/**
|
|
* cc_crop_area_set_min_size:
|
|
* @area: A crop widget
|
|
* @width: The minimal width
|
|
* @height: The minimal height
|
|
*
|
|
* Sets the minimal size of the crop rectangle (in paintable coordinates)
|
|
*/
|
|
void
|
|
cc_crop_area_set_min_size (CcCropArea *area,
|
|
int width,
|
|
int height)
|
|
{
|
|
g_return_if_fail (CC_IS_CROP_AREA (area));
|
|
|
|
area->min_crop_width = width;
|
|
area->min_crop_height = height;
|
|
|
|
gtk_widget_set_size_request (GTK_WIDGET (area),
|
|
area->min_crop_width,
|
|
area->min_crop_height);
|
|
}
|