improve avatar manipulation
Changes interface to mimick the one in Gnome Contacts:
- no more trash or export buttons
- the first choice is to take a photo or import
- adds return/undo buttons during selection process
Change-Id: I432f4bad48c1379f6fb26569edea32a24cbb98e9
Tuleap: #840
diff --git a/src/avatarmanipulation.cpp b/src/avatarmanipulation.cpp
index 2578f21..35148cd 100644
--- a/src/avatarmanipulation.cpp
+++ b/src/avatarmanipulation.cpp
@@ -72,11 +72,18 @@
GtkWidget *video_widget;
GtkWidget *box_views_and_controls;
GtkWidget *box_controls;
- GtkWidget *button_take_photo;
+
+ GtkWidget *button_box_current;
+ GtkWidget *button_box_photo;
+ GtkWidget *button_box_edit;
+
+ GtkWidget *button_start_camera;
GtkWidget *button_choose_picture;
- GtkWidget *button_trash_avatar;
+ GtkWidget *button_take_photo;
+ GtkWidget *button_return_photo;
GtkWidget *button_set_avatar;
- GtkWidget *button_export_avatar;
+ GtkWidget *button_return_edit;
+
GtkWidget *selector_widget;
GtkWidget *stack_views;
GtkWidget *image_avatar;
@@ -93,6 +100,13 @@
GdkModifierType button_pressed;
AvatarManipulationState state;
+ AvatarManipulationState last_state;
+
+ /* this is used to keep track of the state of the preview when the camera is used to take a
+ * photo; if a call is in progress, then the preview should already be started and we don't want
+ * to stop it when the settings are closed, in this case
+ */
+ gboolean video_started_by_avatar_manipulation;
};
G_DEFINE_TYPE_WITH_PRIVATE(AvatarManipulation, avatar_manipulation, GTK_TYPE_BOX);
@@ -102,11 +116,11 @@
static void set_state(AvatarManipulation *self, AvatarManipulationState state);
+static void start_camera(AvatarManipulation *self);
static void take_a_photo(AvatarManipulation *self);
static void choose_picture(AvatarManipulation *self);
-static void export_avatar(AvatarManipulation *self);
+static void return_to_previous_state(AvatarManipulation *self);
static void update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview);
-static void trash_photo(AvatarManipulation *self);
static void set_avatar(AvatarManipulation *self);
static void got_snapshot(AvatarManipulation *parent);
@@ -125,7 +139,8 @@
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(object);
/* make sure we stop the preview and the video widget */
- Video::PreviewManager::instance().stopPreview();
+ if (priv->video_started_by_avatar_manipulation)
+ Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
@@ -162,9 +177,12 @@
{
auto self = avatar_manipulation_new();
- /* in this mode, we want to automatically go to the new avatar state, unless one already exists */
- if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid())
- set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_NEW);
+ /* in this mode, we want to automatically go to the PHOTO avatar state, unless one already exists */
+ if (!ProfileModel::instance().selectedProfile()->person()->photo().isValid()) {
+ // check if there is a camera
+ if (Video::DeviceModel::instance().rowCount() > 0)
+ set_state(AVATAR_MANIPULATION(self), AVATAR_MANIPULATION_STATE_PHOTO);
+ }
return self;
}
@@ -179,14 +197,18 @@
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_views_and_controls);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, box_controls);
- gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
- gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
- gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_trash_avatar);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_start_camera);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_choose_picture);
- gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_export_avatar);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_take_photo);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_photo);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_set_avatar);
+ gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AvatarManipulation, button_return_edit);
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, vbox_selector);
+ 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);
}
static void
@@ -204,7 +226,7 @@
/* 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);
+ 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);
@@ -216,11 +238,12 @@
/* 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);
g_signal_connect_swapped(priv->button_take_photo, "clicked", G_CALLBACK(take_a_photo), self);
+ g_signal_connect_swapped(priv->button_return_photo, "clicked", G_CALLBACK(return_to_previous_state), self);
g_signal_connect_swapped(priv->button_set_avatar, "clicked", G_CALLBACK(set_avatar), self);
- g_signal_connect_swapped(priv->button_trash_avatar, "clicked", G_CALLBACK(trash_photo), self);
- g_signal_connect_swapped(priv->button_export_avatar, "clicked", G_CALLBACK(export_avatar), self);
+ g_signal_connect_swapped(priv->button_return_edit, "clicked", G_CALLBACK(return_to_previous_state), self);
set_state(self, AVATAR_MANIPULATION_STATE_CURRENT);
@@ -234,6 +257,9 @@
// been done by the caller
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
+ // save prev state
+ priv->last_state = priv->state;
+
switch (state) {
case AVATAR_MANIPULATION_STATE_CURRENT:
{
@@ -251,62 +277,60 @@
}
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_avatar");
- // gtk_widget_set_size_request(priv->video_widget, VIDEO_WIDTH, VIDEO_HEIGHT);
- /* available actions: trash or export avatar */
- gtk_widget_set_visible(priv->button_trash_avatar, true);
- gtk_widget_set_visible(priv->button_export_avatar, true);
- gtk_widget_set_visible(priv->button_set_avatar, false);
- gtk_widget_set_visible(priv->button_take_photo, false);
- gtk_widget_set_visible(priv->button_choose_picture, false);
+ /* available actions: start camera (if available) or choose image */
+ if (Video::DeviceModel::instance().rowCount() > 0) {
+ // TODO: update if a video device gets inserted while in this state
+ gtk_widget_set_visible(priv->button_start_camera, true);
+ }
+ gtk_widget_set_visible(priv->button_box_current, true);
+ gtk_widget_set_visible(priv->button_box_photo, false);
+ gtk_widget_set_visible(priv->button_box_edit, false);
- /* make sure video widget and camera is no longer running */
- Video::PreviewManager::instance().stopPreview();
+ /* make sure video widget and camera is not running */
+ if (priv->video_started_by_avatar_manipulation)
+ Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
}
}
break;
- case AVATAR_MANIPULATION_STATE_NEW:
+ case AVATAR_MANIPULATION_STATE_PHOTO:
{
- /* check if a video device is available, if so show the camera stream; otherwise we can
- * only choose a file, and we keep showing the current/default avatar
- *
- * TODO: update if a video device gets inserted while in this state
- */
- if (Video::DeviceModel::instance().rowCount() > 0) {
- priv->video_widget = video_widget_new();
- // gtk_widget_set_size_request(priv->video_widget, VIDEO_WIDTH, VIDEO_HEIGHT);
- video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
- Video::PreviewManager::instance().previewRenderer(),
- VIDEO_RENDERER_REMOTE);
- g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
- gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
- gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
- gtk_widget_set_visible(priv->video_widget, true);
- gtk_stack_add_named(GTK_STACK(priv->stack_views), priv->video_widget, "page_photobooth");
- gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
+ // start the video; if its not available we should not be in this state
+ priv->video_widget = video_widget_new();
+ g_signal_connect_swapped(priv->video_widget, "snapshot-taken", G_CALLBACK (got_snapshot), self);
+ gtk_widget_set_vexpand_set(priv->video_widget, FALSE);
+ gtk_widget_set_hexpand_set(priv->video_widget, FALSE);
+ gtk_widget_set_visible(priv->video_widget, true);
+ gtk_stack_add_named(GTK_STACK(priv->stack_views), priv->video_widget, "page_photobooth");
+ gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_photobooth");
+
+ /* local renderer, but set as "remote" so that it takes up the whole screen */
+ video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
+ Video::PreviewManager::instance().previewRenderer(),
+ VIDEO_RENDERER_REMOTE);
+
+ if (!Video::PreviewManager::instance().isPreviewing()) {
+ priv->video_started_by_avatar_manipulation = TRUE;
Video::PreviewManager::instance().startPreview();
-
- /* available action in this case: take snapshot */
- gtk_widget_set_visible(priv->button_take_photo, true);
} else {
- gtk_widget_set_visible(priv->button_take_photo, false);
+ priv->video_started_by_avatar_manipulation = FALSE;
}
- /* available actions: choose file */
- gtk_widget_set_visible(priv->button_choose_picture, true);
- gtk_widget_set_visible(priv->button_trash_avatar, false);
- gtk_widget_set_visible(priv->button_export_avatar, false);
- gtk_widget_set_visible(priv->button_set_avatar, false);
+ /* available actions: take snapshot, return*/
+ gtk_widget_set_visible(priv->button_box_current, false);
+ gtk_widget_set_visible(priv->button_box_photo, true);
+ gtk_widget_set_visible(priv->button_box_edit, false);
}
break;
case AVATAR_MANIPULATION_STATE_EDIT:
{
- /* make sure video widget and camera is no longer running */
- Video::PreviewManager::instance().stopPreview();
+ /* make sure video widget and camera is not running */
+ if (priv->video_started_by_avatar_manipulation)
+ Video::PreviewManager::instance().stopPreview();
if (priv->video_widget) {
gtk_container_remove(GTK_CONTAINER(priv->stack_views), priv->video_widget);
priv->video_widget = NULL;
@@ -317,12 +341,10 @@
priv->origin[1] = 0;
priv->length = INITIAL_LENTGH;
- /* available actions: set avatar, trash avatar */
- gtk_widget_set_visible(priv->button_set_avatar, true);
- gtk_widget_set_visible(priv->button_trash_avatar, true);
- gtk_widget_set_visible(priv->button_take_photo, false);
- gtk_widget_set_visible(priv->button_choose_picture, false);
- gtk_widget_set_visible(priv->button_export_avatar, false);
+ /* available actions: set avatar, return */
+ gtk_widget_set_visible(priv->button_box_current, false);
+ gtk_widget_set_visible(priv->button_box_photo, false);
+ gtk_widget_set_visible(priv->button_box_edit, true);
gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_views), "page_edit_view");
}
@@ -333,6 +355,12 @@
}
static void
+start_camera(AvatarManipulation *self)
+{
+ set_state(self, AVATAR_MANIPULATION_STATE_PHOTO);
+}
+
+static void
take_a_photo(AvatarManipulation *self)
{
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
@@ -340,16 +368,6 @@
}
static void
-trash_photo(AvatarManipulation *self)
-{
- /* clear the avatar in the profile */
- ProfileModel::instance().selectedProfile()->person()->setPhoto(QVariant());
- ProfileModel::instance().selectedProfile()->save();
-
- set_state(self, AVATAR_MANIPULATION_STATE_NEW);
-}
-
-static void
set_avatar(AvatarManipulation *self)
{
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
@@ -390,6 +408,13 @@
}
static void
+return_to_previous_state(AvatarManipulation *self)
+{
+ AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
+ set_state(self, priv->last_state);
+}
+
+static void
choose_picture(AvatarManipulation *self)
{
AvatarManipulationPrivate *priv = AVATAR_MANIPULATION_GET_PRIVATE(self);
@@ -449,44 +474,6 @@
}
static void
-export_avatar(AvatarManipulation *self)
-{
- GtkWidget *ring_main_window = gtk_widget_get_toplevel(GTK_WIDGET(self));
-
- auto dialog = gtk_file_chooser_dialog_new(_("Save Avatar Image"),
- GTK_WINDOW(ring_main_window),
- GTK_FILE_CHOOSER_ACTION_SAVE,
- _("_Cancel"),
- GTK_RESPONSE_CANCEL,
- _("_Save"),
- GTK_RESPONSE_ACCEPT,
- NULL);
-
- gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
-
- /* start the file chooser */
- auto res = gtk_dialog_run (GTK_DIALOG (dialog));
- if (res == GTK_RESPONSE_ACCEPT) {
- if (auto filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))) {
- GError* error = nullptr;
- auto photo = ProfileModel::instance().selectedProfile()->person()->photo();
- std::shared_ptr<GdkPixbuf> pixbuf_photo = photo.value<std::shared_ptr<GdkPixbuf>>();
-
- if (photo.isValid()) {
- gdk_pixbuf_save(pixbuf_photo.get(), filename, "png", &error, NULL);
- if (error){
- g_warning("(export_avatar) could not save avatar to file: %s\n", error->message);
- g_error_free(error);
- }
- }
- g_free (filename);
- }
- }
-
- gtk_widget_destroy (dialog);
-}
-
-static void
update_preview_cb(GtkFileChooser *file_chooser, GtkWidget *preview)
{
gboolean have_preview = FALSE;