client: Rework headerbar buttons to fit small screens

- Rework components-conversation-actions.ui
- Merge copy/move popovers
- Populate copy/move popover on the fly

Initial workaround for #1185, #1306, #1444, #1164, #1414
TODO: move to AdwBreakPoints from libadwaita
This commit is contained in:
Cédric Bellegarde 2022-12-09 16:05:35 +01:00
parent 548e6d6895
commit 776b04ec9f
6 changed files with 153 additions and 239 deletions

View file

@ -39,6 +39,12 @@
<description>True if we should display a short preview of each message.</description>
</key>
<key name="move-messages-on-tag" type="b">
<default>false</default>
<summary>Move messages by default</summary>
<description>When tagging a message, move it to destination folder.</description>
</key>
<key name="single-key-shortcuts" type="b">
<default>false</default>
<summary>Use single key shortcuts</summary>

View file

@ -27,7 +27,6 @@ public class Application.MainWindow :
public const string ACTION_SEARCH = "search";
public const string ACTION_SELECT_INBOX = "select-inbox";
public const string ACTION_SHOW_COPY_MENU = "show-copy-menu";
public const string ACTION_SHOW_MOVE_MENU = "show-move-menu";
public const string ACTION_TOGGLE_JUNK = "toggle-conversation-junk";
public const string ACTION_TRASH_CONVERSATION = "trash-conversation";
public const string ACTION_ZOOM = "zoom";
@ -56,7 +55,6 @@ public class Application.MainWindow :
{ ACTION_TRASH_CONVERSATION, on_trash_conversation },
{ ACTION_DELETE_CONVERSATION, on_delete_conversation },
{ ACTION_SHOW_COPY_MENU, on_show_copy_menu },
{ ACTION_SHOW_MOVE_MENU, on_show_move_menu },
{ ACTION_CONVERSATION_UP, on_conversation_up },
{ ACTION_CONVERSATION_DOWN, on_conversation_down },
// Message marking actions
@ -414,6 +412,7 @@ public class Application.MainWindow :
[GtkChild] private unowned Gtk.Box conversation_list_box;
[GtkChild] private unowned Gtk.Revealer conversation_list_actions_revealer;
[GtkChild] private unowned Components.ConversationActions conversation_list_actions;
[GtkChild] private unowned Components.ConversationActions conversation_viewer_actions;
[GtkChild] private unowned Gtk.Box conversation_viewer_box;
[GtkChild] private unowned Gtk.Revealer conversation_viewer_actions_revealer;
@ -423,7 +422,6 @@ public class Application.MainWindow :
[GtkChild] private unowned Components.InfoBarStack info_bars;
private Components.ConversationActions[] folder_conversation_actions = {};
private FolderPopover[] folder_popovers = {};
private Components.InfoBar offline_infobar;
private Components.InfoBar cert_problem_infobar;
@ -478,12 +476,6 @@ public class Application.MainWindow :
activate_action(get_window_action(ACTION_SHOW_COPY_MENU));
}
/** Keybinding signal for showing the move menu. */
[Signal (action=true)]
public virtual signal void show_move_menu() {
activate_action(get_window_action(ACTION_SHOW_MOVE_MENU));
}
/** Keybinding signal for archiving the current selection. */
[Signal (action=true)]
public virtual signal void archive_conversations() {
@ -789,10 +781,6 @@ public class Application.MainWindow :
// selected model.
if (this.selected_folder != null) {
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);
this.selected_folder = null;
@ -862,11 +850,6 @@ public class Application.MainWindow :
}
this.conversation_list_view.set_monitor(this.conversations);
// disable copy/move to the new folder
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();
}
@ -1197,11 +1180,6 @@ public class Application.MainWindow :
}
foreach (var context in to_add) {
this.folder_list.add_folder(context);
if (context.folder.account == this.selected_account) {
foreach (var menu in this.folder_popovers) {
menu.add_folder(context, map);
}
}
context.folder.use_changed.connect(on_use_changed);
}
}
@ -1219,11 +1197,6 @@ public class Application.MainWindow :
}
folder.use_changed.disconnect(on_use_changed);
if (folder.account == this.selected_account) {
foreach (var menu in this.folder_popovers) {
menu.remove_folder(folder);
}
}
this.folder_list.remove_folder(context);
}
}
@ -1415,18 +1388,20 @@ public class Application.MainWindow :
this.conversation_list_actions.set_mark_inverted();
this.conversation_headerbar.full_actions.init(this.application.config);
this.conversation_list_actions.init(this.application.config);
this.conversation_viewer_actions.init(this.application.config);
this.folder_conversation_actions = {
this.conversation_headerbar.full_actions,
this.conversation_list_actions
this.conversation_list_actions,
this.conversation_viewer_actions
};
foreach (var actions in this.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);
foreach (var actions in this.folder_conversation_actions) {
var popover = actions.copy_move_popover;
popover.copy_conversation.connect(on_copy_conversation);
popover.move_conversation.connect(on_move_conversation);
}
}
@ -1590,40 +1565,14 @@ public class Application.MainWindow :
private void select_account(Geary.Account? account) {
if (this.selected_account != account) {
if (this.selected_account != null) {
foreach (var menu in this.folder_popovers) {
menu.clear();
}
}
this.selected_account = account;
this.search_bar.set_account(account);
if (account != null) {
var service_provider = account.information.service_provider;
this.conversation_list_actions.service_provider = service_provider;
this.conversation_headerbar.full_actions.service_provider = service_provider;
this.conversation_headerbar.compact_actions.service_provider = service_provider;
foreach (var menu in this.folder_popovers) {
var folders = account.list_folders();
// Build map between path and display name for
// special directories
var map = new Gee.HashMap<string,string>();
foreach (var folder in folders) {
var context = new Application.FolderContext(folder);
if (folder.used_as == Geary.Folder.SpecialUse.NONE)
continue;
map.set(
folder.path.to_string().substring(1),
context.display_name
);
}
foreach (var folder in folders) {
var context = new Application.FolderContext(folder);
menu.add_folder(context, map);
}
}
this.conversation_list_actions.account = account;
this.conversation_viewer_actions.account = account;
this.conversation_headerbar.full_actions.account = account;
this.conversation_headerbar.compact_actions.account = account;
}
update_command_actions();
@ -1894,18 +1843,10 @@ public class Application.MainWindow :
get_window_action(ACTION_REPLY_ALL_CONVERSATION).set_enabled(reply_sensitive);
get_window_action(ACTION_FORWARD_CONVERSATION).set_enabled(reply_sensitive);
bool move_enabled = (
sensitive && (this.selected_folder is Geary.FolderSupport.Move)
);
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 && (this.selected_folder is Geary.FolderSupport.Copy)
);
get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(move_enabled);
get_window_action(ACTION_SHOW_COPY_MENU).set_enabled(copy_enabled);
foreach (var actions in this.folder_conversation_actions) {
actions.set_copy_sensitive(copy_enabled);
}
@ -1979,10 +1920,6 @@ public class Application.MainWindow :
sensitive &&
(supported_operations.contains(typeof(Geary.FolderSupport.Copy)))
);
get_window_action(ACTION_SHOW_MOVE_MENU).set_enabled(
sensitive &&
(supported_operations.contains(typeof(Geary.FolderSupport.Move)))
);
}
}
@ -2492,17 +2429,6 @@ public class Application.MainWindow :
}
}
private void on_show_move_menu() {
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.conversation_headerbar.shown_actions.show_move_menu();
} else {
error_bell();
}
}
private void on_conversation_up() {
this.conversation_list_view.scroll(Gtk.ScrollType.STEP_UP);
}

View file

@ -18,22 +18,23 @@ public class Components.ConversationActions : Gtk.Box {
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(); }
public FolderPopover copy_move_popover {
get {
unowned var popover = this.copy_message_button.popover as FolderPopover;
return popover;
}
}
public int selected_conversations { get; set; }
public Geary.ServiceProvider service_provider { get; set; }
public Geary.Account account { get; set; }
[GtkChild] private unowned Gtk.Box response_buttons { get; }
[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; }
[GtkChild] private unowned Gtk.Box archive_trash_delete_buttons { get; }
[GtkChild] private unowned Gtk.Box action_buttons { get; }
[GtkChild] private unowned Gtk.Button archive_button;
[GtkChild] private unowned Gtk.Button trash_delete_button;
@ -60,8 +61,6 @@ public class Components.ConversationActions : Gtk.Box {
this.notify["selected-conversations"].connect(() => update_conversation_buttons());
this.notify["service-provider"].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.mark_message_button.toggled.connect((button) => {
if (button.active)
@ -69,21 +68,20 @@ public class Components.ConversationActions : Gtk.Box {
});
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);
this.action_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;
this.action_buttons.hexpand = true;
this.action_buttons.halign = END;
}
}
public void set_move_sensitive(bool is_sensitive) {
this.move_message_button.sensitive = is_sensitive;
}
public void show_move_menu() {
this.move_message_button.clicked();
public void init(Application.Configuration config) {
this.copy_message_button.popover = new FolderPopover(config);
this.bind_property(
"account", this.copy_message_button.popover,
"account", BindingFlags.DEFAULT
);
}
public void set_copy_sensitive(bool is_sensitive) {
@ -121,43 +119,34 @@ public class Components.ConversationActions : Gtk.Box {
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
);
var copy_icon_name = "edit-copy-symbolic";
var move_icon_name = "edit-cut-symbolic";
switch (this.service_provider) {
case Geary.ServiceProvider.GMAIL:
this.copy_message_button.tooltip_text = ngettext(
"Add label to conversation",
"Add label to conversations",
this.selected_conversations
if (this.account != null) {
switch (this.account.information.service_provider) {
case Geary.ServiceProvider.GMAIL:
this.copy_message_button.tooltip_text = ngettext(
"Add label to conversation",
"Add label to conversations",
this.selected_conversations
);
this.copy_message_button.set_image(
new Gtk.Image.from_icon_name(
"tag-symbolic", Gtk.IconSize.BUTTON)
);
copy_icon_name = "tag-symbolic";
move_icon_name = "folder-symbolic";
break;
default:
this.copy_message_button.tooltip_text = ngettext(
"Copy conversation",
"Copy conversations",
this.selected_conversations
);
break;
break;
default:
this.copy_message_button.tooltip_text = ngettext(
"Copy conversation",
"Copy conversations",
this.selected_conversations
);
break;
}
}
this.copy_message_button.set_image(
new Gtk.Image.from_icon_name(copy_icon_name, Gtk.IconSize.BUTTON)
);
this.move_message_button.set_image(
new Gtk.Image.from_icon_name(move_icon_name, Gtk.IconSize.BUTTON)
);
if (this.show_trash_button) {
this.trash_delete_button.action_name = Action.Window.prefix(

View file

@ -9,12 +9,16 @@ public class FolderPopover : Gtk.Popover {
[GtkChild] private unowned Gtk.SearchEntry search_entry;
[GtkChild] private unowned Gtk.ListBox list_box;
[GtkChild] private unowned Gtk.Switch move_switch;
public Geary.Account account {get; set;}
private int filtered_folder_count = 0;
public signal void folder_selected(Geary.Folder folder);
public signal void copy_conversation(Geary.Folder folder);
public signal void move_conversation(Geary.Folder folder);
public FolderPopover() {
public FolderPopover(Application.Configuration config) {
list_box.set_filter_func(row_filter);
list_box.set_sort_func(row_sort);
this.show.connect(() => search_entry.grab_focus());
@ -22,17 +26,14 @@ public class FolderPopover : Gtk.Popover {
search_entry.set_text("");
invalidate_filter();
});
config.bind("move-messages-on-tag", this.move_switch, "active");
}
public bool has_folder(Geary.Folder folder) {
return get_row_with_folder(folder) != null;
}
public void add_folder(Application.FolderContext context, Gee.HashMap<string,string> map) {
private void add_folder(Application.FolderContext context, Gee.HashMap<string,string> map) {
Geary.Folder folder = context.folder;
// don't allow multiples and don't allow folders that can't be opened (that means they
// support almost no operations and have no content)
if (has_folder(folder) || folder.properties.is_openable.is_impossible())
if (folder.properties.is_openable.is_impossible())
return;
// also don't allow local-only or virtual folders, which also have a limited set of
@ -66,28 +67,29 @@ public class FolderPopover : Gtk.Popover {
list_box.invalidate_sort();
}
public void enable_disable_folder(Geary.Folder folder, bool visible) {
Gtk.ListBoxRow row = get_row_with_folder(folder);
if (row != null)
row.visible = visible;
[GtkCallback]
private void on_map(Gtk.Widget widget) {
var folders = this.account.list_folders();
// Build map between path and display name for
// special directories
var map = new Gee.HashMap<string,string>();
foreach (var folder in folders) {
var context = new Application.FolderContext(folder);
if (folder.used_as == Geary.Folder.SpecialUse.NONE)
continue;
map.set(
folder.path.to_string().substring(1),
context.display_name
);
}
foreach (var folder in folders) {
var context = new Application.FolderContext(folder);
this.add_folder(context, map);
}
}
public void remove_folder(Geary.Folder folder) {
Gtk.ListBoxRow row = get_row_with_folder(folder);
if (row != null)
list_box.remove(row);
}
public Gtk.ListBoxRow? get_row_with_folder(Geary.Folder folder) {
Gtk.ListBoxRow result = null;
list_box.foreach((row) => {
if (row.get_data<Geary.Folder>("folder") == folder)
result = row as Gtk.ListBoxRow;
});
return result;
}
public void clear() {
[GtkCallback]
private void on_unmap(Gtk.Widget widget) {
list_box.foreach((row) => list_box.remove(row));
}
@ -95,7 +97,11 @@ public class FolderPopover : Gtk.Popover {
private void on_row_activated(Gtk.ListBoxRow? row) {
if (row != null) {
Geary.Folder folder = row.get_data<Geary.Folder>("folder");
folder_selected(folder);
if (this.move_switch.active) {
move_conversation(folder);
} else {
copy_conversation(folder);
}
}
this.hide();

View file

@ -79,7 +79,11 @@
</object>
</child>
<child>
<object class="GtkBox" id="mark_copy_move_buttons">
<object class="GtkImage" id="archive_image">
<property name="visible">True</property>
<property name="icon_name">mail-archive-symbolic</property>
</object>
<object class="GtkBox" id="action_buttons">
<property name="visible">True</property>
<child>
<object class="GtkMenuButton" id="copy_message_button">
@ -89,25 +93,6 @@
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="copy_message_image">
<property name="visible">True</property>
<property name="icon_name">tag-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="move_message_button">
<property name="visible">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="move_message_image">
<property name="visible">True</property>
<property name="icon_name">folder-symbolic</property>
</object>
@ -116,7 +101,40 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="archive_button">
<property name="visible">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="action_name">win.archive-conversation</property>
<property name="image">archive_image</property>
<property name="use_underline">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<object class="GtkButton" id="trash_delete_button">
<property name="visible">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="action_name">win.trash-conversation</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="trash_delete_image">
<property name="visible">True</property>
<property name="icon_name">user-trash-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
@ -138,57 +156,6 @@
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<style>
<class name="raised"/>
<class name="linked"/>
</style>
</object>
</child>
<child>
<object class="GtkImage" id="archive_image">
<property name="visible">True</property>
<property name="icon_name">mail-archive-symbolic</property>
</object>
<object class="GtkBox" id="archive_trash_delete_buttons">
<property name="visible">True</property>
<child>
<object class="GtkButton" id="archive_button">
<property name="label" translatable="yes">_Archive</property>
<property name="visible">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="action_name">win.archive-conversation</property>
<property name="image">archive_image</property>
<property name="use_underline">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="trash_delete_button">
<property name="visible">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property>
<property name="action_name">win.trash-conversation</property>
<property name="always_show_image">True</property>
<child>
<object class="GtkImage" id="trash_delete_image">
<property name="visible">True</property>
<property name="icon_name">user-trash-symbolic</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<style>

View file

@ -3,6 +3,8 @@
<interface>
<requires lib="gtk+" version="3.14"/>
<template class="FolderPopover" parent="GtkPopover">
<signal name="map" handler="on_map" swapped="no"/>
<signal name="unmap" handler="on_unmap" swapped="no"/>
<child>
<object class="GtkBox" id="container">
<property name="visible">True</property>
@ -52,6 +54,24 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="halign">start</property>
<property name="label">Move message to folder</property>
</object>
</child>
<child>
<object class="GtkSwitch" id="move_switch">
<property name="visible">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>