[gnome-contacts] avatar-selection: Use popover and window for crop/cheese



commit 1014e347434e83bc85678ae0c22fd095632a5b1c
Author: Elias Entrup <elias-git flump de>
Date:   Tue Feb 13 18:35:05 2018 +0100

    avatar-selection: Use popover and window for crop/cheese
    
    The avatar selection used to happen in a separate window
    including cropping and webcam.
    
    This commit introduces a Popover instead. For cropping
    and webcam, a separate window is opened again. These design
    changes were done according to new mockups.
    
    https://gitlab.gnome.org/GNOME/gnome-contacts/issues/84

 data/contacts.gresource.xml            |   1 +
 data/ui/contacts-avatar-selector.ui    | 335 +++++++--------------------------
 data/ui/contacts-crop-cheese-dialog.ui | 116 ++++++++++++
 data/ui/style.css                      |   5 +
 po/POTFILES.in                         |   2 +
 po/POTFILES.skip                       |   1 +
 src/contacts-avatar-selector.vala      | 292 +++++++++-------------------
 src/contacts-contact-editor.vala       |   8 +-
 src/contacts-crop-cheese-dialog.vala   | 114 +++++++++++
 src/contacts-utils.vala                |  10 +
 src/meson.build                        |   1 +
 11 files changed, 410 insertions(+), 475 deletions(-)
---
diff --git a/data/contacts.gresource.xml b/data/contacts.gresource.xml
index a20079e..6564cfd 100644
--- a/data/contacts.gresource.xml
+++ b/data/contacts.gresource.xml
@@ -9,6 +9,7 @@
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-editor.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-pane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-contact-sheet.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/contacts-crop-cheese-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-in-app-notification.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-link-suggestion-grid.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/contacts-linked-personas-dialog.ui</file>
diff --git a/data/ui/contacts-avatar-selector.ui b/data/ui/contacts-avatar-selector.ui
index e117827..180ae6b 100644
--- a/data/ui/contacts-avatar-selector.ui
+++ b/data/ui/contacts-avatar-selector.ui
@@ -1,293 +1,90 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.20"/>
-  <template class="ContactsAvatarSelector" parent="GtkDialog">
-    <property name="visible">True</property>
-    <property name="title" translatable="yes">Select Picture</property>
-    <property name="modal">True</property>
-    <style>
-      <class name="contacts-avatar-dialog"/>
-    </style>
-    <child internal-child="vbox">
+  <requires lib="gtk+" version="3.22"/>
+  <template class="ContactsAvatarSelector" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <child>
       <object class="GtkBox">
         <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">10</property>
+        <property name="margin_right">10</property>
+        <property name="margin_top">10</property>
+        <property name="margin_bottom">10</property>
         <property name="orientation">vertical</property>
+        <property name="spacing">10</property>
         <child>
-          <object class="GtkGrid" id="grid">
+          <object class="GtkFlowBox" id="personas_thumbnail_grid">
             <property name="visible">True</property>
-            <property name="border_width">8</property>
-            <property name="column_spacing">16</property>
-            <property name="row_spacing">11</property>
-            <child>
-            </child>
-              <!-- ContactFrame -->
-              <placeholder/>
+            <property name="can_focus">False</property>
+            <property name="column_spacing">5</property>
+            <property name="row_spacing">5</property>
+            <property name="selection_mode">none</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkFlowBox" id="stock_thumbnail_grid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="column_spacing">5</property>
+            <property name="row_spacing">5</property>
+            <property name="min_children_per_line">5</property>
+            <property name="max_children_per_line">8</property>
+            <property name="selection_mode">none</property>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">center</property>
+            <property name="spacing">10</property>
             <child>
-              <object class="GtkLabel" id="contact_name_label">
-                <property name="visible">True</property>
-                <property name="halign">start</property>
-                <property name="valign">start</property>
-                <property name="hexpand">True</property>
-                <property name="margin_top">4</property>
-                <property name="ellipsize">end</property>
-                <property name="label" translatable="yes">New Contact</property>
-                <style>
-                  <class name="contact-display-name"/>
-                </style>
+              <object class="GtkButton" id="cheese_button">
+                <property name="label" translatable="yes">Take a Picture...</property>
+                <property name="can_focus">True</property>
+                <property name="no_show_all">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="on_cheese_clicked" swapped="no"/>
               </object>
               <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
               </packing>
             </child>
             <child>
-              <object class="GtkFrame">
+              <object class="GtkButton">
+                <property name="label" translatable="yes">Select a File...</property>
                 <property name="visible">True</property>
-                <style>
-                  <class name="contacts-avatar-frame"/>
-                </style>
-                <child>
-                  <object class="GtkStack" id="views_stack">
-                    <property name="visible">True</property>
-                    <child>
-                      <object class="GtkGrid" id="thumbnail_page">
-                        <property name="visible">True</property>
-                        <property name="orientation">vertical</property>
-                        <child>
-                          <object class="GtkScrolledWindow">
-                            <property name="visible">True</property>
-                            <property name="hscrollbar_policy">never</property>
-                            <property name="vscrollbar_policy">automatic</property>
-                            <property name="hexpand">True</property>
-                            <property name="vexpand">True</property>
-                            <property name="height_request">300</property>
-                            <child>
-                              <object class="GtkBox">
-                                <property name="visible">True</property>
-                                <property name="orientation">vertical</property>
-                                <child>
-                                  <object class="GtkFlowBox" id="personas_thumbnail_grid">
-                                    <property name="visible">True</property>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkSeparator">
-                                    <property name="visible">True</property>
-                                    <property name="orientation">horizontal</property>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkFlowBox" id="stock_thumbnail_grid">
-                                    <property name="visible">True</property>
-                                    <property name="min_children_per_line">5</property>
-                                    <property name="max_children_per_line">8</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkActionBar">
-                            <property name="visible">True</property>
-                            <child>
-                              <object class="GtkBox" id="webcam_button_box">
-                                <property name="orientation">horizontal</property>
-                                <style>
-                                  <class name="linked"/>
-                                </style>
-                                <child>
-                                  <object class="GtkButton">
-                                    <property name="visible">True</property>
-                                    <signal name="clicked" handler="select_avatar_file_cb" swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">list-add-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkButton" id="webcam_button">
-                                    <property name="visible">True</property>
-                                    <property name="sensitive">False</property>
-                                    <signal name="clicked" handler="on_webcam_button_clicked" swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">camera-photo-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkButton">
-                                <property name="visible" bind-source="webcam_button_box" 
bind-property="visible" bind-flags="invert-boolean|sync-create" />
-                                <signal name="clicked" handler="select_avatar_file_cb" swapped="no"/>
-                                <child>
-                                  <object class="GtkImage">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="pixel_size">16</property>
-                                    <property name="icon_name">list-add-symbolic</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="name">thumbnail-page</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkGrid" id="crop_page">
-                        <property name="visible">True</property>
-                        <property name="orientation">vertical</property>
-                        <child>
-                          <object class="GtkActionBar">
-                            <property name="visible">True</property>
-                            <child>
-                              <object class="GtkBox">
-                                <property name="visible">True</property>
-                                <property name="orientation">horizontal</property>
-                                <style>
-                                  <class name="linked"/>
-                                </style>
-                                <child>
-                                  <object class="GtkButton">
-                                    <property name="visible">True</property>
-                                    <signal name="clicked" handler="on_crop_page_select_button_clicked" 
swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">object-select-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkButton">
-                                    <property name="visible">True</property>
-                                    <signal name="clicked" handler="on_crop_page_cancel_button_clicked" 
swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">edit-undo-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="top_attach">1</property>
-                            <property name="left_attach">0</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="name">crop-page</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkGrid" id="photobooth_page">
-                        <property name="orientation">vertical</property>
-                        <child>
-                          <object class="GtkActionBar">
-                            <property name="visible">True</property>
-                            <child>
-                              <object class="GtkBox">
-                                <property name="visible">True</property>
-                                <property name="orientation">horizontal</property>
-                                <style>
-                                  <class name="linked"/>
-                                </style>
-                                <child>
-                                  <object class="GtkButton">
-                                    <property name="visible">True</property>
-                                    <signal name="clicked" 
handler="on_photobooth_page_select_button_clicked" swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">object-select-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkButton">
-                                    <property name="visible">True</property>
-                                    <signal name="clicked" 
handler="on_photobooth_page_cancel_button_clicked" swapped="no"/>
-                                    <child>
-                                      <object class="GtkImage">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
-                                        <property name="pixel_size">16</property>
-                                        <property name="icon_name">edit-undo-symbolic</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="top_attach">1</property>
-                            <property name="left_attach">0</property>
-                          </packing>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="name">photobooth-page</property>
-                      </packing>
-                    </child>
-                  </object>
-                </child>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <signal name="clicked" handler="on_file_clicked" swapped="no"/>
               </object>
               <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-                <property name="width">2</property>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
               </packing>
             </child>
           </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
         </child>
       </object>
     </child>
-
-    <child type="action">
-      <object class="GtkButton" id="select_button">
-        <property name="visible">True</property>
-        <property name="can-default">True</property>
-        <property name="sensitive">False</property>
-        <property name="label" translatable="yes">Select</property>
-      </object>
-    </child>
-    <child type="action">
-      <object class="GtkButton" id="cancel_button">
-        <property name="visible">True</property>
-        <property name="label" translatable="yes">Cancel</property>
-      </object>
-    </child>
-    <action-widgets>
-      <action-widget response="cancel">cancel_button</action-widget>
-      <action-widget response="ok" default="true">select_button</action-widget>
-    </action-widgets>
   </template>
 </interface>
diff --git a/data/ui/contacts-crop-cheese-dialog.ui b/data/ui/contacts-crop-cheese-dialog.ui
new file mode 100644
index 0000000..bcb402c
--- /dev/null
+++ b/data/ui/contacts-crop-cheese-dialog.ui
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.22"/>
+  <template class="ContactsCropCheeseDialog" parent="GtkWindow">
+    <property name="can_focus">False</property>
+    <property name="modal">True</property>
+    <property name="default_width">400</property>
+    <property name="default_height">400</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="skip_taskbar_hint">True</property>
+    <signal name="destroy" handler="on_destroy" swapped="no"/>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton">
+            <property name="label" translatable="yes">Cancel</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkStack" id="headerbar_stack">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="visible-child-name" bind-source="stack" bind-property="visible-child-name" />
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">5</property>
+                <child>
+                  <object class="GtkButton" id="take_another_button">
+                    <property name="label" translatable="yes">Take Another...</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="no_show_all">True</property>
+                    <signal name="clicked" handler="on_take_another_clicked" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Done</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_done_clicked" swapped="no"/>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="name">crop</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="halign">end</property>
+                <signal name="clicked" handler="on_take_pic_clicked" swapped="no"/>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">camera-photo-symbolic</property>
+                  </object>
+                </child>
+                <style>
+                  <class name="suggested-action"/>
+                </style>
+              </object>
+              <packing>
+                <property name="name">cheese</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/style.css b/data/ui/style.css
index 0292a61..455dd65 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -82,3 +82,8 @@ row.contact-data-row {
 .contacts-avatar-popover .contact-display-name {
   font-size: 20px;
 }
+
+.avatar-button {
+  border-radius: 50%;
+  padding: 0 0;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 699261d..09289c9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -9,6 +9,7 @@ data/ui/contacts-avatar-selector.ui
 data/ui/contacts-contact-editor.ui
 data/ui/contacts-contact-pane.ui
 data/ui/contacts-contact-sheet.ui
+data/ui/contacts-crop-cheese-dialog.ui
 data/ui/contacts-link-suggestion-grid.ui
 data/ui/contacts-linked-personas-dialog.ui
 data/ui/contacts-list-pane.ui
@@ -23,6 +24,7 @@ src/contacts-contact-list.vala
 src/contacts-contact-pane.vala
 src/contacts-contact-sheet.vala
 src/contacts-contact.vala
+src/contacts-crop-cheese-dialog.vala
 src/contacts-esd-setup.vala
 src/contacts-im-service.vala
 src/contacts-linked-personas-dialog.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 3125cfd..8fae880 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -11,6 +11,7 @@ src/contacts-contact-editor.c
 src/contacts-contact-list.c
 src/contacts-contact-pane.c
 src/contacts-contact-sheet.c
+src/contacts-crop-cheese-dialog.c
 src/contacts-esd-setup.c
 src/contacts-im-service.c
 src/contacts-linked-personas-dialog.c
diff --git a/src/contacts-avatar-selector.vala b/src/contacts-avatar-selector.vala
index 83e3631..e2e66cd 100644
--- a/src/contacts-avatar-selector.vala
+++ b/src/contacts-avatar-selector.vala
@@ -27,156 +27,117 @@ using Folks;
  * After a user has initially chosen an avatar, we provide a cropping tool.
  */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-avatar-selector.ui")]
-public class Contacts.AvatarSelector : Dialog {
-  const int MAIN_SIZE = 128;
+public class Contacts.AvatarSelector : Popover {
   const int ICONS_SIZE = 64;
-
-  private Contact contact;
+  const int MAIN_SIZE = 128;
+  const string AVATAR_BUTTON_CSS_NAME = "avatar-button";
 
   // This will provide the default thumbnails
   private Gnome.DesktopThumbnailFactory thumbnail_factory;
+  private Contact contact;
 
-  [GtkChild]
-  private Grid grid;
-  [GtkChild]
-  private Label contact_name_label;
-  [GtkChild]
-  private Stack views_stack;
   [GtkChild]
   private FlowBox personas_thumbnail_grid;
   [GtkChild]
   private FlowBox stock_thumbnail_grid;
-  [GtkChild]
-  private Grid crop_page;
-  private Cc.CropArea crop_area;
-  [GtkChild]
-  private Grid photobooth_page;
-  [GtkChild]
-  private Button webcam_button;
-  [GtkChild]
-  private Box webcam_button_box;
-
-  private Avatar current_avatar;
 
 #if HAVE_CHEESE
-  private Cheese.Flash flash;
-  private Cheese.CameraDeviceMonitor camera_monitor;
-  private Cheese.Widget cheese;
+  [GtkChild]
+  private Button cheese_button;
   private int num_cameras;
+  private Cheese.CameraDeviceMonitor camera_monitor;
 #endif
 
-  private Gdk.Pixbuf? new_pixbuf;
-
   /**
    * Fired after the user has definitely chosen a new avatar.
    */
   public signal void set_avatar (GLib.Icon avatar_icon);
 
-  public AvatarSelector (Window main_window, Contact? contact) {
-    Object (
-      transient_for: main_window,
-      use_header_bar: 1
-    );
-
+  public AvatarSelector (Gtk.Widget relative, Contact? contact) {
+    this.set_relative_to(relative);
     this.thumbnail_factory = new Gnome.DesktopThumbnailFactory (Gnome.ThumbnailSize.NORMAL);
     this.contact = contact;
 
-    // Load the current avatar
-    this.current_avatar = new Avatar (MAIN_SIZE, contact);
-    this.current_avatar.set_hexpand (false);
-    this.current_avatar.show ();
-    this.grid.attach (this.current_avatar, 0, 0);
-
-    if (contact != null)
-      this.contact_name_label.label = contact.individual.display_name;
+    update_thumbnail_grids ();
 
 #if HAVE_CHEESE
-    this.webcam_button_box.show ();
+    this.cheese_button.visible = true;
 
     // Look for camera devices.
     this.camera_monitor = new Cheese.CameraDeviceMonitor ();
     this.camera_monitor.added.connect ( () => {
         this.num_cameras++;
-        this.webcam_button.sensitive = (this.num_cameras > 0);
+        this.cheese_button.sensitive = (this.num_cameras > 0);
       });
     this.camera_monitor.removed.connect ( () => {
         this.num_cameras--;
-        this.webcam_button.sensitive = (this.num_cameras > 0);
+        this.cheese_button.sensitive = (this.num_cameras > 0);
       });
     // Do this in a separate thread, or it blocks the whole UI
     new Thread<void*> ("camera-loader", () => {
         this.camera_monitor.coldplug ();
         return null;
       });
-
-    // Create a photobooth page
-    this.cheese = new Cheese.Widget ();
-    this.cheese.set_vexpand (true);
-    this.cheese.set_hexpand (true);
-    this.cheese.set_no_show_all (true);
-    this.photobooth_page.attach (cheese, 0, 0);
-    this.photobooth_page.show ();
-
-    this.flash = new Cheese.Flash (this);
-#else
-    // Don't show the camera button
-    this.webcam_button_box.hide ();
 #endif
-
-    this.views_stack.set_visible_child_name ("thumbnail-page");
-    /*
-    var remove_button = new ToolButton (null, null);
-    remove_button.set_icon_name ("list-remove-symbolic");
-    remove_button.get_style_context ().add_class (STYLE_CLASS_RAISED);
-    remove_button.is_important = true;
-    toolbar.add (remove_button);
-    remove_button.clicked.connect ( (button) => {
-       });
-    */
-
-    update_thumbnail_grids ();
   }
 
   private Gdk.Pixbuf scale_pixbuf_for_avatar_use (Gdk.Pixbuf pixbuf) {
     int w = pixbuf.get_width ();
     int h = pixbuf.get_height ();
 
-    if (w <= 128 && h <= 128)
+    if (w <= MAIN_SIZE && h <= MAIN_SIZE)
       return pixbuf;
 
     if (w > h) {
-      h = (int)Math.round (h * 128.0 / w);
-      w = 128;
+      h = (int)Math.round (h * (float) MAIN_SIZE / w);
+      w = MAIN_SIZE;
     } else {
-      w = (int)Math.round (w * 128.0 / h);
-      h = 128;
+      w = (int)Math.round (w * (float) MAIN_SIZE / h);
+      h = MAIN_SIZE;
     }
 
     return pixbuf.scale_simple (w, h, Gdk.InterpType.HYPER);
   }
 
-  private Button create_thumbnail (Gdk.Pixbuf source_pixbuf) {
+  private void selected_pixbuf (Gdk.Pixbuf pixbuf) {
+    try {
+      uint8[] buffer;
+      pixbuf.save_to_buffer (out buffer, "png", null);
+      var icon = new BytesIcon (new Bytes (buffer));
+      set_avatar (icon);
+    } catch (GLib.Error e) {
+      warning ("Failed to set avatar: %s", e.message);
+      Utils.show_error_dialog (_("Failed to set avatar."),
+                              this.get_toplevel() as Gtk.Window);
+    }
+  }
+
+  private FlowBoxChild create_thumbnail (Gdk.Pixbuf source_pixbuf) {
     var avatar = new Avatar (ICONS_SIZE);
     var pixbuf = source_pixbuf.scale_simple (ICONS_SIZE, ICONS_SIZE, Gdk.InterpType.HYPER);
     avatar.set_pixbuf (pixbuf);
 
     var button = new Button ();
-    button.get_style_context ().add_class ("flat");
+    button.get_style_context ().add_class (AVATAR_BUTTON_CSS_NAME);
     button.image = avatar;
     button.clicked.connect ( () => {
         selected_pixbuf (scale_pixbuf_for_avatar_use (source_pixbuf));
+        this.popdown ();
       });
+    var child = new FlowBoxChild ();
+    child.add (button);
+    child.set_halign (Align.START);
 
-    return button;
+    return child;
   }
 
-  private Button? thumbnail_for_persona (Persona persona) {
+  private FlowBoxChild? thumbnail_for_persona (Persona persona) {
     var details = persona as AvatarDetails;
     if (details == null || details.avatar == null)
       return null;
 
     try {
-      var stream = details.avatar.load (128, null);
+      var stream = details.avatar.load (MAIN_SIZE, null);
       return create_thumbnail (new Gdk.Pixbuf.from_stream (stream));
     } catch {
       debug ("Couldn't create frame for persona \"%s\".", persona.display_id);
@@ -185,7 +146,7 @@ public class Contacts.AvatarSelector : Dialog {
     return null;
   }
 
-  private Button? thumbnail_for_filename (string filename) {
+  private FlowBoxChild? thumbnail_for_filename (string filename) {
     try {
       return create_thumbnail (new Gdk.Pixbuf.from_file (filename));
     } catch {
@@ -195,14 +156,6 @@ public class Contacts.AvatarSelector : Dialog {
     return null;
   }
 
-  private void selected_pixbuf (Gdk.Pixbuf pixbuf) {
-    var p = pixbuf.scale_simple (MAIN_SIZE, MAIN_SIZE, Gdk.InterpType.HYPER);
-    this.current_avatar.set_pixbuf (p);
-
-    this.new_pixbuf = pixbuf;
-    set_response_sensitive (ResponseType.OK, true);
-  }
-
   private void update_thumbnail_grids () {
     if (this.contact != null) {
       foreach (var p in contact.individual.personas) {
@@ -222,78 +175,18 @@ public class Contacts.AvatarSelector : Dialog {
     this.stock_thumbnail_grid.show_all ();
   }
 
-  public void update_preview (FileChooser chooser) {
-    var uri = chooser.get_preview_uri ();
-    if (uri != null) {
-      Gdk.Pixbuf? pixbuf = null;
-
-      var preview = chooser.get_preview_widget () as Image;
-
-      var file = File.new_for_uri (uri);
-      try {
-        var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
-                         FileQueryInfoFlags.NONE, null);
-        if (file_info != null) {
-          var mime_type = file_info.get_content_type ();
-
-          if (mime_type != null)
-            pixbuf = thumbnail_factory.generate_thumbnail (uri, mime_type);
-        }
-      } catch (GLib.Error e) {
-      }
-
-      (chooser as Dialog).set_response_sensitive (ResponseType.ACCEPT, (pixbuf != null));
-
-      if (pixbuf != null)
-        preview.set_from_pixbuf (pixbuf);
-      else
-        preview.set_from_icon_name ("dialog-question", IconSize.DIALOG);
-    }
-
-    chooser.set_preview_widget_active (true);
-  }
-
-  private void set_crop_widget (Gdk.Pixbuf pixbuf) {
-    this.crop_area = new Cc.CropArea ();
-    this.crop_area.set_vexpand (true);
-    this.crop_area.set_hexpand (true);
-    this.crop_area.set_min_size (48, 48);
-    this.crop_area.set_constrain_aspect (true);
-    this.crop_area.set_picture (pixbuf);
-
-    this.crop_page.attach (this.crop_area, 0, 0);
-    this.crop_page.show_all ();
-
-    this.views_stack.set_visible_child_name ("crop-page");
-  }
-
-  public override void response (int response_id) {
-    if (response_id == ResponseType.OK && this.new_pixbuf != null) {
-      try {
-        uint8[] buffer;
-        if (this.new_pixbuf.save_to_buffer (out buffer, "png", null)) {
-          var icon = new BytesIcon (new Bytes (buffer));
-          set_avatar (icon);
-        } else {
-          /* Failure. Fall through. */
-        }
-      } catch {
-      }
-    }
-
-#if HAVE_CHEESE
-    /* Ensure the Vala garbage collector disposes of the Cheese widget.
-     * This prevents the 'Device or resource busy' warnings, see:
-     *   https://bugzilla.gnome.org/show_bug.cgi?id=700959
-     */
-    this.cheese = null;
-#endif
-
-    this.destroy ();
+  [GtkCallback]
+  private void on_cheese_clicked (Button button) {
+    var dialog = new CropCheeseDialog.for_cheese ((Window) this.get_toplevel());
+    dialog.show_all ();
+    dialog.picture_selected.connect ( (pix) => {
+      selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+    });
+    this.popdown ();
   }
 
   [GtkCallback]
-  private void select_avatar_file_cb (Button button) {
+  private void on_file_clicked (Button button) {
     var chooser = new FileChooserDialog (_("Browse for more pictures"),
                                          (Gtk.Window)this.get_toplevel (),
                                          FileChooserAction.OPEN,
@@ -302,7 +195,7 @@ public class Contacts.AvatarSelector : Dialog {
     chooser.set_modal (true);
     chooser.set_local_only (false);
     var preview = new Image ();
-    preview.set_size_request (128, -1);
+    preview.set_size_request (MAIN_SIZE, -1);
     chooser.set_preview_widget (preview);
     chooser.set_use_preview_label (false);
     preview.show ();
@@ -323,63 +216,58 @@ public class Contacts.AvatarSelector : Dialog {
           var in_stream = file.read ();
           var pixbuf = new Gdk.Pixbuf.from_stream (in_stream, null);
           in_stream.close ();
-          if (pixbuf.get_width () > 128 || pixbuf.get_height () > 128)
-            set_crop_widget (pixbuf);
-          else
+          if (pixbuf.get_width () > MAIN_SIZE || pixbuf.get_height () > MAIN_SIZE) {
+            var dialog = new CropCheeseDialog.for_crop ((Window) this.get_toplevel(),
+                                                        pixbuf);
+            dialog.picture_selected.connect ( (pix) => {
+              selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+            });
+            dialog.show_all();
+          } else {
             selected_pixbuf (scale_pixbuf_for_avatar_use (pixbuf));
-
-          update_thumbnail_grids ();
-        } catch {
+          }
+        } catch (GLib.Error e) {
+          warning ("Failed to set avatar: %s", e.message);
+          Utils.show_error_dialog (_("Failed to set avatar."),
+                                   this.get_toplevel() as Gtk.Window);
         }
 
         chooser.destroy ();
       });
 
     chooser.present ();
+    this.popdown();
   }
 
-  [GtkCallback]
-  private void on_webcam_button_clicked (Button button) {
-#if HAVE_CHEESE
-    this.views_stack.set_visible_child_name ("photobooth-page");
-    this.cheese.show ();
-#endif
-  }
+  private void update_preview (FileChooser chooser) {
+    var uri = chooser.get_preview_uri ();
+    if (uri != null) {
+      Gdk.Pixbuf? pixbuf = null;
 
-  [GtkCallback]
-  private void on_photobooth_page_select_button_clicked (Button button) {
-#if HAVE_CHEESE
-    var camera = this.cheese.get_camera () as Cheese.Camera;
-    this.flash.fire ();
-    camera.photo_taken.connect ( (pix) => {
-        set_crop_widget (pix);
-        this.cheese.hide ();
-      });
+      var preview = chooser.get_preview_widget () as Image;
 
-    if (!camera.take_photo_pixbuf ())
-      warning ("Unable to take photo");
-#endif
-  }
+      var file = File.new_for_uri (uri);
+      try {
+        var file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
+                         FileQueryInfoFlags.NONE, null);
+        if (file_info != null) {
+          var mime_type = file_info.get_content_type ();
 
-  [GtkCallback]
-  private void on_photobooth_page_cancel_button_clicked (Button button) {
-#if HAVE_CHEESE
-    this.views_stack.set_visible_child_name ("thumbnail-page");
-    this.cheese.hide ();
-#endif
-  }
+          if (mime_type != null)
+            pixbuf = thumbnail_factory.generate_thumbnail (uri, mime_type);
+        }
+      } catch {
+      }
 
-  [GtkCallback]
-  private void on_crop_page_select_button_clicked (Button button) {
-    var pix = crop_area.get_picture ();
-    selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
-    this.crop_area.destroy ();
-    this.views_stack.set_visible_child_name ("thumbnail-page");
-  }
+      (chooser as Dialog).set_response_sensitive (ResponseType.ACCEPT, (pixbuf != null));
 
-  [GtkCallback]
-  private void on_crop_page_cancel_button_clicked (Button button) {
-    this.crop_area.destroy ();
-    this.views_stack.set_visible_child_name ("thumbnail-page");
+      if (pixbuf != null)
+        preview.set_from_pixbuf (pixbuf);
+      else
+        preview.set_from_icon_name ("dialog-question", IconSize.DIALOG);
+    }
+
+    chooser.set_preview_widget_active (true);
   }
+
 }
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 16c11da..a311525 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -954,10 +954,10 @@ public class Contacts.ContactEditor : Grid {
     this.container_grid.attach (button, 0, 0, 1, 3);
   }
 
-  // Show the avatar dialog when the avatar is clicked
+  // Show the avatar popover when the avatar is clicked
   private void on_avatar_button_clicked (Button avatar_button) {
-    var dialog = new AvatarSelector ((Window) get_toplevel (), this.contact);
-    dialog.set_avatar.connect ( (icon) =>  {
+    var popover = new AvatarSelector (avatar_button, this.contact);
+    popover.set_avatar.connect ( (icon) =>  {
         this.avatar.set_data ("value", icon);
         this.avatar.set_data ("changed", true);
 
@@ -970,7 +970,7 @@ public class Contacts.ContactEditor : Grid {
 
         this.avatar.set_pixbuf (a_pixbuf);
       });
-    dialog.run ();
+    popover.show();
   }
 
   public bool avatar_changed () {
diff --git a/src/contacts-crop-cheese-dialog.vala b/src/contacts-crop-cheese-dialog.vala
new file mode 100644
index 0000000..cc912c2
--- /dev/null
+++ b/src/contacts-crop-cheese-dialog.vala
@@ -0,0 +1,114 @@
+/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
+/*
+ * Copyright (C) 2018 Elias Entrup <elias-git flump de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-crop-cheese-dialog.ui")]
+public class Contacts.CropCheeseDialog : Gtk.Window {
+  [GtkChild]
+  private Stack stack;
+  [GtkChild]
+  private Button take_another_button;
+
+  private Cc.CropArea crop_area;
+  private const string STACK_NAME_CROP = "crop";
+  private const string STACK_NAME_CHEESE = "cheese";
+
+#if HAVE_CHEESE
+  private Cheese.Flash flash;
+  private Cheese.Widget cheese;
+#endif
+
+  public signal void picture_selected (Gdk.Pixbuf buf);
+
+  public CropCheeseDialog.for_cheese (Gtk.Window parent) {
+#if HAVE_CHEESE
+    setup_widget (parent);
+    this.flash = new Cheese.Flash (this);
+    this.cheese = new Cheese.Widget ();
+    this.cheese.show ();
+    this.stack.add_named (this.cheese, STACK_NAME_CHEESE);
+    this.stack.set_visible_child_name (STACK_NAME_CHEESE);
+#endif
+  }
+
+  public CropCheeseDialog.for_crop (Gtk.Window parent, Gdk.Pixbuf pixbuf) {
+    setup_widget (parent);
+    this.take_another_button.visible = false;
+    this.crop_area.set_picture (pixbuf);
+  }
+
+  /* this function is called from both constructors */
+  private void setup_widget (Gtk.Window parent) {
+    this.set_transient_for (parent);
+
+    this.crop_area = new Cc.CropArea ();
+    this.crop_area.set_vexpand (true);
+    this.crop_area.set_hexpand (true);
+    this.crop_area.set_min_size (48, 48);
+    this.crop_area.set_constrain_aspect (true);
+    this.stack.add_named (this.crop_area, STACK_NAME_CROP);
+  }
+
+  [GtkCallback]
+  private void on_cancel_clicked (Button button) {
+    this.destroy ();
+  }
+
+  [GtkCallback]
+  private void on_take_another_clicked (Button button) {
+#if HAVE_CHEESE
+    this.stack.set_visible_child_name (STACK_NAME_CHEESE);
+#endif
+  }
+
+  [GtkCallback]
+  private void on_take_pic_clicked (Button button) {
+#if HAVE_CHEESE
+    var camera = this.cheese.get_camera () as Cheese.Camera;
+    this.flash.fire ();
+    camera.photo_taken.connect ( (pix) => {
+        this.stack.set_visible_child_name (STACK_NAME_CROP);
+        this.crop_area.set_picture(pix);
+    });
+
+    if (!camera.take_photo_pixbuf ()) {
+      Utils.show_error_dialog (_("Unable to take photo."),
+                               this as Gtk.Window);
+    }
+#endif
+  }
+
+  [GtkCallback]
+  private void on_done_clicked (Button button) {
+    picture_selected (this.crop_area.get_picture ());
+    destroy();
+  }
+  
+  [GtkCallback]
+  private void on_destroy () {
+#if HAVE_CHEESE
+    /* Ensure the Vala garbage collector disposes of the Cheese widget.
+     * This prevents the 'Device or resource busy' warnings, see:
+     *   https://bugzilla.gnome.org/show_bug.cgi?id=700959
+     */
+    this.cheese = null;
+#endif
+  }
+
+}
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index f4c7ffc..ad24d26 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -163,4 +163,14 @@ namespace Contacts.Utils {
     }
     return stores;
   }
+
+  public void show_error_dialog (string error, Gtk.Window toplevel) {
+    var dialog = new Gtk.MessageDialog (toplevel,
+                                        Gtk.DialogFlags.MODAL,
+                                        Gtk.MessageType.ERROR,
+                                        Gtk.ButtonsType.OK,
+                                        error);
+    dialog.run();
+    dialog.destroy();
+  }
 }
diff --git a/src/meson.build b/src/meson.build
index 7dc6d27..d05d1f5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,7 @@ contacts_vala_sources = files(
   'contacts-contact-pane.vala',
   'contacts-contact-sheet.vala',
   'contacts-contact.vala',
+  'contacts-crop-cheese-dialog.vala',
   'contacts-esd-setup.vala',
   'contacts-im-service.vala',
   'contacts-in-app-notification.vala',



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]