[gnome-contacts/nielsdg/account-list-fix-selection] Use a ListModel for address books (PersonaStore-s)




commit 12e5d5cdd159d3f250de8f9301ebd367fc90b6f4
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sat Jun 4 22:38:49 2022 +0200

    Use a ListModel for address books (PersonaStore-s)
    
    Remove the util function `get_eds_address_books()` and the several copy
    pastes that deal with `Folks.Backend`s coming up and dynamically
    changing the list of address book.
    
    Instead, `Contacts.Store` now exposes a `GLib.ListModel` that something
    like the `Contacts.AccountsList` widget can use to generate a
    `Gtk.ListBox`.
    
    While we're at it, remove `AddressBookList`, as it's not used anywhere.
    
    Fixes a problem in `Contacts.AccountsList` where it showed an address
    book as selected, but it didn't seem to register it properly.
    
    Fixes: https://gitlab.gnome.org/GNOME/gnome-contacts/-/issues/238

 po/POTFILES.in                       |   1 -
 po/POTFILES.skip                     |   1 -
 src/contacts-accounts-list.vala      | 169 +++++++++++++++--------------------
 src/contacts-addressbook-dialog.vala |  14 +--
 src/contacts-addressbook-list.vala   | 153 -------------------------------
 src/contacts-setup-window.vala       |  42 +++------
 src/contacts-store.vala              |  17 ++++
 src/contacts-utils.vala              |  16 +---
 src/meson.build                      |   1 -
 9 files changed, 108 insertions(+), 306 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index da710551..36e9a368 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -14,7 +14,6 @@ data/ui/contacts-main-window.ui
 data/ui/contacts-setup-window.ui
 src/contacts-accounts-list.vala
 src/contacts-addressbook-dialog.vala
-src/contacts-addressbook-list.vala
 src/contacts-app.vala
 src/contacts-avatar-selector.vala
 src/contacts-avatar.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 6347f419..f5fcf7ad 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -4,7 +4,6 @@ data/org.gnome.Contacts.appdata.xml
 data/org.gnome.Contacts.desktop
 src/contacts-accounts-list.c
 src/contacts-addressbook-dialog.c
-src/contacts-addressbook-list.c
 src/contacts-app.c
 src/contacts-avatar-selector.c
 src/contacts-avatar.c
diff --git a/src/contacts-accounts-list.vala b/src/contacts-accounts-list.vala
index 8be6a1ab..202c1607 100644
--- a/src/contacts-accounts-list.vala
+++ b/src/contacts-accounts-list.vala
@@ -17,137 +17,110 @@
 
 using Folks;
 
+/**
+ * The AccountsList widget provides a way to list all the known address books
+ * for a user, as well as providing a means of selecting a "primary" address
+ * book, ie the address book that will be used to write the details of a
+ * contact to.
+ *
+ * Internally, each "address book" is a {@link Folks.PersonaStore}.
+ */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-accounts-list.ui")]
 public class Contacts.AccountsList : Adw.Bin {
 
   [GtkChild]
   private unowned Gtk.ListBox listbox;
-  private unowned Gtk.ListBoxRow? last_selected_row = null;
-
-  private Store contacts_store;
-  public PersonaStore? selected_store = null;
 
-  public signal void account_selected ();
+  private Gtk.SingleSelection selection;
 
-  construct {
-    this.listbox.row_activated.connect (on_row_activated);
+  /** The selected PersonaStore (or null if none) */
+  public PersonaStore? selected_store {
+    get { return (PersonaStore) this.selection.selected_item; }
   }
 
   public AccountsList (Store contacts_store) {
-    this.contacts_store = contacts_store;
-  }
-
-  private void on_row_activated (Gtk.ListBox listbox, Gtk.ListBoxRow? row) {
-    if (row == null)
-      return;
-
-    if (this.last_selected_row != null &&
-        this.last_selected_row == row) {
-      return;
-    }
-
-    var checkmark = row.get_data<Gtk.Image> ("checkmark");
-    checkmark.show ();
-
-    if (last_selected_row != null) {
-      checkmark = this.last_selected_row.get_data<Gtk.Image> ("checkmark");
-      if (checkmark != null)
-        checkmark.hide ();
+    // We only list E-D-S address books here, so make a filter model
+    var filter = new Gtk.CustomFilter ((item) => {
+      unowned var store = (PersonaStore) item;
+      return store.type_id == "eds";
+    });
+    var model = new Gtk.FilterListModel (contacts_store.address_books,
+                                         (owned) filter);
+
+    // Setup the selection model
+    this.selection = new Gtk.SingleSelection (model);
+
+    // Update the row when the selection model changes
+    this.selection.selection_changed.connect ((sel, pos, n_items) => {
+      for (uint i = pos; i < pos + n_items; i++) {
+        var row = (AddressbookRow?) this.listbox.get_row_at_index ((int) i);
+        if (row != null)
+          row.selected = this.selection.is_selected (i);
+      }
+      notify_property ("selected-store");
+    });
+
+    // Now bind the listbox to it
+    this.listbox.bind_model (model, (item) => {
+      unowned var persona_store = (PersonaStore) item;
+      var row = new AddressbookRow (persona_store);
+
+      // Update the selection model when the row is activated
+      row.activated.connect ((row) => {
+        this.selection.set_selected ((uint) row.get_index ());
+      });
+
+      return row;
+    });
+
+    // Initially, the primary store (if set) is selected
+    for (uint i = 0; i < model.get_n_items (); i++) {
+      var persona_store = (PersonaStore) model.get_item (i);
+      if (persona_store == contacts_store.aggregator.primary_store)
+        this.selection.set_selected (i);
     }
-
-    // Update the fields
-    this.last_selected_row = row;
-    this.selected_store = row.get_data<PersonaStore> ("store");
-
-    account_selected ();
   }
 
-  public void update_contents (bool select_active) {
-    // Remove all entries
-    unowned var child = this.listbox.get_first_child ();
-    while (child != null) {
-      unowned var next = child.get_next_sibling ();
-      this.listbox.remove (child);
-      child = next;
-    }
+  private class AddressbookRow : Adw.ActionRow {
 
-    // Fill the list with address book
-    PersonaStore[] eds_stores = Utils.get_eds_address_books (this.contacts_store);
-    debug ("Found %d EDS stores", eds_stores.length);
+    public PersonaStore persona_store { get; construct set; }
 
-    unowned PersonaStore? local_store = null;
-    foreach (unowned var persona_store in eds_stores) {
-      if (persona_store.id == "system-address-book") {
-        local_store = persona_store;
-        continue;
-      }
+    public bool selected { get; set; default = false; }
 
-      var source = ((Edsf.PersonaStore) persona_store).source;
+    construct {
+      var source = ((Edsf.PersonaStore) this.persona_store).source;
       var parent_source = eds_source_registry.ref_source (source.parent);
-      var provider_name = Contacts.Utils.format_persona_store_name (persona_store);
 
-      debug ("Contact store \"%s\"", provider_name);
+      debug ("Contact store \"%s\"",
+             Utils.format_persona_store_name (this.persona_store));
 
+      // Image
       var source_account_id = "";
       if (parent_source.has_extension (E.SOURCE_EXTENSION_GOA)) {
         var goa_source_ext = parent_source.get_extension (E.SOURCE_EXTENSION_GOA) as E.SourceGoa;
         source_account_id = goa_source_ext.account_id;
       }
-
-      var row = new Adw.ActionRow ();
-      row.set_data ("store", persona_store);
-
       Gtk.Image provider_image;
-      if (source_account_id != "")
+      if (this.persona_store.id != "system-address-book" && source_account_id != "")
         provider_image = Contacts.get_icon_for_goa_account (source_account_id);
       else
         provider_image = new Gtk.Image.from_icon_name (Config.APP_ID);
       provider_image.icon_size = Gtk.IconSize.LARGE;
-      row.add_prefix (provider_image);
-      row.title = provider_name;
-      row.subtitle = parent_source.display_name;
+      add_prefix (provider_image);
 
+      // Title - subtitle
+      this.title = Utils.format_persona_store_name (this.persona_store);
+      this.subtitle = parent_source.display_name;
+
+      // Checkmark
       var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic");
-      checkmark.margin_end = 6;
-      checkmark.valign = Gtk.Align.CENTER;
-      checkmark.halign = Gtk.Align.END;
-      checkmark.hexpand = true;
-      checkmark.vexpand = true;
-      checkmark.visible = (persona_store == this.contacts_store.aggregator.primary_store);
-      row.add_suffix (checkmark);
-      row.set_activatable_widget (checkmark);
-      row.set_data ("checkmark", checkmark);
-
-      this.listbox.append (row);
-
-      if (select_active &&
-          persona_store == this.contacts_store.aggregator.primary_store) {
-        this.listbox.row_activated (row);
-      }
+      bind_property ("selected", checkmark, "visible", BindingFlags.SYNC_CREATE);
+      add_suffix (checkmark);
+      set_activatable_widget (checkmark);
     }
 
-    if (local_store != null) {
-      var local_row = new Adw.ActionRow ();
-      var provider_image = new Gtk.Image.from_icon_name (Config.APP_ID);
-      provider_image.icon_size = Gtk.IconSize.LARGE;
-      local_row.add_prefix (provider_image);
-      local_row.title = _("Local Address Book");
-      var checkmark = new Gtk.Image.from_icon_name ("object-select-symbolic");
-      checkmark.margin_end = 6;
-      checkmark.valign = Gtk.Align.CENTER;
-      checkmark.halign = Gtk.Align.END;
-      checkmark.hexpand = true;
-      checkmark.vexpand = true;
-      checkmark.visible = (local_store == this.contacts_store.aggregator.primary_store);
-      local_row.add_suffix (checkmark);
-      local_row.set_activatable_widget (checkmark);
-      local_row.set_data ("checkmark", checkmark);
-      local_row.set_data ("store", local_store);
-      this.listbox.append (local_row);
-      if (select_active &&
-          local_store == this.contacts_store.aggregator.primary_store) {
-        this.listbox.row_activated (local_row);
-      }
+    public AddressbookRow (PersonaStore persona_store) {
+      Object (persona_store: persona_store);
     }
   }
 }
diff --git a/src/contacts-addressbook-dialog.vala b/src/contacts-addressbook-dialog.vala
index 8e70460c..03b13b0e 100644
--- a/src/contacts-addressbook-dialog.vala
+++ b/src/contacts-addressbook-dialog.vala
@@ -62,16 +62,8 @@ public class Contacts.AddressbookDialog : Gtk.Dialog {
     box.append (explanation_label);
 
     this.accounts_list = new AccountsList (contacts_store);
-    this.accounts_list.update_contents (true);
-
-    ulong active_button_once = 0;
-    active_button_once = this.accounts_list.account_selected.connect (() => {
-      ok_button.sensitive = true;
-      this.accounts_list.disconnect (active_button_once);
-    });
-
-    contacts_store.backend_store.backend_available.connect (() => {
-      this.accounts_list.update_contents (true);
+    this.accounts_list.notify["selected-store"].connect ((obj, pspec) => {
+      ok_button.sensitive = (this.accounts_list.selected_store != null);
     });
 
     box.append (this.accounts_list);
@@ -81,7 +73,7 @@ public class Contacts.AddressbookDialog : Gtk.Dialog {
     if (response != Gtk.ResponseType.OK)
       return;
 
-    var e_store = this.accounts_list.selected_store as Edsf.PersonaStore;
+    unowned var e_store = (Edsf.PersonaStore) this.accounts_list.selected_store;
     if (e_store != null) {
       eds_source_registry.set_default_address_book (e_store.source);
       var settings = new GLib.Settings ("org.freedesktop.folks");
diff --git a/src/contacts-setup-window.vala b/src/contacts-setup-window.vala
index 8b680b0e..365f94c3 100644
--- a/src/contacts-setup-window.vala
+++ b/src/contacts-setup-window.vala
@@ -17,6 +17,11 @@
 
 using Folks;
 
+/**
+ * The SetupWindow is the window that is shown to the user when they run
+ * Contacts for the first time. It asks the user to setup a primary address
+ * book.
+ */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-setup-window.ui")]
 public class Contacts.SetupWindow : Adw.ApplicationWindow {
 
@@ -26,7 +31,7 @@ public class Contacts.SetupWindow : Adw.ApplicationWindow {
   [GtkChild]
   private unowned Gtk.Button setup_done_button;
 
-  private AccountsList setup_accounts_list;
+  private AccountsList accounts_list;
 
   /**
    * Fired after the user has successfully performed the setup proess.
@@ -35,41 +40,20 @@ public class Contacts.SetupWindow : Adw.ApplicationWindow {
 
   public SetupWindow (App app, Store store) {
     Object (application: app, icon_name: Config.APP_ID);
-    this.setup_accounts_list = new AccountsList (store);
-    this.setup_accounts_list.hexpand = true;
-    this.clamp.set_child (this.setup_accounts_list);
-
-    // Listen for changes
-    store.backend_store.backend_available.connect  ( () => {
-        this.setup_accounts_list.update_contents (false);
-      });
 
-    ulong id2 = 0;
-    id2 = this.setup_accounts_list.account_selected.connect (() => {
-        this.setup_done_button.sensitive = true;
-        this.setup_accounts_list.disconnect (id2);
-      });
+    this.accounts_list = new AccountsList (store);
+    this.clamp.set_child (this.accounts_list);
 
-    fill_accounts_list (store);
+    this.accounts_list.notify["selected-store"].connect ((obj, pspec) => {
+      this.setup_done_button.sensitive = (this.accounts_list.selected_store != null);
+    });
 
     this.setup_done_button.clicked.connect (() => {
-        unowned var selected_store = this.setup_accounts_list.selected_store as Edsf.PersonaStore;
-        setup_done (selected_store);
-      });
+      setup_done ((Edsf.PersonaStore) this.accounts_list.selected_store);
+    });
 
     // Make visible when we're using a nightly build
     if (Config.PROFILE == "development")
         get_style_context ().add_class ("devel");
   }
-
-  private void fill_accounts_list (Store store) {
-    if (store.aggregator.is_prepared) {
-      this.setup_accounts_list.update_contents (false);
-      return;
-    }
-
-    store.prepared.connect ( () => {
-        this.setup_accounts_list.update_contents (false);
-      });
-  }
 }
diff --git a/src/contacts-store.vala b/src/contacts-store.vala
index 47144fbb..c3366296 100644
--- a/src/contacts-store.vala
+++ b/src/contacts-store.vala
@@ -40,6 +40,11 @@ public class Contacts.Store : GLib.Object {
   public IndividualAggregator aggregator { get; private set; }
   public BackendStore backend_store { get { return this.aggregator.backend_store; } }
 
+  private GLib.ListStore _address_books = new GLib.ListStore (typeof (PersonaStore));
+  public GLib.ListModel address_books {
+    get { return this._address_books; }
+  }
+
   // Base list model
   private GLib.ListStore _base_model = new ListStore (typeof (Individual));
   public GLib.ListModel base_model { get { return this._base_model; } }
@@ -132,8 +137,20 @@ public class Contacts.Store : GLib.Object {
     this.dont_suggest_link = new Gee.HashMultiMap<string, string> ();
     read_dont_suggest_db ();
 
+    // Setup the backends
     var backend_store = BackendStore.dup ();
+    // FIXME: we should just turn the "backends" property in folks into a
+    // GListModel directly
+    foreach (var backend in backend_store.enabled_backends.values) {
+      foreach (var persona_store in backend.persona_stores.values)
+        this._address_books.append (persona_store);
+    }
+    backend_store.backend_available.connect ((backend) => {
+      foreach (var persona_store in backend.persona_stores.values)
+        this._address_books.append (persona_store);
+    });
 
+    // Setup the individual aggregator
     this.aggregator = IndividualAggregator.dup_with_backend_store (backend_store);
     aggregator.notify["is-quiescent"].connect ((obj, pspec) => {
       // We seem to get this before individuals_changed, so hack around it
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index f85cb59c..2003dfa5 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -107,18 +107,6 @@ namespace Contacts.Utils {
     return files;
   }
 
-  public PersonaStore[] get_eds_address_books (Store contacts_store) {
-    PersonaStore[] stores = {};
-    foreach (var backend in contacts_store.backend_store.enabled_backends.values) {
-      foreach (var persona_store in backend.persona_stores.values) {
-        if (persona_store.type_id == "eds") {
-          stores += persona_store;
-        }
-      }
-    }
-    return stores;
-  }
-
   public PersonaStore[] get_eds_address_books_from_backend (BackendStore backend_store) {
     PersonaStore[] stores = {};
     foreach (var backend in backend_store.enabled_backends.values) {
@@ -386,6 +374,10 @@ namespace Contacts.Utils {
 
   public string format_persona_store_name (PersonaStore store) {
     if (store.type_id == "eds") {
+      // Special-case the local address book
+      if (store.id == "system-address-book")
+        return _("Local Address Book");
+
       string? eds_name = lookup_esource_name_by_uid (store.id);
       if (eds_name != null)
         return eds_name;
diff --git a/src/meson.build b/src/meson.build
index 7a3ddf62..107514e7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -71,7 +71,6 @@ libcontacts_dep = declare_dependency(
 
 # The gnome-contacts binary
 contacts_vala_sources = files(
-  'contacts-addressbook-list.vala',
   'contacts-addressbook-dialog.vala',
   'contacts-accounts-list.vala',
   'contacts-app.vala',


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