[gnome-contacts/nielsdg/gtk4: 10/10] WIP
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-contacts/nielsdg/gtk4: 10/10] WIP
- Date: Wed, 5 Jan 2022 01:07:59 +0000 (UTC)
commit 57d0fabebeb201449dd393299bb72715c1983033
Author: Niels De Graef <nielsdegraef gmail com>
Date: Wed Jan 5 02:07:23 2022 +0100
WIP
data/ui/contacts-main-window.ui | 8 +-
data/ui/style.css | 19 ++--
src/contacts-avatar.vala | 7 ++
src/contacts-contact-pane.vala | 12 +--
src/contacts-contact-sheet.vala | 21 +----
src/contacts-delete-operation.vala | 55 +++++++++++
src/contacts-editor-property.vala | 26 ++++--
src/contacts-link-operation.vala | 79 ++++++++++++++++
src/contacts-linking.vala | 103 ---------------------
src/contacts-main-window.vala | 183 +++++++++++++++++--------------------
src/contacts-operation.vala | 63 +++++++++++++
src/contacts-unlink-operation.vala | 56 ++++++++++++
src/contacts-utils.vala | 7 +-
src/meson.build | 5 +-
todo-gtk4.md | 7 ++
15 files changed, 391 insertions(+), 260 deletions(-)
---
diff --git a/data/ui/contacts-main-window.ui b/data/ui/contacts-main-window.ui
index 7ef0e5f0..a1bb42ee 100644
--- a/data/ui/contacts-main-window.ui
+++ b/data/ui/contacts-main-window.ui
@@ -68,7 +68,7 @@
<object class="AdwLeaflet" id="header">
<property name="visible-child-name" bind-source="content_box" bind-property="visible-child-name"
bind-flags="bidirectional|sync-create"/>
<property name="mode-transition-duration" bind-source="content_box"
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
- <property name="child-transition-duration" bind-source="content_box"
bind-property="child-transition-duration" bind-flags="bidirectional|sync-create"/>
+ <property name="child-transition-params" bind-source="content_box"
bind-property="child-transition-params" bind-flags="bidirectional|sync-create"/>
<property name="transition-type" bind-source="content_box" bind-property="transition-type"
bind-flags="bidirectional|sync-create"/>
<child>
<object class="AdwLeafletPage">
@@ -148,7 +148,6 @@
<child>
<object class="GtkRevealer" id="back_revealer">
<property name="transition-type">slide-right</property>
- <property name="transition-duration" bind-source="content_box"
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
<child>
<object class="GtkButton" id="back">
<property name="valign">center</property>
@@ -214,10 +213,11 @@
<child type="end">
<object class="GtkButton" id="done_button">
<property name="visible">False</property>
+ <property name="use_underline">True</property>
<property name="label" translatable="yes">Done</property>
<property name="valign">center</property>
<style>
- <class name="text-button"/>
+ <class name="suggested-action"/>
</style>
</object>
</child>
@@ -228,7 +228,7 @@
</object>
</child>
<child>!
- <object class="GtkOverlay" id="notification_overlay">
+ <object class="AdwToastOverlay" id="toast_overlay">
<child>
<object class="AdwLeaflet" id="content_box">
<property name="can-navigate-back">True</property>
diff --git a/data/ui/style.css b/data/ui/style.css
index 1027be8e..fc8b9f0a 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -52,27 +52,20 @@ flowboxchild.circular {
padding: 0 0;
}
-.contacts-flatten:not(:hover) {
- background-color: transparent;
- background-image: none;
- border-color: transparent;
- box-shadow: inset 0 1px rgba(255, 255, 255, 0), 0 1px rgba(255, 255, 255, 0);
- text-shadow: none; -gtk-icon-shadow: none;
- border: 1px solid rgba(205, 199, 194, 0.5);
-}
-
/* Contact Editor-related CSS classes */
-/* Common class all widgets editing a property */
+/* Common class for all widgets editing a property */
.contacts-editor-property {
+ margin: 6px 0;
}
- .contacts-editor-property .contacts-property-icon {
- margin: 9px;
+ .contacts-editor-property .contacts-property-icon,
+ .contacts-editor-property .contacts-editor-main-entry image {
+ margin: 10px;
}
.contacts-editor-property .contacts-editor-main-entry {
- padding: 12px 6px;
+ padding: 6px 6px 6px 0px;
}
/* Class for editing postal address */
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index 70cd43e0..791179d4 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -42,6 +42,13 @@ public class Contacts.Avatar : Adw.Bin {
}
this.child = new Adw.Avatar (size, name, show_initials);
+
+ if (individual != null && individual.avatar != null) {
+ this.map.connect (() => {
+ var stream = this.individual.avatar.load (size, null);
+ this.set_pixbuf (new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true));
+ });
+ }
}
/**
diff --git a/src/contacts-contact-pane.vala b/src/contacts-contact-pane.vala
index a031c2e7..5329d36e 100644
--- a/src/contacts-contact-pane.vala
+++ b/src/contacts-contact-pane.vala
@@ -36,19 +36,11 @@ public class Contacts.ContactPane : Adw.Bin {
[GtkChild]
private unowned Gtk.Stack stack;
- [GtkChild]
- private unowned Gtk.StackPage none_selected_page;
-
- [GtkChild]
- private unowned Gtk.ScrolledWindow contact_sheet_view;
[GtkChild]
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 unowned ContactEditor? editor = null;
@@ -79,11 +71,11 @@ public class Contacts.ContactPane : Adw.Bin {
this.suggestion_grid.suggestion_accepted.connect ( () => {
var linked_contact = this.individual.display_name;
- var operation = new LinkOperation (this.store);
var to_link = new Gee.LinkedList<Individual> ();
to_link.add (this.individual);
to_link.add (i);
- operation.execute.begin (to_link);
+ var operation = new LinkOperation (this.store, to_link);
+ operation.execute.begin ();
this.contacts_linked (null, linked_contact, operation);
remove_suggestion_grid ();
});
diff --git a/src/contacts-contact-sheet.vala b/src/contacts-contact-sheet.vala
index 0fd94c3e..dc0a9830 100644
--- a/src/contacts-contact-sheet.vala
+++ b/src/contacts-contact-sheet.vala
@@ -17,7 +17,7 @@
using Folks;
-// XXX accesibility? tooltips?
+// XXX accesibility?
public class Contacts.ContactSheetRow : Adw.ActionRow {
public ContactSheetRow (string property_name, string title, string? subtitle = null) {
@@ -308,21 +308,10 @@ public class Contacts.ContactSheet : Gtk.Grid {
if (window == null)
return;
- try {
- Gtk.show_uri (window,
- fallback_to_https (url.value),
- Gdk.CURRENT_TIME);
- } catch (Error e) {
- var message = "Failed to open url '%s'".printf(url.value);
-
- // Notify the user
- var notification = new InAppNotification (message);
- notification.show ();
- window.add_notification (notification);
-
- // Print details on stdout
- debug (message + ": " + e.message);
- }
+ // FIXME: use show_uri_full so we can show errors
+ Gtk.show_uri (window,
+ fallback_to_https (url.value),
+ Gdk.CURRENT_TIME);
});
this.attach_row (row);
diff --git a/src/contacts-delete-operation.vala b/src/contacts-delete-operation.vala
new file mode 100644
index 00000000..5c379dc1
--- /dev/null
+++ b/src/contacts-delete-operation.vala
@@ -0,0 +1,55 @@
+/*
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.DeleteOperation : Object, Operation {
+
+ private Gee.List<Individual> individuals;
+
+ // We don't support reversing a removal. What we do instead, is put a timeout
+ // before actually executing this operation so the user has time to change
+ // their mind.
+ public bool reversable { get { return false; } }
+
+ private string _description;
+ public string description { owned get { return this._description; } }
+
+ public DeleteOperation (Gee.List<Individual> individuals) {
+ this.individuals = individuals;
+ this._description = ngettext ("Deleting %d contact",
+ "Deleting %d contacts", individuals.size)
+ .printf (individuals.size);
+ }
+
+ /**
+ * Link individuals
+ */
+ public async void execute () throws GLib.Error {
+ foreach (var indiv in this.individuals) {
+ foreach (var persona in indiv.personas) {
+ // TODO: make sure it is actually removed
+ yield persona.store.remove_persona (persona);
+ }
+ }
+ }
+
+ // See comments near the reversable property
+ protected async void _undo () throws GLib.Error {
+ throw new GLib.IOError.NOT_SUPPORTED("Undoing not supported");
+ }
+}
diff --git a/src/contacts-editor-property.vala b/src/contacts-editor-property.vala
index f486358d..88a8ded9 100644
--- a/src/contacts-editor-property.vala
+++ b/src/contacts-editor-property.vala
@@ -212,7 +212,7 @@ public class Contacts.EditorPropertyRow : Adw.Bin {
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 ("boxed-list");
this.listbox.add_css_class ("contacts-editor-property");
this.revealer.set_child (listbox);
}
@@ -250,7 +250,7 @@ public class Contacts.EditorPropertyRow : Adw.Bin {
/**
* Setter for the main widget, which can be used to actually edit the property
*/
- public void set_main_widget (Gtk.Widget widget) {
+ public void set_main_widget (Gtk.Widget widget, bool add_icon = true) {
var row = new Gtk.ListBoxRow ();
row.focusable = false;
@@ -259,12 +259,14 @@ public class Contacts.EditorPropertyRow : Adw.Bin {
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);
+ if (add_icon) {
+ 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);
+ }
}
// Set the actual widget
@@ -299,7 +301,13 @@ public class Contacts.EditorPropertyRow : Adw.Bin {
entry.placeholder_text = placeholder;
entry.add_css_class ("flat");
entry.add_css_class ("contacts-editor-main-entry");
- this.set_main_widget (entry);
+ // Set the icon as part of the GtkEntry, to avoid it being outside of the margin
+ unowned var icon_name = Utils.get_icon_name_for_property (this.ptype);
+ if (icon_name != null) {
+ entry.primary_icon_name = icon_name;
+ entry.primary_icon_tooltip_text = Utils.get_display_name_for_property (this.ptype);
+ }
+ this.set_main_widget (entry, false);
this.is_empty = (text == "");
entry.changed.connect (() => {
diff --git a/src/contacts-link-operation.vala b/src/contacts-link-operation.vala
new file mode 100644
index 00000000..9e75841d
--- /dev/null
+++ b/src/contacts-link-operation.vala
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 Alexander Larsson <alexl redhat 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.LinkOperation : Object, Operation {
+
+ private weak Store store;
+
+ private Gee.LinkedList<Individual> individuals;
+ private Gee.HashSet<Gee.HashSet<Persona>> personas_to_link
+ = new Gee.HashSet<Gee.HashSet<Persona>> ();
+
+ private bool finished { get; set; default = false; }
+
+ private bool _reversable = false;
+ public bool reversable { get { return this._reversable; } }
+
+ private string _description;
+ public string description { owned get { return this._description; } }
+
+ public LinkOperation (Store store, Gee.LinkedList<Individual> individuals) {
+ this.store = store;
+ this.individuals = individuals;
+
+ this._description = ngettext ("Linked %d contact",
+ "Linked %d contacts", individuals.size)
+ .printf (individuals.size);
+ }
+
+ /**
+ * Link individuals
+ */
+ public async void execute () throws GLib.Error {
+ var personas_to_link = new Gee.HashSet<Persona> ();
+ foreach (var i in individuals) {
+ var saved_personas = new Gee.HashSet<Persona> ();
+ foreach (var persona in i.personas) {
+ personas_to_link.add (persona);
+ saved_personas.add (persona);
+ }
+ this.personas_to_link.add (saved_personas);
+ }
+
+ yield this.store.aggregator.link_personas (personas_to_link);
+ this._reversable = true;
+ notify_property ("reversable");
+ }
+
+ /**
+ * Undoing means unlinking
+ */
+ public async void _undo () throws GLib.Error {
+ var individual = this.personas_to_link.first_match(() => {return true;})
+ .first_match(() => {return true;}).individual;
+
+ yield store.aggregator.unlink_individual (individual);
+
+ foreach (var personas in personas_to_link) {
+ yield this.store.aggregator.link_personas (personas);
+ }
+ this._reversable = false;
+ notify_property ("reversable");
+ }
+}
diff --git a/src/contacts-main-window.vala b/src/contacts-main-window.vala
index 62152a90..81e4a551 100644
--- a/src/contacts-main-window.vala
+++ b/src/contacts-main-window.vala
@@ -27,6 +27,7 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
{ "unlink-contact", unlink_contact },
{ "delete-contact", delete_contact },
{ "sort-on", null, "s", "'surname'", sort_on_changed },
+ { "undo-operation", undo_operation_action },
};
[GtkChild]
@@ -42,11 +43,9 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
[GtkChild]
private unowned Adw.HeaderBar left_header;
[GtkChild]
- private unowned Gtk.Separator header_separator;
- [GtkChild]
private unowned Adw.HeaderBar right_header;
[GtkChild]
- private unowned Gtk.Overlay notification_overlay;
+ private unowned Adw.ToastOverlay toast_overlay;
[GtkChild]
private unowned Gtk.Button select_cancel_button;
[GtkChild]
@@ -84,6 +83,9 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
get; construct set;
}
+ // If an unduable operation was recently performed, this will be set
+ public Operation? last_operation = null;
+
construct {
this.actions.add_action_entries (ACTION_ENTRIES, this);
this.insert_action_group ("window", this.actions);
@@ -189,7 +191,6 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
= this.state.editing ();
this.right_header.show_end_title_buttons = !this.state.editing ();
if (this.state.editing ()) {
- this.done_button.use_underline = true;
this.done_button.label = (this.state == UiState.CREATING)? _("_Add") : _("Done");
// Cast is required because Gtk.Button.set_focus_on_click is deprecated and
// we have to use Gtk.Widget.set_focus_on_click instead
@@ -238,27 +239,27 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
}
private void unlink_contact () {
- var individual = this.contact_pane.individual;
+ unowned var individual = this.contact_pane.individual;
if (individual == null)
return;
set_shown_contact (null);
this.state = UiState.NORMAL;
- var operation = new UnLinkOperation (this.store);
- operation.execute.begin (individual);
-
- var b = new Gtk.Button.with_mnemonic (_("_Undo"));
- var notification = new InAppNotification (_("Contacts unlinked"), b);
+ this.last_operation = new UnlinkOperation (this.store, individual);
+ this.last_operation.execute.begin ((obj, res) => {
+ try {
+ this.last_operation.execute.end (res);
+ } catch (GLib.Error e) {
+ warning ("Error unlinking individuals: %s", e.message);
+ }
+ });
- /* signal handlers */
- b.clicked.connect ( () => {
- /* here, we will link the thing in question */
- operation.undo.begin ();
- notification.dismiss ();
- });
+ var toast = new Adw.Toast (this.last_operation.description);
+ toast.set_button_label (_("_Undo"));
+ toast.action_name = "win.undo-operation";
- add_notification (notification);
+ this.toast_overlay.add_toast (toast);
}
private void delete_contact () {
@@ -276,6 +277,23 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
action.set_state (new_state);
}
+ private void undo_operation_action (SimpleAction action, GLib.Variant? parameter) {
+ if (this.last_operation == null) {
+ warning ("Undo action was called without anything that can be undone?");
+ return;
+ }
+
+ debug ("Undoing operation '%s'", this.last_operation.description);
+ this.last_operation.undo.begin ((obj, res) => {
+ try {
+ this.last_operation.undo.end (res);
+ } catch (GLib.Error e) {
+ warning ("Couldn't undo operation '%s': %s", this.last_operation.description, e.message);
+ }
+ debug ("Finished undoing operation '%s'", this.last_operation.description);
+ });
+ }
+
private void stop_editing (bool cancel = false) {
if (this.state == UiState.CREATING) {
if (cancel) {
@@ -292,11 +310,6 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
update_header_titles (null, "");
}
- public void add_notification (InAppNotification notification) {
- this.notification_overlay.add_overlay (notification);
- notification.reveal ();
- }
-
public void set_shown_contact (Individual? i) {
/* FIXME: ask the user to leave edit-mode and act accordingly */
if (this.contact_pane.on_edit_mode)
@@ -448,7 +461,7 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
return Gdk.EVENT_PROPAGATE;
}
- void list_pane_selection_changed_cb (Individual? new_selection) {
+ private void list_pane_selection_changed_cb (Individual? new_selection) {
set_shown_contact (new_selection);
if (this.state != UiState.SELECTING)
this.state = UiState.SHOWING;
@@ -457,95 +470,67 @@ public class Contacts.MainWindow : Adw.ApplicationWindow {
show_contact_pane ();
}
- void list_pane_link_contacts_cb (Gee.LinkedList<Individual> contact_list) {
+ private void list_pane_link_contacts_cb (Gee.LinkedList<Individual> contact_list) {
set_shown_contact (null);
this.state = UiState.NORMAL;
- var operation = new LinkOperation (this.store);
- operation.execute.begin (contact_list);
-
- string msg = ngettext ("%d contacts linked",
- "%d contacts linked",
- contact_list.size).printf (contact_list.size);
-
- var b = new Gtk.Button.with_mnemonic (_("_Undo"));
- var notification = new InAppNotification (msg, b);
-
- /* signal handlers */
- b.clicked.connect ( () => {
- /* here, we will unlink the thing in question */
- operation.undo.begin ();
- notification.dismiss ();
- });
+ this.last_operation = new LinkOperation (this.store, contact_list);
+ this.last_operation.execute.begin ((obj, res) => {
+ try {
+ this.last_operation.execute.end (res);
+ } catch (GLib.Error e) {
+ warning ("Error linking individuals: %s", e.message);
+ }
+ });
- add_notification (notification);
+ var toast = new Adw.Toast (this.last_operation.description);
+ toast.set_button_label (_("_Undo"));
+ toast.action_name = "win.undo-operation";
+ this.toast_overlay.add_toast (toast);
}
private void delete_contacts (Gee.List<Individual> individuals) {
set_shown_contact (null);
this.state = UiState.NORMAL;
- string msg;
- if (individuals.size == 1)
- msg = _("Deleted contact %s").printf (individuals[0].display_name);
- else
- msg = ngettext ("%d contact deleted", "%d contacts deleted", individuals.size)
- .printf (individuals.size);
+ this.last_operation = new DeleteOperation (individuals);
+ var toast = new Adw.Toast (this.last_operation.description);
+ toast.set_button_label (_("_Undo"));
+ toast.action_name = "win.undo-operation";
- var b = new Gtk.Button.with_mnemonic (_("_Undo"));
+ // signal handlers
+ bool really_delete = true;
+ // XXX
+ // b.clicked.connect (() => {
+ // really_delete = false;
+ // toast.dismiss ();
+ // });
+ toast.dismissed.connect (() => {
+ if (really_delete) {
+ this.last_operation.execute.begin ((obj, res) => {
+ try {
+ this.last_operation.execute.end (res);
+ } catch (Error e) {
+ debug ("Coudln't remove persona: %s", e.message);
+ }
+ });
+ } else {
+ /* Reset the contact list */
+ this.list_pane.undo_deletion ();
- var notification = new InAppNotification (msg, b);
+ set_shown_contact (individuals[0]);
+ this.state = UiState.SHOWING;
+ }
+ });
- // Don't wrap (default), but ellipsize
- notification.message_label.wrap = false;
- notification.message_label.max_width_chars = 45;
- notification.message_label.ellipsize = Pango.EllipsizeMode.END;
+ this.toast_overlay.add_toast (toast);
+ }
- // signal handlers
- bool really_delete = true;
- b.clicked.connect ( () => {
- really_delete = false;
- notification.dismiss ();
-
- /* Reset the contact list */
- list_pane.undo_deletion ();
-
- set_shown_contact (individuals[0]);
- this.state = UiState.SHOWING;
- });
- notification.dismissed.connect ( () => {
- if (really_delete)
- foreach (var i in individuals)
- foreach (var p in i.personas) {
- // TODO: make sure it is acctally removed
- p.store.remove_persona.begin (p, (obj, res) => {
- try {
- p.store.remove_persona.end (res);
- } catch (Error e) {
- debug ("Coudln't remove persona: %s", e.message);
- }
- });
- }
- });
-
- add_notification (notification);
- }
-
- void contact_pane_contacts_linked_cb (string? main_contact, string linked_contact, LinkOperation
operation) {
- string msg;
- if (main_contact != null)
- msg = _("%s linked to %s").printf (main_contact, linked_contact);
- else
- msg = _("%s linked to the contact").printf (linked_contact);
-
- var b = new Gtk.Button.with_mnemonic (_("_Undo"));
- var notification = new InAppNotification (msg, b);
-
- b.clicked.connect ( () => {
- notification.dismiss ();
- operation.undo.begin ();
- });
-
- add_notification (notification);
+ private void contact_pane_contacts_linked_cb (string? main_contact, string linked_contact, LinkOperation
operation) {
+ this.last_operation = operation;
+ var toast = new Adw.Toast (this.last_operation.description);
+ toast.set_button_label (_("_Undo"));
+ toast.action_name = "win.undo-operation";
+ this.toast_overlay.add_toast (toast);
}
}
diff --git a/src/contacts-operation.vala b/src/contacts-operation.vala
new file mode 100644
index 00000000..f77944b4
--- /dev/null
+++ b/src/contacts-operation.vala
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 Niels De Graef <nielsdegraef redhat 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Contacts.Operation is a simple interface to describe actions that can be
+ * executed and possibly undone later on (for example, using a button on an
+ * in-app notification).
+ *
+ * Since some operations might not be able undoable later onwards, there is a
+ * property `reversable` that you should check first before calling undo().
+ */
+public interface Contacts.Operation : Object {
+
+ /**
+ * Whether undo() can be called on this object
+ */
+ public abstract bool reversable { get; }
+
+ /**
+ * A user-facing string that tells us what the operation does
+ */
+ public abstract string description { owned get; }
+
+ /**
+ * This the actual implementation of the operation that a subclass needs to
+ * implement.
+ */
+ public abstract async void execute () throws GLib.Error;
+
+ /**
+ * The is the public API undo. If you want, you can override it still, e.g.
+ * to provide better warnings.
+ */
+ public virtual async void undo () throws GLib.Error {
+ // FIXME: should throw an error instead so we can show something to the user
+ if (!this.reversable) {
+ warning ("Can't undo '%s'", this.description);
+ return;
+ }
+
+ yield this._undo ();
+ }
+
+ /**
+ * This the actual implementation of the undo that a subclass needs to
+ * implement.
+ */
+ protected abstract async void _undo () throws GLib.Error;
+}
diff --git a/src/contacts-unlink-operation.vala b/src/contacts-unlink-operation.vala
new file mode 100644
index 00000000..7b67679b
--- /dev/null
+++ b/src/contacts-unlink-operation.vala
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 Alexander Larsson <alexl redhat 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Folks;
+
+public class Contacts.UnlinkOperation : Object, Operation {
+
+ private weak Store store;
+
+ private Individual individual;
+
+ private Gee.HashSet<Persona> personas = new Gee.HashSet<Persona> ();
+
+ private bool _reversable = false;
+ public bool reversable { get { return this._reversable; } }
+
+ private string _description;
+ public string description { owned get { return this._description; } }
+
+ public UnlinkOperation (Store store, Individual main) {
+ this.store = store;
+ this.individual = main;
+ this._description = _("Unlinking contacts");
+ }
+
+ /* Remove a personas from individual */
+ public async void execute () throws GLib.Error {
+ foreach (var persona in this.individual.personas)
+ this.personas.add (persona);
+
+ yield store.aggregator.unlink_individual (this.individual);
+ this._reversable = true;
+ notify_property ("reversable");
+ }
+
+ /* Undo the unlinking */
+ public async void _undo () throws GLib.Error {
+ yield this.store.aggregator.link_personas (personas);
+ this._reversable = false;
+ notify_property ("reversable");
+ }
+}
diff --git a/src/contacts-utils.vala b/src/contacts-utils.vala
index 4627acee..fd7d72e7 100644
--- a/src/contacts-utils.vala
+++ b/src/contacts-utils.vala
@@ -65,13 +65,10 @@ namespace Contacts {
}
namespace Contacts.Utils {
+
public void compose_mail (string email) {
var mailto_uri = "mailto:" + Uri.escape_string (email, "@" , false);
- try {
- Gtk.show_uri (null, mailto_uri, 0);
- } catch (Error e) {
- debug ("Couldn't launch URI \"%s\": %s", mailto_uri, e.message);
- }
+ Gtk.show_uri (null, mailto_uri, 0);
}
#if HAVE_TELEPATHY
diff --git a/src/meson.build b/src/meson.build
index 733c9822..f801c70f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,12 +6,16 @@ install_data('org.gnome.Contacts.gschema.xml',
# Common library
libcontacts_sources = files(
+ 'contacts-delete-operation.vala',
'contacts-esd-setup.vala',
'contacts-fake-persona-store.vala',
'contacts-im-service.vala',
+ 'contacts-link-operation.vala',
+ 'contacts-operation.vala',
'contacts-store.vala',
'contacts-typeset.vala',
'contacts-type-descriptor.vala',
+ 'contacts-unlink-operation.vala',
'contacts-utils.vala',
'contacts-vcard-type-mapping.vala',
'xdg-portal-camera.vala',
@@ -81,7 +85,6 @@ contacts_vala_sources = files(
'contacts-in-app-notification.vala',
'contacts-link-suggestion-grid.vala',
'contacts-linked-personas-dialog.vala',
- 'contacts-linking.vala',
'contacts-list-pane.vala',
'contacts-main-window.vala',
'contacts-settings.vala',
diff --git a/todo-gtk4.md b/todo-gtk4.md
new file mode 100644
index 00000000..71937a39
--- /dev/null
+++ b/todo-gtk4.md
@@ -0,0 +1,7 @@
+* go through all XML files and make sure `<requires lib="gtk" version="4.0"></requires>`
+* go through all XML files and try to remove `visible=true`
+* go through all code files and remove unnecessary gtk_widget_show()
+* All XXX remarks which you haven't solved yet
+* Check if some things can be replaced by libadwaita stuff (`InAppNotification`
+ → Toast)
+* Finally: maybe we can use this to just go for uncrustify?
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]