Implement initial drag and drop for accounts, sender mailbox ordering

Code courtesy @ebassi's tutorial:
https://blog.gtk.org/2017/04/23/drag-and-drop-in-lists/
This commit is contained in:
Michael Gratton 2018-12-02 13:51:15 +11:00 committed by Michael James Gratton
parent 140ecc8839
commit 9aab053382
4 changed files with 263 additions and 52 deletions

View file

@ -62,9 +62,9 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
this.details_list.add(new NicknameRow(account));
this.senders_list.set_header_func(Editor.seperator_headers);
foreach (Geary.RFC822.MailboxAddress sender
in account.get_sender_mailboxes()) {
this.senders_list.add(new MailboxRow(account, sender));
foreach (Geary.RFC822.MailboxAddress sender in
account.sender_mailboxes) {
this.senders_list.add(new_mailbox_row(sender));
}
this.senders_list.add(new AddMailboxRow());
@ -173,6 +173,12 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
);
}
internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) {
MailboxRow row = new MailboxRow(this.account, sender);
row.dropped.connect(on_sender_row_dropped);
return row;
}
private void on_account_changed() {
update_header();
}
@ -181,6 +187,18 @@ internal class Accounts.EditorEditPane : Gtk.Grid, EditorPane, AccountPane {
update_actions();
}
private void on_sender_row_dropped(EditorRow source, EditorRow target) {
this.commands.execute.begin(
new ReorderMailboxCommand(
(MailboxRow) source,
(MailboxRow) target,
this.account,
this.senders_list
),
null
);
}
[GtkCallback]
private void on_setting_activated(Gtk.ListBoxRow row) {
EditorRow<EditorEditPane>? setting = row as EditorRow<EditorEditPane>;
@ -309,8 +327,7 @@ private class Accounts.AddMailboxRow : AddRow<EditorEditPane> {
pane.commands.execute.begin(
new AppendMailboxCommand(
(Gtk.ListBox) get_parent(),
new MailboxRow(
pane.account,
pane.new_mailbox_row(
new Geary.RFC822.MailboxAddress(
popover.display_name,
popover.address
@ -338,6 +355,7 @@ private class Accounts.MailboxRow : AccountRow<EditorEditPane,Gtk.Label> {
Geary.RFC822.MailboxAddress mailbox) {
base(account, "", new Gtk.Label(""));
this.mailbox = mailbox;
enable_drag();
update();
}
@ -584,6 +602,50 @@ internal class Accounts.UpdateMailboxCommand : Application.Command {
}
internal class Accounts.ReorderMailboxCommand : Application.Command {
private MailboxRow source;
private int source_index;
private int target_index;
private Geary.AccountInformation account;
private Gtk.ListBox list;
public ReorderMailboxCommand(MailboxRow source,
MailboxRow target,
Geary.AccountInformation account,
Gtk.ListBox list) {
this.source = source;
this.source_index = source.get_index();
this.target_index = target.get_index();
this.account = account;
this.list = list;
}
public async override void execute(GLib.Cancellable? cancellable)
throws GLib.Error {
move_source(this.target_index);
}
public async override void undo(GLib.Cancellable? cancellable)
throws GLib.Error {
move_source(this.source_index);
}
private void move_source(int destination) {
this.account.remove_sender(this.source.mailbox);
this.account.insert_sender(destination, this.source.mailbox);
this.list.remove(this.source);
this.list.insert(this.source, destination);
}
}
internal class Accounts.RemoveMailboxCommand : Application.Command {

View file

@ -158,7 +158,9 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
private void add_account(Geary.AccountInformation account,
Manager.Status status) {
this.accounts_list.add(new AccountListRow(account, status));
AccountListRow row = new AccountListRow(account, status);
row.dropped.connect(on_editor_row_dropped);
this.accounts_list.add(row);
}
private void add_notification(InAppNotification notification) {
@ -216,6 +218,15 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
}
}
private void on_editor_row_dropped(EditorRow source, EditorRow target) {
this.commands.execute.begin(
new ReorderAccountCommand(
(AccountListRow) source, (AccountListRow) target, this.accounts
),
null
);
}
private void on_account_removed(Geary.AccountInformation account) {
AccountListRow? row = get_account_row(account);
if (row != null) {
@ -225,17 +236,21 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
}
private void on_execute(Application.Command command) {
InAppNotification ian = new InAppNotification(command.executed_label);
ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO);
add_notification(ian);
if (command.executed_label != null) {
InAppNotification ian = new InAppNotification(command.executed_label);
ian.set_button(_("Undo"), "win." + GearyController.ACTION_UNDO);
add_notification(ian);
}
update_actions();
}
private void on_undo(Application.Command command) {
InAppNotification ian = new InAppNotification(command.undone_label);
ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO);
add_notification(ian);
if (command.undone_label != null) {
InAppNotification ian = new InAppNotification(command.undone_label);
ian.set_button(_("Redo"), "win." + GearyController.ACTION_REDO);
add_notification(ian);
}
update_actions();
}
@ -267,34 +282,26 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane {
}
private class Accounts.AccountListRow : EditorRow<EditorListPane> {
private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
internal Geary.AccountInformation account;
private Gtk.Label service_label = new Gtk.Label("");
private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name(
"dialog-warning-symbolic", Gtk.IconSize.BUTTON
);
private Gtk.Label account_name = new Gtk.Label("");
private Gtk.Label account_details = new Gtk.Label("");
public AccountListRow(Geary.AccountInformation account,
Manager.Status status) {
this.account = account;
base(account, "", new Gtk.Grid());
enable_drag();
this.account_name.show();
this.account_name.set_hexpand(true);
this.account_name.halign = Gtk.Align.START;
this.value.add(this.unavailable_icon);
this.value.add(this.service_label);
this.account_details.show();
this.layout.add(this.unavailable_icon);
this.layout.add(this.account_name);
this.layout.add(this.account_details);
this.service_label.show();
this.account.information_changed.connect(on_account_changed);
update_nickname();
update();
update_status(status);
}
@ -327,24 +334,12 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
}
}
public void update_nickname() {
public override void update() {
string name = this.account.nickname;
if (Geary.String.is_empty(name)) {
name = account.primary_mailbox.to_address_display("", "");
}
this.account_name.set_text(name);
}
public void update_status(Manager.Status status) {
if (status != Manager.Status.UNAVAILABLE) {
this.unavailable_icon.hide();
this.set_tooltip_text("");
} else {
this.unavailable_icon.show();
this.set_tooltip_text(
_("This account has encountered a problem and is unavailable")
);
}
this.label.set_text(name);
string? details = this.account.service_label;
switch (account.service_provider) {
@ -360,27 +355,43 @@ private class Accounts.AccountListRow : EditorRow<EditorListPane> {
details = _("Yahoo");
break;
}
this.account_details.set_text(details);
this.service_label.set_text(details);
}
public void update_status(Manager.Status status) {
if (status != Manager.Status.UNAVAILABLE) {
this.unavailable_icon.hide();
this.set_tooltip_text("");
} else {
this.unavailable_icon.show();
this.set_tooltip_text(
_("This account has encountered a problem and is unavailable")
);
}
if (status == Manager.Status.ENABLED) {
this.account_name.get_style_context().remove_class(
this.label.get_style_context().remove_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
this.account_details.get_style_context().remove_class(
this.service_label.get_style_context().remove_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
} else {
this.account_name.get_style_context().add_class(
this.label.get_style_context().add_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
this.account_details.get_style_context().add_class(
this.service_label.get_style_context().add_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
}
}
private void on_account_changed() {
update_nickname();
update();
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.invalidate_sort();
}
}
}
@ -451,6 +462,56 @@ private class Accounts.AddServiceProviderRow : EditorRow<EditorListPane> {
}
internal class Accounts.ReorderAccountCommand : Application.Command {
private AccountListRow source;
private int source_index;
private int target_index;
private Manager manager;
public ReorderAccountCommand(AccountListRow source,
AccountListRow target,
Manager manager) {
this.source = source;
this.source_index = source.get_index();
this.target_index = target.get_index();
this.manager = manager;
}
public async override void execute(GLib.Cancellable? cancellable)
throws GLib.Error {
move_source(this.target_index);
}
public async override void undo(GLib.Cancellable? cancellable)
throws GLib.Error {
move_source(this.source_index);
}
private void move_source(int destination) {
Gee.List<Geary.AccountInformation> accounts =
this.manager.iterable().to_linked_list();
accounts.sort(Geary.AccountInformation.compare_ascending);
accounts.remove(this.source.account);
accounts.insert(destination, this.source.account);
int ord = 0;
foreach (Geary.AccountInformation account in accounts) {
if (account.ordinal != ord) {
account.ordinal = ord;
account.information_changed();
}
ord++;
}
}
}
internal class Accounts.RemoveAccountCommand : Application.Command {

View file

@ -8,9 +8,19 @@
internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
private const string DND_ATOM = "geary-editor-row";
private const Gtk.TargetEntry[] DRAG_ENTRIES = {
{ DND_ATOM, Gtk.TargetFlags.SAME_APP, 0 }
};
protected Gtk.Grid layout { get; private set; default = new Gtk.Grid(); }
private Gtk.Container drag_handle;
public signal void dropped(EditorRow target);
public EditorRow() {
get_style_context().add_class("geary-settings");
@ -19,6 +29,23 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
this.layout.show();
add(this.layout);
// We'd like to add the drag handle only when needed, but
// GNOME/gtk#1495 prevents us from doing so.
Gtk.EventBox drag_box = new Gtk.EventBox();
drag_box.add(
new Gtk.Image.from_icon_name(
"open-menu-symbolic", Gtk.IconSize.BUTTON
)
);
this.drag_handle = new Gtk.Grid();
this.drag_handle.valign = Gtk.Align.CENTER;
this.drag_handle.add(drag_box);
this.drag_handle.show_all();
this.drag_handle.hide();
// Translators: Tooltip for dragging list items
this.drag_handle.set_tooltip_text(_("Drag to move this item"));
this.layout.add(drag_handle);
this.show();
}
@ -26,6 +53,54 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
// No-op by default
}
/** Adds a drag handle to the row and enables drag signals. */
protected void enable_drag() {
Gtk.drag_source_set(
this.drag_handle,
Gdk.ModifierType.BUTTON1_MASK,
DRAG_ENTRIES,
Gdk.DragAction.MOVE
);
Gtk.drag_dest_set(
this,
Gtk.DestDefaults.ALL,
DRAG_ENTRIES,
Gdk.DragAction.MOVE
);
this.drag_handle.drag_data_get.connect(on_drag_data_get);
this.drag_data_received.connect(on_drag_data_received);
this.drag_handle.get_style_context().add_class("geary-drag-handle");
this.drag_handle.show();
get_style_context().add_class("geary-draggable");
}
private void on_drag_data_get(Gdk.DragContext context,
Gtk.SelectionData selection_data,
uint info, uint time_) {
selection_data.set(
Gdk.Atom.intern_static_string(DND_ATOM), 8,
get_index().to_string().data
);
}
private void on_drag_data_received(Gdk.DragContext context,
int x, int y,
Gtk.SelectionData selection_data,
uint info, uint time_) {
int drag_index = int.parse((string) selection_data.get_data());
Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox;
if (parent != null) {
EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow;
if (drag_row != null && drag_row != this) {
drag_row.dropped(this);
}
}
}
}

View file

@ -208,6 +208,10 @@ row.geary-settings {
padding: 0px;
}
row.geary-settings image {
padding: 0px 6px;
}
row.geary-settings > grid > * {
margin: 18px 6px;
}
@ -222,11 +226,20 @@ row.geary-settings > grid > *:first-child:dir(rtl) {
margin-right: 18px;
}
row.geary-settings > grid > image:dir(ltr) {
margin-right: 6px;
/* dir pseudo-class used here for required additional specificity */
row.geary-settings > grid > grid.geary-drag-handle:dir(ltr),
row.geary-settings > grid > grid.geary-drag-handle:dir(rtl) {
margin: 0;
}
row.geary-settings > grid > image:dir(rtl) {
margin-left: 6px;
row.geary-settings > grid > grid.geary-drag-handle image:dir(ltr) {
padding: 12px;
padding-right: 6px;
}
row.geary-settings > grid > grid.geary-drag-handle image:dir(rtl) {
padding: 12px;
padding-left: 6px;
}
frame.geary-settings.geary-signature {