blob: 100153c9838a964d87eef8c672c238345b6c33f1 [file] [log] [blame]
Nicolas Jagerb413b302016-05-06 11:41:32 -04001/*
2 * Copyright (C) 2016 Savoir-faire Linux Inc.
3 * Author: Nicolas Jager <nicolas.jager@savoirfairelinux.com>
4 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#include "avatarmanipulation.h"
22
23/* LRC */
24#include <globalinstances.h>
25#include <person.h>
26#include <profile.h>
27#include <profilemodel.h>
28#include <video/configurationproxy.h>
29#include <video/previewmanager.h>
30#include <video/devicemodel.h>
31
32/* client */
33#include "native/pixbufmanipulator.h"
34#include "video/video_widget.h"
35
36/* system */
37#include <glib/gi18n.h>
aviau61b71182016-06-06 16:21:17 -040038#include <cmath>
Nicolas Jagerb413b302016-05-06 11:41:32 -040039
40/* size of avatar */
41static constexpr int AVATAR_WIDTH = 100; /* px */
42static constexpr int AVATAR_HEIGHT = 100; /* px */
43
44/* size of video widget */
45static constexpr int VIDEO_WIDTH = 300; /* px */
46static constexpr int VIDEO_HEIGHT = 200; /* px */
47
48/* initial length of the selector */
49static constexpr int INITIAL_LENTGH = 100; /* px */
50
51/* mouse interactions with selector */
52enum ActionOnSelector {
53 MOVE_SELECTOR,
54 PICK_SELECTOR
55};
56
57struct _AvatarManipulation
58{
59 GtkBox parent;
60};
61
62struct _AvatarManipulationClass
63{
64 GtkBoxClass parent_class;
65};
66
67typedef struct _AvatarManipulationPrivate AvatarManipulationPrivate;
68
69struct _AvatarManipulationPrivate
70{
71 GtkWidget *stack_avatar_manipulation;
72 GtkWidget *video_widget;
73 GtkWidget *box_views_and_controls;
74 GtkWidget *box_controls;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -040075
76 GtkWidget *button_box_current;
77 GtkWidget *button_box_photo;
78 GtkWidget *button_box_edit;
79
80 GtkWidget *button_start_camera;
Nicolas Jagerb413b302016-05-06 11:41:32 -040081 GtkWidget *button_choose_picture;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -040082 GtkWidget *button_take_photo;
83 GtkWidget *button_return_photo;
Nicolas Jagerb413b302016-05-06 11:41:32 -040084 GtkWidget *button_set_avatar;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -040085 GtkWidget *button_return_edit;
86
Nicolas Jagerb413b302016-05-06 11:41:32 -040087 GtkWidget *selector_widget;
88 GtkWidget *stack_views;
89 GtkWidget *image_avatar;
90 GtkWidget *vbox_selector;
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -040091 GtkWidget *frame_video;
Nicolas Jagerb413b302016-05-06 11:41:32 -040092 GdkPixbuf *pix_scaled;
93
94 /* selector widget properties */
95 cairo_surface_t * selector_widget_surface;
96 int origin[2]; /* top left coordinates of the selector */
97 int relative_position[2]; /* this position refers the pointer to selector origin */
98 int length; /* selector length */
99 ActionOnSelector action_on_selector;
100 bool do_action;
101 GdkModifierType button_pressed;
102
103 AvatarManipulationState state;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400104 AvatarManipulationState last_state;
105
106 /* this is used to keep track of the state of the preview when the camera is used to take a
107 * photo; if a call is in progress, then the preview should already be started and we don't want
108 * to stop it when the settings are closed, in this case
109 */
110 gboolean video_started_by_avatar_manipulation;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400111};
112
113G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX);
114
115#define AVATAR_MANIPULATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AVATAR_MANIPULATION_TYPE, \
116 AvatarManipulationPrivate))
117
118static void set_state(AvatarManipulation *self, AvatarManipulationState state);
119
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400120static void start_camera(AvatarManipulation *self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400121static void take_a_photo(AvatarManipulation *self);
122static void choose_picture(AvatarManipulation *self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400123static void return_to_previous_state(AvatarManipulation *self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400124static void update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400125static void set_avatar(AvatarManipulation *self);
126static void got_snapshot(AvatarManipulation *parent);
127
128/* area selected */
129static gboolean selector_widget_button_press_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
130static gboolean selector_widget_button_release_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
131static gboolean selector_widget_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self);
132static gboolean selector_widget_configure_event(GtkWidget *widget, GdkEventConfigure *event, AvatarManipulation *self);
133static gboolean selector_widget_draw(GtkWidget *widget, cairo_t *cr, AvatarManipulation *self);
134static void update_and_draw(gdouble x, gdouble y, AvatarManipulation *self);
135static void rescale(gdouble x, gdouble y, AvatarManipulation *self);
136
137static void
138avatar_manipulation_dispose(GObject *object)
139{
140 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
141
142 /* make sure we stop the preview and the video widget */
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400143 if (priv->video_started_by_avatar_manipulation)
144 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400145 if (priv->video_widget) {
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -0400146 gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400147 priv->video_widget = NULL;
148 }
149
150 G_OBJECT_CLASS(avatar_manipulation_parent_class)->dispose(object);
151}
152
153static void
154avatar_manipulation_finalize(GObject *object)
155{
156 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
157
158 if (priv->selector_widget_surface)
159 cairo_surface_destroy(priv->selector_widget_surface);
160
161 if (priv->pix_scaled)
162 g_object_unref(priv->pix_scaled);
163
164 G_OBJECT_CLASS(avatar_manipulation_parent_class)->finalize(object);
165}
166
167GtkWidget*
168avatar_manipulation_new(void)
169{
170 // a profile must exist
171 g_return_val_if_fail(ProfileModel::instance().selectedProfile(), NULL);
172
173 return (GtkWidget *)g_object_new(AVATAR_MANIPULATION_TYPE, NULL);
174}
175
176GtkWidget*
177avatar_manipulation_new_from_wizard(void)
178{
179 auto self = avatar_manipulation_new();
180
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400181 /* in this mode, we want to automatically go to the PHOTO avatar state, unless one already exists */
182 if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid()) {
183 // check if there is a camera
184 if (Video::DeviceModel::instance().rowCount() > 0)
185 set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_PHOTO);
186 }
Nicolas Jagerb413b302016-05-06 11:41:32 -0400187
188 return self;
189}
190
191static void
192avatar_manipulation_class_init(AvatarManipulationClass *klass)
193{
194 G_OBJECT_CLASS(klass)->finalize = avatar_manipulation_finalize;
195 G_OBJECT_CLASS(klass)->dispose = avatar_manipulation_dispose;
196
197 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass), "/cx/ring/RingGnome/avatarmanipulation.ui");
198
199 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_views_and_controls);
200 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_controls);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400201 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_start_camera);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400202 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400203 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
204 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_photo);
205 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
206 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_edit);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400207 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, stack_views);
208 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, image_avatar);
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -0400209 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, frame_video);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400210 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, vbox_selector);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400211 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_current);
212 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_photo);
213 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_edit);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400214}
215
216static void
217avatar_manipulation_init(AvatarManipulation *self)
218{
219 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
220 gtk_widget_init_template(GTK_WIDGET(self));
221
222 /* selector widget */
223 priv->selector_widget = gtk_drawing_area_new();
224 gtk_box_pack_start(GTK_BOX(priv->vbox_selector), priv->selector_widget, FALSE, TRUE, 0);
225
226 /* our desired size for the image area */
227 gtk_widget_set_size_request(priv->stack_views, VIDEO_WIDTH, VIDEO_HEIGHT);
228
229 /* Signals used to handle backing surface */
230 g_signal_connect(priv->selector_widget, "draw", G_CALLBACK (selector_widget_draw), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400231 g_signal_connect(priv->selector_widget, "configure-event", G_CALLBACK (selector_widget_configure_event), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400232 /* Event signals */
233 g_signal_connect(priv->selector_widget, "motion-notify-event", G_CALLBACK(selector_widget_motion_notify_event), self);
234 g_signal_connect(priv->selector_widget, "button-press-event", G_CALLBACK(selector_widget_button_press_event), self);
235 g_signal_connect(priv->selector_widget, "button-release-event", G_CALLBACK(selector_widget_button_release_event), self);
236 /* Ask to receive events the drawing area doesn't normally subscribe to */
237 gtk_widget_set_events (priv->selector_widget, gtk_widget_get_events (priv->selector_widget)
238 | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
239 | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
240
241
242 /* signals */
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400243 g_signal_connect_swapped(priv->button_start_camera, "clicked", G_CALLBACK(start_camera), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400244 g_signal_connect_swapped(priv->button_choose_picture, "clicked", G_CALLBACK(choose_picture), self);
245 g_signal_connect_swapped(priv->button_take_photo, "clicked", G_CALLBACK(take_a_photo), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400246 g_signal_connect_swapped(priv->button_return_photo, "clicked", G_CALLBACK(return_to_previous_state), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400247 g_signal_connect_swapped(priv->button_set_avatar, "clicked", G_CALLBACK(set_avatar), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400248 g_signal_connect_swapped(priv->button_return_edit, "clicked", G_CALLBACK(return_to_previous_state), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400249
250 set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
251
252 gtk_widget_show_all(priv->stack_views);
253}
254
255static void
256set_state(AvatarManipulation *self, AvatarManipulationState state)
257{
258 // note: this funciton does not check if the state transition is valid, this is assumed to have
259 // been done by the caller
260 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
261
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400262 // save prev state
263 priv->last_state = priv->state;
264
Nicolas Jagerb413b302016-05-06 11:41:32 -0400265 switch (state) {
266 case AVATAR_MANIPULATION_STATE_CURRENT:
267 {
268 /* get the current or default profile avatar */
269 auto photo = GlobalInstances::pixmapManipulator().contactPhoto(
270 ProfileModel::instance().selectedProfile()->person(),
271 QSize(AVATAR_WIDTH, AVATAR_HEIGHT),
272 false);
273 std::shared_ptr<GdkPixbuf> pixbuf_photo = photo.value<std::shared_ptr<GdkPixbuf>>();
274
275 if (photo.isValid()) {
276 gtk_image_set_from_pixbuf (GTK_IMAGE(priv->image_avatar), pixbuf_photo.get());
277 } else {
278 g_warning("invlid pixbuf");
279 }
280
281 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_avatar");
Nicolas Jagerb413b302016-05-06 11:41:32 -0400282
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400283 /* available actions: start camera (if available) or choose image */
284 if (Video::DeviceModel::instance().rowCount() > 0) {
285 // TODO: update if a video device gets inserted while in this state
286 gtk_widget_set_visible(priv->button_start_camera, true);
287 }
288 gtk_widget_set_visible(priv->button_box_current, true);
289 gtk_widget_set_visible(priv->button_box_photo, false);
290 gtk_widget_set_visible(priv->button_box_edit, false);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400291
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400292 /* make sure video widget and camera is not running */
293 if (priv->video_started_by_avatar_manipulation)
294 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400295 if (priv->video_widget) {
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -0400296 gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400297 priv->video_widget = NULL;
298 }
299 }
300 break;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400301 case AVATAR_MANIPULATION_STATE_PHOTO:
Nicolas Jagerb413b302016-05-06 11:41:32 -0400302 {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400303 // start the video; if its not available we should not be in this state
304 priv->video_widget = video_widget_new();
305 g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
306 gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
307 gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -0400308 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400309 gtk_widget_set_visible(priv->video_widget, true);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400310 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
Nicolas Jagerb413b302016-05-06 11:41:32 -0400311
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400312
313 /* local renderer, but set as "remote" so that it takes up the whole screen */
314 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
315 Video::PreviewManager::instance().previewRenderer(),
316 VIDEO_RENDERER_REMOTE);
317
318 if (!Video::PreviewManager::instance().isPreviewing()) {
319 priv->video_started_by_avatar_manipulation = TRUE;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400320 Video::PreviewManager::instance().startPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400321 } else {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400322 priv->video_started_by_avatar_manipulation = FALSE;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400323 }
324
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400325 /* available actions: take snapshot, return*/
326 gtk_widget_set_visible(priv->button_box_current, false);
327 gtk_widget_set_visible(priv->button_box_photo, true);
328 gtk_widget_set_visible(priv->button_box_edit, false);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400329 }
330 break;
331 case AVATAR_MANIPULATION_STATE_EDIT:
332 {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400333 /* make sure video widget and camera is not running */
334 if (priv->video_started_by_avatar_manipulation)
335 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400336 if (priv->video_widget) {
Stepan Salenikovich1c1b0d82016-07-29 08:13:59 -0400337 gtk_container_remove(GTK_CONTAINER(priv->frame_video), priv->video_widget);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400338 priv->video_widget = NULL;
339 }
340
341 /* reset the selector */
342 priv->origin[0] = 0;
343 priv->origin[1] = 0;
344 priv->length = INITIAL_LENTGH;
345
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400346 /* available actions: set avatar, return */
347 gtk_widget_set_visible(priv->button_box_current, false);
348 gtk_widget_set_visible(priv->button_box_photo, false);
349 gtk_widget_set_visible(priv->button_box_edit, true);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400350
351 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_edit_view");
352 }
353 break;
354 }
355
356 priv->state = state;
357}
358
359static void
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400360start_camera(AvatarManipulation *self)
361{
362 set_state(self, AVATAR_MANIPULATION_STATE_PHOTO);
363}
364
365static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400366take_a_photo(AvatarManipulation *self)
367{
368 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
369 video_widget_take_snapshot(VIDEO_WIDGET(priv->video_widget));
370}
371
372static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400373set_avatar(AvatarManipulation *self)
374{
375 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
376
377 gchar* png_buffer_signed = nullptr;
378 gsize png_buffer_size;
379 GError* error = nullptr;
380
381 /* get the selected zone */
382 GdkPixbuf *selector_pixbuf = gdk_pixbuf_new_subpixbuf(priv->pix_scaled, priv->origin[0], priv->origin[1],
383 priv->length, priv->length);
384
385 /* scale it */
386 GdkPixbuf* pixbuf_frame_resized = gdk_pixbuf_scale_simple(selector_pixbuf, AVATAR_WIDTH, AVATAR_HEIGHT,
387 GDK_INTERP_HYPER);
388
389 /* save the png in memory */
390 gdk_pixbuf_save_to_buffer(pixbuf_frame_resized, &png_buffer_signed, &png_buffer_size, "png", &error, NULL);
391 if (!png_buffer_signed) {
392 g_warning("(set_avatar) failed to save pixbuffer to png: %s\n", error->message);
393 g_error_free(error);
394 return;
395 }
396
397 /* convert buffer to QByteArray in base 64*/
398 QByteArray png_q_byte_array = QByteArray::fromRawData(png_buffer_signed, png_buffer_size).toBase64();
399
400 /* save in profile */
401 QVariant photo = GlobalInstances::pixmapManipulator().personPhoto(png_q_byte_array);
402 ProfileModel::instance().selectedProfile()->person()->setPhoto(photo);
403 ProfileModel::instance().selectedProfile()->save();
404
405 g_free(png_buffer_signed);
406 g_object_unref(selector_pixbuf);
407 g_object_unref(pixbuf_frame_resized);
408
409 set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
410}
411
412static void
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400413return_to_previous_state(AvatarManipulation *self)
414{
415 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
416 set_state(self, priv->last_state);
417}
418
419static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400420choose_picture(AvatarManipulation *self)
421{
422 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
423 GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
424 GtkFileFilter *filter = gtk_file_filter_new ();
425 gint res;
426
427 auto preview = gtk_image_new();
428
429 GtkWidget *ring_main_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
430
431 auto dialog = gtk_file_chooser_dialog_new (_("Open Avatar Image"),
432 GTK_WINDOW(ring_main_window),
433 action,
434 _("_Cancel"),
435 GTK_RESPONSE_CANCEL,
436 _("_Open"),
437 GTK_RESPONSE_ACCEPT,
438 NULL);
439
440 /* add filters */
441 gtk_file_filter_add_pattern (filter,"*.png");
442 gtk_file_filter_add_pattern (filter,"*.jpg");
443 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),filter);
444
445 /* add an image preview inside the file choose */
446 gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER(dialog), preview);
447 g_signal_connect (GTK_FILE_CHOOSER(dialog), "update-preview", G_CALLBACK (update_preview_cb), preview);
448
449 /* start the file chooser */
450 res = gtk_dialog_run (GTK_DIALOG(dialog)); /* blocks until the dialog is closed */
451
452 if (res == GTK_RESPONSE_ACCEPT) {
453 if(auto filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog))) {
454 GError* error = nullptr; /* initialising to null avoid trouble... */
455
456 if (priv->pix_scaled) {
457 g_object_unref(priv->pix_scaled);
458 priv->pix_scaled = nullptr;
459 }
460 priv->pix_scaled = gdk_pixbuf_new_from_file_at_size (filename, VIDEO_WIDTH, VIDEO_HEIGHT, &error);
461
462 if (!error) {
463 set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
464 } else {
465 g_warning("(choose_picture) failed to load pixbuf from file: %s", error->message);
466 g_error_free(error);
467 }
468
469 g_free(filename);
470 } else {
471 g_warning("(choose_picture) filename empty");
472 }
473 }
474
475 gtk_widget_destroy(dialog);
476}
477
478static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400479update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
480{
481 gboolean have_preview = FALSE;
482 if (auto filename = gtk_file_chooser_get_preview_filename(file_chooser)) {
483 GError* error = nullptr;
484 auto pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 128, 128, &error);
485 if (!error) {
486 gtk_image_set_from_pixbuf(GTK_IMAGE(preview), pixbuf);
487 g_object_unref(pixbuf);
488 have_preview = TRUE;
489 } else {
490 // nothing to do, the file is probably not a picture
491 }
492 g_free (filename);
493 }
494 gtk_file_chooser_set_preview_widget_active(file_chooser, have_preview);
495}
496
497static gboolean
498selector_widget_configure_event(G_GNUC_UNUSED GtkWidget *widget,
499 G_GNUC_UNUSED GdkEventConfigure *event,
500 AvatarManipulation *self)
501{
502 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
503 GtkAllocation allocation;
504
505 gtk_widget_get_allocation (widget, &allocation);
506 if (priv->selector_widget_surface) {
507 cairo_surface_destroy(priv->selector_widget_surface);
508 priv->selector_widget_surface = nullptr;
509 }
510
511 priv->selector_widget_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
512 CAIRO_CONTENT_COLOR,
513 allocation.width,
514 allocation.height);
515
516 /* TRUE = do not propagate */
517 return TRUE;
518}
519
520static gboolean
521selector_widget_draw(G_GNUC_UNUSED GtkWidget *widget, cairo_t *cr, AvatarManipulation *self)
522{
523 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
524
525 if(!priv->pix_scaled)
526 return FALSE;
527
528 int w = gdk_pixbuf_get_width(priv->pix_scaled);
529 int h = gdk_pixbuf_get_height(priv->pix_scaled);
530 gtk_widget_set_size_request(priv->selector_widget, w, h);
531
532 /* add the snapshot/picture on it */
533 gdk_cairo_set_source_pixbuf(cr, priv->pix_scaled, 0, 0);
534 cairo_paint(cr);
535
536 /* dark around the selector : */
537 cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
538 cairo_set_line_width(cr, 2);
539 /* left */
540 cairo_rectangle(cr, 0, 0, priv->origin[0], VIDEO_HEIGHT);
541 cairo_fill(cr);
542 /* right */
543 cairo_rectangle(cr, priv->origin[0]+priv->length, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
544 cairo_fill(cr);
545 /* up */
546 cairo_rectangle(cr, priv->origin[0], 0, priv->length, priv->origin[1]);
547 cairo_fill(cr);
548 /* down */
549 cairo_rectangle(cr, priv->origin[0], priv->origin[1]+priv->length, priv->length, VIDEO_HEIGHT);
550 cairo_fill(cr);
551
552 /* black border around the selector */
553 cairo_set_source_rgb(cr, 0., 0., 0.);
554 cairo_set_line_width(cr, 2);
555 cairo_rectangle(cr, priv->origin[0], priv->origin[1], priv->length, priv->length);
556 cairo_stroke(cr);
557
558 /* white border around the selector */
559 cairo_set_source_rgb(cr, 1., 1., 1.);
560 cairo_set_line_width(cr, 2);
561 cairo_rectangle(cr, priv->origin[0]+2, priv->origin[1]+2, priv->length-4, priv->length-4);
562 cairo_stroke(cr);
563
564 /* crosshair */
565 cairo_set_line_width(cr, 1);
566 double lg = (double)priv->length;
567 if(priv->do_action) {
568 /* horizontales */
569 cairo_move_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+2);
570 cairo_line_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+priv->length-4);
571 cairo_move_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+2);
572 cairo_line_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+priv->length-4);
573 /* verticales */
574 cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(lg/3.0))+2);
575 cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(lg/3.0))+2);
576 cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(2.0*lg/3.0))+2);
577 cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(2.0*lg/3.0))+2);
578 cairo_stroke(cr);
579 }
580
581 return TRUE;
582}
583
584static gboolean
585selector_widget_motion_notify_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self)
586{
587 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
588
589 int x, y;
590 GdkModifierType state;
591 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
592
593 if (priv->do_action && state == priv->button_pressed) {
594 switch (priv->action_on_selector) {
595 case MOVE_SELECTOR:
596 update_and_draw( x - priv->relative_position[0], y - priv->relative_position[1] , self );
597 break;
598 case PICK_SELECTOR:
599 rescale( x, y , self );
600 break;
601 }
602 } else { /* is the pointer just over the selector ? */
603 if (x > priv->origin[0] && x < priv->origin[0] +priv->length
604 && y > priv->origin[1] && y < priv->origin[1] + priv->length) {
605 GdkWindow *window = gtk_widget_get_window( GTK_WIDGET(priv->selector_widget));
606 GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_FLEUR);
607 gdk_window_set_cursor(window, cursor);
608 priv->action_on_selector = MOVE_SELECTOR;
609 } else {
610 GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(priv->selector_widget));
611 GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_CROSSHAIR);
612 gdk_window_set_cursor(window, cursor);
613 priv->action_on_selector = PICK_SELECTOR;
614 }
615 }
616
617 return TRUE;
618}
619
620static gboolean
621selector_widget_button_press_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
622{
623 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
624
625 int x, y;
626 GdkModifierType state;
627 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
628
629 switch (priv->action_on_selector) {
630 case MOVE_SELECTOR:
631 priv->do_action = true;
632 priv->relative_position[0] = x - priv->origin[0];
633 priv->relative_position[1] = y - priv->origin[1];
634 break;
635 case PICK_SELECTOR:
636 priv->do_action = true;
637 priv->length = 0;
638 update_and_draw( x, y , self );
639 break;
640 }
641
642 priv->button_pressed = state;
643
644 return TRUE;
645}
646
647static gboolean
648selector_widget_button_release_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
649{
650 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
651
652 int x, y;
653 GdkModifierType state;
654 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
655
656 if (priv->do_action)
657 switch ( priv->action_on_selector ) {
658 case MOVE_SELECTOR:
659 update_and_draw( x-priv->relative_position[0], y-priv->relative_position[1], self );
660 break;
661 case PICK_SELECTOR:
662 update_and_draw( priv->origin[0], priv->origin[1], self );
663 break;
664 }
665
666 priv->do_action = false;
667
668 return TRUE;
669}
670
671static void
672update_and_draw(gdouble x, gdouble y, AvatarManipulation *self)
673{
674 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
675
676 GdkRectangle update_rect;
677 cairo_t *cr;
678
679 if (!priv->pix_scaled) {
680 g_warning("(update_and_draw) pix_scaled is null");
681 return;
682 }
683 auto width = gdk_pixbuf_get_width(priv->pix_scaled);
684 auto height = gdk_pixbuf_get_height(priv->pix_scaled);
685
686 update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 30;
687 update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 30;
688 update_rect.width = width - update_rect.x;
689 update_rect.height = height - update_rect.y;
690
691 if (x > width - priv->length)
692 priv->origin[0] = (x > width - priv->length) ? width - priv->length : x;
693 else
694 priv->origin[0] = (x > 0) ? x : 0;
695
696 if (y > height - priv->length )
697 priv->origin[1] = (y > height - priv->length ) ? height - priv->length : y;
698 else
699 priv->origin[1] = (y > 0) ? y : 0;
700
701 /* cairo operations */
702 cr = cairo_create(priv->selector_widget_surface);
703 gdk_cairo_rectangle(cr, &update_rect);
704 cairo_fill(cr);
705 cairo_destroy(cr);
706
707 /* invalidate the affected region */
708 gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
709}
710
711static void
712rescale(gdouble x, gdouble y, AvatarManipulation *self)
713{
714 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
715
716 GdkRectangle update_rect;
717 cairo_t *cr;
718
719 if (!priv->pix_scaled) {
720 g_warning("(rescale) pix_scaled is null");
721 return;
722 }
723 auto width = gdk_pixbuf_get_width(priv->pix_scaled);
724 auto height = gdk_pixbuf_get_height(priv->pix_scaled);
725
726 update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 3;
727 update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 3;
728 update_rect.width = width - update_rect.x;
729 update_rect.height = height - update_rect.y;
730
731 int old_length = priv->length;
732 priv->length = sqrt( (priv->origin[0] - x)*(priv->origin[0] - x) + (priv->origin[1] - y)*(priv->origin[1] - y) );
733
734 if (priv->length < 10)
735 priv->length = 10;
736
737 if (priv->origin[0] + priv->length > width)
738 priv->length = old_length;
739
740 if (priv->origin[1] + priv->length > height)
741 priv->length = old_length;
742
743 /* cairo operations */
744 cr = cairo_create(priv->selector_widget_surface);
745
746 gdk_cairo_rectangle(cr, &update_rect);
747 cairo_fill(cr);
748
749 cairo_destroy(cr);
750
751 /* invalidate the affected region */
752 gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
753}
754
755static void
756got_snapshot(AvatarManipulation *self)
757{
758 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
759 GdkPixbuf* pix = video_widget_get_snapshot(VIDEO_WIDGET(priv->video_widget));
760
761 /* in this case we have to deal with the aspect ratio */
762 float w = ((float)gdk_pixbuf_get_width(pix));
763 float h = ((float)gdk_pixbuf_get_height(pix));
764 const float ratio = h/w;
765 const float W = VIDEO_WIDTH;
766 const float H = VIDEO_HEIGHT;
767
768 if (h > w) {
769 h = H;
770 w = h / ratio;
771 } else {
772 w = W;
773 h = w * ratio;
774 }
775
776 if (priv->pix_scaled) {
777 g_object_unref(priv->pix_scaled);
778 priv->pix_scaled = nullptr;
779 }
780 priv->pix_scaled = gdk_pixbuf_scale_simple(pix, w, h, GDK_INTERP_HYPER);
781
782 set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
783}