diff options
author | Niels De Graef <nielsdegraef@gmail.com> | 2022-01-20 11:14:52 +0100 |
---|---|---|
committer | Niels De Graef <nielsdegraef@gmail.com> | 2022-02-08 08:58:40 +0100 |
commit | 54aca556e2465cd73ffdf8413b6267d3389aa952 (patch) | |
tree | 0d345716dc63ff98bee64d3bb3d091ec294d5d4c | |
parent | 223a4c8bd7ac6c766cc364dd2ef2289ee84f5220 (diff) | |
download | gnome-contacts-feature/role-details.tar.gz |
Support showing a role of a contactfeature/role-details
The role property contains 2 (optional) elements: an organisation name
(note that "organisation" is broader than "company"), and the role of
the person in that organisation (for example: "Board Member").
This is mostly useful for people who work in a corporate setting, but
can also be nice to keep track of what your relatives are doing.
In the ContactSheet (ie. viewing mode), the property shows itself as a
single row: "$ROLE at $ORGANISATION"; the text may slightly differ if
one of the two is not available. In the ContactEditor, it gets split up
into two rows in the same listbox: one for the organisation and one for
the actual role.
Finally, note that a contact can have multiple roles/organisations, so
this property can occur more than once.
-rw-r--r-- | data/contacts.gresource.xml | 1 | ||||
-rw-r--r-- | data/icons/scalable/actions/building-symbolic.svg | 4 | ||||
-rw-r--r-- | data/ui/style.css | 14 | ||||
-rw-r--r-- | src/contacts-contact-sheet.vala | 35 | ||||
-rw-r--r-- | src/contacts-editor-persona.vala | 1 | ||||
-rw-r--r-- | src/contacts-editor-property.vala | 76 | ||||
-rw-r--r-- | src/contacts-fake-persona-store.vala | 31 | ||||
-rw-r--r-- | src/contacts-utils.vala | 2 |
8 files changed, 156 insertions, 8 deletions
diff --git a/data/contacts.gresource.xml b/data/contacts.gresource.xml index 25f44e1..0455ec7 100644 --- a/data/contacts.gresource.xml +++ b/data/contacts.gresource.xml @@ -4,6 +4,7 @@ <file compressed="true">ui/style.css</file> <file preprocess="xml-stripblanks">icons/scalable/actions/birthday-symbolic.svg</file> + <file preprocess="xml-stripblanks">icons/scalable/actions/building-symbolic.svg</file> <file preprocess="xml-stripblanks">icons/scalable/actions/calendar-symbolic.svg</file> <file preprocess="xml-stripblanks">icons/scalable/actions/chat-symbolic.svg</file> <file preprocess="xml-stripblanks">icons/scalable/actions/external-link-symbolic.svg</file> diff --git a/data/icons/scalable/actions/building-symbolic.svg b/data/icons/scalable/actions/building-symbolic.svg new file mode 100644 index 0000000..fcf36a6 --- /dev/null +++ b/data/icons/scalable/actions/building-symbolic.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> + <path d="m 2 0 c -0.554688 0 -1.027344 0.445312 -1 1 v 14 h -1 v 1 h 16 v -1 h -1 v -14 c 0 -1 -1 -1 -1 -1 z m 1.25 2 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m -8 3 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m -8 3 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 4 0 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m -8 3 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 3.75 1 h 2 v 3 h -2 z m 4.25 -1 h 1.5 c 0.136719 0 0.25 0.113281 0.25 0.25 v 1.5 c 0 0.136719 -0.113281 0.25 -0.25 0.25 h -1.5 c -0.136719 0 -0.25 -0.113281 -0.25 -0.25 v -1.5 c 0 -0.136719 0.113281 -0.25 0.25 -0.25 z m 0 0" fill="#2e3436"/> +</svg> diff --git a/data/ui/style.css b/data/ui/style.css index 4e68694..c80cfb8 100644 --- a/data/ui/style.css +++ b/data/ui/style.css @@ -70,11 +70,14 @@ flowboxchild.circular { .contacts-editor-property { } - .contacts-editor-property .contacts-property-icon, - .contacts-editor-property entry.contacts-editor-main-widget image { + .contacts-editor-property .contacts-property-icon { margin: 12px 12px; } + .contacts-editor-property entry.contacts-editor-main-widget image { + margin: 9px 12px; + } + .contacts-editor-property entry.contacts-editor-main-widget { padding: 4px 6px 4px 0; /* left padding is for the icon */ } @@ -83,13 +86,14 @@ flowboxchild.circular { padding: 10px 0; } -/* Class for editing postal address */ -.contacts-editor-address { +.contacts-editor-address, +.contacts-editor-role { padding-top: 6px; padding-bottom: 6px; } - .contacts-editor-address entry { + .contacts-editor-address entry, + .contacts-editor-role entry { padding: 6px 3px; } diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala index 0d99aed..6727251 100644 --- a/src/contacts-contact-sheet.vala +++ b/src/contacts-contact-sheet.vala @@ -58,6 +58,7 @@ public class Contacts.ContactSheet : Gtk.Grid { "email-addresses", "phone-numbers", "im-addresses", + "roles", "urls", "nickname", "birthday", @@ -203,12 +204,46 @@ public class Contacts.ContactSheet : Gtk.Grid { case "postal-addresses": add_postal_addresses (persona, property); break; + case "roles": + add_roles (persona, property); + break; default: debug ("Unsupported property: %s", property); break; } } + private void add_roles (Persona persona, string property) { + unowned var details = persona as RoleDetails; + if (details == null) + return; + + var roles = Utils.sort_fields<RoleFieldDetails>(details.roles); + var rows = new GLib.List<Gtk.ListBoxRow> (); + foreach (var role in roles) { + if (role.value.is_empty ()) + continue; + + var role_str = ""; + // TRANSLATORS: "$ROLE at $ORGANISATION", e.g. "CEO at Linux Inc." + if (role.value.title != "") { + if (role.value.organisation_name != "") + role_str = _("%s at %s").printf (role.value.title, role.value.organisation_name); + else + role_str = role.value.title; + } else { + role_str = role.value.organisation_name; + } + + var row = new ContactSheetRow (property, role_str); + + //XXX if no role: set "Organisation" tool tip + rows.append (row); + } + + this.attach_rows (rows); + } + private void add_emails (Persona persona, string property) { unowned var details = persona as EmailDetails; if (details == null) diff --git a/src/contacts-editor-persona.vala b/src/contacts-editor-persona.vala index bdb506f..365e25f 100644 --- a/src/contacts-editor-persona.vala +++ b/src/contacts-editor-persona.vala @@ -31,6 +31,7 @@ public class Contacts.EditorPersona : Gtk.Box { }; private const string[] OTHER_PROPERTIES = { "im-addresses", + "roles", "urls", "nickname", "birthday", diff --git a/src/contacts-editor-property.vala b/src/contacts-editor-property.vala index 888d5a9..3e0ea78 100644 --- a/src/contacts-editor-property.vala +++ b/src/contacts-editor-property.vala @@ -187,6 +187,46 @@ public class Contacts.AddressEditor : Gtk.Box { } } +public class Contacts.RoleEditor : Gtk.Box { + + private Gtk.Entry role_entry; + private Gtk.Entry organisation_entry; + + public signal void changed (); + + construct { + this.add_css_class ("contacts-editor-role"); + this.hexpand = true; + this.orientation = Gtk.Orientation.VERTICAL; + + this.role_entry = new Gtk.Entry (); + this.role_entry.hexpand = true; + this.role_entry.placeholder_text = _("Role"); + this.role_entry.add_css_class ("flat"); + this.role_entry.changed.connect ((_) => { changed(); }); + append (this.role_entry); + + this.organisation_entry = new Gtk.Entry (); + this.organisation_entry.hexpand = true; + this.organisation_entry.placeholder_text = _("Organisation"); + this.organisation_entry.add_css_class ("flat"); + this.organisation_entry.changed.connect ((_) => { changed(); }); + append (this.organisation_entry); + } + + public RoleEditor (RoleFieldDetails details) { + details.value.bind_property ("title", this.role_entry, "text", + BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + details.value.bind_property ("organisation-name", this.organisation_entry, "text", + BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); + } + + public bool is_empty () { + return this.role_entry.get_text () != "" && + this.organisation_entry.get_text () != ""; + } +} + /** * Basic widget to show a single property of a contact (for example an email * address, a birthday, ...). It can show itself using a GtkRevealer animation. @@ -458,6 +498,18 @@ public class Contacts.EditorProperty : Object, ListModel { this.rows.add (create_for_address (address_details.postal_addresses)); } break; + case "roles": + unowned var role_details = p as RoleDetails; + if (role_details != null) { + if (!only_new) { + foreach (var role in role_details.roles) { + this.rows.add (create_for_role (role_details.roles, role)); + } + } + if (this.writeable) + this.rows.add (create_for_role (role_details.roles)); + } + break; } } @@ -669,4 +721,28 @@ public class Contacts.EditorProperty : Object, ListModel { box.sensitive = this.writeable; return box; } + + private EditorPropertyRow create_for_role (Gee.Set<RoleFieldDetails> details_set, + RoleFieldDetails? details = null) { + if (details == null) { + var new_details = new RoleFieldDetails (new Role ()); + details_set.add (new_details); + details = new_details; + } + var box = new EditorPropertyRow ("roles"); + + var role_editor = new RoleEditor (details); + box.set_main_widget (role_editor); + box.is_empty = role_editor.is_empty (); + + role_editor.changed.connect (() => { + // Workaround: we shouldn't do a manual signal + ((FakeHashSet) details_set).changed (); + debug ("Role changed"); + box.is_empty = role_editor.is_empty (); + }); + + box.sensitive = this.writeable; + return box; + } } diff --git a/src/contacts-fake-persona-store.vala b/src/contacts-fake-persona-store.vala index 06437b4..283ad59 100644 --- a/src/contacts-fake-persona-store.vala +++ b/src/contacts-fake-persona-store.vala @@ -77,6 +77,7 @@ public class Contacts.FakePersona : Persona, NameDetails, NoteDetails, PhoneDetails, + RoleDetails, UrlDetails, PostalAddressDetails { @@ -220,6 +221,24 @@ public class Contacts.FakePersona : Persona, } } + public Gee.Set<RoleFieldDetails> roles { + get { + unowned Value? value = this.properties.get ("roles"); + if (value == null) { + var new_value = Value (typeof (Gee.Set)); + var set = new FakeHashSet<RoleFieldDetails> (); + new_value.set_object (set); + set.changed.connect (() => { notify_property ("roles"); }); + this.properties.set ("roles", new_value); + value = new_value; + } + return (Gee.Set<RoleFieldDetails>) value; + } + set { + this.properties.set ("roles", value); + } + } + public DateTime? birthday { get { unowned Value? value = this.properties.get ("birthday"); if (value == null) @@ -363,7 +382,9 @@ public class Contacts.FakePersona : Persona, } break; case "roles": - //roles ((RoleDetails) persona).roles; + foreach (var role in ((RoleDetails) persona).roles) { + this.roles.add (new RoleFieldDetails (role.value, role.parameters)); + } break; case "urls": foreach (var e in ((UrlDetails) persona).urls) { @@ -481,7 +502,13 @@ public class Contacts.FakePersona : Persona, yield ((PostalAddressDetails) persona).change_postal_addresses (copy); break; case "roles": - yield ((RoleDetails) persona).change_roles ((Gee.Set<RoleFieldDetails>) new_value); + var original = (Gee.Set<RoleFieldDetails>) new_value; + var copy = new Gee.HashSet<RoleFieldDetails> (); + foreach (var e in original) { + if (e.value != null && !e.value.is_empty ()) + copy.add (new RoleFieldDetails (e.value, e.parameters)); + } + yield ((RoleDetails) persona).change_roles (copy); break; case "urls": var original = (Gee.Set<UrlFieldDetails>) new_value; diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala index a5359dd..bad8e94 100644 --- a/src/contacts-utils.vala +++ b/src/contacts-utils.vala @@ -563,7 +563,7 @@ namespace Contacts.Utils { { "notes", N_("Note"), "note-symbolic" }, { "phone-numbers", N_("Phone number"), "phone-symbolic" }, { "postal-addresses", N_("Address"), "mark-location-symbolic" }, - { "roles", N_("Role"), null }, + { "roles", N_("Role"), "building-symbolic" }, { "structured-name", N_("Structured name"), "avatar-default-symbolic" }, { "urls", N_("Website"), "website-symbolic" }, { "web-service-addresses", N_("Web service"), null }, |