diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b02d93e..13f5defc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,7 @@ variables: libappstream-glib-devel libgee-devel libhandy1-devel libicu-devel libpeas-devel libsecret-devel libstemmer-devel libunwind-devel libxml2-devel libytnef-devel sqlite-devel webkitgtk4-devel + git FEDORA_TEST_DEPS: glibc-langpack-en gnutls-utils tar Xvfb xz # Ubuntu packages @@ -40,6 +41,7 @@ variables: libhandy-1-dev libicu-dev libjson-glib-dev libmessaging-menu-dev libpeas-dev libsecret-1-dev libsqlite3-dev libstemmer-dev libunwind-dev libwebkit2gtk-4.0-dev libxml2-dev libytnef0-dev + git ca-certificates UBUNTU_TEST_DEPS: gnutls-bin librsvg2-common locales xauth xvfb fedora: diff --git a/meson.build b/meson.build index 99913845..5d826918 100644 --- a/meson.build +++ b/meson.build @@ -89,7 +89,7 @@ icu_uc = dependency('icu-uc', version: '>=60') iso_codes = dependency('iso-codes') javascriptcoregtk = dependency('javascriptcoregtk-4.0', version: '>=' + target_webkit) json_glib = dependency('json-glib-1.0', version: '>= 1.0') -libhandy = dependency('libhandy-1', version: '>= 1.2') +libhandy = dependency('libhandy-1', version: '>= 1.2.1', required: false) libmath = cc.find_library('m') libpeas = dependency('libpeas-1.0', version: '>= 1.24.0') libpeas_gtk = dependency('libpeas-gtk-1.0', version: '>= 1.24.0') @@ -138,6 +138,26 @@ libstemmer = declare_dependency( ], ) +# Required until libhandy 1.2.1 is GA +libhandy_vapi = '' +if not libhandy.found() + libhandy_project = subproject( + 'libhandy', + default_options: [ + 'examples=false', + 'package_subdir=geary', + 'tests=false', + ] + ) + libhandy = declare_dependency( + dependencies: [ + libhandy_project.get_variable('libhandy_dep'), + libhandy_project.get_variable('libhandy_vapi') + ] + ) + libhandy_vapi = meson.build_root() / 'subprojects' / 'libhandy' / 'src' +endif + # Optional dependencies appstream_util = find_program('appstream-util', required: false) desktop_file_validate = find_program('desktop-file-validate', required: false) diff --git a/org.gnome.Geary.json b/org.gnome.Geary.json index 2d556511..83c79872 100644 --- a/org.gnome.Geary.json +++ b/org.gnome.Geary.json @@ -78,6 +78,24 @@ "/share" ] }, + { + "name": "libhandy", + "buildsystem": "meson", + "config-opts": [ + "-Dglade_catalog=disabled" + ], + "sources": [ + { + "type": "git", + "url": "https://gitlab.gnome.org/GNOME/libhandy.git", + "branch": "master" + } + ], + "cleanup": [ + "/bin", + "/share" + ] + }, { "name": "libical", "buildsystem": "cmake-ninja", diff --git a/org.gnome.Geary.yaml b/org.gnome.Geary.yaml index c3908a89..b7d06d52 100644 --- a/org.gnome.Geary.yaml +++ b/org.gnome.Geary.yaml @@ -111,6 +111,19 @@ modules: - /bin - /share + # Geary dependency + - name: libhandy + buildsystem: meson + config-opts: + - "-Dglade_catalog=disabled" + sources: + - type: git + url: "https://gitlab.gnome.org/GNOME/libhandy.git" + branch: master + cleanup: + - /bin + - /share + # EDS dependency - name: libical buildsystem: cmake-ninja diff --git a/po/POTFILES.in b/po/POTFILES.in index b93e507a..d8301ec4 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -38,12 +38,12 @@ src/client/application/secret-mediator.vala src/client/client-action.vala src/client/components/components-attachment-pane.vala src/client/components/components-conversation-actions.vala -src/client/components/components-conversation-action-bar.vala src/client/components/components-entry-undo.vala src/client/components/components-in-app-notification.vala src/client/components/components-info-bar-stack.vala src/client/components/components-info-bar.vala src/client/components/components-inspector.vala +src/client/components/components-main-toolbar.vala src/client/components/components-placeholder-pane.vala src/client/components/components-preferences-window.vala src/client/components/components-problem-report-info-bar.vala @@ -54,7 +54,6 @@ src/client/components/components-web-view.vala src/client/components/count-badge.vala src/client/components/folder-popover.vala src/client/components/icon-factory.vala -src/client/components/main-toolbar.vala src/client/components/monitored-progress-bar.vala src/client/components/monitored-spinner.vala src/client/components/status-bar.vala @@ -458,13 +457,13 @@ ui/components-attachment-pane.ui ui/components-attachment-pane-menus.ui ui/components-attachment-view.ui ui/components-conversation-actions.ui -ui/components-conversation-action-bar.ui -ui/components-conversation-header-bar.ui ui/components-in-app-notification.ui ui/components-info-bar.ui ui/components-inspector-error-view.ui ui/components-inspector-log-view.ui ui/components-inspector.ui +ui/components-main-toolbar.ui +ui/components-main-toolbar-menus.ui ui/components-placeholder-pane.ui ui/conversation-contact-popover.ui ui/conversation-email.ui @@ -476,7 +475,5 @@ ui/conversation-viewer.ui ui/find_bar.glade ui/folder-popover.ui ui/gtk/help-overlay.ui -ui/main-toolbar.ui -ui/main-toolbar-menus.ui ui/password-dialog.glade ui/problem-details-dialog.ui diff --git a/src/client/application/application-main-window.vala b/src/client/application/application-main-window.vala index 0e135326..d37f8eff 100644 --- a/src/client/application/application-main-window.vala +++ b/src/client/application/application-main-window.vala @@ -71,10 +71,17 @@ public class Application.MainWindow : { ACTION_ZOOM, on_zoom, "s" }, }; - private const int STATUS_BAR_HEIGHT = 18; - private const int UPDATE_UI_INTERVAL = 60; - private const int MIN_CONVERSATION_COUNT = 50; + // Handy leaflet children names + private const string INNER_LEAFLET = "inner_leaflet"; + private const string FOLDER_LIST = "folder_list"; + private const string CONVERSATION_LIST = "conversation_list"; + private const string CONVERSATION_VIEWER = "conversation_viewer"; + private const int STATUS_BAR_HEIGHT = 18; + + private const int UPDATE_UI_INTERVAL = 60; + + private const int MIN_CONVERSATION_COUNT = 50; static construct { // Set up default keybindings @@ -255,7 +262,19 @@ public class Application.MainWindow : } - private enum ConversationCount { NONE, SINGLE, MULTIPLE; } + private enum ConversationCount { + NONE, SINGLE, MULTIPLE; + + public static ConversationCount for_size(int size) { + return ( + size == 0 + ? NONE + : size == 1 + ? SINGLE + : MULTIPLE + ); + } + } /** Returns the window's associated client application instance. */ @@ -275,6 +294,41 @@ public class Application.MainWindow : get; private set; default = null; } + /** Specifies if the conversation list is currently displayed. */ + public bool is_folder_list_shown { + get { + return ( + (!this.outer_leaflet.folded || + this.outer_leaflet.visible_child_name == INNER_LEAFLET) && + (!this.inner_leaflet.folded || + this.inner_leaflet.visible_child_name == FOLDER_LIST) + ); + } + } + + /** Specifies if the conversation list is currently displayed. */ + public bool is_conversation_list_shown { + get { + return ( + (!this.outer_leaflet.folded || + this.outer_leaflet.visible_child_name == INNER_LEAFLET) && + (!this.inner_leaflet.folded || + this.inner_leaflet.visible_child_name == CONVERSATION_LIST) + ); + } + } + + /** Specifies if the conversation viewer is currently displayed. */ + public bool is_conversation_viewer_shown { + get { + return ( + (!this.outer_leaflet.folded || + this.outer_leaflet.visible_child_name == CONVERSATION_VIEWER) && + !this.has_composer + ); + } + } + /** The attachment manager for this window. */ public AttachmentManager attachments { get; private set; } @@ -302,14 +356,11 @@ public class Application.MainWindow : // Widget descendants public FolderList.Tree folder_list { get; private set; default = new FolderList.Tree(); } - public MainToolbar main_toolbar { get; private set; } + public Components.MainToolbar main_toolbar { get; private set; } public SearchBar search_bar { get; private set; } public ConversationListView conversation_list_view { get; private set; } public ConversationViewer conversation_viewer { get; private set; } - // Actions in the Conversation HeaderBar or ActionBar - private Components.ConversationActions conversation_actions; - public Components.InfoBarStack conversation_list_info_bars { get; private set; default = new Components.InfoBarStack(PRIORITY_QUEUE); } @@ -342,14 +393,23 @@ public class Application.MainWindow : [GtkChild] private unowned Gtk.Box main_layout; - [GtkChild] private unowned Hdy.Leaflet main_leaflet; - [GtkChild] private unowned Hdy.Leaflet conversations_leaflet; + + // Folds the inner leaftlet and conversation viewer + [GtkChild] private unowned Hdy.Leaflet outer_leaflet; + + // Folds the folder list and the conversation list + [GtkChild] private unowned Hdy.Leaflet inner_leaflet; + [GtkChild] private unowned Gtk.Box folder_box; [GtkChild] private unowned Gtk.ScrolledWindow folder_list_scrolled; + [GtkChild] private unowned Gtk.Box conversation_list_box; [GtkChild] private unowned Gtk.ScrolledWindow conversation_list_scrolled; + [GtkChild] private unowned Gtk.Revealer conversation_list_actions_revealer; + [GtkChild] private unowned Components.ConversationActions conversation_list_actions; + [GtkChild] private unowned Gtk.Box conversation_viewer_box; - [GtkChild] private unowned Components.ConversationActionBar conversation_viewer_action_bar; + [GtkChild] private unowned Gtk.Revealer conversation_viewer_actions_revealer; [GtkChild] private unowned Gtk.SizeGroup folder_size_group; [GtkChild] private unowned Gtk.SizeGroup folder_separator_size_group; [GtkChild] private unowned Gtk.SizeGroup conversations_size_group; @@ -360,7 +420,8 @@ public class Application.MainWindow : [GtkChild] private unowned Gtk.Overlay overlay; - private Components.ConversationActionBar action_bar; + private Components.ConversationActions[] folder_conversation_actions = {}; + private FolderPopover[] folder_popovers = {}; private Components.InfoBarStack info_bars = new Components.InfoBarStack(SINGLE); @@ -712,12 +773,9 @@ public class Application.MainWindow : // selected model. if (this.selected_folder != null) { - this.conversation_actions.copy_folder_menu.enable_disable_folder( - this.selected_folder, true - ); - this.conversation_actions.move_folder_menu.enable_disable_folder( - this.selected_folder, true - ); + foreach (var menu in this.folder_popovers) { + menu.enable_disable_folder(this.selected_folder, true); + } this.progress_monitor.remove(this.selected_folder.opening_monitor); this.selected_folder.properties.notify.disconnect(update_headerbar); @@ -758,11 +816,9 @@ public class Application.MainWindow : this.folder_list.deselect_folder(); } - update_conversation_actions(NONE); update_title(); - this.conversation_actions.update_trash_button( - !this.is_shift_down && this.selected_folder_supports_trash - ); + update_conversation_actions(NONE); + update_trash_action(); this.conversation_viewer.show_loading(); this.previous_selection_was_interactive = is_interactive; @@ -800,12 +856,9 @@ public class Application.MainWindow : this.conversation_list_view.set_model(conversations_model); // disable copy/move to the new folder - this.conversation_actions.copy_folder_menu.enable_disable_folder( - to_select, false - ); - this.conversation_actions.move_folder_menu.enable_disable_folder( - to_select, false - ); + foreach (var menu in this.folder_popovers) { + menu.enable_disable_folder(to_select, false); + } yield open_conversation_monitor(this.conversations, cancellable); yield this.controller.process_pending_composers(); @@ -893,11 +946,11 @@ public class Application.MainWindow : /** Shows the appopriate window menu, if any. */ public void show_window_menu() { - if (this.main_leaflet.folded) { - this.main_leaflet.navigate(Hdy.NavigationDirection.BACK); + if (this.outer_leaflet.folded) { + this.outer_leaflet.navigate(Hdy.NavigationDirection.BACK); } - if (this.conversations_leaflet.folded) { - this.conversations_leaflet.navigate(Hdy.NavigationDirection.BACK); + if (this.inner_leaflet.folded) { + this.inner_leaflet.navigate(Hdy.NavigationDirection.BACK); } this.main_toolbar.show_main_menu(); } @@ -949,7 +1002,7 @@ public class Application.MainWindow : this.conversation_viewer.do_compose(composer); } // Show the correct leaflet - this.main_leaflet.set_visible_child_name("conversation"); + this.outer_leaflet.set_visible_child_name(CONVERSATION_VIEWER); } } @@ -1120,8 +1173,9 @@ public class Application.MainWindow : foreach (var context in to_add) { this.folder_list.add_folder(context); if (context.folder.account == this.selected_account) { - this.conversation_actions.copy_folder_menu.add_folder(context.folder); - this.conversation_actions.move_folder_menu.add_folder(context.folder); + foreach (var menu in this.folder_popovers) { + menu.add_folder(context.folder); + } } context.folder.use_changed.connect(on_use_changed); } @@ -1141,8 +1195,9 @@ public class Application.MainWindow : folder.use_changed.disconnect(on_use_changed); if (folder.account == this.selected_account) { - this.conversation_actions.copy_folder_menu.remove_folder(folder); - this.conversation_actions.move_folder_menu.remove_folder(folder); + foreach (var menu in this.folder_popovers) { + menu.remove_folder(folder); + } } this.folder_list.remove_folder(context); } @@ -1292,18 +1347,8 @@ public class Application.MainWindow : this.conversation_size_group.add_widget(this.conversation_viewer); this.conversation_viewer_box.add(this.conversation_viewer); - - // Setup conversation actions - this.conversation_actions = new Components.ConversationActions(); - this.conversation_actions.move_folder_menu.folder_selected.connect(on_move_conversation); - this.conversation_actions.copy_folder_menu.folder_selected.connect(on_copy_conversation); - this.conversation_actions.bind_property("find-open", - this.conversation_viewer.conversation_find_bar, - "search-mode-enabled", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - // Main toolbar - this.main_toolbar = new MainToolbar(config, conversation_viewer_action_bar); + this.main_toolbar = new Components.MainToolbar(config); this.main_toolbar.add_to_size_groups(this.folder_size_group, this.folder_separator_size_group, this.conversations_size_group, @@ -1311,8 +1356,24 @@ public class Application.MainWindow : this.conversation_size_group); this.main_toolbar.add_to_swipe_groups(this.conversations_swipe_group, this.conversation_swipe_group); - this.main_toolbar.bind_property("search-open", this.search_bar, "search-mode-enabled", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + this.main_toolbar.bind_property( + "search-open", + this.search_bar, "search-mode-enabled", + SYNC_CREATE | BIDIRECTIONAL + ); + this.main_toolbar.bind_property( + "find-open", + this.conversation_viewer.conversation_find_bar, "search-mode-enabled", + SYNC_CREATE | BIDIRECTIONAL + ); + this.main_toolbar.notify["shown-actions"].connect( + () => { + this.conversation_viewer_actions_revealer.reveal_child = ( + this.main_toolbar.shown_actions == + this.main_toolbar.compact_actions + ); + } + ); if (config.desktop_environment == UNITY) { this.main_toolbar.show_close_button = false; this.main_layout.pack_start(main_toolbar, false, true, 0); @@ -1320,8 +1381,6 @@ public class Application.MainWindow : this.main_layout.pack_start(main_toolbar, false, true, 0); } - this.main_toolbar.add_conversation_actions(this.conversation_actions); - this.main_layout.pack_start(this.info_bars, false, true, 0); // Status bar @@ -1332,11 +1391,20 @@ public class Application.MainWindow : this.status_bar.add(this.spinner); this.status_bar.show_all(); - // Action bar - this.action_bar = new Components.ConversationActionBar(); - this.conversation_list_box.add_with_properties(action_bar, - "pack-type", Gtk.PackType.END, - "position", 0); + this.folder_conversation_actions = { + this.main_toolbar.full_actions, + this.main_toolbar.compact_actions, + this.conversation_list_actions + }; + foreach (var actions in folder_conversation_actions) { + var move = actions.move_folder_menu; + this.folder_popovers += move; + move.folder_selected.connect(on_move_conversation); + + var copy = actions.copy_folder_menu; + this.folder_popovers += copy; + copy.folder_selected.connect(on_copy_conversation); + } } /** {@inheritDoc} */ @@ -1504,17 +1572,19 @@ public class Application.MainWindow : private void select_account(Geary.Account? account) { if (this.selected_account != account) { if (this.selected_account != null) { - this.conversation_actions.copy_folder_menu.clear(); - this.conversation_actions.move_folder_menu.clear(); + foreach (var menu in this.folder_popovers) { + menu.clear(); + } } this.selected_account = account; this.search_bar.set_account(account); if (account != null) { - foreach (Geary.Folder folder in account.list_folders()) { - this.conversation_actions.copy_folder_menu.add_folder(folder); - this.conversation_actions.move_folder_menu.add_folder(folder); + foreach (var menu in this.folder_popovers) { + foreach (var folder in account.list_folders()) { + menu.add_folder(folder); + } } } @@ -1536,7 +1606,10 @@ public class Application.MainWindow : // setting it again. this.conversation_list_view.select_conversations(to_select); - this.conversation_actions.selected_conversations = to_select.size; + this.conversation_list_actions.selected_conversations = to_select.size; + this.main_toolbar.full_actions.selected_conversations = to_select.size; + this.main_toolbar.compact_actions.selected_conversations = to_select.size; + if (this.selected_folder != null && !this.has_composer) { switch(to_select.size) { case 0: @@ -1749,14 +1822,18 @@ public class Application.MainWindow : bool move_enabled = ( sensitive && (selected_folder is Geary.FolderSupport.Move) ); - this.conversation_actions.move_message_button.set_sensitive(move_enabled); get_window_action(ACTION_SHOW_MOVE_MENU).set_enabled(move_enabled); + foreach (var actions in this.folder_conversation_actions) { + actions.set_move_sensitive(move_enabled); + } bool copy_enabled = ( sensitive && (selected_folder is Geary.FolderSupport.Copy) ); - this.conversation_actions.copy_message_button.set_sensitive(copy_enabled); get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(move_enabled); + foreach (var actions in this.folder_conversation_actions) { + actions.set_copy_sensitive(copy_enabled); + } get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled( sensitive && (selected_folder is Geary.FolderSupport.Archive) @@ -1769,19 +1846,35 @@ public class Application.MainWindow : ); this.update_context_dependent_actions.begin(sensitive); + update_conversation_list_actions_revealer(count); + } + + private void update_conversation_list_actions_revealer(ConversationCount count) { switch (count) { - case NONE: - conversation_actions.take_ownership(null); - break; - case SINGLE: - this.main_toolbar.add_conversation_actions(this.conversation_actions); - break; - case MULTIPLE: - this.action_bar.add_conversation_actions(this.conversation_actions); - break; + case NONE: + this.conversation_list_actions_revealer.reveal_child = false; + break; + case SINGLE: + this.conversation_list_actions_revealer.reveal_child = ( + this.outer_leaflet.folded + ); + break; + case MULTIPLE: + this.conversation_list_actions_revealer.reveal_child = true; + break; } } + private void update_trash_action() { + var show_trash = ( + !this.is_shift_down && + this.selected_folder_supports_trash + ); + this.conversation_list_actions.update_trash_button(show_trash); + this.main_toolbar.full_actions.update_trash_button(show_trash); + this.main_toolbar.compact_actions.update_trash_button(show_trash); + } + private async void update_context_dependent_actions(bool sensitive) { // Cancel any existing update that is running this.action_update_cancellable.cancel(); @@ -1835,9 +1928,7 @@ public class Application.MainWindow : private void set_shift_key_down(bool down) { this.is_shift_down = down; - this.conversation_actions.update_trash_button( - !down && this.selected_folder_supports_trash - ); + update_trash_action(); } private inline void check_shift_event(Gdk.EventKey event) { @@ -1856,17 +1947,17 @@ public class Application.MainWindow : private void focus_next_pane() { var focus = get_focus(); - if (main_leaflet.folded) { - if (main_leaflet.visible_child_name == "conversations") { - if (conversations_leaflet.folded && - conversations_leaflet.visible_child_name == "folder" || + if (this.outer_leaflet.folded) { + if (this.outer_leaflet.visible_child_name == INNER_LEAFLET) { + if (this.inner_leaflet.folded && + this.inner_leaflet.visible_child_name == FOLDER_LIST || focus == this.folder_list) { - conversations_leaflet.navigate(Hdy.NavigationDirection.FORWARD); + this.inner_leaflet.navigate(Hdy.NavigationDirection.FORWARD); focus = this.conversation_list_view; } else { - if (this.conversation_actions.selected_conversations == 1 && + if (this.conversation_list_view.get_selected().size == 1 && this.selected_folder.properties.email_total > 0) { - main_leaflet.navigate(Hdy.NavigationDirection.FORWARD); + this.outer_leaflet.navigate(Hdy.NavigationDirection.FORWARD); focus = this.conversation_viewer.visible_child; } } @@ -1894,11 +1985,11 @@ public class Application.MainWindow : private void focus_previous_pane() { var focus = get_focus(); - if (main_leaflet.folded) { - if (main_leaflet.visible_child_name == "conversations") { - if (conversations_leaflet.folded) { - if (conversations_leaflet.visible_child_name == "conversations") { - conversations_leaflet.navigate(Hdy.NavigationDirection.BACK); + if (this.outer_leaflet.folded) { + if (this.outer_leaflet.visible_child_name == INNER_LEAFLET) { + if (this.inner_leaflet.folded) { + if (this.inner_leaflet.visible_child_name == CONVERSATION_LIST) { + this.inner_leaflet.navigate(Hdy.NavigationDirection.BACK); focus = this.folder_list; } } else { @@ -1908,7 +1999,7 @@ public class Application.MainWindow : focus = this.conversation_list_view; } } else { - main_leaflet.navigate(Hdy.NavigationDirection.BACK); + this.outer_leaflet.navigate(Hdy.NavigationDirection.BACK); focus = this.conversation_list_view; } } else if (focus != null) { @@ -2012,14 +2103,16 @@ public class Application.MainWindow : } [GtkCallback] - private void on_main_leaflet_visible_child_changed() { - if (main_leaflet.child_transition_running) - return; - - if (main_leaflet.visible_child_name == "conversations" && main_leaflet.folded) - if (this.conversation_viewer.current_composer != null) { - this.conversation_viewer.current_composer.activate_close_action(); - } + private void on_outer_leaflet_changed() { + int selected = this.conversation_list_view.get_selected().size; + update_conversation_list_actions_revealer( + ConversationCount.for_size(selected) + ); + if (this.has_composer && + this.outer_leaflet.folded && + (this.is_folder_list_shown || this.is_conversation_list_shown)) { + close_composer(false, false); + } } private void on_offline_infobar_response() { @@ -2229,8 +2322,9 @@ public class Application.MainWindow : private void on_conversation_activated(Geary.App.Conversation activated, bool single) { if (single) { - if (main_leaflet.folded) + if (this.outer_leaflet.folded) { focus_next_pane(); + } } else if (this.selected_folder != null) { if (this.selected_folder.used_as != DRAFTS) { this.application.new_window.begin( @@ -2295,11 +2389,25 @@ public class Application.MainWindow : } private void on_show_copy_menu() { - this.conversation_actions.copy_message_button.clicked(); + if (this.is_conversation_list_shown && + this.conversation_list_actions_revealer.child_revealed) { + this.conversation_list_actions.show_copy_menu(); + } else if (this.is_conversation_viewer_shown) { + this.main_toolbar.shown_actions.show_copy_menu(); + } else { + this.error_bell(); + } } private void on_show_move_menu() { - this.conversation_actions.move_message_button.clicked(); + if (this.is_conversation_list_shown && + this.conversation_list_actions_revealer.child_revealed) { + this.conversation_list_actions.show_move_menu(); + } else if (this.is_conversation_viewer_shown) { + this.main_toolbar.shown_actions.show_move_menu(); + } else { + this.error_bell(); + } } private void on_conversation_up() { diff --git a/src/client/components/components-conversation-action-bar.vala b/src/client/components/components-conversation-action-bar.vala deleted file mode 100644 index 4b4cfacc..00000000 --- a/src/client/components/components-conversation-action-bar.vala +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2016 Software Freedom Conservancy Inc. - * Copyright © 2020 Purism SPC - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -// Draws the conversation action bar. -[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-action-bar.ui")] -public class Components.ConversationActionBar : Gtk.Revealer { - - - [GtkChild] public unowned Gtk.Box action_box { get; } - - private ulong owner_notify; - - - /** - * This takes ownership of the ConversationActions and places some of - * the buttons into the ActionBar. - */ - public void add_conversation_actions(Components.ConversationActions actions) { - if (actions.owner == this) - return; - - actions.take_ownership(this); - action_box.pack_start(actions.mark_copy_move_buttons, false, false); - action_box.pack_end(actions.archive_trash_delete_buttons, false, false); - reveal_child = true; - this.owner_notify = actions.notify["owner"].connect(() => { - if (actions.owner != this) { - reveal_child = false; - actions.disconnect (this.owner_notify); - } - }); - } -} diff --git a/src/client/components/components-conversation-actions.vala b/src/client/components/components-conversation-actions.vala index a8cdaed9..f7df39fb 100644 --- a/src/client/components/components-conversation-actions.vala +++ b/src/client/components/components-conversation-actions.vala @@ -1,34 +1,39 @@ -/* Copyright 2017 Software Freedom Conservancy Inc. +/* + * Copyright © 2017 Software Freedom Conservancy Inc. + * Copyright © 2021 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ /** - * Container for actions for a conversation generally placed into the ActionBar or HeaderBar - * The user of the actions needs to take ownership before they can place the actions in a container + * A container of conversation-related actions. */ -public class Components.ConversationActions : GLib.Object { - public Gtk.Widget? owner { get; private set; } - // Copy and Move popovers +[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-actions.ui")] +public class Components.ConversationActions : Gtk.Box { + + public bool show_conversation_actions { get; construct; } + + public bool show_response_actions { get; construct; } + + public bool pack_justified { get; construct; } + public FolderPopover copy_folder_menu { get; private set; default = new FolderPopover(); } + public FolderPopover move_folder_menu { get; private set; default = new FolderPopover(); } - // How many conversations are selected right now. Should automatically be updated. + public int selected_conversations { get; set; } - public bool find_open { get; set; } - public Gtk.Box mark_copy_move_buttons { get; private set; } - public Gtk.MenuButton mark_message_button { get; private set; } - public Gtk.MenuButton copy_message_button { get; private set; } - public Gtk.MenuButton move_message_button { get; private set; } + [GtkChild] private unowned Gtk.Box response_buttons { get; } - public Gtk.Box reply_forward_buttons { get; private set; } + [GtkChild] private unowned Gtk.Box mark_copy_move_buttons { get; } + [GtkChild] private unowned Gtk.MenuButton mark_message_button { get; } + [GtkChild] private unowned Gtk.MenuButton copy_message_button { get; } + [GtkChild] private unowned Gtk.MenuButton move_message_button { get; } - public Gtk.Box archive_trash_delete_buttons { get; private set; } - private Gtk.Button archive_button; - private Gtk.Button trash_delete_button; - - public Gtk.ToggleButton find_button { get; private set; } + [GtkChild] private unowned Gtk.Box archive_trash_delete_buttons { get; } + [GtkChild] private unowned Gtk.Button archive_button; + [GtkChild] private unowned Gtk.Button trash_delete_button; private bool show_trash_button = true; @@ -36,47 +41,49 @@ public class Components.ConversationActions : GLib.Object { private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU); private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU); - public ConversationActions() { - Gtk.Builder builder = - new Gtk.Builder.from_resource("/org/gnome/Geary/components-conversation-actions.ui"); + static construct { + set_css_name("components-conversation-actions"); + } + + // GObject style constuction to support loading via GTK Builder files + construct { // Assemble the mark menus - Gtk.Builder menu_builder = - new Gtk.Builder.from_resource("/org/gnome/Geary/main-toolbar-menus.ui"); - MenuModel mark_menu = (MenuModel) menu_builder.get_object("mark_message_menu"); + Gtk.Builder menu_builder = new Gtk.Builder.from_resource( + "/org/gnome/Geary/components-main-toolbar-menus.ui" + ); + GLib.MenuModel mark_menu = (MenuModel) menu_builder.get_object( + "mark_message_menu" + ); - this.mark_copy_move_buttons = (Gtk.Box) builder.get_object("mark_copy_move_buttons"); - this.mark_message_button = (Gtk.MenuButton) builder.get_object("mark_message_button"); - this.copy_message_button = (Gtk.MenuButton) builder.get_object("copy_message_button"); - this.move_message_button = (Gtk.MenuButton) builder.get_object("move_message_button"); - - this.reply_forward_buttons = (Gtk.Box) builder.get_object("reply_forward_buttons"); - - this.archive_trash_delete_buttons = (Gtk.Box) builder.get_object("archive_trash_delete_buttons"); - this.archive_button = (Gtk.Button) builder.get_object("archive_button"); - this.trash_delete_button = (Gtk.Button) builder.get_object("trash_delete_button"); - - this.find_button = (Gtk.ToggleButton) builder.get_object("find_button"); - - this.bind_property("find-open", this.find_button, "active", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); this.notify["selected-conversations"].connect(() => update_conversation_buttons()); this.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu); this.copy_message_button.popover = copy_folder_menu; this.move_message_button.popover = move_folder_menu; + + this.response_buttons.set_visible(this.show_response_actions); + this.mark_copy_move_buttons.set_visible(this.show_conversation_actions); + this.archive_trash_delete_buttons.set_visible(this.show_conversation_actions); + + if (this.pack_justified) { + this.archive_trash_delete_buttons.hexpand = true; + this.archive_trash_delete_buttons.halign = END; + } } - /** Sets the new owner and removes the previous owner and parents of the single actions */ - public void take_ownership(Gtk.Widget? new_owner) { - remove_parent(mark_copy_move_buttons); - remove_parent(reply_forward_buttons); - remove_parent(archive_trash_delete_buttons); - remove_parent(find_button); - owner = new_owner; + public void set_move_sensitive(bool is_sensitive) { + this.move_message_button.sensitive = is_sensitive; } - private void remove_parent (Gtk.Widget widget) { - if (widget.parent != null) - widget.parent.remove(widget); + public void show_move_menu() { + this.move_message_button.clicked(); + } + + public void set_copy_sensitive(bool is_sensitive) { + this.copy_message_button.sensitive = is_sensitive; + } + + public void show_copy_menu() { + this.copy_message_button.clicked(); } public void update_trash_button(bool show_trash) { diff --git a/src/client/components/components-conversation-header-bar.vala b/src/client/components/components-conversation-header-bar.vala deleted file mode 100644 index 202cf7c4..00000000 --- a/src/client/components/components-conversation-header-bar.vala +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2020 Purism SPC - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-header-bar.ui")] -public class Components.ConversationHeaderBar : Hdy.HeaderBar { - public Components.ConversationActionBar action_bar { get; set; } - public bool folded { get; set; } - - private ulong owner_notify; - private Gtk.Widget? reply_forward_buttons; - private Gtk.Widget? archive_trash_delete_buttons; - - public ConversationHeaderBar() { - } - - public override void size_allocate(Gtk.Allocation allocation) { - update_action_bar(); - base.size_allocate(allocation); - } - - [GtkCallback] - private void update_action_bar () { - /* Only show the action_bar when the conversation_header is shown */ - if (parent == null) - action_bar.reveal_child = false; - else if (reply_forward_buttons != null && archive_trash_delete_buttons != null) - if (action_bar.reveal_child && get_allocated_width() > 600) { - action_bar.reveal_child = false; - remove_action_parent(); - pack_start(reply_forward_buttons); - pack_end(archive_trash_delete_buttons); - } else if (!action_bar.reveal_child && get_allocated_width() < 600) { - remove_action_parent(); - action_bar.action_box.pack_start(reply_forward_buttons, false, false); - action_bar.action_box.pack_end(archive_trash_delete_buttons, false, false); - action_bar.reveal_child = true; - } - } - - private void remove_action_parent() { - if (reply_forward_buttons != null && reply_forward_buttons.parent != null) - reply_forward_buttons.parent.remove(reply_forward_buttons); - if (archive_trash_delete_buttons != null && archive_trash_delete_buttons.parent != null) - archive_trash_delete_buttons.parent.remove(archive_trash_delete_buttons); - } - - public void add_conversation_actions(Components.ConversationActions actions) { - if (actions.owner != this) { - actions.take_ownership(this); - pack_start(actions.mark_copy_move_buttons); - pack_end(actions.find_button); - - reply_forward_buttons = actions.reply_forward_buttons; - archive_trash_delete_buttons = actions.archive_trash_delete_buttons; - action_bar.reveal_child = get_allocated_width() > 600; - update_action_bar(); - this.owner_notify = actions.notify["owner"].connect(() => { - if (actions.owner != this) { - action_bar.reveal_child = false; - reply_forward_buttons = null; - archive_trash_delete_buttons = null; - actions.disconnect (this.owner_notify); - } - }); - } - } -} diff --git a/src/client/components/main-toolbar.vala b/src/client/components/components-main-toolbar.vala similarity index 65% rename from src/client/components/main-toolbar.vala rename to src/client/components/components-main-toolbar.vala index 6140053c..a66e9888 100644 --- a/src/client/components/main-toolbar.vala +++ b/src/client/components/components-main-toolbar.vala @@ -1,23 +1,39 @@ -/* Copyright 2017 Software Freedom Conservancy Inc. +/* + * Copyright © 2017 Software Freedom Conservancy Inc. + * Copyright © 2021 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ -// Draws the main toolbar. -[GtkTemplate (ui = "/org/gnome/Geary/main-toolbar.ui")] -public class MainToolbar : Hdy.Leaflet { - // How wide the left pane should be. Auto-synced with our settings - public int left_pane_width { get; set; } - // Used to form the title of the folder header + +/** + * The toolbar for the main window. + * + * @see Application.MainWindow + */ +[GtkTemplate (ui = "/org/gnome/Geary/components-main-toolbar.ui")] +public class Components.MainToolbar : Hdy.Leaflet { + + public string account { get; set; } + public string folder { get; set; } - // Close button settings + public bool show_close_button { get; set; default = true; } - // Search bar + public bool search_open { get; set; default = false; } - private Components.ConversationActionBar conversation_viewer_action_bar; + public bool find_open { get; set; default = false; } + + public ConversationActions shown_actions { + get { + return (ConversationActions) this.actions_squeezer.visible_child; + } + } + + [GtkChild] public unowned ConversationActions full_actions; + [GtkChild] public unowned ConversationActions compact_actions; [GtkChild] private unowned Hdy.Leaflet conversations_leaflet; @@ -27,36 +43,47 @@ public class MainToolbar : Hdy.Leaflet { [GtkChild] private unowned Gtk.Separator folder_separator; - // Conversations header elements + // Conversation list header elements [GtkChild] private unowned Hdy.HeaderBar conversations_header; - [GtkChild] private unowned Gtk.ToggleButton search_conversations_button; + [GtkChild] private unowned Gtk.ToggleButton search_button; [GtkChild] private unowned Gtk.Separator conversations_separator; - // Conversation header elements - [GtkChild] private unowned Components.ConversationHeaderBar conversation_header; + // Conversation viewer header elements + [GtkChild] private unowned Hdy.HeaderBar conversation_header; + [GtkChild] private unowned Hdy.Squeezer actions_squeezer; + [GtkChild] private unowned Gtk.ToggleButton find_button; [GtkChild] private unowned Hdy.HeaderGroup header_group; - Gtk.SizeGroup conversation_group; + private Gtk.SizeGroup conversation_group; - public MainToolbar(Application.Configuration config, - Components.ConversationActionBar action_bar) { + + public MainToolbar(Application.Configuration config) { if (config.desktop_environment != UNITY) { this.bind_property("account", this.conversations_header, "title", BindingFlags.SYNC_CREATE); this.bind_property("folder", this.conversations_header, "subtitle", BindingFlags.SYNC_CREATE); } - this.conversation_viewer_action_bar = action_bar; - this.conversation_header.action_bar = action_bar; // Assemble the main/mark menus - Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/main-toolbar-menus.ui"); + Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/components-main-toolbar-menus.ui"); MenuModel main_menu = (MenuModel) builder.get_object("main_menu"); - // Setup folder header elements this.main_menu_button.popover = new Gtk.Popover.from_model(null, main_menu); - this.bind_property("search-open", this.search_conversations_button, "active", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + this.bind_property( + "search-open", + this.search_button, "active", + SYNC_CREATE | BIDIRECTIONAL + ); + this.bind_property( + "find-open", + this.find_button, "active", + SYNC_CREATE | BIDIRECTIONAL + ); + + this.actions_squeezer.notify["visible-child"].connect_after( + () => { notify_property("shown-actions"); } + ); } public void set_conversation_header(Hdy.HeaderBar header) { @@ -97,10 +124,6 @@ public class MainToolbar : Hdy.Leaflet { conversation_group.add_swipeable(this); } - public void add_conversation_actions(Components.ConversationActions actions) { - conversation_header.add_conversation_actions(actions); - } - public void show_main_menu() { this.main_menu_button.clicked(); } diff --git a/src/client/composer/composer-box.vala b/src/client/composer/composer-box.vala index 2f823cb5..2addc0c1 100644 --- a/src/client/composer/composer-box.vala +++ b/src/client/composer/composer-box.vala @@ -27,14 +27,14 @@ public class Composer.Box : Gtk.Frame, Container { /** {@inheritDoc} */ internal Widget composer { get; set; } - private MainToolbar main_toolbar { get; private set; } + private Components.MainToolbar main_toolbar { get; private set; } /** Emitted when the container is closed. */ public signal void vanished(); - public Box(Widget composer, MainToolbar main_toolbar) { + public Box(Widget composer, Components.MainToolbar main_toolbar) { this.composer = composer; this.composer.set_mode(PANED); diff --git a/src/client/meson.build b/src/client/meson.build index 5aed39de..ff3c8a6d 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -47,8 +47,6 @@ client_vala_sources = files( 'components/components-attachment-pane.vala', 'components/components-conversation-actions.vala', - 'components/components-conversation-action-bar.vala', - 'components/components-conversation-header-bar.vala', 'components/components-entry-undo.vala', 'components/components-info-bar-stack.vala', 'components/components-info-bar.vala', @@ -57,6 +55,7 @@ client_vala_sources = files( 'components/components-inspector-error-view.vala', 'components/components-inspector-log-view.vala', 'components/components-inspector-system-view.vala', + 'components/components-main-toolbar.vala', 'components/components-placeholder-pane.vala', 'components/components-preferences-window.vala', 'components/components-problem-report-info-bar.vala', @@ -67,7 +66,6 @@ client_vala_sources = files( 'components/count-badge.vala', 'components/folder-popover.vala', 'components/icon-factory.vala', - 'components/main-toolbar.vala', 'components/monitored-progress-bar.vala', 'components/monitored-spinner.vala', 'components/status-bar.vala', diff --git a/src/meson.build b/src/meson.build index d2b01803..a67336af 100644 --- a/src/meson.build +++ b/src/meson.build @@ -139,6 +139,9 @@ valadoc_vapi_dirs = [ vapi_dir, meson.current_build_dir() ] +if libhandy_vapi != '' + valadoc_vapi_dirs += libhandy_vapi +endif # Hopefully Meson will get baked-in valadoc support, so we don't have # to resort to these kinds of hacks any more. See @@ -147,7 +150,11 @@ valadoc_vapi_dirs = [ valadoc_dep_args = [] foreach dep : valadoc_dependencies valadoc_dep_args += '--pkg' - valadoc_dep_args += dep.name() + if dep != libhandy + valadoc_dep_args += dep.name() + else + valadoc_dep_args += 'libhandy-1' + endif endforeach valadoc_dep_args += [ '--pkg', 'icu-uc' ] valadoc_dep_args += [ '--pkg', 'libstemmer' ] diff --git a/subprojects/libhandy.wrap b/subprojects/libhandy.wrap new file mode 100644 index 00000000..2dfa9777 --- /dev/null +++ b/subprojects/libhandy.wrap @@ -0,0 +1,5 @@ +[wrap-git] +directory = libhandy +url = https://gitlab.gnome.org/GNOME/libhandy.git +revision = 1.2.1 + diff --git a/ui/application-main-window.ui b/ui/application-main-window.ui index d54d365a..289c6013 100644 --- a/ui/application-main-window.ui +++ b/ui/application-main-window.ui @@ -18,14 +18,14 @@ True vertical - + True True over - - + + - + True True True @@ -57,7 +57,7 @@ - folder + folder_list @@ -98,14 +98,36 @@ 1 + + + True + False + slide-up + + + True + True + False + True + True + + + + + False + False + end + 0 + + - conversations + conversation_list - conversations + inner_leaflet @@ -125,8 +147,17 @@ True vertical - + True + False + slide-up + + + True + True + False + + end @@ -134,7 +165,7 @@ - conversation + conversation_viewer @@ -191,12 +222,12 @@ - + - + diff --git a/ui/components-conversation-action-bar.ui b/ui/components-conversation-action-bar.ui deleted file mode 100644 index 74daca48..00000000 --- a/ui/components-conversation-action-bar.ui +++ /dev/null @@ -1,34 +0,0 @@ - - - - - True - mail-archive-symbolic - - - diff --git a/ui/components-conversation-actions.ui b/ui/components-conversation-actions.ui index f1423bdf..5471d284 100644 --- a/ui/components-conversation-actions.ui +++ b/ui/components-conversation-actions.ui @@ -1,200 +1,201 @@ - - + + diff --git a/ui/components-conversation-header-bar.ui b/ui/components-conversation-header-bar.ui deleted file mode 100644 index 965f1262..00000000 --- a/ui/components-conversation-header-bar.ui +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/ui/main-toolbar-menus.ui b/ui/components-main-toolbar-menus.ui similarity index 100% rename from ui/main-toolbar-menus.ui rename to ui/components-main-toolbar-menus.ui diff --git a/ui/main-toolbar.ui b/ui/components-main-toolbar.ui similarity index 63% rename from ui/main-toolbar.ui rename to ui/components-main-toolbar.ui index aba5529b..0a87acec 100644 --- a/ui/main-toolbar.ui +++ b/ui/components-main-toolbar.ui @@ -2,11 +2,7 @@ - - True - mail-archive-symbolic - -