Index: trunk/src/org/openstreetmap/josm/data/ReorderableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/ReorderableModel.java	(revision 15226)
+++ trunk/src/org/openstreetmap/josm/data/ReorderableModel.java	(revision 15226)
@@ -0,0 +1,86 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import java.util.Arrays;
+import java.util.function.IntSupplier;
+
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Defines a model that can be reordered.
+ * @param <T> item type
+ * @since 15226
+ */
+public interface ReorderableModel<T> {
+
+    /**
+     * Get object value at given index.
+     * @param index index
+     * @return object value at given index
+     */
+    T getValue(int index);
+
+    /**
+     * Set object value at given index.
+     * @param index index
+     * @param value new value
+     * @return the value previously at the specified position
+     */
+    T setValue(int index, T value);
+
+    /**
+     * Checks that a range of rows can be moved by a given number of positions.
+     * @param delta negative or positive delta
+     * @param rowCount row count supplier
+     * @param rows indexes of rows to move
+     * @return {@code true} if rows can be moved
+     */
+    default boolean canMove(int delta, IntSupplier rowCount, int... rows) {
+        if (rows == null || rows.length == 0)
+            return false;
+        int[] sortedRows = Utils.copyArray(rows);
+        Arrays.sort(sortedRows);
+        if (delta < 0)
+            return sortedRows[0] >= -delta;
+        else if (delta > 0)
+            return sortedRows[sortedRows.length-1] <= rowCount.getAsInt()-1 - delta;
+        else
+            return true;
+    }
+
+    /**
+     * Checks that a range of rows can be moved up (by 1 position).
+     * @param rowCount row count supplier
+     * @param rows indexes of rows to move up
+     * @return {@code true} if rows can be moved up
+     */
+    default boolean canMoveUp(IntSupplier rowCount, int... rows) {
+        return canMove(-1, rowCount, rows);
+    }
+
+    /**
+     * Checks that a range of rows can be moved down (by 1 position).
+     * @param rowCount row count supplier
+     * @param rows indexes of rows to move down
+     * @return {@code true} if rows can be moved down
+     */
+    default boolean canMoveDown(IntSupplier rowCount, int... rows) {
+        return canMove(1, rowCount, rows);
+    }
+
+    /**
+     * Performs the move operation, without any check nor selection handling.
+     * @param delta negative or positive delta
+     * @param selectedRows rows to move
+     * @return {@code true} if rows have been moved down
+     */
+    default boolean doMove(int delta, int... selectedRows) {
+        for (int row: selectedRows) {
+            T t1 = getValue(row);
+            T t2 = getValue(row + delta);
+            setValue(row, t2);
+            setValue(row + delta, t1);
+        }
+        return true;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/SortableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/SortableModel.java	(revision 15226)
+++ trunk/src/org/openstreetmap/josm/data/SortableModel.java	(revision 15226)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+/**
+ * Defines a model that can be sorted.
+ * @param <T> item type
+ * @since 15226
+ */
+public interface SortableModel<T> extends ReorderableModel<T> {
+
+    /**
+     * Sort the items.
+     */
+    void sort();
+
+    /**
+     * Reverse the items order.
+     */
+    void reverse();
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/Filter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/Filter.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/data/osm/Filter.java	(revision 15226)
@@ -2,4 +2,5 @@
 package org.openstreetmap.josm.data.osm;
 
+import java.util.Comparator;
 import java.util.Objects;
 
@@ -15,5 +16,5 @@
  * @since 2125
  */
-public class Filter extends SearchSetting {
+public class Filter extends SearchSetting implements Comparable<Filter> {
     private static final String version = "1";
 
@@ -167,3 +168,17 @@
         return e;
     }
+
+    @Override
+    public int compareTo(Filter o) {
+        return Comparator
+                .<Filter, String>comparing(f -> f.text)
+                .thenComparing(f -> f.mode)
+                .thenComparing(f -> f.caseSensitive)
+                .thenComparing(f -> f.regexSearch)
+                .thenComparing(f -> f.mapCSSSearch)
+                .thenComparing(f -> f.enable)
+                .thenComparing(f -> f.hiding)
+                .thenComparing(f -> f.inverted)
+                .compare(this, o);
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/data/osm/FilterModel.java	(revision 15226)
@@ -8,4 +8,5 @@
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -16,4 +17,5 @@
 import javax.swing.JOptionPane;
 
+import org.openstreetmap.josm.data.SortableModel;
 import org.openstreetmap.josm.data.StructUtils;
 import org.openstreetmap.josm.data.osm.Filter.FilterPreferenceEntry;
@@ -29,5 +31,5 @@
  * @since 12400
  */
-public class FilterModel {
+public class FilterModel implements SortableModel<Filter> {
 
     /**
@@ -236,4 +238,19 @@
 
     /**
+     * Moves the filters in the given rows by a number of positions.
+     * @param delta negative or positive increment
+     * @param rowIndexes The filter rows
+     * @return true if the filters have been moved down
+     * @since 15226
+     */
+    public boolean moveFilters(int delta, int... rowIndexes) {
+        if (!canMove(delta, filters::size, rowIndexes))
+            return false;
+        doMove(delta, rowIndexes);
+        updateFilterMatcher();
+        return true;
+    }
+
+    /**
      * Moves down the filter in the given row.
      * @param rowIndex The filter row
@@ -241,9 +258,5 @@
      */
     public boolean moveDownFilter(int rowIndex) {
-        if (rowIndex >= filters.size() - 1)
-            return false;
-        filters.add(rowIndex + 1, filters.remove(rowIndex));
-        updateFilterMatcher();
-        return true;
+        return moveFilters(1, rowIndex);
     }
 
@@ -254,9 +267,5 @@
      */
     public boolean moveUpFilter(int rowIndex) {
-        if (rowIndex <= 0 || rowIndex >= filters.size())
-            return false;
-        filters.add(rowIndex - 1, filters.remove(rowIndex));
-        updateFilterMatcher();
-        return true;
+        return moveFilters(-1, rowIndex);
     }
 
@@ -277,6 +286,13 @@
      * @param filter The filter that should be placed in that row
      * @return the filter previously at the specified position
-     */
+     * @deprecated Use {@link #setValue}
+     */
+    @Deprecated
     public Filter setFilter(int rowIndex, Filter filter) {
+        return setValue(rowIndex, filter);
+    }
+
+    @Override
+    public Filter setValue(int rowIndex, Filter filter) {
         Filter result = filters.set(rowIndex, filter);
         updateFilterMatcher();
@@ -288,6 +304,13 @@
      * @param rowIndex The row index
      * @return The filter in that row
-     */
+     * @deprecated Use {@link #getValue}
+     */
+    @Deprecated
     public Filter getFilter(int rowIndex) {
+        return getValue(rowIndex);
+    }
+
+    @Override
+    public Filter getValue(int rowIndex) {
         return filters.get(rowIndex);
     }
@@ -418,3 +441,15 @@
         return result;
     }
+
+    @Override
+    public void sort() {
+        Collections.sort(filters);
+        updateFilterMatcher();
+    }
+
+    @Override
+    public void reverse() {
+        Collections.reverse(filters);
+        updateFilterMatcher();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/FilterDialog.java	(revision 15226)
@@ -12,7 +12,9 @@
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import javax.swing.AbstractAction;
 import javax.swing.DefaultCellEditor;
+import javax.swing.DefaultListSelectionModel;
 import javax.swing.JCheckBox;
 import javax.swing.JTable;
@@ -62,8 +64,15 @@
 
     private JTable userTable;
-    private final FilterTableModel filterModel = new FilterTableModel();
-
-    private final EnableFilterAction enableFilterAction;
-    private final HidingFilterAction hidingFilterAction;
+    private final FilterTableModel filterModel = new FilterTableModel(new DefaultListSelectionModel());
+
+    private final AddAction addAction = new AddAction();
+    private final EditAction editAction = new EditAction();
+    private final DeleteAction deleteAction = new DeleteAction();
+    private final MoveUpAction moveUpAction = new MoveUpAction();
+    private final MoveDownAction moveDownAction = new MoveDownAction();
+    private final SortAction sortAction = new SortAction();
+    private final ReverseAction reverseAction = new ReverseAction();
+    private final EnableFilterAction enableFilterAction = new EnableFilterAction();
+    private final HidingFilterAction hidingFilterAction = new HidingFilterAction();
 
     /**
@@ -75,6 +84,4 @@
                         KeyEvent.VK_F, Shortcut.ALT_SHIFT), 162);
         build();
-        enableFilterAction = new EnableFilterAction();
-        hidingFilterAction = new HidingFilterAction();
         MultikeyActionsHandler.getInstance().addAction(enableFilterAction);
         MultikeyActionsHandler.getInstance().addAction(hidingFilterAction);
@@ -112,4 +119,138 @@
     };
 
+    private abstract class FilterAction extends AbstractAction implements IEnabledStateUpdating {
+
+        FilterAction(String name, String description, String icon) {
+            putValue(NAME, name);
+            putValue(SHORT_DESCRIPTION, description);
+            new ImageProvider("dialogs", icon).getResource().attachImageIcon(this, true);
+        }
+
+        @Override
+        public void updateEnabledState() {
+            setEnabled(!filterModel.getSelectionModel().isSelectionEmpty());
+        }
+    }
+
+    private class AddAction extends FilterAction {
+        AddAction() {
+            super(tr("Add"), tr("Add filter."), /* ICON(dialogs/) */ "add");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            SearchSetting searchSetting = SearchAction.showSearchDialog(new Filter());
+            if (searchSetting != null) {
+                filterModel.addFilter(new Filter(searchSetting));
+            }
+        }
+
+        @Override
+        public void updateEnabledState() {
+            // Do nothing
+        }
+    }
+
+    private class EditAction extends FilterAction {
+        EditAction() {
+            super(tr("Edit"), tr("Edit filter."), /* ICON(dialogs/) */ "edit");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int index = filterModel.getSelectionModel().getMinSelectionIndex();
+            if (index < 0) return;
+            Filter f = filterModel.getValue(index);
+            SearchSetting searchSetting = SearchAction.showSearchDialog(f);
+            if (searchSetting != null) {
+                filterModel.setValue(index, new Filter(searchSetting));
+            }
+        }
+    }
+
+    private class DeleteAction extends FilterAction {
+        DeleteAction() {
+            super(tr("Delete"), tr("Delete filter."), /* ICON(dialogs/) */ "delete");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int index = filterModel.getSelectionModel().getMinSelectionIndex();
+            if (index >= 0) {
+                filterModel.removeFilter(index);
+            }
+        }
+    }
+
+    private class MoveUpAction extends FilterAction {
+        MoveUpAction() {
+            super(tr("Up"), tr("Move filter up."), /* ICON(dialogs/) */ "up");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int index = userTable.convertRowIndexToModel(userTable.getSelectionModel().getMinSelectionIndex());
+            if (index >= 0 && filterModel.moveUp(index)) {
+                filterModel.getSelectionModel().setSelectionInterval(index-1, index-1);
+            }
+        }
+
+        @Override
+        public void updateEnabledState() {
+            setEnabled(filterModel.canMoveUp());
+        }
+    }
+
+    private class MoveDownAction extends FilterAction {
+        MoveDownAction() {
+            super(tr("Down"), tr("Move filter down."), /* ICON(dialogs/) */ "down");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            int index = userTable.convertRowIndexToModel(userTable.getSelectionModel().getMinSelectionIndex());
+            if (index >= 0 && filterModel.moveDown(index)) {
+                filterModel.getSelectionModel().setSelectionInterval(index+1, index+1);
+            }
+        }
+
+        @Override
+        public void updateEnabledState() {
+            setEnabled(filterModel.canMoveDown());
+        }
+    }
+
+    private class SortAction extends FilterAction {
+        SortAction() {
+            super(tr("Sort"), tr("Sort filters."), /* ICON(dialogs/) */ "sort");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            filterModel.sort();
+        }
+
+        @Override
+        public void updateEnabledState() {
+            setEnabled(filterModel.getRowCount() > 1);
+        }
+    }
+
+    private class ReverseAction extends FilterAction {
+        ReverseAction() {
+            super(tr("Reverse"), tr("Reverse the filters order."), /* ICON(dialogs/) */ "reverse");
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            filterModel.reverse();
+        }
+
+        @Override
+        public void updateEnabledState() {
+            setEnabled(filterModel.getRowCount() > 1);
+        }
+    }
+
     /**
      * Builds the GUI.
@@ -120,5 +261,5 @@
         userTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
         userTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-        userTable.setAutoCreateRowSorter(true);
+        userTable.setSelectionModel(filterModel.getSelectionModel());
 
         TableHelper.adjustColumnWidth(userTable, 0, false);
@@ -130,85 +271,4 @@
         userTable.setDefaultRenderer(String.class, new StringRenderer());
         userTable.setDefaultEditor(String.class, new DefaultCellEditor(new DisableShortcutsOnFocusGainedTextField()));
-
-        SideButton addButton = new SideButton(new AbstractAction() {
-            {
-                putValue(NAME, tr("Add"));
-                putValue(SHORT_DESCRIPTION, tr("Add filter."));
-                new ImageProvider("dialogs", "add").getResource().attachImageIcon(this, true);
-            }
-
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                SearchSetting searchSetting = SearchAction.showSearchDialog(new Filter());
-                if (searchSetting != null) {
-                    filterModel.addFilter(new Filter(searchSetting));
-                }
-            }
-        });
-        SideButton editButton = new SideButton(new AbstractAction() {
-            {
-                putValue(NAME, tr("Edit"));
-                putValue(SHORT_DESCRIPTION, tr("Edit filter."));
-                new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this, true);
-            }
-
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                int index = userTable.getSelectionModel().getMinSelectionIndex();
-                if (index < 0) return;
-                Filter f = filterModel.getFilter(index);
-                SearchSetting searchSetting = SearchAction.showSearchDialog(f);
-                if (searchSetting != null) {
-                    filterModel.setFilter(index, new Filter(searchSetting));
-                }
-            }
-        });
-        SideButton deleteButton = new SideButton(new AbstractAction() {
-            {
-                putValue(NAME, tr("Delete"));
-                putValue(SHORT_DESCRIPTION, tr("Delete filter."));
-                new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this, true);
-            }
-
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                int index = userTable.getSelectionModel().getMinSelectionIndex();
-                if (index >= 0) {
-                    filterModel.removeFilter(index);
-                }
-            }
-        });
-        SideButton upButton = new SideButton(new AbstractAction() {
-            {
-                putValue(NAME, tr("Up"));
-                putValue(SHORT_DESCRIPTION, tr("Move filter up."));
-                new ImageProvider("dialogs", "up").getResource().attachImageIcon(this, true);
-            }
-
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                int index = userTable.getSelectionModel().getMinSelectionIndex();
-                if (index >= 0) {
-                    filterModel.moveUpFilter(index);
-                    userTable.getSelectionModel().setSelectionInterval(index-1, index-1);
-                }
-            }
-        });
-        SideButton downButton = new SideButton(new AbstractAction() {
-            {
-                putValue(NAME, tr("Down"));
-                putValue(SHORT_DESCRIPTION, tr("Move filter down."));
-                new ImageProvider("dialogs", "down").getResource().attachImageIcon(this, true);
-            }
-
-            @Override
-            public void actionPerformed(ActionEvent e) {
-                int index = userTable.getSelectionModel().getMinSelectionIndex();
-                if (index >= 0) {
-                    filterModel.moveDownFilter(index);
-                    userTable.getSelectionModel().setSelectionInterval(index+1, index+1);
-                }
-            }
-        });
 
         // Toggle filter "enabled" on Enter
@@ -218,5 +278,5 @@
                 int index = userTable.getSelectedRow();
                 if (index >= 0) {
-                    Filter filter = filterModel.getFilter(index);
+                    Filter filter = filterModel.getValue(index);
                     filterModel.setValueAt(!filter.enable, index, FilterTableModel.COL_ENABLED);
                 }
@@ -230,5 +290,5 @@
                 int index = userTable.getSelectedRow();
                 if (index >= 0) {
-                    Filter filter = filterModel.getFilter(index);
+                    Filter filter = filterModel.getValue(index);
                     filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
                 }
@@ -236,7 +296,11 @@
         });
 
-        createLayout(userTable, true, Arrays.asList(
-                addButton, editButton, deleteButton, upButton, downButton
-        ));
+        List<FilterAction> actions = Arrays.asList(addAction, editAction, deleteAction, moveUpAction, moveDownAction, sortAction, reverseAction);
+        for (FilterAction action : actions) {
+            TableHelper.adaptTo(action, filterModel);
+            TableHelper.adaptTo(action, filterModel.getSelectionModel());
+            action.updateEnabledState();
+        }
+        createLayout(userTable, true, actions.stream().map(a -> new SideButton(a, false)).collect(Collectors.toList()));
     }
 
@@ -384,7 +448,5 @@
 
             for (int i = 0; i < filterModel.getRowCount(); i++) {
-                Filter filter = filterModel.getFilter(i);
-                MultikeyInfo info = new MultikeyInfo(i, filter.text);
-                result.add(info);
+                result.add(new MultikeyInfo(i, filterModel.getValue(i).text));
             }
 
@@ -420,5 +482,5 @@
         public void executeMultikeyAction(int index, boolean repeatLastAction) {
             if (index >= 0 && index < filterModel.getRowCount()) {
-                Filter filter = filterModel.getFilter(index);
+                Filter filter = filterModel.getValue(index);
                 filterModel.setValueAt(!filter.enable, index, FilterTableModel.COL_ENABLED);
                 lastFilter = filter;
@@ -444,5 +506,5 @@
         public void executeMultikeyAction(int index, boolean repeatLastAction) {
             if (index >= 0 && index < filterModel.getRowCount()) {
-                Filter filter = filterModel.getFilter(index);
+                Filter filter = filterModel.getValue(index);
                 filterModel.setValueAt(!filter.hiding, index, FilterTableModel.COL_HIDING);
                 lastFilter = filter;
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/FilterTableModel.java	(revision 15226)
@@ -9,4 +9,5 @@
 import java.util.List;
 
+import javax.swing.ListSelectionModel;
 import javax.swing.table.AbstractTableModel;
 
@@ -17,4 +18,5 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.autofilter.AutoFilterManager;
+import org.openstreetmap.josm.gui.util.SortableTableModel;
 import org.openstreetmap.josm.gui.widgets.OSDLabel;
 import org.openstreetmap.josm.tools.Logging;
@@ -24,6 +26,7 @@
  *
  * @author Petr_Dlouhý
+ * @since 2125
  */
-public class FilterTableModel extends AbstractTableModel {
+public class FilterTableModel extends AbstractTableModel implements SortableTableModel<Filter> {
 
     /**
@@ -50,5 +53,10 @@
 
     /**
-     * A helper for {@link #drawOSDText(Graphics2D)}.
+     * The selection model
+     */
+    final ListSelectionModel selectionModel;
+
+    /**
+     * A helper for {@link #drawOSDText(Graphics2D)}
      */
     private final OSDLabel lblOSD = new OSDLabel("");
@@ -56,6 +64,8 @@
     /**
      * Constructs a new {@code FilterTableModel}.
-     */
-    public FilterTableModel() {
+     * @param listSelectionModel selection model
+     */
+    public FilterTableModel(ListSelectionModel listSelectionModel) {
+        this.selectionModel = listSelectionModel;
         loadPrefs();
     }
@@ -134,14 +144,31 @@
     }
 
+    @Override
+    public boolean doMove(int delta, int... selectedRows) {
+        return model.moveFilters(delta, selectedRows);
+    }
+
+    @Override
+    public boolean move(int delta, int... selectedRows) {
+        if (!SortableTableModel.super.move(delta, selectedRows))
+            return false;
+        savePrefs();
+        updateFilters();
+        int rowIndex = selectedRows[0];
+        if (delta < 0)
+            fireTableRowsUpdated(rowIndex + delta, rowIndex);
+        else if (delta > 0)
+            fireTableRowsUpdated(rowIndex, rowIndex + delta);
+        return true;
+    }
+
     /**
      * Moves down the filter in the given row.
      * @param rowIndex The filter row
-     */
+     * @deprecated Use {@link #moveDown(int...)}
+     */
+    @Deprecated
     public void moveDownFilter(int rowIndex) {
-        if (model.moveDownFilter(rowIndex)) {
-            savePrefs();
-            updateFilters();
-            fireTableRowsUpdated(rowIndex, rowIndex + 1);
-        }
+        moveDown(rowIndex);
     }
 
@@ -149,11 +176,9 @@
      * Moves up the filter in the given row
      * @param rowIndex The filter row
-     */
+     * @deprecated Use {@link #moveUp(int...)}
+     */
+    @Deprecated
     public void moveUpFilter(int rowIndex) {
-        if (model.moveUpFilter(rowIndex)) {
-            savePrefs();
-            updateFilters();
-            fireTableRowsUpdated(rowIndex - 1, rowIndex);
-        }
+        moveUp(rowIndex);
     }
 
@@ -174,10 +199,18 @@
      * @param rowIndex The row index
      * @param filter The filter that should be placed in that row
-     */
+     * @deprecated Use {@link #setValue}
+     */
+    @Deprecated
     public void setFilter(int rowIndex, Filter filter) {
-        model.setFilter(rowIndex, filter);
+        setValue(rowIndex, filter);
+    }
+
+    @Override
+    public Filter setValue(int rowIndex, Filter filter) {
+        Filter result = model.setValue(rowIndex, filter);
         savePrefs();
         updateFilters();
         fireTableRowsUpdated(rowIndex, rowIndex);
+        return result;
     }
 
@@ -186,7 +219,19 @@
      * @param rowIndex The row index
      * @return The filter in that row
-     */
+     * @deprecated Use {@link #getValue}
+     */
+    @Deprecated
     public Filter getFilter(int rowIndex) {
-        return model.getFilter(rowIndex);
+        return getValue(rowIndex);
+    }
+
+    @Override
+    public Filter getValue(int rowIndex) {
+        return model.getValue(rowIndex);
+    }
+
+    @Override
+    public ListSelectionModel getSelectionModel() {
+        return selectionModel;
     }
 
@@ -225,5 +270,5 @@
      */
     public boolean isCellEnabled(int row, int column) {
-        return model.getFilter(row).enable || column == 0;
+        return model.getValue(row).enable || column == 0;
     }
 
@@ -238,5 +283,5 @@
             return;
         }
-        Filter f = model.getFilter(row);
+        Filter f = model.getValue(row);
         switch (column) {
         case COL_ENABLED:
@@ -254,5 +299,5 @@
         default: // Do nothing
         }
-        setFilter(row, f);
+        setValue(row, f);
     }
 
@@ -262,5 +307,5 @@
             return null;
         }
-        Filter f = model.getFilter(row);
+        Filter f = model.getValue(row);
         switch (column) {
         case COL_ENABLED:
@@ -308,3 +353,15 @@
         return model.getFilters();
     }
+
+    @Override
+    public void sort() {
+        model.sort();
+        fireTableDataChanged();
+    }
+
+    @Override
+    public void reverse() {
+        model.reverse();
+        fireTableDataChanged();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java	(revision 15226)
@@ -17,5 +17,4 @@
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -74,4 +73,6 @@
 import org.openstreetmap.josm.gui.util.MultikeyActionsHandler;
 import org.openstreetmap.josm.gui.util.MultikeyShortcutAction.MultikeyInfo;
+import org.openstreetmap.josm.gui.util.ReorderableTableModel;
+import org.openstreetmap.josm.gui.util.TableHelper;
 import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
@@ -79,4 +80,5 @@
 import org.openstreetmap.josm.gui.widgets.ScrollableTable;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.ArrayUtils;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
@@ -268,11 +270,11 @@
         // -- move up action
         MoveUpAction moveUpAction = new MoveUpAction(model);
-        adaptTo(moveUpAction, model);
-        adaptTo(moveUpAction, selectionModel);
+        TableHelper.adaptTo(moveUpAction, model);
+        TableHelper.adaptTo(moveUpAction, selectionModel);
 
         // -- move down action
         MoveDownAction moveDownAction = new MoveDownAction(model);
-        adaptTo(moveDownAction, model);
-        adaptTo(moveDownAction, selectionModel);
+        TableHelper.adaptTo(moveDownAction, model);
+        TableHelper.adaptTo(moveDownAction, selectionModel);
 
         // -- activate action
@@ -280,5 +282,5 @@
         activateLayerAction.updateEnabledState();
         MultikeyActionsHandler.getInstance().addAction(activateLayerAction);
-        adaptTo(activateLayerAction, selectionModel);
+        TableHelper.adaptTo(activateLayerAction, selectionModel);
 
         JumpToMarkerActions.initialize();
@@ -287,8 +289,8 @@
         showHideLayerAction = new ShowHideLayerAction(model);
         MultikeyActionsHandler.getInstance().addAction(showHideLayerAction);
-        adaptTo(showHideLayerAction, selectionModel);
+        TableHelper.adaptTo(showHideLayerAction, selectionModel);
 
         LayerVisibilityAction visibilityAction = new LayerVisibilityAction(model);
-        adaptTo(visibilityAction, selectionModel);
+        TableHelper.adaptTo(visibilityAction, selectionModel);
         SideButton visibilityButton = new SideButton(visibilityAction, false);
         visibilityAction.setCorrespondingSideButton(visibilityButton);
@@ -297,5 +299,5 @@
         DeleteLayerAction deleteLayerAction = new DeleteLayerAction(model);
         layerList.getActionMap().put("deleteLayer", deleteLayerAction);
-        adaptTo(deleteLayerAction, selectionModel);
+        TableHelper.adaptTo(deleteLayerAction, selectionModel);
         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"
@@ -365,7 +367,9 @@
      * @param listener  the listener
      * @param listSelectionModel  the source emitting {@link ListSelectionEvent}s
-     */
+     * @deprecated Use {@link TableHelper#adaptTo}
+     */
+    @Deprecated
     protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
-        listSelectionModel.addListSelectionListener(e -> listener.updateEnabledState());
+        TableHelper.adaptTo(listener, listSelectionModel);
     }
 
@@ -377,7 +381,9 @@
      * @param listener the listener
      * @param listModel the source emitting {@link ListDataEvent}s
-     */
+     * @deprecated Use {@link TableHelper#adaptTo}
+     */
+    @Deprecated
     protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) {
-        listModel.addTableModelListener(e -> listener.updateEnabledState());
+        TableHelper.adaptTo(listener, listModel);
     }
 
@@ -679,5 +685,5 @@
      */
     public static final class LayerListModel extends AbstractTableModel
-            implements LayerChangeListener, ActiveLayerChangeListener, PropertyChangeListener {
+            implements LayerChangeListener, ActiveLayerChangeListener, PropertyChangeListener, ReorderableTableModel<Layer> {
         /** manages list selection state*/
         private final DefaultListSelectionModel selectionModel;
@@ -801,11 +807,5 @@
          */
         public List<Integer> getSelectedRows() {
-            List<Integer> selected = new ArrayList<>();
-            for (int i = 0; i < getLayers().size(); i++) {
-                if (selectionModel.isSelectedIndex(i)) {
-                    selected.add(i);
-                }
-            }
-            return selected;
+            return ArrayUtils.toList(TableHelper.getSelectedIndices(selectionModel));
         }
 
@@ -820,7 +820,7 @@
             layer.removePropertyChangeListener(this);
             final int size = getRowCount();
-            final List<Integer> rows = getSelectedRows();
-
-            if (rows.isEmpty() && size > 0) {
+            final int[] rows = TableHelper.getSelectedIndices(selectionModel);
+
+            if (rows.length == 0 && size > 0) {
                 selectionModel.setSelectionInterval(size-1, size-1);
             }
@@ -876,70 +876,44 @@
         }
 
-        /**
-         * Replies true if the currently selected layers can move up by one position
-         *
-         * @return true if the currently selected layers can move up by one position
-         */
-        public boolean canMoveUp() {
-            List<Integer> sel = getSelectedRows();
-            return !sel.isEmpty() && sel.get(0) > 0;
-        }
-
-        /**
-         * Move up the currently selected layers by one position
-         *
-         */
-        public void moveUp() {
-            if (!canMoveUp())
-                return;
-            List<Integer> sel = getSelectedRows();
-            List<Layer> layers = getLayers();
-            MapView mapView = MainApplication.getMap().mapView;
-            for (int row : sel) {
-                Layer l1 = layers.get(row);
-                mapView.moveLayer(l1, row-1);
-            }
-            fireTableDataChanged();
-            selectionModel.setValueIsAdjusting(true);
-            selectionModel.clearSelection();
-            for (int row : sel) {
-                selectionModel.addSelectionInterval(row-1, row-1);
-            }
-            selectionModel.setValueIsAdjusting(false);
+        @Override
+        public DefaultListSelectionModel getSelectionModel() {
+            return selectionModel;
+        }
+
+        @Override
+        public Layer getValue(int index) {
+            return getLayer(index);
+        }
+
+        @Override
+        public Layer setValue(int index, Layer value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean doMove(int delta, int... selectedRows) {
+            if (delta != 0) {
+                List<Layer> layers = getLayers();
+                MapView mapView = MainApplication.getMap().mapView;
+                if (delta < 0) {
+                    for (int row : selectedRows) {
+                        mapView.moveLayer(layers.get(row), row + delta);
+                    }
+                } else if (delta > 0) {
+                    for (int i = selectedRows.length - 1; i >= 0; i--) {
+                        mapView.moveLayer(layers.get(selectedRows[i]), selectedRows[i] + delta);
+                    }
+                }
+                fireTableDataChanged();
+            }
+            return delta != 0;
+        }
+
+        @Override
+        public boolean move(int delta, int... selectedRows) {
+            if (!ReorderableTableModel.super.move(delta, selectedRows))
+                return false;
             ensureSelectedIsVisible();
-        }
-
-        /**
-         * Replies true if the currently selected layers can move down by one position
-         *
-         * @return true if the currently selected layers can move down by one position
-         */
-        public boolean canMoveDown() {
-            List<Integer> sel = getSelectedRows();
-            return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1;
-        }
-
-        /**
-         * Move down the currently selected layers by one position
-         */
-        public void moveDown() {
-            if (!canMoveDown())
-                return;
-            List<Integer> sel = getSelectedRows();
-            Collections.reverse(sel);
-            List<Layer> layers = getLayers();
-            MapView mapView = MainApplication.getMap().mapView;
-            for (int row : sel) {
-                Layer l1 = layers.get(row);
-                mapView.moveLayer(l1, row+1);
-            }
-            fireTableDataChanged();
-            selectionModel.setValueIsAdjusting(true);
-            selectionModel.clearSelection();
-            for (int row : sel) {
-                selectionModel.addSelectionInterval(row+1, row+1);
-            }
-            selectionModel.setValueIsAdjusting(false);
-            ensureSelectedIsVisible();
+            return true;
         }
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java	(revision 15226)
@@ -3,5 +3,4 @@
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
@@ -45,5 +44,7 @@
 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
 import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.util.SortableTableModel;
 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel;
+import org.openstreetmap.josm.tools.ArrayUtils;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.bugreport.BugReport;
@@ -53,5 +54,5 @@
  */
 public class MemberTableModel extends AbstractTableModel
-implements TableModelListener, DataSelectionListener, DataSetListener, OsmPrimitivesTableModel {
+implements TableModelListener, DataSelectionListener, DataSetListener, OsmPrimitivesTableModel, SortableTableModel<RelationMember> {
 
     /**
@@ -181,4 +182,8 @@
     /* --------------------------------------------------------------------------- */
 
+    /**
+     * Add a new member model listener.
+     * @param listener member model listener to add
+     */
     public void addMemberModelListener(IMemberModelListener listener) {
         if (listener != null) {
@@ -187,4 +192,8 @@
     }
 
+    /**
+     * Remove a member model listener.
+     * @param listener member model listener to remove
+     */
     public void removeMemberModelListener(IMemberModelListener listener) {
         listeners.remove(listener);
@@ -260,22 +269,13 @@
     }
 
-    /**
-     * Move up selected rows, if possible.
-     * @param selectedRows rows to move up
-     * @see #canMoveUp
-     */
-    public void moveUp(int... selectedRows) {
-        if (!canMoveUp(selectedRows))
-            return;
-
-        for (int row : selectedRows) {
-            RelationMember member1 = members.get(row);
-            RelationMember member2 = members.get(row - 1);
-            members.set(row, member2);
-            members.set(row - 1, member1);
-        }
+    @Override
+    public boolean move(int delta, int... selectedRows) {
+        if (!canMove(delta, this::getRowCount, selectedRows))
+            return false;
+        doMove(delta, selectedRows);
         fireTableDataChanged();
-        getSelectionModel().setValueIsAdjusting(true);
-        getSelectionModel().clearSelection();
+        final ListSelectionModel selectionModel = getSelectionModel();
+        selectionModel.setValueIsAdjusting(true);
+        selectionModel.clearSelection();
         BitSet selected = new BitSet();
         for (int row : selectedRows) {
@@ -284,36 +284,7 @@
         }
         addToSelectedMembers(selected);
-        getSelectionModel().setValueIsAdjusting(false);
-        fireMakeMemberVisible(selectedRows[0] - 1);
-    }
-
-    /**
-     * Move down selected rows, if possible.
-     * @param selectedRows rows to move down
-     * @see #canMoveDown
-     */
-    public void moveDown(int... selectedRows) {
-        if (!canMoveDown(selectedRows))
-            return;
-
-        for (int i = selectedRows.length - 1; i >= 0; i--) {
-            int row = selectedRows[i];
-            RelationMember member1 = members.get(row);
-            RelationMember member2 = members.get(row + 1);
-            members.set(row, member2);
-            members.set(row + 1, member1);
-        }
-        fireTableDataChanged();
-        getSelectionModel();
-        getSelectionModel().setValueIsAdjusting(true);
-        getSelectionModel().clearSelection();
-        BitSet selected = new BitSet();
-        for (int row : selectedRows) {
-            row++;
-            selected.set(row);
-        }
-        addToSelectedMembers(selected);
-        getSelectionModel().setValueIsAdjusting(false);
-        fireMakeMemberVisible(selectedRows[0] + 1);
+        selectionModel.setValueIsAdjusting(false);
+        fireMakeMemberVisible(selectedRows[0] + delta);
+        return true;
     }
 
@@ -338,28 +309,4 @@
 
     /**
-     * Checks that a range of rows can be moved up.
-     * @param rows indexes of rows to move up
-     * @return {@code true} if rows can be moved up
-     */
-    public boolean canMoveUp(int... rows) {
-        if (rows == null || rows.length == 0)
-            return false;
-        Arrays.sort(rows);
-        return rows[0] > 0 && rows[rows.length - 1] < members.size();
-    }
-
-    /**
-     * Checks that a range of rows can be moved down.
-     * @param rows indexes of rows to move down
-     * @return {@code true} if rows can be moved down
-     */
-    public boolean canMoveDown(int... rows) {
-        if (rows == null || rows.length == 0)
-            return false;
-        Arrays.sort(rows);
-        return rows[0] >= 0 && rows[rows.length - 1] < members.size() - 1;
-    }
-
-    /**
      * Checks that a range of rows can be removed.
      * @param rows indexes of rows to remove
@@ -370,8 +317,5 @@
     }
 
-    /**
-     * Returns the selection model.
-     * @return the selection model (never null)
-     */
+    @Override
     public DefaultListSelectionModel getSelectionModel() {
         if (listSelectionModel == null) {
@@ -382,9 +326,23 @@
     }
 
+    @Override
+    public RelationMember getValue(int index) {
+        return members.get(index);
+    }
+
+    @Override
+    public RelationMember setValue(int index, RelationMember value) {
+        return members.set(index, value);
+    }
+
+    /**
+     * Remove members referring to the given list of primitives.
+     * @param primitives list of OSM primitives
+     */
     public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) {
         if (primitives == null)
             return;
-        members.removeIf(member -> primitives.contains(member.getMember()));
-        fireTableDataChanged();
+        if (members.removeIf(member -> primitives.contains(member.getMember())))
+            fireTableDataChanged();
     }
 
@@ -398,8 +356,11 @@
     }
 
+    /**
+     * Determines if this model has the same members as the given relation.
+     * @param relation relation
+     * @return {@code true} if this model has the same members as {@code relation}
+     */
     public boolean hasSameMembersAs(Relation relation) {
-        if (relation == null)
-            return false;
-        if (relation.getMembersCount() != members.size())
+        if (relation == null || relation.getMembersCount() != members.size())
             return false;
         for (int i = 0; i < relation.getMembersCount(); i++) {
@@ -464,14 +425,4 @@
         }
         return false;
-    }
-
-    protected List<Integer> getSelectedIndices() {
-        List<Integer> selectedIndices = new ArrayList<>();
-        for (int i = 0; i < members.size(); i++) {
-            if (getSelectionModel().isSelectedIndex(i)) {
-                selectedIndices.add(i);
-            }
-        }
-        return selectedIndices;
     }
 
@@ -773,6 +724,7 @@
         addToSelectedMembers(selected);
         getSelectionModel().setValueIsAdjusting(false);
-        if (!getSelectedIndices().isEmpty()) {
-            fireMakeMemberVisible(getSelectedIndices().get(0));
+        int[] selectedIndices = getSelectedIndices();
+        if (selectedIndices.length > 0) {
+            fireMakeMemberVisible(selectedIndices[0]);
         }
     }
@@ -793,4 +745,5 @@
      * Sort the selected relation members by the way they are linked.
      */
+    @Override
     public void sort() {
         List<RelationMember> selectedMembers = new ArrayList<>(getSelectedMembers());
@@ -802,5 +755,5 @@
         } else {
             sortedMembers = relationSorter.sortMembers(selectedMembers);
-            List<Integer> selectedIndices = getSelectedIndices();
+            List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices());
             newMembers = new ArrayList<>();
             boolean inserted = false;
@@ -861,7 +814,8 @@
      * Reverse the relation members.
      */
+    @Override
     public void reverse() {
-        List<Integer> selectedIndices = getSelectedIndices();
-        List<Integer> selectedIndicesReversed = getSelectedIndices();
+        List<Integer> selectedIndices = ArrayUtils.toList(getSelectedIndices());
+        List<Integer> selectedIndicesReversed = ArrayUtils.toList(getSelectedIndices());
 
         if (selectedIndices.size() <= 1) {
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ReverseAction.java	(revision 15226)
@@ -23,5 +23,5 @@
 
         putValue(SHORT_DESCRIPTION, tr("Reverse the order of the relation members"));
-        new ImageProvider("dialogs/relation", "reverse").getResource().attachImageIcon(this, true);
+        new ImageProvider("dialogs/", "reverse").getResource().attachImageIcon(this, true);
         putValue(NAME, tr("Reverse"));
         updateEnabledState();
Index: trunk/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java	(revision 15226)
@@ -88,4 +88,5 @@
 import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
 import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.gui.util.ReorderableTableModel;
 import org.openstreetmap.josm.gui.util.TableHelper;
 import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
@@ -627,5 +628,5 @@
      * Table model of active sources.
      */
-    protected class ActiveSourcesModel extends AbstractTableModel {
+    protected class ActiveSourcesModel extends AbstractTableModel implements ReorderableTableModel<SourceEntry> {
         private transient List<SourceEntry> data;
         private final DefaultListSelectionModel selectionModel;
@@ -767,31 +768,17 @@
         }
 
-        public boolean canMove(int i) {
-            int[] sel = tblActiveSources.getSelectedRows();
-            if (sel.length == 0)
-                return false;
-            if (i < 0)
-                return sel[0] >= -i;
-                else if (i > 0)
-                    return sel[sel.length-1] <= getRowCount()-1 - i;
-                else
-                    return true;
-        }
-
-        public void move(int i) {
-            if (!canMove(i)) return;
-            int[] sel = tblActiveSources.getSelectedRows();
-            for (int row: sel) {
-                SourceEntry t1 = data.get(row);
-                SourceEntry t2 = data.get(row + i);
-                data.set(row, t2);
-                data.set(row + i, t1);
-            }
-            selectionModel.setValueIsAdjusting(true);
-            selectionModel.clearSelection();
-            for (int row: sel) {
-                selectionModel.addSelectionInterval(row + i, row + i);
-            }
-            selectionModel.setValueIsAdjusting(false);
+        @Override
+        public DefaultListSelectionModel getSelectionModel() {
+            return selectionModel;
+        }
+
+        @Override
+        public SourceEntry getValue(int index) {
+            return data.get(index);
+        }
+
+        @Override
+        public SourceEntry setValue(int index, SourceEntry value) {
+            return data.set(index, value);
         }
     }
Index: trunk/src/org/openstreetmap/josm/gui/util/ReorderableTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/util/ReorderableTableModel.java	(revision 15226)
+++ trunk/src/org/openstreetmap/josm/gui/util/ReorderableTableModel.java	(revision 15226)
@@ -0,0 +1,132 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import javax.swing.ListSelectionModel;
+import javax.swing.table.TableModel;
+
+import org.openstreetmap.josm.data.ReorderableModel;
+
+/**
+ * Defines a table model that can be reordered.
+ * @param <T> item type
+ * @since 15226
+ */
+public interface ReorderableTableModel<T> extends TableModel, ReorderableModel<T> {
+
+    /**
+     * Returns the selection model.
+     * @return the selection model (never null)
+     */
+    ListSelectionModel getSelectionModel();
+
+    /**
+     * Returns an array of all of the selected indices in the selection model, in increasing order.
+     * @return an array of all of the selected indices in the selection model, in increasing order
+     */
+    default int[] getSelectedIndices() {
+        return TableHelper.getSelectedIndices(getSelectionModel());
+    }
+
+    /**
+     * Checks that the currently selected range of rows can be moved by a number of positions.
+     * @param delta negative or positive delta
+     * @return {@code true} if rows can be moved
+     */
+    default boolean canMove(int delta) {
+        return canMove(delta, this::getRowCount, getSelectedIndices());
+    }
+
+    /**
+     * Checks that the currently selected range of rows can be moved up.
+     * @return {@code true} if rows can be moved up
+     */
+    default boolean canMoveUp() {
+        return canMoveUp(getSelectedIndices());
+    }
+
+    /**
+     * Checks that a range of rows can be moved up.
+     * @param rows indexes of rows to move up
+     * @return {@code true} if rows can be moved up
+     */
+    default boolean canMoveUp(int... rows) {
+        return canMoveUp(this::getRowCount, rows);
+    }
+
+    /**
+     * Checks that the currently selected range of rows can be moved down.
+     * @return {@code true} if rows can be moved down
+     */
+    default boolean canMoveDown() {
+        return canMoveDown(getSelectedIndices());
+    }
+
+    /**
+     * Checks that a range of rows can be moved down.
+     * @param rows indexes of rows to move down
+     * @return {@code true} if rows can be moved down
+     */
+    default boolean canMoveDown(int... rows) {
+        return canMoveDown(this::getRowCount, rows);
+    }
+
+    /**
+     * Move up selected rows, if possible.
+     * @return {@code true} if the move was performed
+     * @see #canMoveUp
+     */
+    default boolean moveUp() {
+        return moveUp(getSelectedIndices());
+    }
+
+    /**
+     * Move up selected rows, if possible.
+     * @param selectedRows rows to move up
+     * @return {@code true} if the move was performed
+     * @see #canMoveUp
+     */
+    default boolean moveUp(int... selectedRows) {
+        return move(-1, selectedRows);
+    }
+
+    /**
+     * Move down selected rows, if possible.
+     * @return {@code true} if the move was performed
+     * @see #canMoveDown
+     */
+    default boolean moveDown() {
+        return moveDown(getSelectedIndices());
+    }
+
+    /**
+     * Move down selected rows by 1 position, if possible.
+     * @param selectedRows rows to move down
+     * @return {@code true} if the move was performed
+     * @see #canMoveDown
+     */
+    default boolean moveDown(int... selectedRows) {
+        return move(1, selectedRows);
+    }
+
+    /**
+     * Move selected rows by any number of positions, if possible.
+     * @param delta negative or positive delta
+     * @param selectedRows rows to move
+     * @return {@code true} if the move was performed
+     * @see #canMove
+     */
+    default boolean move(int delta, int... selectedRows) {
+        if (!canMove(delta, this::getRowCount, selectedRows))
+            return false;
+        if (!doMove(delta, selectedRows))
+            return false;
+        final ListSelectionModel selectionModel = getSelectionModel();
+        selectionModel.setValueIsAdjusting(true);
+        selectionModel.clearSelection();
+        for (int row: selectedRows) {
+            selectionModel.addSelectionInterval(row + delta, row + delta);
+        }
+        selectionModel.setValueIsAdjusting(false);
+        return true;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/util/SortableTableModel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/util/SortableTableModel.java	(revision 15226)
+++ trunk/src/org/openstreetmap/josm/gui/util/SortableTableModel.java	(revision 15226)
@@ -0,0 +1,13 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import org.openstreetmap.josm.data.SortableModel;
+
+/**
+ * Defines a table model that can be sorted.
+ * @param <T> item type
+ * @since 15226
+ */
+public interface SortableTableModel<T> extends ReorderableTableModel<T>, SortableModel<T> {
+
+}
Index: trunk/src/org/openstreetmap/josm/gui/util/TableHelper.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/util/TableHelper.java	(revision 15225)
+++ trunk/src/org/openstreetmap/josm/gui/util/TableHelper.java	(revision 15226)
@@ -5,6 +5,12 @@
 
 import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListDataEvent;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableCellRenderer;
 import javax.swing.table.TableColumn;
+
+import org.openstreetmap.josm.gui.dialogs.IEnabledStateUpdating;
 
 /**
@@ -16,4 +22,30 @@
     private TableHelper() {
         // Hide default constructor for utils classes
+    }
+
+    /**
+     * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that
+     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
+     * on every {@link ListSelectionEvent}.
+     *
+     * @param listener  the listener
+     * @param listSelectionModel  the source emitting {@link ListSelectionEvent}s
+     * @since 15226
+     */
+    public static void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) {
+        listSelectionModel.addListSelectionListener(e -> listener.updateEnabledState());
+    }
+
+    /**
+     * Wires <code>listener</code> to <code>listModel</code> in such a way, that
+     * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()}
+     * on every {@link ListDataEvent}.
+     *
+     * @param listener the listener
+     * @param listModel the source emitting {@link ListDataEvent}s
+     * @since 15226
+     */
+    public static void adaptTo(final IEnabledStateUpdating listener, AbstractTableModel listModel) {
+        listModel.addTableModelListener(e -> listener.updateEnabledState());
     }
 
@@ -83,3 +115,35 @@
         }
     }
+
+    /**
+     * Returns an array of all of the selected indices in the selection model, in increasing order.
+     * Unfortunately this method is not available in OpenJDK before version 11, see
+     * https://bugs.openjdk.java.net/browse/JDK-8199395
+     * Code taken from OpenJDK 11. To be removed when we switch to Java 11 or later.
+     *
+     * @param selectionModel list selection model.
+     *
+     * @return all of the selected indices, in increasing order,
+     *         or an empty array if nothing is selected
+     * @since 15226
+     */
+    public static int[] getSelectedIndices(ListSelectionModel selectionModel) {
+        int iMin = selectionModel.getMinSelectionIndex();
+        int iMax = selectionModel.getMaxSelectionIndex();
+
+        if (iMin < 0 || iMax < 0) {
+            return new int[0];
+        }
+
+        int[] rvTmp = new int[1 + iMax - iMin];
+        int n = 0;
+        for (int i = iMin; i <= iMax; i++) {
+            if (selectionModel.isSelectedIndex(i)) {
+                rvTmp[n++] = i;
+            }
+        }
+        int[] rv = new int[n];
+        System.arraycopy(rvTmp, 0, rv, 0, n);
+        return rv;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/tools/ArrayUtils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/ArrayUtils.java	(revision 15226)
+++ trunk/src/org/openstreetmap/josm/tools/ArrayUtils.java	(revision 15226)
@@ -0,0 +1,47 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.tools;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Utility methods for arrays.
+ * @since 15226
+ */
+public final class ArrayUtils {
+
+    /**
+     * Utility class
+     */
+    private ArrayUtils() {
+        // Hide default constructor for utility classes
+    }
+
+    /**
+     * Converts an array of int to a list of Integer.
+     * @param array array of int
+     * @return list of Integer
+     */
+    public static List<Integer> toList(int[] array) {
+        return Arrays.stream(array).boxed().collect(Collectors.toList());
+    }
+
+    /**
+     * Converts an array of long to a list of Long.
+     * @param array array of long
+     * @return list of Long
+     */
+    public static List<Long> toList(long[] array) {
+        return Arrays.stream(array).boxed().collect(Collectors.toList());
+    }
+
+    /**
+     * Converts an array of double to a list of Double.
+     * @param array array of double
+     * @return list of Double
+     */
+    public static List<Double> toList(double[] array) {
+        return Arrays.stream(array).boxed().collect(Collectors.toList());
+    }
+}
