[gnome-contacts] avatar-selector: move selector to a dialog



commit f5cd13885851ed272ed88f03ddc5bc4df7673061
Author: Julian Sparber <julian sparber net>
Date:   Thu Dec 17 13:37:00 2020 +0100

    avatar-selector: move selector to a dialog
    
    The previously used popover was to big and causes issues on
    small screens and on X11.
    
    This also merges the two FlowBoxes into one.

 data/ui/contacts-avatar-selector.ui | 120 ++++++++++++++++++----------
 data/ui/style.css                   |   5 ++
 src/contacts-avatar-selector.vala   | 155 +++++++++++++++++++++---------------
 src/contacts-contact-editor.vala    |   2 +-
 4 files changed, 173 insertions(+), 109 deletions(-)
---
diff --git a/data/ui/contacts-avatar-selector.ui b/data/ui/contacts-avatar-selector.ui
index 895805a9..336b6baf 100644
--- a/data/ui/contacts-avatar-selector.ui
+++ b/data/ui/contacts-avatar-selector.ui
@@ -1,54 +1,102 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="3.22"/>
-  <template class="ContactsAvatarSelector" parent="GtkPopover">
+  <template class="ContactsAvatarSelector" parent="GtkWindow">
     <property name="can_focus">False</property>
-    <child>
-      <object class="GtkBox">
+    <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="delete-event" handler="on_delete_event" swapped="no"/>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
         <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>
+        <property name="can_focus">False</property>
         <child>
-          <object class="GtkFlowBox" id="personas_thumbnail_grid">
+          <object class="GtkButton">
+            <property name="label" translatable="yes">Cancel</property>
             <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="selection_mode">none</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
+          </object>
+        </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">0</property>
+            <property name="pack_type">end</property>
           </packing>
         </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
         <child>
-          <object class="GtkFlowBox" id="stock_thumbnail_grid">
+          <object class="GtkScrolledWindow">
             <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>
+            <property name="hscrollbar_policy">never</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</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="GtkFlowBox" id="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="max_children_per_line">8</property>
+                    <property name="selection_mode">single</property>
+                    <property name="homogeneous">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
           </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
+        </child>
+        <child>
+          <object class="GtkSeparator">
+            <property name="visible">True</property>
+          </object>>
         </child>
         <child>
           <object class="GtkBox">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="halign">center</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="spacing">10</property>
+            <property name="halign">center</property>
             <child>
               <object class="GtkButton" id="cheese_button">
                 <property name="label" translatable="yes">Take a Picture…</property>
@@ -57,11 +105,6 @@
                 <property name="receives_default">True</property>
                 <signal name="clicked" handler="on_cheese_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">
@@ -71,17 +114,10 @@
                 <property name="receives_default">True</property>
                 <signal name="clicked" handler="on_file_clicked" swapped="no"/>
               </object>
-              <packing>
-                <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>
+            <property name="pack_type">end</property>
           </packing>
         </child>
       </object>
diff --git a/data/ui/style.css b/data/ui/style.css
index de2f0f6d..67bd2ad3 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -42,6 +42,11 @@
 .contacts-avatar-popover .contact-display-name {
   font-size: 20px;
 }
+flowboxchild.circular {
+  padding: 4px;
+  border-radius: 9999px;
+  -gtk-outline-radius: 9999px;
+}
 
 .avatar-button {
   border-radius: 50%;
diff --git a/src/contacts-avatar-selector.vala b/src/contacts-avatar-selector.vala
index 5f382894..1684b340 100644
--- a/src/contacts-avatar-selector.vala
+++ b/src/contacts-avatar-selector.vala
@@ -17,6 +17,44 @@
 
 using Folks;
 
+const int MAIN_SIZE = 128;
+const int ICONS_SIZE = 64;
+
+private class Contacts.Thumbnail : Gtk.FlowBoxChild {
+  public Gdk.Pixbuf? source_pixbuf { get; construct set; }
+  private Thumbnail (Gdk.Pixbuf? source_pixbuf = null) {
+    Object (visible: true, halign : Gtk.Align.CENTER, source_pixbuf: source_pixbuf);
+    this.get_style_context ().add_class ("circular");
+    var avatar = new Avatar (ICONS_SIZE);
+    avatar.set_pixbuf (source_pixbuf);
+    add (avatar);
+  }
+
+  public Thumbnail.for_persona (Persona persona) {
+    Gdk.Pixbuf? pixbuf = null;
+    var details = persona as AvatarDetails;
+    if (details != null && details.avatar != null) {
+      try {
+        var stream = details.avatar.load (MAIN_SIZE, null);
+        pixbuf = new Gdk.Pixbuf.from_stream (stream);
+      } catch (Error e) {
+        debug ("Couldn't create frame for persona '%s': %s", persona.display_id, e.message);
+      }
+    }
+    this (pixbuf);
+  }
+
+  public Thumbnail.for_filename (string filename) {
+    Gdk.Pixbuf? pixbuf = null;
+    try {
+      pixbuf = new Gdk.Pixbuf.from_file (filename);
+    } catch (Error e) {
+      debug ("Couldn't create frame for file '%s': %s", filename, e.message);
+    }
+    this (pixbuf);
+  }
+}
+
 /**
  * The AvatarSelector can be used to choose the avatar for a contact.
  * This can be done by either choosing a stock thumbnail, an image file
@@ -25,9 +63,7 @@ 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 : Gtk.Popover {
-  const int ICONS_SIZE = 64;
-  const int MAIN_SIZE = 128;
+public class Contacts.AvatarSelector : Gtk.Window {
   const string AVATAR_BUTTON_CSS_NAME = "avatar-button";
 
   // This will provide the default thumbnails
@@ -35,9 +71,7 @@ public class Contacts.AvatarSelector : Gtk.Popover {
   private Individual individual;
 
   [GtkChild]
-  private Gtk.FlowBox personas_thumbnail_grid;
-  [GtkChild]
-  private Gtk.FlowBox stock_thumbnail_grid;
+  private Gtk.FlowBox thumbnail_grid;
 
 #if HAVE_CHEESE
   [GtkChild]
@@ -46,12 +80,19 @@ public class Contacts.AvatarSelector : Gtk.Popover {
   private Cheese.CameraDeviceMonitor camera_monitor;
 #endif
 
-  public AvatarSelector (Gtk.Widget relative, Individual? individual) {
-    this.set_relative_to(relative);
+  public AvatarSelector (Individual? individual, Gtk.Window? window = null) {
+    Object (transient_for: window);
     this.thumbnail_factory = new Gnome.DesktopThumbnailFactory (Gnome.ThumbnailSize.NORMAL);
     this.individual = individual;
 
-    update_thumbnail_grids ();
+    unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class (get_class ());
+    Gtk.BindingEntry.add_signal (binding_set,
+                                 Gdk.Key.Escape,
+                                 0,
+                                 "close",
+                                 0);
+
+    update_thumbnail_grid ();
 
 #if HAVE_CHEESE
     this.cheese_button.visible = true;
@@ -75,6 +116,16 @@ public class Contacts.AvatarSelector : Gtk.Popover {
 #endif
   }
 
+  [Signal (action = true)]
+  public new virtual signal void close () {
+    base.close ();
+  }
+
+  [GtkCallback]
+  public bool on_delete_event () {
+    return hide_on_delete ();
+  }
+
   private Gdk.Pixbuf scale_pixbuf_for_avatar_use (Gdk.Pixbuf pixbuf) {
     int w = pixbuf.get_width ();
     int h = pixbuf.get_height ();
@@ -114,67 +165,22 @@ public class Contacts.AvatarSelector : Gtk.Popover {
                                get_toplevel() as Gtk.Window);
     }
   }
-
-  private Gtk.FlowBoxChild create_thumbnail (Gdk.Pixbuf source_pixbuf) {
-    var avatar = new Avatar (ICONS_SIZE);
-    avatar.set_pixbuf (source_pixbuf);
-
-    var button = new Gtk.Button ();
-    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 Gtk.FlowBoxChild ();
-    child.add (button);
-    child.set_halign (Gtk.Align.START);
-
-    return child;
-  }
-
-  private Gtk.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 (MAIN_SIZE, null);
-      return create_thumbnail (new Gdk.Pixbuf.from_stream (stream));
-    } catch (Error e) {
-      debug ("Couldn't create frame for persona '%s': %s", persona.display_id, e.message);
-    }
-
-    return null;
-  }
-
-  private Gtk.FlowBoxChild? thumbnail_for_filename (string filename) {
-    try {
-      return create_thumbnail (new Gdk.Pixbuf.from_file (filename));
-    } catch (Error e) {
-      debug ("Couldn't create frame for file '%s': %s", filename, e.message);
-    }
-
-    return null;
-  }
-
-  private void update_thumbnail_grids () {
+  private void update_thumbnail_grid () {
     if (this.individual != null) {
       foreach (var p in individual.personas) {
-        var button = thumbnail_for_persona (p);
-        if (button != null)
-          this.personas_thumbnail_grid.add (button);
+        var widget = new Thumbnail.for_persona (p);
+        if (widget.source_pixbuf != null)
+          this.thumbnail_grid.add (widget);
       }
     }
-    this.personas_thumbnail_grid.show_all ();
 
     var stock_files = Utils.get_stock_avatars ();
     foreach (var file_name in stock_files) {
-      var button = thumbnail_for_filename (file_name);
-      if (button != null)
-        this.stock_thumbnail_grid.add (button);
+      var widget = new Thumbnail.for_filename (file_name);
+      if (widget.source_pixbuf != null)
+        this.thumbnail_grid.add (widget);
     }
-    this.stock_thumbnail_grid.show_all ();
+    this.thumbnail_grid.show_all ();
   }
 
   [GtkCallback]
@@ -183,8 +189,25 @@ public class Contacts.AvatarSelector : Gtk.Popover {
     dialog.show_all ();
     dialog.picture_selected.connect ( (pix) => {
       selected_pixbuf (scale_pixbuf_for_avatar_use (pix));
+      this.close ();
     });
-    this.popdown ();
+  }
+
+  [GtkCallback]
+  private void on_cancel_clicked (Gtk.Button button) {
+    this.close ();
+  }
+
+  [GtkCallback]
+  private void on_done_clicked (Gtk.Button button) {
+    var selected_children = thumbnail_grid.get_selected_children ();
+    if (selected_children != null) {
+      var thumbnail = (selected_children.data as Thumbnail);
+      if (thumbnail != null)
+        selected_pixbuf (scale_pixbuf_for_avatar_use (thumbnail.source_pixbuf));
+    }
+
+    this.close ();
   }
 
   [GtkCallback]
@@ -233,11 +256,11 @@ public class Contacts.AvatarSelector : Gtk.Popover {
                                    this.get_toplevel() as Gtk.Window);
         }
 
-        chooser.destroy ();
-      });
+      chooser.destroy ();
+    });
 
     chooser.run ();
-    this.popdown();
+    this.close ();
   }
 
   private void update_preview (Gtk.FileChooser chooser) {
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 09753c94..85c34f74 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -57,7 +57,7 @@ public class Contacts.ContactEditor : Gtk.Box {
   // Show the avatar popover when the avatar is clicked
   private void on_avatar_button_clicked (Gtk.Button avatar_button) {
     if (this.avatar_selector == null)
-      this.avatar_selector = new AvatarSelector (avatar_button, this.individual);
+      this.avatar_selector = new AvatarSelector (this.individual, (Gtk.Window) this.get_toplevel());
     this.avatar_selector.show();
   }
 


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