accountcreationwizard: close preview when hidden

Whenever the preview is created and run in the account creation
wizard, there is no way to stop it. This is very annoying because
it grabs the webcam rights without releasing them, and the camera
led stays on, giving the user the impression to be observed.

In this patch we add a mechanism similar to what is done in the
general settings tab: we destroy and create the preview each time
the preview is made visible / hidden. While not the most elegant
solution it allows us to run the preview if and only if it is
displayed & stop it otherwise.

Also, we fix various issues in the account view:
 - when + icon is double clicked, the settings space becomes blank.
   Now if + is clicked while account wizard is open, the account
   wizard gets closed.
 - when an account is selected in the account selection tab while
   the wizard is displayed, nothing "seems to happen". Instead,
   quit the wizard and open the selected tab.
 - when account view is openened for the first time, the settings
   space is blank. Instead, default select an account.

Change-Id: I1e12333e654f70d590886e2aa4f5112154f3068a
Reviewed-by: Sebastien Blin <sebastien.blin@savoirfairelinux.com>
diff --git a/src/accountcreationwizard.cpp b/src/accountcreationwizard.cpp
index 96dd308..8316bbd 100644
--- a/src/accountcreationwizard.cpp
+++ b/src/accountcreationwizard.cpp
@@ -1,6 +1,7 @@
 /*
  *  Copyright (C) 2016-2018 Savoir-faire Linux Inc.
  *  Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
+ *  Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -17,7 +18,6 @@
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
  */
 
-
 // GTK+ related
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
@@ -36,7 +36,6 @@
 #include "accountcreationwizard.h"
 #include "usernameregistrationbox.h"
 
-
 struct _AccountCreationWizard
 {
     GtkBox parent;
@@ -116,25 +115,13 @@
 static guint account_creation_wizard_signals[LAST_SIGNAL] = { 0 };
 
 static void
-destroy_avatar_manipulation(AccountCreationWizard *view)
-{
-    AccountCreationWizardPrivate *priv = ACCOUNT_CREATION_WIZARD_GET_PRIVATE(view);
-
-    /* make sure the AvatarManipulation widget is destroyed so the VideoWidget inside of it is too;
-     * NOTE: destorying its parent (box_avatarselection) first will cause a mystery 'BadDrawable'
-     * crash due to X error */
-    if (priv->avatar_manipulation)
-    {
-        gtk_container_remove(GTK_CONTAINER(priv->box_avatarselection), priv->avatar_manipulation);
-        priv->avatar_manipulation = nullptr;
-    }
-}
-
-static void
 account_creation_wizard_dispose(GObject *object)
 {
     AccountCreationWizardPrivate *priv = ACCOUNT_CREATION_WIZARD_GET_PRIVATE(object);
-    destroy_avatar_manipulation(ACCOUNT_CREATION_WIZARD(object));
+
+    // make sure preview is stopped and destroyed
+    account_creation_wizard_show_preview(ACCOUNT_CREATION_WIZARD(object), FALSE);
+
     QObject::disconnect(priv->account_state_changed);
     G_OBJECT_CLASS(account_creation_wizard_parent_class)->dispose(object);
 }
@@ -257,7 +244,7 @@
     priv->username = new QString(username);
     priv->password = new QString(password);
 
-    g_object_ref(view); // ref so its not desroyed too early
+    g_object_ref(view); // ref so its not destroyed too early
 
     /* create account and set UPnP enabled, as its not by default in the daemon */
     Account *account = nullptr;
@@ -412,6 +399,10 @@
 
     auto status = create_ring_account(win, display_name, username, password, nullptr, nullptr);
 
+    // Now that we've use the preview to generate the avatar, we can safely close it. Don't
+    // assume owner will do it for us, this might not always be the case
+    account_creation_wizard_show_preview(win, FALSE);
+
     g_free(display_name);
     g_free(password);
     g_free(username);
@@ -508,6 +499,14 @@
 }
 
 static void
+account_creation_previous_clicked(G_GNUC_UNUSED GtkButton *button, AccountCreationWizard *view)
+{
+    // make sure to stop preview before going back to choose account type
+    account_creation_wizard_show_preview(view, FALSE);
+    show_choose_account_type(view);
+}
+
+static void
 show_existing_account(AccountCreationWizard *view)
 {
     AccountCreationWizardPrivate *priv = ACCOUNT_CREATION_WIZARD_GET_PRIVATE(view);
@@ -515,24 +514,9 @@
 }
 
 static void
-show_account_creation(AccountCreationWizard *win)
-{
-    AccountCreationWizardPrivate *priv = ACCOUNT_CREATION_WIZARD_GET_PRIVATE(win);
-
-    /* avatar manipulation widget */
-    if (!priv->avatar_manipulation)
-    {
-        priv->avatar_manipulation = avatar_manipulation_new_from_wizard();
-        gtk_box_pack_start(GTK_BOX(priv->box_avatarselection), priv->avatar_manipulation, true, true, 0);
-    }
-
-    gtk_stack_set_visible_child(GTK_STACK(priv->stack_account_creation), priv->account_creation);
-}
-
-static void
 wizard_cancel_clicked(G_GNUC_UNUSED GtkButton *button, AccountCreationWizard *view)
 {
-    g_signal_emit(G_OBJECT(view), account_creation_wizard_signals[ACCOUNT_CREATION_CANCELED], 0);
+    account_creation_wizard_cancel(view);
 }
 
 static void
@@ -660,14 +644,14 @@
     gtk_widget_set_visible(priv->button_wizard_cancel, show_cancel_button);
 
     /* choose_account_type signals */
-    g_signal_connect_swapped(priv->button_new_account, "clicked", G_CALLBACK(show_account_creation), view);
+    g_signal_connect_swapped(priv->button_new_account, "clicked", G_CALLBACK(account_creation_wizard_show_preview), view);
     g_signal_connect_swapped(priv->button_existing_account, "clicked", G_CALLBACK(show_existing_account), view);
     g_signal_connect(priv->button_wizard_cancel, "clicked", G_CALLBACK(wizard_cancel_clicked), view);
 
     /* account_creation signals */
-    g_signal_connect_swapped(priv->button_account_creation_previous, "clicked", G_CALLBACK(show_choose_account_type), view);
     g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entries_new_account_changed), view);
     g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), view);
+    g_signal_connect(priv->button_account_creation_previous, "clicked", G_CALLBACK(account_creation_previous_clicked), view);
     g_signal_connect_swapped(priv->entry_password, "changed", G_CALLBACK(entries_new_account_changed), view);
     g_signal_connect_swapped(priv->entry_password_confirm, "changed", G_CALLBACK(entries_new_account_changed), view);
     g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entries_new_account_changed), view);
@@ -696,3 +680,32 @@
     build_creation_wizard_view(ACCOUNT_CREATION_WIZARD(view), show_cancel_button);
     return (GtkWidget *)view;
 }
+
+void
+account_creation_wizard_show_preview(AccountCreationWizard *win, gboolean show_preview)
+{
+    AccountCreationWizardPrivate *priv = ACCOUNT_CREATION_WIZARD_GET_PRIVATE(win);
+
+    /* Similarily to general settings view, we construct and destroy the avatar manipulation widget
+       each time the profile is made visible / hidden. While not the most elegant solution, this
+       allows us to run the preview if and only if it is displayed, and always stop it when hidden. */
+    if (show_preview && !priv->avatar_manipulation) {
+        priv->avatar_manipulation = avatar_manipulation_new_from_wizard();
+        gtk_box_pack_start(GTK_BOX(priv->box_avatarselection), priv->avatar_manipulation, true, true, 0);
+        gtk_stack_set_visible_child(GTK_STACK(priv->stack_account_creation), priv->account_creation);
+    } else if (priv->avatar_manipulation) {
+        /* make sure the AvatarManipulation widget is destroyed so the VideoWidget inside of it is too;
+         * NOTE: destorying its parent (box_avatarselection) first will cause a mystery 'BadDrawable'
+         * crash due to X error */
+        gtk_container_remove(GTK_CONTAINER(priv->box_avatarselection), priv->avatar_manipulation);
+        priv->avatar_manipulation = nullptr;
+    }
+}
+
+void
+account_creation_wizard_cancel(AccountCreationWizard *win)
+{
+    // make sure to stop preview before doing something else
+    account_creation_wizard_show_preview(win, FALSE);
+    g_signal_emit(G_OBJECT(win), account_creation_wizard_signals[ACCOUNT_CREATION_CANCELED], 0);
+}
diff --git a/src/accountcreationwizard.h b/src/accountcreationwizard.h
index 12634f9..0c6385b 100644
--- a/src/accountcreationwizard.h
+++ b/src/accountcreationwizard.h
@@ -35,4 +35,7 @@
 GType      account_creation_wizard_get_type      (void) G_GNUC_CONST;
 GtkWidget *account_creation_wizard_new           (bool cancel_button);
 
+void       account_creation_wizard_show_preview  (AccountCreationWizard *win, gboolean show_preview = TRUE);
+void       account_creation_wizard_cancel        (AccountCreationWizard *win);
+
 G_END_DECLS
diff --git a/src/accountview.cpp b/src/accountview.cpp
index 1351c32..b234690 100644
--- a/src/accountview.cpp
+++ b/src/accountview.cpp
@@ -126,18 +126,27 @@
     auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
     if(IS_ACCOUNT_CREATION_WIZARD(old_view))
     {
-        /* When using the account creation wizard, The user might create
-         * accounts that will be deleted by the daemon. The fact that the
-         * selection has changed should be ignored. The account creation wizard
-         * should be responsible to remove itself of the stack and then call
-         * account_selection_changed.
-         */
+        // cancel account creation, this will call hide_account_creation_wizard via callback
+        account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
         return;
     }
 
     QModelIndex account_idx = get_index_from_selection(selection);
     if (!account_idx.isValid()) {
-        /* it nothing is slected, simply display something empty */
+        g_debug("accountview: invalid account selection");
+
+        /* If no account is selected, check for account list size. If not 0, select
+           the first account. */
+        GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
+        if (gtk_tree_model_iter_n_children(model, NULL)) {
+            g_debug("accountview: other accounts are available, selecting first one");
+            GtkTreePath *path = gtk_tree_path_new_from_indices(0, -1);
+            gtk_tree_selection_select_path(gtk_tree_view_get_selection (GTK_TREE_VIEW(priv->treeview_account_list)), path);
+            gtk_tree_path_free(path);
+            return;
+        }
+
+        /* there is no account to select, display empty */
         GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
         gtk_widget_show(empty_box);
         gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
@@ -323,6 +332,7 @@
     auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
     if (IS_ACCOUNT_CREATION_WIZARD(old_view))
     {
+        account_creation_wizard_show_preview(ACCOUNT_CREATION_WIZARD(old_view), FALSE);
         gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
     }
 
@@ -369,7 +379,13 @@
         Account::Protocol protocol = qvariant_cast<Account::Protocol>(idx.data((int)ProtocolModel::Role::Protocol));
         if (protocol == Account::Protocol::RING)
         {
-            show_account_creation_wizard(view);
+            auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
+            if (IS_ACCOUNT_CREATION_WIZARD(old_view))
+            {
+                account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
+            } else {
+                show_account_creation_wizard(view);
+            }
         }
         else
         {
@@ -556,3 +572,23 @@
     priv->accountInfo_ = &accountInfo;
     return (GtkWidget *)view;
 }
+
+void
+account_settings_view_show(AccountView *view, gboolean show) {
+    AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
+
+    /* If displayed stack element is still the placeholder element, thats is it hasn't
+       been initialized yet, change account selection. */
+    if (show && strcmp(gtk_stack_get_visible_child_name (GTK_STACK(priv->stack_account)), "placeholder") == 0) {
+        auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
+        account_selection_changed(selection, view);
+        return;
+    }
+
+    /* If account creation wizard is currently open and settings get closed, cancel so
+       the preview is stopped properly. */
+    auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
+    if (!show && IS_ACCOUNT_CREATION_WIZARD(old_view)) {
+        account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
+    }
+}
diff --git a/src/accountview.h b/src/accountview.h
index 35d97f4..3d295b5 100644
--- a/src/accountview.h
+++ b/src/accountview.h
@@ -40,6 +40,8 @@
 GType      account_view_get_type      (void) G_GNUC_CONST;
 GtkWidget *account_view_new           (AccountInfoPointer const & accountInfo);
 
+void       account_settings_view_show (AccountView *view, gboolean show);
+
 G_END_DECLS
 
 #endif /* _ACCOUNTVIEW_H */
diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp
index af38bab..e6144ab 100644
--- a/src/ringmainwindow.cpp
+++ b/src/ringmainwindow.cpp
@@ -348,6 +348,7 @@
     auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
 
     if (gtk_toggle_button_get_active(navbutton)) {
+        account_settings_view_show(ACCOUNT_VIEW(priv->account_settings_view), TRUE);
         gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
         priv->last_settings_view = priv->account_settings_view;
     }
@@ -1175,6 +1176,7 @@
     gtk_widget_hide(widgets->hbox_settings);
 
     /* make sure video preview is stopped, in case it was started */
+    account_settings_view_show(ACCOUNT_VIEW(widgets->account_settings_view), FALSE);
     media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(widgets->media_settings_view), FALSE);
     general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(widgets->general_settings_view),
                                        FALSE);