[gnome-contacts/nielsdg/gtk4: 9/10] wip
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-contacts/nielsdg/gtk4: 9/10] wip
- Date: Wed, 5 Jan 2022 01:07:59 +0000 (UTC)
commit 9c6efd3f03cff8f4fde2a41df7b669764f7d12a2
Author: Niels De Graef <nielsdegraef gmail com>
Date: Wed Dec 8 12:53:32 2021 +0100
wip
data/ui/contacts-contact-pane.ui | 30 +--
data/ui/contacts-main-window.ui | 2 +-
data/ui/style.css | 23 +-
src/contacts-accounts-list.vala | 4 +-
src/contacts-addressbook-list.vala | 4 +-
src/contacts-avatar.vala | 2 -
src/contacts-contact-editor.vala | 10 +-
src/contacts-contact-pane.vala | 24 +-
src/contacts-contact-sheet.vala | 251 +++++++++++---------
src/contacts-crop-dialog.vala | 3 +-
src/contacts-editor-persona.vala | 18 +-
src/contacts-editor-property.vala | 393 ++++++++++++++++---------------
src/contacts-linked-personas-dialog.vala | 9 +-
src/contacts-main-window.vala | 9 +-
src/contacts-type-combo.vala | 70 ++----
src/contacts-typeset.vala | 146 +++++-------
src/contacts-utils.vala | 71 ++++--
17 files changed, 546 insertions(+), 523 deletions(-)
---
diff --git a/data/ui/contacts-contact-pane.ui b/data/ui/contacts-contact-pane.ui
index 710f5143..cab42e67 100644
--- a/data/ui/contacts-contact-pane.ui
+++ b/data/ui/contacts-contact-pane.ui
@@ -10,7 +10,6 @@
<property name="name">none-selected-page</property>
<property name="child">
<object class="AdwStatusPage">
- <property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="icon_name">avatar-default-symbolic</property>
@@ -24,23 +23,16 @@
<property name="name">contact-sheet-page</property>
<property name="child">
<object class="GtkScrolledWindow" id="contact_sheet_view">
- <property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<child>
- <object class="AdwClamp">
- <property name="visible">True</property>
- <property name="margin-top">32</property>
- <property name="margin-bottom">32</property>
- <property name="margin-start">24</property>
- <property name="margin-end">24</property>
- <child>
- <object class="GtkBox" id="contact_sheet_box">
- <property name="visible">True</property>
- </object>
- </child>
+ <object class="AdwClamp" id="contact_sheet_clamp">
+ <property name="maximum-size">500</property>
+ <style>
+ <class name="contacts-contact-sheet-container"/>
+ </style>
</object>
</child>
</object>
@@ -58,15 +50,13 @@
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<child>
- <object class="AdwClamp">
- <property name="visible">True</property>
- <property name="margin-top">32</property>
- <property name="margin-bottom">32</property>
- <property name="margin-start">24</property>
- <property name="margin-end">24</property>
+ <object class="AdwClamp" id="contact_editor_clamp">
+ <style>
+ <class name="contacts-contact-editor-container"/>
+ </style>
+ <property name="maximum-size" bind-source="contact_sheet_clamp"
bind-property="maximum-size" bind-flags="sync-create"/>
<child>
<object class="GtkBox" id="contact_editor_box">
- <property name="visible">True</property>
</object>
</child>
</object>
diff --git a/data/ui/contacts-main-window.ui b/data/ui/contacts-main-window.ui
index 4d2981cb..7ef0e5f0 100644
--- a/data/ui/contacts-main-window.ui
+++ b/data/ui/contacts-main-window.ui
@@ -231,7 +231,7 @@
<object class="GtkOverlay" id="notification_overlay">
<child>
<object class="AdwLeaflet" id="content_box">
- <property name="can-swipe-back">True</property>
+ <property name="can-navigate-back">True</property>
<signal name="notify::folded" handler="on_folded" object="ContactsMainWindow" after="yes"
swapped="no"/>
<signal name="notify::child-transition-running" handler="on_child_transition_running"
object="ContactsMainWindow" after="yes" swapped="no"/>
<child>
diff --git a/data/ui/style.css b/data/ui/style.css
index 33ebf132..1027be8e 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -22,10 +22,13 @@
margin: 12px;
}
-/* Give the avatar in the ContactSheet some margin,
- * so it doesn't jump when switching to the editor. */
-.contacts-contact-sheet .contacts-avatar {
- margin: 4px 8px;
+.contacts-contact-sheet-container,
+.contacts-contact-editor-container {
+ margin: 32px 48px;
+}
+
+.contacts-contact-sheet avatar {
+ margin: 4px 8px 4px 8px;
}
/* The style for the background "watermark" image and text.
@@ -61,14 +64,22 @@ flowboxchild.circular {
/* Contact Editor-related CSS classes */
/* Common class all widgets editing a property */
-.contacts-editor-property-row {
- padding: 12px;
+.contacts-editor-property {
}
+ .contacts-editor-property .contacts-property-icon {
+ margin: 9px;
+ }
+
+ .contacts-editor-property .contacts-editor-main-entry {
+ padding: 12px 6px;
+ }
+
/* Class for editing postal address */
.contacts-editor-address entry {
border-radius: 0;
border-width: 1px 1px 0 1px;
+ padding: 6px 6px;
}
.contacts-editor-address entry:first-child {
diff --git a/src/contacts-accounts-list.vala b/src/contacts-accounts-list.vala
index 265c04cd..c2c49816 100644
--- a/src/contacts-accounts-list.vala
+++ b/src/contacts-accounts-list.vala
@@ -64,9 +64,9 @@ public class Contacts.AccountsList : Adw.Bin {
public void update_contents (bool select_active) {
// Remove all entries
- var child = this.listbox.get_first_child ();
+ unowned var child = this.listbox.get_first_child ();
while (child != null) {
- var next = child.get_next_sibling ();
+ unowned var next = child.get_next_sibling ();
this.listbox.remove (child);
child = next;
}
diff --git a/src/contacts-addressbook-list.vala b/src/contacts-addressbook-list.vala
index 8b8a1c66..87a775ae 100644
--- a/src/contacts-addressbook-list.vala
+++ b/src/contacts-addressbook-list.vala
@@ -73,9 +73,9 @@ public class Contacts.AddressbookList : Adw.Bin {
public void update () {
// Remove all entries
- var child = this.listbox.get_first_child ();
+ unowned var child = this.listbox.get_first_child ();
while (child != null) {
- var next = child.get_next_sibling ();
+ unowned var next = child.get_next_sibling ();
this.listbox.remove (child);
child = next;
}
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index 7ba6a7ac..70cd43e0 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -42,8 +42,6 @@ public class Contacts.Avatar : Adw.Bin {
}
this.child = new Adw.Avatar (size, name, show_initials);
-
- show ();
}
/**
diff --git a/src/contacts-contact-editor.vala b/src/contacts-contact-editor.vala
index 7e90841d..ca8c8968 100644
--- a/src/contacts-contact-editor.vala
+++ b/src/contacts-contact-editor.vala
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2011 Alexander Larsson <alexl redhat com>
* Copyright (C) 2019 Purism SPC
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,13 +23,20 @@ using Folks;
* A widget that allows the user to edit a given {@link Contact}.
*/
public class Contacts.ContactEditor : Gtk.Box {
+
private Individual individual;
private Gtk.Entry name_entry;
private AvatarSelector avatar_selector = null;
private Avatar avatar;
+ construct {
+ this.orientation = Gtk.Orientation.VERTICAL;
+ this.spacing = 12;
+
+ this.add_css_class ("contacts-contact-editor");
+ }
+
public ContactEditor (Individual individual, IndividualAggregator aggregator) {
- Object (orientation: Gtk.Orientation.VERTICAL, spacing: 24);
this.individual = individual;
Gtk.Box header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index 21a0a27b..a031c2e7 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,15 +43,15 @@ public class Contacts.ContactPane : Adw.Bin {
private unowned Gtk.ScrolledWindow contact_sheet_view;
[GtkChild]
- private unowned Gtk.Box contact_sheet_box;
- private ContactSheet? sheet = null;
+ private unowned Adw.Clamp contact_sheet_clamp;
+ private unowned ContactSheet? sheet = null;
[GtkChild]
private unowned Gtk.ScrolledWindow contact_editor_view;
[GtkChild]
private unowned Gtk.Box contact_editor_box;
- private ContactEditor? editor = null;
+ private unowned ContactEditor? editor = null;
public bool on_edit_mode = false;
private LinkSuggestionGrid? suggestion_grid = null;
@@ -69,7 +70,7 @@ public class Contacts.ContactPane : Adw.Bin {
}
public void add_suggestion (Individual i) {
- var parent_overlay = this.get_parent () as Gtk.Overlay;
+ unowned var parent_overlay = this.get_parent () as Gtk.Overlay;
remove_suggestion_grid ();
this.suggestion_grid = new LinkSuggestionGrid (i);
@@ -112,8 +113,10 @@ public class Contacts.ContactPane : Adw.Bin {
assert (this.individual != null);
remove_contact_sheet();
- this.sheet = new ContactSheet (this.individual, this.store);
- this.contact_sheet_box.append (this.sheet);
+ var contacts_sheet = new ContactSheet (this.individual, this.store);
+ contacts_sheet.hexpand = true;
+ this.sheet = contacts_sheet;
+ this.contact_sheet_clamp.set_child (this.sheet);
this.stack.set_visible_child_name ("contact-sheet-page");
var matches = this.store.aggregator.get_potential_matches (this.individual, MatchResult.HIGH);
@@ -132,15 +135,15 @@ public class Contacts.ContactPane : Adw.Bin {
// Remove the suggestion grid that goes along with it.
remove_suggestion_grid ();
- this.contact_sheet_box.remove (this.sheet);
- this.sheet.destroy();
+ this.contact_sheet_clamp.set_child (null);
this.sheet = null;
}
private void create_contact_editor () {
remove_contact_editor ();
- this.editor = new ContactEditor (this.individual, store.aggregator);
+ var contact_editor = new ContactEditor (this.individual, store.aggregator);
+ this.editor = contact_editor;
this.contact_editor_box.append (this.editor);
}
@@ -275,7 +278,8 @@ public class Contacts.ContactPane : Adw.Bin {
if (this.suggestion_grid == null)
return;
- this.suggestion_grid.destroy ();
+ unowned var parent_overlay = this.get_parent () as Gtk.Overlay;
+ parent_overlay.remove_overlay (suggestion_grid);
this.suggestion_grid = null;
}
}
diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala
index 5d71b331..0fd94c3e 100644
--- a/src/contacts-contact-sheet.vala
+++ b/src/contacts-contact-sheet.vala
@@ -17,16 +17,43 @@
using Folks;
+// XXX accesibility? tooltips?
+public class Contacts.ContactSheetRow : Adw.ActionRow {
+
+ public ContactSheetRow (string property_name, string title, string? subtitle = null) {
+ unowned var icon_name = Utils.get_icon_name_for_property (property_name);
+ if (icon_name != null) {
+ var icon = new Gtk.Image.from_icon_name (icon_name);
+ icon.add_css_class ("contacts-property-icon");
+ icon.tooltip_text = Utils.get_display_name_for_property (property_name);
+ this.add_prefix (icon);
+ }
+
+ this.title = Markup.escape_text (title);
+
+ if (subtitle != null)
+ this.subtitle = subtitle;
+ }
+
+ public Gtk.Button add_button (string icon) {
+ var button = new Gtk.Button.from_icon_name (icon);
+ button.valign = Gtk.Align.CENTER;
+ button.add_css_class ("flat");
+ this.add_suffix (button);
+ return button;
+ }
+}
+
/**
* The contact sheet displays the actual information of a contact.
*
* (Note: to edit a contact, use the {@link ContactEditor} instead.
*/
public class Contacts.ContactSheet : Gtk.Grid {
+
private int last_row = 0;
- private Individual individual;
+ private unowned Individual individual;
private unowned Store store;
- public bool narrow { get; set; default = true; }
private const string[] SORTED_PROPERTIES = {
"email-addresses",
@@ -39,8 +66,14 @@ public class Contacts.ContactSheet : Gtk.Grid {
"notes"
};
+ construct {
+ this.row_spacing = 18;
+ this.column_spacing = 12;
+
+ this.add_css_class ("contacts-contact-sheet");
+ }
+
public ContactSheet (Individual individual, Store store) {
- Object (row_spacing: 12, column_spacing: 12);
this.individual = individual;
this.store = store;
@@ -56,37 +89,12 @@ public class Contacts.ContactSheet : Gtk.Grid {
var attrList = new Pango.AttrList ();
attrList.insert (Pango.attr_weight_new (Pango.Weight.BOLD));
store_name.set_attributes (attrList);
- store_name.set_halign (Gtk.Align.START);
- store_name.set_ellipsize (Pango.EllipsizeMode.MIDDLE);
+ store_name.halign = Gtk.Align.START;
+ store_name.ellipsize = Pango.EllipsizeMode.MIDDLE;
return store_name;
}
- private Gtk.Button create_button (string icon) {
- var button = new Gtk.Button.from_icon_name (icon);
- button.valign = Gtk.Align.CENTER;
- button.add_css_class ("flat");
-
- return button;
- }
-
- private void add_row_with_label (string label_value,
- string value,
- Gtk.Widget? btn1 = null,
- Gtk.Widget? btn2 =null) {
- if (value == "" || value == null)
- return;
-
- var action_row = new Adw.ActionRow ();
- action_row.title = value;
- action_row.subtitle = label_value;
- if (btn1 != null)
- action_row.add_suffix (btn1);
- if (btn2 != null)
- action_row.add_suffix (btn2);
- attach_row (action_row);
- }
-
// Helper function that attaches a row to our grid
private void attach_row (Gtk.ListBoxRow row) {
var list_box = new Gtk.ListBox ();
@@ -102,39 +110,35 @@ public class Contacts.ContactSheet : Gtk.Grid {
this.last_row = 0;
// Remove all fields
- var child = get_first_child ();
+ unowned var child = get_first_child ();
while (child != null) {
- var next = child.get_next_sibling ();
+ unowned var next = child.get_next_sibling ();
remove (child);
child = next;
}
- var image_frame = new Avatar (PROFILE_SIZE, this.individual);
- image_frame.set_vexpand (false);
- image_frame.set_valign (Gtk.Align.START);
-
- this.attach (image_frame, 0, 0, 1, 3);
-
- create_name_label ();
+ var header = create_header ();
+ this.attach (header, 0, 0, 1, 1);
- this.last_row += 3; // Name/Avatar takes up 3 rows
+ this.last_row++;
var personas = Utils.get_personas_for_display (this.individual);
/* Cause personas are sorted properly I can do this */
- foreach (var p in personas) {
- bool is_first_persona = (this.last_row == 3);
+ for (int i = 0; i < personas.get_n_items (); i++) {
+ var p = (Persona) personas.get_item (i);
int persona_store_pos = this.last_row;
- if (!is_first_persona) {
+
+ if (i > 0) {
this.attach (create_persona_store_label (p), 0, this.last_row, 3);
this.last_row++;
}
- foreach (var prop in SORTED_PROPERTIES)
+ foreach (unowned var prop in SORTED_PROPERTIES)
add_row_for_property (p, prop);
// Nothing to show in the persona: don't mention it
bool is_empty_persona = (this.last_row == persona_store_pos + 1);
- if (!is_first_persona && is_empty_persona) {
+ if (i > 0 && is_empty_persona) {
this.remove_row (persona_store_pos);
this.last_row--;
}
@@ -147,45 +151,55 @@ public class Contacts.ContactSheet : Gtk.Grid {
name_label.set_markup (name);
}
- private void create_name_label () {
+ private Gtk.Widget create_header () {
+ var header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+
+ var image_frame = new Avatar (PROFILE_SIZE, this.individual);
+ image_frame.vexpand = false;
+ image_frame.valign = Gtk.Align.START;
+ header.append (image_frame);
+
var name_label = new Gtk.Label ("");
+ name_label.hexpand = true;
name_label.ellipsize = Pango.EllipsizeMode.END;
name_label.xalign = 0f;
name_label.lines = 4;
name_label.selectable = true;
- name_label.set_can_focus (false);
- this.attach (name_label, 1, 0, 1, 3);
+ name_label.can_focus = false;
+ header.append (name_label);
update_name_label (name_label);
this.individual.notify["display-name"].connect ((obj, spec) => {
update_name_label (name_label);
});
+
+ return header;
}
private void add_row_for_property (Persona persona, string property) {
switch (property) {
case "email-addresses":
- add_emails (persona);
+ add_emails (persona, property);
break;
case "phone-numbers":
- add_phone_nrs (persona);
+ add_phone_nrs (persona, property);
break;
case "im-addresses":
- add_im_addresses (persona);
+ add_im_addresses (persona, property);
break;
case "urls":
- add_urls (persona);
+ add_urls (persona, property);
break;
case "nickname":
- add_nickname (persona);
+ add_nickname (persona, property);
break;
case "birthday":
- add_birthday (persona);
+ add_birthday (persona, property);
break;
case "notes":
- add_notes (persona);
+ add_notes (persona, property);
break;
case "postal-addresses":
- add_postal_addresses (persona);
+ add_postal_addresses (persona, property);
break;
default:
debug ("Unsupported property: %s", property);
@@ -193,83 +207,90 @@ public class Contacts.ContactSheet : Gtk.Grid {
}
}
- private void add_emails (Persona persona) {
+ private void add_emails (Persona persona, string property) {
unowned var details = persona as EmailDetails;
if (details == null)
return;
var emails = Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
foreach (var email in emails) {
- var action_row = new Adw.ActionRow ();
+ if (email.value == "")
+ continue;
- action_row.add_prefix (new Gtk.Image.from_icon_name ("mail-unread-symbolic"));
- action_row.title = Markup.escape_text (email.value);
- action_row.subtitle = TypeSet.email.format_type (email);
+ var row = new ContactSheetRow (property,
+ email.value,
+ TypeSet.email.format_type (email));
- var button = create_button ("mail-send-symbolic");
+ var button = row.add_button ("mail-send-symbolic");
+ button.tooltip_text = _("Send an email to %s".printf (email.value));
button.clicked.connect (() => {
Utils.compose_mail ("%s <%s>".printf(this.individual.display_name, email.value));
});
- action_row.add_suffix (button);
- this.attach_row (action_row);
+ this.attach_row (row);
}
}
- private void add_phone_nrs (Persona persona) {
+ private void add_phone_nrs (Persona persona, string property) {
unowned var phone_details = persona as PhoneDetails;
if (phone_details == null)
return;
var phones = Utils.sort_fields<PhoneFieldDetails>(phone_details.phone_numbers);
foreach (var phone in phones) {
- var action_row = new Adw.ActionRow ();
+ if (phone.value == "")
+ continue;
- action_row.add_prefix (new Gtk.Image.from_icon_name ("call-start-symbolic"));
- action_row.title = Markup.escape_text (phone.value);
- action_row.subtitle = TypeSet.phone.format_type (phone);
+ var row = new ContactSheetRow (property,
+ phone.value,
+ TypeSet.phone.format_type (phone));
#if HAVE_TELEPATHY
if (this.store.caller_account != null) {
- var button = create_button ("call-start-symbolic");
+ var button = row.add_button ("call-start-symbolic");
+ button.tooltip_text = _("Start a call");
button.clicked.connect (() => {
Utils.start_call (phone.value, this.store.caller_account);
});
- action_row.add_suffix (button);
}
#endif
- this.attach_row (action_row);
+ this.attach_row (row);
}
}
- private void add_im_addresses (Persona persona) {
+ private void add_im_addresses (Persona persona, string property) {
#if HAVE_TELEPATHY
- var im_details = persona as ImDetails;
- if (im_details != null) {
- foreach (var protocol in im_details.im_addresses.get_keys ()) {
- foreach (var id in im_details.im_addresses[protocol]) {
- if (persona is Tpf.Persona) {
- var button = create_button ("user-available-symbolic");
- button.clicked.connect (() => {
- var im_persona = Utils.find_im_persona (individual, protocol, id.value);
- if (im_persona != null) {
- var type = im_persona.presence_type;
- if (type != PresenceType.UNSET && type != PresenceType.ERROR &&
- type != PresenceType.OFFLINE && type != PresenceType.UNKNOWN) {
- Utils.start_chat (this.individual, protocol, id.value);
- }
- }
- });
- add_row_with_label (ImService.get_display_name (protocol), id.value, button);
+ unowned var im_details = persona as ImDetails;
+ if (im_details == null)
+ return;
+
+ foreach (var protocol in im_details.im_addresses.get_keys ()) {
+ foreach (var id in im_details.im_addresses[protocol]) {
+ if (!(persona is Tpf.Persona))
+ continue;
+
+ var row = new ContactSheetRow (property,
+ id.value,
+ ImService.get_display_name (protocol));
+ var button = row.add_button ("user-available-symbolic");
+ button.clicked.connect (() => {
+ var im_persona = Utils.find_im_persona (individual, protocol, id.value);
+ if (im_persona != null) {
+ var type = im_persona.presence_type;
+ if (type != PresenceType.UNSET && type != PresenceType.ERROR &&
+ type != PresenceType.OFFLINE && type != PresenceType.UNKNOWN) {
+ Utils.start_chat (this.individual, protocol, id.value);
+ }
}
- }
+ });
+ this.attach_row (row);
}
}
#endif
}
- private void add_urls (Persona persona) {
+ private void add_urls (Persona persona, string property) {
unowned var url_details = persona as UrlDetails;
if (url_details == null)
return;
@@ -278,12 +299,10 @@ public class Contacts.ContactSheet : Gtk.Grid {
if (url.value == "")
continue;
- var action_row = new Adw.ActionRow ();
-
- action_row.add_prefix (new Gtk.Image.from_icon_name ("web-browser-symbolic"));
- action_row.title = Markup.escape_text (url.value);
+ var row = new ContactSheetRow (property, url.value);
- var button = create_button ("web-browser-symbolic");
+ var button = row.add_button ("web-browser-symbolic");
+ button.tooltip_text = _("Visit website");
button.clicked.connect (() => {
unowned var window = button.get_root () as MainWindow;
if (window == null)
@@ -306,7 +325,7 @@ public class Contacts.ContactSheet : Gtk.Grid {
}
});
- this.attach_row (action_row);
+ this.attach_row (row);
}
}
@@ -319,25 +338,26 @@ public class Contacts.ContactSheet : Gtk.Grid {
return url;
}
- private void add_nickname (Persona persona) {
+ private void add_nickname (Persona persona, string property) {
unowned var name_details = persona as NameDetails;
- if (name_details == null || name_details.nickname != "")
+ if (name_details == null || name_details.nickname == "")
return;
- add_row_with_label (_("Nickname"), name_details.nickname);
+ var row = new ContactSheetRow (property, name_details.nickname);
+ this.attach_row (row);
}
- private void add_birthday (Persona persona) {
+ private void add_birthday (Persona persona, string property) {
unowned var birthday_details = persona as BirthdayDetails;
if (birthday_details == null || birthday_details.birthday == null)
return;
- var action_row = new Adw.ActionRow ();
- action_row.title = Markup.escape_text (birthday_details.birthday.to_local ().format ("%x"));
- this.attach_row (action_row);
+ var birthday_str = birthday_details.birthday.to_local ().format ("%x");
+ var row = new ContactSheetRow (property, birthday_str);
+ this.attach_row (row);
}
- private void add_notes (Persona persona) {
+ private void add_notes (Persona persona, string property) {
unowned var note_details = persona as NoteDetails;
if (note_details == null)
return;
@@ -346,25 +366,24 @@ public class Contacts.ContactSheet : Gtk.Grid {
if (note.value == "")
continue;
- var action_row = new Adw.ActionRow ();
- action_row.title = Markup.escape_text (note.value);
- this.attach_row (action_row);
+ var row = new ContactSheetRow (property, note.value);
+ this.attach_row (row);
}
}
- private void add_postal_addresses (Persona persona) {
+ private void add_postal_addresses (Persona persona, string property) {
unowned var addr_details = persona as PostalAddressDetails;
if (addr_details == null)
return;
foreach (var addr in addr_details.postal_addresses) {
- var action_row = new Adw.ActionRow ();
-
- action_row.add_prefix (new Gtk.Image.from_icon_name ("mark-location-symbolic"));
- action_row.title = Markup.escape_text (string.joinv ("\n", Utils.format_address (addr.value)));
- action_row.subtitle = TypeSet.general.format_type (addr);
+ if (addr.value.is_empty ())
+ continue;
- this.attach_row (action_row);
+ var row = new ContactSheetRow (property,
+ string.joinv ("\n", Utils.format_address (addr.value)),
+ TypeSet.general.format_type (addr));
+ this.attach_row (row);
}
}
}
diff --git a/src/contacts-crop-dialog.vala b/src/contacts-crop-dialog.vala
index c3b20e7d..fba59f1f 100644
--- a/src/contacts-crop-dialog.vala
+++ b/src/contacts-crop-dialog.vala
@@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+// XXX document and make gtkdialog
[GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-crop-dialog.ui")]
public class Contacts.CropDialog : Gtk.Window {
@@ -111,7 +112,7 @@ public class Contacts.CropDialog : Gtk.Window {
this.cheese = null;
#endif
- return Gdk.EVENT_STOP; // XXX what the hell am i supposed to retunr here?
+ return Gdk.EVENT_PROPAGATE;
}
}
diff --git a/src/contacts-editor-persona.vala b/src/contacts-editor-persona.vala
index 88a9889a..03079af0 100644
--- a/src/contacts-editor-persona.vala
+++ b/src/contacts-editor-persona.vala
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2019 Purism SPC
* Author: Julian Sparber <julian sparber puri sm>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,20 +38,21 @@ public class Contacts.EditorPersona : Gtk.Box {
"notes"
};
- private Folks.Persona persona;
- private Gtk.Box header;
+ private unowned Folks.Persona persona;
+ private unowned Gtk.Box header;
private unowned Gtk.Box content;
- private Folks.IndividualAggregator aggregator;
+ private unowned Folks.IndividualAggregator aggregator;
construct {
- this.header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
- append (this.header);
+ var _header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ this.append (_header);
+ this.header = _header;
var listbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 12);
this.content = listbox;
this.content.add_css_class ("content");
- append (this.content);
+ this.append (this.content);
}
public EditorPersona (Persona persona, IndividualAggregator aggregator) {
@@ -77,8 +79,10 @@ public class Contacts.EditorPersona : Gtk.Box {
show_more_content.icon_name = "view-more-symbolic";
show_more_content.label = _("Show More");
show_more_button.set_child (show_more_content);
+ show_more_button.halign = Gtk.Align.CENTER;
+ show_more_button.add_css_class ("flat");
show_more_button.clicked.connect ((current_row) => {
- foreach (var property in OTHER_PROPERTIES) {
+ foreach (unowned string property in OTHER_PROPERTIES) {
debug ("Create property entry for %s", property);
var rows = new EditorProperty (persona, property);
foreach (var row in rows) {
diff --git a/src/contacts-editor-property.vala b/src/contacts-editor-property.vala
index d992bbcb..f486358d 100644
--- a/src/contacts-editor-property.vala
+++ b/src/contacts-editor-property.vala
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2019 Purism SPC
* Author: Julian Sparber <julian sparber puri sm>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,8 +32,8 @@ public class Contacts.BirthdayEditor : Gtk.Dialog {
construct {
// The grid that will contain the Y/M/D fields
var grid = new Gtk.Grid ();
- grid.set_column_spacing (12);
- grid.set_row_spacing (12);
+ grid.column_spacing = 12;
+ grid.row_spacing = 12;
grid.add_css_class ("contacts-editor-birthday");
((Gtk.Box) this.get_content_area ()).append (grid);
@@ -94,9 +95,9 @@ public class Contacts.BirthdayEditor : Gtk.Dialog {
public BirthdayEditor (Gtk.Window window, DateTime birthday) {
Object (transient_for: window, use_header_bar: 1);
- this.day_spin.set_value ((double) birthday.to_local ().get_day_of_month ());
- this.month_combo.set_active (birthday.to_local ().get_month () - 1);
- this.year_spin.set_value ((double)birthday.to_local ().get_year ());
+ this.day_spin.set_value ((double) birthday.get_day_of_month ());
+ this.month_combo.set_active (birthday.get_month () - 1);
+ this.year_spin.set_value ((double)birthday.get_year ());
update_date ();
month_combo.changed.connect (() => {
@@ -153,19 +154,19 @@ public class Contacts.AddressEditor : Gtk.Box {
string postal_part;
details.value.get (AddressEditor.postal_element_props[i], out postal_part);
- entries[i] = new Gtk.Entry ();
- entries[i].set_hexpand (true);
- entries[i].set ("placeholder-text", AddressEditor.postal_element_names[i]);
+ this.entries[i] = new Gtk.Entry ();
+ this.entries[i].hexpand = true;
+ this.entries[i].placeholder_text = AddressEditor.postal_element_names[i];
+ this.entries[i].add_css_class ("flat");
if (postal_part != null)
- entries[i].set_text (postal_part);
+ this.entries[i].text = postal_part;
- append (entries[i]);
+ append (this.entries[i]);
- var entry = entries[i];
var prop_name = AddressEditor.postal_element_props[i];
entries[i].changed.connect (() => {
- details.value.set (prop_name, entry.get_text ());
+ details.value.set (prop_name, this.entries[i].text);
changed ();
});
}
@@ -181,49 +182,43 @@ public class Contacts.AddressEditor : Gtk.Box {
}
}
-public class Contacts.EditorPropertyRow : Gtk.ListBoxRow {
+/**
+ * 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.
+ *
+ * To edit the value of the property, you should supply a widget and set it as
+ * the main widget.
+ */
+public class Contacts.EditorPropertyRow : Adw.Bin {
+
+ private unowned Gtk.Revealer revealer;
+ private unowned Gtk.ListBox listbox;
+
public bool is_empty { get; set; default = true; }
public bool is_removed { get; set; default = false; }
- public string ptype { get; private set; }
- public Gtk.Box container;
- public Gtk.Box header;
- public Gtk.Revealer revealer;
+ public bool removable { get; set; default = false; }
+
+ /** Internal type name of the property */
+ public string ptype { get; construct; }
construct {
- this.add_css_class ("contacts-editor-property-row");
-
- this.revealer = new Gtk.Revealer ();
- //TODO: bind orientation property to available space
- var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
- box.set_valign (Gtk.Align.START);
- this.container = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
- this.header = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
- box.append (this.header);
- box.append (this.container);
- this.set_activatable (false);
- this.set_selectable (false);
-
- this.revealer.set_child (box);
- this.set_child (this.revealer);
- this.revealer.bind_property ("reveal-child", this, "is-removed", BindingFlags.INVERT_BOOLEAN);
+ var _revealer = new Gtk.Revealer ();
+ _revealer.bind_property ("reveal-child", this, "is-removed",
+ BindingFlags.BIDIRECTIONAL | BindingFlags.INVERT_BOOLEAN);
+ this.child = _revealer;
+ this.revealer = _revealer;
+
+ var list_box = new Gtk.ListBox ();
+ this.listbox = list_box;
+ this.listbox.selection_mode = Gtk.SelectionMode.NONE;
+ this.listbox.activate_on_single_click = true;
+ this.listbox.add_css_class ("content");
+ this.listbox.add_css_class ("contacts-editor-property");
+ this.revealer.set_child (listbox);
}
public EditorPropertyRow (string type) {
- this.ptype = type;
- }
-
- // This hides the widget with an animation and then destroys it
- public new void remove () {
- this.revealer.set_reveal_child (false);
- // Remove the separator during the animation to make it look a little better
- Timeout.add (this.revealer.get_transition_duration ()/2, () => {
- this.set_header (null);
- return false;
- });
-
- this.revealer.notify["child-revealed"].connect ( () => {
- this.destroy ();
- });
+ Object (ptype: type);
}
public void show_with_animation (bool animate = true) {
@@ -236,114 +231,104 @@ public class Contacts.EditorPropertyRow : Gtk.ListBoxRow {
}
}
- public void add_base_label (string label) {
- var title_label = new Gtk.Label (label);
- title_label.set_hexpand (false);
- title_label.set_halign (Gtk.Align.START);
- title_label.margin_end = 6;
- this.header.append (title_label);
- }
+ // This hides the widget with an animation and then destroys it
+ public void remove () {
+ debug ("Property %s is removed", this.ptype);
- public void add_base_combo (Gee.Set<AbstractFieldDetails> details_set,
- string label,
- TypeSet combo_type,
- AbstractFieldDetails details) {
- var title_label = new Gtk.Label (label);
- title_label.set_halign (Gtk.Align.START);
- this.header.append (title_label);
- TypeCombo combo = new TypeCombo (combo_type);
- combo.set_hexpand (false);
- combo.set_active_from_field_details (details);
- this.header.append (combo);
-
- combo.changed.connect (() => {
- combo.active_descriptor.save_to_field_details(details);
- // Workaround: we shouldn't do a manual signal
- ((FakeHashSet) details_set).changed ();
- debug ("Property phone changed");
+ this.revealer.set_reveal_child (false);
+
+ // Remove the separator during the animation to make it look a little better
+ Timeout.add (this.revealer.get_transition_duration ()/2, () => {
+ return false;
});
- }
- //FIXME: create only one add_base_entry
- public void add_base_entry_email (Gee.Set<AbstractFieldDetails> details_set,
- EmailFieldDetails details,
- string placeholder) {
- var value_entry = new Gtk.Entry ();
- value_entry.set_input_purpose (Gtk.InputPurpose.EMAIL);
- value_entry.placeholder_text = placeholder;
- value_entry.set_text (details.value);
- value_entry.set_hexpand (true);
- this.container.append (value_entry);
-
- this.is_empty = details.value == "";
-
- value_entry.changed.connect (() => {
- details.value = value_entry.get_text ();
- // Workaround: we shouldn't do a manual signal
- ((FakeHashSet) details_set).changed ();
- debug ("Property email changed");
- this.is_empty = value_entry.get_text () == "";
+ this.revealer.notify["child-revealed"].connect ( () => {
+ this.destroy ();
});
}
- public void add_base_entry_phone (Gee.Set<AbstractFieldDetails> details_set,
- PhoneFieldDetails details,
- string placeholder) {
- var value_entry = new Gtk.Entry ();
- value_entry.set_input_purpose (Gtk.InputPurpose.PHONE);
- value_entry.placeholder_text = placeholder;
- value_entry.set_text (details.value);
- value_entry.set_hexpand (true);
- this.container.append (value_entry);
+ /**
+ * Setter for the main widget, which can be used to actually edit the property
+ */
+ public void set_main_widget (Gtk.Widget widget) {
+ var row = new Gtk.ListBoxRow ();
+ row.focusable = false;
+
+ var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+ widget.hexpand = true;
+ row.set_child (box);
+
+ // Start with the icon (if known)
+ unowned var icon_name = Utils.get_icon_name_for_property (this.ptype);
+ if (icon_name != null) {
+ var icon = new Gtk.Image.from_icon_name (icon_name);
+ icon.add_css_class ("contacts-property-icon");
+ icon.tooltip_text = Utils.get_display_name_for_property (this.ptype);
+ box.prepend (icon);
+ }
- this.is_empty = details.value == "";
+ // Set the actual widget
+ // (mimic Adw.ActionRow's "activatable-widget")
+ box.append (widget);
+ this.listbox.row_activated.connect ((activated_row) => {
+ if (row == activated_row)
+ widget.mnemonic_activate (false);
+ });
- value_entry.changed.connect (() => {
- details.value = value_entry.get_text ();
- // Workaround: we shouldn't do a manual signal
- ((FakeHashSet) details_set).changed ();
- debug ("Property type changed");
+ // Add a delete buton if needed
+ if (this.removable) {
+ var delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
+ delete_button.tooltip_text = _("Delete field");
+ this.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE |
BindingFlags.INVERT_BOOLEAN);
- this.is_empty = value_entry.get_text () == "";
- });
+ delete_button.clicked.connect ((b) => { this.remove (); });
+
+ box.append (delete_button);
+ }
+
+ this.listbox.append (row);
}
- public void add_base_entry_url (Gee.Set<AbstractFieldDetails> details_set,
- UrlFieldDetails details,
- string placeholder) {
- var value_entry = new Gtk.Entry ();
- value_entry.placeholder_text = placeholder;
- value_entry.set_input_purpose (Gtk.InputPurpose.URL);
- value_entry.set_text (details.value);
- value_entry.set_hexpand (true);
- this.container.append (value_entry);
+ /**
+ * Wrapper around set_main_widget() with some extra styling for GtkEntries,
+ * as well as making sure the "is-empty" property is updated.
+ */
+ public Gtk.Entry set_main_entry (string text, string? placeholder = null) {
+ var entry = new Gtk.Entry ();
+ entry.text = text;
+ entry.placeholder_text = placeholder;
+ entry.add_css_class ("flat");
+ entry.add_css_class ("contacts-editor-main-entry");
+ this.set_main_widget (entry);
+
+ this.is_empty = (text == "");
+ entry.changed.connect (() => {
+ this.is_empty = (entry.text == "");
+ });
- this.is_empty = details.value == "";
+ return entry;
+ }
- value_entry.changed.connect (() => {
- details.value = value_entry.get_text ();
+ // Adds an extra row for a type combo, to choose between e.g. "Home" or "Work"
+ public void add_type_combo (Gee.Set<AbstractFieldDetails> details_set,
+ TypeSet combo_type,
+ AbstractFieldDetails details) {
+ var row = new TypeComboRow (combo_type);
+ row.title = _("Label");
+ row.set_selected_from_field_details (details);
+ this.listbox.append (row);
+
+ row.notify["selected-item"].connect ((obj, pspec) => {
+ unowned var descr = row.selected_descriptor;
+ descr.save_to_field_details (details);
// Workaround: we shouldn't do a manual signal
((FakeHashSet) details_set).changed ();
- debug ("Property type changed");
-
- this.is_empty = value_entry.get_text () == "";
+ debug ("Property phone changed");
});
}
public void add_base_delete (Gee.Set<AbstractFieldDetails> details_set,
AbstractFieldDetails details) {
- var delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic");
- delete_button.tooltip_text = _("Delete field");
- delete_button.set_valign (Gtk.Align.START);
- this.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE |
BindingFlags.INVERT_BOOLEAN);
- this.container.append (delete_button);
-
-
- delete_button.clicked.connect (() => {
- debug ("Property removed");
- this.remove ();
- details_set.remove (details);
- });
}
}
@@ -355,7 +340,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
public bool writeable { get; private set; default = false; }
public EditorProperty (Persona persona, string property_name, bool only_new = false) {
- foreach (var s in persona.writeable_properties) {
+ foreach (unowned string s in persona.writeable_properties) {
if (s == property_name) {
this.writeable = true;
break;
@@ -368,7 +353,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
private void create_for_property (Persona p, string prop_name, bool only_new) {
switch (prop_name) {
case "email-addresses":
- var details = p as EmailDetails;
+ unowned var details = p as EmailDetails;
if (details != null) {
var emails = Utils.sort_fields<EmailFieldDetails>(details.email_addresses);
if (!only_new)
@@ -380,7 +365,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
break;
case "phone-numbers":
- var details = p as PhoneDetails;
+ unowned var details = p as PhoneDetails;
if (details != null) {
var phones = Utils.sort_fields<PhoneFieldDetails>(details.phone_numbers);
if (!only_new)
@@ -392,7 +377,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
break;
case "urls":
- var details = p as UrlDetails;
+ unowned var details = p as UrlDetails;
if (details != null) {
var urls = Utils.sort_fields<UrlFieldDetails>(details.urls);
if (!only_new)
@@ -403,19 +388,19 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
break;
case "nickname":
- var name_details = p as NameDetails;
+ unowned var name_details = p as NameDetails;
if (name_details != null && name_details.nickname != null && !only_new) {
add (create_for_nick (name_details));
}
break;
case "birthday":
- var birthday_details = p as BirthdayDetails;
+ unowned var birthday_details = p as BirthdayDetails;
if (birthday_details != null && !only_new) {
add (create_for_birthday (birthday_details));
}
break;
case "notes":
- var note_details = p as NoteDetails;
+ unowned var note_details = p as NoteDetails;
if (note_details != null) {
if (!only_new)
foreach (var note in note_details.notes) {
@@ -426,7 +411,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
break;
case "postal-addresses":
- var address_details = p as PostalAddressDetails;
+ unowned var address_details = p as PostalAddressDetails;
if (address_details != null) {
if (!only_new)
foreach (var addr in address_details.postal_addresses) {
@@ -439,83 +424,105 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
}
- private EditorPropertyRow create_for_email (Gee.Set<AbstractFieldDetails> set,
+ private EditorPropertyRow create_for_email (Gee.Set<AbstractFieldDetails> details_set,
EmailFieldDetails? details = null) {
if (details == null) {
var parameters = new Gee.HashMultiMap<string, string> ();
parameters["type"] = "PERSONAL";
var new_details = new EmailFieldDetails ("", parameters);
- set.add(new_details);
+ details_set.add (new_details);
details = new_details;
}
- var box = new EditorPropertyRow ("email-addresses");
- box.add_base_combo (set, _("Email address"), TypeSet.email, details);
- box.add_base_entry_email (set, details, _("Add email"));
- box.add_base_delete (set, details);
+ var box = new EditorPropertyRow ("email-addresses");
box.sensitive = this.writeable;
+
+ var entry = box.set_main_entry (details.value, _("Add email"));
+ entry.set_input_purpose (Gtk.InputPurpose.EMAIL);
+ entry.changed.connect (() => {
+ details.value = entry.get_text ();
+ // Workaround: we shouldn't do a manual signal
+ ((FakeHashSet) details_set).changed ();
+ debug ("Property email changed");
+ });
+
+ box.add_type_combo (details_set, TypeSet.email, details);
+ box.add_base_delete (details_set, details);
+
return box;
}
- private EditorPropertyRow create_for_phone (Gee.Set<AbstractFieldDetails> set,
+ private EditorPropertyRow create_for_phone (Gee.Set<AbstractFieldDetails> details_set,
PhoneFieldDetails? details = null) {
if (details == null) {
var parameters = new Gee.HashMultiMap<string, string> ();
parameters["type"] = "CELL";
var new_details = new PhoneFieldDetails ("", parameters);
- set.add(new_details);
+ details_set.add (new_details);
details = new_details;
}
var box = new EditorPropertyRow ("phone-numbers");
- box.add_base_combo (set, _("Phone number"), TypeSet.phone, details);
- box.add_base_entry_phone (set, details, _("Add number"));
- box.add_base_delete (set, details);
-
box.sensitive = this.writeable;
+
+ var entry = box.set_main_entry (details.value, _("Add phone number"));
+ entry.set_input_purpose (Gtk.InputPurpose.PHONE);
+ entry.changed.connect (() => {
+ details.value = entry.text;
+ // Workaround: we shouldn't do a manual signal
+ ((FakeHashSet) details_set).changed ();
+ debug ("Property type changed");
+ });
+
+ box.add_type_combo (details_set, TypeSet.phone, details);
+ box.add_base_delete (details_set, details);
+
return box;
}
// TODO: add support for different types of urls
- private EditorPropertyRow create_for_url (Gee.Set<AbstractFieldDetails> set,
+ private EditorPropertyRow create_for_url (Gee.Set<AbstractFieldDetails> details_set,
UrlFieldDetails? details = null) {
if (details == null) {
var parameters = new Gee.HashMultiMap<string, string> ();
parameters["type"] = "PERSONAL";
var new_details = new UrlFieldDetails ("", parameters);
- set.add(new_details);
+ details_set.add (new_details);
details = new_details;
}
var box = new EditorPropertyRow ("urls");
- box.add_base_label (_("Website"));
- box.add_base_entry_url (set, details, _("https://example.com"));
- box.add_base_delete (set, details);
-
box.sensitive = this.writeable;
+
+ var entry = box.set_main_entry (details.value, _("https://example.com"));
+ entry.set_input_purpose (Gtk.InputPurpose.PHONE);
+ entry.changed.connect (() => {
+ details.value = entry.get_text ();
+ // Workaround: we shouldn't do a manual signal
+ ((FakeHashSet) details_set).changed ();
+ debug ("Property type changed");
+ });
+
+ box.add_base_delete (details_set, details);
+
return box;
}
private EditorPropertyRow create_for_nick (NameDetails details) {
var box = new EditorPropertyRow ("nickname");
- box.add_base_label (_("Nickname"));
-
- var value_entry = new Gtk.Entry ();
- value_entry.set_text (details.nickname);
- value_entry.set_hexpand (true);
- box.container.append (value_entry);
+ box.sensitive = this.writeable;
- value_entry.changed.connect (() => {
- details.nickname = value_entry.get_text ();
+ var entry = box.set_main_entry (details.nickname, _("Nickname"));
+ entry.set_input_purpose (Gtk.InputPurpose.NAME);
+ entry.changed.connect (() => {
+ details.nickname = entry.text;
debug ("Nickname changed");
- box.is_empty = value_entry.get_text () == "";
});
- box.sensitive = this.writeable;
return box;
}
- // TODO: support different types of nodes
+ // TODO: support different types of notes
private EditorPropertyRow create_for_note (Gee.Set<NoteFieldDetails> details_set,
NoteFieldDetails? details = null) {
if (details == null) {
@@ -526,24 +533,24 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
details = new_details;
}
var box = new EditorPropertyRow ("notes");
- box.add_base_label (_("Note"));
var sw = new Gtk.ScrolledWindow ();
- sw.has_frame = true;
+ sw.focusable = false;
+ sw.has_frame = false;
sw.set_size_request (-1, 100);
- var value_text = new Gtk.TextView ();
- value_text.get_buffer ().set_text (details.value);
- value_text.set_hexpand (true);
- sw.set_child (value_text);
- box.container.append (sw);
+ var textview = new Gtk.TextView ();
+ textview.get_buffer ().set_text (details.value);
+ textview.hexpand = true;
+ sw.set_child (textview);
+ box.set_main_widget (sw);
box.add_base_delete (details_set, details);
- value_text.get_buffer ().changed.connect (() => {
+ textview.get_buffer ().changed.connect (() => {
Gtk.TextIter start, end;
- value_text.get_buffer ().get_start_iter (out start);
- value_text.get_buffer ().get_end_iter (out end);
- details.value = value_text.get_buffer ().get_text (start, end, true);
+ textview.get_buffer ().get_start_iter (out start);
+ textview.get_buffer ().get_end_iter (out end);
+ details.value = textview.get_buffer ().get_text (start, end, true);
// Workaround: we shouldn't do a manual signal
((FakeHashSet) details_set).changed ();
debug ("Property changed");
@@ -555,20 +562,17 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
}
private EditorPropertyRow create_for_birthday (BirthdayDetails? details) {
- DateTime date;
+ var date = details.birthday ?? new DateTime.now ();
+
Gtk.Button button;
if (details.birthday == null) {
- date = new DateTime.now ();
button = new Gtk.Button.with_label (_("Set Birthday"));
} else {
- date = details.birthday;
button = new Gtk.Button.with_label (details.birthday.to_local ().format ("%x"));
}
var box = new EditorPropertyRow ("birthday");
- box.add_base_label (_("Birthday"));
-
- box.container.append (button);
+ box.set_main_widget (button);
button.clicked.connect (() => {
unowned var parent_window = button.get_root () as Gtk.Window;
@@ -578,7 +582,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
dialog.changed.connect (() => {
if (dialog.is_set) {
details.birthday = dialog.get_birthday ();
- button.set_label (details.birthday.to_local ().format ("%x"));
+ button.set_label (details.birthday.format ("%x"));
box.is_empty = false;
}
});
@@ -592,7 +596,7 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
delete_button.tooltip_text = _("Delete field");
delete_button.set_valign (Gtk.Align.START);
box.bind_property ("is-empty", delete_button, "sensitive", BindingFlags.SYNC_CREATE |
BindingFlags.INVERT_BOOLEAN);
- box.container.append (delete_button);
+ // box.container.append (delete_button); XXX
delete_button.clicked.connect (() => {
debug ("Birthday removed");
@@ -616,13 +620,12 @@ public class Contacts.EditorProperty : Gee.ArrayList<EditorPropertyRow> {
details = new_details;
}
var box = new EditorPropertyRow ("postal-addresses");
- box.add_base_combo (details_set, _("Address"), TypeSet.general, details);
var value_address = new AddressEditor (details);
- box.container.append (value_address);
-
+ box.set_main_widget (value_address);
box.is_empty = value_address.is_empty ();
+ box.add_type_combo (details_set, TypeSet.general, details);
box.add_base_delete (details_set, details);
value_address.changed.connect (() => {
diff --git a/src/contacts-linked-personas-dialog.vala b/src/contacts-linked-personas-dialog.vala
index 4f89d892..f3b605da 100644
--- a/src/contacts-linked-personas-dialog.vala
+++ b/src/contacts-linked-personas-dialog.vala
@@ -40,13 +40,8 @@ public class Contacts.LinkedPersonasDialog : Gtk.Dialog {
// loading personas for display
var personas = Contacts.Utils.get_personas_for_display (individual);
- bool is_first = true;
- foreach (var p in personas) {
- if (is_first) {
- is_first = false;
- continue;
- }
-
+ for (int i = 1; i < personas.get_n_items (); i++) {
+ var p = (Persona) personas.get_item (i);
var row_grid = new Gtk.Grid ();
var image_frame = new Avatar (AVATAR_SIZE, individual);
diff --git a/src/contacts-main-window.vala b/src/contacts-main-window.vala
index e471c326..62152a90 100644
--- a/src/contacts-main-window.vala
+++ b/src/contacts-main-window.vala
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 Alexander Larsson <alexl redhat com>
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef gmail com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -196,9 +197,9 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
}
// Allow the back gesture when not browsing
- this.content_box.can_swipe_back = this.state == UiState.NORMAL ||
- this.state == UiState.SHOWING ||
- this.state == UiState.SELECTING;
+ this.content_box.can_navigate_back = this.state == UiState.NORMAL ||
+ this.state == UiState.SHOWING ||
+ this.state == UiState.SELECTING;
}
[GtkCallback]
@@ -444,7 +445,7 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
this.settings.window_maximized = this.maximized;
this.settings.window_fullscreen = this.fullscreened;
- return Gdk.EVENT_STOP; // XXX what the hell am i supposed to retunr here?
+ return Gdk.EVENT_PROPAGATE;
}
void list_pane_selection_changed_cb (Individual? new_selection) {
diff --git a/src/contacts-type-combo.vala b/src/contacts-type-combo.vala
index 7104fcf9..3f252f7d 100644
--- a/src/contacts-type-combo.vala
+++ b/src/contacts-type-combo.vala
@@ -18,77 +18,47 @@
using Folks;
/**
- * The TypeCombo is a widget that fills itself with the types of a certain
+ * The TypeComboRow is a widget that fills itself with the types of a certain
* category (using {@link Contacts.TypeSet}). For example, it allows the user
* to choose between "Personal", "Home" and "Work" for email addresses,
* together with all the custom labels it has encountered since then.
*/
-public class Contacts.TypeCombo : Gtk.ComboBox {
+public class Contacts.TypeComboRow : Adw.ComboRow {
- private unowned TypeSet type_set;
-
- /**
- * The {@link Contacts.TypeDescriptor} that is currently shown
- */
- public TypeDescriptor active_descriptor {
- get {
- Gtk.TreeIter iter;
-
- get_active_iter (out iter);
- assert (!is_separator (this.model, iter));
-
- unowned TypeDescriptor descriptor;
- this.model.get (iter, 1, out descriptor);
- return descriptor;
- }
- set {
- set_active_iter (value.iter);
- }
+ public TypeDescriptor selected_descriptor {
+ get { return (TypeDescriptor) this.selected_item; }
}
- construct {
- this.valign = Gtk.Align.START;
- this.halign = Gtk.Align.FILL;
- this.hexpand = true;
- this.visible = true;
-
- var renderer = new Gtk.CellRendererText ();
- pack_start (renderer, true);
- set_attributes (renderer, "text", 0);
-
- set_row_separator_func (is_separator);
+ public TypeSet type_set {
+ get { return (TypeSet) this.model; }
}
/**
- * Creates a TypeCombo for the given TypeSet. To set the active value,
- * use the "current-decsriptor" property, set_active_from_field_details(),
- * or set_active_from_vcard_type()
+ * Creates a TypeComboRow for the given TypeSet.
*/
- public TypeCombo (TypeSet type_set) {
- this.type_set = type_set;
- this.model = type_set.store;
- }
-
- private bool is_separator (Gtk.TreeModel model, Gtk.TreeIter iter) {
- unowned string? s;
- model.get (iter, 0, out s);
- return s == null;
+ public TypeComboRow (TypeSet type_set) {
+ Object (
+ model: type_set,
+ expression: new Gtk.PropertyExpression (typeof (TypeDescriptor), null, "display-name")
+ );
}
/**
* Sets the value to the type of the given {@link Folks.AbstractFieldDetails}.
*/
- public void set_active_from_field_details (AbstractFieldDetails details) {
- this.active_descriptor = this.type_set.lookup_descriptor_for_field_details (details);
+ public void set_selected_from_field_details (AbstractFieldDetails details) {
+ uint position = 0;
+ this.type_set.lookup_by_field_details (details, out position);
+ this.selected = position;
}
/**
* Sets the value to the type that best matches the given vcard type
* (for example "HOME" or "WORK").
*/
- public void set_active_from_vcard_type (string type) {
- Gtk.TreeIter iter;
- this.type_set.get_iter_for_vcard_type (type, out iter);
- set_active_iter (iter);
+ public void set_selected_from_vcard_type (string type) {
+ uint position = 0;
+ this.type_set.lookup_by_vcard_type (type, out position);
+ this.selected = position;
}
}
diff --git a/src/contacts-typeset.vala b/src/contacts-typeset.vala
index 3208ac47..731f9d30 100644
--- a/src/contacts-typeset.vala
+++ b/src/contacts-typeset.vala
@@ -22,7 +22,7 @@ using Folks;
* phone number can be both for a personal phone, a work phone or even a fax
* machine.
*/
-public class Contacts.TypeSet : Object {
+public class Contacts.TypeSet : Object, GLib.ListModel {
/** Returns the category of typeset (mostly used for debugging). */
public string category { get; construct set; }
@@ -34,58 +34,20 @@ public class Contacts.TypeSet : Object {
private Gee.List<VcardTypeMapping?> vcard_type_mappings
= new Gee.ArrayList<VcardTypeMapping?> ();
- // Contains 2 columns:
- // 1. The type's display name (or null for a separator)
- // 2. The TypeDescriptor
- public Gtk.ListStore store { get; private set; }
+ private GenericArray<TypeDescriptor> descriptors = new GenericArray<TypeDescriptor> ();
/**
* Creates a TypeSet for the given category, e.g. "phones" (used for debugging)
*/
private TypeSet (string? category) {
Object (category: category);
-
- this.store = new Gtk.ListStore (2, typeof (unowned string?), typeof (TypeDescriptor));
- }
-
- /**
- * Returns the {@link Gtk.TreeIter} which corresponds to the type of the
- * given {@link Folks.AbstractFieldDetails}.
- */
- public void get_iter_for_field_details (AbstractFieldDetails detail,
- out Gtk.TreeIter iter) {
- // Note that we shouldn't have null here, but it's there just to be sure.
- var d = lookup_descriptor_for_field_details (detail);
- iter = d.iter;
- }
-
- /**
- * Returns the {@link Gtk.TreeIter} which corresponds the best to the given
- * vcard type.
- *
- * @param type A VCard-like type, such as "HOME" or "CELL".
- */
- public void get_iter_for_vcard_type (string type, out Gtk.TreeIter iter) {
- unowned TypeDescriptor? d = lookup_descriptor_by_vcard_type (type);
- iter = (d != null)? d.iter : this.other_dummy.iter;
- }
-
- /**
- * Returns the {@link Gtk.TreeIter} which corresponds the best to the given
- * custom label.
- */
- public void get_iter_for_custom_label (string label, out Gtk.TreeIter iter) {
- var descr = get_descriptor_for_custom_label (label);
- if (descr == null)
- descr = create_descriptor_for_custom_label (label);
- iter = descr.iter;
}
/**
* Returns the display name for the type of the given AbstractFieldDetails.
*/
public unowned string format_type (AbstractFieldDetails detail) {
- var d = lookup_descriptor_for_field_details (detail);
+ var d = lookup_by_field_details (detail);
return d.display_name;
}
@@ -93,15 +55,10 @@ public class Contacts.TypeSet : Object {
* Adds the TypeDescriptor to the {@link TypeSet}'s store.
* @param descriptor The TypeDescription to be added
*/
- private void add_descriptor_to_store (TypeDescriptor descriptor) {
+ private void add_descriptor (TypeDescriptor descriptor) {
debug ("%s: Adding type %s to store", this.category, descriptor.to_string ());
-
- if (descriptor.is_custom ())
- this.store.insert_before (out descriptor.iter, null);
- else
- this.store.append (out descriptor.iter);
-
- store.set (descriptor.iter, 0, descriptor.display_name, 1, descriptor);
+ this.descriptors.add (descriptor);
+ this.items_changed (this.descriptors.length - 1, 0, 1);
}
/**
@@ -111,60 +68,57 @@ public class Contacts.TypeSet : Object {
* @param display_name The translated display name
* @return The appropriate TypeDescriptor or null if no match was found.
*/
- public unowned TypeDescriptor? lookup_descriptor_in_store (string display_name) {
- Gtk.TreeIter iter;
-
- // Make sure we handle an empty store
- if (!this.store.get_iter_first (out iter))
- return null;
-
- do {
- unowned TypeDescriptor? type_descr;
- this.store.get (iter, 1, out type_descr);
-
- if (display_name.ascii_casecmp (type_descr.display_name) == 0)
- return type_descr;
- if (display_name.ascii_casecmp (type_descr.name) == 0)
- return type_descr;
- } while (this.store.iter_next (ref iter));
+ public unowned TypeDescriptor? lookup_by_display_name (string display_name,
+ out uint position) {
+ for (int i = 0; i < this.descriptors.length; i++) {
+ unowned var type_descr = this.descriptors[i];
+
+ if (display_name.ascii_casecmp (type_descr.display_name) != 0)
+ continue;
+ if (display_name.ascii_casecmp (type_descr.name) != 0)
+ continue;
+
+ position = i;
+ return type_descr;
+ }
// Nothing was found
+ position = 0;
return null;
}
private void add_vcard_mapping (VcardTypeMapping vcard_mapping) {
- TypeDescriptor? descriptor = lookup_descriptor_in_store (vcard_mapping.name);
+ uint position;
+ var descriptor = lookup_by_display_name (vcard_mapping.name, out position);
if (descriptor == null) {
descriptor = new TypeDescriptor.vcard (vcard_mapping.name, vcard_mapping.types);
- add_descriptor_to_store (descriptor);
+ debug ("%s: Adding VCard type %s to store", this.category, descriptor.to_string ());
+ this.add_descriptor (descriptor);
}
this.vcard_type_mappings.add (vcard_mapping);
}
- // Refers to the type of the detail, i.e. "Other" instead of "Personal" or "Work"
- private void add_type_other () {
- store.append (out other_dummy.iter);
- store.set (other_dummy.iter, 0, other_dummy.display_name, 1, other_dummy);
- }
-
/**
* Tries to find the TypeDescriptor matching the given custom label, or null if none.
*/
- public unowned TypeDescriptor? get_descriptor_for_custom_label (string label) {
+ public TypeDescriptor? lookup_by_custom_label (string label,
+ out uint position) {
// Check in the current display names
- unowned TypeDescriptor? descriptor = lookup_descriptor_in_store (label);
+ unowned var descriptor = lookup_by_display_name (label, out position);
if (descriptor != null)
return descriptor;
// Try again, but use the vcard types too
- descriptor = lookup_descriptor_by_vcard_type (label);
+ descriptor = lookup_by_vcard_type (label, out position);
return descriptor;
}
private TypeDescriptor create_descriptor_for_custom_label (string label) {
var new_descriptor = new TypeDescriptor.custom (label);
- add_descriptor_to_store (new_descriptor);
+ debug ("%s: Adding custom type %s to store",
+ this.category, new_descriptor.to_string ());
+ this.add_descriptor (new_descriptor);
return new_descriptor;
}
@@ -172,19 +126,26 @@ public class Contacts.TypeSet : Object {
* Returns the TypeDescriptor which corresponds the best to the given vcard type.
* @param str A VCard-like type, such as "HOME" or "CELL".
*/
- private unowned TypeDescriptor? lookup_descriptor_by_vcard_type (string str) {
+ public unowned TypeDescriptor? lookup_by_vcard_type (string str,
+ out uint position) {
foreach (VcardTypeMapping? mapping in this.vcard_type_mappings) {
if (mapping.contains (str))
- return lookup_descriptor_in_store (mapping.name);
+ return lookup_by_display_name (mapping.name, out position);
}
+ position = 0;
return null;
}
- public TypeDescriptor lookup_descriptor_for_field_details (AbstractFieldDetails detail) {
+ /**
+ * Looks up the TypeDescriptor for the given field details. If the descriptor
+ * is not found, it will be created and returned, so this never returns null.
+ */
+ public TypeDescriptor lookup_by_field_details (AbstractFieldDetails detail,
+ out uint position = null) {
if (detail.parameters.contains (TypeDescriptor.X_GOOGLE_LABEL)) {
var label = Utils.get_first<string> (detail.parameters[TypeDescriptor.X_GOOGLE_LABEL]);
- var descriptor = get_descriptor_for_custom_label (label);
+ var descriptor = lookup_by_custom_label (label, out position);
// Still didn't find it => create it
if (descriptor == null)
descriptor = create_descriptor_for_custom_label (label);
@@ -199,12 +160,26 @@ public class Contacts.TypeSet : Object {
foreach (VcardTypeMapping? d in this.vcard_type_mappings) {
if (d.matches (types))
- return lookup_descriptor_in_store (d.name);
+ return lookup_by_display_name (d.name, out position);
}
return this.other_dummy;
}
+ public GLib.Type get_item_type () {
+ return typeof (TypeDescriptor);
+ }
+
+ public uint get_n_items () {
+ return this.descriptors.length;
+ }
+
+ public GLib.Object? get_item (uint i) {
+ if (i > this.descriptors.length)
+ return null;
+
+ return this.descriptors[i];
+ }
private static TypeSet _general;
private const VcardTypeMapping[] general_data = {
@@ -218,7 +193,8 @@ public class Contacts.TypeSet : Object {
_general = new TypeSet ("General");
for (int i = 0; i < general_data.length; i++)
_general.add_vcard_mapping (general_data[i]);
- _general.add_type_other ();
+
+ _general.add_descriptor (general.other_dummy);
}
return _general;
@@ -238,7 +214,7 @@ public class Contacts.TypeSet : Object {
_email = new TypeSet ("Emails");
for (int i = 0; i < email_data.length; i++)
_email.add_vcard_mapping (email_data[i]);
- _email.add_type_other ();
+ _email.add_descriptor (_email.other_dummy);
}
return _email;
@@ -275,7 +251,7 @@ public class Contacts.TypeSet : Object {
_phone = new TypeSet ("Phones");
for (int i = 0; i < phone_data.length; i++)
_phone.add_vcard_mapping (phone_data[i]);
- _phone.add_type_other ();
+ _phone.add_descriptor (_phone.other_dummy);
}
return _phone;
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index f7a95f31..4627acee 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -348,18 +348,23 @@ namespace Contacts.Utils {
return false;
}
- public Gee.List<Persona> get_personas_for_display (Individual individual) {
- CompareDataFunc<Persona> compare_persona_by_store = (a, b) => {
- unowned var store_a = a.store;
- unowned var store_b = b.store;
+ public ListModel get_personas_for_display (Individual individual) {
+ var persona_list = new ListStore(typeof(Persona));
+ foreach (var persona in individual.personas)
+ if (persona.store.type_id != "key-file")
+ persona_list.append (persona);
+
+ persona_list.sort ((a, b) => {
+ unowned var store_a = ((Persona) a).store;
+ unowned var store_b = ((Persona) b).store;
// In the same store, sort Google 'other' contacts last
if (store_a == store_b) {
- if (!persona_is_google (a))
+ if (!persona_is_google ((Persona) a))
return 0;
- var a_is_other = persona_is_google_other (a);
- if (a_is_other != persona_is_google_other (b))
+ var a_is_other = persona_is_google_other ((Persona) a);
+ if (a_is_other != persona_is_google_other ((Persona) b))
return a_is_other? 1 : -1;
}
@@ -373,14 +378,8 @@ namespace Contacts.Utils {
// Normal case: use alphabetical sorting
return strcmp (store_a.id, store_b.id);
- };
-
- var persona_list = new Gee.ArrayList<Persona>();
- foreach (var persona in individual.personas)
- if (persona.store.type_id != "key-file")
- persona_list.add (persona);
+ });
- persona_list.sort ((owned) compare_persona_by_store);
return persona_list;
}
@@ -567,6 +566,50 @@ namespace Contacts.Utils {
}
}
+ // A helper struct to keep track on general properties on how each Persona
+ // property should be displayed
+ private struct PropertyDisplayInfo {
+ string property_name;
+ string display_name;
+ string icon_name;
+ }
+
+ private const PropertyDisplayInfo[] display_infos = {
+ { "alias", N_("Alias"), null },
+ { "avatar", N_("Avatar"), "emblem-photos-symbolic" },
+ { "birthday", N_("Birthday"), "x-office-calendar-symbolic" },
+ { "calendar-event-id", N_("Calendar event"), "x-office-calendar-symbolic" },
+ { "email-addresses", N_("Email address"), "mail-unread-symbolic" },
+ { "full-name", N_("Full name"), null },
+ { "gender", N_("Gender"), null },
+ { "groups", N_("Group"), null },
+ { "im-addresses", N_("Instant messaging"), "user-available-symbolic" },
+ { "is-favourite", N_("Favourite"), "emblem-favorite-symbolic" },
+ { "local-ids", N_("Local ID"), null },
+ { "nickname", N_("Nickname"), "avatar-default-symbolic" },
+ { "notes", N_("Note"), "accessories-text-editor-symbolic" },
+ { "phone-numbers", N_("Phone number"), "phone-symbolic" },
+ { "postal-addresses", N_("Address"), "mark-location-symbolic" },
+ { "roles", N_("Role"), null },
+ { "structured-name", N_("Structured name"), "avatar-default-symbolic" },
+ { "urls", N_("Website"), "web-browser-symbolic" },
+ { "web-service-addresses", N_("Web service"), null },
+ };
+
+ public unowned string get_display_name_for_property (string property_name) {
+ foreach (unowned var info in display_infos)
+ if (info.property_name == property_name)
+ return gettext (info.display_name);
+ return_val_if_reached (null);
+ }
+
+ public unowned string? get_icon_name_for_property (string property_name) {
+ foreach (unowned var info in display_infos)
+ if (info.property_name == property_name)
+ return info.icon_name;
+ return null;
+ }
+
#if HAVE_TELEPATHY
public void fetch_contact_info (Individual individual) {
/* TODO: Ideally Folks should have API for this (#675131) */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]