[gnome-contacts/nielsdg/account-list-fix-selection] Use a ListModel for address books (PersonaStore-s)
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-contacts/nielsdg/account-list-fix-selection] Use a ListModel for address books (PersonaStore-s)
- Date: Sat, 4 Jun 2022 21:00:52 +0000 (UTC)
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]