blob: 35148cd0c7dd9bc2cd6d0471df7a2e9d3a02f8a0 [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;
91 GdkPixbuf *pix_scaled;
92
93 /* selector widget properties */
94 cairo_surface_t * selector_widget_surface;
95 int origin[2]; /* top left coordinates of the selector */
96 int relative_position[2]; /* this position refers the pointer to selector origin */
97 int length; /* selector length */
98 ActionOnSelector action_on_selector;
99 bool do_action;
100 GdkModifierType button_pressed;
101
102 AvatarManipulationState state;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400103 AvatarManipulationState last_state;
104
105 /* this is used to keep track of the state of the preview when the camera is used to take a
106 * photo; if a call is in progress, then the preview should already be started and we don't want
107 * to stop it when the settings are closed, in this case
108 */
109 gboolean video_started_by_avatar_manipulation;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400110};
111
112G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX);
113
114#define AVATAR_MANIPULATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AVATAR_MANIPULATION_TYPE, \
115 AvatarManipulationPrivate))
116
117static void set_state(AvatarManipulation *self, AvatarManipulationState state);
118
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400119static void start_camera(AvatarManipulation *self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400120static void take_a_photo(AvatarManipulation *self);
121static void choose_picture(AvatarManipulation *self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400122static void return_to_previous_state(AvatarManipulation *self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400123static void update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400124static void set_avatar(AvatarManipulation *self);
125static void got_snapshot(AvatarManipulation *parent);
126
127/* area selected */
128static gboolean selector_widget_button_press_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
129static gboolean selector_widget_button_release_event(GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self);
130static gboolean selector_widget_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self);
131static gboolean selector_widget_configure_event(GtkWidget *widget, GdkEventConfigure *event, AvatarManipulation *self);
132static gboolean selector_widget_draw(GtkWidget *widget, cairo_t *cr, AvatarManipulation *self);
133static void update_and_draw(gdouble x, gdouble y, AvatarManipulation *self);
134static void rescale(gdouble x, gdouble y, AvatarManipulation *self);
135
136static void
137avatar_manipulation_dispose(GObject *object)
138{
139 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
140
141 /* make sure we stop the preview and the video widget */
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400142 if (priv->video_started_by_avatar_manipulation)
143 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400144 if (priv->video_widget) {
145 gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
146 priv->video_widget = NULL;
147 }
148
149 G_OBJECT_CLASS(avatar_manipulation_parent_class)->dispose(object);
150}
151
152static void
153avatar_manipulation_finalize(GObject *object)
154{
155 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
156
157 if (priv->selector_widget_surface)
158 cairo_surface_destroy(priv->selector_widget_surface);
159
160 if (priv->pix_scaled)
161 g_object_unref(priv->pix_scaled);
162
163 G_OBJECT_CLASS(avatar_manipulation_parent_class)->finalize(object);
164}
165
166GtkWidget*
167avatar_manipulation_new(void)
168{
169 // a profile must exist
170 g_return_val_if_fail(ProfileModel::instance().selectedProfile(), NULL);
171
172 return (GtkWidget *)g_object_new(AVATAR_MANIPULATION_TYPE, NULL);
173}
174
175GtkWidget*
176avatar_manipulation_new_from_wizard(void)
177{
178 auto self = avatar_manipulation_new();
179
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400180 /* in this mode, we want to automatically go to the PHOTO avatar state, unless one already exists */
181 if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid()) {
182 // check if there is a camera
183 if (Video::DeviceModel::instance().rowCount() > 0)
184 set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_PHOTO);
185 }
Nicolas Jagerb413b302016-05-06 11:41:32 -0400186
187 return self;
188}
189
190static void
191avatar_manipulation_class_init(AvatarManipulationClass *klass)
192{
193 G_OBJECT_CLASS(klass)->finalize = avatar_manipulation_finalize;
194 G_OBJECT_CLASS(klass)->dispose = avatar_manipulation_dispose;
195
196 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass), "/cx/ring/RingGnome/avatarmanipulation.ui");
197
198 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_views_and_controls);
199 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_controls);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400200 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_start_camera);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400201 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400202 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
203 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_photo);
204 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
205 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_edit);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400206 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, stack_views);
207 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, image_avatar);
208 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, vbox_selector);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400209 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_current);
210 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_photo);
211 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_box_edit);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400212}
213
214static void
215avatar_manipulation_init(AvatarManipulation *self)
216{
217 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
218 gtk_widget_init_template(GTK_WIDGET(self));
219
220 /* selector widget */
221 priv->selector_widget = gtk_drawing_area_new();
222 gtk_box_pack_start(GTK_BOX(priv->vbox_selector), priv->selector_widget, FALSE, TRUE, 0);
223
224 /* our desired size for the image area */
225 gtk_widget_set_size_request(priv->stack_views, VIDEO_WIDTH, VIDEO_HEIGHT);
226
227 /* Signals used to handle backing surface */
228 g_signal_connect(priv->selector_widget, "draw", G_CALLBACK (selector_widget_draw), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400229 g_signal_connect(priv->selector_widget, "configure-event", G_CALLBACK (selector_widget_configure_event), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400230 /* Event signals */
231 g_signal_connect(priv->selector_widget, "motion-notify-event", G_CALLBACK(selector_widget_motion_notify_event), self);
232 g_signal_connect(priv->selector_widget, "button-press-event", G_CALLBACK(selector_widget_button_press_event), self);
233 g_signal_connect(priv->selector_widget, "button-release-event", G_CALLBACK(selector_widget_button_release_event), self);
234 /* Ask to receive events the drawing area doesn't normally subscribe to */
235 gtk_widget_set_events (priv->selector_widget, gtk_widget_get_events (priv->selector_widget)
236 | GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
237 | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
238
239
240 /* signals */
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400241 g_signal_connect_swapped(priv->button_start_camera, "clicked", G_CALLBACK(start_camera), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400242 g_signal_connect_swapped(priv->button_choose_picture, "clicked", G_CALLBACK(choose_picture), self);
243 g_signal_connect_swapped(priv->button_take_photo, "clicked", G_CALLBACK(take_a_photo), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400244 g_signal_connect_swapped(priv->button_return_photo, "clicked", G_CALLBACK(return_to_previous_state), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400245 g_signal_connect_swapped(priv->button_set_avatar, "clicked", G_CALLBACK(set_avatar), self);
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400246 g_signal_connect_swapped(priv->button_return_edit, "clicked", G_CALLBACK(return_to_previous_state), self);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400247
248 set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
249
250 gtk_widget_show_all(priv->stack_views);
251}
252
253static void
254set_state(AvatarManipulation *self, AvatarManipulationState state)
255{
256 // note: this funciton does not check if the state transition is valid, this is assumed to have
257 // been done by the caller
258 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
259
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400260 // save prev state
261 priv->last_state = priv->state;
262
Nicolas Jagerb413b302016-05-06 11:41:32 -0400263 switch (state) {
264 case AVATAR_MANIPULATION_STATE_CURRENT:
265 {
266 /* get the current or default profile avatar */
267 auto photo = GlobalInstances::pixmapManipulator().contactPhoto(
268 ProfileModel::instance().selectedProfile()->person(),
269 QSize(AVATAR_WIDTH, AVATAR_HEIGHT),
270 false);
271 std::shared_ptr<GdkPixbuf> pixbuf_photo = photo.value<std::shared_ptr<GdkPixbuf>>();
272
273 if (photo.isValid()) {
274 gtk_image_set_from_pixbuf (GTK_IMAGE(priv->image_avatar), pixbuf_photo.get());
275 } else {
276 g_warning("invlid pixbuf");
277 }
278
279 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_avatar");
Nicolas Jagerb413b302016-05-06 11:41:32 -0400280
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400281 /* available actions: start camera (if available) or choose image */
282 if (Video::DeviceModel::instance().rowCount() > 0) {
283 // TODO: update if a video device gets inserted while in this state
284 gtk_widget_set_visible(priv->button_start_camera, true);
285 }
286 gtk_widget_set_visible(priv->button_box_current, true);
287 gtk_widget_set_visible(priv->button_box_photo, false);
288 gtk_widget_set_visible(priv->button_box_edit, false);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400289
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400290 /* make sure video widget and camera is not running */
291 if (priv->video_started_by_avatar_manipulation)
292 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400293 if (priv->video_widget) {
294 gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
295 priv->video_widget = NULL;
296 }
297 }
298 break;
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400299 case AVATAR_MANIPULATION_STATE_PHOTO:
Nicolas Jagerb413b302016-05-06 11:41:32 -0400300 {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400301 // start the video; if its not available we should not be in this state
302 priv->video_widget = video_widget_new();
303 g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
304 gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
305 gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
306 gtk_widget_set_visible(priv->video_widget, true);
307 gtk_stack_add_named(GTK_STACK(priv->stack_views), priv->video_widget, "page_photobooth");
308 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
Nicolas Jagerb413b302016-05-06 11:41:32 -0400309
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400310
311 /* local renderer, but set as "remote" so that it takes up the whole screen */
312 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
313 Video::PreviewManager::instance().previewRenderer(),
314 VIDEO_RENDERER_REMOTE);
315
316 if (!Video::PreviewManager::instance().isPreviewing()) {
317 priv->video_started_by_avatar_manipulation = TRUE;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400318 Video::PreviewManager::instance().startPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400319 } else {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400320 priv->video_started_by_avatar_manipulation = FALSE;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400321 }
322
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400323 /* available actions: take snapshot, return*/
324 gtk_widget_set_visible(priv->button_box_current, false);
325 gtk_widget_set_visible(priv->button_box_photo, true);
326 gtk_widget_set_visible(priv->button_box_edit, false);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400327 }
328 break;
329 case AVATAR_MANIPULATION_STATE_EDIT:
330 {
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400331 /* make sure video widget and camera is not running */
332 if (priv->video_started_by_avatar_manipulation)
333 Video::PreviewManager::instance().stopPreview();
Nicolas Jagerb413b302016-05-06 11:41:32 -0400334 if (priv->video_widget) {
335 gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
336 priv->video_widget = NULL;
337 }
338
339 /* reset the selector */
340 priv->origin[0] = 0;
341 priv->origin[1] = 0;
342 priv->length = INITIAL_LENTGH;
343
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400344 /* available actions: set avatar, return */
345 gtk_widget_set_visible(priv->button_box_current, false);
346 gtk_widget_set_visible(priv->button_box_photo, false);
347 gtk_widget_set_visible(priv->button_box_edit, true);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400348
349 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_edit_view");
350 }
351 break;
352 }
353
354 priv->state = state;
355}
356
357static void
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400358start_camera(AvatarManipulation *self)
359{
360 set_state(self, AVATAR_MANIPULATION_STATE_PHOTO);
361}
362
363static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400364take_a_photo(AvatarManipulation *self)
365{
366 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
367 video_widget_take_snapshot(VIDEO_WIDGET(priv->video_widget));
368}
369
370static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400371set_avatar(AvatarManipulation *self)
372{
373 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
374
375 gchar* png_buffer_signed = nullptr;
376 gsize png_buffer_size;
377 GError* error = nullptr;
378
379 /* get the selected zone */
380 GdkPixbuf *selector_pixbuf = gdk_pixbuf_new_subpixbuf(priv->pix_scaled, priv->origin[0], priv->origin[1],
381 priv->length, priv->length);
382
383 /* scale it */
384 GdkPixbuf* pixbuf_frame_resized = gdk_pixbuf_scale_simple(selector_pixbuf, AVATAR_WIDTH, AVATAR_HEIGHT,
385 GDK_INTERP_HYPER);
386
387 /* save the png in memory */
388 gdk_pixbuf_save_to_buffer(pixbuf_frame_resized, &png_buffer_signed, &png_buffer_size, "png", &error, NULL);
389 if (!png_buffer_signed) {
390 g_warning("(set_avatar) failed to save pixbuffer to png: %s\n", error->message);
391 g_error_free(error);
392 return;
393 }
394
395 /* convert buffer to QByteArray in base 64*/
396 QByteArray png_q_byte_array = QByteArray::fromRawData(png_buffer_signed, png_buffer_size).toBase64();
397
398 /* save in profile */
399 QVariant photo = GlobalInstances::pixmapManipulator().personPhoto(png_q_byte_array);
400 ProfileModel::instance().selectedProfile()->person()->setPhoto(photo);
401 ProfileModel::instance().selectedProfile()->save();
402
403 g_free(png_buffer_signed);
404 g_object_unref(selector_pixbuf);
405 g_object_unref(pixbuf_frame_resized);
406
407 set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
408}
409
410static void
Stepan Salenikovichae92efd2016-07-28 18:51:27 -0400411return_to_previous_state(AvatarManipulation *self)
412{
413 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
414 set_state(self, priv->last_state);
415}
416
417static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400418choose_picture(AvatarManipulation *self)
419{
420 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
421 GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
422 GtkFileFilter *filter = gtk_file_filter_new ();
423 gint res;
424
425 auto preview = gtk_image_new();
426
427 GtkWidget *ring_main_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
428
429 auto dialog = gtk_file_chooser_dialog_new (_("Open Avatar Image"),
430 GTK_WINDOW(ring_main_window),
431 action,
432 _("_Cancel"),
433 GTK_RESPONSE_CANCEL,
434 _("_Open"),
435 GTK_RESPONSE_ACCEPT,
436 NULL);
437
438 /* add filters */
439 gtk_file_filter_add_pattern (filter,"*.png");
440 gtk_file_filter_add_pattern (filter,"*.jpg");
441 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),filter);
442
443 /* add an image preview inside the file choose */
444 gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER(dialog), preview);
445 g_signal_connect (GTK_FILE_CHOOSER(dialog), "update-preview", G_CALLBACK (update_preview_cb), preview);
446
447 /* start the file chooser */
448 res = gtk_dialog_run (GTK_DIALOG(dialog)); /* blocks until the dialog is closed */
449
450 if (res == GTK_RESPONSE_ACCEPT) {
451 if(auto filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (dialog))) {
452 GError* error = nullptr; /* initialising to null avoid trouble... */
453
454 if (priv->pix_scaled) {
455 g_object_unref(priv->pix_scaled);
456 priv->pix_scaled = nullptr;
457 }
458 priv->pix_scaled = gdk_pixbuf_new_from_file_at_size (filename, VIDEO_WIDTH, VIDEO_HEIGHT, &error);
459
460 if (!error) {
461 set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
462 } else {
463 g_warning("(choose_picture) failed to load pixbuf from file: %s", error->message);
464 g_error_free(error);
465 }
466
467 g_free(filename);
468 } else {
469 g_warning("(choose_picture) filename empty");
470 }
471 }
472
473 gtk_widget_destroy(dialog);
474}
475
476static void
Nicolas Jagerb413b302016-05-06 11:41:32 -0400477update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
478{
479 gboolean have_preview = FALSE;
480 if (auto filename = gtk_file_chooser_get_preview_filename(file_chooser)) {
481 GError* error = nullptr;
482 auto pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 128, 128, &error);
483 if (!error) {
484 gtk_image_set_from_pixbuf(GTK_IMAGE(preview), pixbuf);
485 g_object_unref(pixbuf);
486 have_preview = TRUE;
487 } else {
488 // nothing to do, the file is probably not a picture
489 }
490 g_free (filename);
491 }
492 gtk_file_chooser_set_preview_widget_active(file_chooser, have_preview);
493}
494
495static gboolean
496selector_widget_configure_event(G_GNUC_UNUSED GtkWidget *widget,
497 G_GNUC_UNUSED GdkEventConfigure *event,
498 AvatarManipulation *self)
499{
500 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
501 GtkAllocation allocation;
502
503 gtk_widget_get_allocation (widget, &allocation);
504 if (priv->selector_widget_surface) {
505 cairo_surface_destroy(priv->selector_widget_surface);
506 priv->selector_widget_surface = nullptr;
507 }
508
509 priv->selector_widget_surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
510 CAIRO_CONTENT_COLOR,
511 allocation.width,
512 allocation.height);
513
514 /* TRUE = do not propagate */
515 return TRUE;
516}
517
518static gboolean
519selector_widget_draw(G_GNUC_UNUSED GtkWidget *widget, cairo_t *cr, AvatarManipulation *self)
520{
521 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
522
523 if(!priv->pix_scaled)
524 return FALSE;
525
526 int w = gdk_pixbuf_get_width(priv->pix_scaled);
527 int h = gdk_pixbuf_get_height(priv->pix_scaled);
528 gtk_widget_set_size_request(priv->selector_widget, w, h);
529
530 /* add the snapshot/picture on it */
531 gdk_cairo_set_source_pixbuf(cr, priv->pix_scaled, 0, 0);
532 cairo_paint(cr);
533
534 /* dark around the selector : */
535 cairo_set_source_rgba(cr, 0., 0., 0., 0.4);
536 cairo_set_line_width(cr, 2);
537 /* left */
538 cairo_rectangle(cr, 0, 0, priv->origin[0], VIDEO_HEIGHT);
539 cairo_fill(cr);
540 /* right */
541 cairo_rectangle(cr, priv->origin[0]+priv->length, 0, VIDEO_WIDTH, VIDEO_HEIGHT);
542 cairo_fill(cr);
543 /* up */
544 cairo_rectangle(cr, priv->origin[0], 0, priv->length, priv->origin[1]);
545 cairo_fill(cr);
546 /* down */
547 cairo_rectangle(cr, priv->origin[0], priv->origin[1]+priv->length, priv->length, VIDEO_HEIGHT);
548 cairo_fill(cr);
549
550 /* black border around the selector */
551 cairo_set_source_rgb(cr, 0., 0., 0.);
552 cairo_set_line_width(cr, 2);
553 cairo_rectangle(cr, priv->origin[0], priv->origin[1], priv->length, priv->length);
554 cairo_stroke(cr);
555
556 /* white border around the selector */
557 cairo_set_source_rgb(cr, 1., 1., 1.);
558 cairo_set_line_width(cr, 2);
559 cairo_rectangle(cr, priv->origin[0]+2, priv->origin[1]+2, priv->length-4, priv->length-4);
560 cairo_stroke(cr);
561
562 /* crosshair */
563 cairo_set_line_width(cr, 1);
564 double lg = (double)priv->length;
565 if(priv->do_action) {
566 /* horizontales */
567 cairo_move_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+2);
568 cairo_line_to(cr, priv->origin[0]+((int)(lg/3.0))+2, priv->origin[1]+priv->length-4);
569 cairo_move_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+2);
570 cairo_line_to(cr, priv->origin[0]+((int)(2.0*lg/3.0))+2, priv->origin[1]+priv->length-4);
571 /* verticales */
572 cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(lg/3.0))+2);
573 cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(lg/3.0))+2);
574 cairo_move_to(cr, priv->origin[0]+2, priv->origin[1]+((int)(2.0*lg/3.0))+2);
575 cairo_line_to(cr, priv->origin[0]+priv->length-4, priv->origin[1]+((int)(2.0*lg/3.0))+2);
576 cairo_stroke(cr);
577 }
578
579 return TRUE;
580}
581
582static gboolean
583selector_widget_motion_notify_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventMotion *event, AvatarManipulation *self)
584{
585 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
586
587 int x, y;
588 GdkModifierType state;
589 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
590
591 if (priv->do_action && state == priv->button_pressed) {
592 switch (priv->action_on_selector) {
593 case MOVE_SELECTOR:
594 update_and_draw( x - priv->relative_position[0], y - priv->relative_position[1] , self );
595 break;
596 case PICK_SELECTOR:
597 rescale( x, y , self );
598 break;
599 }
600 } else { /* is the pointer just over the selector ? */
601 if (x > priv->origin[0] && x < priv->origin[0] +priv->length
602 && y > priv->origin[1] && y < priv->origin[1] + priv->length) {
603 GdkWindow *window = gtk_widget_get_window( GTK_WIDGET(priv->selector_widget));
604 GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_FLEUR);
605 gdk_window_set_cursor(window, cursor);
606 priv->action_on_selector = MOVE_SELECTOR;
607 } else {
608 GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(priv->selector_widget));
609 GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_CROSSHAIR);
610 gdk_window_set_cursor(window, cursor);
611 priv->action_on_selector = PICK_SELECTOR;
612 }
613 }
614
615 return TRUE;
616}
617
618static gboolean
619selector_widget_button_press_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
620{
621 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
622
623 int x, y;
624 GdkModifierType state;
625 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
626
627 switch (priv->action_on_selector) {
628 case MOVE_SELECTOR:
629 priv->do_action = true;
630 priv->relative_position[0] = x - priv->origin[0];
631 priv->relative_position[1] = y - priv->origin[1];
632 break;
633 case PICK_SELECTOR:
634 priv->do_action = true;
635 priv->length = 0;
636 update_and_draw( x, y , self );
637 break;
638 }
639
640 priv->button_pressed = state;
641
642 return TRUE;
643}
644
645static gboolean
646selector_widget_button_release_event(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, AvatarManipulation *self)
647{
648 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
649
650 int x, y;
651 GdkModifierType state;
652 gdk_window_get_device_position (event->window, event->device, &x, &y, &state);
653
654 if (priv->do_action)
655 switch ( priv->action_on_selector ) {
656 case MOVE_SELECTOR:
657 update_and_draw( x-priv->relative_position[0], y-priv->relative_position[1], self );
658 break;
659 case PICK_SELECTOR:
660 update_and_draw( priv->origin[0], priv->origin[1], self );
661 break;
662 }
663
664 priv->do_action = false;
665
666 return TRUE;
667}
668
669static void
670update_and_draw(gdouble x, gdouble y, AvatarManipulation *self)
671{
672 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
673
674 GdkRectangle update_rect;
675 cairo_t *cr;
676
677 if (!priv->pix_scaled) {
678 g_warning("(update_and_draw) pix_scaled is null");
679 return;
680 }
681 auto width = gdk_pixbuf_get_width(priv->pix_scaled);
682 auto height = gdk_pixbuf_get_height(priv->pix_scaled);
683
684 update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 30;
685 update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 30;
686 update_rect.width = width - update_rect.x;
687 update_rect.height = height - update_rect.y;
688
689 if (x > width - priv->length)
690 priv->origin[0] = (x > width - priv->length) ? width - priv->length : x;
691 else
692 priv->origin[0] = (x > 0) ? x : 0;
693
694 if (y > height - priv->length )
695 priv->origin[1] = (y > height - priv->length ) ? height - priv->length : y;
696 else
697 priv->origin[1] = (y > 0) ? y : 0;
698
699 /* cairo operations */
700 cr = cairo_create(priv->selector_widget_surface);
701 gdk_cairo_rectangle(cr, &update_rect);
702 cairo_fill(cr);
703 cairo_destroy(cr);
704
705 /* invalidate the affected region */
706 gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
707}
708
709static void
710rescale(gdouble x, gdouble y, AvatarManipulation *self)
711{
712 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
713
714 GdkRectangle update_rect;
715 cairo_t *cr;
716
717 if (!priv->pix_scaled) {
718 g_warning("(rescale) pix_scaled is null");
719 return;
720 }
721 auto width = gdk_pixbuf_get_width(priv->pix_scaled);
722 auto height = gdk_pixbuf_get_height(priv->pix_scaled);
723
724 update_rect.x = ( ( x - priv->origin[0] < 0 ) ? x : priv->origin[0] ) - 3;
725 update_rect.y = ( ( y - priv->origin[1] < 0 ) ? y : priv->origin[1] ) - 3;
726 update_rect.width = width - update_rect.x;
727 update_rect.height = height - update_rect.y;
728
729 int old_length = priv->length;
730 priv->length = sqrt( (priv->origin[0] - x)*(priv->origin[0] - x) + (priv->origin[1] - y)*(priv->origin[1] - y) );
731
732 if (priv->length < 10)
733 priv->length = 10;
734
735 if (priv->origin[0] + priv->length > width)
736 priv->length = old_length;
737
738 if (priv->origin[1] + priv->length > height)
739 priv->length = old_length;
740
741 /* cairo operations */
742 cr = cairo_create(priv->selector_widget_surface);
743
744 gdk_cairo_rectangle(cr, &update_rect);
745 cairo_fill(cr);
746
747 cairo_destroy(cr);
748
749 /* invalidate the affected region */
750 gdk_window_invalidate_rect(gtk_widget_get_window (priv->selector_widget), &update_rect, FALSE);
751}
752
753static void
754got_snapshot(AvatarManipulation *self)
755{
756 AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
757 GdkPixbuf* pix = video_widget_get_snapshot(VIDEO_WIDGET(priv->video_widget));
758
759 /* in this case we have to deal with the aspect ratio */
760 float w = ((float)gdk_pixbuf_get_width(pix));
761 float h = ((float)gdk_pixbuf_get_height(pix));
762 const float ratio = h/w;
763 const float W = VIDEO_WIDTH;
764 const float H = VIDEO_HEIGHT;
765
766 if (h > w) {
767 h = H;
768 w = h / ratio;
769 } else {
770 w = W;
771 h = w * ratio;
772 }
773
774 if (priv->pix_scaled) {
775 g_object_unref(priv->pix_scaled);
776 priv->pix_scaled = nullptr;
777 }
778 priv->pix_scaled = gdk_pixbuf_scale_simple(pix, w, h, GDK_INTERP_HYPER);
779
780 set_state(self, AVATAR_MANIPULATION_STATE_EDIT);
781}