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:
parent
140ecc8839
commit
9aab053382
4 changed files with 263 additions and 52 deletions
|
|
@ -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 {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
21
ui/geary.css
21
ui/geary.css
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue