Fix toolbar problems with Adwaita: Closes #5567

Complete refactoring of dropdown menu toolbar buttons.  This uses
a better approach toward using customized widgets for the toggle
buttons and a more centralized approach toward maintaining the
dropdown menu.

In additional, Geary.FolderPath is now a Comparable, and should be
used as such in the future.

It's believed this patch will also fix ticket #5831.
This commit is contained in:
Jim Nelson 2012-09-19 17:23:02 -07:00
parent a969b44ad2
commit 403ae6dc69
9 changed files with 291 additions and 165 deletions

View file

@ -237,8 +237,8 @@ client/util/util-date.vala
client/util/util-email.vala
client/util/util-files.vala
client/util/util-gravatar.vala
client/util/util-gtk.vala
client/util/util-keyring.vala
client/util/util-menu.vala
client/util/util-webkit.vala
client/views/formatted-conversation-data.vala

View file

@ -724,7 +724,7 @@ public class ComposerWindow : Gtk.Window {
return;
font_menu.show_all();
font_menu.popup(null, null, menu_popup_relative, 0, 0);
font_menu.popup(null, null, GtkUtil.menu_popup_relative, 0, 0);
}
private void on_deactivate_font_menu() {
@ -736,7 +736,7 @@ public class ComposerWindow : Gtk.Window {
return;
font_size_menu.show_all();
font_size_menu.popup(null, null, menu_popup_relative, 0, 0);
font_size_menu.popup(null, null, GtkUtil.menu_popup_relative, 0, 0);
}
private void on_deactivate_font_size_menu() {

View file

@ -194,13 +194,11 @@ public class GearyController {
mark_unstarred.label = _("U_nstar");
entries += mark_unstarred;
Gtk.ActionEntry copy_menu = { ACTION_COPY_MENU, null, TRANSLATABLE, "L", null,
on_show_copy_menu };
Gtk.ActionEntry copy_menu = { ACTION_COPY_MENU, null, TRANSLATABLE, "L", null, null };
copy_menu.label = _("_Label");
entries += copy_menu;
Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", null,
on_show_move_menu };
Gtk.ActionEntry move_menu = { ACTION_MOVE_MENU, null, TRANSLATABLE, "M", null, null };
move_menu.label = _("_Move");
entries += move_menu;
@ -891,15 +889,7 @@ public class GearyController {
private void on_mark_complete() {
set_busy(false);
}
private void on_show_copy_menu() {
main_window.main_toolbar.copy_folder_menu.show();
}
private void on_show_move_menu() {
main_window.main_toolbar.move_folder_menu.show();
}
private void on_copy_conversation(Geary.Folder destination) {
// Nothing to do if nothing selected.
if (selected_conversations == null || selected_conversations.size == 0)
@ -1227,7 +1217,7 @@ public class GearyController {
}
// Disables all single-message buttons and enables all multi-message buttons.
public void enable_multiple_message_buttons(){
public void enable_multiple_message_buttons() {
// Single message only buttons.
GearyApplication.instance.actions.get_action(ACTION_REPLY_TO_MESSAGE).sensitive = false;
GearyApplication.instance.actions.get_action(ACTION_REPLY_ALL_MESSAGE).sensitive = false;

View file

@ -4,33 +4,20 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class FolderMenu {
private Gtk.Menu? menu = null;
private Gtk.Menu proxy_menu;
private Gtk.ToggleToolButton button;
public class FolderMenu : GtkUtil.ToggleToolbarDropdown {
private Gee.List<Geary.Folder> folder_list = new Gee.ArrayList<Geary.Folder>();
public signal void folder_selected(Geary.Folder folder);
public FolderMenu(Gtk.ToggleToolButton button, Icon? icon, string? label) {
this.button = button;
public FolderMenu(Icon icon, Gtk.IconSize icon_size, Gtk.Menu? supplied_menu,
Gtk.Menu? supplied_proxy_menu) {
base (icon, icon_size, supplied_menu, supplied_proxy_menu);
// TODO Add fancy filter option.
// TODO Make the menu items checkboxes instead of buttons.
// TODO Merge the move/copy menus and just have a move/copy buttons at bottom of this menu.
menu = new Gtk.Menu();
attach_menu(button, menu);
menu.deactivate.connect(on_menu_deactivate);
menu.show_all();
proxy_menu = new Gtk.Menu();
add_proxy_menu(button, label, proxy_menu);
// only use label for proxy, not the toolbar
make_menu_dropdown_button(button, icon, null);
}
public void add_folder(Geary.Folder folder) {
folder_list.add(folder);
folder_list.sort((CompareFunc) folder_sort);
@ -56,39 +43,17 @@ public class FolderMenu {
proxy_menu.show_all();
}
public void show() {
if (!button.active)
return;
menu.popup(null, null, menu_popup_relative, 0, 0);
menu.select_first(true);
}
private Gtk.MenuItem build_menu_item(Geary.Folder folder) {
Gtk.MenuItem menu_item = new Gtk.MenuItem.with_label(folder.get_path().to_string());
menu_item.activate.connect(() => {
on_menu_item_activated(folder);
folder_selected(folder);
});
return menu_item;
}
private void attach_menu(Gtk.ToggleToolButton button, Gtk.Menu menu) {
menu.attach_to_widget(button, null);
menu.deactivate.connect(() => button.active = false);
button.clicked.connect(show);
}
private void on_menu_deactivate() {
button.active = false;
}
private void on_menu_item_activated(Geary.Folder folder) {
folder_selected(folder);
}
private static int folder_sort(Geary.Folder a, Geary.Folder b) {
return a.get_path().to_string().casefold().collate(b.get_path().to_string().casefold());
return a.get_path().compare(b.get_path());
}
}

View file

@ -53,18 +53,23 @@ public class IconFactory {
unstarred = load("non-starred-grey", STAR_ICON_SIZE);
}
public Icon get_custom_icon(string name, Gtk.IconSize size) {
int pixels;
switch (size) {
private int icon_size_to_pixels(Gtk.IconSize icon_size) {
switch (icon_size) {
case ICON_SIDEBAR:
pixels = 16;
break;
return 16;
case ICON_TOOLBAR:
default:
pixels = 24;
break;
return 24;
}
}
public Icon get_theme_icon(string name) {
return new ThemedIcon(name);
}
public Icon get_custom_icon(string name, Gtk.IconSize size) {
int pixels = icon_size_to_pixels(size);
return new FileIcon(icons_dir.get_child("%dx%d".printf(pixels, pixels)).get_child("%s.svg".printf(name)));
}

View file

@ -9,7 +9,10 @@ public class MainToolbar : Gtk.Box {
private Gtk.Toolbar toolbar;
public FolderMenu copy_folder_menu { get; private set; }
public FolderMenu move_folder_menu { get; private set; }
private GtkUtil.ToggleToolbarDropdown mark_menu_dropdown;
private GtkUtil.ToggleToolbarDropdown app_menu_dropdown;
public MainToolbar() {
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
@ -24,58 +27,52 @@ public class MainToolbar : Gtk.Box {
set_toolbutton_action(builder, GearyController.ACTION_DELETE_MESSAGE);
// Setup the folder menus (move/copy).
Gtk.ToggleToolButton copy_menu_button = set_toolbutton_action(builder,
Gtk.ToggleToolButton copy_toggle_button = set_toolbutton_action(builder,
GearyController.ACTION_COPY_MENU) as Gtk.ToggleToolButton;
copy_folder_menu = new FolderMenu(copy_menu_button,
IconFactory.instance.get_custom_icon("tag-new", IconFactory.ICON_TOOLBAR), _("Label as"));
Gtk.ToggleToolButton move_menu_button = set_toolbutton_action(builder,
copy_folder_menu = new FolderMenu(
IconFactory.instance.get_custom_icon("tag-new", IconFactory.ICON_TOOLBAR),
Gtk.IconSize.LARGE_TOOLBAR, null, null);
copy_folder_menu.attach(copy_toggle_button);
Gtk.ToggleToolButton move_toggle_button = set_toolbutton_action(builder,
GearyController.ACTION_MOVE_MENU) as Gtk.ToggleToolButton;
move_folder_menu = new FolderMenu(move_menu_button,
IconFactory.instance.get_custom_icon("mail-move", IconFactory.ICON_TOOLBAR), _("Move to"));
move_folder_menu = new FolderMenu(
IconFactory.instance.get_custom_icon("mail-move", IconFactory.ICON_TOOLBAR),
Gtk.IconSize.LARGE_TOOLBAR, null, null);
move_folder_menu.attach(move_toggle_button);
// Assemble mark menu button.
GearyApplication.instance.load_ui_file("toolbar_mark_menu.ui");
Gtk.Menu mark_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu")
as Gtk.Menu;
Gtk.ToggleToolButton mark_menu_button = set_toolbutton_action(builder,
GearyController.ACTION_MARK_AS_MENU) as Gtk.ToggleToolButton;
attach_menu(mark_menu, mark_menu_button);
make_menu_dropdown_button(mark_menu_button,
IconFactory.instance.get_custom_icon("edit-mark", IconFactory.ICON_TOOLBAR), null);
Gtk.Menu mark_proxy_menu = (Gtk.Menu) GearyApplication.instance.ui_manager
.get_widget("/ui/ToolbarMarkMenuProxy");
add_proxy_menu(mark_menu_button, _("Mark"), mark_proxy_menu);
Gtk.ToggleToolButton mark_menu_button = set_toolbutton_action(builder,
GearyController.ACTION_MARK_AS_MENU) as Gtk.ToggleToolButton;
mark_menu_dropdown = new GtkUtil.ToggleToolbarDropdown(
IconFactory.instance.get_custom_icon("edit-mark", IconFactory.ICON_TOOLBAR),
Gtk.IconSize.LARGE_TOOLBAR, mark_menu, mark_proxy_menu);
mark_menu_dropdown.attach(mark_menu_button);
// Setup the application menu.
GearyApplication.instance.load_ui_file("toolbar_menu.ui");
Gtk.Menu menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu") as Gtk.Menu;
Gtk.ToggleToolButton application_menu_button = (Gtk.ToggleToolButton) builder.get_object("menu_button");
attach_menu(menu, application_menu_button);
Gtk.Menu application_proxy_menu = (Gtk.Menu) GearyApplication.instance.ui_manager
.get_widget("/ui/ToolbarMenuProxy");
add_proxy_menu(application_menu_button, application_menu_button.label, application_proxy_menu);
Gtk.Menu application_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu")
as Gtk.Menu;
Gtk.Menu application_proxy_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenuProxy")
as Gtk.Menu;
Gtk.ToggleToolButton app_menu_button = (Gtk.ToggleToolButton) builder.get_object("menu_button");
app_menu_dropdown = new GtkUtil.ToggleToolbarDropdown(
IconFactory.instance.get_theme_icon("application-menu"), Gtk.IconSize.LARGE_TOOLBAR,
application_menu, application_proxy_menu);
app_menu_dropdown.show_arrow = false;
app_menu_dropdown.attach(app_menu_button);
toolbar.get_style_context().add_class("primary-toolbar");
add(toolbar);
}
private void attach_menu(Gtk.Menu menu, Gtk.ToggleToolButton button) {
menu.attach_to_widget(button, null);
menu.deactivate.connect(() => {
button.active = false;
});
button.clicked.connect(() => {
// Prevent loops.
if (!button.active) {
return;
}
menu.popup(null, null, menu_popup_relative, 0, 0);
});
}
private Gtk.ToolButton set_toolbutton_action(Gtk.Builder builder, string action) {
Gtk.ToolButton button = builder.get_object(action) as Gtk.ToolButton;
button.set_related_action(GearyApplication.instance.actions.get_action(action));

View file

@ -0,0 +1,196 @@
/* Copyright 2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
namespace GtkUtil {
// The reason GtkUtil.ToggleToolbarDropdown exists (rather than using Gtk.MenuToolButton) is that
// the latter creates two separate buttons, one for the icon (which activates the "default" action)
// and one for the arrow (which presents the dropdown menu). We need a single button that shows
// the dropdown menu only, hence this version.
//
// In order to use this, create a Gtk.ToggleToolButton and call attach().
//
// TODO: An better solution would be for this to subclass Gtk.ToggleToolButton and register class
// with Gtk.Builder and Glade.
//
// TODO: Would be better to get the icon from the ToggleToolbarButton (could do this even without
// above improvement), but unlike the label, that's not so straightforward due to the number of
// ways of representing an icon in GTK.
public class ToggleToolbarDropdown : Object {
public const int DEFAULT_PADDING = 2;
public bool show_arrow { get; set; default = true; }
public Gtk.Menu menu { get; private set; }
public Gtk.Menu proxy_menu { get; private set; }
private int padding;
private Gtk.Image icon;
private Gtk.Label label = new Gtk.Label(null);
private Gtk.Arrow icon_arrow = new Gtk.Arrow(Gtk.ArrowType.DOWN, Gtk.ShadowType.NONE);
private Gtk.Box icon_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
private Gtk.Arrow label_arrow = new Gtk.Arrow(Gtk.ArrowType.DOWN, Gtk.ShadowType.NONE);
private Gtk.Box label_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
private Gtk.ToggleToolButton? owner = null;
public ToggleToolbarDropdown(Icon icon, Gtk.IconSize icon_size, Gtk.Menu? supplied_menu,
Gtk.Menu? supplied_proxy_menu, int padding = DEFAULT_PADDING) {
this.padding = padding;
this.icon = new Gtk.Image.from_gicon(icon, icon_size);
menu = supplied_menu ?? new Gtk.Menu();
proxy_menu = supplied_proxy_menu ?? new Gtk.Menu();
// icon widget
icon_box.pack_start(this.icon, true, true, padding);
icon_box.pack_end(icon_arrow, true, true, padding);
icon_box.no_show_all = true;
// label widget
label_box.pack_start(this.label, true, true, padding);
label_box.pack_end(label_arrow, true, true, padding);
label_box.no_show_all = true;
this.icon.visible = true;
this.icon_arrow.visible = show_arrow;
icon_box.visible = true;
this.label.visible = true;
this.label_arrow.visible = show_arrow;
label_box.visible = true;
notify["show-arrow"].connect(on_refresh_now);
}
~ToggleToolbarDropdown() {
detach();
}
public void attach(Gtk.ToggleToolButton owner) {
if (this.owner != null) {
debug("ToggleToolbarDropdown already attached");
return;
}
this.owner = owner;
owner.set_icon_widget(icon_box);
owner.set_label_widget(label_box);
menu.attach_to_widget(owner, null);
menu.deactivate.connect(on_menu_deactivated);
add_proxy_menu(owner, owner.label, proxy_menu);
owner.clicked.connect(on_clicked);
owner.notify["label"].connect(on_refresh_now);
owner.notify["active"].connect(on_refresh_now);
owner.notify["is-important"].connect(on_refresh_now);
owner.notify["sensitive"].connect(on_refresh_now);
owner.toolbar_reconfigured.connect(on_refresh_now);
on_refresh_now();
}
public void detach() {
if (owner == null)
return;
owner.clicked.disconnect(on_clicked);
owner.notify["label"].disconnect(on_refresh_now);
owner.notify["active"].disconnect(on_refresh_now);
owner.notify["is-important"].disconnect(on_refresh_now);
owner.notify["sensitive"].disconnect(on_refresh_now);
owner.toolbar_reconfigured.disconnect(on_refresh_now);
this.owner = null;
}
private void on_menu_deactivated() {
if (owner != null)
owner.active = false;
}
private void on_clicked() {
if (owner != null && owner.active)
menu.popup(null, null, menu_popup_relative, 0, 0);
}
private void on_refresh_now() {
if (owner == null)
return;
label.set_label(owner.label);
icon.sensitive = owner.sensitive;
icon_arrow.sensitive = owner.sensitive;
label.sensitive = owner.sensitive;
label_arrow.sensitive = owner.sensitive;
switch (owner.get_toolbar_style()) {
case Gtk.ToolbarStyle.BOTH:
icon.visible = true;
icon_arrow.visible = show_arrow;
label.visible = true;
label_arrow.visible = false;
break;
case Gtk.ToolbarStyle.ICONS:
icon.visible = true;
icon_arrow.visible = show_arrow;
label.visible = false;
label.visible = false;
break;
case Gtk.ToolbarStyle.TEXT:
icon.visible = false;
icon_arrow.visible = false;
label.visible = true;
label_arrow.visible = show_arrow;
break;
case Gtk.ToolbarStyle.BOTH_HORIZ:
default:
icon.visible = true;
icon_arrow.visible = !owner.is_important && show_arrow;
label.visible = owner.is_important;
label_arrow.visible = owner.is_important && show_arrow;
break;
}
}
}
// Use this MenuPositionFunc to position a popup menu relative to a widget
// with Gtk.Menu.popup().
//
// You *must* attach the button widget with Gtk.Menu.attach_to_widget() before
// this function can be used.
public void menu_popup_relative(Gtk.Menu menu, out int x, out int y, out bool push_in) {
menu.realize();
int rx, ry;
menu.get_attach_widget().get_window().get_origin(out rx, out ry);
Gtk.Allocation menu_button_allocation;
menu.get_attach_widget().get_allocation(out menu_button_allocation);
x = rx + menu_button_allocation.x;
y = ry + menu_button_allocation.y + menu_button_allocation.height;
push_in = false;
}
public void add_proxy_menu(Gtk.ToolItem tool_item, string label, Gtk.Menu proxy_menu) {
Gtk.MenuItem proxy_menu_item = new Gtk.MenuItem.with_label(label);
proxy_menu_item.submenu = proxy_menu;
tool_item.create_menu_proxy.connect((sender) => {
sender.set_proxy_menu_item("proxy", proxy_menu_item);
return true;
});
}
}

View file

@ -1,60 +0,0 @@
/* Copyright 2011-2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Use this MenuPositionFunc to position a popup menu relative to a widget
// with Gtk.Menu.popup().
//
// You *must* attach the button widget with Gtk.Menu.attach_to_widget() before
// this function can be used.
public void menu_popup_relative(Gtk.Menu menu, out int x, out int y, out bool push_in) {
menu.realize();
int rx, ry;
menu.get_attach_widget().get_window().get_origin(out rx, out ry);
Gtk.Allocation menu_button_allocation;
menu.get_attach_widget().get_allocation(out menu_button_allocation);
x = rx + menu_button_allocation.x;
y = ry + menu_button_allocation.y + menu_button_allocation.height;
push_in = false;
}
// This method must be called AFTER the button is added to the toolbar.
public void make_menu_dropdown_button(Gtk.ToggleToolButton toggle_tool_button, Icon? icon, string? label) {
Gtk.ToggleButton? toggle_button = toggle_tool_button.get_child() as Gtk.ToggleButton;
if (toggle_button == null) {
debug("Problem making dropdown button: ToggleToolButton's child is not a ToggleButton");
return;
}
Gtk.Widget? child = toggle_button.get_child();
if (child != null)
toggle_button.remove(child);
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
box.set_homogeneous(false);
if (icon != null)
box.pack_start(new Gtk.Image.from_gicon(icon, Gtk.IconSize.LARGE_TOOLBAR));
if (label != null)
box.pack_start(new Gtk.Label(label));
box.pack_end(new Gtk.Image.from_icon_name("menu-down", Gtk.IconSize.LARGE_TOOLBAR));
toggle_button.add(box);
}
public void add_proxy_menu(Gtk.ToolItem tool_item, string label, Gtk.Menu proxy_menu) {
Gtk.MenuItem proxy_menu_item = new Gtk.MenuItem.with_label(label);
proxy_menu_item.submenu = proxy_menu;
tool_item.create_menu_proxy.connect((sender) => {
sender.set_proxy_menu_item("proxy", proxy_menu_item);
return true;
});
}

View file

@ -4,7 +4,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.FolderPath : Object, Hashable, Equalable {
public class Geary.FolderPath : Object, Hashable, Equalable, Comparable {
public string basename { get; private set; }
private Gee.List<Geary.FolderPath>? path = null;
@ -116,6 +116,39 @@ public class Geary.FolderPath : Object, Hashable, Equalable {
return cs ? str_hash(basename) : str_hash(basename.down());
}
/**
* Comparisons for Geary.FolderPath is defined as (a) empty paths are less-than non-empty paths
* and (b) each element is compared to the corresponding path element of the other FolderPath
* following collation rules for casefolded (case-insensitive) compared, and (c) shorter paths
* are less-than longer paths, assuming the path elements are equal up to the shorter path's
* length.
*/
public int compare(Comparable o) {
FolderPath? other = o as FolderPath;
if (other == null)
return -1;
if (this == other)
return 0;
// walk elements using as_list() as that includes the basename (whereas path does not),
// avoids the null problem, and makes comparisons straightforward
Gee.List<string> this_list = as_list();
Gee.List<string> other_list = other.as_list();
// if paths exist, do comparison of each parent in order
int min = int.min(this_list.size, other_list.size);
for (int ctr = 0; ctr < min; ctr++) {
int result = this_list[ctr].casefold().collate(other_list[ctr].casefold());
if (result != 0)
return result;
}
// paths up to the min element count are equal, shortest path is less-than, otherwise
// equal paths
return this_list.size - other_list.size;
}
public uint to_hash() {
if (hash != uint.MAX)
return hash;