summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiels De Graef <nielsdegraef@gmail.com>2022-01-20 11:14:52 +0100
committerNiels De Graef <nielsdegraef@gmail.com>2022-02-08 08:58:40 +0100
commit54aca556e2465cd73ffdf8413b6267d3389aa952 (patch)
tree0d345716dc63ff98bee64d3bb3d091ec294d5d4c
parent223a4c8bd7ac6c766cc364dd2ef2289ee84f5220 (diff)
downloadgnome-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.xml1
-rw-r--r--data/icons/scalable/actions/building-symbolic.svg4
-rw-r--r--data/ui/style.css14
-rw-r--r--src/contacts-contact-sheet.vala35
-rw-r--r--src/contacts-editor-persona.vala1
-rw-r--r--src/contacts-editor-property.vala76
-rw-r--r--src/contacts-fake-persona-store.vala31
-rw-r--r--src/contacts-utils.vala2
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 },