Commit 5c54b353 authored by Stepan Salenikovich's avatar Stepan Salenikovich

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
parent 1dc123f9
......@@ -299,6 +299,8 @@ SET( SRC_FILES
src/accountimportexportview.cpp
src/contactpopupmenu.h
src/contactpopupmenu.cpp
src/cc-crop-area.h
src/cc-crop-area.c
)
# compile glib resource files to c code
......
......@@ -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 AVATAR_HEIGHT = 100; /* px */
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 @@ struct _AvatarManipulationPrivate
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 @@ struct _AvatarManipulationPrivate
* 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 update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
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 @@ avatar_manipulation_finalize(GObject *object)
{
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 @@ avatar_manipulation_class_init(AvatarManipulationClass *klass)
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 @@ avatar_manipulation_init(AvatarManipulation *self)
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 @@ set_state(AvatarManipulation *self, AvatarManipulationState state)
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 @@ set_avatar(AvatarManipulation *self)
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 @@ update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
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);
}
This diff is collapsed.
/*
* 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_ */
......@@ -56,11 +56,8 @@
</packing>
</child>
<child>
<object class="GtkBox" id="vbox_selector">
<object class="GtkBox" id="vbox_crop_area">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="valign">center</property>
<property name="halign">center</property>
</object>
<packing>
<property name="name">page_edit_view</property>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment