components: Rework Folder Popover

- Do not use tags for service providers not using this metaphor
- Do not show folders with a dedicated button: Trash, Archive, ...
- Do not show folders as an IMAP path but as a human readable path (>INBOX>Folder vs Boîte de reception/Folder)
This commit is contained in:
Cédric Bellegarde 2022-07-18 13:54:39 +02:00
parent 8348f2ef19
commit 864a3135e3
12 changed files with 230 additions and 36 deletions

View file

@ -46,6 +46,17 @@ public class Application.FolderContext : Geary.BaseObject,
return this.folder.path.compare_to(other.folder.path); return this.folder.path.compare_to(other.folder.path);
} }
private string get_default_icon_name() {
var service_provider = this.folder.account.information.service_provider;
switch (service_provider) {
case Geary.ServiceProvider.GMAIL:
return "tag-symbolic";
default:
return "folder-symbolic";
}
}
private void update() { private void update() {
this.display_name = Util.I18n.to_folder_display_name(this.folder); this.display_name = Util.I18n.to_folder_display_name(this.folder);
@ -88,7 +99,7 @@ public class Application.FolderContext : Geary.BaseObject,
break; break;
default: default:
this.icon_name = "tag-symbolic"; this.icon_name = get_default_icon_name();
break; break;
} }

View file

@ -1179,11 +1179,23 @@ public class Application.MainWindow :
/** Adds a folder to the window. */ /** Adds a folder to the window. */
private void add_folders(Gee.Collection<FolderContext> to_add) { private void add_folders(Gee.Collection<FolderContext> to_add) {
// Build map between path and display name for
// special directories
var map = new Gee.HashMap<string,string>();
foreach (var context in to_add) {
var folder = context.folder;
if (folder.used_as == Geary.Folder.SpecialUse.NONE)
continue;
map.set(
folder.path.to_string().substring(1),
context.display_name
);
}
foreach (var context in to_add) { foreach (var context in to_add) {
this.folder_list.add_folder(context); this.folder_list.add_folder(context);
if (context.folder.account == this.selected_account) { if (context.folder.account == this.selected_account) {
foreach (var menu in this.folder_popovers) { foreach (var menu in this.folder_popovers) {
menu.add_folder(context.folder); menu.add_folder(context, map);
} }
} }
context.folder.use_changed.connect(on_use_changed); context.folder.use_changed.connect(on_use_changed);
@ -1588,9 +1600,28 @@ public class Application.MainWindow :
this.search_bar.set_account(account); this.search_bar.set_account(account);
if (account != null) { if (account != null) {
var service_provider = account.information.service_provider;
this.conversation_list_actions.service_provider = service_provider;
this.main_toolbar.full_actions.service_provider = service_provider;
this.main_toolbar.compact_actions.service_provider = service_provider;
foreach (var menu in this.folder_popovers) { foreach (var menu in this.folder_popovers) {
foreach (var folder in account.list_folders()) { var folders = account.list_folders();
menu.add_folder(folder); // 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);
} }
} }
} }

View file

@ -24,6 +24,8 @@ public class Components.ConversationActions : Gtk.Box {
public int selected_conversations { get; set; } public int selected_conversations { get; set; }
public Geary.ServiceProvider service_provider { get; set; }
[GtkChild] private unowned Gtk.Box response_buttons { get; } [GtkChild] private unowned Gtk.Box response_buttons { get; }
[GtkChild] private unowned Gtk.Box mark_copy_move_buttons { get; } [GtkChild] private unowned Gtk.Box mark_copy_move_buttons { get; }
@ -56,6 +58,7 @@ public class Components.ConversationActions : Gtk.Box {
); );
this.notify["selected-conversations"].connect(() => update_conversation_buttons()); 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.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu);
this.copy_message_button.popover = copy_folder_menu; this.copy_message_button.popover = copy_folder_menu;
this.move_message_button.popover = move_folder_menu; this.move_message_button.popover = move_folder_menu;
@ -110,11 +113,7 @@ public class Components.ConversationActions : Gtk.Box {
"Mark conversations", "Mark conversations",
this.selected_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( this.move_message_button.tooltip_text = ngettext(
"Move conversation", "Move conversation",
"Move conversations", "Move conversations",
@ -126,6 +125,33 @@ public class Components.ConversationActions : Gtk.Box {
this.selected_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
);
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;
}
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) { if (this.show_trash_button) {
this.trash_delete_button.action_name = Action.Window.prefix( this.trash_delete_button.action_name = Action.Window.prefix(
Application.MainWindow.ACTION_TRASH_CONVERSATION Application.MainWindow.ACTION_TRASH_CONVERSATION

View file

@ -0,0 +1,32 @@
/* Copyright 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
*
* 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/folder-popover-row.ui")]
public class FolderPopoverRow : Gtk.ListBoxRow {
[GtkChild] private unowned Gtk.Image image;
[GtkChild] private unowned Gtk.Label label;
public FolderPopoverRow(Application.FolderContext context, Gee.HashMap<string,string> map) {
string[] as_array = context.folder.path.as_array();
if (map.has_key(as_array[0])) {
as_array[0] = map[as_array[0]];
}
var i = 0;
foreach (string name in as_array) {
as_array[i] = GLib.Markup.escape_text(name);
i += 1;
}
this.set_data("folder", context.folder);
this.image.icon_name = context.icon_name;
this.label.set_markup(string.joinv("<span alpha='30%'> / </span>", as_array));
this.label.query_tooltip.connect(Util.Gtk.query_tooltip_label);
}
}

View file

@ -28,7 +28,8 @@ public class FolderPopover : Gtk.Popover {
return get_row_with_folder(folder) != null; return get_row_with_folder(folder) != null;
} }
public void add_folder(Geary.Folder folder) { public 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 // 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) // support almost no operations and have no content)
if (has_folder(folder) || folder.properties.is_openable.is_impossible()) if (has_folder(folder) || folder.properties.is_openable.is_impossible())
@ -39,14 +40,36 @@ public class FolderPopover : Gtk.Popover {
if (folder.properties.is_local_only || folder.properties.is_virtual) if (folder.properties.is_local_only || folder.properties.is_virtual)
return; return;
list_box.add(build_row(folder)); // Moving mails to Drafts folder not supported
switch (folder.account.information.service_provider) {
case Geary.ServiceProvider.GMAIL:
if (folder.used_as == Geary.Folder.SpecialUse.DRAFTS)
return;
break;
default:
break;
}
// Ignore special directories already having a dedicated button
switch (folder.used_as) {
case Geary.Folder.SpecialUse.ARCHIVE:
case Geary.Folder.SpecialUse.TRASH:
case Geary.Folder.SpecialUse.JUNK:
return;
default:
break;
}
var row = new FolderPopoverRow(context, map);
row.show();
list_box.add(row);
list_box.invalidate_sort(); list_box.invalidate_sort();
} }
public void enable_disable_folder(Geary.Folder folder, bool sensitive) { public void enable_disable_folder(Geary.Folder folder, bool visible) {
Gtk.ListBoxRow row = get_row_with_folder(folder); Gtk.ListBoxRow row = get_row_with_folder(folder);
if (row != null) if (row != null)
row.sensitive = sensitive; row.visible = visible;
} }
public void remove_folder(Geary.Folder folder) { public void remove_folder(Geary.Folder folder) {
@ -68,20 +91,6 @@ public class FolderPopover : Gtk.Popover {
list_box.foreach((row) => list_box.remove(row)); list_box.foreach((row) => list_box.remove(row));
} }
private Gtk.ListBoxRow build_row(Geary.Folder folder) {
Gtk.ListBoxRow row = new Gtk.ListBoxRow();
row.get_style_context().add_class("geary-folder-popover-list-row");
row.set_data("folder", folder);
Gtk.Label label = new Gtk.Label(folder.path.to_string());
label.set_halign(Gtk.Align.START);
row.add(label);
row.show_all();
return row;
}
[GtkCallback] [GtkCallback]
private void on_row_activated(Gtk.ListBoxRow? row) { private void on_row_activated(Gtk.ListBoxRow? row) {
if (row != null) { if (row != null) {
@ -118,8 +127,8 @@ public class FolderPopover : Gtk.Popover {
} }
private bool row_filter(Gtk.ListBoxRow row) { private bool row_filter(Gtk.ListBoxRow row) {
Gtk.Label label = row.get_child() as Gtk.Label; Geary.Folder folder = row.get_data<Geary.Folder>("folder");
if (label.label.down().contains(search_entry.text.down())) { if (folder.path.to_string().down().contains(search_entry.text.down())) {
filtered_folder_count++; filtered_folder_count++;
return true; return true;
} }
@ -129,6 +138,13 @@ public class FolderPopover : Gtk.Popover {
private int row_sort(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) { private int row_sort(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
Geary.Folder folder1 = row1.get_data<Geary.Folder>("folder"); Geary.Folder folder1 = row1.get_data<Geary.Folder>("folder");
Geary.Folder folder2 = row2.get_data<Geary.Folder>("folder"); Geary.Folder folder2 = row2.get_data<Geary.Folder>("folder");
return folder1.path.compare_to(folder2.path); if (folder1.used_as != Geary.Folder.SpecialUse.NONE &&
folder2.used_as == Geary.Folder.SpecialUse.NONE)
return -1;
else if (folder1.used_as == Geary.Folder.SpecialUse.NONE &&
folder2.used_as != Geary.Folder.SpecialUse.NONE)
return 1;
else
return folder1.path.compare_to(folder2.path);
} }
} }

View file

@ -43,7 +43,18 @@ public class FolderList.AccountBranch : Sidebar.Branch {
// Translators: The name of the folder group containing // Translators: The name of the folder group containing
// folders created by people (as opposed to special-use // folders created by people (as opposed to special-use
// folders) // folders)
user_folder_group = new SpecialGrouping(2, _("Labels"), "tag-symbolic"); string name, icon_name;
switch (account.information.service_provider) {
case Geary.ServiceProvider.GMAIL:
name = _("Labels");
icon_name = "tag-symbolic";
break;
default:
name = _("Folders");
icon_name = "folder-symbolic";
break;
}
user_folder_group = new SpecialGrouping(2, name, icon_name);
folder_entries = new Gee.HashMap<Geary.FolderPath, FolderEntry>(); folder_entries = new Gee.HashMap<Geary.FolderPath, FolderEntry>();
this.display_name = account.information.display_name; this.display_name = account.information.display_name;

View file

@ -66,6 +66,7 @@ client_vala_sources = files(
'components/components-web-view.vala', 'components/components-web-view.vala',
'components/count-badge.vala', 'components/count-badge.vala',
'components/folder-popover.vala', 'components/folder-popover.vala',
'components/folder-popover-row.vala',
'components/icon-factory.vala', 'components/icon-factory.vala',
'components/monitored-progress-bar.vala', 'components/monitored-progress-bar.vala',
'components/monitored-spinner.vala', 'components/monitored-spinner.vala',

View file

@ -227,4 +227,13 @@ namespace Util.Gtk {
}; };
} }
/* Connect this to Gtk.Widget.query_tooltip signal, will only show tooltip if label ellipsized */
public bool query_tooltip_label(global::Gtk.Widget widget, int x, int y, bool keyboard, global::Gtk.Tooltip tooltip) {
global::Gtk.Label label = widget as global::Gtk.Label;
if (label.get_layout().is_ellipsized()) {
tooltip.set_markup(label.label);
return true;
}
return false;
}
} }

46
ui/folder-popover-row.ui Normal file
View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.14"/>
<template class="FolderPopoverRow" parent="GtkListBoxRow">
<child>
<object class="GtkBox" id="container">
<property name="visible">True</property>
<property name="margin_start">6</property>
<property name="margin_end">6</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="orientation">horizontal</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="image">
<property name="visible">True</property>
<property name="halign">start</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="halign">start</property>
<property name="hexpand">True</property>
<property name="has_tooltip">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<style>
<class name="geary-folder-popover-list-row"/>
</style>
</template>
</interface>

View file

@ -29,14 +29,15 @@
</child> </child>
<child> <child>
<object class="GtkScrolledWindow" id="scrolled"> <object class="GtkScrolledWindow" id="scrolled">
<property name="min_content_width">200</property> <property name="width-request">300</property>
<property name="min_content_height">320</property> <property name="min_content_height">320</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="shadow_type">in</property> <property name="shadow_type">none</property>
<property name="hscrollbar_policy">never</property> <property name="hscrollbar_policy">never</property>
<child> <child>
<object class="GtkListBox" id="list_box"> <object class="GtkListBox" id="list_box">
<property name="visible">True</property> <property name="visible">True</property>
<property name="valign">start</property>
<property name="activate_on_single_click">True</property> <property name="activate_on_single_click">True</property>
<signal name="row_activated" handler="on_row_activated" swapped="no"/> <signal name="row_activated" handler="on_row_activated" swapped="no"/>
<style> <style>

View file

@ -68,12 +68,21 @@ revealer components-conversation-actions {
/* FolderPopover */ /* FolderPopover */
.geary-folder-popover-list {
border: 1px solid @borders;
border-radius: 3px;
padding: 0px;
}
row.geary-folder-popover-list-row { row.geary-folder-popover-list-row {
padding: 6px; padding: 6px;
border-color: @borders; border-bottom: 1px solid @borders;
border-style: groove;
border-bottom-width: 1px;
} }
row.geary-folder-popover-list-row:last-child {
border-bottom: none;
}
row.geary-folder-popover-list-row > label { row.geary-folder-popover-list-row > label {
color: @theme_text_color; color: @theme_text_color;
} }

View file

@ -40,6 +40,7 @@
<file compressed="true">conversation-web-view.js</file> <file compressed="true">conversation-web-view.js</file>
<file compressed="true" preprocess="xml-stripblanks">find_bar.glade</file> <file compressed="true" preprocess="xml-stripblanks">find_bar.glade</file>
<file compressed="true" preprocess="xml-stripblanks">folder-popover.ui</file> <file compressed="true" preprocess="xml-stripblanks">folder-popover.ui</file>
<file compressed="true" preprocess="xml-stripblanks">folder-popover-row.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" preprocess="xml-stripblanks">password-dialog.glade</file> <file compressed="true" preprocess="xml-stripblanks">password-dialog.glade</file>
<file compressed="true" preprocess="xml-stripblanks">problem-details-dialog.ui</file> <file compressed="true" preprocess="xml-stripblanks">problem-details-dialog.ui</file>