diff --git a/desktop/org.gnome.Geary.gschema.xml b/desktop/org.gnome.Geary.gschema.xml
index bbbadc36..89354dc2 100644
--- a/desktop/org.gnome.Geary.gschema.xml
+++ b/desktop/org.gnome.Geary.gschema.xml
@@ -21,42 +21,12 @@
The last recorded height of the application window.
-
- 100
- Position of folder list pane
- Position of the folder list Paned grabber.
-
-
-
- -1
- Position of folder list pane when horizontal
- Position of the folder list Paned grabber in the horizontal orientation.
-
-
-
- 200
- Position of folder list pane when vertical
- Position of the folder list Paned grabber in the vertical orientation.
-
-
-
- true
- Orientation of the folder list pane
- True if the folder list Paned is in the horizontal orientation.
-
-
false
Show/hide formatting toolbar
True if the formatting toolbar in the composer is shown.
-
- 250
- Position of message list pane
- Position of the message list Paned grabber.
-
-
true
Autoselect next message
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 09c3c970..68e3ca34 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -39,6 +39,8 @@ src/client/application/secret-mediator.vala
src/client/client-action.vala
src/client/components/client-web-view.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
@@ -455,6 +457,8 @@ ui/composer-widget.ui
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-in-app-notification.ui
ui/components-inspector-error-view.ui
ui/components-inspector-log-view.ui
diff --git a/src/client/application/application-configuration.vala b/src/client/application/application-configuration.vala
index 00c359c9..48542df6 100644
--- a/src/client/application/application-configuration.vala
+++ b/src/client/application/application-configuration.vala
@@ -19,12 +19,7 @@ public class Application.Configuration : Geary.BaseObject {
public const string COMPOSE_AS_HTML_KEY = "compose-as-html";
public const string CONVERSATION_VIEWER_ZOOM_KEY = "conversation-viewer-zoom";
public const string DISPLAY_PREVIEW_KEY = "display-preview";
- public const string FOLDER_LIST_PANE_HORIZONTAL_KEY = "folder-list-pane-horizontal";
- public const string FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY = "folder-list-pane-position-horizontal";
- public const string FOLDER_LIST_PANE_POSITION_KEY = "folder-list-pane-position";
- public const string FOLDER_LIST_PANE_POSITION_VERTICAL_KEY = "folder-list-pane-position-vertical";
public const string FORMATTING_TOOLBAR_VISIBLE = "formatting-toolbar-visible";
- public const string MESSAGES_PANE_POSITION_KEY = "messages-pane-position";
public const string OPTIONAL_PLUGINS = "optional-plugins";
public const string SEARCH_STRATEGY_KEY = "search-strategy";
public const string SINGLE_KEY_SHORTCUTS = "single-key-shortcuts";
@@ -90,33 +85,11 @@ public class Application.Configuration : Geary.BaseObject {
get { return settings.get_boolean(WINDOW_MAXIMIZE_KEY); }
}
- public int folder_list_pane_position_old {
- get { return settings.get_int(FOLDER_LIST_PANE_POSITION_KEY); }
- }
-
- public int folder_list_pane_position_horizontal {
- get { return settings.get_int(FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY); }
- set { settings.set_int(FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY, value); }
- }
-
- public int folder_list_pane_position_vertical {
- get { return settings.get_int(FOLDER_LIST_PANE_POSITION_VERTICAL_KEY); }
- }
-
- public bool folder_list_pane_horizontal {
- get { return settings.get_boolean(FOLDER_LIST_PANE_HORIZONTAL_KEY); }
- }
-
public bool formatting_toolbar_visible {
get { return settings.get_boolean(FORMATTING_TOOLBAR_VISIBLE); }
set { settings.set_boolean(FORMATTING_TOOLBAR_VISIBLE, value); }
}
- public int messages_pane_position {
- get { return settings.get_int(MESSAGES_PANE_POSITION_KEY); }
- set { settings.set_int(MESSAGES_PANE_POSITION_KEY, value); }
- }
-
public bool autoselect {
get { return settings.get_boolean(AUTOSELECT_KEY); }
}
diff --git a/src/client/application/application-main-window.vala b/src/client/application/application-main-window.vala
index 47749019..0e6a89e1 100644
--- a/src/client/application/application-main-window.vala
+++ b/src/client/application/application-main-window.vala
@@ -31,6 +31,7 @@ public class Application.MainWindow :
public const string ACTION_TOGGLE_JUNK = "toggle-conversation-junk";
public const string ACTION_TRASH_CONVERSATION = "trash-conversation";
public const string ACTION_ZOOM = "zoom";
+ public const string ACTION_NAVIGATION_BACK = "navigation-back";
private const ActionEntry[] EDIT_ACTIONS = {
{ Action.Edit.UNDO, on_undo },
@@ -42,6 +43,7 @@ public class Application.MainWindow :
{ ACTION_FIND_IN_CONVERSATION, on_find_in_conversation_action },
{ ACTION_SEARCH, on_search_activated },
+ { ACTION_NAVIGATION_BACK, focus_previous_pane},
// Message actions
{ ACTION_REPLY_CONVERSATION, on_reply_conversation },
{ ACTION_REPLY_ALL_CONVERSATION, on_reply_all_conversation },
@@ -282,6 +284,9 @@ public class Application.MainWindow :
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);
}
@@ -317,11 +322,9 @@ public class Application.MainWindow :
[GtkChild]
private Gtk.Box main_layout;
[GtkChild]
- private Gtk.Box search_bar_box;
+ private Hdy.Leaflet main_leaflet;
[GtkChild]
- private Gtk.Paned folder_paned;
- [GtkChild]
- private Gtk.Paned conversations_paned;
+ private Hdy.Leaflet conversations_leaflet;
[GtkChild]
private Gtk.Box folder_box;
[GtkChild]
@@ -330,9 +333,26 @@ public class Application.MainWindow :
private Gtk.Box conversation_list_box;
[GtkChild]
private Gtk.ScrolledWindow conversation_list_scrolled;
+ [GtkChild]
+ private Gtk.SizeGroup folder_size_group;
+ [GtkChild]
+ private Gtk.SizeGroup folder_separator_size_group;
+ [GtkChild]
+ private Gtk.SizeGroup conversations_size_group;
+ [GtkChild]
+ private Gtk.SizeGroup conversations_separator_size_group;
+ [GtkChild]
+ private Gtk.SizeGroup conversation_size_group;
+ [GtkChild]
+ private Hdy.SwipeGroup conversations_swipe_group;
+ [GtkChild]
+ private Hdy.SwipeGroup conversation_swipe_group;
+
[GtkChild]
private Gtk.Overlay overlay;
+ private Components.ConversationActionBar action_bar;
+
private Components.InfoBarStack info_bars =
new Components.InfoBarStack(SINGLE);
@@ -511,7 +531,7 @@ public class Application.MainWindow :
});
setup_layout(application.config);
- on_change_orientation();
+ this.folder_box.pack_start(status_bar, false, false);
update_command_actions();
update_conversation_actions(NONE);
@@ -677,10 +697,10 @@ public class Application.MainWindow :
// selected model.
if (this.selected_folder != null) {
- this.main_toolbar.copy_folder_menu.enable_disable_folder(
+ this.conversation_actions.copy_folder_menu.enable_disable_folder(
this.selected_folder, true
);
- this.main_toolbar.move_folder_menu.enable_disable_folder(
+ this.conversation_actions.move_folder_menu.enable_disable_folder(
this.selected_folder, true
);
@@ -725,9 +745,10 @@ public class Application.MainWindow :
update_conversation_actions(NONE);
update_title();
- this.main_toolbar.update_trash_button(
+ this.conversation_actions.update_trash_button(
!this.is_shift_down && this.selected_folder_supports_trash
);
+
this.conversation_viewer.show_loading();
this.previous_selection_was_interactive = is_interactive;
@@ -764,10 +785,10 @@ public class Application.MainWindow :
this.conversation_list_view.set_model(conversations_model);
// disable copy/move to the new folder
- this.main_toolbar.copy_folder_menu.enable_disable_folder(
+ this.conversation_actions.copy_folder_menu.enable_disable_folder(
to_select, false
);
- this.main_toolbar.move_folder_menu.enable_disable_folder(
+ this.conversation_actions.move_folder_menu.enable_disable_folder(
to_select, false
);
@@ -901,6 +922,8 @@ public class Application.MainWindow :
} else {
this.conversation_viewer.do_compose(composer);
}
+ // Show the correct leaflet
+ this.main_leaflet.set_visible_child_name("conversation");
}
}
@@ -1073,8 +1096,8 @@ public class Application.MainWindow :
foreach (var context in to_add) {
this.folder_list.add_folder(context);
if (context.folder.account == this.selected_account) {
- this.main_toolbar.copy_folder_menu.add_folder(context.folder);
- this.main_toolbar.move_folder_menu.add_folder(context.folder);
+ this.conversation_actions.copy_folder_menu.add_folder(context.folder);
+ this.conversation_actions.move_folder_menu.add_folder(context.folder);
}
context.folder.use_changed.connect(on_use_changed);
}
@@ -1094,8 +1117,8 @@ public class Application.MainWindow :
folder.use_changed.disconnect(on_use_changed);
if (folder.account == this.selected_account) {
- this.main_toolbar.copy_folder_menu.remove_folder(folder);
- this.main_toolbar.move_folder_menu.remove_folder(folder);
+ this.conversation_actions.copy_folder_menu.remove_folder(folder);
+ this.conversation_actions.move_folder_menu.remove_folder(folder);
}
this.folder_list.remove_folder(context);
}
@@ -1130,18 +1153,9 @@ public class Application.MainWindow :
// This code both loads AND saves the pane positions with live updating. This is more
// resilient against crashes because the value in dconf changes *immediately*, and
// stays saved in the event of a crash.
- config.bind(Configuration.MESSAGES_PANE_POSITION_KEY, this.conversations_paned, "position");
config.bind(Configuration.WINDOW_WIDTH_KEY, this, "window-width");
config.bind(Configuration.WINDOW_HEIGHT_KEY, this, "window-height");
config.bind(Configuration.WINDOW_MAXIMIZE_KEY, this, "window-maximized");
- // Update to layout
- if (config.folder_list_pane_position_horizontal == -1) {
- config.folder_list_pane_position_horizontal = config.folder_list_pane_position_old;
- config.messages_pane_position += config.folder_list_pane_position_old;
- }
- config.settings.changed[
- Configuration.FOLDER_LIST_PANE_HORIZONTAL_KEY
- ].connect(on_change_orientation);
}
private void restore_saved_window_state() {
@@ -1218,12 +1232,14 @@ public class Application.MainWindow :
// Search bar
this.search_bar = new SearchBar(this.application.engine);
this.search_bar.search_text_changed.connect(on_search);
- this.search_bar_box.pack_start(this.search_bar, false, false, 0);
+ this.conversation_list_box.pack_start(this.search_bar, false, false, 0);
+
// Folder list
this.folder_list.folder_selected.connect(on_folder_selected);
this.folder_list.move_conversation.connect(on_move_conversation);
this.folder_list.copy_conversation.connect(on_copy_conversation);
+ this.folder_list.folder_activated.connect(on_folder_activated);
this.folder_list_scrolled.add(this.folder_list);
// Conversation list
@@ -1248,16 +1264,31 @@ public class Application.MainWindow :
on_conversation_view_added
);
- this.conversations_paned.pack2(this.conversation_viewer, true, false);
+ this.conversation_viewer.hexpand = true;
+ this.conversation_size_group.add_widget(this.conversation_viewer);
+ this.main_leaflet.add_with_properties(this.conversation_viewer, "name", "conversation", null);
+
+
+ // 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);
- this.main_toolbar.move_folder_menu.folder_selected.connect(on_move_conversation);
- this.main_toolbar.copy_folder_menu.folder_selected.connect(on_copy_conversation);
+ this.main_toolbar.add_to_size_groups(this.folder_size_group,
+ this.folder_separator_size_group,
+ this.conversations_size_group,
+ this.conversations_separator_size_group,
+ 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("find-open", this.conversation_viewer.conversation_find_bar,
- "search-mode-enabled", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
if (config.desktop_environment == UNITY) {
this.main_toolbar.show_close_button = false;
this.main_layout.pack_start(main_toolbar, false, true, 0);
@@ -1268,6 +1299,8 @@ public class Application.MainWindow :
set_titlebar(titlebar);
}
+ this.main_toolbar.add_conversation_actions(this.conversation_actions);
+
this.main_layout.pack_start(this.info_bars, false, true, 0);
// Status bar
@@ -1277,6 +1310,12 @@ public class Application.MainWindow :
this.spinner.set_progress_monitor(progress_monitor);
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);
}
/** {@inheritDoc} */
@@ -1444,8 +1483,8 @@ public class Application.MainWindow :
private void select_account(Geary.Account? account) {
if (this.selected_account != account) {
if (this.selected_account != null) {
- this.main_toolbar.copy_folder_menu.clear();
- this.main_toolbar.move_folder_menu.clear();
+ this.conversation_actions.copy_folder_menu.clear();
+ this.conversation_actions.move_folder_menu.clear();
}
this.selected_account = account;
@@ -1453,8 +1492,8 @@ public class Application.MainWindow :
if (account != null) {
foreach (Geary.Folder folder in account.list_folders()) {
- this.main_toolbar.copy_folder_menu.add_folder(folder);
- this.main_toolbar.move_folder_menu.add_folder(folder);
+ this.conversation_actions.copy_folder_menu.add_folder(folder);
+ this.conversation_actions.move_folder_menu.add_folder(folder);
}
}
@@ -1476,7 +1515,7 @@ public class Application.MainWindow :
// setting it again.
this.conversation_list_view.select_conversations(to_select);
- this.main_toolbar.selected_conversations = to_select.size;
+ this.conversation_actions.selected_conversations = to_select.size;
if (this.selected_folder != null && !this.has_composer) {
switch(to_select.size) {
case 0:
@@ -1642,38 +1681,6 @@ public class Application.MainWindow :
}
}
- private void on_change_orientation() {
- bool horizontal = this.application.config.folder_list_pane_horizontal;
- bool initial = true;
-
- if (this.status_bar.parent != null) {
- this.status_bar.parent.remove(status_bar);
- initial = false;
- }
-
- GLib.Settings.unbind(this.folder_paned, "position");
- this.folder_paned.orientation = horizontal ? Gtk.Orientation.HORIZONTAL :
- Gtk.Orientation.VERTICAL;
-
- int folder_list_width =
- this.application.config.folder_list_pane_position_horizontal;
- if (horizontal) {
- if (!initial)
- this.conversations_paned.position += folder_list_width;
- this.folder_box.pack_start(status_bar, false, false);
- } else {
- if (!initial)
- this.conversations_paned.position -= folder_list_width;
- this.conversation_list_box.pack_start(status_bar, false, false);
- }
-
- this.application.config.bind(
- horizontal
- ? Configuration.FOLDER_LIST_PANE_POSITION_HORIZONTAL_KEY
- : Configuration.FOLDER_LIST_PANE_POSITION_VERTICAL_KEY,
- this.folder_paned, "position");
- }
-
private void update_headerbar() {
update_title();
if (this.selected_folder != null) {
@@ -1721,13 +1728,13 @@ public class Application.MainWindow :
bool move_enabled = (
sensitive && (selected_folder is Geary.FolderSupport.Move)
);
- this.main_toolbar.move_message_button.set_sensitive(move_enabled);
+ this.conversation_actions.move_message_button.set_sensitive(move_enabled);
get_window_action(ACTION_SHOW_MOVE_MENU).set_enabled(move_enabled);
bool copy_enabled = (
sensitive && (selected_folder is Geary.FolderSupport.Copy)
);
- this.main_toolbar.copy_message_button.set_sensitive(copy_enabled);
+ this.conversation_actions.copy_message_button.set_sensitive(copy_enabled);
get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(move_enabled);
get_window_action(ACTION_ARCHIVE_CONVERSATION).set_enabled(
@@ -1741,6 +1748,17 @@ public class Application.MainWindow :
);
this.update_context_dependent_actions.begin(sensitive);
+ 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;
+ }
}
private async void update_context_dependent_actions(bool sensitive) {
@@ -1796,7 +1814,7 @@ public class Application.MainWindow :
private void set_shift_key_down(bool down) {
this.is_shift_down = down;
- this.main_toolbar.update_trash_button(
+ this.conversation_actions.update_trash_button(
!down && this.selected_folder_supports_trash
);
}
@@ -1816,7 +1834,23 @@ public class Application.MainWindow :
private void focus_next_pane() {
var focus = get_focus();
- if (focus != null) {
+
+ if (main_leaflet.folded) {
+ if (main_leaflet.visible_child_name == "conversations") {
+ if (conversations_leaflet.folded &&
+ conversations_leaflet.visible_child_name == "folder" ||
+ focus == this.folder_list) {
+ conversations_leaflet.navigate(Hdy.NavigationDirection.FORWARD);
+ focus = this.conversation_list_view;
+ } else {
+ if (this.conversation_actions.selected_conversations == 1 &&
+ this.selected_folder.properties.email_total > 0) {
+ main_leaflet.navigate(Hdy.NavigationDirection.FORWARD);
+ focus = this.conversation_viewer.visible_child;
+ }
+ }
+ }
+ } else if (focus != null) {
if (focus == this.folder_list ||
focus.is_ancestor(this.folder_list)) {
focus = this.conversation_list_view;
@@ -1838,7 +1872,25 @@ public class Application.MainWindow :
private void focus_previous_pane() {
var focus = get_focus();
- if (focus != null) {
+
+ 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);
+ focus = this.folder_list;
+ }
+ } else {
+ if (focus == this.conversation_list_view)
+ focus = this.folder_list;
+ else
+ focus = this.conversation_list_view;
+ }
+ } else {
+ main_leaflet.navigate(Hdy.NavigationDirection.BACK);
+ focus = this.conversation_list_view;
+ }
+ } else if (focus != null) {
if (focus == this.folder_list ||
focus.is_ancestor(this.folder_list)) {
focus = this.conversation_viewer.visible_child;
@@ -1938,6 +1990,17 @@ public class Application.MainWindow :
return Gdk.EVENT_STOP;
}
+ [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_offline_infobar_response() {
this.info_bars.remove(this.offline_infobar);
}
@@ -2121,13 +2184,21 @@ public class Application.MainWindow :
}
}
- private void on_conversation_activated(Geary.App.Conversation activated) {
- if (this.selected_folder != null) {
+ private void on_folder_activated(Geary.Folder? folder) {
+ if (folder != null)
+ focus_next_pane();
+ }
+
+ private void on_conversation_activated(Geary.App.Conversation activated, bool single) {
+ if (single) {
+ if (main_leaflet.folded)
+ focus_next_pane();
+ } else if (this.selected_folder != null) {
if (this.selected_folder.used_as != DRAFTS) {
this.application.new_window.begin(
this.selected_folder,
this.conversation_list_view.copy_selected()
- );
+ );
} else {
// TODO: Determine how to map between conversations
// and drafts correctly.
@@ -2176,11 +2247,11 @@ public class Application.MainWindow :
}
private void on_show_copy_menu() {
- this.main_toolbar.copy_message_button.clicked();
+ this.conversation_actions.copy_message_button.clicked();
}
private void on_show_move_menu() {
- this.main_toolbar.move_message_button.clicked();
+ this.conversation_actions.move_message_button.clicked();
}
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
new file mode 100644
index 00000000..cb574521
--- /dev/null
+++ b/src/client/components/components-conversation-action-bar.vala
@@ -0,0 +1,39 @@
+/*
+ * 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 {
+ private ulong owner_notify;
+
+ [GtkChild]
+ private Gtk.Box action_box;
+
+ public ConversationActionBar() {
+ }
+
+ /**
+ * 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
new file mode 100644
index 00000000..a8cdaed9
--- /dev/null
+++ b/src/client/components/components-conversation-actions.vala
@@ -0,0 +1,132 @@
+/* Copyright 2017 Software Freedom Conservancy Inc.
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (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
+ */
+public class Components.ConversationActions : GLib.Object {
+ public Gtk.Widget? owner { get; private set; }
+ // Copy and Move popovers
+ 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; }
+
+ public Gtk.Box reply_forward_buttons { get; private set; }
+
+ 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; }
+
+ private bool show_trash_button = true;
+
+ // Load these at construction time
+ 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");
+ // 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");
+
+ 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;
+ }
+
+ /** 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;
+ }
+
+ private void remove_parent (Gtk.Widget widget) {
+ if (widget.parent != null)
+ widget.parent.remove(widget);
+ }
+
+ public void update_trash_button(bool show_trash) {
+ this.show_trash_button = show_trash;
+ update_conversation_buttons();
+ }
+
+ /** Updates tooltip text depending on number of conversations selected. */
+ private void update_conversation_buttons() {
+ this.mark_message_button.tooltip_text = ngettext(
+ "Mark conversation",
+ "Mark conversations",
+ this.selected_conversations
+ );
+ this.copy_message_button.tooltip_text = ngettext(
+ "Add label to conversation",
+ "Add label to conversations",
+ this.selected_conversations
+ );
+ this.move_message_button.tooltip_text = ngettext(
+ "Move conversation",
+ "Move conversations",
+ this.selected_conversations
+ );
+ this.archive_button.tooltip_text = ngettext(
+ "Archive conversation",
+ "Archive conversations",
+ this.selected_conversations
+ );
+
+ if (this.show_trash_button) {
+ this.trash_delete_button.action_name = Action.Window.prefix(
+ Application.MainWindow.ACTION_TRASH_CONVERSATION
+ );
+ this.trash_delete_button.image = trash_image;
+ this.trash_delete_button.tooltip_text = ngettext(
+ "Move conversation to Trash",
+ "Move conversations to Trash",
+ this.selected_conversations
+ );
+ } else {
+ this.trash_delete_button.action_name = Action.Window.prefix(
+ Application.MainWindow.ACTION_DELETE_CONVERSATION
+ );
+ this.trash_delete_button.image = delete_image;
+ this.trash_delete_button.tooltip_text = ngettext(
+ "Delete conversation",
+ "Delete conversations",
+ this.selected_conversations
+ );
+ }
+ }
+}
diff --git a/src/client/components/components-preferences-window.vala b/src/client/components/components-preferences-window.vala
index b1bdfd7b..ea978a3a 100644
--- a/src/client/components/components-preferences-window.vala
+++ b/src/client/components/components-preferences-window.vala
@@ -136,16 +136,6 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
display_preview_row.activatable_widget = display_preview;
display_preview_row.add(display_preview);
- var three_pane_view = new Gtk.Switch();
- three_pane_view.valign = CENTER;
-
- var three_pane_view_row = new Hdy.ActionRow();
- /// Translators: Preferences label
- three_pane_view_row.title = _("Use _three pane view");
- three_pane_view_row.use_underline = true;
- three_pane_view_row.activatable_widget = three_pane_view;
- three_pane_view_row.add(three_pane_view);
-
var single_key_shortucts = new Gtk.Switch();
single_key_shortucts.valign = CENTER;
@@ -180,7 +170,6 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
//group.description = _("General application preferences");
group.add(autoselect_row);
group.add(display_preview_row);
- group.add(three_pane_view_row);
group.add(single_key_shortucts_row);
group.add(startup_notifications_row);
@@ -210,11 +199,6 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
display_preview,
"state"
);
- config.bind(
- Application.Configuration.FOLDER_LIST_PANE_HORIZONTAL_KEY,
- three_pane_view,
- "state"
- );
config.bind(
Application.Configuration.SINGLE_KEY_SHORTCUTS,
single_key_shortucts,
diff --git a/src/client/components/main-toolbar.vala b/src/client/components/main-toolbar.vala
index 31f87df6..6458b7fb 100644
--- a/src/client/components/main-toolbar.vala
+++ b/src/client/components/main-toolbar.vala
@@ -6,7 +6,7 @@
// Draws the main toolbar.
[GtkTemplate (ui = "/org/gnome/Geary/main-toolbar.ui")]
-public class MainToolbar : Gtk.Box {
+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
@@ -14,145 +14,101 @@ public class MainToolbar : Gtk.Box {
public string folder { get; set; }
// Close button settings
public bool show_close_button { get; set; default = true; }
- // Search and find bar
+ // Search bar
public bool search_open { get; set; default = false; }
- public bool find_open { get; set; default = false; }
- // Copy and Move popovers
- 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; }
+ [GtkChild]
+ private Hdy.Leaflet conversations_leaflet;
// Folder header elements
[GtkChild]
private Gtk.HeaderBar folder_header;
[GtkChild]
- private Gtk.ToggleButton search_conversations_button;
- [GtkChild]
private Gtk.MenuButton main_menu_button;
+ [GtkChild]
+ private Gtk.Separator folder_separator;
+
+ // Conversations header elements
+ [GtkChild]
+ private Gtk.HeaderBar conversations_header;
+ [GtkChild]
+ private Gtk.ToggleButton search_conversations_button;
+
+ [GtkChild]
+ private Gtk.Separator conversations_separator;
+
// Conversation header elements
[GtkChild]
private Gtk.HeaderBar conversation_header;
- [GtkChild]
- private Gtk.MenuButton mark_message_button;
- [GtkChild]
- public Gtk.MenuButton copy_message_button;
- [GtkChild]
- public Gtk.MenuButton move_message_button;
- [GtkChild]
- private Gtk.Button archive_button;
- [GtkChild]
- private Gtk.Button trash_delete_button;
- [GtkChild]
- private Gtk.ToggleButton find_button;
[GtkChild]
private Hdy.HeaderGroup header_group;
- private bool show_trash_button = true;
-
- // Load these at construction time
- 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);
-
+ Gtk.SizeGroup conversation_group;
public MainToolbar(Application.Configuration config) {
- // Sync headerbar width with left pane
- config.bind(
- Application.Configuration.MESSAGES_PANE_POSITION_KEY,
- this,
- "left-pane-width",
- SettingsBindFlags.GET
- );
- this.bind_property("left-pane-width", this.folder_header, "width-request", BindingFlags.SYNC_CREATE);
-
if (config.desktop_environment != UNITY) {
- this.bind_property("account", this.folder_header, "title", BindingFlags.SYNC_CREATE);
- this.bind_property("folder", this.folder_header, "subtitle", BindingFlags.SYNC_CREATE);
+ this.bind_property("account", this.conversations_header, "title", BindingFlags.SYNC_CREATE);
+ this.bind_property("folder", this.conversations_header, "subtitle", BindingFlags.SYNC_CREATE);
}
// Assemble the main/mark menus
Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/main-toolbar-menus.ui");
MenuModel main_menu = (MenuModel) builder.get_object("main_menu");
- MenuModel mark_menu = (MenuModel) builder.get_object("mark_message_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);
+ }
- // Setup conversation header elements
- 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;
+ public void add_conversation_actions(Components.ConversationActions actions) {
+ if (actions.owner == this)
+ return;
- this.bind_property("find-open", this.find_button, "active",
- BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+ actions.take_ownership(this);
+ conversation_header.pack_start(actions.mark_copy_move_buttons);
+ conversation_header.pack_start(actions.reply_forward_buttons);
+ conversation_header.pack_end(actions.find_button);
+ conversation_header.pack_end(actions.archive_trash_delete_buttons);
}
public void set_conversation_header(Gtk.HeaderBar header) {
- conversation_header.hide();
+ remove(conversation_header);
this.header_group.add_gtk_header_bar(header);
- pack_start(header, true, true);
+ header.hexpand = true;
+ conversation_group.remove_widget(conversation_header);
+ conversation_group.add_widget(header);
+ add(header);
+ child_set(header, "name", "conversation", null);
}
public void remove_conversation_header(Gtk.HeaderBar header) {
remove(header);
this.header_group.remove_gtk_header_bar(header);
- conversation_header.show();
+ conversation_group.remove_widget(header);
+ conversation_group.add_widget(conversation_header);
+ add(conversation_header);
+ child_set(conversation_header, "name", "conversation", null);
}
- public void update_trash_button(bool show_trash) {
- this.show_trash_button = show_trash;
- update_conversation_buttons();
+ public void add_to_size_groups(Gtk.SizeGroup folder_group,
+ Gtk.SizeGroup folder_separator_group,
+ Gtk.SizeGroup conversations_group,
+ Gtk.SizeGroup conversations_separator_group,
+ Gtk.SizeGroup conversation_group) {
+ folder_group.add_widget(folder_header);
+ folder_separator_group.add_widget(folder_separator);
+ conversations_group.add_widget(conversations_header);
+ conversations_separator_group.add_widget(conversations_separator);
+ conversation_group.add_widget(conversation_header);
+ this.conversation_group = conversation_group;
}
- // Updates tooltip text depending on number of conversations selected.
- private void update_conversation_buttons() {
- this.mark_message_button.tooltip_text = ngettext(
- "Mark conversation",
- "Mark conversations",
- this.selected_conversations
- );
- this.copy_message_button.tooltip_text = ngettext(
- "Add label to conversation",
- "Add label to conversations",
- this.selected_conversations
- );
- this.move_message_button.tooltip_text = ngettext(
- "Move conversation",
- "Move conversations",
- this.selected_conversations
- );
- this.archive_button.tooltip_text = ngettext(
- "Archive conversation",
- "Archive conversations",
- this.selected_conversations
- );
-
- if (this.show_trash_button) {
- this.trash_delete_button.action_name = Action.Window.prefix(
- Application.MainWindow.ACTION_TRASH_CONVERSATION
- );
- this.trash_delete_button.image = trash_image;
- this.trash_delete_button.tooltip_text = ngettext(
- "Move conversation to Trash",
- "Move conversations to Trash",
- this.selected_conversations
- );
- } else {
- this.trash_delete_button.action_name = Action.Window.prefix(
- Application.MainWindow.ACTION_DELETE_CONVERSATION
- );
- this.trash_delete_button.image = delete_image;
- this.trash_delete_button.tooltip_text = ngettext(
- "Delete conversation",
- "Delete conversations",
- this.selected_conversations
- );
- }
+ public void add_to_swipe_groups(Hdy.SwipeGroup conversations_group,
+ Hdy.SwipeGroup conversation_group) {
+ conversations_group.add_swipeable(this.conversations_leaflet);
+ conversation_group.add_swipeable(this);
}
}
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 37e93fb4..17430021 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -1361,6 +1361,11 @@ public class Composer.Widget : Gtk.EventBox, Geary.BaseInterface {
}
}
+ /* Activate the close action */
+ public void activate_close_action() {
+ this.actions.activate_action(ACTION_CLOSE, null);
+ }
+
internal void set_mode(PresentationMode new_mode) {
this.current_mode = new_mode;
this.header.set_mode(new_mode);
diff --git a/src/client/conversation-list/conversation-list-view.vala b/src/client/conversation-list/conversation-list-view.vala
index a4d5cca6..0baa66b4 100644
--- a/src/client/conversation-list/conversation-list-view.vala
+++ b/src/client/conversation-list/conversation-list-view.vala
@@ -17,6 +17,7 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
private Geary.Scheduler.Scheduled? scheduled_update_visible_conversations = null;
private Gee.Set selected = new Gee.HashSet();
private Geary.IdleManager selection_update;
+ private Gtk.GestureMultiPress gesture;
// Determines if the next folder scan should avoid selecting a
// conversation when autoselect is enabled
@@ -26,7 +27,7 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
public signal void conversations_selected(Gee.Set selected);
// Signal for when a conversation has been double-clicked, or selected and enter is pressed.
- public signal void conversation_activated(Geary.App.Conversation activated);
+ public signal void conversation_activated(Geary.App.Conversation activated, bool single = false);
public virtual signal void load_more() {
enable_load_more = false;
@@ -52,11 +53,13 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
Gtk.TreeSelection selection = get_selection();
selection.set_mode(Gtk.SelectionMode.MULTIPLE);
style_updated.connect(on_style_changed);
- row_activated.connect(on_row_activated);
notify["vadjustment"].connect(on_vadjustment_changed);
+ key_press_event.connect(on_key_press);
button_press_event.connect(on_button_press);
+ gesture = new Gtk.GestureMultiPress(this);
+ gesture.pressed.connect(on_gesture_pressed);
// Set up drag and drop.
Gtk.drag_source_set(this, Gdk.ModifierType.BUTTON1_MASK, FolderList.Tree.TARGET_ENTRY_LIST,
@@ -269,6 +272,53 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
return parent.get_vadjustment();
}
+ private void on_gesture_pressed(int n_press, double x, double y) {
+ if (gesture.get_current_button() != Gdk.BUTTON_PRIMARY)
+ return;
+
+ Gtk.TreePath? path;
+ get_path_at_pos((int) x, (int) y, out path, null, null, null);
+
+ // If the user clicked in an empty area, do nothing.
+ if (path == null)
+ return;
+
+ Geary.App.Conversation? c = get_model().get_conversation_at_path(path);
+ if (c == null)
+ return;
+
+ Gdk.Event event = gesture.get_last_event(gesture.get_current_sequence());
+ Gdk.ModifierType modifiers = Gtk.accelerator_get_default_mod_mask();
+
+ Gdk.ModifierType state_mask;
+ event.get_state(out state_mask);
+
+ if ((state_mask & modifiers) == 0 && n_press == 1) {
+ conversation_activated(c, true);
+ } else if ((state_mask & modifiers) == Gdk.ModifierType.SHIFT_MASK && n_press == 2) {
+ conversation_activated(c);
+ }
+ }
+
+ private bool on_key_press(Gdk.EventKey event) {
+ if (this.selected.size != 1)
+ return false;
+
+ Geary.App.Conversation? c = this.selected.to_array()[0];
+ if (c == null)
+ return false;
+
+ Gdk.ModifierType modifiers = Gtk.accelerator_get_default_mod_mask();
+
+ if (event.keyval == Gdk.Key.Return ||
+ event.keyval == Gdk.Key.ISO_Enter ||
+ event.keyval == Gdk.Key.KP_Enter ||
+ event.keyval == Gdk.Key.space ||
+ event.keyval == Gdk.Key.KP_Space)
+ conversation_activated(c, !((event.state & modifiers) == Gdk.ModifierType.SHIFT_MASK));
+ return false;
+ }
+
private bool on_button_press(Gdk.EventButton event) {
// Get the coordinates on the cell as well as the clicked path.
int cell_x;
@@ -575,12 +625,6 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
return false;
}
- private void on_row_activated(Gtk.TreePath path) {
- Geary.App.Conversation? c = get_model().get_conversation_at_path(path);
- if (c != null)
- conversation_activated(c);
- }
-
// Enable/disable hover effect on all selected cells.
private void set_hover_selected(bool hover) {
ConversationListCellRenderer.set_hover_selected(hover);
diff --git a/src/client/conversation-viewer/conversation-viewer.vala b/src/client/conversation-viewer/conversation-viewer.vala
index f04a1d26..eec5f6a4 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -68,6 +68,10 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
public signal void conversation_removed(ConversationListBox list);
+ static construct {
+ set_css_name("geary-conversation-viewer");
+ }
+
/**
* Constructs a new conversation view instance.
*/
diff --git a/src/client/folder-list/folder-list-tree.vala b/src/client/folder-list/folder-list-tree.vala
index fb91347e..f820d12f 100644
--- a/src/client/folder-list/folder-list-tree.vala
+++ b/src/client/folder-list/folder-list-tree.vala
@@ -16,6 +16,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
public signal void folder_selected(Geary.Folder? folder);
+ public signal void folder_activated(Geary.Folder? folder);
public signal void copy_conversation(Geary.Folder folder);
public signal void move_conversation(Geary.Folder folder);
@@ -30,7 +31,9 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
public Tree() {
base(TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE, drop_handler);
base_ref();
+ set_activate_on_single_click(true);
entry_selected.connect(on_entry_selected);
+ entry_activated.connect(on_entry_activated);
// GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll
// remove it.
@@ -87,6 +90,13 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
}
}
+ private void on_entry_activated(Sidebar.SelectableEntry selectable) {
+ AbstractFolderEntry? entry = selectable as AbstractFolderEntry;
+ if (entry != null) {
+ folder_activated(entry.folder);
+ }
+ }
+
public void set_user_folders_root_name(Geary.Account account, string name) {
if (account_branches.has_key(account))
account_branches.get(account).user_folder_group.rename(name);
diff --git a/src/client/meson.build b/src/client/meson.build
index 088f4e47..c0eb0c16 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -48,6 +48,8 @@ client_vala_sources = files(
'components/client-web-view.vala',
'components/components-attachment-pane.vala',
+ 'components/components-conversation-actions.vala',
+ 'components/components-conversation-action-bar.vala',
'components/components-entry-undo.vala',
'components/components-info-bar-stack.vala',
'components/components-info-bar.vala',
diff --git a/src/client/sidebar/sidebar-tree.vala b/src/client/sidebar/sidebar-tree.vala
index a5f95092..78d73e98 100644
--- a/src/client/sidebar/sidebar-tree.vala
+++ b/src/client/sidebar/sidebar-tree.vala
@@ -80,6 +80,8 @@ public class Sidebar.Tree : Gtk.TreeView {
public signal void entry_selected(Sidebar.SelectableEntry selectable);
+ public signal void entry_activated(Sidebar.SelectableEntry selectable);
+
public signal void selected_entry_removed(Sidebar.SelectableEntry removed);
public signal void branch_added(Sidebar.Branch branch);
@@ -298,6 +300,15 @@ public class Sidebar.Tree : Gtk.TreeView {
return true;
}
+ public override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn column) {
+ EntryWrapper? wrapper = get_wrapper_at_path(path);
+ if (wrapper != null) {
+ Sidebar.SelectableEntry? selectable = wrapper.entry as Sidebar.SelectableEntry;
+ if (selectable != null)
+ entry_activated(selectable);
+ }
+ }
+
public override void cursor_changed() {
Gtk.TreePath? path = get_selected_path();
if (path == null) {
diff --git a/ui/application-main-window.ui b/ui/application-main-window.ui
index f429a5ee..547b063f 100644
--- a/ui/application-main-window.ui
+++ b/ui/application-main-window.ui
@@ -24,113 +24,118 @@
False
vertical
-
-
+
True
False
+ True
+ over
-
-
- True
- True
- 2
+ conversation
-