From 91085126c2dd960d25cb31916048f3f765001f76 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 12 Apr 2023 16:10:53 +0200 Subject: [PATCH 01/72] Update dependencies --- meson.build | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/meson.build b/meson.build index 93fe3a47c..155f3fd92 100644 --- a/meson.build +++ b/meson.build @@ -11,20 +11,15 @@ add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi glib_dep = dependency('glib-2.0') gobject_dep = dependency('gobject-2.0') -granite_dep = dependency('granite', version: '>= 6.0.0') +granite_dep = dependency('granite-7') gee_dep = dependency('gee-0.8') -handy_dep = dependency('libhandy-1', version: '>=1.1.90') +adw_dep = dependency('libadwaita-1') +gtk_dep = dependency('gtk4') camel_dep = dependency('camel-1.2', version: '>= 3.28') -libedataserver_dep = dependency('libedataserver-1.2', version: '>= 3.28') -libedataserverui_dep = dependency('libedataserverui-1.2', version: '>= 3.28') -if libedataserverui_dep.version().version_compare('>=3.45.1') - add_project_arguments('--define=HAS_SOUP_3', language: 'vala') - webkit2_dep = dependency('webkit2gtk-4.1') - webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-4.1') -else - webkit2_dep = dependency('webkit2gtk-4.0', version: '>=2.28') - webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-4.0', version: '>=2.28') -endif +libedataserver_dep = dependency('libedataserver-1.2', version: '>=3.45.1') +libedataserverui_dep = dependency('libedataserverui4-1.0', version: '>= 3.45.1') +webkit2_dep = dependency('webkit2gtk-5.0') +webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-5.0') folks_dep = dependency('folks') m_dep = meson.get_compiler('c').find_library('m') @@ -35,7 +30,8 @@ dependencies = [ gobject_dep, granite_dep, gee_dep, - handy_dep, + adw_dep, + gtk_dep, camel_dep, libedataserver_dep, libedataserverui_dep, @@ -59,7 +55,6 @@ extension_dependencies = [ add_global_arguments([ '-DGETTEXT_PACKAGE="@0@"'.format(meson.project_name()), - '-DHANDY_USE_UNSTABLE_API' ], language:'c' ) From 6c3e396e4b8e2c8b489deccff3da57b5ed5a9e49 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Fri, 14 Apr 2023 01:14:44 +0200 Subject: [PATCH 02/72] FolderList: Start work on SourceList replacement --- src/FolderList/AccountItemModel.vala | 89 ++++++++++++++++++ src/FolderList/FolderItemModel.vala | 86 ++++++++++++++++++ src/FolderList/FolderList.vala | 131 +++++++++++++++++++++++++++ src/FolderList/FolderListItem.vala | 21 +++++ 4 files changed, 327 insertions(+) create mode 100644 src/FolderList/AccountItemModel.vala create mode 100644 src/FolderList/FolderItemModel.vala create mode 100644 src/FolderList/FolderList.vala create mode 100644 src/FolderList/FolderListItem.vala diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala new file mode 100644 index 000000000..2e2d44b32 --- /dev/null +++ b/src/FolderList/AccountItemModel.vala @@ -0,0 +1,89 @@ +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- +/*- + * Copyright (c) 2017 elementary LLC. (https://elementary.io) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authored by: Corentin Noël + */ + +public class Mail.AccountItemModel : Object { + public Mail.Backend.Account account { get; construct; } + + public signal void loaded (); + + public string name; + public ListStore folder_list; + + private GLib.Cancellable connect_cancellable; + private unowned Camel.OfflineStore offlinestore; + + construct { + connect_cancellable = new GLib.Cancellable (); + folder_list = new ListStore (typeof(FolderItemModel)); + + offlinestore = (Camel.OfflineStore) account.service; + name = offlinestore.display_name; + + unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); + network_monitor.network_changed.connect (() =>{ + connect_to_account.begin (); + }); + load.begin (); + } + + ~AccountItemModel () { + connect_cancellable.cancel (); + } + + public async void load () { + try { + var folderinfo = yield offlinestore.get_folder_info (null, Camel.StoreGetFolderInfoFlags.RECURSIVE, GLib.Priority.DEFAULT, connect_cancellable); + if (folderinfo != null) { + show_info (folderinfo); + } + } catch (Error e) { + critical (e.message); + } + + connect_to_account.begin (); + } + + private async void connect_to_account () { + unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); + if (network_monitor.network_available == false) { + return; + } + + try { + yield offlinestore.set_online (true, GLib.Priority.DEFAULT, connect_cancellable); + yield offlinestore.connect (GLib.Priority.DEFAULT, connect_cancellable); + + yield offlinestore.synchronize (false, GLib.Priority.DEFAULT, connect_cancellable); + } catch (Error e) { + critical (e.message); + } + } + + private void show_info (Camel.FolderInfo? _folderinfo) { + var folderinfo = _folderinfo; + while (folderinfo != null) { + var folder_item = new FolderItemModel (folderinfo, account); + folder_list.append (folder_item); + folderinfo = (Camel.FolderInfo?) folderinfo.next; + } + } +} diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala new file mode 100644 index 000000000..11237a7f9 --- /dev/null +++ b/src/FolderList/FolderItemModel.vala @@ -0,0 +1,86 @@ +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- +/*- + * Copyright (c) 2017 elementary LLC. (https://elementary.io) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authored by: Corentin Noël + */ + +public class Mail.FolderItemModel : Object { + public string icon_name { get; construct; } + public string name { get; construct; } + public int unread { get; construct; } + + public Mail.Backend.Account account { get; construct; } + public string full_name { get; construct; } + public Camel.FolderInfo folder_info { get; construct; } + + public ListStore folder_list; + + public FolderItemModel (Camel.FolderInfo folderinfo, Mail.Backend.Account account) { + Object (account: account, + folder_info: folderinfo + ); + } + + construct { + name = folder_info.display_name; + full_name = folder_info.full_name; + unread = folder_info.unread; + + folder_list = new ListStore (typeof(FolderItemModel)); + + if (folder_info.child != null) { + var current_folder_info = folder_info.child; + while (current_folder_info != null) { + var folder_item = new FolderItemModel (current_folder_info, account); + folder_list.append (folder_item); + + current_folder_info = (Camel.FolderInfo?) current_folder_info.next; + } + } + + var full_folder_info_flags = Utils.get_full_folder_info_flags (account.service, folder_info); + switch (full_folder_info_flags & Camel.FOLDER_TYPE_MASK) { + case Camel.FolderInfoFlags.TYPE_INBOX: + icon_name = "mail-inbox"; + break; + case Camel.FolderInfoFlags.TYPE_OUTBOX: + icon_name = "mail-outbox"; + break; + case Camel.FolderInfoFlags.TYPE_TRASH: + icon_name = folder_info.total == 0 ? "user-trash" : "user-trash-full"; + break; + case Camel.FolderInfoFlags.TYPE_JUNK: + icon_name = "edit-flag"; + break; + case Camel.FolderInfoFlags.TYPE_SENT: + icon_name = "mail-sent"; + break; + case Camel.FolderInfoFlags.TYPE_ARCHIVE: + icon_name = "mail-archive"; + break; + case Camel.FolderInfoFlags.TYPE_DRAFTS: + icon_name = "mail-drafts"; + break; + default: + icon_name = "folder"; + break; + } + } +} + diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala new file mode 100644 index 000000000..314731759 --- /dev/null +++ b/src/FolderList/FolderList.vala @@ -0,0 +1,131 @@ +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- +/*- + * Copyright (c) 2017 elementary LLC. (https://elementary.io) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authored by: Corentin Noël + */ + +public class Mail.FoldersListView : Gtk.Box { + public signal void folder_selected (Gee.Map folder_full_name_per_account_uid); + + public Adw.HeaderBar header_bar { get; private set; } + + private Gtk.ListStore root_model; + private Gtk.TreeListModel tree_list; + private Gtk.SingleSelection selection_model; + private Gtk.ListView folder_list_view; + private static GLib.Settings settings; + + static construct { + settings = new GLib.Settings ("io.elementary.mail"); + } + + construct { + var application_instance = (Gtk.Application) GLib.Application.get_default (); + + var compose_button = new Gtk.Button.from_icon_name ("mail-message-new") { + action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_COMPOSE_MESSAGE, + halign = Gtk.Align.START + }; + compose_button.tooltip_markup = Granite.markup_accel_tooltip ( + application_instance.get_accels_for_action (compose_button.action_name), + _("Compose new message") + ); + + header_bar = new Gtk.HeaderBar (); + header_bar.pack_end (compose_button); + header_bar.add_css_class (Granite.STYLE_CLASS_FLAT); + + tree_list = new Gtk.TreeListModel (root_model, false, false, create_folder_list_func); + selection_model = new Gtk.SingleSelection (tree_list); + var list_factory = new Gtk.SignalListItemFactory (); + + folder_list_view = new Gtk.ListView (selection_model, list_factory); + + var scrolled_window = new Gtk.ScrolledWindow () { + child = folder_list_view, + vexpand = true + }; + + orientation = Gtk.Orientation.VERTICAL; + width_request = 100; + append (header_bar); + append (scrolled_window); + + list_factory.setup.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + + var tree_expander = new Gtk.TreeExpander () { + child = new FolderListItem (), + indent_for_icon = false + //indent_for_depth = false + }; + + list_item.child = tree_expander; + }); + + list_factory.bind.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + + var expander = (Gtk.TreeExpander) list_item.child; + expander.list_row = tree_list.get_row (list_item.get_position()); + + var item = list_item.item; + if (item is AccountItemModel) { + ((AccountItemModel)expander.child).bind_account (item); + } else if (item is FolderItemModel) { + ((FolderItemModel)expander.child).bind_folder (item); + } + }); + + var session = Mail.Backend.Session.get_default (); + + session.get_accounts ().foreach ((account) => { + add_account (account); + return true; + }); + + session.account_added.connect (add_account); + + selection_model.selection_changed.connect ((position) => { + var item = ((Gtk.TreeListRow)selection_model.get_selected_item()).get_item (); + + if (item is FolderItemModel) { + var folder_name_per_account_uid = new Gee.HashMap (); + folder_name_per_account_uid.set (item.account, item.full_name); + folder_selected (folder_name_per_account_uid.read_only_view); + + // settings.set ("selected-folder", "(ss)", folder_info.store.uid, folder_info.folder_info.full_name); + } + }); + } + + public ListModel? create_folder_list_func (Object item) { + if (item is AccountItemModel) { + return item.folder_list; + } else if (item is FolderItemModel) { + return item.folder_list; + } + return null; + } + + private void add_account (Mail.Backend.Account account) { + var account_item = new AccountItemModel (account); + root_model.append (account_item); + } +} diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala new file mode 100644 index 000000000..0f005bad3 --- /dev/null +++ b/src/FolderList/FolderListItem.vala @@ -0,0 +1,21 @@ +public class FolderListItem : Gtk.Box { + private Gtk.Image image; + private Gtk.Label label; + + construct { + orientation = HORIZONTAL; + + label = new Gtk.Label (""); + + append (label); + } + + public void bind_account (AccountItemModel item_model) { + label.label = item_model.name; + } + + public void bind_folder (FolderItemModel item_model) { + label.label = item_model.name; + } + +} From 93225d88f6b05d0873dedecad840bacd80034b90 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Fri, 14 Apr 2023 12:27:27 +0200 Subject: [PATCH 03/72] Working FolderList protoype --- src/FolderList/AccountItemModel.vala | 8 ++++++-- src/FolderList/FolderItemModel.vala | 2 +- src/FolderList/FolderList.vala | 22 +++++++++++----------- src/FolderList/FolderListItem.vala | 8 ++++++-- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 2e2d44b32..07450b1d6 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -21,16 +21,20 @@ */ public class Mail.AccountItemModel : Object { - public Mail.Backend.Account account { get; construct; } - public signal void loaded (); public string name; public ListStore folder_list; + public Mail.Backend.Account account { get; construct; } + private GLib.Cancellable connect_cancellable; private unowned Camel.OfflineStore offlinestore; + public AccountItemModel (Mail.Backend.Account account) { + Object (account: account); + } + construct { connect_cancellable = new GLib.Cancellable (); folder_list = new ListStore (typeof(FolderItemModel)); diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index 11237a7f9..6cdcf685b 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -39,8 +39,8 @@ public class Mail.FolderItemModel : Object { construct { name = folder_info.display_name; - full_name = folder_info.full_name; unread = folder_info.unread; + full_name = folder_info.full_name; folder_list = new ListStore (typeof(FolderItemModel)); diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 314731759..3ad13785f 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -20,15 +20,14 @@ * Authored by: Corentin Noël */ -public class Mail.FoldersListView : Gtk.Box { +public class Mail.FolderList : Gtk.Box { public signal void folder_selected (Gee.Map folder_full_name_per_account_uid); - public Adw.HeaderBar header_bar { get; private set; } + public Gtk.HeaderBar header_bar; - private Gtk.ListStore root_model; + private ListStore root_model; private Gtk.TreeListModel tree_list; private Gtk.SingleSelection selection_model; - private Gtk.ListView folder_list_view; private static GLib.Settings settings; static construct { @@ -51,18 +50,19 @@ public class Mail.FoldersListView : Gtk.Box { header_bar.pack_end (compose_button); header_bar.add_css_class (Granite.STYLE_CLASS_FLAT); + root_model = new ListStore (typeof(AccountItemModel)); tree_list = new Gtk.TreeListModel (root_model, false, false, create_folder_list_func); selection_model = new Gtk.SingleSelection (tree_list); var list_factory = new Gtk.SignalListItemFactory (); - folder_list_view = new Gtk.ListView (selection_model, list_factory); + var folder_list_view = new Gtk.ListView (selection_model, list_factory); var scrolled_window = new Gtk.ScrolledWindow () { child = folder_list_view, vexpand = true }; - orientation = Gtk.Orientation.VERTICAL; + orientation = VERTICAL; width_request = 100; append (header_bar); append (scrolled_window); @@ -72,8 +72,8 @@ public class Mail.FoldersListView : Gtk.Box { var tree_expander = new Gtk.TreeExpander () { child = new FolderListItem (), - indent_for_icon = false - //indent_for_depth = false + indent_for_icon = false, + // indent_for_depth = false }; list_item.child = tree_expander; @@ -85,11 +85,11 @@ public class Mail.FoldersListView : Gtk.Box { var expander = (Gtk.TreeExpander) list_item.child; expander.list_row = tree_list.get_row (list_item.get_position()); - var item = list_item.item; + var item = expander.item; if (item is AccountItemModel) { - ((AccountItemModel)expander.child).bind_account (item); + ((FolderListItem)expander.child).bind_account ((AccountItemModel)item); } else if (item is FolderItemModel) { - ((FolderItemModel)expander.child).bind_folder (item); + ((FolderListItem)expander.child).bind_folder ((FolderItemModel)item); } }); diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala index 0f005bad3..b41f3c3d6 100644 --- a/src/FolderList/FolderListItem.vala +++ b/src/FolderList/FolderListItem.vala @@ -5,16 +5,20 @@ public class FolderListItem : Gtk.Box { construct { orientation = HORIZONTAL; + image = new Gtk.Image (); label = new Gtk.Label (""); + append (image); append (label); } - public void bind_account (AccountItemModel item_model) { + public void bind_account (Mail.AccountItemModel item_model) { + image.set_from_icon_name (""); label.label = item_model.name; } - public void bind_folder (FolderItemModel item_model) { + public void bind_folder (Mail.FolderItemModel item_model) { + image.set_from_icon_name (item_model.icon_name); label.label = item_model.name; } From 45996e816eeb1bf9d6a098067a3aa56323453066 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sat, 15 Apr 2023 19:17:56 +0200 Subject: [PATCH 04/72] Session: Replace E.CredentialsPrompter --- src/Backend/Session.vala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Backend/Session.vala b/src/Backend/Session.vala index f4abf7444..839fc4bcf 100644 --- a/src/Backend/Session.vala +++ b/src/Backend/Session.vala @@ -126,9 +126,17 @@ public class Mail.Backend.Session : Camel.Session { /* We need a password, preferrably one cached in * the keyring or else by interactive user prompt. */ - var credentials_prompter = new E.CredentialsPrompter (registry); - credentials_prompter.set_auto_prompt (true); - return credentials_prompter.loop_prompt_sync (source, E.CredentialsPrompterPromptFlags.ALLOW_SOURCE_SAVE, (prompter, source, credentials, out out_authenticated, cancellable) => try_credentials_sync (prompter, source, credentials, out out_authenticated, cancellable, service, mechanism)); + try { + var credentials_provider = new E.SourceCredentialsProvider (registry); + E.NamedParameters out_credentials; + credentials_provider.lookup_sync (source, cancellable, out out_credentials); + bool out_authenticated; + try_credentials_sync (credentials_provider, source, out_credentials, out out_authenticated, cancellable, service, mechanism); + return out_authenticated; + } catch (Error e) { + critical (e.message); + return false; + } } else { return (result == Camel.AuthenticationResult.ACCEPTED); } @@ -174,7 +182,7 @@ public class Mail.Backend.Session : Camel.Session { return success; } - public bool try_credentials_sync (E.CredentialsPrompter prompter, E.Source source, E.NamedParameters credentials, out bool out_authenticated, GLib.Cancellable? cancellable, Camel.Service service, string? mechanism) throws GLib.Error { + public bool try_credentials_sync (E.SourceCredentialsProvider provider, E.Source source, E.NamedParameters credentials, out bool out_authenticated, GLib.Cancellable? cancellable, Camel.Service service, string? mechanism) throws GLib.Error { string credential_name = null; if (source.has_extension (E.SOURCE_EXTENSION_AUTHENTICATION)) { @@ -194,7 +202,7 @@ public class Mail.Backend.Session : Camel.Session { out_authenticated = (result == Camel.AuthenticationResult.ACCEPTED); if (out_authenticated) { - var credentials_source = prompter.get_provider ().ref_credentials_source (source); + var credentials_source = provider.ref_credentials_source (source); if (credentials_source != null) { credentials_source.invoke_authenticate_sync (credentials); From 53fda7426fccc47a0568a93dfee0a2bc35104bd1 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sat, 15 Apr 2023 19:25:29 +0200 Subject: [PATCH 05/72] Composer: Update style classes --- src/Composer.vala | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index dbbf464a3..26a7c52fd 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -5,7 +5,7 @@ * Authored by: David Hewitt */ -public class Mail.Composer : Hdy.ApplicationWindow { +public class Mail.Composer : Gtk.ApplicationWindow { public signal void finished (); private const string ACTION_GROUP_PREFIX = "win"; @@ -36,7 +36,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { private Gtk.Revealer cc_revealer; private Gtk.Revealer bcc_revealer; private Gtk.ToggleButton cc_button; - private Granite.Widgets.OverlayBar message_url_overlay; + private Granite.OverlayBar message_url_overlay; private Gtk.ComboBoxText from_combo; private Gtk.Entry subject_val; @@ -87,17 +87,17 @@ public class Mail.Composer : Hdy.ApplicationWindow { application.set_accels_for_action (Action.print_detailed_name (ACTION_PREFIX + ACTION_STRIKETHROUGH, ACTION_STRIKETHROUGH), {"percent"}); application.set_accels_for_action (Action.print_detailed_name (ACTION_PREFIX + ACTION_UNDERLINE, ACTION_UNDERLINE), {"U"}); - var headerbar = new Hdy.HeaderBar () { + var headerbar = new Gtk.HeaderBar () { has_subtitle = false, show_close_button = true }; - headerbar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); - headerbar.get_style_context ().add_class ("default-decoration"); + headerbar.add_css_class (Gtk.STYLE_CLASS_FLAT); + headerbar.add_css_class ("default-decoration"); var from_label = new Gtk.Label (_("From:")) { xalign = 1 }; - from_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + from_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); from_combo = new Gtk.ComboBoxText () { hexpand = true @@ -116,12 +116,12 @@ public class Mail.Composer : Hdy.ApplicationWindow { var to_label = new Gtk.Label (_("To:")) { xalign = 1 }; - to_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + to_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); var subject_label = new Gtk.Label (_("Subject:")) { xalign = 1 }; - subject_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + subject_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); to_val = new Gtk.Entry () { hexpand = true @@ -139,7 +139,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { var cc_label = new Gtk.Label (_("Cc:")) { xalign = 1 }; - cc_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + cc_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); cc_val = new Gtk.Entry () { hexpand = true @@ -157,7 +157,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { var bcc_label = new Gtk.Label (_("Bcc:")) { xalign = 1 }; - bcc_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + bcc_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); bcc_val = new Gtk.Entry () { hexpand = true @@ -245,7 +245,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { }; var formatting_buttons = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); - formatting_buttons.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED); + formatting_buttons.add_css_class (Gtk.STYLE_CLASS_LINKED); formatting_buttons.add (bold); formatting_buttons.add (italic); formatting_buttons.add (underline); @@ -282,7 +282,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { homogeneous = true, selection_mode = Gtk.SelectionMode.NONE }; - attachment_box.get_style_context ().add_class (Gtk.STYLE_CLASS_VIEW); + attachment_box.add_css_class (Gtk.STYLE_CLASS_VIEW); var discard = new Gtk.Button.from_icon_name ("edit-delete-symbolic", Gtk.IconSize.MENU) { action_name = ACTION_PREFIX + ACTION_DISCARD, @@ -313,13 +313,13 @@ public class Mail.Composer : Hdy.ApplicationWindow { application.get_accels_for_action (ACTION_PREFIX + ACTION_SEND) ) }; - send.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + send.add_css_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); var action_bar = new Gtk.ActionBar () { // Workaround styling issue margin_top = 1 }; - action_bar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + action_bar.add_css_class (Gtk.STYLE_CLASS_FLAT); action_bar.pack_start (discard); action_bar.pack_start (attach); action_bar.pack_end (send); @@ -726,7 +726,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { discard_dialog.add_button (_("Cancel"), Gtk.ResponseType.CANCEL); var discard_anyway = discard_dialog.add_button (_("Delete Draft"), Gtk.ResponseType.ACCEPT); - discard_anyway.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + discard_anyway.add_css_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); discard_dialog.present (); discard_dialog.response.connect ((response) => { @@ -759,7 +759,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { no_subject_dialog.add_button (_("Don't Send"), Gtk.ResponseType.CANCEL); var send_anyway = no_subject_dialog.add_button (_("Send Anyway"), Gtk.ResponseType.ACCEPT); - send_anyway.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + send_anyway.add_css_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); no_subject_dialog.present (); no_subject_dialog.response.connect ((response) => { @@ -926,7 +926,7 @@ public class Mail.Composer : Hdy.ApplicationWindow { }; var size_label = new Gtk.Label ("(%s)".printf (GLib.format_size (info.get_size ()))); - size_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + size_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); var remove_button = new Gtk.Button.from_icon_name ("process-stop-symbolic", Gtk.IconSize.SMALL_TOOLBAR); From d56b165930b3be7d5aeac1b3e920c1d0214d2a46 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sat, 15 Apr 2023 20:29:23 +0200 Subject: [PATCH 06/72] Composer: Update to gtk4 --- src/Composer.vala | 252 ++++++++++++++++++++++------------------------ 1 file changed, 121 insertions(+), 131 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index 26a7c52fd..57c15951b 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -11,7 +11,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { private const string ACTION_GROUP_PREFIX = "win"; private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; - private const string ACTION_ADD_ATTACHMENT= "add-attachment"; + private const string ACTION_ADD_ATTACHMENT= "append-attachment"; private const string ACTION_BOLD = "bold"; private const string ACTION_ITALIC = "italic"; private const string ACTION_UNDERLINE = "underline"; @@ -88,16 +88,15 @@ public class Mail.Composer : Gtk.ApplicationWindow { application.set_accels_for_action (Action.print_detailed_name (ACTION_PREFIX + ACTION_UNDERLINE, ACTION_UNDERLINE), {"U"}); var headerbar = new Gtk.HeaderBar () { - has_subtitle = false, - show_close_button = true + // has_title_buttons = true }; - headerbar.add_css_class (Gtk.STYLE_CLASS_FLAT); + headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); headerbar.add_css_class ("default-decoration"); var from_label = new Gtk.Label (_("From:")) { xalign = 1 }; - from_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + from_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); from_combo = new Gtk.ComboBoxText () { hexpand = true @@ -106,8 +105,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { var from_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { margin_bottom = 6 }; - from_box.add (from_label); - from_box.add (from_combo); + from_box.append (from_label); + from_box.append (from_combo); var from_revealer = new Gtk.Revealer () { child = from_box @@ -116,12 +115,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { var to_label = new Gtk.Label (_("To:")) { xalign = 1 }; - to_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + to_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var subject_label = new Gtk.Label (_("Subject:")) { xalign = 1 }; - subject_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + subject_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); to_val = new Gtk.Entry () { hexpand = true @@ -131,15 +130,15 @@ public class Mail.Composer : Gtk.ApplicationWindow { var bcc_button = new Gtk.ToggleButton.with_label (_("Bcc")); - var to_grid = new EntryGrid (); - to_grid.add (to_val); - to_grid.add (cc_button); - to_grid.add (bcc_button); + var to_box = new Gtk.Box (HORIZONTAL, 0); + to_box.append (to_val); + to_box.append (cc_button); + to_box.append (bcc_button); var cc_label = new Gtk.Label (_("Cc:")) { xalign = 1 }; - cc_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + cc_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); cc_val = new Gtk.Entry () { hexpand = true @@ -148,16 +147,17 @@ public class Mail.Composer : Gtk.ApplicationWindow { var cc_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { margin_top = 6 }; - cc_box.add (cc_label); - cc_box.add (cc_val); + cc_box.append (cc_label); + cc_box.append (cc_val); - cc_revealer = new Gtk.Revealer (); - cc_revealer.add (cc_box); + cc_revealer = new Gtk.Revealer () { + child = cc_box + }; var bcc_label = new Gtk.Label (_("Bcc:")) { xalign = 1 }; - bcc_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + bcc_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); bcc_val = new Gtk.Entry () { hexpand = true @@ -166,11 +166,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { var bcc_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { margin_top = 6 }; - bcc_box.add (bcc_label); - bcc_box.add (bcc_val); + bcc_box.append (bcc_label); + bcc_box.append (bcc_val); - bcc_revealer = new Gtk.Revealer (); - bcc_revealer.add (bcc_box); + bcc_revealer = new Gtk.Revealer () { + child = bcc_box + }; subject_val = new Gtk.Entry () { margin_top = 6 @@ -196,7 +197,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { }; recipient_grid.attach (from_revealer, 0, 0, 2); recipient_grid.attach (to_label, 0, 1); - recipient_grid.attach (to_grid, 1, 1); + recipient_grid.attach (to_box, 1, 1); recipient_grid.attach (cc_revealer, 0, 2, 2); recipient_grid.attach (bcc_revealer, 0, 3, 2); recipient_grid.attach (subject_label, 0, 4); @@ -205,21 +206,21 @@ public class Mail.Composer : Gtk.ApplicationWindow { var bold = new Gtk.ToggleButton () { action_name = ACTION_PREFIX + ACTION_BOLD, action_target = ACTION_BOLD, - image = new Gtk.Image.from_icon_name ("format-text-bold-symbolic", Gtk.IconSize.MENU), + icon_name = "format-text-bold-symbolic", tooltip_markup = Granite.markup_accel_tooltip ({"B"}, _("Bold")) }; var italic = new Gtk.ToggleButton () { action_name = ACTION_PREFIX + ACTION_ITALIC, action_target = ACTION_ITALIC, - image = new Gtk.Image.from_icon_name ("format-text-italic-symbolic", Gtk.IconSize.MENU), + icon_name = "format-text-italic-symbolic", tooltip_markup = Granite.markup_accel_tooltip ({"I"}, _("Italic")) }; var underline = new Gtk.ToggleButton () { action_name = ACTION_PREFIX + ACTION_UNDERLINE, action_target = ACTION_UNDERLINE, - image = new Gtk.Image.from_icon_name ("format-text-underline-symbolic", Gtk.IconSize.MENU), + icon_name = "format-text-underline-symbolic", tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (Action.print_detailed_name (ACTION_PREFIX + ACTION_UNDERLINE, ACTION_UNDERLINE)), _("Underline") @@ -229,14 +230,14 @@ public class Mail.Composer : Gtk.ApplicationWindow { var strikethrough = new Gtk.ToggleButton () { action_name = ACTION_PREFIX + ACTION_STRIKETHROUGH, action_target = ACTION_STRIKETHROUGH, - image = new Gtk.Image.from_icon_name ("format-text-strikethrough-symbolic", Gtk.IconSize.MENU), + icon_name = "format-text-strikethrough-symbolic", tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (Action.print_detailed_name (ACTION_PREFIX + ACTION_STRIKETHROUGH, ACTION_STRIKETHROUGH)), _("Strikethrough") ) }; - var clear_format = new Gtk.Button.from_icon_name ("format-text-clear-formatting-symbolic", Gtk.IconSize.MENU) { + var clear_format = new Gtk.Button.from_icon_name ("format-text-clear-formatting-symbolic") { action_name = ACTION_PREFIX + ACTION_REMOVE_FORMAT, tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (ACTION_PREFIX + ACTION_REMOVE_FORMAT), @@ -245,13 +246,13 @@ public class Mail.Composer : Gtk.ApplicationWindow { }; var formatting_buttons = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); - formatting_buttons.add_css_class (Gtk.STYLE_CLASS_LINKED); - formatting_buttons.add (bold); - formatting_buttons.add (italic); - formatting_buttons.add (underline); - formatting_buttons.add (strikethrough); + formatting_buttons.add_css_class (Granite.STYLE_CLASS_LINKED); + formatting_buttons.append (bold); + formatting_buttons.append (italic); + formatting_buttons.append (underline); + formatting_buttons.append (strikethrough); - var link = new Gtk.Button.from_icon_name ("insert-link-symbolic", Gtk.IconSize.MENU) { + var link = new Gtk.Button.from_icon_name ("insert-link-symbolic") { action_name = ACTION_PREFIX + ACTION_INSERT_LINK, tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (ACTION_PREFIX + ACTION_INSERT_LINK), @@ -263,9 +264,9 @@ public class Mail.Composer : Gtk.ApplicationWindow { margin_start = 6, margin_bottom = 6 }; - button_row.add (formatting_buttons); - button_row.add (clear_format ); - button_row.add (link); + button_row.append (formatting_buttons); + button_row.append (clear_format ); + button_row.append (link); web_view = new WebView (); try { @@ -282,9 +283,9 @@ public class Mail.Composer : Gtk.ApplicationWindow { homogeneous = true, selection_mode = Gtk.SelectionMode.NONE }; - attachment_box.add_css_class (Gtk.STYLE_CLASS_VIEW); + attachment_box.add_css_class (Granite.STYLE_CLASS_VIEW); - var discard = new Gtk.Button.from_icon_name ("edit-delete-symbolic", Gtk.IconSize.MENU) { + var discard = new Gtk.Button.from_icon_name ("edit-delete-symbolic") { action_name = ACTION_PREFIX + ACTION_DISCARD, tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (ACTION_PREFIX + ACTION_DISCARD), @@ -292,7 +293,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { ) }; - var attach = new Gtk.Button.from_icon_name ("mail-attachment-symbolic", Gtk.IconSize.MENU) { + var attach = new Gtk.Button.from_icon_name ("mail-attachment-symbolic") { action_name = ACTION_PREFIX + ACTION_ADD_ATTACHMENT, tooltip_markup = Granite.markup_accel_tooltip ( application.get_accels_for_action (ACTION_PREFIX + ACTION_ADD_ATTACHMENT), @@ -300,9 +301,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { ) }; - var send = new Gtk.Button.from_icon_name ("mail-send-symbolic", Gtk.IconSize.MENU) { + var send = new Gtk.Button.from_icon_name ("mail-send-symbolic") { action_name = ACTION_PREFIX + ACTION_SEND, - always_show_image = true, label = _("Send"), margin_top = 6, margin_end = 0, @@ -313,37 +313,39 @@ public class Mail.Composer : Gtk.ApplicationWindow { application.get_accels_for_action (ACTION_PREFIX + ACTION_SEND) ) }; - send.add_css_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + send.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); var action_bar = new Gtk.ActionBar () { // Workaround styling issue margin_top = 1 }; - action_bar.add_css_class (Gtk.STYLE_CLASS_FLAT); + action_bar.add_css_class (Granite.STYLE_CLASS_FLAT); action_bar.pack_start (discard); action_bar.pack_start (attach); action_bar.pack_end (send); - var view_overlay = new Gtk.Overlay (); - view_overlay.add (web_view); - message_url_overlay = new Granite.Widgets.OverlayBar (view_overlay); - message_url_overlay.no_show_all = true; + var view_overlay = new Gtk.Overlay () { + child = web_view + }; + message_url_overlay = new Granite.OverlayBar (view_overlay); + message_url_overlay.visible = false; var main_box = new Gtk.Box (VERTICAL, 0); - main_box.add (headerbar); - main_box.add (recipient_grid); - main_box.add (button_row); - main_box.add (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); - main_box.add (view_overlay); - main_box.add (attachment_box); - main_box.add (action_bar); - + // main_box.append (headerbar); + main_box.append (recipient_grid); + main_box.append (button_row); + main_box.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); + main_box.append (view_overlay); + main_box.append (attachment_box); + main_box.append (action_bar); + + titlebar = headerbar; default_height = 500; default_width = 680; title = _("New Message"); - add (main_box); + set_child (main_box); - delete_event.connect (() => { + close_request.connect (() => { save_draft.begin ((obj, res) => { if (!save_draft.end (res)) { finished (); @@ -362,7 +364,6 @@ public class Mail.Composer : Gtk.ApplicationWindow { from_revealer.reveal_child = from_combo.model.iter_n_children (null) > 1; bind_property ("has-recipients", send, "sensitive"); - bind_property ("title", headerbar, "title"); cc_button.clicked.connect (() => { cc_revealer.reveal_child = cc_button.active; @@ -399,17 +400,17 @@ public class Mail.Composer : Gtk.ApplicationWindow { has_recipients = to_val.text != ""; }); - to_val.get_style_context ().changed.connect (() => { - unowned Gtk.StyleContext to_grid_style_context = to_grid.get_style_context (); - var state = to_grid_style_context.get_state (); - if (to_val.has_focus) { - state |= Gtk.StateFlags.FOCUSED; - } else { - state ^= Gtk.StateFlags.FOCUSED; - } + // to_val.get_style_context ().changed.connect (() => { + // unowned Gtk.StyleContext to_grid_style_context = to_grid.get_style_context (); + // var state = to_grid_style_context.get_state (); + // if (to_val.has_focus) { + // state |= Gtk.StateFlags.FOCUSED; + // } else { + // state ^= Gtk.StateFlags.FOCUSED; + // } - to_grid_style_context.set_state (state); - }); + // to_grid_style_context.set_state (state); + // }); if (to != null) { to_val.text = to; @@ -422,11 +423,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { foreach (unowned string param in params) { var terms = param.split ("="); if (terms.length == 2) { -#if HAS_SOUP_3 result[terms[0].down ()] = (GLib.Uri.unescape_string (terms[1])); -#else - result[terms[0].down ()] = (Soup.URI.decode (terms[1])); -#endif } else { critical ("Invalid mailto URL"); } @@ -460,12 +457,15 @@ public class Mail.Composer : Gtk.ApplicationWindow { foreach (var path in result["attachment"]) { var file = path.has_prefix ("file://") ? File.new_for_uri (path) : File.new_for_path (path); - var attachment = new Attachment (file); - attachment.margin = 3; + var attachment = new Attachment (file) { + margin_top = 3, + margin_bottom = 3, + margin_start = 3, + margin_end = 3 + }; - attachment_box.add (attachment); + attachment_box.append (attachment); } - attachment_box.show_all (); } } } @@ -487,24 +487,23 @@ public class Mail.Composer : Gtk.ApplicationWindow { private void on_add_attachment () { var filechooser = new Gtk.FileChooserNative ( _("Choose a file"), - (Gtk.Window) get_toplevel (), + this, Gtk.FileChooserAction.OPEN, _("Attach"), _("Cancel") ); - if (filechooser.run () == Gtk.ResponseType.ACCEPT) { - filechooser.hide (); - foreach (unowned File file in filechooser.get_files ()) { - var attachment = new Attachment (file); - attachment.margin = 3; + // if (filechooser.run () == Gtk.ResponseType.ACCEPT) { + // filechooser.hide (); + // foreach (unowned File file in filechooser.get_files ()) { + // var attachment = new Attachment (file); + // attachment.margin = 3; - attachment_box.add (attachment); - } - attachment_box.show_all (); - } + // attachment_box.append (attachment); + // } + // } - filechooser.destroy (); + // filechooser.destroy (); } private void on_insert_link_clicked () { @@ -512,12 +511,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { } private async void ask_insert_link () { - var selected_text = yield web_view.get_selected_text (); - var insert_link_dialog = new InsertLinkDialog (selected_text) { - transient_for = (Gtk.Window) get_toplevel () - }; - insert_link_dialog.present (); - insert_link_dialog.insert_link.connect ((url, title) => on_link_inserted (url, title, selected_text)); + // var selected_text = yield web_view.get_selected_text (); + // var insert_link_dialog = new InsertLinkDialog (selected_text) { + // transient_for = (Gtk.Window) get_toplevel () + // }; + // insert_link_dialog.present (); + // insert_link_dialog.insert_link.connect ((url, title) => on_link_inserted (url, title, selected_text)); } private void on_link_inserted (string url, string title, string? selected_text) { @@ -535,18 +534,13 @@ public class Mail.Composer : Gtk.ApplicationWindow { private void on_mouse_target_changed (WebKit.WebView web_view, WebKit.HitTestResult hit_test, uint mods) { if (hit_test.context_is_link ()) { var url = hit_test.get_link_uri (); -#if HAS_SOUP_3 var hover_url = url != null ? GLib.Uri.unescape_string (url) : null; -#else - var hover_url = url != null ? Soup.URI.decode (url) : null; -#endif if (hover_url == null) { message_url_overlay.hide (); } else { message_url_overlay.label = hover_url; - message_url_overlay.no_show_all = false; - message_url_overlay.show_all (); + message_url_overlay.show (); } } else { message_url_overlay.hide (); @@ -720,13 +714,13 @@ public class Mail.Composer : Gtk.ApplicationWindow { Gtk.ButtonsType.NONE ) { badge_icon = new ThemedIcon ("edit-delete"), - transient_for = get_toplevel () as Gtk.Window + transient_for = this }; discard_dialog.add_button (_("Cancel"), Gtk.ResponseType.CANCEL); var discard_anyway = discard_dialog.add_button (_("Delete Draft"), Gtk.ResponseType.ACCEPT); - discard_anyway.add_css_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + discard_anyway.add_css_class (Granite.STYLE_CLASS_DESTRUCTIVE_ACTION); discard_dialog.present (); discard_dialog.response.connect ((response) => { @@ -754,12 +748,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { "mail-send", Gtk.ButtonsType.NONE ); - no_subject_dialog.transient_for = get_toplevel () as Gtk.Window; + no_subject_dialog.transient_for = this; no_subject_dialog.add_button (_("Don't Send"), Gtk.ResponseType.CANCEL); var send_anyway = no_subject_dialog.add_button (_("Send Anyway"), Gtk.ResponseType.ACCEPT); - send_anyway.add_css_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + send_anyway.add_css_class (Granite.STYLE_CLASS_DESTRUCTIVE_ACTION); no_subject_dialog.present (); no_subject_dialog.response.connect ((response) => { @@ -864,15 +858,16 @@ public class Mail.Composer : Gtk.ApplicationWindow { body.set_boundary (null); body.add_part (part); - if (attachment_box.get_children ().length () > 0) { - foreach (unowned Gtk.Widget attachment in attachment_box.get_children ()) { - if (!(attachment is Attachment)) { - continue; - } - - unowned var attachment_obj = (Attachment)attachment; - body.add_part (attachment_obj.get_mime_part ()); + Gtk.Widget current_attachment = attachment_box.get_first_child (); + while (current_attachment != null) { + if (!(current_attachment is Attachment)) { + current_attachment = current_attachment.get_next_sibling (); + continue; } + + unowned var attachment_obj = (Attachment)current_attachment; + body.add_part (attachment_obj.get_mime_part ()); + current_attachment = current_attachment.get_next_sibling (); } var message = new Camel.MimeMessage (); @@ -926,23 +921,24 @@ public class Mail.Composer : Gtk.ApplicationWindow { }; var size_label = new Gtk.Label ("(%s)".printf (GLib.format_size (info.get_size ()))); - size_label.add_css_class (Gtk.STYLE_CLASS_DIM_LABEL); + size_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); - var remove_button = new Gtk.Button.from_icon_name ("process-stop-symbolic", Gtk.IconSize.SMALL_TOOLBAR); - - unowned Gtk.StyleContext remove_button_context = remove_button.get_style_context (); - remove_button_context.add_class (Gtk.STYLE_CLASS_FLAT); - remove_button_context.add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + var remove_button = new Gtk.Button.from_icon_name ("process-stop-symbolic"); + remove_button.add_css_class (Granite.STYLE_CLASS_FLAT); + remove_button.add_css_class (Granite.STYLE_CLASS_DESTRUCTIVE_ACTION); var box = new Gtk.Box (HORIZONTAL, 3) { - margin = 3 + margin_top = 3, + margin_bottom = 3, + margin_start = 3, + margin_end = 3 }; - box.add (image); - box.add (name_label); - box.add (size_label); - box.add (remove_button); + box.append (image); + box.append (name_label); + box.append (size_label); + box.append (remove_button); - add (box); + set_child (box); remove_button.clicked.connect (() => { destroy (); @@ -996,12 +992,6 @@ public class Mail.Composer : Gtk.ApplicationWindow { } } - private class EntryGrid : Gtk.Grid { - static construct { - set_css_name (Gtk.STYLE_CLASS_ENTRY); - } - } - public async bool save_draft () { if (discard_draft || !web_view.body_html_changed) { return false; From 537ad42533c625b2ff63bd583bb21d7381ada034 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sat, 15 Apr 2023 20:47:10 +0200 Subject: [PATCH 07/72] MessageList: Update to gtk4 --- src/MessageList/MessageList.vala | 137 ++++++++++++++----------------- 1 file changed, 61 insertions(+), 76 deletions(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index f3479a80c..9227705c2 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -7,22 +7,24 @@ public class Mail.MessageList : Gtk.Box { public signal void hovering_over_link (string? label, string? uri); - public Hdy.HeaderBar headerbar { get; private set; } + public Gtk.HeaderBar headerbar { get; private set; } + public GenericArray uids { get; private set; default = new GenericArray (); } private Gtk.ListBox list_box; private Gtk.ScrolledWindow scrolled_window; private Gee.HashMap messages; construct { - get_style_context ().add_class (Gtk.STYLE_CLASS_BACKGROUND); + // add_css_class (Granite.STYLE_CLASS_BACKGROUND); var application_instance = (Gtk.Application) GLib.Application.get_default (); var load_images_menuitem = new Granite.SwitchModelButton (_("Always Show Remote Images")); - var account_settings_menuitem = new Gtk.ModelButton () { - text = _("Account Settings…") + var account_settings_menuitem = new Gtk.Button () { + label = _("Account Settings…") }; + account_settings_menuitem.add_css_class (Granite.STYLE_CLASS_MENUITEM); var app_menu_separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { margin_bottom = 3, @@ -33,21 +35,20 @@ public class Mail.MessageList : Gtk.Box { margin_bottom = 3, margin_top = 3 }; - app_menu_box.add (load_images_menuitem); - app_menu_box.add (app_menu_separator); - app_menu_box.add (account_settings_menuitem); - app_menu_box.show_all (); + app_menu_box.append (load_images_menuitem); + app_menu_box.append (app_menu_separator); + app_menu_box.append (account_settings_menuitem); - var app_menu_popover = new Gtk.Popover (null); - app_menu_popover.add (app_menu_box); + var app_menu_popover = new Gtk.Popover (); + app_menu_popover.set_child (app_menu_box); var app_menu = new Gtk.MenuButton () { - image = new Gtk.Image.from_icon_name ("open-menu", Gtk.IconSize.LARGE_TOOLBAR), + icon_name = "open-menu", popover = app_menu_popover, tooltip_text = _("Menu") }; - var reply_button = new Gtk.Button.from_icon_name ("mail-reply-sender", Gtk.IconSize.LARGE_TOOLBAR) { + var reply_button = new Gtk.Button.from_icon_name ("mail-reply-sender") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REPLY, action_target = "" }; @@ -56,7 +57,7 @@ public class Mail.MessageList : Gtk.Box { _("Reply") ); - var reply_all_button = new Gtk.Button.from_icon_name ("mail-reply-all", Gtk.IconSize.LARGE_TOOLBAR) { + var reply_all_button = new Gtk.Button.from_icon_name ("mail-reply-all") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REPLY_ALL, action_target = "" }; @@ -65,7 +66,7 @@ public class Mail.MessageList : Gtk.Box { _("Reply All") ); - var forward_button = new Gtk.Button.from_icon_name ("mail-forward", Gtk.IconSize.LARGE_TOOLBAR) { + var forward_button = new Gtk.Button.from_icon_name ("mail-forward") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_FORWARD, action_target = "" }; @@ -74,45 +75,32 @@ public class Mail.MessageList : Gtk.Box { _("Forward") ); - var mark_unread_item = new Gtk.MenuItem () { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD - }; + var mark_unread_item = new MenuItem (_("Mark as Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); mark_unread_item.bind_property ("sensitive", mark_unread_item, "visible"); - mark_unread_item.add (new Granite.AccelLabel.from_action_name (_("Mark as Unread"), mark_unread_item.action_name)); - var mark_read_item = new Gtk.MenuItem () { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ - }; + var mark_read_item = new MenuItem (_("Mark as Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); mark_read_item.bind_property ("sensitive", mark_read_item, "visible"); - mark_read_item.add (new Granite.AccelLabel.from_action_name (_("Mark as Read"), mark_read_item.action_name)); - var mark_star_item = new Gtk.MenuItem () { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR - }; + var mark_star_item = new MenuItem (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); mark_star_item.bind_property ("sensitive", mark_star_item, "visible"); - mark_star_item.add (new Granite.AccelLabel.from_action_name (_("Star"), mark_star_item.action_name)); - var mark_unstar_item = new Gtk.MenuItem () { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR - }; + var mark_unstar_item = new MenuItem (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); mark_unstar_item.bind_property ("sensitive", mark_unstar_item, "visible"); - mark_unstar_item.add (new Granite.AccelLabel.from_action_name (_("Unstar"), mark_unstar_item.action_name)); - var mark_menu = new Gtk.Menu (); - mark_menu.add (mark_unread_item); - mark_menu.add (mark_read_item); - mark_menu.add (mark_star_item); - mark_menu.add (mark_unstar_item); - mark_menu.show_all (); + var mark_menu = new Menu (); + mark_menu.append_item (mark_unread_item); + mark_menu.append_item (mark_read_item); + mark_menu.append_item (mark_star_item); + mark_menu.append_item (mark_unstar_item); var mark_button = new Gtk.MenuButton () { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK, - image = new Gtk.Image.from_icon_name ("edit-mark", Gtk.IconSize.LARGE_TOOLBAR), - popup = mark_menu, + // action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK, + icon_name = "edit-mark", + menu_model = mark_menu, tooltip_text = _("Mark Conversation") }; - var archive_button = new Gtk.Button.from_icon_name ("mail-archive", Gtk.IconSize.LARGE_TOOLBAR) { + var archive_button = new Gtk.Button.from_icon_name ("mail-archive") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_ARCHIVE }; archive_button.tooltip_markup = Granite.markup_accel_tooltip ( @@ -120,7 +108,7 @@ public class Mail.MessageList : Gtk.Box { _("Move conversations to archive") ); - var trash_button = new Gtk.Button.from_icon_name ("edit-delete", Gtk.IconSize.LARGE_TOOLBAR) { + var trash_button = new Gtk.Button.from_icon_name ("edit-delete") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH }; trash_button.tooltip_markup = Granite.markup_accel_tooltip ( @@ -128,10 +116,10 @@ public class Mail.MessageList : Gtk.Box { _("Move conversations to Trash") ); - headerbar = new Hdy.HeaderBar () { - show_close_button = true + headerbar = new Gtk.HeaderBar () { + // show_close_button = true }; - headerbar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); headerbar.pack_start (reply_button); headerbar.pack_start (reply_all_button); headerbar.pack_start (forward_button); @@ -155,10 +143,8 @@ public class Mail.MessageList : Gtk.Box { var placeholder = new Gtk.Label (_("No Message Selected")) { visible = true }; - - var placeholder_style_context = placeholder.get_style_context (); - placeholder_style_context.add_class (Granite.STYLE_CLASS_H2_LABEL); - placeholder_style_context.add_class (Gtk.STYLE_CLASS_DIM_LABEL); + placeholder.add_css_class (Granite.STYLE_CLASS_H2_LABEL); + placeholder.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); list_box = new Gtk.ListBox () { hexpand = true, @@ -166,24 +152,24 @@ public class Mail.MessageList : Gtk.Box { selection_mode = NONE }; - list_box.get_style_context ().add_class (Gtk.STYLE_CLASS_BACKGROUND); + // list_box.add_css_class (Granite.STYLE_CLASS_BACKGROUND); list_box.set_placeholder (placeholder); list_box.set_sort_func (message_sort_function); - scrolled_window = new Gtk.ScrolledWindow (null, null) { + scrolled_window = new Gtk.ScrolledWindow () { hscrollbar_policy = NEVER }; - scrolled_window.add (list_box); + scrolled_window.set_child (list_box); // Prevent the focus of the webview causing the ScrolledWindow to scroll - var scrolled_child = scrolled_window.get_child (); - if (scrolled_child is Gtk.Container) { - ((Gtk.Container) scrolled_child).set_focus_vadjustment (new Gtk.Adjustment (0, 0, 0, 0, 0, 0)); - } + // var scrolled_child = scrolled_window.get_child (); + // if (scrolled_child is Gtk.Viewport) { + // ((Gtk.Viewport) scrolled_child).set_focus_vadjustment (new Gtk.Adjustment (0, 0, 0, 0, 0, 0)); + // } orientation = VERTICAL; - add (headerbar); - add (scrolled_window); + append (headerbar); + append (scrolled_window); } public void set_conversation (Camel.FolderThreadNode? node) { @@ -195,9 +181,12 @@ public class Mail.MessageList : Gtk.Box { can_reply (false); can_move_thread (false); - list_box.get_children ().foreach ((child) => { - child.destroy (); - }); + var current_child = list_box.get_row_at_index (0); + for (int i = 0; current_child != null; i++) { + list_box.remove (current_child); + current_child = list_box.get_row_at_index (i); + } + messages = new Gee.HashMap (null, null); if (node == null) { @@ -211,24 +200,20 @@ public class Mail.MessageList : Gtk.Box { can_move_thread (true); var item = new MessageListItem (node.message); - list_box.add (item); + list_box.append (item); messages.set (node.message.uid, item); if (node.child != null) { go_down ((Camel.FolderThreadNode?) node.child); } - var children = list_box.get_children (); - var num_children = children.length (); - if (num_children > 0) { - var child = list_box.get_row_at_index ((int) num_children - 1); - if (child != null && child is MessageListItem) { - var list_item = (MessageListItem) child; - list_item.expanded = true; + var child = list_box.get_last_child ().get_prev_sibling (); //The last child is the placeholder + if (child != null && child is MessageListItem) { + var list_item = (MessageListItem) child; + list_item.expanded = true; + can_reply (list_item.loaded); + list_item.notify["loaded"].connect (() => { can_reply (list_item.loaded); - list_item.notify["loaded"].connect (() => { - can_reply (list_item.loaded); - }); - } + }); } if (node.message != null && Camel.MessageFlags.DRAFT in (int) node.message.flags) { @@ -240,7 +225,7 @@ public class Mail.MessageList : Gtk.Box { unowned Camel.FolderThreadNode? current_node = node; while (current_node != null) { var item = new MessageListItem (current_node.message); - list_box.add (item); + list_box.append (item); messages.set (current_node.message.uid, item); if (current_node.next != null) { go_down ((Camel.FolderThreadNode?) current_node.next); @@ -252,7 +237,7 @@ public class Mail.MessageList : Gtk.Box { public async void compose (Composer.Type type, Variant uid) { /* Can't open a new composer if thread is empty*/ - var last_child = list_box.get_row_at_index ((int) list_box.get_children ().length () - 1); + var last_child = list_box.get_last_child ().get_prev_sibling (); //The last child is the placeholder if (last_child == null) { return; } @@ -272,8 +257,8 @@ public class Mail.MessageList : Gtk.Box { mime_message = message_item.mime_message; message_info = message_item.message_info; - var composer = new Composer.with_quote ((Gtk.Window)get_toplevel (), type, message_info, mime_message, content_to_quote); - composer.show_all (); + var composer = new Composer.with_quote ((Gtk.Window)get_root (), type, message_info, mime_message, content_to_quote); + composer.present (); composer.finished.connect (() => { can_reply (true); can_move_thread (true); From feb7099b3339e537dae9e7b28a64367bb789b294 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 00:13:41 +0200 Subject: [PATCH 08/72] MessageListItem: Update for gtk4 --- src/MessageList/MessageListItem.vala | 119 ++++++++++----------------- 1 file changed, 43 insertions(+), 76 deletions(-) diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index c944acef0..1bc7f34e0 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -29,8 +29,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { private Gtk.InfoBar blocked_images_infobar; private Gtk.Revealer secondary_revealer; private Gtk.Stack header_stack; - private Gtk.StyleContext style_context; - private Hdy.Avatar avatar; + private Adw.Avatar avatar; private Gtk.FlowBox attachment_bar = null; private string message_content; @@ -52,9 +51,9 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { get_message.begin (); message_loaded = true; } - style_context.remove_class ("collapsed"); + remove_css_class ("collapsed"); } else { - style_context.add_class ("collapsed"); + add_css_class ("collapsed"); } } } @@ -79,8 +78,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { construct { loading_cancellable = new GLib.Cancellable (); - style_context = get_style_context (); - style_context.add_class (Granite.STYLE_CLASS_CARD); + add_css_class (Granite.STYLE_CLASS_CARD); unowned string? parsed_address; unowned string? parsed_name; @@ -93,7 +91,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { parsed_name = parsed_address; } - avatar = new Hdy.Avatar (48, parsed_name, true) { + avatar = new Adw.Avatar (48, parsed_name, true) { valign = Gtk.Align.START }; @@ -101,19 +99,19 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { halign = END, valign = START }; - from_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + from_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var to_label = new Gtk.Label (_("To:")) { halign = END, valign = START }; - to_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + to_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var subject_label = new Gtk.Label (_("Subject:")) { halign = END, valign = START }; - subject_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + subject_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var from_val_label = new Gtk.Label (message_info.from) { wrap = true, @@ -147,7 +145,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { halign = END, valign = START }; - cc_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + cc_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var cc_val_label = new Gtk.Label (cc_info) { wrap = true, @@ -167,12 +165,12 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { small_fields_grid.attach (small_from_label, 0, 0, 1, 1); header_stack = new Gtk.Stack () { - homogeneous = false, + hhomogeneous = false, + vhomogeneous = false, transition_type = CROSSFADE }; header_stack.add_named (fields_grid, "large"); header_stack.add_named (small_fields_grid, "small"); - header_stack.show_all (); var relevant_timestamp = message_info.date_received; if (relevant_timestamp == 0) { @@ -185,10 +183,9 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { ///TRANSLATORS: The first %s represents the date and the second %s the time of the message (either when it was received or sent) var datetime_label = new Gtk.Label (new DateTime.from_unix_utc (relevant_timestamp).to_local ().format (_("%s at %s").printf (date_format, time_format))); - datetime_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + datetime_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var starred_icon = new Gtk.Image (); - starred_icon.icon_size = Gtk.IconSize.MENU; if (Camel.MessageFlags.FLAGGED in (int) message_info.flags) { starred_icon.icon_name = "starred-symbolic"; @@ -201,7 +198,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { var starred_button = new Gtk.Button () { child = starred_icon }; - starred_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + starred_button.add_css_class (Granite.STYLE_CLASS_FLAT); var upper_section = new Menu (); upper_section.append (_("Reply"), Action.print_detailed_name ( @@ -224,15 +221,14 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { actions_menu.append_section (null, lower_section); var actions_menu_button = new Gtk.MenuButton () { - image = new Gtk.Image.from_icon_name ("view-more-symbolic", Gtk.IconSize.MENU), + icon_name = "view-more-symbolic", tooltip_text = _("More"), margin_top = 6, valign = START, halign = END, - menu_model = actions_menu, - use_popover = false + menu_model = actions_menu }; - actions_menu_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + actions_menu_button.add_css_class (Granite.STYLE_CLASS_FLAT); var action_grid = new Gtk.Grid () { column_spacing = 3, @@ -251,15 +247,13 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { margin_end = 12, column_spacing = 12 }; + header.set_cursor_from_name ("pointer"); header.attach (avatar, 0, 0, 1, 3); header.attach (header_stack, 1, 0, 1, 3); header.attach (action_grid, 2, 0); - var header_event_box = new Gtk.EventBox (); - header_event_box.events |= Gdk.EventMask.ENTER_NOTIFY_MASK; - header_event_box.events |= Gdk.EventMask.LEAVE_NOTIFY_MASK; - header_event_box.events |= Gdk.EventMask.BUTTON_RELEASE_MASK; - header_event_box.add (header); + var header_gesture_click = new Gtk.GestureClick (); + header.add_controller (header_gesture_click); var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { hexpand = true @@ -276,14 +270,12 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { }; blocked_images_infobar.add_button (_("Show Images"), 1); blocked_images_infobar.add_button (_("Always Show from Sender"), 2); - blocked_images_infobar.get_style_context ().add_class (Gtk.STYLE_CLASS_FRAME); - blocked_images_infobar.no_show_all = true; + // blocked_images_infobar.add_css_class (Granite.STYLE_CLASS_FRAME); - var infobar_content = blocked_images_infobar.get_content_area (); - infobar_content.add (new Gtk.Label (_("This message contains remote images."))); - infobar_content.show_all (); + // var infobar_content = blocked_images_infobar.get_content_area (); + // infobar_content.append (new Gtk.Label (_("This message contains remote images."))); - ((Gtk.Box) blocked_images_infobar.get_action_area ()).orientation = Gtk.Orientation.VERTICAL; + // ((Gtk.Box) blocked_images_infobar.get_action_area ()).orientation = Gtk.Orientation.VERTICAL; web_view = new Mail.WebView () { margin_top = 12, @@ -298,24 +290,24 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { }); var secondary_box = new Gtk.Box (VERTICAL, 0); - secondary_box.add (separator); - secondary_box.add (blocked_images_infobar); - secondary_box.add (web_view); + secondary_box.append (separator); + secondary_box.append (blocked_images_infobar); + secondary_box.append (web_view); secondary_revealer = new Gtk.Revealer () { - transition_type = SLIDE_UP + transition_type = SLIDE_UP, + child = secondary_box }; - secondary_revealer.add (secondary_box); var base_box = new Gtk.Box (VERTICAL, 0) { hexpand = true, vexpand = true }; - base_box.add (header_event_box); - base_box.add (secondary_revealer); + base_box.append (header); + base_box.append (secondary_revealer); if (Camel.MessageFlags.ATTACHMENTS in (int) message_info.flags) { - var attachment_icon = new Gtk.Image.from_icon_name ("mail-attachment-symbolic", Gtk.IconSize.MENU); + var attachment_icon = new Gtk.Image.from_icon_name ("mail-attachment-symbolic"); attachment_icon.margin_start = 6; attachment_icon.tooltip_text = _("This message contains one or more attachments"); action_grid.attach (attachment_icon, 1, 0); @@ -324,41 +316,18 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { hexpand = true, homogeneous = true }; - attachment_bar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); - attachment_bar.get_style_context ().add_class ("bottom-toolbar"); - secondary_box.add (attachment_bar); + attachment_bar.add_css_class (Granite.STYLE_CLASS_FLAT); + attachment_bar.add_css_class ("bottom-toolbar"); + secondary_box.append (attachment_bar); } - add (base_box); + set_child (base_box); expanded = false; - show_all (); - avatar.set_loadable_icon (new GravatarIcon (parsed_address, get_style_context ().get_scale ())); + // avatar.set_loadable_icon (new GravatarIcon (parsed_address, get_style_context ().get_scale ())); - /* Override default handler to stop event propagation. Otherwise clicking the menu will - expand or collapse the MessageListItem. */ - actions_menu_button.button_release_event.connect ((event) => { - actions_menu_button.set_active (true); - return Gdk.EVENT_STOP; - }); - - header_event_box.enter_notify_event.connect ((event) => { - if (event.detail != Gdk.NotifyType.INFERIOR) { - var window = header_event_box.get_window (); - var cursor = new Gdk.Cursor.from_name (window.get_display (), "pointer"); - window.set_cursor (cursor); - } - }); - - header_event_box.leave_notify_event.connect ((event) => { - if (event.detail != Gdk.NotifyType.INFERIOR) { - header_event_box.get_window ().set_cursor (null); - } - }); - - header_event_box.button_release_event.connect ((event) => { + header_gesture_click.released.connect (() => { expanded = !expanded; - return Gdk.EVENT_STOP; }); destroy.connect (() => { @@ -366,7 +335,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { }); /* Connecting to clicked () doesn't allow us to prevent the event from propagating to header_event_box */ - starred_button.button_release_event.connect (() => { + starred_button.clicked.connect (() => { if (Camel.MessageFlags.FLAGGED in (int) message_info.flags) { message_info.set_flags (Camel.MessageFlags.FLAGGED, 0); starred_icon.icon_name = "non-starred-symbolic"; @@ -376,8 +345,6 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { starred_icon.icon_name = "starred-symbolic"; starred_icon.tooltip_text = _("Unstar message"); } - - return Gdk.EVENT_STOP; }); web_view.image_load_blocked.connect (() => { @@ -415,7 +382,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { /* @TODO: include header fields in printed output */ var print_operation = new WebKit.PrintOperation (web_view); print_operation.set_print_settings (settings); - print_operation.run_dialog ((Gtk.ApplicationWindow) get_toplevel ()); + print_operation.run_dialog ((Gtk.ApplicationWindow) get_root ()); } catch (Error e) { var print_error_dialog = new Granite.MessageDialog.with_image_from_icon_name ( _("Unable to print email"), @@ -423,7 +390,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { "printer" ) { badge_icon = new ThemedIcon ("dialog-error"), - transient_for = (Gtk.Window) get_toplevel () + transient_for = (Gtk.Window) get_root () }; print_error_dialog.show_error_details (e.message); print_error_dialog.present (); @@ -548,7 +515,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { } else if (part.disposition == "attachment") { var button = new AttachmentButton (part, loading_cancellable); button.activate.connect (() => show_attachment (button.mime_part)); - attachment_bar.add (button); + attachment_bar.append (button); } if (field.type == "text") { yield handle_text_mime (part.content); @@ -646,11 +613,11 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { new ThemedIcon ("dialog-warning"), Gtk.ButtonsType.CANCEL ) { - transient_for = (Gtk.Window) get_toplevel () + transient_for = (Gtk.Window) get_root () }; var open_button = dialog.add_button (_("Open Anyway"), Gtk.ResponseType.OK); - open_button.get_style_context ().add_class (Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION); + open_button.add_css_class (Granite.STYLE_CLASS_DESTRUCTIVE_ACTION); dialog.present (); dialog.response.connect ((response_id) => { From 9613ef5631314317981cd8649f9809e732d117f4 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 00:19:05 +0200 Subject: [PATCH 09/72] AttachmentButton: Update for gtk4 --- src/MessageList/AttachmentButton.vala | 75 +++++++++++++-------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/src/MessageList/AttachmentButton.vala b/src/MessageList/AttachmentButton.vala index 539cdd8f0..e1dd69d14 100644 --- a/src/MessageList/AttachmentButton.vala +++ b/src/MessageList/AttachmentButton.vala @@ -53,25 +53,17 @@ public class AttachmentButton : Gtk.FlowBoxChild { actions.add_action (save_as_action); insert_action_group (ACTION_GROUP_PREFIX, actions); + var gesture_secondary_click = new Gtk.GestureClick () { + button = Gdk.BUTTON_SECONDARY + }; + add_controller (gesture_secondary_click); + var context_menu_model = new Menu (); context_menu_model.append (_("Open"), ACTION_PREFIX + ACTION_OPEN); context_menu_model.append (_("Save As…"), ACTION_PREFIX + ACTION_SAVE_AS); - var menu = new Gtk.Menu.from_model (context_menu_model); - - var event_box = new Gtk.EventBox (); - event_box.events |= Gdk.EventMask.BUTTON_PRESS_MASK; - event_box.button_press_event.connect ((event) => { - if (event.button == Gdk.BUTTON_SECONDARY) { - menu.attach_widget = this; - menu.show_all (); - menu.popup_at_pointer (event); - } else { - activate (); - } - - return true; - }); + var menu = new Gtk.PopoverMenu.from_model (context_menu_model); + menu.set_parent (this); var grid = new Gtk.Grid () { margin_top = 6, @@ -85,7 +77,7 @@ public class AttachmentButton : Gtk.FlowBoxChild { var glib_type = GLib.ContentType.from_mime_type (mime_type); var content_icon = GLib.ContentType.get_icon (glib_type); - preview_image = new Gtk.Image.from_gicon (content_icon, Gtk.IconSize.DND) { + preview_image = new Gtk.Image.from_gicon (content_icon) { valign = Gtk.Align.CENTER }; @@ -96,7 +88,7 @@ public class AttachmentButton : Gtk.FlowBoxChild { size_label = new Gtk.Label (null) { xalign = 0 }; - size_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + size_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); new Thread (null, () => { string? size_text = null; @@ -112,7 +104,7 @@ public class AttachmentButton : Gtk.FlowBoxChild { size_label.label = size_text; } else { size_label.label = _("Unknown"); - size_label.get_style_context ().add_class (Gtk.STYLE_CLASS_ERROR); + size_label.add_css_class (Granite.STYLE_CLASS_ERROR); } return GLib.Source.REMOVE; @@ -124,29 +116,36 @@ public class AttachmentButton : Gtk.FlowBoxChild { grid.attach (preview_image, 0, 0, 1, 2); grid.attach (name_label, 1, 0, 1, 1); grid.attach (size_label, 1, 1, 1, 1); - event_box.add (grid); - add (event_box); - show_all (); + set_child (grid); + + gesture_secondary_click.pressed.connect ((n_press, x, y) => { + var rect = Gdk.Rectangle () { + x = (int) x, + y = (int) y + }; + menu.pointing_to = rect; + menu.popup (); + }); } private void on_save_as () { - Gtk.Window? parent_window = get_toplevel () as Gtk.Window; - var chooser = new Gtk.FileChooserNative ( - null, - parent_window, - Gtk.FileChooserAction.SAVE, - _("Save"), - _("Cancel") - ); - - chooser.set_current_name (mime_part.get_filename ()); - chooser.do_overwrite_confirmation = true; - - if (chooser.run () == Gtk.ResponseType.ACCEPT) { - write_to_file.begin (chooser.get_file ()); - } - - chooser.destroy (); + // Gtk.Window? parent_window = (Gtk.Window) get_root (); + // var chooser = new Gtk.FileChooserNative ( + // null, + // parent_window, + // Gtk.FileChooserAction.SAVE, + // _("Save"), + // _("Cancel") + // ); + + // chooser.set_current_name (mime_part.get_filename ()); + // chooser.do_overwrite_confirmation = true; + + // if (chooser.run () == Gtk.ResponseType.ACCEPT) { + // write_to_file.begin (chooser.get_file ()); + // } + + // chooser.destroy (); } private async void write_to_file (GLib.File file) { From c1af7e43fd7110506d413e4c9c5fbf058fa423b1 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 12:06:17 +0200 Subject: [PATCH 10/72] ConversationList: Prep for gtk4 --- src/ConversationList/ConversationList.vala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 43e2bb285..ba1b371e8 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -47,7 +47,7 @@ public class Mail.ConversationList : Gtk.Box { construct { orientation = VERTICAL; - get_style_context ().add_class (Gtk.STYLE_CLASS_VIEW); + add_css_class (Granite.STYLE_CLASS_VIEW); conversations = new Gee.HashMap (); folders = new Gee.HashMap (); @@ -92,9 +92,8 @@ public class Mail.ConversationList : Gtk.Box { margin_bottom = 3, margin_top = 3 }; - filter_menu_popover_box.add (hide_read_switch); - filter_menu_popover_box.add (hide_unstarred_switch); - filter_menu_popover_box.show_all (); + filter_menu_popover_box.append (hide_read_switch); + filter_menu_popover_box.append (hide_unstarred_switch); var filter_popover = new Gtk.Popover (null) { child = filter_menu_popover_box @@ -111,7 +110,7 @@ public class Mail.ConversationList : Gtk.Box { custom_title = search_entry }; search_header.pack_end (filter_button); - search_header.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + search_header.add_css_class (Granite.STYLE_CLASS_FLAT); var scrolled_window = new Gtk.ScrolledWindow (null, null) { hscrollbar_policy = Gtk.PolicyType.NEVER, @@ -120,7 +119,7 @@ public class Mail.ConversationList : Gtk.Box { child = list_box }; - var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic", Gtk.IconSize.SMALL_TOOLBAR) { + var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic") { //Small toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REFRESH }; @@ -145,11 +144,11 @@ public class Mail.ConversationList : Gtk.Box { var conversation_action_bar = new Gtk.ActionBar (); conversation_action_bar.pack_start (refresh_stack); - conversation_action_bar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + conversation_action_bar.add_css_class (Granite.STYLE_CLASS_FLAT); - add (search_header); - add (scrolled_window); - add (conversation_action_bar); + append (search_header); + append (scrolled_window); + append (conversation_action_bar); search_entry.search_changed.connect (() => load_folder.begin (folder_full_name_per_account)); From 3a6c7bb2f02db693ac1bb4b83b9be5f2b1bace38 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 12:28:45 +0200 Subject: [PATCH 11/72] ConversationListStore: Update for gtk4 --- .../ConversationListStore.vala | 101 +++++------------- 1 file changed, 27 insertions(+), 74 deletions(-) diff --git a/src/ConversationList/ConversationListStore.vala b/src/ConversationList/ConversationListStore.vala index b83641a30..230a60669 100644 --- a/src/ConversationList/ConversationListStore.vala +++ b/src/ConversationList/ConversationListStore.vala @@ -20,94 +20,47 @@ * Authored by: David Hewitt */ -public class Mail.ConversationListStore : VirtualizingListBoxModel { - public delegate bool RowVisibilityFunc (GLib.Object row); +public class Mail.ConversationListStore : ListModel, Object { + //The items_changed signal is not emitted automatically, the object using this has to emit it manually!!! - private GLib.Sequence data = new GLib.Sequence (); - private uint last_position = uint.MAX; - private GLib.SequenceIter? last_iter; - private unowned GLib.CompareDataFunc compare_func; - private unowned RowVisibilityFunc filter_func; + private GLib.List data = new GLib.List (); - public override uint get_n_items () { - return data.get_length (); + public GLib.Type get_item_type () { + return typeof(ConversationItemModel); } - public override GLib.Object? get_item (uint index) { - return get_item_internal (index); - } - - public override GLib.Object? get_item_unfiltered (uint index) { - return get_item_internal (index, true); + public uint get_n_items () { + uint n_items = 0; + data.foreach ((item) => { + n_items++; + }); + return n_items; } - private GLib.Object? get_item_internal (uint index, bool unfiltered = false) { - GLib.SequenceIter? iter = null; - - if (last_position != uint.MAX) { - if (last_position == index + 1) { - iter = last_iter.prev (); - } else if (last_position == index - 1) { - iter = last_iter.next (); - } else if (last_position == index) { - iter = last_iter; - } - } - - if (iter == null) { - iter = data.get_iter_at_pos ((int)index); - } - - last_iter = iter; - last_position = index; - - if (iter.is_end ()) { - return null; - } - - if (filter_func == null) { - return iter.get (); - } else if (filter_func (iter.get ())) { - return iter.get (); - } else if (unfiltered) { - return iter.get (); - } else { - return null; - } + public GLib.Object? get_item (uint index) { + return get_item_internal (index); } - public void add (ConversationItemModel data) { - if (compare_func != null) { - this.data.insert_sorted (data, compare_func); - } else { - this.data.append (data); - } - - last_iter = null; - last_position = uint.MAX; + private ConversationItemModel get_item_internal (uint index) { + return data.nth_data (index); } - public void remove (ConversationItemModel data) { - var iter = this.data.get_iter_at_pos (get_index_of_unfiltered (data)); - iter.remove (); - - last_iter = null; - last_position = uint.MAX; + public void add (ConversationItemModel item) { + /*Adding automatically sorts according to timestamp*/ + data.insert_sorted (item, (a,b)=> { + var item1 = (ConversationItemModel) a; + var item2 = (ConversationItemModel) b; + return (int)(item2.timestamp - item1.timestamp); + }); } public void remove_all () { - data.get_begin_iter ().remove_range (data.get_end_iter ()); - unselect_all (); - - last_iter = null; - last_position = uint.MAX; - } - - public void set_sort_func (GLib.CompareDataFunc function) { - this.compare_func = function; + data.foreach ((item) => { + data.remove (item); + }); } - public void set_filter_func (RowVisibilityFunc? function) { - filter_func = function; + public void remove (ConversationItemModel item) { + data.remove (item); } } From cfcdea4ca640bba3b302e9b85f831adbd446e6db Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 12:35:53 +0200 Subject: [PATCH 12/72] ConversationListItem: Update for gtk4 --- .../ConversationListItem.vala | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index 7db572596..ba1f49100 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -19,7 +19,7 @@ * Authored by: Corentin Noël */ -public class Mail.ConversationListItem : VirtualizingListBoxRow { +public class Mail.ConversationListItem : Gtk.Grid { private Gtk.Image status_icon; private Gtk.Label date; private Gtk.Label messages; @@ -29,13 +29,13 @@ public class Mail.ConversationListItem : VirtualizingListBoxRow { private Gtk.Revealer status_revealer; construct { - status_icon = new Gtk.Image.from_icon_name ("mail-unread-symbolic", Gtk.IconSize.MENU); + status_icon = new Gtk.Image.from_icon_name ("mail-unread-symbolic"); status_revealer = new Gtk.Revealer () { child = status_icon }; - var flagged_icon = new Gtk.Image.from_icon_name ("starred-symbolic", Gtk.IconSize.MENU); + var flagged_icon = new Gtk.Image.from_icon_name ("starred-symbolic"); flagged_icon_revealer = new Gtk.Revealer () { child = flagged_icon }; @@ -46,15 +46,13 @@ public class Mail.ConversationListItem : VirtualizingListBoxRow { use_markup = true, xalign = 0 }; - source.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL); + source.add_css_class (Granite.STYLE_CLASS_H3_LABEL); messages = new Gtk.Label (null) { halign = Gtk.Align.END }; - - weak Gtk.StyleContext messages_style = messages.get_style_context (); - messages_style.add_class (Granite.STYLE_CLASS_BADGE); - messages_style.add_class (Gtk.STYLE_CLASS_FLAT); + messages.add_css_class (Granite.STYLE_CLASS_BADGE); + messages.add_css_class (Granite.STYLE_CLASS_FLAT); topic = new Gtk.Label (null) { hexpand = true, @@ -65,7 +63,7 @@ public class Mail.ConversationListItem : VirtualizingListBoxRow { date = new Gtk.Label (null) { halign = Gtk.Align.END }; - date.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + date.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); var grid = new Gtk.Grid () { margin_top = 12, @@ -76,17 +74,14 @@ public class Mail.ConversationListItem : VirtualizingListBoxRow { row_spacing = 6 }; - grid.attach (status_revealer, 0, 0); - grid.attach (flagged_icon_revealer, 0, 1, 1, 1); - grid.attach (source, 1, 0, 1, 1); - grid.attach (date, 2, 0, 2, 1); - grid.attach (topic, 1, 1, 2, 1); - grid.attach (messages, 3, 1, 1, 1); - - get_style_context ().add_class ("conversation-list-item"); - add (grid); + attach (status_revealer, 0, 0); + attach (flagged_icon_revealer, 0, 1, 1, 1); + attach (source, 1, 0, 1, 1); + attach (date, 2, 0, 2, 1); + attach (topic, 1, 1, 2, 1); + attach (messages, 3, 1, 1, 1); - show_all (); + add_css_class ("conversation-list-item"); } public void assign (ConversationItemModel data) { @@ -105,7 +100,7 @@ public class Mail.ConversationListItem : VirtualizingListBoxRow { uint num_messages = data.num_messages; messages.label = num_messages > 1 ? "%u".printf (num_messages) : null; messages.visible = num_messages > 1; - messages.no_show_all = num_messages <= 1; + // messages.no_show_all = num_messages <= 1; if (data.unread) { get_style_context ().add_class ("unread-message"); From 2ed1416b294cabab1f42442916080c814c195b5e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 12:51:41 +0200 Subject: [PATCH 13/72] Derive directly from Gtk.Grid --- src/ConversationList/ConversationListItem.vala | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index ba1f49100..d18dd2742 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -65,14 +65,12 @@ public class Mail.ConversationListItem : Gtk.Grid { }; date.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); - var grid = new Gtk.Grid () { - margin_top = 12, - margin_bottom = 12, - margin_start = 12, - margin_end = 12, - column_spacing = 12, - row_spacing = 6 - }; + margin_top = 12; + margin_bottom = 12; + margin_start = 12; + margin_end = 12; + column_spacing = 12; + row_spacing = 6; attach (status_revealer, 0, 0); attach (flagged_icon_revealer, 0, 1, 1, 1); From 7bc70f0860d2f96d445a5a74ccaf048ecb750227 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 12:58:35 +0200 Subject: [PATCH 14/72] Derive from separate Gtk.Box --- .../ConversationListItem.vala | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index d18dd2742..20e72e7dc 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -19,7 +19,7 @@ * Authored by: Corentin Noël */ -public class Mail.ConversationListItem : Gtk.Grid { +public class Mail.ConversationListItem : Gtk.Box { private Gtk.Image status_icon; private Gtk.Label date; private Gtk.Label messages; @@ -65,21 +65,24 @@ public class Mail.ConversationListItem : Gtk.Grid { }; date.add_css_class (Granite.STYLE_CLASS_DIM_LABEL); - margin_top = 12; - margin_bottom = 12; - margin_start = 12; - margin_end = 12; - column_spacing = 12; - row_spacing = 6; + var grid = new Gtk.Grid () { + margin_top = 12, + margin_bottom = 12, + margin_start = 12, + margin_end = 12, + column_spacing = 12, + row_spacing = 6 + }; - attach (status_revealer, 0, 0); - attach (flagged_icon_revealer, 0, 1, 1, 1); - attach (source, 1, 0, 1, 1); - attach (date, 2, 0, 2, 1); - attach (topic, 1, 1, 2, 1); - attach (messages, 3, 1, 1, 1); + grid.attach (status_revealer, 0, 0); + grid.attach (flagged_icon_revealer, 0, 1, 1, 1); + grid.attach (source, 1, 0, 1, 1); + grid.attach (date, 2, 0, 2, 1); + grid.attach (topic, 1, 1, 2, 1); + grid.attach (messages, 3, 1, 1, 1); add_css_class ("conversation-list-item"); + append (grid); } public void assign (ConversationItemModel data) { @@ -101,19 +104,19 @@ public class Mail.ConversationListItem : Gtk.Grid { // messages.no_show_all = num_messages <= 1; if (data.unread) { - get_style_context ().add_class ("unread-message"); + add_css_class ("unread-message"); status_icon.icon_name = "mail-unread-symbolic"; status_icon.tooltip_text = _("Unread"); - status_icon.get_style_context ().add_class (Granite.STYLE_CLASS_ACCENT); + status_icon.add_css_class (Granite.STYLE_CLASS_ACCENT); status_revealer.reveal_child = true; - source.get_style_context ().add_class (Granite.STYLE_CLASS_ACCENT); + source.add_css_class (Granite.STYLE_CLASS_ACCENT); } else { - get_style_context ().remove_class ("unread-message"); - status_icon.get_style_context ().remove_class (Granite.STYLE_CLASS_ACCENT); - source.get_style_context ().remove_class (Granite.STYLE_CLASS_ACCENT); + remove_css_class ("unread-message"); + status_icon.remove_css_class (Granite.STYLE_CLASS_ACCENT); + source.remove_css_class (Granite.STYLE_CLASS_ACCENT); if (data.replied_all || data.replied) { status_icon.icon_name = "mail-replied-symbolic"; From 60120c311f66296995b17823bd00331d25aea03c Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 14:31:20 +0200 Subject: [PATCH 15/72] Add gesture click --- src/ConversationList/ConversationListItem.vala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index 20e72e7dc..cb2dde2c5 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -20,6 +20,9 @@ */ public class Mail.ConversationListItem : Gtk.Box { + public signal void secondary_click (double x, double y); + public ulong handler_id; + private Gtk.Image status_icon; private Gtk.Label date; private Gtk.Label messages; @@ -83,6 +86,14 @@ public class Mail.ConversationListItem : Gtk.Box { add_css_class ("conversation-list-item"); append (grid); + + var gesture_click = new Gtk.GestureClick () { + button = Gdk.BUTTON_SECONDARY + }; + add_controller (gesture_click); + gesture_click.released.connect ((n_press, x, y) => { + secondary_click (x, y); + }); } public void assign (ConversationItemModel data) { From e5a348adab26a18b87890b8c474332052ddbbe82 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 14:31:40 +0200 Subject: [PATCH 16/72] Protoype ListView rewrite --- src/ConversationList/ConversationList.vala | 427 +++++++++++---------- 1 file changed, 223 insertions(+), 204 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index ba1b371e8..13c2bc367 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -29,18 +29,21 @@ public class Mail.ConversationList : Gtk.Box { public Gee.Map folder_full_name_per_account { get; private set; } public Gee.HashMap folders { get; private set; } public Gee.HashMap folder_info_flags { get; private set; } - public Hdy.HeaderBar search_header { get; private set; } + public Gtk.HeaderBar search_header { get; private set; } private GLib.Cancellable? cancellable = null; private Gee.HashMap threads; private Gee.HashMap conversations; - private ConversationListStore list_store; private MoveHandler move_handler; - private VirtualizingListBox list_box; private Gtk.SearchEntry search_entry; private Granite.SwitchModelButton hide_read_switch; private Granite.SwitchModelButton hide_unstarred_switch; private Gtk.MenuButton filter_button; + private ConversationListStore list_store; + private Gtk.EveryFilter every_filter; + private Gtk.SingleSelection selection_model; + private Gtk.ListView list_view; + private Gtk.PopoverMenu context_menu; private Gtk.Stack refresh_stack; private uint mark_read_timeout_id = 0; @@ -53,28 +56,25 @@ public class Mail.ConversationList : Gtk.Box { folders = new Gee.HashMap (); folder_info_flags = new Gee.HashMap (); threads = new Gee.HashMap (); - list_store = new ConversationListStore (); - list_store.set_sort_func (thread_sort_function); - list_store.set_filter_func (filter_function); move_handler = new MoveHandler (); - list_box = new VirtualizingListBox () { - activate_on_single_click = true, - model = list_store - }; - list_box.factory_func = (item, old_widget) => { - ConversationListItem? row = null; - if (old_widget != null) { - row = old_widget as ConversationListItem; - } else { - row = new ConversationListItem (); - } - - row.assign ((ConversationItemModel)item); - row.show_all (); - return row; - }; + // list_box = new VirtualizingListBox () { + // activate_on_single_click = true, + // model = list_store + // }; + // list_box.factory_func = (item, old_widget) => { + // ConversationListItem? row = null; + // if (old_widget != null) { + // row = old_widget as ConversationListItem; + // } else { + // row = new ConversationListItem (); + // } + + // row.assign ((ConversationItemModel)item); + // row.show_all (); + // return row; + // }; var application_instance = (Gtk.Application) GLib.Application.get_default (); @@ -95,28 +95,54 @@ public class Mail.ConversationList : Gtk.Box { filter_menu_popover_box.append (hide_read_switch); filter_menu_popover_box.append (hide_unstarred_switch); - var filter_popover = new Gtk.Popover (null) { + var filter_popover = new Gtk.Popover () { child = filter_menu_popover_box }; filter_button = new Gtk.MenuButton () { - image = new Gtk.Image.from_icon_name ("mail-filter-symbolic", Gtk.IconSize.SMALL_TOOLBAR), + icon_name = "mail-filter-symbolic", //Small toolbar popover = filter_popover, tooltip_text = _("Filter Conversations"), valign = Gtk.Align.CENTER }; - search_header = new Hdy.HeaderBar () { - custom_title = search_entry + search_header = new Gtk.HeaderBar () { + title_widget = search_entry, + show_title_buttons = false }; search_header.pack_end (filter_button); search_header.add_css_class (Granite.STYLE_CLASS_FLAT); - var scrolled_window = new Gtk.ScrolledWindow (null, null) { + list_store = new ConversationListStore (); + + every_filter = new Gtk.EveryFilter (); + var filter_model = new Gtk.FilterListModel (list_store, every_filter); + + selection_model = new Gtk.SingleSelection (filter_model) { + autoselect = false + }; + + var factory = new Gtk.SignalListItemFactory (); + + list_view = new Gtk.ListView (selection_model, factory) { + show_separators = false, + }; + + var event_controller_focus = new Gtk.EventControllerFocus (); + list_view.add_controller (event_controller_focus); + + context_menu = new Gtk.PopoverMenu.from_model (null) { + position = RIGHT, + has_arrow = false + }; + context_menu.set_parent (list_view); + + var scrolled_window = new Gtk.ScrolledWindow () { hscrollbar_policy = Gtk.PolicyType.NEVER, width_request = 158, - expand = true, - child = list_box + hexpand = true, + vexpand = true, + child = list_view }; var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic") { //Small toolbar @@ -129,7 +155,7 @@ public class Mail.ConversationList : Gtk.Box { ); var refresh_spinner = new Gtk.Spinner () { - active = true, + spinning = true, halign = Gtk.Align.CENTER, valign = Gtk.Align.CENTER, tooltip_text = _("Fetching new messages…") @@ -152,89 +178,102 @@ public class Mail.ConversationList : Gtk.Box { search_entry.search_changed.connect (() => load_folder.begin (folder_full_name_per_account)); - // Disable delete accelerators when the conversation list box loses keyboard focus, - // restore them when it returns (Replace with EventControllerFocus in GTK4) - list_box.set_focus_child.connect ((widget) => { - if (widget == null) { - application_instance.set_accels_for_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, - {} - ); - } else { - application_instance.set_accels_for_action ( - MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, - MainWindow.action_accelerators[MainWindow.ACTION_MOVE_TO_TRASH].to_array () - ); - } + hide_read_switch.toggled.connect (() => load_folder.begin (folder_full_name_per_account)); + + hide_unstarred_switch.toggled.connect (() => load_folder.begin (folder_full_name_per_account)); + + factory.setup.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + var conversation_list_item = new ConversationListItem (); + conversation_list_item.handler_id = conversation_list_item.secondary_click.connect ((x, y) => { + if (!selection_model.is_selected (list_item.get_position ())) { + selection_model.select_item (list_item.get_position (), true); + } + double dest_x; + double dest_y; + conversation_list_item.translate_coordinates (list_view, x, y, out dest_x, out dest_y); + create_context_menu (dest_x, dest_y); + }); + list_item.set_child (conversation_list_item); + }); + + factory.bind.connect ((obj) => { + var list_item = (Gtk.ListItem) obj; + var conversation_list_item = (ConversationListItem) list_item.child; + conversation_list_item.assign((ConversationItemModel) list_item.get_item ()); }); - list_box.row_activated.connect ((row) => { + //@TODO: Needed? + factory.teardown.connect ((obj) => { + var conversation_list_item = (ConversationListItem) ((Gtk.ListItem) obj).child; + conversation_list_item.disconnect (conversation_list_item.handler_id); + }); + + selection_model.selection_changed.connect (() => { if (mark_read_timeout_id != 0) { GLib.Source.remove (mark_read_timeout_id); mark_read_timeout_id = 0; } - if (row == null) { + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first (selected_items, out current_item_position); + + if (!bitset_iter.is_valid ()) { conversation_focused (null); + conversation_selected (null); } else { - conversation_focused (((ConversationItemModel) row).node); + var conversation_item = (ConversationItemModel) selection_model.get_item (current_item_position); + conversation_focused (conversation_item.node); - if (((ConversationItemModel) row).unread) { + if (conversation_item.unread) { mark_read_timeout_id = GLib.Timeout.add_seconds (MARK_READ_TIMEOUT_SECONDS, () => { - set_thread_flag (((ConversationItemModel) row).node, Camel.MessageFlags.SEEN); + set_thread_flag (conversation_item.node, Camel.MessageFlags.SEEN); mark_read_timeout_id = 0; return false; }); } - } - }); - list_box.row_selected.connect ((row) => { - if (row == null) { - conversation_selected (null); - } else { // We call get_action_group() on the parent window, instead of on `this` directly, due to a // bug with Gtk.Widget.get_action_group(). See https://gitlab.gnome.org/GNOME/gtk/issues/1396 - var window = (Gtk.ApplicationWindow) get_toplevel (); - weak GLib.ActionMap win_action_map = (GLib.ActionMap) window.get_action_group (MainWindow.ACTION_GROUP_PREFIX); - ((SimpleAction) win_action_map.lookup_action (MainWindow.ACTION_MARK_READ)).set_enabled (((ConversationItemModel) row).unread); - ((SimpleAction) win_action_map.lookup_action (MainWindow.ACTION_MARK_UNREAD)).set_enabled (!((ConversationItemModel) row).unread); - ((SimpleAction) win_action_map.lookup_action (MainWindow.ACTION_MARK_STAR)).set_enabled (!((ConversationItemModel) row).flagged); - ((SimpleAction) win_action_map.lookup_action (MainWindow.ACTION_MARK_UNSTAR)).set_enabled (((ConversationItemModel) row).flagged); - conversation_selected (((ConversationItemModel) row).node); - } - }); + var window = (MainWindow) get_root (); + window.get_action (MainWindow.ACTION_MARK_READ).set_enabled (conversation_item.unread); + window.get_action (MainWindow.ACTION_MARK_UNREAD).set_enabled (!conversation_item.unread); + window.get_action (MainWindow.ACTION_MARK_STAR).set_enabled (!conversation_item.flagged); + window.get_action (MainWindow.ACTION_MARK_UNSTAR).set_enabled (conversation_item.flagged); - hide_read_switch.toggled.connect (() => load_folder.begin (folder_full_name_per_account)); - - hide_unstarred_switch.toggled.connect (() => load_folder.begin (folder_full_name_per_account)); - - button_release_event.connect ((e) => { - - if (e.button != Gdk.BUTTON_SECONDARY) { - return Gdk.EVENT_PROPAGATE; + conversation_selected (conversation_item.node); } + }); - var row = list_box.get_row_at_y ((int)e.y); - - if (list_box.selected_row_widget != row) { - list_box.select_row (row); - } + // Disable delete accelerators when the conversation list box loses keyboard focus, + // restore them when it returns + event_controller_focus.enter.connect (() => { + application_instance.set_accels_for_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, + MainWindow.action_accelerators[MainWindow.ACTION_MOVE_TO_TRASH].to_array () + ); + }); - return create_context_menu (e, (ConversationListItem)row); + event_controller_focus.leave.connect (() => { + application_instance.set_accels_for_action ( + MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH, + {} + ); }); - key_release_event.connect ((e) => { + // key_release_event.connect ((e) => { - if (e.keyval != Gdk.Key.Menu) { - return Gdk.EVENT_PROPAGATE; - } + // if (e.keyval != Gdk.Key.Menu) { + // return Gdk.EVENT_PROPAGATE; + // } - var row = list_box.selected_row_widget; + // var row = list_box.selected_row_widget; - return create_context_menu (e, (ConversationListItem)row); - }); + // return create_context_menu (e, (ConversationListItem)row); + // }); } private static void set_thread_flag (Camel.FolderThreadNode? node, Camel.MessageFlags flag) { @@ -272,7 +311,6 @@ public class Mail.ConversationList : Gtk.Box { threads.clear (); list_store.remove_all (); - list_store.items_changed (0, previous_items, 0); cancellable = new GLib.Cancellable (); @@ -323,7 +361,7 @@ public class Mail.ConversationList : Gtk.Box { } } - list_store.items_changed (0, 0, list_store.get_n_items ()); + list_store.items_changed (0, previous_items, list_store.get_n_items ()); } public async void refresh_folder (GLib.Cancellable? cancellable = null) { @@ -357,13 +395,12 @@ public class Mail.ConversationList : Gtk.Box { threads[service_uid] = new Camel.FolderThread (folders[service_uid], search_result_uids, false); - var removed = 0; + var previous_items = list_store.get_n_items (); change_info.get_removed_uids ().foreach ((uid) => { var item = conversations[uid]; if (item != null) { conversations.unset (uid); list_store.remove (item); - removed++; } }); @@ -380,7 +417,6 @@ public class Mail.ConversationList : Gtk.Box { if (item.is_older_than (child)) { conversations.unset (child.message.uid); list_store.remove (item); - removed++; add_conversation_item (folder_info_flags[service_uid], child, threads[service_uid], service_uid); }; } @@ -388,7 +424,7 @@ public class Mail.ConversationList : Gtk.Box { child = child.next; } - list_store.items_changed (0, removed, list_store.get_n_items ()); + list_store.items_changed (0, previous_items, list_store.get_n_items ()); } } } @@ -465,101 +501,117 @@ public class Mail.ConversationList : Gtk.Box { } public void mark_read_selected_messages () { - var selected_rows = list_box.get_selected_rows (); - foreach (var row in selected_rows) { - (((ConversationItemModel)row).node).message.set_flags (Camel.MessageFlags.SEEN, ~0); + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + while (bitset_iter.is_valid ()) { + ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, ~0); + bitset_iter.next (out current_item_position); } } public void mark_star_selected_messages () { - var selected_rows = list_box.get_selected_rows (); - foreach (var row in selected_rows) { - (((ConversationItemModel)row).node).message.set_flags (Camel.MessageFlags.FLAGGED, ~0); + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + while (bitset_iter.is_valid ()) { + ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, ~0); + bitset_iter.next (out current_item_position); } } public void mark_unread_selected_messages () { - var selected_rows = list_box.get_selected_rows (); - foreach (var row in selected_rows) { - (((ConversationItemModel)row).node).message.set_flags (Camel.MessageFlags.SEEN, 0); + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + while (bitset_iter.is_valid ()) { + ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, 0); + bitset_iter.next (out current_item_position); } } public void mark_unstar_selected_messages () { - var selected_rows = list_box.get_selected_rows (); - foreach (var row in selected_rows) { - (((ConversationItemModel)row).node).message.set_flags (Camel.MessageFlags.FLAGGED, 0); + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + while (bitset_iter.is_valid ()) { + ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, 0); + bitset_iter.next (out current_item_position); } } - public async int archive_selected_messages () { - var archive_threads = new Gee.HashMap> (); + // public async int archive_selected_messages () { + // var archive_threads = new Gee.HashMap> (); - var selected_rows = list_box.get_selected_rows (); - int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); + // var selected_rows = list_box.get_selected_rows (); + // int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); - foreach (unowned var selected_row in selected_rows) { - var selected_item_model = (ConversationItemModel) selected_row; + // foreach (unowned var selected_row in selected_rows) { + // var selected_item_model = (ConversationItemModel) selected_row; - if (archive_threads[selected_item_model.service_uid] == null) { - archive_threads[selected_item_model.service_uid] = new Gee.ArrayList (); - } + // if (archive_threads[selected_item_model.service_uid] == null) { + // archive_threads[selected_item_model.service_uid] = new Gee.ArrayList (); + // } - archive_threads[selected_item_model.service_uid].add (selected_item_model.node); - } + // archive_threads[selected_item_model.service_uid].add (selected_item_model.node); + // } - var archived = 0; - foreach (var service_uid in archive_threads.keys) { - archived += yield move_handler.archive_threads (folders[service_uid], archive_threads[service_uid]); - } + // var archived = 0; + // foreach (var service_uid in archive_threads.keys) { + // archived += yield move_handler.archive_threads (folders[service_uid], archive_threads[service_uid]); + // } - if (archived > 0) { - foreach (var service_uid in archive_threads.keys) { - var threads = archive_threads[service_uid]; + // if (archived > 0) { + // foreach (var service_uid in archive_threads.keys) { + // var threads = archive_threads[service_uid]; - foreach (unowned var thread in threads) { - unowned var uid = thread.message.uid; - var item = conversations[uid]; - if (item != null) { - conversations.unset (uid); - list_store.remove (item); - } - } - } - } + // foreach (unowned var thread in threads) { + // unowned var uid = thread.message.uid; + // var item = conversations[uid]; + // if (item != null) { + // conversations.unset (uid); + // list_store.remove (item); + // } + // } + // } + // } - list_store.items_changed (0, archived, list_store.get_n_items ()); - list_box.select_row_at_index (selected_rows_start_index); + // list_store.items_changed (0, archived, list_store.get_n_items ()); + // list_box.select_row_at_index (selected_rows_start_index); - return archived; - } + // return archived; + // } - public int trash_selected_messages () { - var trash_threads = new Gee.HashMap> (); + // public int trash_selected_messages () { + // var trash_threads = new Gee.HashMap> (); - var selected_rows = list_box.get_selected_rows (); - int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); + // var selected_rows = list_box.get_selected_rows (); + // int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); - foreach (unowned var selected_row in selected_rows) { - var selected_item_model = (ConversationItemModel) selected_row; + // foreach (unowned var selected_row in selected_rows) { + // var selected_item_model = (ConversationItemModel) selected_row; - if (trash_threads[selected_item_model.service_uid] == null) { - trash_threads[selected_item_model.service_uid] = new Gee.ArrayList (); - } + // if (trash_threads[selected_item_model.service_uid] == null) { + // trash_threads[selected_item_model.service_uid] = new Gee.ArrayList (); + // } - trash_threads[selected_item_model.service_uid].add (selected_item_model.node); - } + // trash_threads[selected_item_model.service_uid].add (selected_item_model.node); + // } - var deleted = 0; - foreach (var service_uid in trash_threads.keys) { - deleted += move_handler.delete_threads (folders[service_uid], trash_threads[service_uid]); - } + // var deleted = 0; + // foreach (var service_uid in trash_threads.keys) { + // deleted += move_handler.delete_threads (folders[service_uid], trash_threads[service_uid]); + // } - list_store.items_changed (0, 0, list_store.get_n_items ()); - list_box.select_row_at_index (selected_rows_start_index + 1); + // list_store.items_changed (0, 0, list_store.get_n_items ()); + // list_box.select_row_at_index (selected_rows_start_index + 1); - return deleted; - } + // return deleted; + // } public void undo_move () { move_handler.undo_last_move.begin ((obj, res) => { @@ -572,65 +624,32 @@ public class Mail.ConversationList : Gtk.Box { move_handler.expire_undo (); } - private bool create_context_menu (Gdk.Event e, ConversationListItem row) { - var item = (ConversationItemModel)row.model_item; - - var menu = new Gtk.Menu (); + private void create_context_menu (double x, double y) { + var menu = new Menu (); - var trash_menu_item = new Gtk.MenuItem (); - trash_menu_item.add (new Granite.AccelLabel.from_action_name (_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH)); - menu.add (trash_menu_item); - - trash_menu_item.activate.connect (() => { - trash_selected_messages (); - }); + var conversation_item_model = (ConversationItemModel) selection_model.get_selected_item (); - if (!item.unread) { - var mark_unread_menu_item = new Gtk.MenuItem (); - mark_unread_menu_item.add (new Granite.AccelLabel.from_action_name (_("Mark As Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD)); - menu.add (mark_unread_menu_item); + menu.append(_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH); - mark_unread_menu_item.activate.connect (() => { - mark_unread_selected_messages (); - }); + if (!conversation_item_model.unread) { + menu.append (_("Mark As Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); } else { - var mark_read_menu_item = new Gtk.MenuItem (); - mark_read_menu_item.add (new Granite.AccelLabel.from_action_name (_("Mark as Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ)); - menu.add (mark_read_menu_item); - - mark_read_menu_item.activate.connect (() => { - mark_read_selected_messages (); - }); + menu.append (_("Mark As Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); } - if (!item.flagged) { - var mark_starred_menu_item = new Gtk.MenuItem (); - mark_starred_menu_item.add (new Granite.AccelLabel.from_action_name (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR)); - menu.add (mark_starred_menu_item); - - mark_starred_menu_item.activate.connect (() => { - mark_star_selected_messages (); - }); + if (!conversation_item_model.flagged) { + menu.append (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); } else { - var mark_unstarred_menu_item = new Gtk.MenuItem (); - mark_unstarred_menu_item.add (new Granite.AccelLabel.from_action_name (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR)); - menu.add (mark_unstarred_menu_item); - - mark_unstarred_menu_item.activate.connect (() => { - mark_unstar_selected_messages (); - }); + menu.append (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); } - menu.show_all (); + context_menu.set_menu_model (menu); - if (e.type == Gdk.EventType.BUTTON_RELEASE) { - menu.popup_at_pointer (e); - return Gdk.EVENT_STOP; - } else if (e.type == Gdk.EventType.KEY_RELEASE) { - menu.popup_at_widget (row, Gdk.Gravity.EAST, Gdk.Gravity.CENTER, e); - return Gdk.EVENT_STOP; - } - - return Gdk.EVENT_PROPAGATE; + Gdk.Rectangle pos = Gdk.Rectangle () { + x = (int) x, + y = (int) y + }; + context_menu.set_pointing_to (pos); + context_menu.popup (); } } From 9740934902c84c0dad10a361ec9254c6c79a90d2 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 14:43:50 +0200 Subject: [PATCH 17/72] ConversationList: Remove unused method, reenable deleted filter --- src/ConversationList/ConversationList.vala | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 13c2bc367..27799713d 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -40,7 +40,6 @@ public class Mail.ConversationList : Gtk.Box { private Granite.SwitchModelButton hide_unstarred_switch; private Gtk.MenuButton filter_button; private ConversationListStore list_store; - private Gtk.EveryFilter every_filter; private Gtk.SingleSelection selection_model; private Gtk.ListView list_view; private Gtk.PopoverMenu context_menu; @@ -115,8 +114,9 @@ public class Mail.ConversationList : Gtk.Box { list_store = new ConversationListStore (); - every_filter = new Gtk.EveryFilter (); - var filter_model = new Gtk.FilterListModel (list_store, every_filter); + var deleted_filter = new Gtk.CustomFilter (deleted_filter_func); + + var filter_model = new Gtk.FilterListModel (list_store, deleted_filter); selection_model = new Gtk.SingleSelection (filter_model) { autoselect = false @@ -488,16 +488,9 @@ public class Mail.ConversationList : Gtk.Box { list_store.add (item); } - private static bool filter_function (GLib.Object obj) { - if (obj is ConversationItemModel) { - return !((ConversationItemModel)obj).deleted; - } else { - return false; - } - } - - private static int thread_sort_function (ConversationItemModel item1, ConversationItemModel item2) { - return (int)(item2.timestamp - item1.timestamp); + private static bool deleted_filter_func (Object item) { + var conversation_item = (ConversationItemModel) item; + return !conversation_item.deleted; } public void mark_read_selected_messages () { From f08879c6ce624f8b979c5477c04672b521065ec9 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 15:27:08 +0200 Subject: [PATCH 18/72] ConversationList: Fix seg fault + reenable trash --- src/ConversationList/ConversationList.vala | 53 +++++++++++----------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 27799713d..ec81f4cb2 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -203,12 +203,6 @@ public class Mail.ConversationList : Gtk.Box { conversation_list_item.assign((ConversationItemModel) list_item.get_item ()); }); - //@TODO: Needed? - factory.teardown.connect ((obj) => { - var conversation_list_item = (ConversationListItem) ((Gtk.ListItem) obj).child; - conversation_list_item.disconnect (conversation_list_item.handler_id); - }); - selection_model.selection_changed.connect (() => { if (mark_read_timeout_id != 0) { GLib.Source.remove (mark_read_timeout_id); @@ -489,8 +483,7 @@ public class Mail.ConversationList : Gtk.Box { } private static bool deleted_filter_func (Object item) { - var conversation_item = (ConversationItemModel) item; - return !conversation_item.deleted; + return !((ConversationItemModel)item).deleted; } public void mark_read_selected_messages () { @@ -579,32 +572,38 @@ public class Mail.ConversationList : Gtk.Box { // return archived; // } - // public int trash_selected_messages () { - // var trash_threads = new Gee.HashMap> (); + public int trash_selected_messages () { + var trash_threads = new Gee.HashMap> (); - // var selected_rows = list_box.get_selected_rows (); - // int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); + var previous_items = list_store.get_n_items (); - // foreach (unowned var selected_row in selected_rows) { - // var selected_item_model = (ConversationItemModel) selected_row; + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + var selected_items_start_index = current_item_position; - // if (trash_threads[selected_item_model.service_uid] == null) { - // trash_threads[selected_item_model.service_uid] = new Gee.ArrayList (); - // } + while (bitset_iter.is_valid ()) { + var selected_item_model = (ConversationItemModel)selection_model.get_item (current_item_position); - // trash_threads[selected_item_model.service_uid].add (selected_item_model.node); - // } + if (trash_threads[selected_item_model.service_uid] == null) { + trash_threads[selected_item_model.service_uid] = new Gee.ArrayList (); + } - // var deleted = 0; - // foreach (var service_uid in trash_threads.keys) { - // deleted += move_handler.delete_threads (folders[service_uid], trash_threads[service_uid]); - // } + trash_threads[selected_item_model.service_uid].add (selected_item_model.node); + bitset_iter.next (out current_item_position); + } - // list_store.items_changed (0, 0, list_store.get_n_items ()); - // list_box.select_row_at_index (selected_rows_start_index + 1); + var deleted = 0; + foreach (var service_uid in trash_threads.keys) { + deleted += move_handler.delete_threads (folders[service_uid], trash_threads[service_uid]); + } - // return deleted; - // } + list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); + selection_model.select_item (selected_items_start_index, true); + + return deleted; + } public void undo_move () { move_handler.undo_last_move.begin ((obj, res) => { From 6a173fd9d08b716c17179af53f4de54f3f14f1cf Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 15:54:55 +0200 Subject: [PATCH 19/72] MessageList: use get_action, use window controls --- src/MessageList/MessageList.vala | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index 9227705c2..65ca53761 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -117,7 +117,8 @@ public class Mail.MessageList : Gtk.Box { ); headerbar = new Gtk.HeaderBar () { - // show_close_button = true + show_title_buttons = false, + title_widget = new Gtk.Label ("") }; headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); headerbar.pack_start (reply_button); @@ -127,6 +128,7 @@ public class Mail.MessageList : Gtk.Box { headerbar.pack_start (mark_button); headerbar.pack_start (archive_button); headerbar.pack_start (trash_button); + headerbar.pack_end (new Gtk.WindowControls (END)); headerbar.pack_end (app_menu); var settings = new GLib.Settings ("io.elementary.mail"); @@ -272,17 +274,17 @@ public class Mail.MessageList : Gtk.Box { } private void can_reply (bool enabled) { - unowned var main_window = (Gtk.ApplicationWindow) ((Gtk.Application) GLib.Application.get_default ()).active_window; - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_FORWARD)).set_enabled (enabled); - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_REPLY_ALL)).set_enabled (enabled); - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_REPLY)).set_enabled (enabled); + unowned var main_window = (MainWindow) get_root (); + main_window.get_action (MainWindow.ACTION_FORWARD).set_enabled (enabled); + main_window.get_action (MainWindow.ACTION_REPLY_ALL).set_enabled (enabled); + main_window.get_action (MainWindow.ACTION_REPLY).set_enabled (enabled); } private void can_move_thread (bool enabled) { - unowned var main_window = (Gtk.ApplicationWindow) ((Gtk.Application) GLib.Application.get_default ()).active_window; - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_ARCHIVE)).set_enabled (enabled); - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_MARK)).set_enabled (enabled); - ((SimpleAction) main_window.lookup_action (MainWindow.ACTION_MOVE_TO_TRASH)).set_enabled (enabled); + unowned var main_window = (MainWindow) get_root (); + main_window.get_action (MainWindow.ACTION_ARCHIVE).set_enabled (enabled); + main_window.get_action (MainWindow.ACTION_MARK).set_enabled (enabled); + main_window.get_action (MainWindow.ACTION_MOVE_TO_TRASH).set_enabled (enabled); } private static int message_sort_function (Gtk.ListBoxRow item1, Gtk.ListBoxRow item2) { From 3d2cefe82b40f4f69f99d9264f93bf0370c0702e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 16:02:31 +0200 Subject: [PATCH 20/72] AttachmentButton: refine --- src/MessageList/AttachmentButton.vala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/MessageList/AttachmentButton.vala b/src/MessageList/AttachmentButton.vala index e1dd69d14..b5f5aeb59 100644 --- a/src/MessageList/AttachmentButton.vala +++ b/src/MessageList/AttachmentButton.vala @@ -53,6 +53,11 @@ public class AttachmentButton : Gtk.FlowBoxChild { actions.add_action (save_as_action); insert_action_group (ACTION_GROUP_PREFIX, actions); + var gesture_primary_click = new Gtk.GestureClick () { + button = Gdk.BUTTON_PRIMARY + }; + add_controller (gesture_primary_click); + var gesture_secondary_click = new Gtk.GestureClick () { button = Gdk.BUTTON_SECONDARY }; @@ -62,7 +67,9 @@ public class AttachmentButton : Gtk.FlowBoxChild { context_menu_model.append (_("Open"), ACTION_PREFIX + ACTION_OPEN); context_menu_model.append (_("Save As…"), ACTION_PREFIX + ACTION_SAVE_AS); - var menu = new Gtk.PopoverMenu.from_model (context_menu_model); + var menu = new Gtk.PopoverMenu.from_model (context_menu_model) { + has_arrow = false + }; menu.set_parent (this); var grid = new Gtk.Grid () { @@ -118,6 +125,8 @@ public class AttachmentButton : Gtk.FlowBoxChild { grid.attach (size_label, 1, 1, 1, 1); set_child (grid); + gesture_primary_click.pressed.connect (() => activate ()); + gesture_secondary_click.pressed.connect ((n_press, x, y) => { var rect = Gdk.Rectangle () { x = (int) x, From 557405b43df9e0ccc64dd1dd6f59946100d957d5 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 16:21:10 +0200 Subject: [PATCH 21/72] MessageList: Cleanup + update mark menu --- src/MessageList/MessageList.vala | 72 +++++++++++++++++--------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index 65ca53761..3fbfe68fb 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -9,7 +9,7 @@ public class Mail.MessageList : Gtk.Box { public signal void hovering_over_link (string? label, string? uri); public Gtk.HeaderBar headerbar { get; private set; } - public GenericArray uids { get; private set; default = new GenericArray (); } + private Gtk.PopoverMenu mark_popover; private Gtk.ListBox list_box; private Gtk.ScrolledWindow scrolled_window; private Gee.HashMap messages; @@ -75,31 +75,15 @@ public class Mail.MessageList : Gtk.Box { _("Forward") ); - var mark_unread_item = new MenuItem (_("Mark as Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); - mark_unread_item.bind_property ("sensitive", mark_unread_item, "visible"); - - var mark_read_item = new MenuItem (_("Mark as Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); - mark_read_item.bind_property ("sensitive", mark_read_item, "visible"); - - var mark_star_item = new MenuItem (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); - mark_star_item.bind_property ("sensitive", mark_star_item, "visible"); - - var mark_unstar_item = new MenuItem (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); - mark_unstar_item.bind_property ("sensitive", mark_unstar_item, "visible"); - - var mark_menu = new Menu (); - mark_menu.append_item (mark_unread_item); - mark_menu.append_item (mark_read_item); - mark_menu.append_item (mark_star_item); - mark_menu.append_item (mark_unstar_item); - - var mark_button = new Gtk.MenuButton () { - // action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK, + var mark_button = new Gtk.Button () { + action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK, icon_name = "edit-mark", - menu_model = mark_menu, tooltip_text = _("Mark Conversation") }; + mark_popover = new Gtk.PopoverMenu.from_model (null); + mark_popover.set_parent (mark_button); + var archive_button = new Gtk.Button.from_icon_name ("mail-archive") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_ARCHIVE }; @@ -131,17 +115,6 @@ public class Mail.MessageList : Gtk.Box { headerbar.pack_end (new Gtk.WindowControls (END)); headerbar.pack_end (app_menu); - var settings = new GLib.Settings ("io.elementary.mail"); - settings.bind ("always-load-remote-images", load_images_menuitem, "active", SettingsBindFlags.DEFAULT); - - account_settings_menuitem.clicked.connect (() => { - try { - AppInfo.launch_default_for_uri ("settings://accounts/online", null); - } catch (Error e) { - warning ("Failed to open account settings: %s", e.message); - } - }); - var placeholder = new Gtk.Label (_("No Message Selected")) { visible = true }; @@ -172,6 +145,39 @@ public class Mail.MessageList : Gtk.Box { orientation = VERTICAL; append (headerbar); append (scrolled_window); + + var settings = new GLib.Settings ("io.elementary.mail"); + settings.bind ("always-load-remote-images", load_images_menuitem, "active", SettingsBindFlags.DEFAULT); + + account_settings_menuitem.clicked.connect (() => { + try { + AppInfo.launch_default_for_uri ("settings://accounts/online", null); + } catch (Error e) { + warning ("Failed to open account settings: %s", e.message); + } + }); + + mark_button.clicked.connect (create_context_menu); + } + + public void create_context_menu () { + unowned var main_window = (MainWindow) get_root (); + var mark_menu = new Menu (); + + if (main_window.get_action (MainWindow.ACTION_MARK_UNREAD).enabled) { + mark_menu.append (_("Mark as Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); + } else { + mark_menu.append (_("Mark as Read"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_READ); + } + + if (main_window.get_action (MainWindow.ACTION_MARK_STAR).enabled) { + mark_menu.append (_("Star"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_STAR); + } else { + mark_menu.append (_("Unstar"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNSTAR); + } + + mark_popover.set_menu_model (mark_menu); + mark_popover.popup (); } public void set_conversation (Camel.FolderThreadNode? node) { From 8e4366bc510f1d9d12f98474b6310959cc35638e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 16:28:21 +0200 Subject: [PATCH 22/72] ConversationListItem: Remove unsused variable --- src/ConversationList/ConversationListItem.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index cb2dde2c5..cf1386f69 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -21,7 +21,6 @@ public class Mail.ConversationListItem : Gtk.Box { public signal void secondary_click (double x, double y); - public ulong handler_id; private Gtk.Image status_icon; private Gtk.Label date; From 796d63a53f918ebc2c58b2001314415b20657a0e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 16:34:35 +0200 Subject: [PATCH 23/72] ConversationList: Cleanup --- src/ConversationList/ConversationList.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index ec81f4cb2..a8a80bab0 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -185,7 +185,7 @@ public class Mail.ConversationList : Gtk.Box { factory.setup.connect ((obj) => { var list_item = (Gtk.ListItem) obj; var conversation_list_item = new ConversationListItem (); - conversation_list_item.handler_id = conversation_list_item.secondary_click.connect ((x, y) => { + conversation_list_item.secondary_click.connect ((x, y) => { if (!selection_model.is_selected (list_item.get_position ())) { selection_model.select_item (list_item.get_position (), true); } From 949fd3147b768517ea46a6cb59dd7f830596e4dc Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Sun, 16 Apr 2023 16:39:17 +0200 Subject: [PATCH 24/72] WelcomeView: Update for gtk4 --- src/WelcomeView.vala | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/WelcomeView.vala b/src/WelcomeView.vala index 4a168a469..3250e1077 100644 --- a/src/WelcomeView.vala +++ b/src/WelcomeView.vala @@ -20,10 +20,8 @@ public class Mail.WelcomeView : Gtk.Box { construct { - var headerbar = new Hdy.HeaderBar () { - show_close_button = true - }; - headerbar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + var headerbar = new Gtk.HeaderBar (); + headerbar.add_css_class (Granite.STYLE_CLASS_FLAT); var welcome_icon = new Gtk.Image () { icon_name = "io.elementary.mail", @@ -32,7 +30,7 @@ public class Mail.WelcomeView : Gtk.Box { pixel_size = 64 }; - var welcome_badge = new Gtk.Image.from_icon_name ("preferences-desktop-online-accounts", Gtk.IconSize.DIALOG) { + var welcome_badge = new Gtk.Image.from_icon_name ("preferences-desktop-online-accounts") { halign = valign = Gtk.Align.END, }; @@ -47,24 +45,25 @@ public class Mail.WelcomeView : Gtk.Box { wrap = true, xalign = 0 }; - welcome_title.get_style_context ().add_class (Granite.STYLE_CLASS_H1_LABEL); + welcome_title.add_css_class (Granite.STYLE_CLASS_H1_LABEL); var welcome_description = new Gtk.Label (_("Mail uses email accounts configured in System Settings.")) { max_width_chars = 70, wrap = true, xalign = 0 }; - welcome_description.get_style_context ().add_class (Granite.STYLE_CLASS_H3_LABEL); + welcome_description.add_css_class (Granite.STYLE_CLASS_H3_LABEL); var welcome_button = new Gtk.Button.with_label (_("Online Accounts…")) { margin_top = 24 }; - welcome_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + welcome_button.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); var grid = new Gtk.Grid () { column_spacing = 12, halign = valign = Gtk.Align.CENTER, - expand = true + hexpand = true, + vexpand = true }; grid.attach (welcome_overlay, 0, 0, 1, 2); grid.attach (welcome_title, 1, 0); @@ -72,19 +71,19 @@ public class Mail.WelcomeView : Gtk.Box { grid.attach (welcome_button, 1, 2); var main_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); - main_box.add (headerbar); - main_box.add (grid); + main_box.append (headerbar); + main_box.append (grid); - var window_handle = new Hdy.WindowHandle () { - child = main_box + var window_handle = new Gtk.WindowHandle () { + child = main_box, + hexpand = vexpand = true }; - add (window_handle); - show_all (); + append (window_handle); welcome_button.clicked.connect (() => { try { - Gtk.show_uri_on_window ((Gtk.Window) get_toplevel (), "settings://accounts/online", Gdk.CURRENT_TIME); + Gtk.show_uri ((Gtk.Window) get_root (), "settings://accounts/online", Gdk.CURRENT_TIME); } catch (Error e) { critical (e.message); } From fceb060e7d6aa1b259d8d3e705416ba7c8b1e0fe Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 12:04:11 +0200 Subject: [PATCH 25/72] Application: Update for gtk4 --- src/Application.vala | 47 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index 9dedb9997..f5425257a 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -44,7 +44,6 @@ public class Mail.Application : Gtk.Application { string to = null; try { -#if HAS_SOUP_3 GLib.Uri? mailto= null; try { mailto = GLib.Uri.parse (mailto_uri, GLib.UriFlags.NONE); @@ -63,32 +62,12 @@ public class Mail.Application : Gtk.Application { to = GLib.Uri.unescape_string (mailto.get_path ()); if (main_window.is_session_started) { - new Composer (main_window, to, mailto.get_query ()).show_all (); + new Composer (main_window, to, mailto.get_query ()).present (); } else { main_window.session_started.connect (() => { - new Composer (main_window, to, mailto.get_query ()).show_all (); + new Composer (main_window, to, mailto.get_query ()).present (); }); } -#else - Soup.URI mailto = new Soup.URI (mailto_uri); - if (mailto == null) { - throw new OptionError.BAD_VALUE ("Argument is not a URL."); - } - - if (mailto.scheme != "mailto") { - throw new OptionError.BAD_VALUE ("Cannot open non-mailto: URL"); - } - - to = Soup.URI.decode (mailto.path); - - if (main_window.is_session_started) { - new Composer (main_window, to, mailto.query).show_all (); - } else { - main_window.session_started.connect (() => { - new Composer (main_window, to, mailto.query).show_all (); - }); - } -#endif } catch (OptionError e) { warning ("Argument parsing error. %s", e.message); } @@ -100,8 +79,6 @@ public class Mail.Application : Gtk.Application { protected override void startup () { base.startup (); - Hdy.init (); - var granite_settings = Granite.Settings.get_default (); var gtk_settings = Gtk.Settings.get_default (); @@ -113,7 +90,7 @@ public class Mail.Application : Gtk.Application { var css_provider = new Gtk.CssProvider (); css_provider.load_from_resource ("io/elementary/mail/application.css"); - Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); var quit_action = new SimpleAction ("quit", null); quit_action.activate.connect (() => { @@ -138,23 +115,21 @@ public class Mail.Application : Gtk.Application { var main_window = new MainWindow (this); add_window (main_window); - int window_x, window_y; - var rect = Gtk.Allocation (); + // int window_x, window_y; + // var rect = Gtk.Allocation (); - settings.get ("window-position", "(ii)", out window_x, out window_y); - settings.get ("window-size", "(ii)", out rect.width, out rect.height); + // settings.get ("window-position", "(ii)", out window_x, out window_y); + // settings.get ("window-size", "(ii)", out rect.width, out rect.height); - if (window_x != -1 || window_y != -1) { - main_window.move (window_x, window_y); - } + // if (window_x != -1 || window_y != -1) { + // main_window.move (window_x, window_y); + // } - main_window.set_allocation (rect); + // main_window.set_allocation (rect); if (settings.get_boolean ("window-maximized")) { main_window.maximize (); } - - main_window.show_all (); } active_window.present (); From 6cf81bb14ff55aa4d4e05b4f9acd139d2f9b7c8b Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 17:24:13 +0200 Subject: [PATCH 26/72] Update style classes --- src/FolderList/FolderList.vala | 2 ++ src/MessageList/MessageList.vala | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 3ad13785f..f216b8c1b 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -45,6 +45,7 @@ public class Mail.FolderList : Gtk.Box { application_instance.get_accels_for_action (compose_button.action_name), _("Compose new message") ); + compose_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); header_bar = new Gtk.HeaderBar (); header_bar.pack_end (compose_button); @@ -64,6 +65,7 @@ public class Mail.FolderList : Gtk.Box { orientation = VERTICAL; width_request = 100; + add_css_class (Granite.STYLE_CLASS_SIDEBAR); append (header_bar); append (scrolled_window); diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index 3fbfe68fb..3261cf093 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -15,7 +15,7 @@ public class Mail.MessageList : Gtk.Box { private Gee.HashMap messages; construct { - // add_css_class (Granite.STYLE_CLASS_BACKGROUND); + add_css_class (Granite.STYLE_CLASS_BACKGROUND); var application_instance = (Gtk.Application) GLib.Application.get_default (); @@ -47,6 +47,7 @@ public class Mail.MessageList : Gtk.Box { popover = app_menu_popover, tooltip_text = _("Menu") }; + app_menu.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); var reply_button = new Gtk.Button.from_icon_name ("mail-reply-sender") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REPLY, @@ -56,6 +57,7 @@ public class Mail.MessageList : Gtk.Box { application_instance.get_accels_for_action (reply_button.action_name + "::"), _("Reply") ); + reply_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); var reply_all_button = new Gtk.Button.from_icon_name ("mail-reply-all") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REPLY_ALL, @@ -65,6 +67,7 @@ public class Mail.MessageList : Gtk.Box { application_instance.get_accels_for_action (reply_all_button.action_name + "::"), _("Reply All") ); + reply_all_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); var forward_button = new Gtk.Button.from_icon_name ("mail-forward") { //Large toolbar action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_FORWARD, @@ -74,12 +77,14 @@ public class Mail.MessageList : Gtk.Box { application_instance.get_accels_for_action (forward_button.action_name + "::"), _("Forward") ); + forward_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); var mark_button = new Gtk.Button () { action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK, icon_name = "edit-mark", tooltip_text = _("Mark Conversation") }; + mark_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); mark_popover = new Gtk.PopoverMenu.from_model (null); mark_popover.set_parent (mark_button); @@ -91,14 +96,16 @@ public class Mail.MessageList : Gtk.Box { application_instance.get_accels_for_action (archive_button.action_name), _("Move conversations to archive") ); + archive_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); - var trash_button = new Gtk.Button.from_icon_name ("edit-delete") { //Large toolbar + var trash_button = new Gtk.Button.from_icon_name ("edit-delete") { action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH }; trash_button.tooltip_markup = Granite.markup_accel_tooltip ( application_instance.get_accels_for_action (trash_button.action_name), _("Move conversations to Trash") ); + trash_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); headerbar = new Gtk.HeaderBar () { show_title_buttons = false, @@ -126,8 +133,7 @@ public class Mail.MessageList : Gtk.Box { vexpand = true, selection_mode = NONE }; - - // list_box.add_css_class (Granite.STYLE_CLASS_BACKGROUND); + list_box.add_css_class (Granite.STYLE_CLASS_BACKGROUND); list_box.set_placeholder (placeholder); list_box.set_sort_func (message_sort_function); From 9299a256544f925f2c4a3f92a6f21e6955f4a469 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 17:39:06 +0200 Subject: [PATCH 27/72] FolderList: Styling updates, window controls --- src/FolderList/FolderList.vala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index f216b8c1b..5de98d9d2 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -47,7 +47,13 @@ public class Mail.FolderList : Gtk.Box { ); compose_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); - header_bar = new Gtk.HeaderBar (); + var window_controls = new Gtk.WindowControls (START); + + header_bar = new Gtk.HeaderBar () { + show_title_buttons = false, + title_widget = new Gtk.Label ("") + }; + header_bar.pack_start (window_controls); header_bar.pack_end (compose_button); header_bar.add_css_class (Granite.STYLE_CLASS_FLAT); From c7933fc9efa2038b3458763bd30af57bf11f0c06 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 17:52:32 +0200 Subject: [PATCH 28/72] Update attachment handling --- src/MessageList/AttachmentButton.vala | 7 ------- src/MessageList/MessageListItem.vala | 10 +++++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/MessageList/AttachmentButton.vala b/src/MessageList/AttachmentButton.vala index b5f5aeb59..a36b18b93 100644 --- a/src/MessageList/AttachmentButton.vala +++ b/src/MessageList/AttachmentButton.vala @@ -53,11 +53,6 @@ public class AttachmentButton : Gtk.FlowBoxChild { actions.add_action (save_as_action); insert_action_group (ACTION_GROUP_PREFIX, actions); - var gesture_primary_click = new Gtk.GestureClick () { - button = Gdk.BUTTON_PRIMARY - }; - add_controller (gesture_primary_click); - var gesture_secondary_click = new Gtk.GestureClick () { button = Gdk.BUTTON_SECONDARY }; @@ -125,8 +120,6 @@ public class AttachmentButton : Gtk.FlowBoxChild { grid.attach (size_label, 1, 1, 1, 1); set_child (grid); - gesture_primary_click.pressed.connect (() => activate ()); - gesture_secondary_click.pressed.connect ((n_press, x, y) => { var rect = Gdk.Rectangle () { x = (int) x, diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 1bc7f34e0..0ab33fcb7 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -314,11 +314,16 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { attachment_bar = new Gtk.FlowBox () { hexpand = true, - homogeneous = true + homogeneous = true, + activate_on_single_click = true }; attachment_bar.add_css_class (Granite.STYLE_CLASS_FLAT); attachment_bar.add_css_class ("bottom-toolbar"); secondary_box.append (attachment_bar); + + attachment_bar.child_activated.connect ((child) => { + show_attachment (((AttachmentButton)child).mime_part); + }); } set_child (base_box); @@ -334,7 +339,6 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { loading_cancellable.cancel (); }); - /* Connecting to clicked () doesn't allow us to prevent the event from propagating to header_event_box */ starred_button.clicked.connect (() => { if (Camel.MessageFlags.FLAGGED in (int) message_info.flags) { message_info.set_flags (Camel.MessageFlags.FLAGGED, 0); @@ -514,7 +518,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { yield handle_inline_mime (part); } else if (part.disposition == "attachment") { var button = new AttachmentButton (part, loading_cancellable); - button.activate.connect (() => show_attachment (button.mime_part)); + // button.activate.connect (() => show_attachment (button.mime_part)); attachment_bar.append (button); } if (field.type == "text") { From 0c284ee25162fc4112a7e036d60a7ec2fc00bad7 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 17:53:49 +0200 Subject: [PATCH 29/72] ConversationList: Remove unused variable --- src/ConversationList/ConversationList.vala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index a8a80bab0..acc6caefd 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -575,8 +575,6 @@ public class Mail.ConversationList : Gtk.Box { public int trash_selected_messages () { var trash_threads = new Gee.HashMap> (); - var previous_items = list_store.get_n_items (); - var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); From e30d28c3828f043e0c9b0ac9cb856a1fb0dc698f Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 17:58:35 +0200 Subject: [PATCH 30/72] ConversationList: Cleanup --- src/ConversationList/ConversationList.vala | 28 ++++------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index acc6caefd..5a09dae14 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -47,34 +47,13 @@ public class Mail.ConversationList : Gtk.Box { private uint mark_read_timeout_id = 0; - construct { - orientation = VERTICAL; - add_css_class (Granite.STYLE_CLASS_VIEW); - - conversations = new Gee.HashMap (); + construct {conversations = new Gee.HashMap (); folders = new Gee.HashMap (); folder_info_flags = new Gee.HashMap (); threads = new Gee.HashMap (); move_handler = new MoveHandler (); - // list_box = new VirtualizingListBox () { - // activate_on_single_click = true, - // model = list_store - // }; - // list_box.factory_func = (item, old_widget) => { - // ConversationListItem? row = null; - // if (old_widget != null) { - // row = old_widget as ConversationListItem; - // } else { - // row = new ConversationListItem (); - // } - - // row.assign ((ConversationItemModel)item); - // row.show_all (); - // return row; - // }; - var application_instance = (Gtk.Application) GLib.Application.get_default (); search_entry = new Gtk.SearchEntry () { @@ -172,6 +151,9 @@ public class Mail.ConversationList : Gtk.Box { conversation_action_bar.pack_start (refresh_stack); conversation_action_bar.add_css_class (Granite.STYLE_CLASS_FLAT); + orientation = VERTICAL; + add_css_class (Granite.STYLE_CLASS_VIEW); + append (search_header); append (scrolled_window); append (conversation_action_bar); @@ -230,8 +212,6 @@ public class Mail.ConversationList : Gtk.Box { }); } - // We call get_action_group() on the parent window, instead of on `this` directly, due to a - // bug with Gtk.Widget.get_action_group(). See https://gitlab.gnome.org/GNOME/gtk/issues/1396 var window = (MainWindow) get_root (); window.get_action (MainWindow.ACTION_MARK_READ).set_enabled (conversation_item.unread); window.get_action (MainWindow.ACTION_MARK_UNREAD).set_enabled (!conversation_item.unread); From 8fdda0f2adcafc1030242ffba7a50b18e6b51ec3 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 18:05:10 +0200 Subject: [PATCH 31/72] ConversationList: Reenable archiving --- src/ConversationList/ConversationList.vala | 86 +++++++++++----------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 5a09dae14..c34bca7cb 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -510,47 +510,51 @@ public class Mail.ConversationList : Gtk.Box { } } - // public async int archive_selected_messages () { - // var archive_threads = new Gee.HashMap> (); - - // var selected_rows = list_box.get_selected_rows (); - // int selected_rows_start_index = list_store.get_index_of (selected_rows.to_array ()[0]); - - // foreach (unowned var selected_row in selected_rows) { - // var selected_item_model = (ConversationItemModel) selected_row; - - // if (archive_threads[selected_item_model.service_uid] == null) { - // archive_threads[selected_item_model.service_uid] = new Gee.ArrayList (); - // } - - // archive_threads[selected_item_model.service_uid].add (selected_item_model.node); - // } - - // var archived = 0; - // foreach (var service_uid in archive_threads.keys) { - // archived += yield move_handler.archive_threads (folders[service_uid], archive_threads[service_uid]); - // } - - // if (archived > 0) { - // foreach (var service_uid in archive_threads.keys) { - // var threads = archive_threads[service_uid]; - - // foreach (unowned var thread in threads) { - // unowned var uid = thread.message.uid; - // var item = conversations[uid]; - // if (item != null) { - // conversations.unset (uid); - // list_store.remove (item); - // } - // } - // } - // } - - // list_store.items_changed (0, archived, list_store.get_n_items ()); - // list_box.select_row_at_index (selected_rows_start_index); - - // return archived; - // } + public async int archive_selected_messages () { + var archive_threads = new Gee.HashMap> (); + + var selected_items = selection_model.get_selection (); + uint current_item_position; + Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); + bitset_iter.init_first(selected_items, out current_item_position); + var selected_items_start_index = current_item_position; + + while (bitset_iter.is_valid ()) { + var selected_item_model = (ConversationItemModel)selection_model.get_item (current_item_position); + + if (archive_threads[selected_item_model.service_uid] == null) { + archive_threads[selected_item_model.service_uid] = new Gee.ArrayList (); + } + + archive_threads[selected_item_model.service_uid].add (selected_item_model.node); + bitset_iter.next (out current_item_position); + } + + var archived = 0; + foreach (var service_uid in archive_threads.keys) { + archived += yield move_handler.archive_threads (folders[service_uid], archive_threads[service_uid]); + } + + if (archived > 0) { + foreach (var service_uid in archive_threads.keys) { + var threads = archive_threads[service_uid]; + + foreach (unowned var thread in threads) { + unowned var uid = thread.message.uid; + var item = conversations[uid]; + if (item != null) { + conversations.unset (uid); + list_store.remove (item); + } + } + } + } + + list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); + selection_model.select_item (selected_items_start_index, true); + + return archived; + } public int trash_selected_messages () { var trash_threads = new Gee.HashMap> (); From b83530fb84f618bf1525e7d3f4ae2c6b63a360f8 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 18:05:33 +0200 Subject: [PATCH 32/72] ConversationList: Formatting --- src/ConversationList/ConversationList.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index c34bca7cb..0ccfacb23 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -519,7 +519,7 @@ public class Mail.ConversationList : Gtk.Box { bitset_iter.init_first(selected_items, out current_item_position); var selected_items_start_index = current_item_position; - while (bitset_iter.is_valid ()) { + while (bitset_iter.is_valid ()) { var selected_item_model = (ConversationItemModel)selection_model.get_item (current_item_position); if (archive_threads[selected_item_model.service_uid] == null) { From 64a0dde1e976a7a9c224a0b05204e72122c7825e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 20:11:35 +0200 Subject: [PATCH 33/72] Reenable insert link dialog --- src/Composer.vala | 12 ++++++------ src/Dialogs/InsertLinkDialog.vala | 17 ++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index 193efc4e5..9becd4aea 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -512,12 +512,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { } private async void ask_insert_link () { - // var selected_text = yield web_view.get_selected_text (); - // var insert_link_dialog = new InsertLinkDialog (selected_text) { - // transient_for = this - // }; - // insert_link_dialog.present (); - // insert_link_dialog.insert_link.connect ((url, title) => on_link_inserted (url, title, selected_text)); + var selected_text = yield web_view.get_selected_text (); + var insert_link_dialog = new InsertLinkDialog (selected_text) { + transient_for = this + }; + insert_link_dialog.present (); + insert_link_dialog.insert_link.connect ((url, title) => on_link_inserted (url, title, selected_text)); } private void on_link_inserted (string url, string title, string? selected_text) { diff --git a/src/Dialogs/InsertLinkDialog.vala b/src/Dialogs/InsertLinkDialog.vala index 0e9c898c5..7a9fda98b 100644 --- a/src/Dialogs/InsertLinkDialog.vala +++ b/src/Dialogs/InsertLinkDialog.vala @@ -40,13 +40,14 @@ public class InsertLinkDialog : Granite.Dialog { var title_entry = new Gtk.Entry (); title_entry.activates_default = true; title_entry.placeholder_text = _("Example Website"); - if (selected_text != "") { + if (selected_text != null && selected_text != "") { title_entry.text = selected_text; } var grid = new Gtk.Grid () { - margin = 12, - margin_top = 0 + margin_bottom = 12, + margin_start = 12, + margin_end = 12 }; grid.column_spacing = 6; grid.row_spacing = 6; @@ -54,21 +55,19 @@ public class InsertLinkDialog : Granite.Dialog { grid.attach (url_entry, 1, 0); grid.attach (title_label, 0, 1); grid.attach (title_entry, 1, 1); - grid.show_all (); - get_content_area ().add (grid); + get_content_area ().append (grid); add_button (_("Cancel"), Gtk.ResponseType.CANCEL); var insert_button = add_button (_("Insert Link"), Gtk.ResponseType.APPLY); - insert_button.can_default = true; - insert_button.has_default = true; + // insert_button.can_default = true; + // insert_button.has_default = true; insert_button.sensitive = false; - insert_button.get_style_context ().add_class (Gtk.STYLE_CLASS_SUGGESTED_ACTION); + insert_button.add_css_class (Granite.STYLE_CLASS_SUGGESTED_ACTION); deletable = false; modal = true; - skip_taskbar_hint = true; url_entry.changed.connect (() => { bool is_valid = false; From dc6c3280943e5f83e73164ecc01e2e2e41992403 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 20:12:23 +0200 Subject: [PATCH 34/72] MessageList: Cleanup --- src/MessageList/MessageList.vala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index 852f1e610..fbf74962e 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -142,11 +142,11 @@ public class Mail.MessageList : Gtk.Box { }; scrolled_window.set_child (list_box); - // Prevent the focus of the webview causing the ScrolledWindow to scroll - // var scrolled_child = scrolled_window.get_child (); - // if (scrolled_child is Gtk.Viewport) { - // ((Gtk.Viewport) scrolled_child).set_focus_vadjustment (new Gtk.Adjustment (0, 0, 0, 0, 0, 0)); - // } + // Prevent the focus of the webview causing the ScrolledWindow to scroll @TODO: correct replacement? + var scrolled_child = scrolled_window.get_child (); + if (scrolled_child is Gtk.Viewport) { + ((Gtk.Viewport) scrolled_child).scroll_to_focus = true; + } orientation = VERTICAL; append (headerbar); From 4522f5c3d3c79ffb03464ac514f63c5405520b12 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 20:27:32 +0200 Subject: [PATCH 35/72] Composer: Fix seg fault --- src/Composer.vala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index 9becd4aea..2fb48ef34 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -605,12 +605,12 @@ public class Mail.Composer : Gtk.ApplicationWindow { message_content = content_to_quote; unowned var to = message.get_recipients (Camel.RECIPIENT_TYPE_TO); - if (to != null) { + if (to.format () != null) { to_val.text = to.format (); } unowned var cc = message.get_recipients (Camel.RECIPIENT_TYPE_CC); - if (cc != null) { + if (cc.format () != null) { cc_val.text = cc.format (); if (cc_val.text.length > 0) { @@ -619,7 +619,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { } unowned var bcc = message.get_recipients (Camel.RECIPIENT_TYPE_BCC); - if (bcc != null) { + if (bcc.format () != null) { bcc_val.text = bcc.format (); if (bcc_val.text.length > 0) { @@ -631,7 +631,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { string date_format = _("%a, %b %-e, %Y at %-l:%M %p"); if (type == Type.REPLY || type == Type.REPLY_ALL) { var reply_to = message.get_reply_to (); - if (reply_to != null) { + if (reply_to.format () != null) { to_val.text = reply_to.format (); } else { to_val.text = message.get_from ().format (); From c21086d906c258fc1aaabc7e5af4df6fa6691812 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 20:49:35 +0200 Subject: [PATCH 36/72] Composer: Update attachment handling and reenable file chooser --- src/Composer.vala | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index 2fb48ef34..2bfdc1d82 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -458,12 +458,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { foreach (var path in result["attachment"]) { var file = path.has_prefix ("file://") ? File.new_for_uri (path) : File.new_for_path (path); - var attachment = new Attachment (file) { - margin_top = 3, - margin_bottom = 3, - margin_start = 3, - margin_end = 3 - }; + var attachment = new Attachment (file); + attachment.remove.connect (() => attachment_box.remove (attachment)); attachment_box.append (attachment); } @@ -494,17 +490,21 @@ public class Mail.Composer : Gtk.ApplicationWindow { _("Cancel") ); - // if (filechooser.run () == Gtk.ResponseType.ACCEPT) { - // filechooser.hide (); - // foreach (unowned File file in filechooser.get_files ()) { - // var attachment = new Attachment (file); - // attachment.margin = 3; + filechooser.response.connect ((response) => { + filechooser.hide (); + if (response == Gtk.ResponseType.ACCEPT) { + var files = filechooser.get_files (); + for (int i = 0; files.get_item (i) != null; i++) { + var attachment = new Attachment ((File)files.get_item (i)); + attachment.remove.connect (() => attachment_box.remove (attachment)); - // attachment_box.append (attachment); - // } - // } + attachment_box.append (attachment); + } + } + filechooser.destroy (); + }); - // filechooser.destroy (); + filechooser.show (); } private void on_insert_link_clicked () { @@ -890,6 +890,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { } private class Attachment : Gtk.FlowBoxChild { + public signal void remove (); + public GLib.FileInfo? info { private get; construct; } public GLib.File file { get; construct; } @@ -940,10 +942,15 @@ public class Mail.Composer : Gtk.ApplicationWindow { box.append (size_label); box.append (remove_button); + margin_top = 3; + margin_bottom = 3; + margin_start = 3; + margin_end = 3; + set_child (box); remove_button.clicked.connect (() => { - destroy (); + remove (); }); } From 33e4644980e5af1b91235087f38cc959b3de58c3 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 20:56:21 +0200 Subject: [PATCH 37/72] AttachmentButton: Reenable filechooser --- src/MessageList/AttachmentButton.vala | 36 ++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/MessageList/AttachmentButton.vala b/src/MessageList/AttachmentButton.vala index a36b18b93..c6a8cdf99 100644 --- a/src/MessageList/AttachmentButton.vala +++ b/src/MessageList/AttachmentButton.vala @@ -131,23 +131,25 @@ public class AttachmentButton : Gtk.FlowBoxChild { } private void on_save_as () { - // Gtk.Window? parent_window = (Gtk.Window) get_root (); - // var chooser = new Gtk.FileChooserNative ( - // null, - // parent_window, - // Gtk.FileChooserAction.SAVE, - // _("Save"), - // _("Cancel") - // ); - - // chooser.set_current_name (mime_part.get_filename ()); - // chooser.do_overwrite_confirmation = true; - - // if (chooser.run () == Gtk.ResponseType.ACCEPT) { - // write_to_file.begin (chooser.get_file ()); - // } - - // chooser.destroy (); + Gtk.Window? parent_window = (Gtk.Window) get_root (); + var chooser = new Gtk.FileChooserNative ( + null, + parent_window, + Gtk.FileChooserAction.SAVE, + _("Save"), + _("Cancel") + ); + + chooser.set_current_name (mime_part.get_filename ()); + + chooser.response.connect ((response) => { + if (response == Gtk.ResponseType.ACCEPT) { + write_to_file.begin (chooser.get_file ()); + } + chooser.destroy (); + }); + + chooser.show (); } private async void write_to_file (GLib.File file) { From 99ce7fb2a4153b927242a5f460ceef5b237ee30b Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 17 Apr 2023 21:08:31 +0200 Subject: [PATCH 38/72] MessageListItem: Update infobar --- src/MessageList/MessageListItem.vala | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 0ab33fcb7..42af28aab 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -261,21 +261,18 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { settings = new GLib.Settings ("io.elementary.mail"); - blocked_images_infobar = new Gtk.InfoBar () { + blocked_images_infobar = new Gtk.InfoBar () { //@TODO replacement: new styleclass? margin_top = 12, margin_bottom = 12, margin_start = 12, margin_end = 12, - message_type = WARNING + message_type = WARNING, + revealed = false }; - blocked_images_infobar.add_button (_("Show Images"), 1); + blocked_images_infobar.add_child (new Gtk.Label (_("This message contains remote images.")) { ellipsize = END }); //@TODO: Not so sure here + blocked_images_infobar.add_button (_("Show Images"), 1); // Vertical content area doesn't work anymore blocked_images_infobar.add_button (_("Always Show from Sender"), 2); - // blocked_images_infobar.add_css_class (Granite.STYLE_CLASS_FRAME); - - // var infobar_content = blocked_images_infobar.get_content_area (); - // infobar_content.append (new Gtk.Label (_("This message contains remote images."))); - - // ((Gtk.Box) blocked_images_infobar.get_action_area ()).orientation = Gtk.Orientation.VERTICAL; + blocked_images_infobar.add_css_class (Granite.STYLE_CLASS_FRAME); web_view = new Mail.WebView () { margin_top = 12, @@ -352,7 +349,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { }); web_view.image_load_blocked.connect (() => { - blocked_images_infobar.show (); + blocked_images_infobar.revealed = true; }); web_view.link_activated.connect ((uri) => { try { @@ -472,7 +469,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { } web_view.load_images (); - blocked_images_infobar.destroy (); + blocked_images_infobar.revealed = false; }); } From cac0cbfb62f0a057f6ea2f7ffae79746efc2f52a Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 14:32:15 +0200 Subject: [PATCH 39/72] FolderList: Remember expanded folders --- src/FolderList/AccountItemModel.vala | 6 +++- src/FolderList/FolderItemModel.vala | 6 +++- src/FolderList/FolderList.vala | 41 ++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 07450b1d6..9ac54749a 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -20,7 +20,7 @@ * Authored by: Corentin Noël */ -public class Mail.AccountItemModel : Object { +public class Mail.AccountItemModel : ItemModel, Object { public signal void loaded (); public string name; @@ -53,6 +53,10 @@ public class Mail.AccountItemModel : Object { connect_cancellable.cancel (); } + public string get_account_uid () { + return account.service.uid; + } + public async void load () { try { var folderinfo = yield offlinestore.get_folder_info (null, Camel.StoreGetFolderInfoFlags.RECURSIVE, GLib.Priority.DEFAULT, connect_cancellable); diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index 6cdcf685b..ecde296bd 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -20,7 +20,7 @@ * Authored by: Corentin Noël */ -public class Mail.FolderItemModel : Object { +public class Mail.FolderItemModel : ItemModel, Object { public string icon_name { get; construct; } public string name { get; construct; } public int unread { get; construct; } @@ -82,5 +82,9 @@ public class Mail.FolderItemModel : Object { break; } } + + public string get_account_uid () { + return account.service.uid; + } } diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 5de98d9d2..45f5d83db 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -80,8 +80,7 @@ public class Mail.FolderList : Gtk.Box { var tree_expander = new Gtk.TreeExpander () { child = new FolderListItem (), - indent_for_icon = false, - // indent_for_depth = false + // indent_for_icon = false }; list_item.child = tree_expander; @@ -91,13 +90,41 @@ public class Mail.FolderList : Gtk.Box { var list_item = (Gtk.ListItem) obj; var expander = (Gtk.TreeExpander) list_item.child; - expander.list_row = tree_list.get_row (list_item.get_position()); + var list_row = expander.list_row = tree_list.get_row (list_item.get_position()); + + var item = (ItemModel) expander.list_row.item; + + var account_settings = new GLib.Settings.with_path ("io.elementary.mail.accounts", "/io/elementary/mail/accounts/%s/".printf (item.get_account_uid ())); - var item = expander.item; if (item is AccountItemModel) { ((FolderListItem)expander.child).bind_account ((AccountItemModel)item); + account_settings.bind ("expanded", list_row, "expanded", SettingsBindFlags.DEFAULT | SettingsBindFlags.GET_NO_CHANGES); } else if (item is FolderItemModel) { - ((FolderListItem)expander.child).bind_folder ((FolderItemModel)item); + var folder_item = (FolderItemModel)item; + + ((FolderListItem)expander.child).bind_folder (folder_item); + + if (folder_item.full_name in account_settings.get_strv ("expanded-folders")) { + list_row.expanded = true; + } + + list_row.notify["expanded"].connect (() => { + var folders = account_settings.get_strv ("expanded-folders"); + if (list_row.expanded) { + folders += folder_item.full_name; + } else { + string[] new_folders = {}; + foreach (var folder in folders) { + if (folder != folder_item.full_name) { + new_folders += folder; + } + } + + folders = new_folders; + } + + account_settings.set_strv ("expanded-folders", folders); + }); } }); @@ -137,3 +164,7 @@ public class Mail.FolderList : Gtk.Box { root_model.append (account_item); } } + +public interface ItemModel : Object { + public abstract string get_account_uid (); +} From 45a531f1e2db3bbc4b6adb2c1ef63a19d99ac0b0 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 15:00:54 +0200 Subject: [PATCH 40/72] FolderList: Remember selected folder --- src/FolderList/FolderList.vala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 45f5d83db..57ac1dd3b 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -29,6 +29,7 @@ public class Mail.FolderList : Gtk.Box { private Gtk.TreeListModel tree_list; private Gtk.SingleSelection selection_model; private static GLib.Settings settings; + private bool already_selected; static construct { settings = new GLib.Settings ("io.elementary.mail"); @@ -90,7 +91,7 @@ public class Mail.FolderList : Gtk.Box { var list_item = (Gtk.ListItem) obj; var expander = (Gtk.TreeExpander) list_item.child; - var list_row = expander.list_row = tree_list.get_row (list_item.get_position()); + var list_row = expander.list_row = tree_list.get_row (list_item.position); var item = (ItemModel) expander.list_row.item; @@ -104,6 +105,15 @@ public class Mail.FolderList : Gtk.Box { ((FolderListItem)expander.child).bind_folder (folder_item); + if (!already_selected) { + string selected_folder_uid, selected_folder_full_name; + settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_full_name); + if (folder_item.get_account_uid () == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { + selection_model.set_selected (list_item.position); + already_selected = true; + } + } + if (folder_item.full_name in account_settings.get_strv ("expanded-folders")) { list_row.expanded = true; } @@ -145,7 +155,7 @@ public class Mail.FolderList : Gtk.Box { folder_name_per_account_uid.set (item.account, item.full_name); folder_selected (folder_name_per_account_uid.read_only_view); - // settings.set ("selected-folder", "(ss)", folder_info.store.uid, folder_info.folder_info.full_name); + settings.set ("selected-folder", "(ss)", item.get_account_uid (), item.full_name); } }); } From 1ea82538ee52dd0f6cde508c046221628724db11 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 15:09:35 +0200 Subject: [PATCH 41/72] FolderList: Cleanup --- src/FolderList/AccountItemModel.vala | 9 ++++----- src/FolderList/FolderItemModel.vala | 6 ++---- src/FolderList/FolderList.vala | 8 ++++---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 9ac54749a..199ac3eef 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -27,12 +27,15 @@ public class Mail.AccountItemModel : ItemModel, Object { public ListStore folder_list; public Mail.Backend.Account account { get; construct; } + public string account_uid { get; construct; } private GLib.Cancellable connect_cancellable; private unowned Camel.OfflineStore offlinestore; public AccountItemModel (Mail.Backend.Account account) { - Object (account: account); + Object (account: account, + account_uid: account.service.uid + ); } construct { @@ -53,10 +56,6 @@ public class Mail.AccountItemModel : ItemModel, Object { connect_cancellable.cancel (); } - public string get_account_uid () { - return account.service.uid; - } - public async void load () { try { var folderinfo = yield offlinestore.get_folder_info (null, Camel.StoreGetFolderInfoFlags.RECURSIVE, GLib.Priority.DEFAULT, connect_cancellable); diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index ecde296bd..d9df4622c 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -26,6 +26,7 @@ public class Mail.FolderItemModel : ItemModel, Object { public int unread { get; construct; } public Mail.Backend.Account account { get; construct; } + public string account_uid { get; construct; } public string full_name { get; construct; } public Camel.FolderInfo folder_info { get; construct; } @@ -33,6 +34,7 @@ public class Mail.FolderItemModel : ItemModel, Object { public FolderItemModel (Camel.FolderInfo folderinfo, Mail.Backend.Account account) { Object (account: account, + account_uid: account.service.uid, folder_info: folderinfo ); } @@ -82,9 +84,5 @@ public class Mail.FolderItemModel : ItemModel, Object { break; } } - - public string get_account_uid () { - return account.service.uid; - } } diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 57ac1dd3b..3627cc651 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -95,7 +95,7 @@ public class Mail.FolderList : Gtk.Box { var item = (ItemModel) expander.list_row.item; - var account_settings = new GLib.Settings.with_path ("io.elementary.mail.accounts", "/io/elementary/mail/accounts/%s/".printf (item.get_account_uid ())); + var account_settings = new GLib.Settings.with_path ("io.elementary.mail.accounts", "/io/elementary/mail/accounts/%s/".printf (item.account_uid)); if (item is AccountItemModel) { ((FolderListItem)expander.child).bind_account ((AccountItemModel)item); @@ -108,7 +108,7 @@ public class Mail.FolderList : Gtk.Box { if (!already_selected) { string selected_folder_uid, selected_folder_full_name; settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_full_name); - if (folder_item.get_account_uid () == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { + if (folder_item.account_uid == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { selection_model.set_selected (list_item.position); already_selected = true; } @@ -155,7 +155,7 @@ public class Mail.FolderList : Gtk.Box { folder_name_per_account_uid.set (item.account, item.full_name); folder_selected (folder_name_per_account_uid.read_only_view); - settings.set ("selected-folder", "(ss)", item.get_account_uid (), item.full_name); + settings.set ("selected-folder", "(ss)", item.account_uid, item.full_name); } }); } @@ -176,5 +176,5 @@ public class Mail.FolderList : Gtk.Box { } public interface ItemModel : Object { - public abstract string get_account_uid (); + public abstract string account_uid { get; construct; } } From 6614123999bc8a8d137869360f09e4af3e3e1947 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 16:05:01 +0200 Subject: [PATCH 42/72] FolderList: Reimplement refresh context menu --- src/FolderList/FolderItemModel.vala | 10 ++++++ src/FolderList/FolderListItem.vala | 54 +++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index d9df4622c..178f46a54 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -84,5 +84,15 @@ public class Mail.FolderItemModel : ItemModel, Object { break; } } + + public async void refresh () { + var offlinestore = (Camel.Store) account.service; + try { + var folder = yield offlinestore.get_folder (full_name, 0, GLib.Priority.DEFAULT, null); + yield folder.refresh_info (GLib.Priority.DEFAULT, null); + } catch (Error e) { + critical (e.message); + } + } } diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala index b41f3c3d6..f281c1686 100644 --- a/src/FolderList/FolderListItem.vala +++ b/src/FolderList/FolderListItem.vala @@ -2,24 +2,74 @@ public class FolderListItem : Gtk.Box { private Gtk.Image image; private Gtk.Label label; + private Mail.FolderItemModel? folder_item = null; + + private const string ACTION_GROUP_PREFIX = "folderlistitem"; + private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; + private const string ACTION_REFRESH = "refresh"; + construct { - orientation = HORIZONTAL; + var refresh_action = new SimpleAction (ACTION_REFRESH, null); + refresh_action.activate.connect (on_refresh); + + var actions = new SimpleActionGroup (); + actions.add_action (refresh_action); + insert_action_group (ACTION_GROUP_PREFIX, actions); + + var gesture_secondary_click = new Gtk.GestureClick () { + button = Gdk.BUTTON_SECONDARY + }; + add_controller (gesture_secondary_click); + + var context_menu_model = new Menu (); + context_menu_model.append (_("Refresh"), ACTION_PREFIX + ACTION_REFRESH); + + var menu = new Gtk.PopoverMenu.from_model (context_menu_model) { + has_arrow = false + }; + menu.set_parent (this); image = new Gtk.Image (); - label = new Gtk.Label (""); + label = new Gtk.Label ("") { + margin_start = 3 + }; + + hexpand = true; + vexpand = true; + orientation = HORIZONTAL; append (image); append (label); + + gesture_secondary_click.pressed.connect ((n_press, x, y) => { + if (folder_item != null) { + var rect = Gdk.Rectangle () { + x = (int) x, + y = (int) y + }; + menu.pointing_to = rect; + menu.popup (); + } + }); } public void bind_account (Mail.AccountItemModel item_model) { + folder_item = null; + image.set_from_icon_name (""); label.label = item_model.name; } public void bind_folder (Mail.FolderItemModel item_model) { + folder_item = item_model; + image.set_from_icon_name (item_model.icon_name); label.label = item_model.name; } + private void on_refresh () { + if (folder_item != null) { + folder_item.refresh.begin (); + } + } } From 4d8c8fbe7ce2f2cc1854dade2cff5f9415c0073e Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 18:05:02 +0200 Subject: [PATCH 43/72] FolderList: Reimplement grouped folders --- src/FolderList/AccountItemModel.vala | 19 +++++------ src/FolderList/FolderItemModel.vala | 15 +++----- src/FolderList/FolderList.vala | 51 +++++++++++++++++++++------- src/FolderList/FolderListItem.vala | 13 +++---- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 199ac3eef..555902279 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -20,30 +20,27 @@ * Authored by: Corentin Noël */ -public class Mail.AccountItemModel : ItemModel, Object { +public class Mail.AccountItemModel : ItemModel { public signal void loaded (); - public string name; - public ListStore folder_list; - public Mail.Backend.Account account { get; construct; } - public string account_uid { get; construct; } private GLib.Cancellable connect_cancellable; private unowned Camel.OfflineStore offlinestore; public AccountItemModel (Mail.Backend.Account account) { - Object (account: account, - account_uid: account.service.uid - ); + Object (account: account); } construct { - connect_cancellable = new GLib.Cancellable (); - folder_list = new ListStore (typeof(FolderItemModel)); - offlinestore = (Camel.OfflineStore) account.service; + + icon_name = ""; name = offlinestore.display_name; + account_uid = offlinestore.uid; + folder_list = new ListStore (typeof(FolderItemModel)); + + connect_cancellable = new GLib.Cancellable (); unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); network_monitor.network_changed.connect (() =>{ diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index 178f46a54..54477cdbe 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -20,32 +20,27 @@ * Authored by: Corentin Noël */ -public class Mail.FolderItemModel : ItemModel, Object { - public string icon_name { get; construct; } - public string name { get; construct; } +public class Mail.FolderItemModel : ItemModel { public int unread { get; construct; } public Mail.Backend.Account account { get; construct; } - public string account_uid { get; construct; } - public string full_name { get; construct; } public Camel.FolderInfo folder_info { get; construct; } - - public ListStore folder_list; + public string full_name { get; construct; } public FolderItemModel (Camel.FolderInfo folderinfo, Mail.Backend.Account account) { Object (account: account, - account_uid: account.service.uid, folder_info: folderinfo ); } construct { name = folder_info.display_name; + account_uid = account.service.uid; + folder_list = new ListStore (typeof(FolderItemModel)); + unread = folder_info.unread; full_name = folder_info.full_name; - folder_list = new ListStore (typeof(FolderItemModel)); - if (folder_info.child != null) { var current_folder_info = folder_info.child; while (current_folder_info != null) { diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 3627cc651..8fa12f9e5 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -25,11 +25,13 @@ public class Mail.FolderList : Gtk.Box { public Gtk.HeaderBar header_bar; - private ListStore root_model; - private Gtk.TreeListModel tree_list; - private Gtk.SingleSelection selection_model; private static GLib.Settings settings; - private bool already_selected; + + private ListStore root_model; + private Mail.Backend.Session session; + private SessionItemModel? session_item = null; + + private bool already_selected = false; static construct { settings = new GLib.Settings ("io.elementary.mail"); @@ -58,9 +60,9 @@ public class Mail.FolderList : Gtk.Box { header_bar.pack_end (compose_button); header_bar.add_css_class (Granite.STYLE_CLASS_FLAT); - root_model = new ListStore (typeof(AccountItemModel)); - tree_list = new Gtk.TreeListModel (root_model, false, false, create_folder_list_func); - selection_model = new Gtk.SingleSelection (tree_list); + root_model = new ListStore (typeof(ItemModel)); + var tree_list = new Gtk.TreeListModel (root_model, false, false, create_folder_list_func); + var selection_model = new Gtk.SingleSelection (tree_list); var list_factory = new Gtk.SignalListItemFactory (); var folder_list_view = new Gtk.ListView (selection_model, list_factory); @@ -98,12 +100,12 @@ public class Mail.FolderList : Gtk.Box { var account_settings = new GLib.Settings.with_path ("io.elementary.mail.accounts", "/io/elementary/mail/accounts/%s/".printf (item.account_uid)); if (item is AccountItemModel) { - ((FolderListItem)expander.child).bind_account ((AccountItemModel)item); + ((FolderListItem)expander.child).bind (item); account_settings.bind ("expanded", list_row, "expanded", SettingsBindFlags.DEFAULT | SettingsBindFlags.GET_NO_CHANGES); } else if (item is FolderItemModel) { var folder_item = (FolderItemModel)item; - ((FolderListItem)expander.child).bind_folder (folder_item); + ((FolderListItem)expander.child).bind (folder_item); if (!already_selected) { string selected_folder_uid, selected_folder_full_name; @@ -135,10 +137,17 @@ public class Mail.FolderList : Gtk.Box { account_settings.set_strv ("expanded-folders", folders); }); + } else if (item is SessionItemModel) { + ((FolderListItem)expander.child).bind (item); + // list_row.expanded = true; @TODO: causes snapshot warning ? + } else if (item is GroupedFolderItemModel) { + ((FolderListItem)expander.child).bind (item); } }); - var session = Mail.Backend.Session.get_default (); + session_item = new SessionItemModel (); + + session = Mail.Backend.Session.get_default (); session.get_accounts ().foreach ((account) => { add_account (account); @@ -156,6 +165,10 @@ public class Mail.FolderList : Gtk.Box { folder_selected (folder_name_per_account_uid.read_only_view); settings.set ("selected-folder", "(ss)", item.account_uid, item.full_name); + } else if (item is GroupedFolderItemModel) { + folder_selected (item.get_folder_full_name_per_account ()); + + settings.set ("selected-folder", "(ss)", item.account_uid, "GROUPED"); } }); } @@ -165,16 +178,30 @@ public class Mail.FolderList : Gtk.Box { return item.folder_list; } else if (item is FolderItemModel) { return item.folder_list; + } else if (item is SessionItemModel) { + return item.folder_list; + } else if (item is GroupedFolderItemModel) { + return null; } return null; } private void add_account (Mail.Backend.Account account) { + if (session.get_accounts ().size > 1) { + if (!(root_model.get_item (0) is SessionItemModel)) { + root_model.insert (0, session_item); + } + } + session_item.add_account (account); + var account_item = new AccountItemModel (account); root_model.append (account_item); } } -public interface ItemModel : Object { - public abstract string account_uid { get; construct; } +public class ItemModel : Object { + public string account_uid { get; protected set; } + public string icon_name { get; protected set; } + public string name { get; protected set; } + public ListStore? folder_list { get; protected set; default = null; } } diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala index f281c1686..fbad21dd4 100644 --- a/src/FolderList/FolderListItem.vala +++ b/src/FolderList/FolderListItem.vala @@ -53,18 +53,15 @@ public class FolderListItem : Gtk.Box { }); } - public void bind_account (Mail.AccountItemModel item_model) { + public void bind (ItemModel item_model) { folder_item = null; - image.set_from_icon_name (""); - label.label = item_model.name; - } - - public void bind_folder (Mail.FolderItemModel item_model) { - folder_item = item_model; - image.set_from_icon_name (item_model.icon_name); label.label = item_model.name; + + if (item_model is Mail.FolderItemModel) { + folder_item = (Mail.FolderItemModel)item_model; + } } private void on_refresh () { From 7182e9c1bc88769c29bb64bb47220e21e0886896 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 18:06:02 +0200 Subject: [PATCH 44/72] FolderList: More reimplement grouped folders --- src/FolderList/GroupedFolderItemModel.vala | 154 +++++++++++++++++++++ src/FolderList/SessionItemModel.vala | 45 ++++++ 2 files changed, 199 insertions(+) create mode 100644 src/FolderList/GroupedFolderItemModel.vala create mode 100644 src/FolderList/SessionItemModel.vala diff --git a/src/FolderList/GroupedFolderItemModel.vala b/src/FolderList/GroupedFolderItemModel.vala new file mode 100644 index 000000000..d5b272580 --- /dev/null +++ b/src/FolderList/GroupedFolderItemModel.vala @@ -0,0 +1,154 @@ +// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- +/*- + * Copyright (c) 2017 elementary LLC. (https://elementary.io) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authored by: Corentin Noël + */ + +public class Mail.GroupedFolderItemModel : ItemModel { + public int unread { get; set; } + public Camel.FolderInfoFlags folder_type { get; construct; } + + private Gee.HashMap account_folderinfo; + + public GroupedFolderItemModel (Camel.FolderInfoFlags folder_type) { + Object (folder_type: folder_type); + } + + construct { + account_uid = "UNIFIED ACCOUNT"; + account_folderinfo = new Gee.HashMap (); + + switch (folder_type & Camel.FOLDER_TYPE_MASK) { + case Camel.FolderInfoFlags.TYPE_INBOX: + name = _("Inbox"); + icon_name = "mail-inbox"; + break; + case Camel.FolderInfoFlags.TYPE_ARCHIVE: + name = _("Archive"); + icon_name = ("mail-archive"); + break; + case Camel.FolderInfoFlags.TYPE_SENT: + name = _("Sent"); + icon_name = "mail-sent"; + break; + default: + name = "%i".printf (folder_type & Camel.FOLDER_TYPE_MASK); + icon_name = "folder"; + warning ("Unknown grouped folder type: %s", name); + break; + } + } + + public Gee.Map get_folder_full_name_per_account () { + var folder_full_name_per_account = new Gee.HashMap (); + lock (account_folderinfo) { + foreach (var entry in account_folderinfo) { + if (entry.value != null) { + folder_full_name_per_account.set (entry.key, entry.value.full_name); + } else { + folder_full_name_per_account.set (entry.key, null); + } + } + } + + return folder_full_name_per_account.read_only_view; + } + + public void add_account (Mail.Backend.Account account) { + lock (account_folderinfo) { + account_folderinfo.set (account, null); + } + load_folder_info.begin (account); + } + + private async void load_folder_info (Mail.Backend.Account account) { + var offlinestore = (Camel.OfflineStore) account.service; + var full_name = build_folder_full_name (account); + Camel.FolderInfo? folderinfo = null; + + if (full_name != null) { + try { + folderinfo = yield offlinestore.get_folder_info (full_name, 0, GLib.Priority.DEFAULT, null); + } catch (Error e) { + // We can cancel the operation + if (!(e is GLib.IOError.CANCELLED)) { + warning ("Unable to fetch %s of account '%s': %s", full_name, account.service.display_name, e.message); + } + } + } + + lock (account_folderinfo) { + account_folderinfo.set (account, folderinfo); + } + update_infos (); + } + + private void update_infos () { + var total_unread = 0; + lock (account_folderinfo) { + foreach (var entry in account_folderinfo) { + if (entry.value == null) { + continue; + } + total_unread += entry.value.unread; + } + } + unread = total_unread; + } + + private string? build_folder_full_name (Backend.Account account) { + var session = Mail.Backend.Session.get_default (); + var service_source = session.ref_source (account.service.uid); + if (service_source == null || !service_source.has_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT)) { + return null; + } + + var mail_account_extension = (E.SourceMailAccount) service_source.get_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT); + if (Camel.FolderInfoFlags.TYPE_INBOX == (folder_type & Camel.FOLDER_TYPE_MASK)) { + if ("ews".ascii_casecmp (mail_account_extension.backend_name) == 0) { + return "Inbox"; + } + return "INBOX"; + } + + if (Camel.FolderInfoFlags.TYPE_ARCHIVE == (folder_type & Camel.FOLDER_TYPE_MASK)) { + return Utils.strip_folder_full_name (account.service.uid, mail_account_extension.dup_archive_folder ()); + } + + var identity_uid = mail_account_extension.dup_identity_uid (); + var identity_source = session.ref_source (identity_uid); + + if ( + Camel.FolderInfoFlags.TYPE_SENT == (folder_type & Camel.FOLDER_TYPE_MASK) + && + identity_source.has_extension (E.SOURCE_EXTENSION_MAIL_SUBMISSION) + ) { + unowned var mail_submission_extension = (E.SourceMailSubmission) identity_source.get_extension (E.SOURCE_EXTENSION_MAIL_SUBMISSION); + return Utils.strip_folder_full_name (account.service.uid, mail_submission_extension.sent_folder); + } + + return null; + } + + public void remove_account (Mail.Backend.Account account) { + lock (account_folderinfo) { + account_folderinfo.unset (account); + } + } +} diff --git a/src/FolderList/SessionItemModel.vala b/src/FolderList/SessionItemModel.vala new file mode 100644 index 000000000..6ae0c4fac --- /dev/null +++ b/src/FolderList/SessionItemModel.vala @@ -0,0 +1,45 @@ +/* +* Copyright 2021 elementary, Inc. (https://elementary.io) +* +* 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 3 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, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +*/ + + public class Mail.SessionItemModel : ItemModel { + construct { + name = _("All Mailboxes"); + icon_name = ""; + account_uid = "UNIFIED ACCOUNT"; + + folder_list = new ListStore (typeof(GroupedFolderItemModel)); + folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_INBOX)); + folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_ARCHIVE)); + folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_SENT)); + } + + public void add_account (Mail.Backend.Account account) { + for (int i = 0; folder_list.get_item (i) != null; i++) { + var item = (GroupedFolderItemModel)folder_list.get_item (i); + item.add_account (account); + } + } + + public void remov_account (Mail.Backend.Account account) { + for (int i = 0; folder_list.get_item (i) != null; i++) { + var item = (GroupedFolderItemModel)folder_list.get_item (i); + item.remove_account (account); + } + } +} From 191915b6b3714bb17146a59b31fbe7f4937d052b Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:30:53 +0200 Subject: [PATCH 45/72] Application: Cleanup --- src/Application.vala | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Application.vala b/src/Application.vala index d2ed05dd8..197142a93 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -132,22 +132,6 @@ public class Mail.Application : Gtk.Application { if (main_window == null) { main_window = new MainWindow (this); add_window (main_window); - - // int window_x, window_y; - // var rect = Gtk.Allocation (); - - // settings.get ("window-position", "(ii)", out window_x, out window_y); - // settings.get ("window-size", "(ii)", out rect.width, out rect.height); - - // if (window_x != -1 || window_y != -1) { - // main_window.move (window_x, window_y); - // } - - // main_window.set_allocation (rect); - - if (settings.get_boolean ("window-maximized")) { - main_window.maximize (); - } } main_window.present (); From 8ebeab17c3130188dcb59e268d390398e9bd486d Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:32:12 +0200 Subject: [PATCH 46/72] meson: Temporarily add has soup 3 argument --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 155f3fd92..e00faf69b 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ webkit2_dep = dependency('webkit2gtk-5.0') webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-5.0') folks_dep = dependency('folks') m_dep = meson.get_compiler('c').find_library('m') +add_project_arguments('--define=HAS_SOUP_3', language: 'vala') webkit2_extension_path = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name(), 'webkit2') From f9ead6002d99e2cc4bfd04f7e0a35b580960592d Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:35:35 +0200 Subject: [PATCH 47/72] MessageList: make windowcontrols public for fullscreen --- src/MessageList/MessageList.vala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index fbf74962e..b5462c3bd 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -7,6 +7,7 @@ public class Mail.MessageList : Gtk.Box { public signal void hovering_over_link (string? label, string? uri); + public Gtk.WindowControls window_controls { get; set; } public Gtk.HeaderBar headerbar { get; private set; } private Gtk.PopoverMenu mark_popover; @@ -107,6 +108,8 @@ public class Mail.MessageList : Gtk.Box { ); trash_button.add_css_class (Granite.STYLE_CLASS_LARGE_ICONS); + window_controls = new Gtk.WindowControls (END); + headerbar = new Gtk.HeaderBar () { show_title_buttons = false, title_widget = new Gtk.Label ("") @@ -119,7 +122,7 @@ public class Mail.MessageList : Gtk.Box { headerbar.pack_start (mark_button); headerbar.pack_start (archive_button); headerbar.pack_start (trash_button); - headerbar.pack_end (new Gtk.WindowControls (END)); + headerbar.pack_end (window_controls); headerbar.pack_end (app_menu); var placeholder = new Gtk.Label (_("No Message Selected")) { From de51b4a258e197cd7d8ffdbd91721cb4059df278 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:38:35 +0200 Subject: [PATCH 48/72] FolderItemModel: Make the expander icon hidden when no children --- src/FolderList/FolderItemModel.vala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index 54477cdbe..cdeb19878 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -36,12 +36,14 @@ public class Mail.FolderItemModel : ItemModel { construct { name = folder_info.display_name; account_uid = account.service.uid; - folder_list = new ListStore (typeof(FolderItemModel)); unread = folder_info.unread; full_name = folder_info.full_name; if (folder_info.child != null) { + if (folder_list == null) { + folder_list = new ListStore (typeof(FolderItemModel)); + } var current_folder_info = folder_info.child; while (current_folder_info != null) { var folder_item = new FolderItemModel (current_folder_info, account); From 8dc1050dfa1bdf37b719c4aa7af501f89e68c8cf Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:41:00 +0200 Subject: [PATCH 49/72] MainWindow: Update for gtk4 --- src/MainWindow.vala | 148 ++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 82 deletions(-) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index bdb17f59d..ffb55aa32 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -18,17 +18,13 @@ * Authored by: Corentin Noël */ -public class Mail.MainWindow : Hdy.ApplicationWindow { +public class Mail.MainWindow : Adw.ApplicationWindow { private Gtk.Paned paned_end; private Gtk.Paned paned_start; - private FoldersListView folders_list_view; - private Gtk.Overlay view_overlay; private ConversationList conversation_list; private MessageList message_list; - private uint configure_id; - public bool is_session_started { get; private set; default = false; } public signal void session_started (); @@ -105,18 +101,21 @@ public class Mail.MainWindow : Hdy.ApplicationWindow { ); } - folders_list_view = new FoldersListView (); + var folder_list = new FolderList (); + conversation_list = new ConversationList (); message_list = new MessageList (); - view_overlay = new Gtk.Overlay () { - expand = true + var view_overlay = new Gtk.Overlay () { + vexpand = true, + hexpand = true, + child = message_list }; - view_overlay.add (message_list); - var message_overlay = new Granite.Widgets.OverlayBar (view_overlay); - message_overlay.no_show_all = true; + var message_overlay = new Granite.OverlayBar (view_overlay) { + visible = false + }; message_list.hovering_over_link.connect ((label, url) => { #if HAS_SOUP_3 @@ -133,13 +132,21 @@ public class Mail.MainWindow : Hdy.ApplicationWindow { } }); - paned_start = new Gtk.Paned (Gtk.Orientation.HORIZONTAL); - paned_start.pack1 (folders_list_view, false, false); - paned_start.pack2 (conversation_list, true, false); + paned_start = new Gtk.Paned (Gtk.Orientation.HORIZONTAL) { + shrink_start_child = false, + shrink_end_child = false, + wide_handle = true + }; + paned_start.set_start_child (folder_list); + paned_start.set_end_child (conversation_list); - paned_end = new Gtk.Paned (Gtk.Orientation.HORIZONTAL); - paned_end.pack1 (paned_start, false, false); - paned_end.pack2 (view_overlay, true, false); + paned_end = new Gtk.Paned (Gtk.Orientation.HORIZONTAL) { + shrink_start_child = false, + shrink_end_child = false, + wide_handle = true + }; + paned_end.set_start_child (paned_start); + paned_end.set_end_child (view_overlay); var welcome_view = new Mail.WelcomeView (); @@ -147,25 +154,22 @@ public class Mail.MainWindow : Hdy.ApplicationWindow { placeholder_stack.add_named (paned_end, "mail"); placeholder_stack.add_named (welcome_view, "welcome"); - add (placeholder_stack); - - var header_group = new Hdy.HeaderGroup (); - header_group.add_header_bar (folders_list_view.header_bar); - header_group.add_header_bar (conversation_list.search_header); - header_group.add_header_bar (message_list.headerbar); + set_content (placeholder_stack); var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.VERTICAL); - size_group.add_widget (folders_list_view.header_bar); + size_group.add_widget (folder_list.header_bar); size_group.add_widget (conversation_list.search_header); size_group.add_widget (message_list.headerbar); var settings = new GLib.Settings ("io.elementary.mail"); + settings.bind ("window-width", this, "default-width", SettingsBindFlags.DEFAULT); + settings.bind ("window-height", this, "default-height", SettingsBindFlags.DEFAULT); + settings.bind ("window-maximized", this, "maximized", SettingsBindFlags.DEFAULT); + settings.bind ("paned-start-position", paned_start, "position", SettingsBindFlags.DEFAULT); settings.bind ("paned-end-position", paned_end, "position", SettingsBindFlags.DEFAULT); - destroy.connect (() => destroy ()); - - folders_list_view.folder_selected.connect (conversation_list.load_folder); + folder_list.folder_selected.connect (conversation_list.load_folder); conversation_list.conversation_selected.connect (message_list.set_conversation); @@ -205,18 +209,26 @@ public class Mail.MainWindow : Hdy.ApplicationWindow { private void on_mark_read () { conversation_list.mark_read_selected_messages (); + get_action (ACTION_MARK_READ).set_enabled (false); + get_action (ACTION_MARK_UNREAD).set_enabled (true); } private void on_mark_star () { conversation_list.mark_star_selected_messages (); + get_action (ACTION_MARK_STAR).set_enabled (false); + get_action (ACTION_MARK_UNSTAR).set_enabled (true); } private void on_mark_unread () { conversation_list.mark_unread_selected_messages (); + get_action (ACTION_MARK_UNREAD).set_enabled (false); + get_action (ACTION_MARK_READ).set_enabled (true); } private void on_mark_unstar () { conversation_list.mark_unstar_selected_messages (); + get_action (ACTION_MARK_UNSTAR).set_enabled (false); + get_action (ACTION_MARK_STAR).set_enabled (true); } private void action_compose (SimpleAction action, Variant? parameter) { @@ -252,69 +264,41 @@ public class Mail.MainWindow : Hdy.ApplicationWindow { } private void send_move_toast (string message) { - foreach (weak Gtk.Widget child in view_overlay.get_children ()) { - if (child is Granite.Widgets.Toast) { - child.destroy (); - } - } - - var toast = new Granite.Widgets.Toast (message); - toast.set_default_action (_("Undo")); - toast.show_all (); - - toast.default_action.connect (() => { - conversation_list.undo_move (); - }); - - toast.notify["child-revealed"].connect (() => { - if (!toast.child_revealed) { - conversation_list.undo_expired (); - } - }); - - view_overlay.add_overlay (toast); - toast.send_notification (); + // foreach (weak Gtk.Widget child in view_overlay.get_children ()) { + // if (child is Granite.Widgets.Toast) { + // child.destroy (); + // } + // } + + // var toast = new Granite.Widgets.Toast (message); + // toast.set_default_action (_("Undo")); + // toast.show_all (); + + // toast.default_action.connect (() => { + // conversation_list.undo_move (); + // }); + + // toast.notify["child-revealed"].connect (() => { + // if (!toast.child_revealed) { + // conversation_list.undo_expired (); + // } + // }); + + // view_overlay.add_overlay (toast); + // toast.send_notification (); } private void on_fullscreen () { - if (Gdk.WindowState.FULLSCREEN in get_window ().get_state ()) { - message_list.headerbar.show_close_button = true; + if (is_fullscreen ()) { + message_list.window_controls.visible = true; unfullscreen (); } else { - message_list.headerbar.show_close_button = false; + message_list.window_controls.visible = false; fullscreen (); } } - private SimpleAction? get_action (string name) { - return lookup_action (name) as SimpleAction; - } - - public override bool configure_event (Gdk.EventConfigure event) { - if (configure_id != 0) { - GLib.Source.remove (configure_id); - } - - configure_id = Timeout.add (100, () => { - configure_id = 0; - - if (is_maximized) { - Mail.Application.settings.set_boolean ("window-maximized", true); - } else { - Mail.Application.settings.set_boolean ("window-maximized", false); - - Gdk.Rectangle rect; - get_allocation (out rect); - Mail.Application.settings.set ("window-size", "(ii)", rect.width, rect.height); - - int root_x, root_y; - get_position (out root_x, out root_y); - Mail.Application.settings.set ("window-position", "(ii)", root_x, root_y); - } - - return false; - }); - - return base.configure_event (event); + public SimpleAction? get_action (string name) { + return (SimpleAction) lookup_action (name) ; } } From a732c3053952b3836b0242a752cb64bdc8996098 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:46:05 +0200 Subject: [PATCH 50/72] Reenable trash toast --- src/ConversationList/ConversationList.vala | 2 +- src/MainWindow.vala | 37 +++++++--------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 0ccfacb23..65989ba18 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -590,7 +590,7 @@ public class Mail.ConversationList : Gtk.Box { public void undo_move () { move_handler.undo_last_move.begin ((obj, res) => { move_handler.undo_last_move.end (res); - list_store.items_changed (0, 0, list_store.get_n_items ()); + list_store.items_changed (0, list_store.get_n_items (), list_store.get_n_items ()); }); } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index ffb55aa32..0c1ff76af 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -23,6 +23,7 @@ public class Mail.MainWindow : Adw.ApplicationWindow { private Gtk.Paned paned_start; private ConversationList conversation_list; + private Granite.Toast toast; private MessageList message_list; public bool is_session_started { get; private set; default = false; } @@ -113,6 +114,14 @@ public class Mail.MainWindow : Adw.ApplicationWindow { child = message_list }; + toast = new Granite.Toast (""); + toast.set_default_action (_("Undo")); + view_overlay.add_overlay (toast); + + toast.default_action.connect (() => { + conversation_list.undo_move (); + }); + var message_overlay = new Granite.OverlayBar (view_overlay) { visible = false }; @@ -259,35 +268,11 @@ public class Mail.MainWindow : Adw.ApplicationWindow { private void on_move_to_trash () { var result = conversation_list.trash_selected_messages (); if (result > 0) { - send_move_toast (ngettext ("Message Deleted", "Messages Deleted", result)); + toast.title = ngettext ("Message Deleted", "Messages Deleted", result); + toast.send_notification (); } } - private void send_move_toast (string message) { - // foreach (weak Gtk.Widget child in view_overlay.get_children ()) { - // if (child is Granite.Widgets.Toast) { - // child.destroy (); - // } - // } - - // var toast = new Granite.Widgets.Toast (message); - // toast.set_default_action (_("Undo")); - // toast.show_all (); - - // toast.default_action.connect (() => { - // conversation_list.undo_move (); - // }); - - // toast.notify["child-revealed"].connect (() => { - // if (!toast.child_revealed) { - // conversation_list.undo_expired (); - // } - // }); - - // view_overlay.add_overlay (toast); - // toast.send_notification (); - } - private void on_fullscreen () { if (is_fullscreen ()) { message_list.window_controls.visible = true; From 3fe6f21473aeb3b8747a1ac3c7d710cf1b326229 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 19:56:36 +0200 Subject: [PATCH 51/72] Data: Update keys in gschema for new state saving --- data/io.elementary.mail.gschema.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/io.elementary.mail.gschema.xml b/data/io.elementary.mail.gschema.xml index edc4f9709..cfdafb461 100644 --- a/data/io.elementary.mail.gschema.xml +++ b/data/io.elementary.mail.gschema.xml @@ -7,15 +7,15 @@ Whether the window was maximized on last run Whether the window was maximized on last run - - (-1, -1) - Window position - Most recent window position (x, y) - - - (1024, 750) - Most recent window size - Most recent window size (width, height) + + 1024 + Most recent window width + Most recent window width + + + 750 + Most recent window height + Most recent window height 190 From 3c1c6770b3c5af35c2e4f2f037df219a512aecc7 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Tue, 18 Apr 2023 21:16:19 +0200 Subject: [PATCH 52/72] MessageListItem: Reimplement gravatar (?) --- src/MessageList/MessageListItem.vala | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 42af28aab..ee238fbec 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -326,7 +326,10 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { set_child (base_box); expanded = false; - // avatar.set_loadable_icon (new GravatarIcon (parsed_address, get_style_context ().get_scale ())); + get_gravatar.begin (parsed_address, (obj, res) => { + var gravatar = get_gravatar.end (res); + avatar.set_custom_image (gravatar); + }); header_gesture_click.released.connect (() => { expanded = !expanded; @@ -607,6 +610,25 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { return yield web_view.get_body_html (); } + private async Gtk.IconPaintable? get_gravatar (string address) { //@TODO: Worked once then never again; no idea :) + var uri = "https://www.gravatar.com/avatar/%s?d=404".printf ( + Checksum.compute_for_string (ChecksumType.MD5, address.strip ().down ()) + ); + var server_file = File.new_for_uri (uri); + var path = Path.build_filename (Environment.get_tmp_dir (), server_file.get_basename ()); + var local_file = File.new_for_path (path); + + if (!local_file.query_exists (loading_cancellable)) { + try { + yield server_file.copy_async (local_file, FileCopyFlags.OVERWRITE, GLib.Priority.DEFAULT, loading_cancellable, null); + } catch (Error e) { + warning (e.message); + return null; + } + } + return new Gtk.IconPaintable.for_file (local_file, avatar.size, get_style_context ().get_scale ()); + } + private void show_attachment (Camel.MimePart mime_part) { var dialog = new Granite.MessageDialog ( _("Trust and open “%s”?").printf (mime_part.get_filename ()), From 8c932a625b6ab9fe1c832a80fe8abf2b2a58266c Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 17:04:25 +0200 Subject: [PATCH 53/72] MessageListItem: Update comment --- src/MessageList/MessageListItem.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index ee238fbec..09c5a56c3 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -269,7 +269,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { message_type = WARNING, revealed = false }; - blocked_images_infobar.add_child (new Gtk.Label (_("This message contains remote images.")) { ellipsize = END }); //@TODO: Not so sure here + blocked_images_infobar.add_child (new Gtk.Label (_("This message contains remote images.")) { ellipsize = END }); //@TODO: Ellipsize: designwise not so sure here blocked_images_infobar.add_button (_("Show Images"), 1); // Vertical content area doesn't work anymore blocked_images_infobar.add_button (_("Always Show from Sender"), 2); blocked_images_infobar.add_css_class (Granite.STYLE_CLASS_FRAME); From 342adb14a68d2a6b061b15e8e4e2aa662568302b Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 17:05:20 +0200 Subject: [PATCH 54/72] Composer: Updates --- src/Composer.vala | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index 2bfdc1d82..ea8fd7bc7 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -131,7 +131,7 @@ public class Mail.Composer : Gtk.ApplicationWindow { var bcc_button = new Gtk.ToggleButton.with_label (_("Bcc")); - var to_box = new Gtk.Box (HORIZONTAL, 0); + var to_box = new EntryBox (); to_box.append (to_val); to_box.append (cc_button); to_box.append (bcc_button); @@ -401,18 +401,6 @@ public class Mail.Composer : Gtk.ApplicationWindow { has_recipients = to_val.text != ""; }); - // to_val.get_style_context ().changed.connect (() => { - // unowned Gtk.StyleContext to_grid_style_context = to_grid.get_style_context (); - // var state = to_grid_style_context.get_state (); - // if (to_val.has_focus) { - // state |= Gtk.StateFlags.FOCUSED; - // } else { - // state ^= Gtk.StateFlags.FOCUSED; - // } - - // to_grid_style_context.set_state (state); - // }); - if (to != null) { to_val.text = to; } @@ -1001,8 +989,16 @@ public class Mail.Composer : Gtk.ApplicationWindow { } } + private class EntryBox : Gtk.Box { + static construct { + set_css_name ("entry"); + } + } + public async bool save_draft () { - if (discard_draft || !web_view.body_html_changed) { + /* @TODO: Currently we always save (also empty drafts) maybe change that. + * Previously (gtk3) it only saved if the web view body html changed, but it's hard to detect that now or at least I did find an easy way */ + if (discard_draft) { return false; } From 8f3d7104263d36f3454e48ecde790680443b7d86 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 17:25:18 +0200 Subject: [PATCH 55/72] FolderList: Some cleanup + remember recent grouped folder --- src/FolderList/AccountItemModel.vala | 2 -- src/FolderList/FolderList.vala | 26 +++++++++++++--------- src/FolderList/GroupedFolderItemModel.vala | 7 +++++- src/FolderList/SessionItemModel.vala | 6 +++-- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 555902279..98f1cd200 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -21,8 +21,6 @@ */ public class Mail.AccountItemModel : ItemModel { - public signal void loaded (); - public Mail.Backend.Account account { get; construct; } private GLib.Cancellable connect_cancellable; diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 8fa12f9e5..298d0bcf9 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -139,9 +139,21 @@ public class Mail.FolderList : Gtk.Box { }); } else if (item is SessionItemModel) { ((FolderListItem)expander.child).bind (item); - // list_row.expanded = true; @TODO: causes snapshot warning ? + // list_row.expanded = true; //@TODO: causes snapshot warning ? } else if (item is GroupedFolderItemModel) { - ((FolderListItem)expander.child).bind (item); + var folder_item = (GroupedFolderItemModel)item; + + ((FolderListItem)expander.child).bind (folder_item); + + if (!already_selected) { + string selected_folder_uid, selected_folder_full_name; + settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_full_name); + if (folder_item.account_uid == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { + print ("set selected"); + selection_model.set_selected (list_item.position); + already_selected = true; + } + } } }); @@ -168,20 +180,14 @@ public class Mail.FolderList : Gtk.Box { } else if (item is GroupedFolderItemModel) { folder_selected (item.get_folder_full_name_per_account ()); - settings.set ("selected-folder", "(ss)", item.account_uid, "GROUPED"); + settings.set ("selected-folder", "(ss)", item.account_uid, item.full_name); } }); } public ListModel? create_folder_list_func (Object item) { - if (item is AccountItemModel) { - return item.folder_list; - } else if (item is FolderItemModel) { - return item.folder_list; - } else if (item is SessionItemModel) { + if (item is ItemModel) { return item.folder_list; - } else if (item is GroupedFolderItemModel) { - return null; } return null; } diff --git a/src/FolderList/GroupedFolderItemModel.vala b/src/FolderList/GroupedFolderItemModel.vala index d5b272580..8483eee72 100644 --- a/src/FolderList/GroupedFolderItemModel.vala +++ b/src/FolderList/GroupedFolderItemModel.vala @@ -22,7 +22,9 @@ public class Mail.GroupedFolderItemModel : ItemModel { public int unread { get; set; } + public Camel.FolderInfoFlags folder_type { get; construct; } + public string full_name { get; construct; } private Gee.HashMap account_folderinfo; @@ -31,21 +33,24 @@ public class Mail.GroupedFolderItemModel : ItemModel { } construct { - account_uid = "UNIFIED ACCOUNT"; + account_uid = Mail.SessionItemModel.account; account_folderinfo = new Gee.HashMap (); switch (folder_type & Camel.FOLDER_TYPE_MASK) { case Camel.FolderInfoFlags.TYPE_INBOX: name = _("Inbox"); icon_name = "mail-inbox"; + full_name = "inbox"; break; case Camel.FolderInfoFlags.TYPE_ARCHIVE: name = _("Archive"); icon_name = ("mail-archive"); + full_name = "archive"; break; case Camel.FolderInfoFlags.TYPE_SENT: name = _("Sent"); icon_name = "mail-sent"; + full_name = "sent"; break; default: name = "%i".printf (folder_type & Camel.FOLDER_TYPE_MASK); diff --git a/src/FolderList/SessionItemModel.vala b/src/FolderList/SessionItemModel.vala index 6ae0c4fac..da64e0e82 100644 --- a/src/FolderList/SessionItemModel.vala +++ b/src/FolderList/SessionItemModel.vala @@ -18,10 +18,12 @@ */ public class Mail.SessionItemModel : ItemModel { + public const string account = "SESSION ACCOUNT"; + construct { name = _("All Mailboxes"); icon_name = ""; - account_uid = "UNIFIED ACCOUNT"; + account_uid = account_uid; folder_list = new ListStore (typeof(GroupedFolderItemModel)); folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_INBOX)); @@ -36,7 +38,7 @@ } } - public void remov_account (Mail.Backend.Account account) { + public void remove_account (Mail.Backend.Account account) { for (int i = 0; folder_list.get_item (i) != null; i++) { var item = (GroupedFolderItemModel)folder_list.get_item (i); item.remove_account (account); From 5967dd3df6991720b79a0ad6558bf8852b56cd28 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 17:30:49 +0200 Subject: [PATCH 56/72] FolderList: Some cleanup --- src/FolderList/FolderList.vala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 298d0bcf9..6f34a59d5 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -29,7 +29,7 @@ public class Mail.FolderList : Gtk.Box { private ListStore root_model; private Mail.Backend.Session session; - private SessionItemModel? session_item = null; + private SessionItemModel? session_item; private bool already_selected = false; @@ -82,8 +82,7 @@ public class Mail.FolderList : Gtk.Box { var list_item = (Gtk.ListItem) obj; var tree_expander = new Gtk.TreeExpander () { - child = new FolderListItem (), - // indent_for_icon = false + child = new FolderListItem () }; list_item.child = tree_expander; @@ -193,10 +192,8 @@ public class Mail.FolderList : Gtk.Box { } private void add_account (Mail.Backend.Account account) { - if (session.get_accounts ().size > 1) { - if (!(root_model.get_item (0) is SessionItemModel)) { - root_model.insert (0, session_item); - } + if (session.get_accounts ().size > 1 && !(root_model.get_item (0) is SessionItemModel)) { + root_model.insert (0, session_item); } session_item.add_account (account); From 15c9ade1739ef2eefb6fa08d26f7d96c60d563f9 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:30:57 +0200 Subject: [PATCH 57/72] FolderList: Cleanup + add unread badge --- src/FolderList/AccountItemModel.vala | 2 +- src/FolderList/FolderList.vala | 1 - src/FolderList/FolderListItem.vala | 19 +++++++++++++++++-- src/FolderList/SessionItemModel.vala | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 98f1cd200..34adbf2b1 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -33,7 +33,7 @@ public class Mail.AccountItemModel : ItemModel { construct { offlinestore = (Camel.OfflineStore) account.service; - icon_name = ""; + icon_name = "avatar-default"; name = offlinestore.display_name; account_uid = offlinestore.uid; folder_list = new ListStore (typeof(FolderItemModel)); diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 6f34a59d5..8d42860d2 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -148,7 +148,6 @@ public class Mail.FolderList : Gtk.Box { string selected_folder_uid, selected_folder_full_name; settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_full_name); if (folder_item.account_uid == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { - print ("set selected"); selection_model.set_selected (list_item.position); already_selected = true; } diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala index fbad21dd4..4c797839c 100644 --- a/src/FolderList/FolderListItem.vala +++ b/src/FolderList/FolderListItem.vala @@ -1,6 +1,7 @@ public class FolderListItem : Gtk.Box { private Gtk.Image image; private Gtk.Label label; + private Gtk.Label badge; private Mail.FolderItemModel? folder_item = null; @@ -30,16 +31,23 @@ public class FolderListItem : Gtk.Box { menu.set_parent (this); image = new Gtk.Image (); + label = new Gtk.Label ("") { margin_start = 3 }; + badge = new Gtk.Label ("") { + halign = END //@TODO: Tbh no idea how to move this to the right without another widget + }; + badge.add_css_class (Granite.STYLE_CLASS_BADGE); + hexpand = true; vexpand = true; orientation = HORIZONTAL; append (image); append (label); + append (badge); gesture_secondary_click.pressed.connect ((n_press, x, y) => { if (folder_item != null) { @@ -54,13 +62,20 @@ public class FolderListItem : Gtk.Box { } public void bind (ItemModel item_model) { - folder_item = null; - image.set_from_icon_name (item_model.icon_name); label.label = item_model.name; if (item_model is Mail.FolderItemModel) { folder_item = (Mail.FolderItemModel)item_model; + badge.label = "%d".printf (item_model.unread); + print (badge.label); + badge.visible = item_model.unread > 0; + } else if (item_model is Mail.GroupedFolderItemModel) { + badge.label = "%d".printf (item_model.unread); + badge.visible = item_model.unread > 0; + } else { + folder_item = null; + badge.visible = false; } } diff --git a/src/FolderList/SessionItemModel.vala b/src/FolderList/SessionItemModel.vala index da64e0e82..881f94edb 100644 --- a/src/FolderList/SessionItemModel.vala +++ b/src/FolderList/SessionItemModel.vala @@ -22,7 +22,7 @@ construct { name = _("All Mailboxes"); - icon_name = ""; + icon_name = "go-home"; account_uid = account_uid; folder_list = new ListStore (typeof(GroupedFolderItemModel)); From 543cda33ca549093c4da47d3f22b3aa4603f95bc Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:41:29 +0200 Subject: [PATCH 58/72] FolderList: Cleanup + handle folder signals --- src/FolderList/AccountItemModel.vala | 7 +++++++ src/FolderList/FolderListItem.vala | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index 34adbf2b1..b50e96c59 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -40,6 +40,11 @@ public class Mail.AccountItemModel : ItemModel { connect_cancellable = new GLib.Cancellable (); + offlinestore.folder_created.connect (load); + offlinestore.folder_deleted.connect (load); + offlinestore.folder_info_stale.connect (load); + offlinestore.folder_renamed.connect (load); + unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); network_monitor.network_changed.connect (() =>{ connect_to_account.begin (); @@ -81,6 +86,8 @@ public class Mail.AccountItemModel : ItemModel { } private void show_info (Camel.FolderInfo? _folderinfo) { + folder_list.remove_all (); + var folderinfo = _folderinfo; while (folderinfo != null) { var folder_item = new FolderItemModel (folderinfo, account); diff --git a/src/FolderList/FolderListItem.vala b/src/FolderList/FolderListItem.vala index 4c797839c..c5e0f2f04 100644 --- a/src/FolderList/FolderListItem.vala +++ b/src/FolderList/FolderListItem.vala @@ -68,7 +68,6 @@ public class FolderListItem : Gtk.Box { if (item_model is Mail.FolderItemModel) { folder_item = (Mail.FolderItemModel)item_model; badge.label = "%d".printf (item_model.unread); - print (badge.label); badge.visible = item_model.unread > 0; } else if (item_model is Mail.GroupedFolderItemModel) { badge.label = "%d".printf (item_model.unread); From e940a4f694a2fc480a336deb60b768c80c8c2e40 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:49:43 +0200 Subject: [PATCH 59/72] Remove unused stuff and update meson --- src/FoldersView/AccountSavedState.vala | 65 ------- src/FoldersView/AccountSourceItem.vala | 161 ------------------ src/FoldersView/FolderSourceItem.vala | 91 ---------- src/FoldersView/FoldersListView.vala | 143 ---------------- src/FoldersView/GroupedFolderSourceItem.vala | 168 ------------------- src/FoldersView/SessionSourceItem.vala | 52 ------ src/MessageList/GravatarIcon.vala | 61 ------- src/WebView.vala | 19 +-- src/meson.build | 25 ++- 9 files changed, 16 insertions(+), 769 deletions(-) delete mode 100644 src/FoldersView/AccountSavedState.vala delete mode 100644 src/FoldersView/AccountSourceItem.vala delete mode 100644 src/FoldersView/FolderSourceItem.vala delete mode 100644 src/FoldersView/FoldersListView.vala delete mode 100644 src/FoldersView/GroupedFolderSourceItem.vala delete mode 100644 src/FoldersView/SessionSourceItem.vala delete mode 100644 src/MessageList/GravatarIcon.vala diff --git a/src/FoldersView/AccountSavedState.vala b/src/FoldersView/AccountSavedState.vala deleted file mode 100644 index 8c85c80fa..000000000 --- a/src/FoldersView/AccountSavedState.vala +++ /dev/null @@ -1,65 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2017 elementary LLC. (https://elementary.io) - * - * 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 3 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 . - * - * Authored by: Corentin Noël - */ - -public class Mail.AccountSavedState : GLib.Object { - public unowned Mail.Backend.Account account { get; construct; } - - private GLib.Settings settings; - private Gee.HashMap items; - - public AccountSavedState (Mail.Backend.Account account) { - Object (account: account); - } - - construct { - settings = new GLib.Settings.with_path ("io.elementary.mail.accounts", "/io/elementary/mail/accounts/%s/".printf (account.service.uid)); - items = new Gee.HashMap (); - } - - public void bind_with_expandable_item (Mail.SourceList.ExpandableItem item) { - if (item is AccountSourceItem) { - settings.bind ("expanded", item, "expanded", SettingsBindFlags.DEFAULT | SettingsBindFlags.GET_NO_CHANGES); - } else if (item is FolderSourceItem) { - var folder_item = (FolderSourceItem) item; - items[folder_item.full_name] = folder_item; - if (folder_item.full_name in settings.get_strv ("expanded-folders")) { - item.expanded = true; - } - - item.notify["expanded"].connect (() => { - var folders = settings.get_strv ("expanded-folders"); - if (item.expanded) { - folders += folder_item.full_name; - } else { - string[] new_folders = {}; - foreach (var folder in folders) { - if (folder != folder_item.full_name) { - new_folders += folder; - } - } - - folders = new_folders; - } - - settings.set_strv ("expanded-folders", folders); - }); - } - } -} diff --git a/src/FoldersView/AccountSourceItem.vala b/src/FoldersView/AccountSourceItem.vala deleted file mode 100644 index 31db1d496..000000000 --- a/src/FoldersView/AccountSourceItem.vala +++ /dev/null @@ -1,161 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2017 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: Corentin Noël - */ - -public class Mail.AccountSourceItem : Mail.SourceList.ExpandableItem { - public Mail.Backend.Account account { get; construct; } - - public signal void loaded (); - - private GLib.Cancellable connect_cancellable; - private Gee.HashMap folder_items; - private AccountSavedState saved_state; - private unowned Camel.OfflineStore offlinestore; - - public AccountSourceItem (Mail.Backend.Account account) { - Object (account: account); - } - - construct { - visible = true; - connect_cancellable = new GLib.Cancellable (); - folder_items = new Gee.HashMap (); - saved_state = new AccountSavedState (account); - saved_state.bind_with_expandable_item (this); - - offlinestore = (Camel.OfflineStore) account.service; - name = offlinestore.display_name; - offlinestore.folder_created.connect (folder_created); - offlinestore.folder_deleted.connect (folder_deleted); - offlinestore.folder_info_stale.connect (reload_folders); - offlinestore.folder_renamed.connect (folder_renamed); - unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); - network_monitor.network_changed.connect (() =>{ - connect_to_account.begin (); - }); - } - - ~AccountSourceItem () { - connect_cancellable.cancel (); - } - - public async void load () { - try { - var folderinfo = yield offlinestore.get_folder_info (null, Camel.StoreGetFolderInfoFlags.RECURSIVE, GLib.Priority.DEFAULT, connect_cancellable); - if (folderinfo != null) { - show_info (folderinfo, this); - } - } catch (Error e) { - critical (e.message); - } - - connect_to_account.begin (); - } - - private async void connect_to_account () { - unowned GLib.NetworkMonitor network_monitor = GLib.NetworkMonitor.get_default (); - if (network_monitor.network_available == false) { - return; - } - - try { - yield offlinestore.set_online (true, GLib.Priority.DEFAULT, connect_cancellable); - yield offlinestore.connect (GLib.Priority.DEFAULT, connect_cancellable); - - yield offlinestore.synchronize (false, GLib.Priority.DEFAULT, connect_cancellable); - } catch (Error e) { - critical (e.message); - } - } - - private void folder_renamed (string old_name, Camel.FolderInfo folder_info) { - var item = folder_items[old_name]; - item.update_infos (folder_info); - } - - private void folder_deleted (Camel.FolderInfo folder_info) { - Mail.FolderSourceItem? item = folder_items[folder_info.full_name]; - if (item != null) { - item.parent.remove (item); - folder_items.unset (folder_info.full_name); - } - } - - private void folder_created (Camel.FolderInfo folder_info) { - if (folder_info.parent == null) { - show_info (folder_info, this); - } else { - unowned Camel.FolderInfo parent_info = (Camel.FolderInfo) folder_info.parent; - var parent_item = folder_items[parent_info.full_name]; - if (parent_item == null) { - // Create the parent, then retry to create the children. - folder_created (parent_info); - folder_created (folder_info); - } else { - show_info (folder_info, parent_item); - } - } - } - - private async void reload_folders () { - var offlinestore = (Camel.OfflineStore) account.service; - foreach (var folder_item in folder_items.values) { - try { - var folder_info = yield offlinestore.get_folder_info (folder_item.full_name, 0, GLib.Priority.DEFAULT, connect_cancellable); - folder_item.update_infos (folder_info); - } catch (Error e) { - // We can cancel the operation - if (!(e is GLib.IOError.CANCELLED)) { - critical (e.message); - } - } - } - } - - private void show_info (Camel.FolderInfo? _folderinfo, Mail.SourceList.ExpandableItem item) { - var folderinfo = _folderinfo; - while (folderinfo != null) { - var folder_item = new FolderSourceItem (account, folderinfo); - saved_state.bind_with_expandable_item (folder_item); - folder_items[folderinfo.full_name] = folder_item; - folder_item.refresh.connect (() => { - refresh_folder.begin (folder_item.full_name); - }); - - if (folderinfo.child != null) { - show_info ((Camel.FolderInfo?) folderinfo.child, folder_item); - } - - item.add (folder_item); - folderinfo = (Camel.FolderInfo?) folderinfo.next; - } - } - - private async void refresh_folder (string folder_name) { - var offlinestore = (Camel.Store) account.service; - try { - var folder = yield offlinestore.get_folder (folder_name, 0, GLib.Priority.DEFAULT, connect_cancellable); - yield folder.refresh_info (GLib.Priority.DEFAULT, connect_cancellable); - } catch (Error e) { - critical (e.message); - } - } -} diff --git a/src/FoldersView/FolderSourceItem.vala b/src/FoldersView/FolderSourceItem.vala deleted file mode 100644 index 59c27e868..000000000 --- a/src/FoldersView/FolderSourceItem.vala +++ /dev/null @@ -1,91 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2017 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: Corentin Noël - */ - -public class Mail.FolderSourceItem : Mail.SourceList.ExpandableItem { - public signal void refresh (); - - public string full_name; - public Backend.Account account { get; construct; } - - private bool can_modify = true; - - public FolderSourceItem (Backend.Account account, Camel.FolderInfo folderinfo) { - Object (account: account); - update_infos (folderinfo); - } - - public override Gtk.Menu? get_context_menu () { - var menu = new Gtk.Menu (); - var refresh_item = new Gtk.MenuItem.with_label (_("Refresh folder")); - menu.add (refresh_item); - menu.show_all (); - - refresh_item.activate.connect (() => refresh ()); - return menu; - } - - public void update_infos (Camel.FolderInfo folderinfo) { - name = folderinfo.display_name; - full_name = folderinfo.full_name; - if (folderinfo.unread > 0) { - badge = "%d".printf (folderinfo.unread); - } - - var full_folder_info_flags = Utils.get_full_folder_info_flags (account.service, folderinfo); - switch (full_folder_info_flags & Camel.FOLDER_TYPE_MASK) { - case Camel.FolderInfoFlags.TYPE_INBOX: - icon = new ThemedIcon ("mail-inbox"); - can_modify = false; - break; - case Camel.FolderInfoFlags.TYPE_OUTBOX: - icon = new ThemedIcon ("mail-outbox"); - can_modify = false; - break; - case Camel.FolderInfoFlags.TYPE_TRASH: - icon = new ThemedIcon (folderinfo.total == 0 ? "user-trash" : "user-trash-full"); - can_modify = false; - badge = null; - break; - case Camel.FolderInfoFlags.TYPE_JUNK: - icon = new ThemedIcon ("edit-flag"); - can_modify = false; - break; - case Camel.FolderInfoFlags.TYPE_SENT: - icon = new ThemedIcon ("mail-sent"); - can_modify = false; - break; - case Camel.FolderInfoFlags.TYPE_ARCHIVE: - icon = new ThemedIcon ("mail-archive"); - can_modify = false; - badge = null; - break; - case Camel.FolderInfoFlags.TYPE_DRAFTS: - icon = new ThemedIcon ("mail-drafts"); - can_modify = false; - break; - default: - icon = new ThemedIcon ("folder"); - can_modify = true; - break; - } - } -} diff --git a/src/FoldersView/FoldersListView.vala b/src/FoldersView/FoldersListView.vala deleted file mode 100644 index fd8eaa45e..000000000 --- a/src/FoldersView/FoldersListView.vala +++ /dev/null @@ -1,143 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2017 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: Corentin Noël - */ - -public class Mail.FoldersListView : Gtk.Grid { - public signal void folder_selected (Gee.Map folder_full_name_per_account); - - public Hdy.HeaderBar header_bar { get; private set; } - - private Mail.SourceList source_list; - private Mail.SessionSourceItem session_source_item; - private static GLib.Settings settings; - - static construct { - settings = new GLib.Settings ("io.elementary.mail"); - } - - construct { - source_list = new Mail.SourceList (); - - var application_instance = (Gtk.Application) GLib.Application.get_default (); - - var compose_button = new Gtk.Button.from_icon_name ("mail-message-new", Gtk.IconSize.LARGE_TOOLBAR) { - action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_COMPOSE_MESSAGE, - halign = Gtk.Align.START - }; - compose_button.tooltip_markup = Granite.markup_accel_tooltip ( - application_instance.get_accels_for_action (compose_button.action_name), - _("Compose new message") - ); - - header_bar = new Hdy.HeaderBar () { - show_close_button = true - }; - header_bar.pack_end (compose_button); - header_bar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); - - var scrolled_window = new Gtk.ScrolledWindow (null, null); - scrolled_window.add (source_list); - - orientation = Gtk.Orientation.VERTICAL; - width_request = 100; - get_style_context ().add_class (Gtk.STYLE_CLASS_SIDEBAR); - add (header_bar); - add (scrolled_window); - - var session = Mail.Backend.Session.get_default (); - - session_source_item = new Mail.SessionSourceItem (session); - source_list.root.add (session_source_item); - - session.get_accounts ().foreach ((account) => { - add_account (account); - return true; - }); - - session.account_added.connect (add_account); - source_list.item_selected.connect ((item) => { - if (item == null) { - return; - } - - if (item is FolderSourceItem) { - unowned FolderSourceItem folder_item = (FolderSourceItem) item; - var folder_name_per_account = new Gee.HashMap (); - folder_name_per_account.set (folder_item.account, folder_item.full_name); - folder_selected (folder_name_per_account.read_only_view); - - settings.set ("selected-folder", "(ss)", folder_item.account.service.uid, folder_item.full_name); - - } else if (item is GroupedFolderSourceItem) { - unowned GroupedFolderSourceItem grouped_folder_item = (GroupedFolderSourceItem) item; - folder_selected (grouped_folder_item.get_folder_full_name_per_account ()); - - settings.set ("selected-folder", "(ss)", "GROUPED", grouped_folder_item.name); - } - }); - } - - private void add_account (Mail.Backend.Account account) { - var account_item = new Mail.AccountSourceItem (account); - source_list.root.add (account_item); - account_item.load.begin ((obj, res) => { - account_item.load.end (res); - - string selected_folder_uid, selected_folder_name; - settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_name); - - if (account.service.uid == selected_folder_uid) { - select_saved_folder (account_item, selected_folder_name); - } else if (selected_folder_uid == "GROUPED") { - select_saved_folder (session_source_item, selected_folder_name); - } - }); - } - - private bool select_saved_folder (Mail.SourceList.ExpandableItem item, string selected_folder_name) { - foreach (var child in item.children) { - if (child is FolderSourceItem) { - if (select_saved_folder ((Mail.SourceList.ExpandableItem) child, selected_folder_name)) { - return true; - } - - unowned FolderSourceItem folder_item = (FolderSourceItem) child; - if (folder_item.full_name == selected_folder_name) { - source_list.selected = child; - - var folder_name_per_account = new Gee.HashMap (); - folder_name_per_account.set (folder_item.account, folder_item.full_name); - folder_selected (folder_name_per_account.read_only_view); - return true; - } - } else if (child is GroupedFolderSourceItem) { - unowned GroupedFolderSourceItem grouped_folder_item = (GroupedFolderSourceItem) child; - if (grouped_folder_item.name == selected_folder_name) { - source_list.selected = child; - folder_selected (grouped_folder_item.get_folder_full_name_per_account ()); - return true; - } - } - } - - return false; - } -} diff --git a/src/FoldersView/GroupedFolderSourceItem.vala b/src/FoldersView/GroupedFolderSourceItem.vala deleted file mode 100644 index 7edf70e11..000000000 --- a/src/FoldersView/GroupedFolderSourceItem.vala +++ /dev/null @@ -1,168 +0,0 @@ -/* -* Copyright 2021 elementary, Inc. (https://elementary.io) -* -* 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 3 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, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -*/ - -public class Mail.GroupedFolderSourceItem : Mail.SourceList.Item { - public Mail.Backend.Session session { get; construct; } - public Camel.FolderInfoFlags folder_type { get; construct; } - - private GLib.Cancellable connect_cancellable; - private Gee.HashMap account_folderinfo; - - public GroupedFolderSourceItem (Mail.Backend.Session session, Camel.FolderInfoFlags folder_type) { - Object (session: session, folder_type: folder_type); - } - - construct { - visible = true; - connect_cancellable = new GLib.Cancellable (); - account_folderinfo = new Gee.HashMap (); - - switch (folder_type & Camel.FOLDER_TYPE_MASK) { - case Camel.FolderInfoFlags.TYPE_INBOX: - name = _("Inbox"); - icon = new ThemedIcon ("mail-inbox"); - break; - case Camel.FolderInfoFlags.TYPE_ARCHIVE: - name = _("Archive"); - icon = new ThemedIcon ("mail-archive"); - break; - case Camel.FolderInfoFlags.TYPE_SENT: - name = _("Sent"); - icon = new ThemedIcon ("mail-sent"); - break; - default: - name = "%i".printf (folder_type & Camel.FOLDER_TYPE_MASK); - icon = new ThemedIcon ("folder"); - warning ("Unknown grouped folder type: %s", name); - break; - } - - session.get_accounts ().foreach ((account) => { - add_account (account); - return true; - }); - - session.account_added.connect (add_account); - session.account_removed.connect (removed_account); - } - - ~GroupedFolderSourceItem () { - connect_cancellable.cancel (); - } - - public Gee.Map get_folder_full_name_per_account () { - var folder_full_name_per_account = new Gee.HashMap (); - lock (account_folderinfo) { - foreach (var entry in account_folderinfo) { - if (entry.value != null) { - folder_full_name_per_account.set (entry.key, entry.value.full_name); - } else { - folder_full_name_per_account.set (entry.key, null); - } - } - } - - return folder_full_name_per_account.read_only_view; - } - - private void add_account (Mail.Backend.Account account) { - lock (account_folderinfo) { - account_folderinfo.set (account, null); - } - load_folder_info.begin (account); - } - - private async void load_folder_info (Mail.Backend.Account account) { - var offlinestore = (Camel.OfflineStore) account.service; - var full_name = build_folder_full_name (account); - Camel.FolderInfo? folderinfo = null; - - if (full_name != null) { - try { - folderinfo = yield offlinestore.get_folder_info (full_name, 0, GLib.Priority.DEFAULT, connect_cancellable); - } catch (Error e) { - // We can cancel the operation - if (!(e is GLib.IOError.CANCELLED)) { - warning ("Unable to fetch %s of account '%s': %s", full_name, account.service.display_name, e.message); - } - } - } - - lock (account_folderinfo) { - account_folderinfo.set (account, folderinfo); - } - update_infos (); - } - - private void removed_account (Mail.Backend.Account account) { - lock (account_folderinfo) { - account_folderinfo.unset (account); - } - } - - private void update_infos () { - badge = null; - var total_unread = 0; - lock (account_folderinfo) { - foreach (var entry in account_folderinfo) { - if (entry.value == null) { - continue; - } - total_unread += entry.value.unread; - } - } - - if (total_unread > 0) { - badge = "%d".printf (total_unread); - } - } - - private string? build_folder_full_name (Backend.Account account) { - var service_source = session.ref_source (account.service.uid); - if (service_source == null || !service_source.has_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT)) { - return null; - } - - var mail_account_extension = (E.SourceMailAccount) service_source.get_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT); - if (Camel.FolderInfoFlags.TYPE_INBOX == (folder_type & Camel.FOLDER_TYPE_MASK)) { - if ("ews".ascii_casecmp (mail_account_extension.backend_name) == 0) { - return "Inbox"; - } - return "INBOX"; - } - - if (Camel.FolderInfoFlags.TYPE_ARCHIVE == (folder_type & Camel.FOLDER_TYPE_MASK)) { - return Utils.strip_folder_full_name (account.service.uid, mail_account_extension.dup_archive_folder ()); - } - - var identity_uid = mail_account_extension.dup_identity_uid (); - var identity_source = session.ref_source (identity_uid); - - if ( - Camel.FolderInfoFlags.TYPE_SENT == (folder_type & Camel.FOLDER_TYPE_MASK) - && - identity_source.has_extension (E.SOURCE_EXTENSION_MAIL_SUBMISSION) - ) { - unowned var mail_submission_extension = (E.SourceMailSubmission) identity_source.get_extension (E.SOURCE_EXTENSION_MAIL_SUBMISSION); - return Utils.strip_folder_full_name (account.service.uid, mail_submission_extension.sent_folder); - } - - return null; - } -} diff --git a/src/FoldersView/SessionSourceItem.vala b/src/FoldersView/SessionSourceItem.vala deleted file mode 100644 index 460ff4ba9..000000000 --- a/src/FoldersView/SessionSourceItem.vala +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright 2021 elementary, Inc. (https://elementary.io) -* -* 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 3 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, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -*/ - - public class Mail.SessionSourceItem : Mail.SourceList.ExpandableItem { - public Mail.Backend.Session session { get; construct; } - - public SessionSourceItem (Mail.Backend.Session session) { - Object (session: session); - } - - construct { - name = _("All Mailboxes"); - visible = session.get_accounts ().size > 1; - expanded = true; - collapsible = false; - - add (new GroupedFolderSourceItem (session, Camel.FolderInfoFlags.TYPE_INBOX)); - add (new GroupedFolderSourceItem (session, Camel.FolderInfoFlags.TYPE_ARCHIVE)); - add (new GroupedFolderSourceItem (session, Camel.FolderInfoFlags.TYPE_SENT)); - - session.account_added.connect (added_account); - session.account_removed.connect (removed_account); - } - - private void added_account (Mail.Backend.Account account) { - if (session.get_accounts ().size > 1) { - visible = true; - } - } - - private void removed_account (Mail.Backend.Account account) { - if (session.get_accounts ().size < 2) { - visible = false; - } - } -} diff --git a/src/MessageList/GravatarIcon.vala b/src/MessageList/GravatarIcon.vala deleted file mode 100644 index f5d01d8fe..000000000 --- a/src/MessageList/GravatarIcon.vala +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright 2021 elementary, Inc. (https://elementary.io) -* -* 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 3 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, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -*/ - -public class GravatarIcon: Object, Icon, LoadableIcon { - - public string address { get; construct; } - public int scale { get; construct; } - - public GravatarIcon (string address, int scale) { - Object (address: address, scale: scale); - } - - public bool equal (Icon? icon) { - var gravatar_icon = (GravatarIcon?) icon; - if (gravatar_icon == null) { - return false; - } - return address == gravatar_icon.address && scale == gravatar_icon.scale; - } - - public uint hash () { - return "%s-@%i".printf (address, scale).hash (); - } - - public InputStream load (int size, out string? type, Cancellable? cancellable = null) throws Error { - var uri = "https://secure.gravatar.com/avatar/%s?d=404&s=%d".printf ( - Checksum.compute_for_string (ChecksumType.MD5, address.strip ().down ()), - size * scale - ); - type = null; - var server_file = File.new_for_uri (uri); - var path = Path.build_filename (Environment.get_tmp_dir (), server_file.get_basename ()); - var local_file = File.new_for_path (path); - - if (!local_file.query_exists (cancellable)) { - server_file.copy (local_file, FileCopyFlags.OVERWRITE, cancellable, null); - } - - return local_file.read (); - } - - public async InputStream load_async (int size, Cancellable? cancellable = null, out string? type = null) throws Error { - return load (size, out type, cancellable); - } -} diff --git a/src/WebView.vala b/src/WebView.vala index e30e9363c..951cb77b1 100644 --- a/src/WebView.vala +++ b/src/WebView.vala @@ -26,12 +26,9 @@ public class Mail.WebView : WebKit.WebView { public signal void selection_changed (); public signal void load_finished (); - public bool body_html_changed { get; private set; default = false; } - private const string INTERNAL_URL_BODY = "elementary-mail:body"; private const string SERVER_BUS_NAME = "io.elementary.mail.WebViewServer"; - private int preferred_height = 0; private Gee.Map internal_resources; private bool loaded = false; @@ -55,16 +52,13 @@ public class Mail.WebView : WebKit.WebView { construct { cancellable = new GLib.Cancellable (); - expand = true; + vexpand = true; + hexpand = true; internal_resources = new Gee.HashMap (); decide_policy.connect (on_decide_policy); load_changed.connect (on_load_changed); - - key_release_event.connect (() => { - body_html_changed = true; - }); } public WebView () { @@ -93,8 +87,7 @@ public class Mail.WebView : WebKit.WebView { send_message_to_page.begin (message, cancellable, (obj, res) => { try { var response = send_message_to_page.end (res); - preferred_height = response.parameters.get_int32 (); - queue_resize (); + height_request = response.parameters.get_int32 (); //@TODO: Needs refinement: On a quick switch of message this doesn't update correctly } catch (Error e) { // We can cancel the operation if (!(e is GLib.IOError.CANCELLED)) { @@ -118,10 +111,6 @@ public class Mail.WebView : WebKit.WebView { } } - public override void get_preferred_height (out int minimum_height, out int natural_height) { - minimum_height = natural_height = preferred_height; - } - public new void load_html (string? body) { base.load_html (body, INTERNAL_URL_BODY); } @@ -184,7 +173,6 @@ public class Mail.WebView : WebKit.WebView { public void execute_editor_command (string command, string argument = "") { var message = new WebKit.UserMessage ("execute-editor-command", new Variant ("(ss)", command, argument)); send_message_to_page.begin (message, cancellable); - body_html_changed = true; } public async bool query_command_state (string command) { @@ -279,3 +267,4 @@ public class Mail.WebView : WebKit.WebView { return false; } } + diff --git a/src/meson.build b/src/meson.build index a4e503f90..3bd22f49b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,22 +15,21 @@ vala_files = files( 'ConversationList/ConversationListItem.vala', 'ConversationList/ConversationListStore.vala', 'Dialogs/InsertLinkDialog.vala', - 'FoldersView/AccountSavedState.vala', - 'FoldersView/AccountSourceItem.vala', - 'FoldersView/FoldersListView.vala', - 'FoldersView/FolderSourceItem.vala', - 'FoldersView/GroupedFolderSourceItem.vala', - 'FoldersView/SessionSourceItem.vala', + 'FolderList/FolderList.vala', + 'FolderList/FolderListItem.vala', + 'FolderList/AccountItemModel.vala', + 'FolderList/FolderItemModel.vala', + 'FolderList/SessionItemModel.vala', + 'FolderList/GroupedFolderItemModel.vala', 'MessageList/AttachmentButton.vala', - 'MessageList/GravatarIcon.vala', 'MessageList/MessageList.vala', 'MessageList/MessageListItem.vala', - 'SourceList/CellRendererBadge.vala', - 'SourceList/CellRendererExpander.vala', - 'SourceList/SourceList.vala', - 'VirtualizingListBox/VirtualizingListBoxModel.vala', - 'VirtualizingListBox/VirtualizingListBoxRow.vala', - 'VirtualizingListBox/VirtualizingListBox.vala' + # 'SourceList/CellRendererBadge.vala', + # 'SourceList/CellRendererExpander.vala', + # 'SourceList/SourceList.vala', + # 'VirtualizingListBox/VirtualizingListBoxModel.vala', + # 'VirtualizingListBox/VirtualizingListBoxRow.vala', + # 'VirtualizingListBox/VirtualizingListBox.vala' ) executable( From 79941103504f10baef2d22bfefcbff4e23e12f73 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:51:50 +0200 Subject: [PATCH 60/72] Update meson + mail page --- webkit-extension/MailPage.vala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/webkit-extension/MailPage.vala b/webkit-extension/MailPage.vala index 08a798420..e0881cad8 100644 --- a/webkit-extension/MailPage.vala +++ b/webkit-extension/MailPage.vala @@ -116,7 +116,6 @@ public class Mail.Page : Object { private bool on_send_request (WebKit.WebPage page, WebKit.URIRequest request, WebKit.URIResponse? response) { bool should_load = false; -#if HAS_SOUP_3 GLib.Uri? uri = null; try { uri = GLib.Uri.parse (request.get_uri (), GLib.UriFlags.NONE); @@ -124,9 +123,6 @@ public class Mail.Page : Object { warning ("Could not parse uri: %s", e.message); return should_load; } -#else - Soup.URI? uri = new Soup.URI (request.get_uri ()); -#endif if (uri != null && uri.get_scheme () in ALLOWED_SCHEMES) { // Always load internal resources should_load = true; From 4b081d49ed99e500b1b2dc6e1b77d656513c3e66 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:52:15 +0200 Subject: [PATCH 61/72] Update meson --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e00faf69b..3e2a9c334 100644 --- a/meson.build +++ b/meson.build @@ -11,7 +11,7 @@ add_project_arguments(['--vapidir', join_paths(meson.current_source_dir(), 'vapi glib_dep = dependency('glib-2.0') gobject_dep = dependency('gobject-2.0') -granite_dep = dependency('granite-7') +granite_dep = dependency('granite-7', version: '>=7.1') gee_dep = dependency('gee-0.8') adw_dep = dependency('libadwaita-1') gtk_dep = dependency('gtk4') From 9bf3b5c2d9b805fabd9d815c40cae4d68e93483c Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 18:53:00 +0200 Subject: [PATCH 62/72] Remove more unused stuff --- src/SourceList/CellRendererBadge.vala | 123 - src/SourceList/CellRendererExpander.vala | 104 - src/SourceList/SourceList.vala | 2599 ----------------- .../VirtualizingListBox.vala | 881 ------ .../VirtualizingListBoxModel.vala | 145 - .../VirtualizingListBoxRow.vala | 48 - src/meson.build | 8 +- 7 files changed, 1 insertion(+), 3907 deletions(-) delete mode 100644 src/SourceList/CellRendererBadge.vala delete mode 100644 src/SourceList/CellRendererExpander.vala delete mode 100644 src/SourceList/SourceList.vala delete mode 100644 src/VirtualizingListBox/VirtualizingListBox.vala delete mode 100644 src/VirtualizingListBox/VirtualizingListBoxModel.vala delete mode 100644 src/VirtualizingListBox/VirtualizingListBoxRow.vala diff --git a/src/SourceList/CellRendererBadge.vala b/src/SourceList/CellRendererBadge.vala deleted file mode 100644 index 0dd449252..000000000 --- a/src/SourceList/CellRendererBadge.vala +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2019 elementary, Inc. (https://elementary.io) - * Copyright 2012–2013 Victor Eduardo - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -/** - * A badge renderer. - * - * Informs the user quickly on the content of the corresponding view. For example - * it might be used to show how much songs are in a playlist or how much updates - * are available. - * - * {{../doc/images/cellrendererbadge.png}} - * - * @since 0.2 - */ -public class Mail.CellRendererBadge : Gtk.CellRenderer { - public string text { get; set; default = ""; } - - private Pango.Rectangle text_logical_rect; - private Pango.Layout text_layout; - private Gtk.Border margin; - private Gtk.Border padding; - private Gtk.Border border; - - public override Gtk.SizeRequestMode get_request_mode () { - return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; - } - - public override void get_preferred_width ( - Gtk.Widget widget, - out int minimum_size, - out int natural_size - ) { - update_layout_properties (widget); - - int width = text_logical_rect.width; - width += margin.left + margin.right; - width += padding.left + padding.right; - width += border.left + border.right; - - minimum_size = natural_size = width + 2 * (int) xpad; - } - - public override void get_preferred_height_for_width ( - Gtk.Widget widget, int width, - out int minimum_height, - out int natural_height - ) { - update_layout_properties (widget); - - int height = text_logical_rect.height; - height += margin.top + margin.bottom; - height += padding.top + padding.bottom; - height += border.top + border.bottom; - - minimum_height = natural_height = height + 2 * (int) ypad; - } - - private void update_layout_properties (Gtk.Widget widget) { - var ctx = widget.get_style_context (); - ctx.save (); - - // Add class before creating the pango layout and fetching paddings. - // This is needed in order to fetch the proper style information. - ctx.add_class (Granite.STYLE_CLASS_BADGE); - - var state = ctx.get_state (); - - margin = ctx.get_margin (state); - padding = ctx.get_padding (state); - border = ctx.get_border (state); - - text_layout = widget.create_pango_layout (text); - - ctx.restore (); - - Pango.Rectangle ink_rect; - text_layout.get_pixel_extents (out ink_rect, out text_logical_rect); - } - - public override void render ( - Cairo.Context context, - Gtk.Widget widget, - Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, - Gtk.CellRendererState flags - ) { - update_layout_properties (widget); - - Gdk.Rectangle aligned_area = get_aligned_area (widget, flags, cell_area); - - int x = aligned_area.x; - int y = aligned_area.y; - int width = aligned_area.width; - int height = aligned_area.height; - - // Apply margin - x += margin.right; - y += margin.top; - width -= margin.left + margin.right; - height -= margin.top + margin.bottom; - - var ctx = widget.get_style_context (); - ctx.add_class (Granite.STYLE_CLASS_BADGE); - - ctx.render_background (context, x, y, width, height); - ctx.render_frame (context, x, y, width, height); - - // Apply border width and padding offsets - x += border.right + padding.right; - y += border.top + padding.top; - width -= border.left + border.right + padding.left + padding.right; - height -= border.top + border.bottom + padding.top + padding.bottom; - - // Center text - x += text_logical_rect.x + (width - text_logical_rect.width) / 2; - y += text_logical_rect.y + (height - text_logical_rect.height) / 2; - - ctx.render_layout (context, x, y, text_layout); - } -} diff --git a/src/SourceList/CellRendererExpander.vala b/src/SourceList/CellRendererExpander.vala deleted file mode 100644 index 3a2393012..000000000 --- a/src/SourceList/CellRendererExpander.vala +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2012–2019 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -/** - * An expander renderer. - * - * For it to draw an expander, the the {@link Gtk.CellRenderer.is_expander} property must - * be set to true; otherwise nothing is drawn. The state of the expander (i.e. expanded or - * collapsed) is controlled by the {@link Gtk.CellRenderer.is_expanded} property. - * - * @since 0.2 - */ -public class Mail.CellRendererExpander : Gtk.CellRenderer { - public bool is_category_expander { get; set; default = false; } - - public override Gtk.SizeRequestMode get_request_mode () { - return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; - } - - public override void get_preferred_width ( - Gtk.Widget widget, - out int minimum_size, - out int natural_size - ) { - apply_style_changes (widget); - minimum_size = natural_size = get_arrow_size (widget) + 2 * (int) xpad; - revert_style_changes (widget); - } - - public override void get_preferred_height_for_width ( - Gtk.Widget widget, int width, - out int minimum_height, - out int natural_height - ) { - apply_style_changes (widget); - minimum_height = natural_height = get_arrow_size (widget) + 2 * (int) ypad; - revert_style_changes (widget); - } - - /** - * Gets the size of the expander arrow. - * - * The default implementation tries to retrieve the "expander-size" style property from - * //widget//, as it is primarily meant to be used along with a {@link Gtk.TreeView}. - * For those with special needs, it is recommended to override this method. - * - * @param widget Widget used to query the "expander-size" style property (should be a Gtk.TreeView.) - * @return Size of the expander arrow. - * @since 0.2 - */ - public virtual int get_arrow_size (Gtk.Widget widget) { - int arrow_size; - widget.style_get ("expander-size", out arrow_size); - return arrow_size; - } - - public override void render ( - Cairo.Context context, - Gtk.Widget widget, - Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, - Gtk.CellRendererState flags - ) { - if (!is_expander) { - return; - } - - unowned Gtk.StyleContext ctx = apply_style_changes (widget); - - Gdk.Rectangle aligned_area = get_aligned_area (widget, flags, cell_area); - - int arrow_size = int.min (get_arrow_size (widget), aligned_area.width); - - int offset = arrow_size / 2; - int x = aligned_area.x + aligned_area.width / 2 - offset; - int y = aligned_area.y + aligned_area.height / 2 - offset; - - var state = ctx.get_state (); - const Gtk.StateFlags EXPANDED_FLAG = Gtk.StateFlags.CHECKED; - ctx.set_state (is_expanded ? state | EXPANDED_FLAG : state & ~EXPANDED_FLAG); - - ctx.render_expander (context, x, y, arrow_size, arrow_size); - - revert_style_changes (widget); - } - - private unowned Gtk.StyleContext apply_style_changes (Gtk.Widget widget) { - unowned Gtk.StyleContext ctx = widget.get_style_context (); - ctx.save (); - - if (is_category_expander) - ctx.add_class (Granite.STYLE_CLASS_CATEGORY_EXPANDER); - else - ctx.add_class (Gtk.STYLE_CLASS_EXPANDER); - - return ctx; - } - - private void revert_style_changes (Gtk.Widget widget) { - widget.get_style_context ().restore (); - } -} diff --git a/src/SourceList/SourceList.vala b/src/SourceList/SourceList.vala deleted file mode 100644 index 24a10c285..000000000 --- a/src/SourceList/SourceList.vala +++ /dev/null @@ -1,2599 +0,0 @@ -/* - * Copyright 2019 elementary, Inc. (https://elementary.io) - * Copyright 2012-2014 Victor Martinez - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -/** - * An interface for sorting items. - * - * @since 0.3 - */ -private interface Mail.SourceListSortable : Mail.SourceList.ExpandableItem { - /** - * Emitted after a user has re-ordered an item via DnD. - * - * @param moved The item that was moved to a different position by the user. - * @since 0.3 - */ - public signal void user_moved_item (SourceList.Item moved); - - /** - * Whether this item will allow users to re-arrange its children via DnD. - * - * This feature can co-exist with a sort algorithm (implemented - * by {@link Granite.Widgets.SourceListSortable.compare}), but - * the actual order of the items in the list will always - * honor that method. The sort function has to be compatible with - * the kind of DnD reordering the item wants to allow, since the user can - * only reorder those items for which //compare// returns 0. - * - * @return Whether the item's children can be re-arranged by users. - * @since 0.3 - */ - public abstract bool allow_dnd_sorting (); - - /** - * Should return a negative integer, zero, or a positive integer if ''a'' - * sorts //before// ''b'', ''a'' sorts //with// ''b'', or ''a'' sorts - * //after// ''b'' respectively. If two items compare as equal, their - * order in the sorted source list is undefined. - * - * In order to ensure that the source list behaves as expected, this - * method must define a partial order on the source list tree; i.e. it - * must be reflexive, antisymmetric and transitive. Not complying with - * those requirements could make the program fall into an infinite loop - * and freeze the user interface. - * - * Should return //0// to allow any pair of items to be sortable via DnD. - * - * @param a First item. - * @param b Second item. - * @return A //negative// integer if //a// sorts before //b//, - * //zero// if //a// equals //b//, or a //positive// - * integer if //a// sorts after //b//. - * @since 0.3 - */ - public abstract int compare (SourceList.Item a, SourceList.Item b); -} - -/** - * An interface for dragging items out of the source list widget. - * - * @since 0.3 - */ -public interface Mail.SourceListDragSource : Mail.SourceList.Item { - /** - * Determines whether this item can be dragged outside the source list widget. - * - * Even if this method returns //false//, the item could still be dragged around - * within the source list if its parent allows DnD reordering. This only happens - * when the parent implements {@link Granite.Widgets.SourceListSortable}. - * - * @return //true// if the item can be dragged; //false// otherwise. - * @since 0.3 - * @see Granite.Widgets.SourceListSortable - */ - public abstract bool draggable (); - - /** - * This method is called when the drop site requests the data which is dragged. - * - * It is the responsibility of this method to fill //selection_data// with the - * data in the format which is indicated by {@link Gtk.SelectionData.get_target}. - * - * @param selection_data {@link Gtk.SelectionData} containing source data. - * @since 0.3 - * @see Gtk.SelectionData.set - * @see Gtk.SelectionData.set_uris - * @see Gtk.SelectionData.set_text - */ - public abstract void prepare_selection_data (Gtk.SelectionData selection_data); -} - -/** - * An interface for receiving data from other widgets via drag-and-drop. - * - * @since 0.3 - */ -public interface Mail.SourceListDragDest : Mail.SourceList.Item { - /** - * Determines whether //data// can be dropped into this item. - * - * @param context The drag context. - * @param data {@link Gtk.SelectionData} containing source data. - * @return //true// if the drop is possible; //false// otherwise. - * @since 0.3 - */ - public abstract bool data_drop_possible (Gdk.DragContext context, Gtk.SelectionData data); - - /** - * If a data drop is deemed possible, then this method is called - * when the data is actually dropped into this item. Any actions - * consequence of the data received should be handled here. - * - * @param context The drag context. - * @param data {@link Gtk.SelectionData} containing source data. - * @return The action taken, or //0// to indicate that the dropped data was not accepted. - * @since 0.3 - */ - public abstract Gdk.DragAction data_received (Gdk.DragContext context, Gtk.SelectionData data); -} - -/** - * A widget that can display a list of items organized in categories. - * - * The source list widget consists of a collection of items, some of which are also expandable (and - * thus can contain more items). All the items displayed in the source list are children of the widget's - * root item. The API is meant to be used as follows: - * - * 1. Create the items you want to display in the source list, setting the appropriate values for their - * properties. The desired hierarchy is achieved by creating expandable items and adding items to them. - * These will be displayed as descendants in the widget's tree structure. The expandable items that are - * not nested inside any other item are considered to be at root level, and should be added to - * the widget's root item.<
> - * - * Expandable items located at the root level are treated as categories, and only support text. - * - * ''Example''<
> - * The final tree will have the following structure: - * {{{ - * Libraries - * Music - * Stores - * My Store - * Music - * Podcasts - * Devices - * Player 1 - * Player 2 - * }}} - * - * {{{ - * var library_category = new Granite.Widgets.SourceList.ExpandableItem ("Libraries"); - * var store_category = new Granite.Widgets.SourceList.ExpandableItem ("Stores"); - * var device_category = new Granite.Widgets.SourceList.ExpandableItem ("Devices"); - * - * var music_item = new Granite.Widgets.SourceList.Item ("Music"); - * - * // "Libraries" will be the parent category of "Music" - * library_category.add (music_item); - * - * // We plan to add sub-items to the store, so let's use an expandable item - * var my_store_item = new Granite.Widgets.SourceList.ExpandableItem ("My Store"); - * store_category.add (my_store_item); - * - * var my_store_podcast_item = new Granite.Widgets.SourceList.Item ("Podcasts"); - * var my_store_music_item = new Granite.Widgets.SourceList.Item ("Music"); - * - * my_store_item.add (my_store_music_item); - * my_store_item.add (my_store_podcast_item); - * - * var player1_item = new Granite.Widgets.SourceList.Item ("Player 1"); - * var player2_item = new Granite.Widgets.SourceList.Item ("Player 2"); - * - * device_category.add (player1_item); - * device_category.add (player2_item); - * }}} - * - * 2. Create a source list widget.<
> - * {{{ - * var source_list = new Granite.Widgets.SourceList (); - * }}} - * - * 3. Add root-level items to the {@link Granite.Widgets.SourceList.root} item. - * This item only serves as a container, and all its properties are ignored by the widget. - * - * {{{ - * // This will add the main categories (including their children) to the source list. After - * // having being added to be widget, any other item added to any of these items - * // (or any other child item in a deeper level) will be automatically added too. - * // There's no need to deal with the source list widget directly. - * - * var root = source_list.root; - * - * root.add (library_category); - * root.add (store_category); - * root.add (device_category); - * }}} - * - * The steps mentioned above are enough for initializing the source list. Future changes to the items' - * properties are ''automatically'' reflected by the widget. - * - * Final steps would involve connecting handlers to the source list events, being - * {@link Granite.Widgets.SourceList.item_selected} the most important, as it indicates that - * the selection was modified. - * - * Pack the source list into the GUI using the {@link Gtk.Paned} widget. - * This is usually done as follows: - * {{{ - * var pane = new Gtk.Paned (Gtk.Orientation.HORIZONTAL); - * pane.pack1 (source_list, false, false); - * pane.pack2 (content_area, true, false); - * }}} - * - * @since 0.2 - * @see Gtk.Paned - */ -public class Mail.SourceList : Gtk.ScrolledWindow { - - /** - * = WORKING INTERNALS = - * - * In order to offer a transparent Item-based API, and avoid the need of providing methods - * to deal with items directly on the SourceList widget, it was decided to follow a monitor-like - * implementation, where the source list permanently monitors its root item and any other - * child item added to it. The task of monitoring the properties of the items has been - * divided among different objects, as shown below: - * - * Monitored by: Object::method that receives the signals indicating the property change. - * Applied by: Object::method that actually updates the tree to reflect the property changes - * (directly or indirectly, as in the case of the tree data model). - * - * --------------------------------------------------------------------------------------------- - * PROPERTY | MONITORED BY | APPLIED BY - * --------------------------------------------------------------------------------------------- - * + Item | | - * - parent | Not monitored | N/A - * - name | DataModel::on_item_prop_changed | Tree::name_cell_data_func - * - editable | DataModel::on_item_prop_changed | Queried when needed (See Tree::start_editing_item) - * - visible | DataModel::on_item_prop_changed | DataModel::filter_visible_func - * - icon | DataModel::on_item_prop_changed | Tree::icon_cell_data_func - * - activatable | Same as @icon | Same as @icon - * + ExpandableItem | | - * - collapsible | DataModel::on_item_prop_changed | Tree::update_expansion - * | | Tree::expander_cell_data_func - * - expanded | Same as @collapsible | Same as @collapsible - * --------------------------------------------------------------------------------------------- - * * Only automatic properties are monitored. ExpandableItem's additions/removals are handled by - * DataModel::add_item() and DataModel::remove_item() - * - * Other features: - * - Sorting: this happens on the tree-model level (DataModel). - */ - - - - /** - * A source list entry. - * - * Any change made to any of its properties will be ''automatically'' reflected - * by the {@link Granite.Widgets.SourceList} widget. - * - * @since 0.2 - */ - public class Item : Object { - - /** - * Emitted when the user has finished editing the item's name. - * - * By default, if the name doesn't consist of white space, it is automatically assigned - * to the {@link Granite.Widgets.SourceList.Item.name} property. The default behavior can - * be changed by overriding this signal. - * @param new_name The item's new name (result of editing.) - * @since 0.2 - */ - public virtual signal void edited (string new_name) { - if (editable && new_name.strip () != "") - this.name = new_name; - } - - /** - * The {@link Granite.Widgets.SourceList.Item.activatable} icon was activated. - * - * @see Granite.Widgets.SourceList.Item.activatable - * @since 0.2 - */ - public virtual signal void action_activated () { } - - /** - * Emitted when the item is double-clicked or when it is selected and one of the keys: - * Space, Shift+Space, Return or Enter is pressed. This signal is //also// for - * editable items. - * - * @since 0.2 - */ - public virtual signal void activated () { } - - /** - * Parent {@link Granite.Widgets.SourceList.ExpandableItem} of the item. - * ''Must not'' be modified. - * - * @since 0.2 - */ - public ExpandableItem parent { get; internal set; } - - /** - * The item's name. Primary and most important information. - * - * @since 0.2 - */ - public string name { get; set; default = ""; } - - /** - * The item's tooltip. If set to null (default), the tooltip for the item will be the - * contents of the {@link Granite.Widgets.SourceList.Item.name} property. - * - * @since 5.3 - */ - public string? tooltip { get; set; default = null; } - - /** - * Markup to be used instead of {@link Granite.Widgets.SourceList.ExpandableItem.name} - * This would mean that &, <, etc have to be escaped in the text, but basic formatting - * can be done on the item with HTML style tags. - * - * Note: Only the {@link Granite.Widgets.SourceList.ExpandableItem.name} property - * is modified for editable items. So this property will be need to updated and - * reformatted with editable items. - * - * @since 5.0 - */ - public string? markup { get; set; default = null; } - - /** - * A badge shown next to the item's name. - * - * It can be used for displaying the number of unread messages in the "Inbox" item, - * for instance. - * - * @since 0.2 - */ - public string badge { get; set; default = ""; } - - /** - * Whether the item's name can be edited from within the source list. - * - * When this property is set to //true//, users can edit the item by pressing - * the F2 key, or by double-clicking its name. - * - * ''This property only works for selectable items''. - * - * @see Granite.Widgets.SourceList.Item.selectable - * @see Granite.Widgets.SourceList.start_editing_item - * @since 0.2 - */ - public bool editable { get; set; default = false; } - - /** - * Whether the item should appear in the source list's tree or not. - * - * @since 0.2 - */ - public bool visible { get; set; default = true; } - - /** - * Whether the item can be selected or not. - * - * Setting this property to true doesn't guarantee that the item will actually be - * selectable, since there are other external factors to take into account, like the - * item's {@link Granite.Widgets.SourceList.Item.visible} property; whether the item is - * a category; the parent item is collapsed, etc. - * - * @see Granite.Widgets.SourceList.Item.visible - * @since 0.2 - */ - public bool selectable { get; set; default = true; } - - /** - * Primary icon. - * - * This property should be used to give the user an idea of what the item represents - * (i.e. content type.) - * - * @since 0.2 - */ - public Icon icon { get; set; } - - /** - * An activatable icon that works like a button. - * - * It can be used for e.g. showing an //"eject"// icon on a device's item. - * - * @see Granite.Widgets.SourceList.Item.action_activated - * @since 0.2 - */ - public Icon activatable { get; set; } - - /** - * The tooltip for the activatable icon. - * - * @since 5.0 - */ - public string activatable_tooltip { get; set; default = ""; } - - /** - * Creates a new {@link Granite.Widgets.SourceList.Item}. - * - * @param name Name of the item. - * @return (transfer full) A new {@link Granite.Widgets.SourceList.Item}. - * @since 0.2 - */ - private Item (string name = "") { - this.name = name; - } - - /** - * Invoked when the item is secondary-clicked or when the usual menu keys are pressed. - * - * Note that since Granite 5.0, right clicking on an item no longer selects/activates it, so - * any context menu items should be actioned on the item instance rather than the selected item - * in the SourceList - * - * @return A {@link Gtk.Menu} or //null// if nothing should be displayed. - * @since 0.2 - */ - public virtual Gtk.Menu? get_context_menu () { - return null; - } - } - - - - /** - * An item that can contain more items. - * - * It supports all the properties inherited from {@link Granite.Widgets.SourceList.Item}, - * and behaves like a normal item, except when it is located at the root level; in that case, - * the following properties are ignored by the widget: - * - * * {@link Granite.Widgets.SourceList.Item.selectable} - * * {@link Granite.Widgets.SourceList.Item.editable} - * * {@link Granite.Widgets.SourceList.Item.icon} - * * {@link Granite.Widgets.SourceList.Item.activatable} - * * {@link Granite.Widgets.SourceList.Item.badge} - * - * Root-level expandable items (i.e. Main Categories) are ''not'' displayed when they contain - * zero visible children. - * - * @since 0.2 - */ - public class ExpandableItem : Item { - - /** - * Emitted when an item is added. - * - * @param item Item added. - * @see Granite.Widgets.SourceList.ExpandableItem.add - * @since 0.2 - */ - public signal void child_added (Item item); - - /** - * Emitted when an item is removed. - * - * @param item Item removed. - * @see Granite.Widgets.SourceList.ExpandableItem.remove - * @since 0.2 - */ - public signal void child_removed (Item item); - - /** - * Emitted when the item is expanded or collapsed. - * - * @since 0.2 - */ - public virtual signal void toggled () { } - - /** - * Whether the item is collapsible or not. - * - * When set to //false//, the item is //always// expanded and the expander is - * not shown. Please note that this will also affect the value returned by the - * {@link Granite.Widgets.SourceList.ExpandableItem.expanded} property. - * - * @see Granite.Widgets.SourceList.ExpandableItem.expanded - * @since 0.2 - */ - public bool collapsible { get; set; default = true; } - - /** - * Whether the item is expanded or not. - * - * The source list widget will obey the value of this property when possible. - * - * This property has no effect when {@link Granite.Widgets.SourceList.ExpandableItem.collapsible} - * is set to //false//. Also keep in mind that, __when set to //true//__, this property - * doesn't always represent the actual expansion state of an item. For example, it might - * be the case that an expandable item is collapsed because it has zero visible children, - * but its //expanded// property value is still //true//; in such case, once one of the - * item's children becomes visible, the item will be expanded again. Same applies to items - * hidden behind a collapsed parent item. - * - * If obtaining the ''actual'' expansion state of an item is important, - * use {@link Granite.Widgets.SourceList.is_item_expanded} instead. - * - * @see Granite.Widgets.SourceList.ExpandableItem.collapsible - * @see Granite.Widgets.SourceList.is_item_expanded - * @since 0.2 - */ - private bool _expanded = false; - public bool expanded { - get { return _expanded || !collapsible; } // if not collapsible, always return true - set { - if (value != _expanded) { - _expanded = value; - toggled (); - } - } - } - - /** - * Number of children contained by the item. - * - * @since 0.2 - */ - private uint n_children { - get { return children_list.size; } - } - - /** - * The item's children. - * - * This returns a newly-created list containing the children. - * It's safe to iterate it while removing items with - * {@link Granite.Widgets.SourceList.ExpandableItem.remove} - * - * @since 0.2 - */ - public Gee.Collection children { - owned get { - // Create a copy of the children so that it's safe to iterate it - // (e.g. by using foreach) while removing items. - var children_list_copy = new Gee.ArrayList (); - children_list_copy.add_all (children_list); - return children_list_copy; - } - } - - private Gee.Collection children_list = new Gee.ArrayList (); - - /** - * Creates a new {@link Granite.Widgets.SourceList.ExpandableItem} - * - * @param name Title of the item. - * @return (transfer full) A new {@link Granite.Widgets.SourceList.ExpandableItem}. - * @since 0.2 - */ - public ExpandableItem (string name = "") { - base (name); - } - - construct { - editable = false; - } - - /** - * Adds an item. - * - * {@link Granite.Widgets.SourceList.ExpandableItem.child_added} is fired after the item is added. - * - * While adding a child item, //the item it's being added to will set itself as the parent//. - * Please note that items are required to have their //parent// property set to //null// before - * being added, so make sure the item is removed from its previous parent before attempting - * to add it to another item. For instance: - * {{{ - * if (item.parent != null) - * item.parent.remove (item); // this will set item's parent to null - * new_parent.add (item); - * }}} - * - * @param item The item to add. Its parent __must__ be //null//. - * @see Granite.Widgets.SourceList.ExpandableItem.child_added - * @see Granite.Widgets.SourceList.ExpandableItem.remove - * @since 0.2 - */ - public void add (Item item) requires (item.parent == null) { - item.parent = this; - children_list.add (item); - child_added (item); - } - - /** - * Removes an item. - * - * The {@link Granite.Widgets.SourceList.ExpandableItem.child_removed} signal is fired - * //after removing the item//. Finally (i.e. after all the handlers have been invoked), - * the item's {@link Granite.Widgets.SourceList.Item.parent} property is set to //null//. - * This has the advantage of letting signal handlers know the parent from which //item// - * is being removed. - * - * @param item The item to remove. This will fail if item has a different parent. - * @see Granite.Widgets.SourceList.ExpandableItem.child_removed - * @see Granite.Widgets.SourceList.ExpandableItem.clear - * @since 0.2 - */ - public void remove (Item item) requires (item.parent == this) { - children_list.remove (item); - child_removed (item); - item.parent = null; - } - - /** - * Recursively expands the item along with its parent(s). - * - * @see Granite.Widgets.SourceList.ExpandableItem.expanded - * @since 0.2 - */ - public void expand_with_parents () { - // Update parent items first due to GtkTreeView's working internals: - // Expanding children before their parents would not always work, because - // they could be obscured behind a collapsed row by the time the treeview - // tries to expand them, obviously failing. - if (parent != null) - parent.expand_with_parents (); - expanded = true; - } - } - - - - /** - * The model backing the SourceList tree. - * - * It monitors item property changes, and handles children additions and removals. It also controls - * the visibility of the items based on their "visible" property, and on their number of children, - * if they happen to be categories. Its main purpose is to provide an easy and practical interface - * for sorting, adding, removing and updating items, eliminating the need of repeatedly dealing with - * the Gtk.TreeModel API directly. - */ - private class DataModel : Gtk.TreeModelFilter, Gtk.TreeDragSource, Gtk.TreeDragDest { - - /** - * An object that references a particular row in a model. This class is a wrapper built around - * Gtk.TreeRowReference, and exists with the purpose of ensuring we never use invalid tree paths - * or iters in the model, since most of these errors provoke failures due to GTK+ assertions - * or, even worse, unexpected behavior. - */ - private class NodeWrapper { - - /** - * The actual reference to the node. If is is null, it is treated as invalid. - */ - private Gtk.TreeRowReference? row_reference; - - /** - * A newly-created Gtk.TreeIter pointing to the node if it exists; null otherwise. - */ - public Gtk.TreeIter? iter { - owned get { - Gtk.TreeIter? rv = null; - - if (valid) { - var _path = this.path; - if (_path != null) { - Gtk.TreeIter _iter; - if (row_reference.get_model ().get_iter (out _iter, _path)) - rv = _iter; - } - } - - return rv; - } - } - - /** - * A newly-created Gtk.TreePath pointing to the node if it exists; null otherwise. - */ - public Gtk.TreePath? path { - owned get { return valid ? row_reference.get_path () : null; } - } - - /** - * Whether the node is valid or not. When it is not valid, no valid references are - * returned by the object to avoid errors (null is returned instead). - */ - private bool valid { - get { return row_reference != null && row_reference.valid (); } - } - - public NodeWrapper (Gtk.TreeModel model, Gtk.TreeIter iter) { - row_reference = new Gtk.TreeRowReference (model, model.get_path (iter)); - } - } - - /** - * Helper object used to monitor item property changes. - */ - private class ItemMonitor { - public signal void changed (Item self, string prop_name); - private Item item; - - public ItemMonitor (Item item) { - this.item = item; - item.notify.connect_after (on_notify); - } - - ~ItemMonitor () { - item.notify.disconnect (on_notify); - } - - private void on_notify (ParamSpec prop) { - changed (item, prop.name); - } - } - - private enum Column { - ITEM, - N_COLUMNS; - - public Type type () { - switch (this) { - case ITEM: - return typeof (Item); - - default: - assert_not_reached (); // a Type must be returned for every valid column - } - } - } - - public signal void item_updated (Item item); - - /** - * Used by push_parent_update() as key to associate the respective data to the objects. - */ - private const string ITEM_PARENT_NEEDS_UPDATE = "item-parent-needs-update"; - - private ExpandableItem _root; - - /** - * Root item. - * - * This item is not actually part of the model. It's only used as a proxy - * for adding and removing items. - */ - public ExpandableItem root { - get { return _root; } - set { - if (_root != null) { - remove_children_monitor (_root); - foreach (var item in _root.children) - remove_item (item); - } - - _root = value; - - add_children_monitor (_root); - foreach (var item in _root.children) - add_item (item); - } - } - - // This hash map stores items and their respective child node references. For that reason, the - // references it contains should only be used on the child_tree model, or converted to filter - // iters/paths using convert_child_*_to_*() before using them with the filter (i.e. this) model. - private Gee.HashMap items = new Gee.HashMap (); - - private Gee.HashMap monitors = new Gee.HashMap (); - - private Gtk.TreeStore child_tree; - private unowned SourceList.VisibleFunc? filter_func; - - construct { - child_tree = new Gtk.TreeStore (Column.N_COLUMNS, Column.ITEM.type ()); - child_model = child_tree; - virtual_root = null; - - child_tree.set_default_sort_func (child_model_sort_func); - resort (); - - set_visible_func (filter_visible_func); - } - - public bool has_item (Item item) { - return items.has_key (item); - } - - private void update_item (Item item) requires (has_item (item)) { - assert (root != null); - - // Emitting row_changed() for this item's row in the child model causes the filter - // (i.e. this model) to re-evaluate whether a row is visible or not, calling - // filter_visible_func() for that row again, and that's exactly what we want. - var node_reference = items.get (item); - if (node_reference != null) { - var path = node_reference.path; - var iter = node_reference.iter; - if (path != null && iter != null) { - child_tree.row_changed (path, iter); - item_updated (item); - } - } - } - - private void add_item (Item item) requires (!has_item (item)) { - assert (root != null); - - // Find the parent iter - Gtk.TreeIter? parent_child_iter = null, child_iter; - var parent = item.parent; - - if (parent != null && parent != root) { - // Add parent if it hasn't been added yet - if (!has_item (parent)) - add_item (parent); - - // Try to find the parent's iter - parent_child_iter = get_item_child_iter (parent); - - // Parent must have been added prior to adding this item - assert (parent_child_iter != null); - } - - child_tree.append (out child_iter, parent_child_iter); - child_tree.set (child_iter, Column.ITEM, item, -1); - - items.set (item, new NodeWrapper (child_tree, child_iter)); - - // This is equivalent to a property change. The tree still needs to update - // some of the new item's properties through this signal's handler. - item_updated (item); - - add_property_monitor (item); - - push_parent_update (parent); - - // If the item is expandable, also add children - var expandable = item as ExpandableItem; - if (expandable != null) { - foreach (var child_item in expandable.children) - add_item (child_item); - - // Monitor future additions/removals through signal handlers - add_children_monitor (expandable); - } - } - - private void remove_item (Item item) requires (has_item (item)) { - assert (root != null); - - remove_property_monitor (item); - - // get_item_child_iter() depends on items.get(item) for retrieving the right reference, - // so don't unset the item from @items yet! We first get the child iter and then - // unset the value. - var child_iter = get_item_child_iter (item); - - // Now we remove the item from the table, because that way get_item_child_iter() and - // all the methods that depend on it won't return invalid iters or items when - // called. This is important because child_tree.remove() will emit row_deleted(), - // and its handlers could potentially depend on one of the methods mentioned above. - items.unset (item); - - if (child_iter != null) - child_tree.remove (ref child_iter); - - push_parent_update (item.parent); - - // If the item is expandable, also remove children - var expandable = item as ExpandableItem; - if (expandable != null) { - // No longer monitor future additions or removals - remove_children_monitor (expandable); - - foreach (var child_item in expandable.children) - remove_item (child_item); - } - } - - private void add_property_monitor (Item item) { - var wrapper = new ItemMonitor (item); - monitors[item] = wrapper; - wrapper.changed.connect (on_item_prop_changed); - } - - private void remove_property_monitor (Item item) { - var wrapper = monitors[item]; - if (wrapper != null) - wrapper.changed.disconnect (on_item_prop_changed); - monitors.unset (item); - } - - private void add_children_monitor (ExpandableItem item) { - item.child_added.connect_after (on_item_child_added); - item.child_removed.connect_after (on_item_child_removed); - } - - private void remove_children_monitor (ExpandableItem item) { - item.child_added.disconnect (on_item_child_added); - item.child_removed.disconnect (on_item_child_removed); - } - - private void on_item_child_added (Item item) { - add_item (item); - } - - private void on_item_child_removed (Item item) { - remove_item (item); - } - - private void on_item_prop_changed (Item item, string prop_name) { - if (prop_name != "parent") - update_item (item); - } - - /** - * Pushes a call to update_item() if //parent// is not //null//. - * - * This is needed because the visibility of categories depends on their n_children property, - * and also because item expansion should be updated after adding or removing items. - * If many updates are pushed, and the item has still not been updated, only one is processed. - * This guarantees efficiency as updating a category item could trigger expensive actions. - */ - private void push_parent_update (ExpandableItem? parent) { - if (parent == null) - return; - - bool needs_update = parent.get_data (ITEM_PARENT_NEEDS_UPDATE); - - // If an update is already waiting to be processed, just return, as we - // don't need to queue another one for the same item. - if (needs_update) - return; - - var path = get_item_path (parent); - - if (path != null) { - // Let's mark this item for update - parent.set_data (ITEM_PARENT_NEEDS_UPDATE, true); - - Idle.add (() => { - if (parent != null) { - update_item (parent); - - // Already updated. No longer needs an update. - parent.set_data (ITEM_PARENT_NEEDS_UPDATE, false); - } - - return false; - }); - } - } - - /** - * Returns the Item pointed by iter, or null if the iter doesn't refer to a valid item. - */ - public Item? get_item (Gtk.TreeIter iter) { - Item? item; - get (iter, Column.ITEM, out item, -1); - return item; - } - - /** - * Returns the Item pointed by path, or null if the path doesn't refer to a valid item. - */ - public Item? get_item_from_path (Gtk.TreePath path) { - Gtk.TreeIter iter; - if (get_iter (out iter, path)) - return get_item (iter); - - return null; - } - - /** - * Returns a newly-created path pointing to the item, or null in case a valid path - * is not found. - */ - public Gtk.TreePath? get_item_path (Item item) { - Gtk.TreePath? path = null, child_path = get_item_child_path (item); - - // We want a filter path, not a child_model path - if (child_path != null) - path = convert_child_path_to_path (child_path); - - return path; - } - - /** - * External "extra" filter method. - */ - public void set_filter_func (SourceList.VisibleFunc? visible_func) { - this.filter_func = visible_func; - } - - /** - * Checks whether an item is a category (i.e. a root-level expandable item). - * The caller must pass an iter or path pointing to the item, but not both - * (one of them must be null.) - * - * TODO: instead of checking the position of the iter or path, we should simply - * check whether the item's parent is the root item and whether the item is - * expandable. We don't do so right now because vala still allows client code - * to access the Item.parent property, even though its setter is defined as internal. - */ - public bool is_category (Item item, Gtk.TreeIter? iter, Gtk.TreePath? path = null) { - bool is_category = false; - // either iter or path has to be null - if (item is ExpandableItem) { - if (iter != null) { - assert (path == null); - is_category = is_iter_at_root_level (iter); - } else { - assert (iter == null); - is_category = is_path_at_root_level (path); - } - } - return is_category; - } - - public bool is_iter_at_root_level (Gtk.TreeIter iter) { - return is_path_at_root_level (get_path (iter)); - } - - private bool is_path_at_root_level (Gtk.TreePath path) { - return path.get_depth () == 1; - } - - private void resort () { - child_tree.set_sort_column_id (Gtk.SortColumn.UNSORTED, Gtk.SortType.ASCENDING); - child_tree.set_sort_column_id (Gtk.SortColumn.DEFAULT, Gtk.SortType.ASCENDING); - } - - private int child_model_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) { - int order = 0; - - Item? item_a, item_b; - child_tree.get (a, Column.ITEM, out item_a, -1); - child_tree.get (b, Column.ITEM, out item_b, -1); - - // code should only compare items at same hierarchy level - assert (item_a.parent == item_b.parent); - - var parent = item_a.parent as SourceListSortable; - if (parent != null) - order = parent.compare (item_a, item_b); - - return order; - } - - private Gtk.TreeIter? get_item_child_iter (Item item) { - Gtk.TreeIter? child_iter = null; - - var child_node_wrapper = items.get (item); - if (child_node_wrapper != null) - child_iter = child_node_wrapper.iter; - - return child_iter; - } - - private Gtk.TreePath? get_item_child_path (Item item) { - Gtk.TreePath? child_path = null; - - var child_node_wrapper = items.get (item); - if (child_node_wrapper != null) - child_path = child_node_wrapper.path; - - return child_path; - } - - /** - * Filters the child-tree items based on their "visible" property. - */ - private bool filter_visible_func (Gtk.TreeModel child_model, Gtk.TreeIter iter) { - bool item_visible = false; - - Item? item; - child_tree.get (iter, Column.ITEM, out item, -1); - - if (item != null) { - item_visible = item.visible; - - // If the item is a category, also query the number of visible children - // because empty categories should not be displayed. - var expandable = item as ExpandableItem; - if (expandable != null && child_tree.iter_depth (iter) == 0) { - uint n_visible_children = 0; - foreach (var child_item in expandable.children) { - if (child_item.visible) - n_visible_children++; - } - item_visible = item_visible && n_visible_children > 0; - } - } - - if (filter_func != null) - item_visible = item_visible && filter_func (item); - - return item_visible; - } - - /** - * TreeDragDest implementation - */ - - private bool drag_data_received (Gtk.TreePath dest, Gtk.SelectionData selection_data) { - Gtk.TreeModel model; - Gtk.TreePath src_path; - - // Check if the user is dragging a row: - // - // Due to Gtk.TreeModelFilter's implementation of drag_data_get the values returned by - // tree_row_drag_data for GtkModel and GtkPath correspond to the child model and not the filter. - if (Gtk.tree_get_row_drag_data (selection_data, out model, out src_path) && model == child_tree) { - // get a child path representation of dest - var child_dest = convert_path_to_child_path (dest); - - if (child_dest != null) { - // New GtkTreeIters will be assigned to the rows at child_dest and its children. - if (child_tree_drag_data_received (child_dest, src_path)) - return true; - } - } - - // no new row inserted - return false; - } - - private bool child_tree_drag_data_received (Gtk.TreePath dest, Gtk.TreePath src_path) { - bool retval = false; - Gtk.TreeIter src_iter, dest_iter; - - if (!child_tree.get_iter (out src_iter, src_path)) - return false; - - var prev = dest; - - // Get the path to insert _after_ (dest is the path to insert _before_) - if (!prev.prev ()) { - // dest was the first spot at the current depth; which means - // we are supposed to prepend. - - var parent = dest; - Gtk.TreeIter? dest_parent = null; - - if (parent.up () && parent.get_depth () > 0) - child_tree.get_iter (out dest_parent, parent); - - child_tree.prepend (out dest_iter, dest_parent); - retval = true; - } else if (child_tree.get_iter (out dest_iter, prev)) { - var tmp_iter = dest_iter; - child_tree.insert_after (out dest_iter, null, tmp_iter); - retval = true; - } - - // If we succeeded in creating dest_iter, walk src_iter tree branch, - // duplicating it below dest_iter. - if (retval) { - recursive_node_copy (src_iter, dest_iter); - - // notify that the item was moved - Item item; - child_tree.get (src_iter, Column.ITEM, out item, -1); - return_val_if_fail (item != null, retval); - - // XXX Workaround: - // GtkTreeView automatically collapses expanded items that - // are dragged to a new location. Oddly, GtkTreeView doesn't fire - // 'row-collapsed' for the respective path, so we cannot keep track - // of that behavior via standard means. For now we'll just have - // our tree view check the properties of item again and ensure - // they're honored - update_item (item); - - var parent = item.parent as SourceListSortable; - return_val_if_fail (parent != null, retval); - - parent.user_moved_item (item); - } - - return retval; - } - - private void recursive_node_copy (Gtk.TreeIter src_iter, Gtk.TreeIter dest_iter) { - move_item (src_iter, dest_iter); - - Gtk.TreeIter child; - if (child_tree.iter_children (out child, src_iter)) { - // Need to create children and recurse. Note our dependence on - // persistent iterators here. - do { - Gtk.TreeIter copy; - child_tree.append (out copy, dest_iter); - recursive_node_copy (child, copy); - } while (child_tree.iter_next (ref child)); - } - } - - private void move_item (Gtk.TreeIter src_iter, Gtk.TreeIter dest_iter) { - Item item; - child_tree.get (src_iter, Column.ITEM, out item, -1); - return_if_fail (item != null); - - // update the row reference of item with the new location - child_tree.set (dest_iter, Column.ITEM, item, -1); - items.set (item, new NodeWrapper (child_tree, dest_iter)); - } - - private bool row_drop_possible (Gtk.TreePath dest, Gtk.SelectionData selection_data) { - Gtk.TreeModel model; - Gtk.TreePath src_path; - - // Check if the user is dragging a row: - // Due to Gtk.TreeModelFilter's implementation of drag_data_get the values returned by - // tree_row_drag_data for GtkModel and GtkPath correspond to the child model and not the filter. - if (!Gtk.tree_get_row_drag_data (selection_data, out model, out src_path) || model != child_tree) - return false; - - // get a representation of dest in the child model - var child_dest = convert_path_to_child_path (dest); - - // don't allow dropping an item into itself - if (child_dest == null || src_path.compare (child_dest) == 0) - return false; - - // Only allow DnD between items at the same depth (indentation level) - // This doesn't mean their parent is the same. - int src_depth = src_path.get_depth (); - int dest_depth = child_dest.get_depth (); - - if (src_depth != dest_depth) - return false; - - // no need to check dest_depth since we know its equal to src_depth - if (src_depth < 1) - return false; - - Item? parent = null; - - // if the depth is 1, we're talking about the items at root level, - // and by definition they share the same parent (root). We don't - // need to verify anything else for that specific case - if (src_depth == 1) { - parent = root; - } else { - // we verified equality above. this must be true - assert (dest_depth > 1); - - // Only allow reordering between siblings, i.e. items with the same - // parent. We don't want items to change their parent through DnD - // because that would complicate our existing APIs, and may introduce - // unpredictable behavior. - var src_indices = src_path.get_indices (); - var dest_indices = child_dest.get_indices (); - - // parent index is given by indices[depth-2], where depth > 1 - int src_parent_index = src_indices[src_depth - 2]; - int dest_parent_index = dest_indices[dest_depth - 2]; - - if (src_parent_index != dest_parent_index) - return false; - - // get parent. Note that we don't use the child path for this - var dest_parent = dest; - - if (!dest_parent.up () || dest_parent.get_depth () < 1) - return false; - - parent = get_item_from_path (dest_parent); - } - - var sortable = parent as SourceListSortable; - - if (sortable == null || !sortable.allow_dnd_sorting ()) - return false; - - var dest_item = get_item_from_path (dest); - - if (dest_item == null) - return true; - - Item? source_item = null; - var filter_src_path = convert_child_path_to_path (src_path); - - if (filter_src_path != null) - source_item = get_item_from_path (filter_src_path); - - if (source_item == null) - return false; - - // If order isn't indifferent (=0), 'dest' has to sort before 'source'. - // Otherwise we'd allow the user to move the 'source_item' to a new - // location before 'dest_item', but that location would be changed - // later by the sort function, making the whole interaction poinless. - // We better prevent such reorderings from the start by giving the - // user a visual clue about the invalid drop location. - if (sortable.compare (dest_item, source_item) >= 0) { - if (!dest.prev ()) - return true; - - // 'source_item' also has to sort 'after' or 'equal' the item currently - // preceding 'dest_item' - var dest_item_prev = get_item_from_path (dest); - - return dest_item_prev != null - && dest_item_prev != source_item - && sortable.compare (dest_item_prev, source_item) <= 0; - } - - return false; - } - - /** - * Override default implementation of TreeDragSource - * - * drag_data_delete is not overriden because the default implementation - * does exactly what we need. - */ - - private bool drag_data_get (Gtk.TreePath path, Gtk.SelectionData selection_data) { - // If we're asked for a data about a row, just have the default implementation fill in - // selection_data. Please note that it will provide information relative to child_model. - if (selection_data.get_target () == Gdk.Atom.intern_static_string ("GTK_TREE_MODEL_ROW")) - return base.drag_data_get (path, selection_data); - - // check if the item at path provides DnD source data - var drag_source_item = get_item_from_path (path) as SourceListDragSource; - if (drag_source_item != null && drag_source_item.draggable ()) { - drag_source_item.prepare_selection_data (selection_data); - return true; - } - - return false; - } - - private bool row_draggable (Gtk.TreePath path) { - if (!base.row_draggable (path)) - return false; - - var item = get_item_from_path (path); - - if (item != null) { - // check if the item's parent allows DnD sorting - var sortable_item = item.parent as SourceListSortable; - - if (sortable_item != null && sortable_item.allow_dnd_sorting ()) - return true; - - // Since the parent item does not allow DnD sorting, there's no - // reason to allow dragging it unless the row is actually draggable. - var drag_source_item = item as SourceListDragSource; - - if (drag_source_item != null && drag_source_item.draggable ()) - return true; - } - - return false; - } - } - - - /** - * Class responsible for rendering Item.icon and Item.activatable. It also - * notifies about clicks through the activated() signal. - */ - private class CellRendererIcon : Gtk.CellRendererPixbuf { - public signal void activated (string path); - - construct { - mode = Gtk.CellRendererMode.ACTIVATABLE; - stock_size = Gtk.IconSize.MENU; - } - - public override bool activate ( - Gdk.Event event, - Gtk.Widget widget, - string path, - Gdk.Rectangle background_area, - Gdk.Rectangle cell_area, - Gtk.CellRendererState flags - ) { - activated (path); - return true; - } - } - - /** - * A cell renderer that only adds space. - */ - private class CellRendererSpacer : Gtk.CellRenderer { - /** - * Indentation level represented by this cell renderer - */ - public int level { get; set; default = -1; } - - public override Gtk.SizeRequestMode get_request_mode () { - return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; - } - - public override void get_preferred_width (Gtk.Widget widget, out int min_size, out int natural_size) { - min_size = natural_size = 2 * (int) xpad; - } - - public override void get_preferred_height_for_width ( - Gtk.Widget widget, - int width, - out int min_height, - out int natural_height - ) { - min_height = natural_height = 2 * (int) ypad; - } - - public override void render ( - Cairo.Context context, - Gtk.Widget widget, - Gdk.Rectangle bg_area, - Gdk.Rectangle cell_area, - Gtk.CellRendererState flags - ) { - // Nothing to do. This renderer only adds space. - } - } - - - - /** - * The tree that actually displays the items. - * - * All the user interaction happens here. - */ - private class Tree : Gtk.TreeView { - public DataModel data_model { get; construct set; } - - public signal void item_selected (Item? item); - - public Item? selected_item { - get { return selected; } - set { set_selected (value, true); } - } - - public bool editing { - get { return text_cell.editing; } - } - - public Pango.EllipsizeMode ellipsize_mode { - get { return text_cell.ellipsize; } - set { text_cell.ellipsize = value; } - } - - private enum Column { - ITEM, - N_COLS - } - - private Item? selected; - private unowned Item? edited; - - private Gtk.Entry? editable_entry; - private Gtk.CellRendererText text_cell; - private CellRendererIcon icon_cell; - private CellRendererIcon activatable_cell; - private CellRendererBadge badge_cell; - private CellRendererExpander primary_expander_cell; - private CellRendererExpander secondary_expander_cell; - private Gee.HashMap spacer_cells; // cells used for left spacing - private bool unselectable_item_clicked = false; - - private const string DEFAULT_STYLESHEET = """ - .sidebar.badge { - border-radius: 10px; - border-width: 0; - padding: 1px 2px 1px 2px; - font-weight: bold; - } - """; - - private const string STYLE_PROP_LEVEL_INDENTATION = "level-indentation"; - private const string STYLE_PROP_LEFT_PADDING = "left-padding"; - private const string STYLE_PROP_EXPANDER_SPACING = "expander-spacing"; - - static construct { - install_style_property (new ParamSpecInt ( - STYLE_PROP_LEVEL_INDENTATION, - "Level Indentation", - "Space to add at the beginning of every indentation level. Must be an even number.", - 1, - 50, - 6, - ParamFlags.READABLE - )); - - install_style_property (new ParamSpecInt ( - STYLE_PROP_LEFT_PADDING, - "Left Padding", - "Padding added to the left side of the tree. Must be an even number.", - 1, - 50, - 4, - ParamFlags.READABLE - )); - - install_style_property (new ParamSpecInt ( - STYLE_PROP_EXPANDER_SPACING, - "Expander Spacing", - "Space added between an item and its expander. Must be an even number.", - 1, - 50, - 4, - ParamFlags.READABLE - )); - } - - public Tree (DataModel data_model) { - Object (data_model: data_model); - } - - construct { - unowned Gtk.StyleContext style_context = get_style_context (); - style_context.add_class (Gtk.STYLE_CLASS_SIDEBAR); - style_context.add_class (Granite.STYLE_CLASS_SOURCE_LIST); - - var css_provider = new Gtk.CssProvider (); - try { - css_provider.load_from_data (DEFAULT_STYLESHEET, -1); - style_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK); - } catch (Error e) { - warning ("Could not create CSS Provider: %s\nStylesheet:\n%s", e.message, DEFAULT_STYLESHEET); - } - - set_model (data_model); - - halign = valign = Gtk.Align.FILL; - expand = true; - - enable_search = false; - headers_visible = false; - enable_grid_lines = Gtk.TreeViewGridLines.NONE; - - // Deactivate GtkTreeView's built-in expander functionality - expander_column = null; - show_expanders = false; - - var item_column = new Gtk.TreeViewColumn (); - item_column.expand = true; - - insert_column (item_column, Column.ITEM); - - // Now pack the cell renderers. We insert them in reverse order (using pack_end) - // because we want to use TreeViewColumn.pack_start exclusively for inserting - // spacer cell renderers for level-indentation purposes. - // See add_spacer_cell_for_level() for more details. - - // Second expander. Used for main categories - secondary_expander_cell = new CellRendererExpander (); - secondary_expander_cell.is_category_expander = true; - secondary_expander_cell.xpad = 10; - item_column.pack_end (secondary_expander_cell, false); - item_column.set_cell_data_func (secondary_expander_cell, expander_cell_data_func); - - activatable_cell = new CellRendererIcon (); - activatable_cell.xpad = 6; - activatable_cell.activated.connect (on_activatable_activated); - item_column.pack_end (activatable_cell, false); - item_column.set_cell_data_func (activatable_cell, icon_cell_data_func); - - badge_cell = new CellRendererBadge (); - badge_cell.xpad = 1; - badge_cell.xalign = 1; - item_column.pack_end (badge_cell, false); - item_column.set_cell_data_func (badge_cell, badge_cell_data_func); - - text_cell = new Gtk.CellRendererText (); - text_cell.editable_set = true; - text_cell.editable = false; - text_cell.editing_started.connect (on_editing_started); - text_cell.editing_canceled.connect (on_editing_canceled); - text_cell.ellipsize = Pango.EllipsizeMode.END; - text_cell.xalign = 0; - item_column.pack_end (text_cell, true); - item_column.set_cell_data_func (text_cell, name_cell_data_func); - - icon_cell = new CellRendererIcon (); - icon_cell.xpad = 2; - item_column.pack_end (icon_cell, false); - item_column.set_cell_data_func (icon_cell, icon_cell_data_func); - - // First expander. Used for normal expandable items - primary_expander_cell = new CellRendererExpander (); - - int expander_spacing; - style_get (STYLE_PROP_EXPANDER_SPACING, out expander_spacing); - primary_expander_cell.xpad = expander_spacing / 2; - - item_column.pack_end (primary_expander_cell, false); - item_column.set_cell_data_func (primary_expander_cell, expander_cell_data_func); - - // Selection - var selection = get_selection (); - selection.mode = Gtk.SelectionMode.BROWSE; - selection.set_select_function (select_func); - - // Monitor item changes - enable_item_property_monitor (); - - // Add root-level indentation. New levels will be added by update_item_expansion() - add_spacer_cell_for_level (1); - - // Enable basic row drag and drop - configure_drag_source (null); - configure_drag_dest (null, 0); - - query_tooltip.connect_after (on_query_tooltip); - has_tooltip = true; - } - - ~Tree () { - disable_item_property_monitor (); - } - - public override bool drag_motion (Gdk.DragContext context, int x, int y, uint time) { - // call the base signal to get rows with children to spring open - if (!base.drag_motion (context, x, y, time)) - return false; - - Gtk.TreePath suggested_path, current_path; - Gtk.TreeViewDropPosition suggested_pos, current_pos; - - if (get_dest_row_at_pos (x, y, out suggested_path, out suggested_pos)) { - // the base implementation of drag_motion was likely to set a drop - // destination row. If that's the case, we configure the row position - // to only allow drops before or after it, but not into it - get_drag_dest_row (out current_path, out current_pos); - - if (current_path != null && suggested_path.compare (current_path) == 0) { - // If the source widget is this treeview, we assume we're - // just dragging rows around, because at the moment dragging - // rows into other rows (re-parenting) is not implemented. - var source_widget = Gtk.drag_get_source_widget (context); - bool dragging_treemodel_row = (source_widget == this); - - if (dragging_treemodel_row) { - // we don't allow DnD into other rows, only in between them - // (no row is highlighted) - if (current_pos != Gtk.TreeViewDropPosition.BEFORE) { - if (current_pos == Gtk.TreeViewDropPosition.INTO_OR_BEFORE) - set_drag_dest_row (current_path, Gtk.TreeViewDropPosition.BEFORE); - else - set_drag_dest_row (null, Gtk.TreeViewDropPosition.AFTER); - } - } else { - // for DnD originated on a different widget, we don't want to insert - // between rows, only select the rows themselves - if (current_pos == Gtk.TreeViewDropPosition.BEFORE) - set_drag_dest_row (current_path, Gtk.TreeViewDropPosition.INTO_OR_BEFORE); - else if (current_pos == Gtk.TreeViewDropPosition.AFTER) - set_drag_dest_row (current_path, Gtk.TreeViewDropPosition.INTO_OR_AFTER); - - // determine if external DnD is supported by the item at destination - var dest = data_model.get_item_from_path (current_path) as SourceListDragDest; - - if (dest != null) { - var target_list = Gtk.drag_dest_get_target_list (this); - var target = Gtk.drag_dest_find_target (this, context, target_list); - - // have 'drag_get_data' call 'drag_data_received' to determine - // if the data can actually be dropped. - context.set_data ("suggested-dnd-action", context.get_suggested_action ()); - Gtk.drag_get_data (this, context, target, time); - } else { - // dropping data here is not supported. Unset dest row - set_drag_dest_row (null, Gtk.TreeViewDropPosition.BEFORE); - } - } - } - } else { - // dropping into blank areas of SourceList is not allowed - set_drag_dest_row (null, Gtk.TreeViewDropPosition.AFTER); - return false; - } - - return true; - } - - public override void drag_data_received ( - Gdk.DragContext context, - int x, - int y, - Gtk.SelectionData selection_data, - uint info, - uint time - ) { - var target_list = Gtk.drag_dest_get_target_list (this); - var target = Gtk.drag_dest_find_target (this, context, target_list); - - if (target == Gdk.Atom.intern_static_string ("GTK_TREE_MODEL_ROW")) { - base.drag_data_received (context, x, y, selection_data, info, time); - return; - } - - Gtk.TreePath path; - Gtk.TreeViewDropPosition pos; - - if (context.get_data ("suggested-dnd-action") != 0) { - context.set_data ("suggested-dnd-action", 0); - - get_drag_dest_row (out path, out pos); - - if (path != null) { - // determine if external DnD is allowed by the item at destination - var dest = data_model.get_item_from_path (path) as SourceListDragDest; - - if (dest == null || !dest.data_drop_possible (context, selection_data)) { - // dropping data here is not allowed. unset any previously - // selected destination row - set_drag_dest_row (null, Gtk.TreeViewDropPosition.BEFORE); - Gdk.drag_status (context, 0, time); - return; - } - } - - Gdk.drag_status (context, context.get_suggested_action (), time); - } else { - if (get_dest_row_at_pos (x, y, out path, out pos)) { - // Data coming from external source/widget was dropped into this item. - // selection_data contains something other than a tree row; most likely - // we're dealing with a DnD not originated within the Source List tree. - // Let's pass the data to the corresponding item, if there's a handler. - - var drag_dest = data_model.get_item_from_path (path) as SourceListDragDest; - - if (drag_dest != null) { - var action = drag_dest.data_received (context, selection_data); - Gtk.drag_finish (context, action != 0, action == Gdk.DragAction.MOVE, time); - return; - } - } - - // failure - Gtk.drag_finish (context, false, false, time); - } - } - - public void configure_drag_source (Gtk.TargetEntry[]? src_entries) { - // Append GTK_TREE_MODEL_ROW to src_entries and src_entries to enable row DnD. - var entries = append_row_target_entry (src_entries); - - unset_rows_drag_source (); - enable_model_drag_source (Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.MOVE); - } - - public void configure_drag_dest (Gtk.TargetEntry[]? dest_entries, Gdk.DragAction actions) { - // Append GTK_TREE_MODEL_ROW to dest_entries and dest_entries to enable row DnD. - var entries = append_row_target_entry (dest_entries); - - unset_rows_drag_dest (); - - // DragAction.MOVE needs to be enabled for row drag-and-drop to work properly - enable_model_drag_dest (entries, Gdk.DragAction.MOVE | actions); - } - - private bool on_query_tooltip (int x, int y, bool keyboard_tooltip, Gtk.Tooltip tooltip) { - Gtk.TreePath path; - Gtk.TreeViewColumn column = get_column (Column.ITEM); - - get_tooltip_context (ref x, ref y, keyboard_tooltip, null, out path, null); - if (path == null) { - return false; - } - - var item = data_model.get_item_from_path (path); - if (item != null) { - bool should_show = false; - - Gdk.Rectangle start_cell_area; - get_cell_area (path, column, out start_cell_area); - - set_tooltip_row (tooltip, path); - - if (item.tooltip == null) { - tooltip.set_markup (item.name); - should_show = true; - } else if (item.tooltip != "") { - tooltip.set_markup (item.tooltip); - should_show = true; - } - - if (keyboard_tooltip) { - return should_show; - } - - if (over_cell (column, path, text_cell, x - start_cell_area.x) || - over_cell (column, path, icon_cell, x - start_cell_area.x)) { - - return should_show; - } else if (over_cell (column, path, activatable_cell, x - start_cell_area.x)) { - if (item.activatable_tooltip == "") { - return false; - } else { - tooltip.set_markup (item.activatable_tooltip); - return true; - } - } - } - - return false; - } - - private static Gtk.TargetEntry[] append_row_target_entry (Gtk.TargetEntry[]? orig) { - const Gtk.TargetEntry row_target_entry = { // vala-lint=naming-convention - "GTK_TREE_MODEL_ROW", - Gtk.TargetFlags.SAME_WIDGET, - 0 - }; - - var entries = new Gtk.TargetEntry[0]; - entries += row_target_entry; - - if (orig != null) { - foreach (var target_entry in orig) - entries += target_entry; - } - - return entries; - } - - private void enable_item_property_monitor () { - data_model.item_updated.connect_after (on_model_item_updated); - } - - private void disable_item_property_monitor () { - data_model.item_updated.disconnect (on_model_item_updated); - } - - private void on_model_item_updated (Item item) { - // Currently, all the other properties are updated automatically by the - // cell-data functions after a change in the model. - var expandable_item = item as ExpandableItem; - if (expandable_item != null) - update_expansion (expandable_item); - } - - private void add_spacer_cell_for_level ( - int level, - bool check_previous = true - ) requires (level > 0) { - if (spacer_cells == null) - spacer_cells = new Gee.HashMap (); - - if (!spacer_cells.has_key (level)) { - var spacer_cell = new CellRendererSpacer (); - spacer_cell.level = level; - spacer_cells[level] = spacer_cell; - - uint cell_xpadding; - - // The primary expander is not visible for root-level (i.e. first level) - // items, so for the second level of indentation we use a low padding - // because the primary expander will add enough space. For the root level, - // we use left_padding, and level_indentation for the remaining levels. - // The value of cell_xpadding will be allocated *twice* by the cell renderer, - // so we set the value to a half of actual (desired) value. - switch (level) { - case 1: // root - int left_padding; - style_get (STYLE_PROP_LEFT_PADDING, out left_padding); - cell_xpadding = left_padding / 2; - break; - - case 2: // second level - cell_xpadding = 0; - break; - - default: // remaining levels - int level_indentation; - style_get (STYLE_PROP_LEVEL_INDENTATION, out level_indentation); - cell_xpadding = level_indentation / 2; - break; - } - - spacer_cell.xpad = cell_xpadding; - - var item_column = get_column (Column.ITEM); - item_column.pack_start (spacer_cell, false); - item_column.set_cell_data_func (spacer_cell, spacer_cell_data_func); - - // Make sure that the previous indentation levels also exist - if (check_previous) { - for (int i = level - 1; i > 0; i--) - add_spacer_cell_for_level (i, false); - } - } - } - - /** - * Evaluates whether the item at the specified path can be selected or not. - */ - private bool select_func ( - Gtk.TreeSelection selection, - Gtk.TreeModel model, - Gtk.TreePath path, - bool path_currently_selected - ) { - bool selectable = false; - var item = data_model.get_item_from_path (path); - - if (item != null) { - // Main categories ARE NOT selectable, so check for that - if (!data_model.is_category (item, null, path)) - selectable = item.selectable; - } - - return selectable; - } - - private Gtk.TreePath? get_selected_path () { - Gtk.TreePath? selected_path = null; - Gtk.TreeSelection? selection = get_selection (); - - if (selection != null) { - Gtk.TreeModel? model; - var selected_rows = selection.get_selected_rows (out model); - if (selected_rows.length () == 1) - selected_path = selected_rows.nth_data (0); - } - - return selected_path; - } - - private void set_selected (Item? item, bool scroll_to_item) { - if (item == null) { - Gtk.TreeSelection? selection = get_selection (); - if (selection != null) - selection.unselect_all (); - - // As explained in cursor_changed(), we cannot emit signals for this special - // case from there because that wouldn't allow us to implement the behavior - // we want (i.e. restoring the old selection after expanding a previously - // collapsed category) without emitting the undesired item_selected() signal - // along the way. This special case is handled manually, because it *should* - // only happen in response to client code requests and never in response to - // user interaction. We do that here because there's no way to determine - // whether the cursor change came from code (i.e. this method) or user - // interaction from cursor_changed(). - this.selected = null; - item_selected (null); - } else if (item.selectable) { - if (scroll_to_item) - this.scroll_to_item (item); - - var to_select = data_model.get_item_path (item); - if (to_select != null) - set_cursor_on_cell (to_select, get_column (Column.ITEM), text_cell, false); - } - } - - public override void cursor_changed () { - var path = get_selected_path (); - Item? new_item = path != null ? data_model.get_item_from_path (path) : null; - - // Don't do anything if @new_item is null. - // - // The only way 'this.selected' can be null is by setting it explicitly to - // that value from client code, and thus we handle that case in set_selected(). - // THIS CANNOT HAPPEN IN RESPONSE TO USER INTERACTION. For example, if an - // item is un-selected because its parent category has been collapsed, then it will - // remain as the current selected item (not in reality, just as the value of - // this.selected) and will be re-selected after the parent is expanded again. - // THIS ALL HAPPENS SILENTLY BEHIND THE SCENES, so client code will never know - // it ever happened; the value of selected_item remains unchanged and item_selected() - // is not emitted. - if (new_item != null && new_item != this.selected) { - this.selected = new_item; - item_selected (new_item); - } - } - - public bool scroll_to_item (Item item, bool use_align = false, float row_align = 0) { - bool scrolled = false; - - var path = data_model.get_item_path (item); - if (path != null) { - scroll_to_cell (path, null, use_align, row_align, 0); - scrolled = true; - } - - return scrolled; - } - - public bool start_editing_item (Item item) requires (item.editable) requires (item.selectable) { - if (editing && item == edited) // If same item again, simply return. - return false; - - var path = data_model.get_item_path (item); - if (path != null) { - edited = item; - text_cell.editable = true; - set_cursor_on_cell (path, get_column (Column.ITEM), text_cell, true); - } else { - warning ("Could not edit \"%s\": path not found", item.name); - } - - return editing; - } - - public void stop_editing () { - if (editing && edited != null) { - var path = data_model.get_item_path (edited); - - // Setting the cursor on the same cell without starting an edit cancels any - // editing operation going on. - if (path != null) - set_cursor_on_cell (path, get_column (Column.ITEM), text_cell, false); - } - } - - private void on_editing_started (Gtk.CellEditable editable, string path) { - editable_entry = editable as Gtk.Entry; - if (editable_entry != null) { - editable_entry.editing_done.connect (on_editing_done); - editable_entry.editable = true; - } - } - - private void on_editing_canceled () { - if (editable_entry != null) { - editable_entry.editable = false; - editable_entry.editing_done.disconnect (on_editing_done); - } - - text_cell.editable = false; - edited = null; - } - - private void on_editing_done () { - if (edited != null && edited.editable && editable_entry != null) - edited.edited (editable_entry.get_text ()); - - // Same actions as when canceling editing - on_editing_canceled (); - } - - private void on_activatable_activated (string item_path_str) { - var item = get_item_from_path_string (item_path_str); - if (item != null) - item.action_activated (); - } - - private Item? get_item_from_path_string (string item_path_str) { - var item_path = new Gtk.TreePath.from_string (item_path_str); - return data_model.get_item_from_path (item_path); - } - - private bool toggle_expansion (ExpandableItem item) { - if (item.collapsible) { - item.expanded = !item.expanded; - return true; - } - return false; - } - - /** - * Updates the tree to reflect the ''expanded'' property of expandable_item. - */ - private void update_expansion (ExpandableItem expandable_item) { - var path = data_model.get_item_path (expandable_item); - - if (path != null) { - // Make sure that the indentation cell for the item's level exists. - // We use +1 because the method will make sure that the previous - // indentation levels exist too. - add_spacer_cell_for_level (path.get_depth () + 1); - - if (expandable_item.expanded) { - expand_row (path, false); - - // Since collapsing an item un-selects any child item previously selected, - // we need to restore the selection. This will be done silently because - // set_selected checks for equality between the previously "selected" - // item and the newly selected, and only emits the item_selected() signal - // if they are different. See cursor_changed() for a better explanation - // of this behavior. - if (selected != null && selected.parent == expandable_item) - set_selected (selected, true); - - // Collapsing expandable_item's row also collapsed all its children, - // and thus we need to update the "expanded" property of each of them - // to reflect their previous state. - foreach (var child_item in expandable_item.children) { - var child_expandable_item = child_item as ExpandableItem; - if (child_expandable_item != null) - update_expansion (child_expandable_item); - } - } else { - collapse_row (path); - } - } - } - - public override void row_expanded (Gtk.TreeIter iter, Gtk.TreePath path) { - var item = data_model.get_item (iter) as ExpandableItem; - return_if_fail (item != null); - - disable_item_property_monitor (); - item.expanded = true; - enable_item_property_monitor (); - } - - public override void row_collapsed (Gtk.TreeIter iter, Gtk.TreePath path) { - var item = data_model.get_item (iter) as ExpandableItem; - return_if_fail (item != null); - - disable_item_property_monitor (); - item.expanded = false; - enable_item_property_monitor (); - } - - public override void row_activated (Gtk.TreePath path, Gtk.TreeViewColumn column) { - if (column == get_column (Column.ITEM)) { - var item = data_model.get_item_from_path (path); - if (item != null) - item.activated (); - } - } - - public override bool key_release_event (Gdk.EventKey event) { - if (selected_item != null) { - switch (event.keyval) { - case Gdk.Key.F2: - var modifiers = Gtk.accelerator_get_default_mod_mask (); - // try to start editing selected item - if ((event.state & modifiers) == 0 && selected_item.editable) - start_editing_item (selected_item); - break; - } - } - - return base.key_release_event (event); - } - - public override bool button_release_event (Gdk.EventButton event) { - if (unselectable_item_clicked && event.window == get_bin_window ()) { - unselectable_item_clicked = false; - - Gtk.TreePath path; - Gtk.TreeViewColumn column; - int x = (int) event.x, y = (int) event.y, cell_x, cell_y; - - if (get_path_at_pos (x, y, out path, out column, out cell_x, out cell_y)) { - var item = data_model.get_item_from_path (path) as ExpandableItem; - - if (item != null) { - if (!item.selectable || data_model.is_category (item, null, path)) - toggle_expansion (item); - } - } - } - - return base.button_release_event (event); - } - - public override bool button_press_event (Gdk.EventButton event) { - if (event.window != get_bin_window ()) - return base.button_press_event (event); - - Gtk.TreePath path; - Gtk.TreeViewColumn column; - int x = (int) event.x, y = (int) event.y, cell_x, cell_y; - - if (get_path_at_pos (x, y, out path, out column, out cell_x, out cell_y)) { - var item = data_model.get_item_from_path (path); - - // This is needed because the treeview adds an offset at the beginning of every level - Gdk.Rectangle start_cell_area; - get_cell_area (path, get_column (0), out start_cell_area); - cell_x -= start_cell_area.x; - - if (item != null && column == get_column (Column.ITEM)) { - // Cancel any editing operation going on - stop_editing (); - - if (event.button == Gdk.BUTTON_SECONDARY) { - popup_context_menu (item, event); - return true; - } else if (event.button == Gdk.BUTTON_PRIMARY) { - // Check whether an expander (or an equivalent area) was clicked. - bool is_expandable = item is ExpandableItem; - bool is_category = is_expandable && data_model.is_category (item, null, path); - - if (event.type == Gdk.EventType.BUTTON_PRESS) { - if (is_expandable) { - // Checking for secondary_expander_cell is not necessary because the entire row - // serves for this purpose when the item is a category or when the item is a - // normal expandable item that is not selectable (special care is taken to - // not break the activatable/action icons for such cases). - // The expander only works like a visual indicator for these items. - unselectable_item_clicked = is_category - || (!item.selectable && !over_cell (column, path, activatable_cell, cell_x)); - - if (!unselectable_item_clicked - && over_primary_expander (column, path, cell_x) - && toggle_expansion (item as ExpandableItem)) - return true; - } - } else if ( - event.type == Gdk.EventType.2BUTTON_PRESS - && !is_category // Main categories are *not* editable - && item.editable - && item.selectable - && over_cell (column, path, text_cell, cell_x) - && start_editing_item (item) - ) { - // The user double-clicked over the text cell, and editing started successfully. - return true; - } - } - } - } - - return base.button_press_event (event); - } - - private bool over_primary_expander (Gtk.TreeViewColumn col, Gtk.TreePath path, int x) { - Gtk.TreeIter iter; - if (!model.get_iter (out iter, path)) - return false; - - // Call the cell-data function and make it assign the proper visibility state to the cell - expander_cell_data_func (col, primary_expander_cell, model, iter); - - if (!primary_expander_cell.visible) - return false; - - // We want to return false if the cell is not expandable (i.e. the arrow is hidden) - if (model.iter_n_children (iter) < 1) - return false; - - // Now that we're sure that the item is expandable, let's see if the user clicked - // over the expander area. We don't do so directly by querying the primary expander - // position because it's not fixed, yielding incorrect coordinates depending on whether - // a different area was re-drawn before this method was called. We know that the last - // spacer cell precedes (in a LTR fashion) the expander cell. Because the position - // of the spacer cell is fixed, we can safely query it. - int indentation_level = path.get_depth (); - var last_spacer_cell = spacer_cells[indentation_level]; - - if (last_spacer_cell != null) { - int cell_x, cell_width; - - if (col.cell_get_position (last_spacer_cell, out cell_x, out cell_width)) { - // Add a pixel so that the expander area is a bit wider - int expander_width = get_cell_width (primary_expander_cell) + 1; - - var dir = get_direction (); - if (dir == Gtk.TextDirection.NONE) { - dir = Gtk.Widget.get_default_direction (); - } - - if (dir == Gtk.TextDirection.LTR) { - int indentation_offset = cell_x + cell_width; - return x >= indentation_offset && x <= indentation_offset + expander_width; - } - - return x <= cell_x && x >= cell_x - expander_width; - } - } - - return false; - } - - private bool over_cell (Gtk.TreeViewColumn col, Gtk.TreePath path, Gtk.CellRenderer cell, int x) { - int cell_x, cell_width; - bool found = col.cell_get_position (cell, out cell_x, out cell_width); - return found && x > cell_x && x < cell_x + cell_width; - } - - private int get_cell_width (Gtk.CellRenderer cell_renderer) { - Gtk.Requisition min_req; - cell_renderer.get_preferred_size (this, out min_req, null); - return min_req.width; - } - - public override bool popup_menu () { - return popup_context_menu (null, null); - } - - private bool popup_context_menu (Item? item, Gdk.EventButton? event) { - if (item == null) - item = selected_item; - - if (item != null) { - var menu = item.get_context_menu (); - if (menu != null) { - menu.attach_widget = this; - menu.popup_at_pointer (event); - if (event == null) { - menu.select_first (false); - } - - return true; - } - } - - return false; - } - - private static Item? get_item_from_model (Gtk.TreeModel model, Gtk.TreeIter iter) { - var data_model = model as DataModel; - assert (data_model != null); - return data_model.get_item (iter); - } - - private static void spacer_cell_data_func ( - Gtk.CellLayout layout, - Gtk.CellRenderer renderer, - Gtk.TreeModel model, - Gtk.TreeIter iter - ) { - var spacer = renderer as CellRendererSpacer; - assert (spacer != null); - assert (spacer.level > 0); - - var path = model.get_path (iter); - - int level = -1; - if (path != null) - level = path.get_depth (); - - renderer.visible = spacer.level <= level; - } - - private void name_cell_data_func ( - Gtk.CellLayout layout, - Gtk.CellRenderer renderer, - Gtk.TreeModel model, - Gtk.TreeIter iter - ) { - var text_renderer = renderer as Gtk.CellRendererText; - assert (text_renderer != null); - - var text = new StringBuilder (); - var weight = Pango.Weight.NORMAL; - bool use_markup = false; - - var item = get_item_from_model (model, iter); - if (item != null) { - if (item.markup != null) { - text.append (item.markup); - use_markup = true; - } else { - text.append (item.name); - } - - if (data_model.is_category (item, iter)) - weight = Pango.Weight.BOLD; - } - - text_renderer.weight = weight; - if (use_markup) { - text_renderer.markup = text.str; - } else { - text_renderer.text = text.str; - } - } - - private void badge_cell_data_func ( - Gtk.CellLayout layout, - Gtk.CellRenderer renderer, - Gtk.TreeModel model, - Gtk.TreeIter iter - ) { - var badge_renderer = renderer as CellRendererBadge; - assert (badge_renderer != null); - - string text = ""; - bool visible = false; - - var item = get_item_from_model (model, iter); - if (item != null) { - // Badges are not displayed for main categories - visible = !data_model.is_category (item, iter) - && item.badge != null - && item.badge.strip () != ""; - - if (visible) - text = item.badge; - } - - badge_renderer.visible = visible; - badge_renderer.text = text; - } - - private void icon_cell_data_func ( - Gtk.CellLayout layout, - Gtk.CellRenderer renderer, - Gtk.TreeModel model, Gtk.TreeIter iter - ) { - var icon_renderer = renderer as CellRendererIcon; - assert (icon_renderer != null); - - bool visible = false; - Icon? icon = null; - - var item = get_item_from_model (model, iter); - if (item != null) { - // Icons are not displayed for main categories - visible = !data_model.is_category (item, iter); - - if (visible) { - if (icon_renderer == icon_cell) - icon = item.icon; - else if (icon_renderer == activatable_cell) - icon = item.activatable; - else - assert_not_reached (); - } - } - - visible = visible && icon != null; - - icon_renderer.visible = visible; - icon_renderer.gicon = visible ? icon : null; - } - - /** - * Controls expander visibility. - */ - private void expander_cell_data_func ( - Gtk.CellLayout layout, - Gtk.CellRenderer renderer, - Gtk.TreeModel model, - Gtk.TreeIter iter - ) { - var item = get_item_from_model (model, iter); - if (item != null) { - // Gtk.CellRenderer.is_expander takes into account whether the item has children or not. - // The tree-view checks for that and sets this property for us. It also sets - // Gtk.CellRenderer.is_expanded, and thus we don't need to check for that either. - var expandable_item = item as ExpandableItem; - if (expandable_item != null) - renderer.is_expander = renderer.is_expander && expandable_item.collapsible; - } - - if (renderer == primary_expander_cell) - renderer.visible = !data_model.is_iter_at_root_level (iter); - else if (renderer == secondary_expander_cell) - renderer.visible = data_model.is_category (item, iter); - else - assert_not_reached (); - } - } - - - - /** - * Emitted when the source list selection changes. - * - * @param item Selected item; //null// if nothing is selected. - * @since 0.2 - */ - public virtual signal void item_selected (Item? item) { } - - /** - * A {@link Granite.Widgets.SourceList.VisibleFunc} should return true if the item should be - * visible; false otherwise. If //item//'s {@link Granite.Widgets.SourceList.Item.visible} - * property is set to //false//, then it won't be displayed even if this method returns //true//. - * - * It is important to note that the method ''must not modify any property of //item//''. - * Doing so would result in an infinite loop, freezing the application's user interface. - * This happens because the source list invokes this method to "filter" an item after - * any of its properties changes, so by modifying a property this method would be invoking - * itself again. - * - * For most use cases, modifying the {@link Granite.Widgets.SourceList.Item.visible} property is enough. - * - * The advantage of using this method is that its nature is non-destructive, and the - * changes it makes can be easily reverted (see {@link Granite.Widgets.SourceList.refilter}). - * - * @param item Item to be checked. - * @return Whether //item// should be visible or not. - * @since 0.2 - */ - public delegate bool VisibleFunc (Item item); - - /** - * Root-level expandable item. - * - * This item contains the first-level source list items. It //only serves as an item container//. - * It is used to add and remove items to/from the widget. - * - * Internally, it allows the source list to connect to its {@link Granite.Widgets.SourceList.ExpandableItem.child_added} - * and {@link Granite.Widgets.SourceList.ExpandableItem.child_removed} signals in order to monitor - * new children additions/removals. - * - * @since 0.2 - */ - public ExpandableItem root { - get { return data_model.root; } - set { data_model.root = value; } - } - - /** - * The current selected item. - * - * Setting it to //null// un-selects the previously selected item, if there was any. - * {@link Granite.Widgets.SourceList.ExpandableItem.expand_with_parents} is called on the - * item's parent to make sure it's possible to select it. - * - * @since 0.2 - */ - public Item? selected { - get { return tree.selected_item; } - set { - if (value != null && value.parent != null) - value.parent.expand_with_parents (); - tree.selected_item = value; - } - } - - /** - * Text ellipsize mode. - * - * @since 0.2 - */ - private Pango.EllipsizeMode ellipsize_mode { - get { return tree.ellipsize_mode; } - set { tree.ellipsize_mode = value; } - } - - /** - * Whether an item is being edited. - * - * @see Granite.Widgets.SourceList.start_editing_item - * @since 0.2 - */ - private bool editing { - get { return tree.editing; } - } - - private Tree tree; - private DataModel data_model = new DataModel (); - - /** - * Creates a new {@link Granite.Widgets.SourceList}. - * - * @return A new {@link Granite.Widgets.SourceList}. - * @since 0.2 - */ - public SourceList (ExpandableItem root = new ExpandableItem ()) { - this.root = root; - } - - construct { - tree = new Tree (data_model); - - set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); - add (tree); - show_all (); - - tree.item_selected.connect ((item) => item_selected (item)); - } - - /** - * Checks whether //item// is part of the source list. - * - * @param item The item to query. - * @return //true// if the item belongs to the source list; //false// otherwise. - * @since 0.2 - */ - public bool has_item (Item item) { - return data_model.has_item (item); - } - - /** - * Sets the method used for filtering out items. - * - * @param visible_func The method to use for filtering items. - * @param refilter Whether to call {@link Granite.Widgets.SourceList.refilter} using the new function. - * @see Granite.Widgets.SourceList.VisibleFunc - * @see Granite.Widgets.SourceList.refilter - * @since 0.2 - */ - public void set_filter_func (VisibleFunc? visible_func, bool refilter) { - data_model.set_filter_func (visible_func); - if (refilter) - this.refilter (); - } - - /** - * Applies the filter method set by {@link Granite.Widgets.SourceList.set_filter_func} - * to all the items that are part of the current tree. - * - * @see Granite.Widgets.SourceList.VisibleFunc - * @see Granite.Widgets.SourceList.set_filter_func - * @since 0.2 - */ - private void refilter () { - data_model.refilter (); - } -} diff --git a/src/VirtualizingListBox/VirtualizingListBox.vala b/src/VirtualizingListBox/VirtualizingListBox.vala deleted file mode 100644 index e1c5ee085..000000000 --- a/src/VirtualizingListBox/VirtualizingListBox.vala +++ /dev/null @@ -1,881 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2018 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: David Hewitt - */ - -public class VirtualizingListBox : Gtk.Container, Gtk.Scrollable { - public delegate VirtualizingListBoxRow RowFactoryMethod (GLib.Object item, VirtualizingListBoxRow? old_widget); - - public RowFactoryMethod factory_func; - - public signal void row_activated (GLib.Object row); - public signal void row_selected (GLib.Object row); - public signal void selected_rows_changed (); - - private VirtualizingListBoxModel? _model; - public VirtualizingListBoxModel? model { - get { - return _model; - } - set { - if (_model != null) { - _model.items_changed.disconnect (on_items_changed); - } - - _model = value; - _model.items_changed.connect (on_items_changed); - queue_resize (); - } - } - - private Gtk.Adjustment? _vadjustment; - public Gtk.Adjustment? vadjustment { - set { - if (_vadjustment != null) { - _vadjustment.value_changed.disconnect (on_adjustment_value_changed); - _vadjustment.notify["page-size"].disconnect (on_adjustment_page_size_changed); - } - - _vadjustment = value; - if (_vadjustment != null) { - _vadjustment.value_changed.connect (on_adjustment_value_changed); - _vadjustment.notify["page-size"].connect (on_adjustment_page_size_changed); - configure_adjustment (); - } - } - get { - return _vadjustment; - } - } - - private int bin_y { - get { - int y = 0; - if (vadjustment != null) { - y = -(int)vadjustment.value; //vala-lint=space-before-paren - } - - return y + (int)bin_y_diff; - } - } - - private bool bin_window_full { - get { - int bin_height = 0; - if (get_realized ()) { - bin_height = bin_window.get_height (); - } - - var widget_height = get_allocated_height (); - return (bin_y + bin_height > widget_height) || (shown_to - shown_from == model.get_n_items ()); - } - } - - public VirtualizingListBoxRow? selected_row_widget { - get { - var item = selected_row; - - foreach (var child in current_widgets) { - if (child.model_item == item) { - return (VirtualizingListBoxRow)child; - } - } - - return null; - } - } - - public Gtk.Adjustment hadjustment { get; set; } - public Gtk.ScrollablePolicy hscroll_policy { get; set; } - public Gtk.ScrollablePolicy vscroll_policy { get; set; } - public bool activate_on_single_click { get; set; } - public Gtk.SelectionMode selection_mode { get; set; default = Gtk.SelectionMode.SINGLE; } - private double bin_y_diff { get; private set; } - public GLib.Object selected_row { get; private set; } - - private Gee.ArrayList current_widgets = new Gee.ArrayList (); - private Gee.ArrayList recycled_widgets = new Gee.ArrayList (); - private Gdk.Window bin_window; - private uint shown_to; - private uint shown_from; - private bool block; - private int last_valid_widget_height = 1; - private VirtualizingListBoxRow? active_row; - private Gtk.GestureMultiPress multipress; - - static construct { - set_css_name ("list"); - } - - construct { - multipress = new Gtk.GestureMultiPress (this); - multipress.set_propagation_phase (Gtk.PropagationPhase.BUBBLE); - multipress.touch_only = false; - multipress.button = Gdk.BUTTON_PRIMARY; - multipress.pressed.connect (on_multipress_pressed); - multipress.released.connect (on_multipress_released); - } - - public override void realize () { - set_realized (true); - Gtk.Allocation allocation; - get_allocation (out allocation); - - var attr = Gdk.WindowAttr (); - attr.x = allocation.x; - attr.y = allocation.y; - attr.width = allocation.width; - attr.height = allocation.height; - attr.window_type = Gdk.WindowType.CHILD; - attr.event_mask = Gdk.EventMask.ALL_EVENTS_MASK; - attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT; - attr.visual = get_visual (); - - Gdk.WindowAttributesType attr_types; - attr_types = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y | Gdk.WindowAttributesType.VISUAL; - - var window = new Gdk.Window (get_parent_window (), attr, attr_types); - - set_window (window); - register_window (window); - - attr.height = 1; - bin_window = new Gdk.Window (window, attr, attr_types); - register_window (bin_window); - bin_window.show (); - } - - public override void size_allocate (Gtk.Allocation allocation) { - bool height_changed = allocation.height != get_allocated_height (); - bool width_changed = allocation.width != get_allocated_width (); - set_allocation (allocation); - position_children (); - - if (get_realized ()) { - get_window ().move_resize (allocation.x, - allocation.y, - allocation.width, - allocation.height); - update_bin_window (); - } - - if (vadjustment != null && height_changed || width_changed) { - configure_adjustment (); - } - - if (height_changed || width_changed) { - ensure_visible_widgets (); - } - } - - public override void map () { - base.map (); - ensure_visible_widgets (); - } - - public override void remove (Gtk.Widget w) { - assert (w.get_parent () == this); - } - - public override void forall_internal (bool include_internals, Gtk.Callback callback) { - foreach (var child in current_widgets) { - callback (child); - } - } - - public override GLib.Type child_type () { - return typeof (VirtualizingListBoxRow); - } - - private VirtualizingListBoxRow? get_widget (uint index) { - var item = model.get_object (index); - if (item == null) { - return null; - } - - VirtualizingListBoxRow? old_widget = null; - if (recycled_widgets.size > 0) { - old_widget = recycled_widgets[recycled_widgets.size - 1]; - recycled_widgets.remove (old_widget); - } - - VirtualizingListBoxRow new_widget = factory_func (item, old_widget); - if (model.get_item_selected (item)) { - new_widget.set_state_flags (Gtk.StateFlags.SELECTED, false); - } else { - new_widget.unset_state_flags (Gtk.StateFlags.SELECTED); - } - - new_widget.model_item = item; - new_widget.show (); - - return new_widget; - } - - private void on_items_changed (uint position, uint removed, uint added) { - if (position >= shown_to && bin_window_full) { - if (vadjustment == null) { - queue_resize (); - } else { - configure_adjustment (); - } - - return; - } - - remove_all_widgets (); - shown_to = shown_from; - update_bin_window (); - ensure_visible_widgets (true); - - if (vadjustment == null) { - queue_resize (); - } - } - - private inline int widget_y (int index) { - int y = 0; - for (int i = 0; i < index; i ++) { - y += get_widget_height (current_widgets[i]); - } - - return y; - } - - private int get_widget_height (Gtk.Widget w) { - int min; - w.get_preferred_height_for_width (get_allocated_width (), out min, null); - return min; - } - - private void position_children () { - Gtk.Allocation allocation; - Gtk.Allocation child_allocation = {0}; - - get_allocation (out allocation); - - int y = 0; - if (vadjustment != null) { - y = allocation.y; - } - - child_allocation.x = 0; - if (allocation.width > 0) { - child_allocation.width = allocation.width; - } else { - child_allocation.width = 1; - } - - foreach (var child in current_widgets) { - child.get_preferred_height_for_width (get_allocated_width (), out child_allocation.height, null); - child.get_preferred_width_for_height (child_allocation.height, out child_allocation.width, null); - child_allocation.width = int.max (child_allocation.width, get_allocated_width ()); - child_allocation.y = y; - child.size_allocate (child_allocation); - - y += child_allocation.height; - } - } - - private void update_bin_window (int new_bin_height = -1) { - Gtk.Allocation allocation; - get_allocation (out allocation); - - var h = (new_bin_height == -1 ? 0 : new_bin_height); - - if (new_bin_height == -1) { - foreach (var w in current_widgets) { - h += get_widget_height (w); - } - } - - if (h == 0) { - h = 1; - } - - if (h != bin_window.get_height () || allocation.width != bin_window.get_width ()) { - bin_window.move_resize (0, bin_y, allocation.width, h); - } else { - bin_window.move (0, bin_y); - } - } - - private void remove_all_widgets () { - foreach (var w in current_widgets) { - w.unparent (); - } - - recycled_widgets.add_all (current_widgets); - current_widgets.clear (); - } - - private void remove_child_internal (VirtualizingListBoxRow widget) { - current_widgets.remove (widget); - widget.set_state_flags (Gtk.StateFlags.NORMAL, true); - widget.unparent (); - recycled_widgets.add (widget); - } - - private void on_adjustment_value_changed () { - ensure_visible_widgets (); - } - - private void on_adjustment_page_size_changed () { - if (!get_mapped ()) { - return; - } - - double max_value = vadjustment.upper - vadjustment.page_size; - - if (vadjustment.value > max_value) { - set_value (max_value); - } - - configure_adjustment (); - } - - private void insert_child_internal (VirtualizingListBoxRow widget, int index) { - widget.set_parent_window (bin_window); - widget.set_parent (this); - current_widgets.insert (index, widget); - } - - private bool remove_top_widgets (ref int bin_height) { - bool removed = false; - for (int i = 0; i < current_widgets.size; i++) { - var w = current_widgets[i]; - int w_height = get_widget_height (w); - if (bin_y + widget_y (i) + w_height < 0) { - bin_y_diff += w_height; - bin_height -= w_height; - remove_child_internal (w); - shown_from++; - removed = true; - } else { - break; - } - } - - return removed; - } - - private bool insert_top_widgets (ref int bin_height) { - bool added = false; - while (shown_from > 0 && bin_y >= 0) { - shown_from--; - var new_widget = get_widget (shown_from); - if (new_widget == null) { - continue; - } - - insert_child_internal (new_widget, 0); - var min = get_widget_height (new_widget); - - bin_y_diff -= min; - bin_height += min; - added = true; - } - - if (bin_y > 0) { - bin_y_diff = 0; - block = true; - set_value (0.0); - block = false; - } - - return added; - } - - private bool remove_bottom_widgets (ref int bin_height) { - for (int i = current_widgets.size - 1; i >= 0; i--) { - var w = current_widgets[i]; - - int widget_y = bin_y + widget_y (i); - if (widget_y > get_allocated_height ()) { - int w_height = get_widget_height (w); - remove_child_internal (w); - bin_height -= w_height; - shown_to--; - } else { - break; - } - } - - return false; - } - - private bool insert_bottom_widgets (ref int bin_height) { - bool added = false; - while (bin_y + bin_height <= get_allocated_height () && shown_to < model.get_n_items ()) { - var new_widget = get_widget (shown_to); - if (new_widget == null) { - shown_to++; - continue; - } - - insert_child_internal (new_widget, current_widgets.size); - - int min = get_widget_height (new_widget); - bin_height += min; - added = true; - shown_to ++; - } - - return added; - } - - private void ensure_visible_widgets (bool model_changed = false) { - if (!get_mapped () || model == null || block) { - return; - } - - var widget_height = get_allocated_height (); - var bin_height = bin_window.get_height (); - if (bin_height == 1) { - bin_height = 0; - } - - if (bin_y + bin_height < 0 || bin_y >= widget_height) { - int estimated_widget_height = estimated_widget_height (); - - remove_all_widgets (); - bin_height = 0; - - double percentage = vadjustment.value / vadjustment.upper; - uint top_widget_index = (uint)(model.get_n_items () * percentage); - - if (top_widget_index > model.get_n_items ()) { - shown_to = model.get_n_items (); - shown_from = model.get_n_items (); - bin_y_diff = vadjustment.value + vadjustment.page_size; - } else { - shown_from = top_widget_index; - shown_to = top_widget_index; - bin_y_diff = top_widget_index * estimated_widget_height; - } - } - - var top_removed = remove_top_widgets (ref bin_height); - var top_added = insert_top_widgets (ref bin_height); - var bottom_removed = remove_bottom_widgets (ref bin_height); - var bottom_added = insert_bottom_widgets (ref bin_height); - - var widgets_changed = top_removed || top_added || bottom_removed || bottom_added || model_changed; - - if (vadjustment != null && widgets_changed) { - uint top_part; - uint widget_part; - uint bottom_part; - - uint new_upper = estimated_list_height (out top_part, out bottom_part, out widget_part); - - if (new_upper > _vadjustment.upper) { - bin_y_diff = double.max (top_part, vadjustment.value); - } else { - bin_y_diff = double.min (top_part, vadjustment.value); - } - - configure_adjustment (); - - set_value (bin_y_diff - bin_y); - if (vadjustment.value < bin_y_diff) { - set_value (bin_y_diff); - } - - if (bin_y > 0) { - bin_y_diff = vadjustment.value; - } - } - - configure_adjustment (); - update_bin_window (bin_height); - position_children (); - queue_draw (); - } - - private int estimated_widget_height () { - int average_widget_height = 0; - int used_widgets = 0; - - foreach (var w in current_widgets) { - if (w.visible) { - average_widget_height += get_widget_height (w); - used_widgets ++; - } - } - - if (used_widgets > 0) { - average_widget_height /= used_widgets; - } else { - average_widget_height = last_valid_widget_height; - } - - last_valid_widget_height = average_widget_height; - - return average_widget_height; - } - - private void configure_adjustment () { - int widget_height = get_allocated_height (); - uint list_height = estimated_list_height (); - - if ((int)vadjustment.upper != uint.max (list_height, widget_height)) { - vadjustment.upper = uint.max (list_height, widget_height); - } else if (list_height == 0) { - vadjustment.upper = widget_height; - } - - if ((int)vadjustment.page_size != widget_height) { - vadjustment.page_size = widget_height; - } - - if (vadjustment.value > vadjustment.upper - vadjustment.page_size) { - double v = vadjustment.upper - vadjustment.page_size; - set_value (v); - } - } - - private void set_value (double v) { - if (v == vadjustment.value) - return; - - block = true; - vadjustment.value = v; - block = false; - } - - private uint estimated_list_height (out uint top = null, out uint bottom = null, out uint visible_widgets = null) { - if (model == null) { - top = 0; - bottom = 0; - visible_widgets = 0; - return 0; - } - - int widget_height = estimated_widget_height (); - uint top_widgets = shown_from; - uint bottom_widgets = model.get_n_items () - shown_to; - - int exact_height = 0; - foreach (var w in current_widgets) { - int h = get_widget_height (w); - exact_height += h; - } - - top = top_widgets * widget_height; - bottom = bottom_widgets * widget_height; - visible_widgets = exact_height; - - uint h = top + bottom + visible_widgets; - return h; - } - - public unowned VirtualizingListBoxRow? get_row_at_y (int y) { - Gtk.Allocation alloc; - foreach (var row in current_widgets) { - row.get_allocation (out alloc); - if (y >= alloc.y + bin_y && y <= alloc.y + bin_y + alloc.height) { - unowned VirtualizingListBoxRow return_value = row; - return return_value; - } - } - - return null; - } - - private void on_multipress_pressed (int n_press, double x, double y) { - active_row = null; - var row = get_row_at_y ((int)y); - if (row != null && row.sensitive) { - active_row = row; - row.set_state_flags (Gtk.StateFlags.ACTIVE, false); - - if (n_press == 2 && !activate_on_single_click) { - row_activated (row.model_item); - } - } - } - - private void get_current_selection_modifiers (out bool modify, out bool extend) { - Gdk.ModifierType state; - Gdk.ModifierType mask; - - modify = false; - extend = false; - - if (Gtk.get_current_event_state (out state)) { - mask = get_modifier_mask (Gdk.ModifierIntent.MODIFY_SELECTION); - if ((state & mask) == mask) { - modify = true; - } - - mask = get_modifier_mask (Gdk.ModifierIntent.EXTEND_SELECTION); - if ((state & mask) == mask) { - extend = true; - } - } - } - - private void on_multipress_released (int n_press, double x, double y) { - if (active_row != null) { - var focus_on_click = active_row.focus_on_click; - active_row.unset_state_flags (Gtk.StateFlags.ACTIVE); - - if (n_press == 1 && activate_on_single_click) { - select_and_activate (active_row, focus_on_click); - } else { - bool modify, extend; - get_current_selection_modifiers (out modify, out extend); - var sequence = multipress.get_current_sequence (); - var event = multipress.get_last_event (sequence); - var source = event.get_source_device ().get_source (); - - if (source == Gdk.InputSource.TOUCHSCREEN) { - modify = !modify; - } - - update_selection (active_row, modify, extend, focus_on_click); - } - } - } - - private void update_selection (VirtualizingListBoxRow row, bool modify, bool extend, bool grab_cursor = true) { - update_cursor (row.model_item, grab_cursor); - - if (selection_mode == Gtk.SelectionMode.NONE || !row.selectable) { - return; - } - - if (selection_mode == Gtk.SelectionMode.BROWSE) { - select_row (row); - } else if (selection_mode == Gtk.SelectionMode.SINGLE) { - var was_selected = model.get_item_selected (row.model_item); - unselect_all_internal (); - var select = modify ? !was_selected : true; - model.set_item_selected (row.model_item, select); - selected_row = select ? row.model_item : null; - if (select) { - row.set_state_flags (Gtk.StateFlags.SELECTED, false); - } else { - row.unset_state_flags (Gtk.StateFlags.SELECTED); - } - - row_selected (selected_row); - } else { - if (extend) { - var selected = selected_row; - unselect_all_internal (); - if (selected == null) { - select_row (row); - } else { - select_all_between (selected, row.model_item, false); - } - } else { - if (modify) { - var selected = model.get_item_selected (row.model_item); - if (selected) { - row.unset_state_flags (Gtk.StateFlags.SELECTED); - } else { - row.set_state_flags (Gtk.StateFlags.SELECTED, false); - } - - model.set_item_selected (row.model_item, !selected); - } else { - unselect_all_internal (); - select_row (row); - } - } - } - } - - private void select_all_between (GLib.Object from, GLib.Object to, bool modify) { - var items = model.get_items_between (from, to); - foreach (var item in items) { - model.set_item_selected (item, true); - } - - foreach (VirtualizingListBoxRow row in current_widgets) { - if (row.model_item in items) { - row.set_state_flags (Gtk.StateFlags.SELECTED, false); - } - } - } - - public void select_row_at_index (int index) { - var row = ensure_index_visible (index); - - if (row != null) { - select_and_activate (row); - } - } - - private void select_and_activate (VirtualizingListBoxRow row, bool grab_focus = true) { - select_row (row); - update_cursor (row.model_item, grab_focus); - row_activated (row.model_item); - } - - public void update_cursor (GLib.Object item, bool grab_focus) { - var row = ensure_index_visible (model.get_index_of (item)); - if (row != null && grab_focus) { - row.grab_focus (); - } - } - - private VirtualizingListBoxRow? ensure_index_visible (int index) { - if (index < 0) { - return null; - } - - var n_items = model.get_n_items (); - if (n_items == 0) { - return null; - } - - var index_max = n_items - 1; - if (index > index_max) { - return null; - } - - if (index == 0) { - set_value (0.0); - ensure_visible_widgets (); - foreach (VirtualizingListBoxRow row in current_widgets) { - if (index == model.get_index_of (row.model_item)) { - return row; - } - } - } - - if (index == index_max) { - set_value (vadjustment.upper); - ensure_visible_widgets (); - foreach (VirtualizingListBoxRow row in current_widgets) { - if (index == model.get_index_of (row.model_item)) { - return row; - } - } - } - - while (index <= shown_from) { - vadjustment.value--; - ensure_visible_widgets (); - } - - while (index + 1 >= shown_to) { - vadjustment.value++; - ensure_visible_widgets (); - } - - foreach (VirtualizingListBoxRow row in current_widgets) { - if (index == model.get_index_of (row.model_item)) { - return row; - } - } - - return null; - } - - public void select_row (VirtualizingListBoxRow row) { - if (model.get_item_selected (row) || selection_mode == Gtk.SelectionMode.NONE) { - return; - } - - if (selection_mode != Gtk.SelectionMode.MULTIPLE) { - unselect_all_internal (); - } - - model.set_item_selected (row.model_item, true); - row.set_state_flags (Gtk.StateFlags.SELECTED, false); - selected_row = row.model_item; - - row_selected (row.model_item); - selected_rows_changed (); - } - - private bool unselect_all_internal () { - if (selection_mode == Gtk.SelectionMode.NONE) { - return false; - } - - foreach (var row in current_widgets) { - row.unset_state_flags (Gtk.StateFlags.SELECTED); - } - - model.unselect_all (); - selected_row = null; - - return true; - } - - public override bool focus (Gtk.DirectionType direction) { - var focus_child = get_focus_child () as VirtualizingListBoxRow; - int next_focus_index = -1; - - if (focus_child != null && focus_child.model_item != null) { - if (focus_child.child_focus (direction)) { - return true; - } - - if (direction == Gtk.DirectionType.UP || direction == Gtk.DirectionType.TAB_BACKWARD) { - next_focus_index = model.get_index_of_item_before (focus_child.model_item); - } else if (direction == Gtk.DirectionType.DOWN || direction == Gtk.DirectionType.TAB_FORWARD) { - next_focus_index = model.get_index_of_item_after (focus_child.model_item); - } - } else { - if (direction == Gtk.DirectionType.UP || direction == Gtk.DirectionType.TAB_BACKWARD) { - next_focus_index = model.get_index_of (focus_child.model_item); - if (next_focus_index == -1) { - next_focus_index = (int)model.get_n_items () - 1; - } - } else { - next_focus_index = model.get_index_of (focus_child); - if (next_focus_index == -1) { - next_focus_index = 0; - } - } - } - - if (next_focus_index == -1) { - if (keynav_failed (direction)) { - return true; - } - - return false; - } - - var widget = ensure_index_visible (next_focus_index); - if (widget != null) { - update_selection (widget, false, false); - return true; - } - - return false; - } - - public bool get_border (out Gtk.Border border) { - border = Gtk.Border (); - return false; - } - - public Gee.HashSet get_selected_rows () { - return model.get_selected_rows (); - } -} diff --git a/src/VirtualizingListBox/VirtualizingListBoxModel.vala b/src/VirtualizingListBox/VirtualizingListBoxModel.vala deleted file mode 100644 index 61e56a18c..000000000 --- a/src/VirtualizingListBox/VirtualizingListBoxModel.vala +++ /dev/null @@ -1,145 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2018 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: David Hewitt - */ - -public abstract class VirtualizingListBoxModel : GLib.ListModel, GLib.Object { - private Gee.HashSet selected_rows = new Gee.HashSet (); - - public GLib.Type get_item_type () { - return typeof (GLib.Object); - } - - public abstract uint get_n_items (); - public abstract GLib.Object? get_item (uint index); - public abstract GLib.Object? get_item_unfiltered (uint index); - - public void unselect_all () { - selected_rows.clear (); - } - - public void set_item_selected (GLib.Object item, bool selected) { - if (!selected) { - selected_rows.remove (item); - } else { - selected_rows.add (item); - } - } - - public bool get_item_selected (GLib.Object item) { - return selected_rows.contains (item); - } - - public Gee.ArrayList get_items_between (GLib.Object from, GLib.Object to) { - var items = new Gee.ArrayList (); - var start_found = false; - var ignore_next_break = false; - var length = get_n_items (); - for (int i = 0; i < length; i++) { - var item = get_item (i); - if ((item == from || item == to) && !start_found) { - start_found = true; - ignore_next_break = true; - } else if (!start_found) { - continue; - } - - if (item != null) { - items.add (item); - } - - if ((item == to || item == from) && !ignore_next_break) { - break; - } - - ignore_next_break = false; - } - - return items; - } - - public int get_index_of (GLib.Object? item) { - if (item == null) { - return -1; - } - - var length = get_n_items (); - for (int i = 0; i < length; i++) { - if (item == get_item (i)) { - return i; - } - } - - return -1; - } - - public int get_index_of_unfiltered (GLib.Object? item) { - if (item == null) { - return -1; - } - - var length = get_n_items (); - for (int i = 0; i < length; i++) { - if (item == get_item_unfiltered (i)) { - return i; - } - } - - return -1; - } - - public int get_index_of_item_before (GLib.Object item) { - if (item == get_item (0)) { - return -1; - } - - var length = get_n_items (); - for (int i = 1; i < length; i++) { - if (get_item (i) == item) { - if (get_item (i - 1) != null) { - return i - 1; - } - } - } - - return -1; - } - - public int get_index_of_item_after (GLib.Object item) { - if (item == get_item (get_n_items () - 1)) { - return -1; - } - - var length = get_n_items (); - for (int i = 0; i < length - 1; i++) { - if (get_item (i) == item) { - if (get_item (i + 1) != null) { - return i + 1; - } - } - } - - return -1; - } - - public Gee.HashSet get_selected_rows () { - return selected_rows; - } -} diff --git a/src/VirtualizingListBox/VirtualizingListBoxRow.vala b/src/VirtualizingListBox/VirtualizingListBoxRow.vala deleted file mode 100644 index 8ace8ebeb..000000000 --- a/src/VirtualizingListBox/VirtualizingListBoxRow.vala +++ /dev/null @@ -1,48 +0,0 @@ -// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- -/*- - * Copyright (c) 2018 elementary LLC. (https://elementary.io) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - * - * Authored by: David Hewitt - */ - -public class VirtualizingListBoxRow : Gtk.Bin { - public bool selectable { get; set; default = true; } - public weak GLib.Object model_item { get; set; } - - static construct { - set_css_name ("row"); - } - - construct { - can_focus = true; - set_redraw_on_allocate (true); - - get_style_context ().add_class ("activatable"); - } - - public override bool draw (Cairo.Context ct) { - var sc = this.get_style_context (); - Gtk.Allocation alloc; - this.get_allocation (out alloc); - - sc.render_background (ct, 0, 0, alloc.width, alloc.height); - sc.render_frame (ct, 0, 0, alloc.width, alloc.height); - - return base.draw (ct); - } -} diff --git a/src/meson.build b/src/meson.build index 3bd22f49b..efb4948a9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,13 +23,7 @@ vala_files = files( 'FolderList/GroupedFolderItemModel.vala', 'MessageList/AttachmentButton.vala', 'MessageList/MessageList.vala', - 'MessageList/MessageListItem.vala', - # 'SourceList/CellRendererBadge.vala', - # 'SourceList/CellRendererExpander.vala', - # 'SourceList/SourceList.vala', - # 'VirtualizingListBox/VirtualizingListBoxModel.vala', - # 'VirtualizingListBox/VirtualizingListBoxRow.vala', - # 'VirtualizingListBox/VirtualizingListBox.vala' + 'MessageList/MessageListItem.vala' ) executable( From d5c2e18733aa877a08d4534a14e3589b9dab8362 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 23:01:42 +0200 Subject: [PATCH 63/72] FolderList: Cleanup --- src/FolderList/FolderItemModel.vala | 4 +--- src/FolderList/FolderList.vala | 14 +++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index cdeb19878..ba3966906 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -25,7 +25,6 @@ public class Mail.FolderItemModel : ItemModel { public Mail.Backend.Account account { get; construct; } public Camel.FolderInfo folder_info { get; construct; } - public string full_name { get; construct; } public FolderItemModel (Camel.FolderInfo folderinfo, Mail.Backend.Account account) { Object (account: account, @@ -38,7 +37,6 @@ public class Mail.FolderItemModel : ItemModel { account_uid = account.service.uid; unread = folder_info.unread; - full_name = folder_info.full_name; if (folder_info.child != null) { if (folder_list == null) { @@ -85,7 +83,7 @@ public class Mail.FolderItemModel : ItemModel { public async void refresh () { var offlinestore = (Camel.Store) account.service; try { - var folder = yield offlinestore.get_folder (full_name, 0, GLib.Priority.DEFAULT, null); + var folder = yield offlinestore.get_folder (folder_info.full_name, 0, GLib.Priority.DEFAULT, null); yield folder.refresh_info (GLib.Priority.DEFAULT, null); } catch (Error e) { critical (e.message); diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 8d42860d2..3aa49c34b 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -29,7 +29,7 @@ public class Mail.FolderList : Gtk.Box { private ListStore root_model; private Mail.Backend.Session session; - private SessionItemModel? session_item; + private SessionItemModel session_item; private bool already_selected = false; @@ -109,24 +109,24 @@ public class Mail.FolderList : Gtk.Box { if (!already_selected) { string selected_folder_uid, selected_folder_full_name; settings.get ("selected-folder", "(ss)", out selected_folder_uid, out selected_folder_full_name); - if (folder_item.account_uid == selected_folder_uid && folder_item.full_name == selected_folder_full_name) { + if (folder_item.account_uid == selected_folder_uid && folder_item.folder_info.full_name == selected_folder_full_name) { selection_model.set_selected (list_item.position); already_selected = true; } } - if (folder_item.full_name in account_settings.get_strv ("expanded-folders")) { + if (folder_item.folder_info.full_name in account_settings.get_strv ("expanded-folders")) { list_row.expanded = true; } list_row.notify["expanded"].connect (() => { var folders = account_settings.get_strv ("expanded-folders"); if (list_row.expanded) { - folders += folder_item.full_name; + folders += folder_item.folder_info.full_name; } else { string[] new_folders = {}; foreach (var folder in folders) { - if (folder != folder_item.full_name) { + if (folder != folder_item.folder_info.full_name) { new_folders += folder; } } @@ -171,10 +171,10 @@ public class Mail.FolderList : Gtk.Box { if (item is FolderItemModel) { var folder_name_per_account_uid = new Gee.HashMap (); - folder_name_per_account_uid.set (item.account, item.full_name); + folder_name_per_account_uid.set (item.account, item.folder_info.full_name); folder_selected (folder_name_per_account_uid.read_only_view); - settings.set ("selected-folder", "(ss)", item.account_uid, item.full_name); + settings.set ("selected-folder", "(ss)", item.account_uid, item.folder_info.full_name); } else if (item is GroupedFolderItemModel) { folder_selected (item.get_folder_full_name_per_account ()); From 1299dbde2f3c53d002d9bf26ed7b7d227c595c93 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Wed, 19 Apr 2023 23:08:22 +0200 Subject: [PATCH 64/72] Fix lint --- src/ConversationList/ConversationList.vala | 16 ++++++++-------- src/FolderList/FolderItemModel.vala | 3 +-- src/FolderList/FolderList.vala | 6 +++--- src/FolderList/GroupedFolderItemModel.vala | 4 ++-- src/FolderList/SessionItemModel.vala | 6 +++--- src/WebView.vala | 1 - 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 65989ba18..37f3dd17c 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -182,7 +182,7 @@ public class Mail.ConversationList : Gtk.Box { factory.bind.connect ((obj) => { var list_item = (Gtk.ListItem) obj; var conversation_list_item = (ConversationListItem) list_item.child; - conversation_list_item.assign((ConversationItemModel) list_item.get_item ()); + conversation_list_item.assign ((ConversationItemModel) list_item.get_item ()); }); selection_model.selection_changed.connect (() => { @@ -470,7 +470,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); while (bitset_iter.is_valid ()) { ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, ~0); bitset_iter.next (out current_item_position); @@ -481,7 +481,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); while (bitset_iter.is_valid ()) { ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, ~0); bitset_iter.next (out current_item_position); @@ -492,7 +492,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); while (bitset_iter.is_valid ()) { ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.SEEN, 0); bitset_iter.next (out current_item_position); @@ -503,7 +503,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); while (bitset_iter.is_valid ()) { ((ConversationItemModel)selection_model.get_item (current_item_position)).node.message.set_flags (Camel.MessageFlags.FLAGGED, 0); bitset_iter.next (out current_item_position); @@ -516,7 +516,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); var selected_items_start_index = current_item_position; while (bitset_iter.is_valid ()) { @@ -562,7 +562,7 @@ public class Mail.ConversationList : Gtk.Box { var selected_items = selection_model.get_selection (); uint current_item_position; Gtk.BitsetIter bitset_iter = Gtk.BitsetIter (); - bitset_iter.init_first(selected_items, out current_item_position); + bitset_iter.init_first (selected_items, out current_item_position); var selected_items_start_index = current_item_position; while (bitset_iter.is_valid ()) { @@ -603,7 +603,7 @@ public class Mail.ConversationList : Gtk.Box { var conversation_item_model = (ConversationItemModel) selection_model.get_selected_item (); - menu.append(_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH); + menu.append (_("Move To Trash"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MOVE_TO_TRASH); if (!conversation_item_model.unread) { menu.append (_("Mark As Unread"), MainWindow.ACTION_PREFIX + MainWindow.ACTION_MARK_UNREAD); diff --git a/src/FolderList/FolderItemModel.vala b/src/FolderList/FolderItemModel.vala index ba3966906..ccc6b023b 100644 --- a/src/FolderList/FolderItemModel.vala +++ b/src/FolderList/FolderItemModel.vala @@ -40,7 +40,7 @@ public class Mail.FolderItemModel : ItemModel { if (folder_info.child != null) { if (folder_list == null) { - folder_list = new ListStore (typeof(FolderItemModel)); + folder_list = new ListStore (typeof (FolderItemModel)); } var current_folder_info = folder_info.child; while (current_folder_info != null) { @@ -90,4 +90,3 @@ public class Mail.FolderItemModel : ItemModel { } } } - diff --git a/src/FolderList/FolderList.vala b/src/FolderList/FolderList.vala index 3aa49c34b..a3cce22dc 100644 --- a/src/FolderList/FolderList.vala +++ b/src/FolderList/FolderList.vala @@ -60,7 +60,7 @@ public class Mail.FolderList : Gtk.Box { header_bar.pack_end (compose_button); header_bar.add_css_class (Granite.STYLE_CLASS_FLAT); - root_model = new ListStore (typeof(ItemModel)); + root_model = new ListStore (typeof (ItemModel)); var tree_list = new Gtk.TreeListModel (root_model, false, false, create_folder_list_func); var selection_model = new Gtk.SingleSelection (tree_list); var list_factory = new Gtk.SignalListItemFactory (); @@ -167,7 +167,7 @@ public class Mail.FolderList : Gtk.Box { session.account_added.connect (add_account); selection_model.selection_changed.connect ((position) => { - var item = ((Gtk.TreeListRow)selection_model.get_selected_item()).get_item (); + var item = ((Gtk.TreeListRow)selection_model.get_selected_item ()).get_item (); if (item is FolderItemModel) { var folder_name_per_account_uid = new Gee.HashMap (); @@ -202,7 +202,7 @@ public class Mail.FolderList : Gtk.Box { } public class ItemModel : Object { - public string account_uid { get; protected set; } + public string account_uid { get; protected set; } public string icon_name { get; protected set; } public string name { get; protected set; } public ListStore? folder_list { get; protected set; default = null; } diff --git a/src/FolderList/GroupedFolderItemModel.vala b/src/FolderList/GroupedFolderItemModel.vala index 8483eee72..d73f3b752 100644 --- a/src/FolderList/GroupedFolderItemModel.vala +++ b/src/FolderList/GroupedFolderItemModel.vala @@ -33,7 +33,7 @@ public class Mail.GroupedFolderItemModel : ItemModel { } construct { - account_uid = Mail.SessionItemModel.account; + account_uid = Mail.SessionItemModel.SESSION_ACCOUNT_UID; account_folderinfo = new Gee.HashMap (); switch (folder_type & Camel.FOLDER_TYPE_MASK) { @@ -118,7 +118,7 @@ public class Mail.GroupedFolderItemModel : ItemModel { } private string? build_folder_full_name (Backend.Account account) { - var session = Mail.Backend.Session.get_default (); + var session = Mail.Backend.Session.get_default (); var service_source = session.ref_source (account.service.uid); if (service_source == null || !service_source.has_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT)) { return null; diff --git a/src/FolderList/SessionItemModel.vala b/src/FolderList/SessionItemModel.vala index 881f94edb..e7f5f6e7d 100644 --- a/src/FolderList/SessionItemModel.vala +++ b/src/FolderList/SessionItemModel.vala @@ -18,14 +18,14 @@ */ public class Mail.SessionItemModel : ItemModel { - public const string account = "SESSION ACCOUNT"; + public const string SESSION_ACCOUNT_UID = "SESSION ACCOUNT"; construct { name = _("All Mailboxes"); icon_name = "go-home"; - account_uid = account_uid; + account_uid = SESSION_ACCOUNT_UID; - folder_list = new ListStore (typeof(GroupedFolderItemModel)); + folder_list = new ListStore (typeof (GroupedFolderItemModel)); folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_INBOX)); folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_ARCHIVE)); folder_list.append (new GroupedFolderItemModel (Camel.FolderInfoFlags.TYPE_SENT)); diff --git a/src/WebView.vala b/src/WebView.vala index 951cb77b1..efd48f31d 100644 --- a/src/WebView.vala +++ b/src/WebView.vala @@ -267,4 +267,3 @@ public class Mail.WebView : WebKit.WebView { return false; } } - From 1656fad15f5b8cadd6426582f3a998db70fdcf11 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Thu, 20 Apr 2023 19:46:53 +0200 Subject: [PATCH 65/72] ConversationList: Cleanup --- src/ConversationList/ConversationList.vala | 33 +++++-------------- .../ConversationListItem.vala | 2 +- .../ConversationListStore.vala | 4 +-- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/ConversationList/ConversationList.vala b/src/ConversationList/ConversationList.vala index 37f3dd17c..7cb9a2af6 100644 --- a/src/ConversationList/ConversationList.vala +++ b/src/ConversationList/ConversationList.vala @@ -22,7 +22,6 @@ public class Mail.ConversationList : Gtk.Box { public signal void conversation_selected (Camel.FolderThreadNode? node); - public signal void conversation_focused (Camel.FolderThreadNode? node); private const int MARK_READ_TIMEOUT_SECONDS = 5; @@ -47,7 +46,8 @@ public class Mail.ConversationList : Gtk.Box { private uint mark_read_timeout_id = 0; - construct {conversations = new Gee.HashMap (); + construct { + conversations = new Gee.HashMap (); folders = new Gee.HashMap (); folder_info_flags = new Gee.HashMap (); threads = new Gee.HashMap (); @@ -78,7 +78,7 @@ public class Mail.ConversationList : Gtk.Box { }; filter_button = new Gtk.MenuButton () { - icon_name = "mail-filter-symbolic", //Small toolbar + icon_name = "mail-filter-symbolic", popover = filter_popover, tooltip_text = _("Filter Conversations"), valign = Gtk.Align.CENTER @@ -104,7 +104,7 @@ public class Mail.ConversationList : Gtk.Box { var factory = new Gtk.SignalListItemFactory (); list_view = new Gtk.ListView (selection_model, factory) { - show_separators = false, + show_separators = false }; var event_controller_focus = new Gtk.EventControllerFocus (); @@ -124,7 +124,7 @@ public class Mail.ConversationList : Gtk.Box { child = list_view }; - var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic") { //Small toolbar + var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic") { action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_REFRESH }; @@ -197,11 +197,9 @@ public class Mail.ConversationList : Gtk.Box { bitset_iter.init_first (selected_items, out current_item_position); if (!bitset_iter.is_valid ()) { - conversation_focused (null); conversation_selected (null); } else { var conversation_item = (ConversationItemModel) selection_model.get_item (current_item_position); - conversation_focused (conversation_item.node); if (conversation_item.unread) { mark_read_timeout_id = GLib.Timeout.add_seconds (MARK_READ_TIMEOUT_SECONDS, () => { @@ -237,17 +235,6 @@ public class Mail.ConversationList : Gtk.Box { {} ); }); - - // key_release_event.connect ((e) => { - - // if (e.keyval != Gdk.Key.Menu) { - // return Gdk.EVENT_PROPAGATE; - // } - - // var row = list_box.selected_row_widget; - - // return create_context_menu (e, (ConversationListItem)row); - // }); } private static void set_thread_flag (Camel.FolderThreadNode? node, Camel.MessageFlags flag) { @@ -273,7 +260,6 @@ public class Mail.ConversationList : Gtk.Box { cancellable.cancel (); } - conversation_focused (null); conversation_selected (null); uint previous_items = list_store.get_n_items (); @@ -404,13 +390,12 @@ public class Mail.ConversationList : Gtk.Box { } private GenericArray? get_search_result_uids (string service_uid) { - var style_context = filter_button.get_style_context (); if (hide_read_switch.active || hide_unstarred_switch.active) { - if (!style_context.has_class (Granite.STYLE_CLASS_ACCENT)) { - style_context.add_class (Granite.STYLE_CLASS_ACCENT); + if (!filter_button.has_css_class (Granite.STYLE_CLASS_ACCENT)) { + filter_button.add_css_class (Granite.STYLE_CLASS_ACCENT); } - } else if (style_context.has_class (Granite.STYLE_CLASS_ACCENT)) { - style_context.remove_class (Granite.STYLE_CLASS_ACCENT); + } else if (filter_button.has_css_class (Granite.STYLE_CLASS_ACCENT)) { + filter_button.remove_css_class (Granite.STYLE_CLASS_ACCENT); } lock (folders) { diff --git a/src/ConversationList/ConversationListItem.vala b/src/ConversationList/ConversationListItem.vala index cf1386f69..8322b4fce 100644 --- a/src/ConversationList/ConversationListItem.vala +++ b/src/ConversationList/ConversationListItem.vala @@ -90,6 +90,7 @@ public class Mail.ConversationListItem : Gtk.Box { button = Gdk.BUTTON_SECONDARY }; add_controller (gesture_click); + gesture_click.released.connect ((n_press, x, y) => { secondary_click (x, y); }); @@ -111,7 +112,6 @@ public class Mail.ConversationListItem : Gtk.Box { uint num_messages = data.num_messages; messages.label = num_messages > 1 ? "%u".printf (num_messages) : null; messages.visible = num_messages > 1; - // messages.no_show_all = num_messages <= 1; if (data.unread) { add_css_class ("unread-message"); diff --git a/src/ConversationList/ConversationListStore.vala b/src/ConversationList/ConversationListStore.vala index 230a60669..189d3fc6c 100644 --- a/src/ConversationList/ConversationListStore.vala +++ b/src/ConversationList/ConversationListStore.vala @@ -21,7 +21,7 @@ */ public class Mail.ConversationListStore : ListModel, Object { - //The items_changed signal is not emitted automatically, the object using this has to emit it manually!!! + /* The items_changed signal is not emitted automatically, the object using this has to emit it manually */ private GLib.List data = new GLib.List (); @@ -46,7 +46,7 @@ public class Mail.ConversationListStore : ListModel, Object { } public void add (ConversationItemModel item) { - /*Adding automatically sorts according to timestamp*/ + /* Adding automatically sorts according to timestamp */ data.insert_sorted (item, (a,b)=> { var item1 = (ConversationItemModel) a; var item2 = (ConversationItemModel) b; From 18dc9d1a0e770d016ccd68497e0a1985dd42f337 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Thu, 20 Apr 2023 19:48:04 +0200 Subject: [PATCH 66/72] ConversationListStore: Formatting --- src/ConversationList/ConversationListStore.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ConversationList/ConversationListStore.vala b/src/ConversationList/ConversationListStore.vala index 189d3fc6c..6255039c1 100644 --- a/src/ConversationList/ConversationListStore.vala +++ b/src/ConversationList/ConversationListStore.vala @@ -26,7 +26,7 @@ public class Mail.ConversationListStore : ListModel, Object { private GLib.List data = new GLib.List (); public GLib.Type get_item_type () { - return typeof(ConversationItemModel); + return typeof (ConversationItemModel); } public uint get_n_items () { @@ -47,7 +47,7 @@ public class Mail.ConversationListStore : ListModel, Object { public void add (ConversationItemModel item) { /* Adding automatically sorts according to timestamp */ - data.insert_sorted (item, (a,b)=> { + data.insert_sorted (item, (a, b)=> { var item1 = (ConversationItemModel) a; var item2 = (ConversationItemModel) b; return (int)(item2.timestamp - item1.timestamp); From cc4351799be6a9c28b9f6b8974b089acfcb30d33 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Thu, 20 Apr 2023 19:53:59 +0200 Subject: [PATCH 67/72] FolderList: Cleanup --- src/FolderList/AccountItemModel.vala | 2 +- src/FolderList/GroupedFolderItemModel.vala | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/FolderList/AccountItemModel.vala b/src/FolderList/AccountItemModel.vala index b50e96c59..4cbd5c5f6 100644 --- a/src/FolderList/AccountItemModel.vala +++ b/src/FolderList/AccountItemModel.vala @@ -36,7 +36,7 @@ public class Mail.AccountItemModel : ItemModel { icon_name = "avatar-default"; name = offlinestore.display_name; account_uid = offlinestore.uid; - folder_list = new ListStore (typeof(FolderItemModel)); + folder_list = new ListStore (typeof (FolderItemModel)); connect_cancellable = new GLib.Cancellable (); diff --git a/src/FolderList/GroupedFolderItemModel.vala b/src/FolderList/GroupedFolderItemModel.vala index d73f3b752..292f25bec 100644 --- a/src/FolderList/GroupedFolderItemModel.vala +++ b/src/FolderList/GroupedFolderItemModel.vala @@ -27,6 +27,7 @@ public class Mail.GroupedFolderItemModel : ItemModel { public string full_name { get; construct; } private Gee.HashMap account_folderinfo; + private GLib.Cancellable connect_cancellable; public GroupedFolderItemModel (Camel.FolderInfoFlags folder_type) { Object (folder_type: folder_type); @@ -36,6 +37,8 @@ public class Mail.GroupedFolderItemModel : ItemModel { account_uid = Mail.SessionItemModel.SESSION_ACCOUNT_UID; account_folderinfo = new Gee.HashMap (); + connect_cancellable = new GLib.Cancellable (); + switch (folder_type & Camel.FOLDER_TYPE_MASK) { case Camel.FolderInfoFlags.TYPE_INBOX: name = _("Inbox"); @@ -60,6 +63,10 @@ public class Mail.GroupedFolderItemModel : ItemModel { } } + ~GroupedFolderItemModel () { + connect_cancellable.cancel (); + } + public Gee.Map get_folder_full_name_per_account () { var folder_full_name_per_account = new Gee.HashMap (); lock (account_folderinfo) { @@ -89,7 +96,7 @@ public class Mail.GroupedFolderItemModel : ItemModel { if (full_name != null) { try { - folderinfo = yield offlinestore.get_folder_info (full_name, 0, GLib.Priority.DEFAULT, null); + folderinfo = yield offlinestore.get_folder_info (full_name, 0, GLib.Priority.DEFAULT, connect_cancellable); } catch (Error e) { // We can cancel the operation if (!(e is GLib.IOError.CANCELLED)) { From c3ac9f25454a21faadeb904fb8ee6d4b6548a9c0 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Thu, 20 Apr 2023 19:58:57 +0200 Subject: [PATCH 68/72] Some cleanup, make some dialogs transient --- src/Composer.vala | 9 ++++++--- src/MessageList/MessageList.vala | 2 +- src/MessageList/MessageListItem.vala | 1 - 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Composer.vala b/src/Composer.vala index ea8fd7bc7..71d177b7b 100644 --- a/src/Composer.vala +++ b/src/Composer.vala @@ -772,7 +772,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { new ThemedIcon ("mail-send"), Gtk.ButtonsType.CLOSE ) { - badge_icon = new ThemedIcon ("dialog-warning") + badge_icon = new ThemedIcon ("dialog-warning"), + transient_for = this }; warning_dialog.present (); warning_dialog.response.connect (() => warning_dialog.destroy ()); @@ -788,7 +789,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { new ThemedIcon ("mail-send"), Gtk.ButtonsType.CLOSE ) { - badge_icon = new ThemedIcon ("dialog-error") + badge_icon = new ThemedIcon ("dialog-error"), + transient_for = this }; error_dialog.show_error_details (e.message); error_dialog.present (); @@ -1020,7 +1022,8 @@ public class Mail.Composer : Gtk.ApplicationWindow { new ThemedIcon ("mail-drafts"), Gtk.ButtonsType.CLOSE ) { - badge_icon = new ThemedIcon ("dialog-error") + badge_icon = new ThemedIcon ("dialog-error"), + transient_for = this }; error_dialog.show_error_details (e.message); error_dialog.present (); diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index b5462c3bd..e10704ba9 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -145,7 +145,7 @@ public class Mail.MessageList : Gtk.Box { }; scrolled_window.set_child (list_box); - // Prevent the focus of the webview causing the ScrolledWindow to scroll @TODO: correct replacement? + // Prevent the focus of the webview causing the ScrolledWindow to scroll. @TODO: correct replacement? var scrolled_child = scrolled_window.get_child (); if (scrolled_child is Gtk.Viewport) { ((Gtk.Viewport) scrolled_child).scroll_to_focus = true; diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 09c5a56c3..bf5e4fda0 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -518,7 +518,6 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { yield handle_inline_mime (part); } else if (part.disposition == "attachment") { var button = new AttachmentButton (part, loading_cancellable); - // button.activate.connect (() => show_attachment (button.mime_part)); attachment_bar.append (button); } if (field.type == "text") { From 917e37946a89d57aa247b3fef0f7061ddac31c29 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Thu, 20 Apr 2023 20:00:33 +0200 Subject: [PATCH 69/72] Remove SOUP 2 stuff --- meson.build | 1 - src/MainWindow.vala | 4 ---- src/WebView.vala | 4 ---- 3 files changed, 9 deletions(-) diff --git a/meson.build b/meson.build index 3e2a9c334..d8a3fc688 100644 --- a/meson.build +++ b/meson.build @@ -22,7 +22,6 @@ webkit2_dep = dependency('webkit2gtk-5.0') webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-5.0') folks_dep = dependency('folks') m_dep = meson.get_compiler('c').find_library('m') -add_project_arguments('--define=HAS_SOUP_3', language: 'vala') webkit2_extension_path = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name(), 'webkit2') diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 0c1ff76af..8007e4733 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -127,11 +127,7 @@ public class Mail.MainWindow : Adw.ApplicationWindow { }; message_list.hovering_over_link.connect ((label, url) => { -#if HAS_SOUP_3 var hover_url = url != null ? GLib.Uri.unescape_string (url) : null; -#else - var hover_url = url != null ? Soup.URI.decode (url) : null; -#endif if (hover_url == null) { message_overlay.hide (); diff --git a/src/WebView.vala b/src/WebView.vala index efd48f31d..ca527a80b 100644 --- a/src/WebView.vala +++ b/src/WebView.vala @@ -234,11 +234,7 @@ public class Mail.WebView : WebKit.WebView { } private bool handle_internal_response (WebKit.URISchemeRequest request) { -#if HAS_SOUP_3 string name = GLib.Uri.unescape_string (request.get_path ()); -#else - string name = Soup.URI.decode (request.get_path ()); -#endif InputStream? buf = this.internal_resources[name]; if (buf != null) { request.finish (buf, -1, null); From 6df8991366e35e5ec88874ceefa41a7e1392a446 Mon Sep 17 00:00:00 2001 From: Leonhard <106322251+leolost2605@users.noreply.github.com> Date: Mon, 24 Apr 2023 18:31:58 +0200 Subject: [PATCH 70/72] Update to webkigtk 6, revert Session authenticate stuff (#879) --- meson.build | 12 ++++++------ src/Backend/Session.vala | 18 +++++------------- src/MessageList/MessageListItem.vala | 2 +- src/WebView.vala | 8 ++++---- src/meson.build | 2 +- webkit-extension/Main.vala | 6 +++--- webkit-extension/meson.build | 2 +- 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/meson.build b/meson.build index d8a3fc688..32284e43f 100644 --- a/meson.build +++ b/meson.build @@ -17,13 +17,13 @@ adw_dep = dependency('libadwaita-1') gtk_dep = dependency('gtk4') camel_dep = dependency('camel-1.2', version: '>= 3.28') libedataserver_dep = dependency('libedataserver-1.2', version: '>=3.45.1') -libedataserverui_dep = dependency('libedataserverui4-1.0', version: '>= 3.45.1') -webkit2_dep = dependency('webkit2gtk-5.0') -webkit2_web_extension_dep = dependency('webkit2gtk-web-extension-5.0') +libedataserverui_dep = dependency('libedataserverui4-1.0', version: '>= 3.46.4') +webkit_dep = dependency('webkitgtk-6.0') +webkit_web_extension_dep = dependency('webkitgtk-web-process-extension-6.0') folks_dep = dependency('folks') m_dep = meson.get_compiler('c').find_library('m') -webkit2_extension_path = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name(), 'webkit2') +webkit_extension_path = join_paths(get_option('prefix'), get_option('libdir'), meson.project_name(), 'webkit2') dependencies = [ glib_dep, @@ -35,7 +35,7 @@ dependencies = [ camel_dep, libedataserver_dep, libedataserverui_dep, - webkit2_dep, + webkit_dep, folks_dep, m_dep ] @@ -50,7 +50,7 @@ extension_dependencies = [ glib_dep, gobject_dep, gee_dep, - webkit2_web_extension_dep + webkit_web_extension_dep ] add_global_arguments([ diff --git a/src/Backend/Session.vala b/src/Backend/Session.vala index 839fc4bcf..f4abf7444 100644 --- a/src/Backend/Session.vala +++ b/src/Backend/Session.vala @@ -126,17 +126,9 @@ public class Mail.Backend.Session : Camel.Session { /* We need a password, preferrably one cached in * the keyring or else by interactive user prompt. */ - try { - var credentials_provider = new E.SourceCredentialsProvider (registry); - E.NamedParameters out_credentials; - credentials_provider.lookup_sync (source, cancellable, out out_credentials); - bool out_authenticated; - try_credentials_sync (credentials_provider, source, out_credentials, out out_authenticated, cancellable, service, mechanism); - return out_authenticated; - } catch (Error e) { - critical (e.message); - return false; - } + var credentials_prompter = new E.CredentialsPrompter (registry); + credentials_prompter.set_auto_prompt (true); + return credentials_prompter.loop_prompt_sync (source, E.CredentialsPrompterPromptFlags.ALLOW_SOURCE_SAVE, (prompter, source, credentials, out out_authenticated, cancellable) => try_credentials_sync (prompter, source, credentials, out out_authenticated, cancellable, service, mechanism)); } else { return (result == Camel.AuthenticationResult.ACCEPTED); } @@ -182,7 +174,7 @@ public class Mail.Backend.Session : Camel.Session { return success; } - public bool try_credentials_sync (E.SourceCredentialsProvider provider, E.Source source, E.NamedParameters credentials, out bool out_authenticated, GLib.Cancellable? cancellable, Camel.Service service, string? mechanism) throws GLib.Error { + public bool try_credentials_sync (E.CredentialsPrompter prompter, E.Source source, E.NamedParameters credentials, out bool out_authenticated, GLib.Cancellable? cancellable, Camel.Service service, string? mechanism) throws GLib.Error { string credential_name = null; if (source.has_extension (E.SOURCE_EXTENSION_AUTHENTICATION)) { @@ -202,7 +194,7 @@ public class Mail.Backend.Session : Camel.Session { out_authenticated = (result == Camel.AuthenticationResult.ACCEPTED); if (out_authenticated) { - var credentials_source = provider.ref_credentials_source (source); + var credentials_source = prompter.get_provider ().ref_credentials_source (source); if (credentials_source != null) { credentials_source.invoke_authenticate_sync (credentials); diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index bf5e4fda0..8670343b9 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -281,7 +281,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { margin_end = 12 }; web_view.mouse_target_changed.connect (on_mouse_target_changed); - web_view.context_menu.connect (on_webview_context_menu); + // web_view.context_menu.connect (on_webview_context_menu); web_view.load_finished.connect (() => { loaded = true; }); diff --git a/src/WebView.vala b/src/WebView.vala index ca527a80b..532aaf819 100644 --- a/src/WebView.vala +++ b/src/WebView.vala @@ -39,8 +39,8 @@ public class Mail.WebView : WebKit.WebView { static construct { unowned WebKit.WebContext context = WebKit.WebContext.get_default (); unowned string? webkit_extension_path_env = Environment.get_variable ("WEBKIT_EXTENSION_PATH"); - context.set_web_extensions_directory (webkit_extension_path_env ?? WEBKIT_EXTENSION_PATH); - context.set_sandbox_enabled (true); + context.set_web_process_extensions_directory (webkit_extension_path_env ?? WEBKIT_EXTENSION_PATH); + // context.set_sandbox_enabled (true); context.register_uri_scheme ("cid", (req) => { WebView? view = req.get_web_view () as WebView; @@ -67,12 +67,12 @@ public class Mail.WebView : WebKit.WebView { setts.enable_fullscreen = false; setts.enable_html5_database = false; setts.enable_html5_local_storage = false; - setts.enable_java = false; + // setts.enable_java = false; setts.enable_javascript = false; setts.enable_media_stream = false; setts.enable_offline_web_application_cache = false; setts.enable_page_cache = false; - setts.enable_plugins = false; + // setts.enable_plugins = false; Object (settings: setts); } diff --git a/src/meson.build b/src/meson.build index efb4948a9..cef29646c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -32,6 +32,6 @@ executable( config_file, asresources, dependencies: dependencies, - c_args: '-DWEBKIT_EXTENSION_PATH="' + webkit2_extension_path + '"', + c_args: '-DWEBKIT_EXTENSION_PATH="' + webkit_extension_path + '"', install: true ) diff --git a/webkit-extension/Main.vala b/webkit-extension/Main.vala index 5ae3e1582..d5ffbf6f9 100644 --- a/webkit-extension/Main.vala +++ b/webkit-extension/Main.vala @@ -17,15 +17,15 @@ * Authored by: David Hewitt */ -namespace WebkitWebExtension { +namespace WebkitWebProcessExtension { private static void on_page_created (WebKit.WebPage page) { var mail_page = new Mail.Page (page); // Make so that the Mail.Page is destroyed at the same time of the WebKit.WebPage page.set_data ("elementary-mail-page", (owned) mail_page); } - [CCode (cname = "G_MODULE_EXPORT webkit_web_extension_initialize", instance_pos = -1)] - public void initialize (WebKit.WebExtension extension) { + [CCode (cname = "G_MODULE_EXPORT webkit_web_process_extension_initialize", instance_pos = -1)] + public void initialize (WebKit.WebProcessExtension extension) { extension.page_created.connect (on_page_created); } } diff --git a/webkit-extension/meson.build b/webkit-extension/meson.build index 40c0239c9..f06726493 100644 --- a/webkit-extension/meson.build +++ b/webkit-extension/meson.build @@ -7,6 +7,6 @@ shared_module('io.elementary.mail-webkit-extension', extension_files, dependencies: extension_dependencies, install: true, - install_dir: webkit2_extension_path + install_dir: webkit_extension_path ) From f48a1ad1ae3d300c367bd5de3c84d249ef56d5d0 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 24 Apr 2023 18:35:19 +0200 Subject: [PATCH 71/72] ConversationListStore: Fixes --- src/ConversationList/ConversationListStore.vala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ConversationList/ConversationListStore.vala b/src/ConversationList/ConversationListStore.vala index 6255039c1..c077da0e4 100644 --- a/src/ConversationList/ConversationListStore.vala +++ b/src/ConversationList/ConversationListStore.vala @@ -56,11 +56,11 @@ public class Mail.ConversationListStore : ListModel, Object { public void remove_all () { data.foreach ((item) => { - data.remove (item); + data.remove_all (item); }); } public void remove (ConversationItemModel item) { - data.remove (item); + data.remove_all (item); } } From e08641f6cebaa8d8eef8eb8d248dd286ea202120 Mon Sep 17 00:00:00 2001 From: Leonhard K Date: Mon, 24 Apr 2023 19:11:34 +0200 Subject: [PATCH 72/72] MessageList: Update context_menu and scroll to focus, fix crash --- src/MessageList/MessageList.vala | 2 +- src/MessageList/MessageListItem.vala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MessageList/MessageList.vala b/src/MessageList/MessageList.vala index e10704ba9..a3c96f21d 100644 --- a/src/MessageList/MessageList.vala +++ b/src/MessageList/MessageList.vala @@ -148,7 +148,7 @@ public class Mail.MessageList : Gtk.Box { // Prevent the focus of the webview causing the ScrolledWindow to scroll. @TODO: correct replacement? var scrolled_child = scrolled_window.get_child (); if (scrolled_child is Gtk.Viewport) { - ((Gtk.Viewport) scrolled_child).scroll_to_focus = true; + ((Gtk.Viewport) scrolled_child).scroll_to_focus = false; } orientation = VERTICAL; diff --git a/src/MessageList/MessageListItem.vala b/src/MessageList/MessageListItem.vala index 8670343b9..c2a3c2954 100644 --- a/src/MessageList/MessageListItem.vala +++ b/src/MessageList/MessageListItem.vala @@ -281,7 +281,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { margin_end = 12 }; web_view.mouse_target_changed.connect (on_mouse_target_changed); - // web_view.context_menu.connect (on_webview_context_menu); + web_view.context_menu.connect (on_webview_context_menu); web_view.load_finished.connect (() => { loaded = true; }); @@ -411,7 +411,7 @@ public class Mail.MessageListItem : Gtk.ListBoxRow { } } - private bool on_webview_context_menu (WebKit.ContextMenu menu, Gdk.Event event, WebKit.HitTestResult hit_test) { + private bool on_webview_context_menu (WebKit.ContextMenu menu, WebKit.HitTestResult hit_test) { WebKit.ContextMenu new_context_menu = new WebKit.ContextMenu (); for (int i = 0; i < menu.get_n_items (); i++) {